* feat(access-token): add ephemeral access-token resource Signed-off-by: Mauritz Uphoff <mauritz.uphoff@stackit.cloud>
132 lines
4.7 KiB
Go
132 lines
4.7 KiB
Go
package access_token
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/terraform-plugin-framework/ephemeral"
|
|
"github.com/hashicorp/terraform-plugin-framework/ephemeral/schema"
|
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
|
"github.com/stackitcloud/stackit-sdk-go/core/auth"
|
|
"github.com/stackitcloud/stackit-sdk-go/core/clients"
|
|
"github.com/stackitcloud/stackit-sdk-go/core/config"
|
|
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
|
|
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
|
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features"
|
|
)
|
|
|
|
var (
|
|
_ ephemeral.EphemeralResource = &accessTokenEphemeralResource{}
|
|
_ ephemeral.EphemeralResourceWithConfigure = &accessTokenEphemeralResource{}
|
|
)
|
|
|
|
func NewAccessTokenEphemeralResource() ephemeral.EphemeralResource {
|
|
return &accessTokenEphemeralResource{}
|
|
}
|
|
|
|
type accessTokenEphemeralResource struct {
|
|
keyAuthConfig config.Configuration
|
|
}
|
|
|
|
func (e *accessTokenEphemeralResource) Configure(ctx context.Context, req ephemeral.ConfigureRequest, resp *ephemeral.ConfigureResponse) {
|
|
ephemeralProviderData, ok := conversion.ParseEphemeralProviderData(ctx, req.ProviderData, &resp.Diagnostics)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
features.CheckBetaResourcesEnabled(
|
|
ctx,
|
|
&ephemeralProviderData.ProviderData,
|
|
&resp.Diagnostics,
|
|
"stackit_access_token", "ephemeral_resource",
|
|
)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
e.keyAuthConfig = config.Configuration{
|
|
ServiceAccountKey: ephemeralProviderData.ServiceAccountKey,
|
|
ServiceAccountKeyPath: ephemeralProviderData.ServiceAccountKeyPath,
|
|
PrivateKeyPath: ephemeralProviderData.PrivateKey,
|
|
PrivateKey: ephemeralProviderData.PrivateKeyPath,
|
|
TokenCustomUrl: ephemeralProviderData.TokenCustomEndpoint,
|
|
}
|
|
}
|
|
|
|
type ephemeralTokenModel struct {
|
|
AccessToken types.String `tfsdk:"access_token"`
|
|
}
|
|
|
|
func (e *accessTokenEphemeralResource) Metadata(_ context.Context, req ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) {
|
|
resp.TypeName = req.ProviderTypeName + "_access_token"
|
|
}
|
|
|
|
func (e *accessTokenEphemeralResource) Schema(_ context.Context, _ ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) {
|
|
description := features.AddBetaDescription(
|
|
fmt.Sprintf(
|
|
"%s\n\n%s",
|
|
"Ephemeral resource that generates a short-lived STACKIT access token (JWT) using a service account key. "+
|
|
"A new token is generated each time the resource is evaluated, and it remains consistent for the duration of a Terraform operation. "+
|
|
"If a private key is not explicitly provided, the provider attempts to extract it from the service account key instead. "+
|
|
"Access tokens generated from service account keys expire after 60 minutes.",
|
|
"~> Service account key credentials must be configured either in the STACKIT provider configuration or via environment variables (see example below). "+
|
|
"If any other authentication method is configured, this ephemeral resource will fail with an error.",
|
|
),
|
|
core.EphemeralResource,
|
|
)
|
|
|
|
resp.Schema = schema.Schema{
|
|
Description: description,
|
|
Attributes: map[string]schema.Attribute{
|
|
"access_token": schema.StringAttribute{
|
|
Description: "JWT access token for STACKIT API authentication.",
|
|
Computed: true,
|
|
Sensitive: true,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (e *accessTokenEphemeralResource) Open(ctx context.Context, req ephemeral.OpenRequest, resp *ephemeral.OpenResponse) {
|
|
var model ephemeralTokenModel
|
|
|
|
resp.Diagnostics.Append(req.Config.Get(ctx, &model)...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
accessToken, err := getAccessToken(&e.keyAuthConfig)
|
|
if err != nil {
|
|
core.LogAndAddError(ctx, &resp.Diagnostics, "Access token generation failed", err.Error())
|
|
return
|
|
}
|
|
|
|
model.AccessToken = types.StringValue(accessToken)
|
|
resp.Diagnostics.Append(resp.Result.Set(ctx, model)...)
|
|
}
|
|
|
|
// getAccessToken initializes authentication using the provided config and returns an access token via the KeyFlow mechanism.
|
|
func getAccessToken(keyAuthConfig *config.Configuration) (string, error) {
|
|
roundTripper, err := auth.KeyAuth(keyAuthConfig)
|
|
if err != nil {
|
|
return "", fmt.Errorf(
|
|
"failed to initialize authentication: %w. "+
|
|
"Make sure service account credentials are configured either in the provider configuration or via environment variables",
|
|
err,
|
|
)
|
|
}
|
|
|
|
// Type assert to access token functionality
|
|
client, ok := roundTripper.(*clients.KeyFlow)
|
|
if !ok {
|
|
return "", fmt.Errorf("internal error: expected *clients.KeyFlow, but received a different implementation of http.RoundTripper")
|
|
}
|
|
|
|
// Retrieve the access token
|
|
accessToken, err := client.GetAccessToken()
|
|
if err != nil {
|
|
return "", fmt.Errorf("error obtaining access token: %w", err)
|
|
}
|
|
|
|
return accessToken, nil
|
|
}
|