diff --git a/.changelog/34839.txt b/.changelog/34839.txt new file mode 100644 index 000000000000..d099346ca288 --- /dev/null +++ b/.changelog/34839.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_ssoadmin_trusted_token_issuer +``` diff --git a/internal/service/ssoadmin/exports_test.go b/internal/service/ssoadmin/exports_test.go index 4ab0999ae72d..3ff2a1929a1a 100644 --- a/internal/service/ssoadmin/exports_test.go +++ b/internal/service/ssoadmin/exports_test.go @@ -8,8 +8,10 @@ var ( ResourceApplication = newResourceApplication ResourceApplicationAssignment = newResourceApplicationAssignment ResourceApplicationAssignmentConfiguration = newResourceApplicationAssignmentConfiguration + ResourceTrustedTokenIssuer = newResourceTrustedTokenIssuer FindApplicationByID = findApplicationByID FindApplicationAssignmentByID = findApplicationAssignmentByID FindApplicationAssignmentConfigurationByID = findApplicationAssignmentConfigurationByID + FindTrustedTokenIssuerByARN = findTrustedTokenIssuerByARN ) diff --git a/internal/service/ssoadmin/service_package_gen.go b/internal/service/ssoadmin/service_package_gen.go index 4c91e986ad3e..6ad0c85e1de2 100644 --- a/internal/service/ssoadmin/service_package_gen.go +++ b/internal/service/ssoadmin/service_package_gen.go @@ -48,6 +48,11 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic Factory: newResourceApplicationAssignmentConfiguration, Name: "Application Assignment Configuration", }, + { + Factory: newResourceTrustedTokenIssuer, + Name: "Trusted Token Issuer", + Tags: &types.ServicePackageResourceTags{}, + }, } } diff --git a/internal/service/ssoadmin/trusted_token_issuer.go b/internal/service/ssoadmin/trusted_token_issuer.go new file mode 100644 index 000000000000..98d5a67198c4 --- /dev/null +++ b/internal/service/ssoadmin/trusted_token_issuer.go @@ -0,0 +1,531 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssoadmin + +import ( + "context" + "errors" + "fmt" + "log" + "strings" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ssoadmin" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssoadmin/types" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/enum" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkResource(name="Trusted Token Issuer") +// @Tags +func newResourceTrustedTokenIssuer(_ context.Context) (resource.ResourceWithConfigure, error) { + return &resourceTrustedTokenIssuer{}, nil +} + +const ( + ResNameTrustedTokenIssuer = "Trusted Token Issuer" +) + +type resourceTrustedTokenIssuer struct { + framework.ResourceWithConfigure +} + +func (r *resourceTrustedTokenIssuer) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "aws_ssoadmin_trusted_token_issuer" +} + +func (r *resourceTrustedTokenIssuer) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "arn": framework.ARNAttributeComputedOnly(), + "client_token": schema.StringAttribute{ + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "id": framework.IDAttribute(), + "instance_arn": schema.StringAttribute{ + CustomType: fwtypes.ARNType, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "name": schema.StringAttribute{ + Required: true, + }, + "trusted_token_issuer_type": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + enum.FrameworkValidate[awstypes.TrustedTokenIssuerType](), + }, + }, + + names.AttrTags: tftags.TagsAttribute(), + names.AttrTagsAll: tftags.TagsAttributeComputedOnly(), + }, + Blocks: map[string]schema.Block{ + "trusted_token_issuer_configuration": schema.ListNestedBlock{ + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + listvalidator.IsRequired(), + }, + NestedObject: schema.NestedBlockObject{ + Blocks: map[string]schema.Block{ + "oidc_jwt_configuration": schema.ListNestedBlock{ + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + listvalidator.IsRequired(), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "claim_attribute_path": schema.StringAttribute{ + Required: true, + }, + "identity_store_attribute_path": schema.StringAttribute{ + Required: true, + }, + "issuer_url": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ // Not part of OidcJwtUpdateConfiguration struct, have to recreate at change + stringplanmodifier.RequiresReplace(), + }, + }, + "jwks_retrieval_option": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + enum.FrameworkValidate[awstypes.JwksRetrievalOption](), + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func (r *resourceTrustedTokenIssuer) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + conn := r.Meta().SSOAdminClient(ctx) + + var plan resourceTrustedTokenIssuerData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + in := &ssoadmin.CreateTrustedTokenIssuerInput{ + InstanceArn: aws.String(plan.InstanceARN.ValueString()), + Name: aws.String(plan.Name.ValueString()), + TrustedTokenIssuerType: awstypes.TrustedTokenIssuerType(plan.TrustedTokenIssuerType.ValueString()), + Tags: getTagsIn(ctx), + } + + if !plan.ClientToken.IsNull() { + in.ClientToken = aws.String(plan.ClientToken.ValueString()) + } + + if !plan.TrustedTokenIssuerConfiguration.IsNull() { + var tfList []TrustedTokenIssuerConfigurationData + resp.Diagnostics.Append(plan.TrustedTokenIssuerConfiguration.ElementsAs(ctx, &tfList, false)...) + if resp.Diagnostics.HasError() { + return + } + + trustedTokenIssuerConfiguration, d := expandTrustedTokenIssuerConfiguration(ctx, tfList) + resp.Diagnostics.Append(d...) + if resp.Diagnostics.HasError() { + return + } + in.TrustedTokenIssuerConfiguration = trustedTokenIssuerConfiguration + } + + out, err := conn.CreateTrustedTokenIssuer(ctx, in) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSOAdmin, create.ErrActionCreating, ResNameTrustedTokenIssuer, plan.Name.String(), err), + err.Error(), + ) + return + } + + if out == nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSOAdmin, create.ErrActionCreating, ResNameTrustedTokenIssuer, plan.Name.String(), nil), + errors.New("empty output").Error(), + ) + return + } + + plan.ARN = flex.StringToFramework(ctx, out.TrustedTokenIssuerArn) + plan.ID = flex.StringToFramework(ctx, out.TrustedTokenIssuerArn) + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func (r *resourceTrustedTokenIssuer) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + conn := r.Meta().SSOAdminClient(ctx) + + var state resourceTrustedTokenIssuerData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + out, err := findTrustedTokenIssuerByARN(ctx, conn, state.ID.ValueString()) + if tfresource.NotFound(err) { + resp.State.RemoveResource(ctx) + return + } + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSOAdmin, create.ErrActionSetting, ResNameTrustedTokenIssuer, state.ID.String(), err), + err.Error(), + ) + return + } + + instanceARN, _ := TrustedTokenIssuerParseInstanceARN(r.Meta(), aws.ToString(out.TrustedTokenIssuerArn)) + + state.ARN = flex.StringToFramework(ctx, out.TrustedTokenIssuerArn) + state.Name = flex.StringToFramework(ctx, out.Name) + state.ID = flex.StringToFramework(ctx, out.TrustedTokenIssuerArn) + state.InstanceARN = flex.StringToFrameworkARN(ctx, aws.String(instanceARN)) + state.TrustedTokenIssuerType = flex.StringValueToFramework(ctx, out.TrustedTokenIssuerType) + + trustedTokenIssuerConfiguration, d := flattenTrustedTokenIssuerConfiguration(ctx, out.TrustedTokenIssuerConfiguration) + resp.Diagnostics.Append(d...) + state.TrustedTokenIssuerConfiguration = trustedTokenIssuerConfiguration + + // listTags requires both trusted token issuer and instance ARN, so must be called + // explicitly rather than with transparent tagging. + tags, err := listTags(ctx, conn, state.ARN.ValueString(), state.InstanceARN.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSOAdmin, create.ErrActionSetting, ResNameTrustedTokenIssuer, state.ID.String(), err), + err.Error(), + ) + return + } + setTagsOut(ctx, Tags(tags)) + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *resourceTrustedTokenIssuer) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + conn := r.Meta().SSOAdminClient(ctx) + + var plan, state resourceTrustedTokenIssuerData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + if !plan.Name.Equal(state.Name) || !plan.TrustedTokenIssuerConfiguration.Equal(state.TrustedTokenIssuerConfiguration) { + in := &ssoadmin.UpdateTrustedTokenIssuerInput{ + TrustedTokenIssuerArn: aws.String(plan.ID.ValueString()), + } + + if !plan.Name.IsNull() { + in.Name = aws.String(plan.Name.ValueString()) + } + + if !plan.TrustedTokenIssuerConfiguration.IsNull() { + var tfList []TrustedTokenIssuerConfigurationData + resp.Diagnostics.Append(plan.TrustedTokenIssuerConfiguration.ElementsAs(ctx, &tfList, false)...) + if resp.Diagnostics.HasError() { + return + } + + trustedTokenIssuerUpdateConfiguration, d := expandTrustedTokenIssuerUpdateConfiguration(ctx, tfList) + resp.Diagnostics.Append(d...) + if resp.Diagnostics.HasError() { + return + } + in.TrustedTokenIssuerConfiguration = trustedTokenIssuerUpdateConfiguration + } + + out, err := conn.UpdateTrustedTokenIssuer(ctx, in) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSOAdmin, create.ErrActionUpdating, ResNameTrustedTokenIssuer, plan.ID.String(), err), + err.Error(), + ) + return + } + if out == nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSOAdmin, create.ErrActionUpdating, ResNameTrustedTokenIssuer, plan.ID.String(), nil), + errors.New("empty output").Error(), + ) + return + } + } + + // updateTags requires both trusted token issuer and instance ARN, so must be called + // explicitly rather than with transparent tagging. + if oldTagsAll, newTagsAll := state.TagsAll, plan.TagsAll; !newTagsAll.Equal(oldTagsAll) { + if err := updateTags(ctx, conn, plan.ID.ValueString(), plan.InstanceARN.ValueString(), oldTagsAll, newTagsAll); err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSOAdmin, create.ErrActionUpdating, ResNameTrustedTokenIssuer, plan.ID.String(), err), + err.Error(), + ) + return + } + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *resourceTrustedTokenIssuer) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + conn := r.Meta().SSOAdminClient(ctx) + + var state resourceTrustedTokenIssuerData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + in := &ssoadmin.DeleteTrustedTokenIssuerInput{ + TrustedTokenIssuerArn: aws.String(state.ID.ValueString()), + } + + _, err := conn.DeleteTrustedTokenIssuer(ctx, in) + if err != nil { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSOAdmin, create.ErrActionDeleting, ResNameTrustedTokenIssuer, state.ID.String(), err), + err.Error(), + ) + return + } +} + +func (r *resourceTrustedTokenIssuer) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +func (r *resourceTrustedTokenIssuer) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { + r.SetTagsAll(ctx, req, resp) +} + +func findTrustedTokenIssuerByARN(ctx context.Context, conn *ssoadmin.Client, arn string) (*ssoadmin.DescribeTrustedTokenIssuerOutput, error) { + in := &ssoadmin.DescribeTrustedTokenIssuerInput{ + TrustedTokenIssuerArn: aws.String(arn), + } + + out, err := conn.DescribeTrustedTokenIssuer(ctx, in) + if err != nil { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: in, + } + } + + return nil, err + } + + if out == nil { + return nil, tfresource.NewEmptyResultError(in) + } + + return out, nil +} + +func expandTrustedTokenIssuerConfiguration(ctx context.Context, tfList []TrustedTokenIssuerConfigurationData) (awstypes.TrustedTokenIssuerConfiguration, diag.Diagnostics) { + var diags diag.Diagnostics + + if len(tfList) == 0 { + return nil, diags + } + tfObj := tfList[0] + + var OIDCJWTConfigurationData []OIDCJWTConfigurationData + diags.Append(tfObj.OIDCJWTConfiguration.ElementsAs(ctx, &OIDCJWTConfigurationData, false)...) + + apiObject := &awstypes.TrustedTokenIssuerConfigurationMemberOidcJwtConfiguration{ + Value: *expandOIDCJWTConfiguration(OIDCJWTConfigurationData), + } + + return apiObject, diags +} + +func expandOIDCJWTConfiguration(tfList []OIDCJWTConfigurationData) *awstypes.OidcJwtConfiguration { + if len(tfList) == 0 { + return nil + } + + tfObj := tfList[0] + + apiObject := &awstypes.OidcJwtConfiguration{ + ClaimAttributePath: aws.String(tfObj.ClaimAttributePath.ValueString()), + IdentityStoreAttributePath: aws.String(tfObj.IdentityStoreAttributePath.ValueString()), + IssuerUrl: aws.String(tfObj.IssuerUrl.ValueString()), + JwksRetrievalOption: awstypes.JwksRetrievalOption(tfObj.JWKSRetrievalOption.ValueString()), + } + + return apiObject +} + +func expandTrustedTokenIssuerUpdateConfiguration(ctx context.Context, tfList []TrustedTokenIssuerConfigurationData) (awstypes.TrustedTokenIssuerUpdateConfiguration, diag.Diagnostics) { + var diags diag.Diagnostics + + if len(tfList) == 0 { + return nil, diags + } + tfObj := tfList[0] + + var OIDCJWTConfigurationData []OIDCJWTConfigurationData + diags.Append(tfObj.OIDCJWTConfiguration.ElementsAs(ctx, &OIDCJWTConfigurationData, false)...) + + apiObject := &awstypes.TrustedTokenIssuerUpdateConfigurationMemberOidcJwtConfiguration{ + Value: *expandOIDCJWTUpdateConfiguration(OIDCJWTConfigurationData), + } + + return apiObject, diags +} + +func expandOIDCJWTUpdateConfiguration(tfList []OIDCJWTConfigurationData) *awstypes.OidcJwtUpdateConfiguration { + if len(tfList) == 0 { + return nil + } + + tfObj := tfList[0] + + apiObject := &awstypes.OidcJwtUpdateConfiguration{ + ClaimAttributePath: aws.String(tfObj.ClaimAttributePath.ValueString()), + IdentityStoreAttributePath: aws.String(tfObj.IdentityStoreAttributePath.ValueString()), + JwksRetrievalOption: awstypes.JwksRetrievalOption(tfObj.JWKSRetrievalOption.ValueString()), + } + + return apiObject +} + +func flattenTrustedTokenIssuerConfiguration(ctx context.Context, apiObject awstypes.TrustedTokenIssuerConfiguration) (types.List, diag.Diagnostics) { + var diags diag.Diagnostics + elemType := types.ObjectType{AttrTypes: TrustedTokenIssuerConfigurationAttrTypes} + + if apiObject == nil { + return types.ListNull(elemType), diags + } + + obj := map[string]attr.Value{} + + switch v := apiObject.(type) { + case *awstypes.TrustedTokenIssuerConfigurationMemberOidcJwtConfiguration: + oidcJWTConfiguration, d := flattenOIDCJWTConfiguration(ctx, &v.Value) + obj["oidc_jwt_configuration"] = oidcJWTConfiguration + diags.Append(d...) + default: + log.Println("union is nil or unknown type") + } + + objVal, d := types.ObjectValue(TrustedTokenIssuerConfigurationAttrTypes, obj) + diags.Append(d...) + + listVal, d := types.ListValue(elemType, []attr.Value{objVal}) + diags.Append(d...) + + return listVal, diags +} + +func flattenOIDCJWTConfiguration(ctx context.Context, apiObject *awstypes.OidcJwtConfiguration) (types.List, diag.Diagnostics) { + var diags diag.Diagnostics + elemType := types.ObjectType{AttrTypes: OIDCJWTConfigurationAttrTypes} + + if apiObject == nil { + return types.ListNull(elemType), diags + } + + obj := map[string]attr.Value{ + "claim_attribute_path": flex.StringToFramework(ctx, apiObject.ClaimAttributePath), + "identity_store_attribute_path": flex.StringToFramework(ctx, apiObject.IdentityStoreAttributePath), + "issuer_url": flex.StringToFramework(ctx, apiObject.IssuerUrl), + "jwks_retrieval_option": flex.StringValueToFramework(ctx, apiObject.JwksRetrievalOption), + } + + objVal, d := types.ObjectValue(OIDCJWTConfigurationAttrTypes, obj) + diags.Append(d...) + + listVal, d := types.ListValue(elemType, []attr.Value{objVal}) + diags.Append(d...) + + return listVal, diags +} + +// Instance ARN is not returned by DescribeTrustedTokenIssuer but is needed for schema consistency when importing and tagging. +// Instance ARN can be extracted from the Trusted Token Issuer ARN. +func TrustedTokenIssuerParseInstanceARN(conn *conns.AWSClient, id string) (string, diag.Diagnostics) { + var diags diag.Diagnostics + parts := strings.Split(id, "/") + + if len(parts) == 3 && parts[0] != "" && parts[1] != "" && parts[2] != "" { + return fmt.Sprintf("arn:%s:sso:::instance/%s", conn.Partition, parts[1]), diags + } + + return "", diags +} + +type resourceTrustedTokenIssuerData struct { + ARN types.String `tfsdk:"arn"` + ClientToken types.String `tfsdk:"client_token"` + ID types.String `tfsdk:"id"` + InstanceARN fwtypes.ARN `tfsdk:"instance_arn"` + Name types.String `tfsdk:"name"` + TrustedTokenIssuerConfiguration types.List `tfsdk:"trusted_token_issuer_configuration"` + TrustedTokenIssuerType types.String `tfsdk:"trusted_token_issuer_type"` + Tags types.Map `tfsdk:"tags"` + TagsAll types.Map `tfsdk:"tags_all"` +} + +type TrustedTokenIssuerConfigurationData struct { + OIDCJWTConfiguration types.List `tfsdk:"oidc_jwt_configuration"` +} + +type OIDCJWTConfigurationData struct { + ClaimAttributePath types.String `tfsdk:"claim_attribute_path"` + IdentityStoreAttributePath types.String `tfsdk:"identity_store_attribute_path"` + IssuerUrl types.String `tfsdk:"issuer_url"` + JWKSRetrievalOption types.String `tfsdk:"jwks_retrieval_option"` +} + +var TrustedTokenIssuerConfigurationAttrTypes = map[string]attr.Type{ + "oidc_jwt_configuration": types.ListType{ElemType: types.ObjectType{AttrTypes: OIDCJWTConfigurationAttrTypes}}, +} + +var OIDCJWTConfigurationAttrTypes = map[string]attr.Type{ + "claim_attribute_path": types.StringType, + "identity_store_attribute_path": types.StringType, + "issuer_url": types.StringType, + "jwks_retrieval_option": types.StringType, +} diff --git a/internal/service/ssoadmin/trusted_token_issuer_test.go b/internal/service/ssoadmin/trusted_token_issuer_test.go new file mode 100644 index 000000000000..010e0b302e1e --- /dev/null +++ b/internal/service/ssoadmin/trusted_token_issuer_test.go @@ -0,0 +1,306 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssoadmin_test + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/ssoadmin" + "github.com/aws/aws-sdk-go-v2/service/ssoadmin/types" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + tfssoadmin "github.com/hashicorp/terraform-provider-aws/internal/service/ssoadmin" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccSSOAdminTrustedTokenIssuer_basic(t *testing.T) { + ctx := acctest.Context(t) + var application ssoadmin.DescribeTrustedTokenIssuerOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ssoadmin_trusted_token_issuer.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckSSOAdminInstances(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SSOAdminEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrustedTokenIssuerDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccTrustedTokenIssuerConfigBase_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrustedTokenIssuerExists(ctx, resourceName, &application), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "trusted_token_issuer_type", string(types.TrustedTokenIssuerTypeOidcJwt)), + resource.TestCheckResourceAttr(resourceName, "trusted_token_issuer_configuration.0.oidc_jwt_configuration.0.claim_attribute_path", "email"), + resource.TestCheckResourceAttr(resourceName, "trusted_token_issuer_configuration.0.oidc_jwt_configuration.0.identity_store_attribute_path", "emails.value"), + resource.TestCheckResourceAttr(resourceName, "trusted_token_issuer_configuration.0.oidc_jwt_configuration.0.issuer_url", "https://example.com"), + resource.TestCheckResourceAttr(resourceName, "trusted_token_issuer_configuration.0.oidc_jwt_configuration.0.jwks_retrieval_option", string(types.JwksRetrievalOptionOpenIdDiscovery)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccSSOAdminTrustedTokenIssuer_update(t *testing.T) { + ctx := acctest.Context(t) + var application ssoadmin.DescribeTrustedTokenIssuerOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rNameUpdated := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ssoadmin_trusted_token_issuer.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckSSOAdminInstances(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SSOAdminEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrustedTokenIssuerDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccTrustedTokenIssuerConfigBase_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrustedTokenIssuerExists(ctx, resourceName, &application), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "trusted_token_issuer_configuration.0.oidc_jwt_configuration.0.claim_attribute_path", "email"), + resource.TestCheckResourceAttr(resourceName, "trusted_token_issuer_configuration.0.oidc_jwt_configuration.0.identity_store_attribute_path", "emails.value"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccTrustedTokenIssuerConfigBase_basicUpdated(rNameUpdated, "name", "userName"), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrustedTokenIssuerExists(ctx, resourceName, &application), + resource.TestCheckResourceAttr(resourceName, "name", rNameUpdated), + resource.TestCheckResourceAttr(resourceName, "trusted_token_issuer_configuration.0.oidc_jwt_configuration.0.claim_attribute_path", "name"), + resource.TestCheckResourceAttr(resourceName, "trusted_token_issuer_configuration.0.oidc_jwt_configuration.0.identity_store_attribute_path", "userName"), + ), + }, + }, + }) +} + +func TestAccSSOAdminTrustedTokenIssuer_disappears(t *testing.T) { + ctx := acctest.Context(t) + var application ssoadmin.DescribeTrustedTokenIssuerOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ssoadmin_trusted_token_issuer.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckSSOAdminInstances(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SSOAdminEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrustedTokenIssuerDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccTrustedTokenIssuerConfigBase_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrustedTokenIssuerExists(ctx, resourceName, &application), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfssoadmin.ResourceTrustedTokenIssuer, resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccSSOAdminTrustedTokenIssuer_tags(t *testing.T) { + ctx := acctest.Context(t) + var application ssoadmin.DescribeTrustedTokenIssuerOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ssoadmin_trusted_token_issuer.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckSSOAdminInstances(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SSOAdminEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrustedTokenIssuerDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccTrustedTokenIssuerConfigBase_tags(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrustedTokenIssuerExists(ctx, resourceName, &application), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccTrustedTokenIssuerConfigBase_tags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrustedTokenIssuerExists(ctx, resourceName, &application), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccTrustedTokenIssuerConfigBase_tags(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrustedTokenIssuerExists(ctx, resourceName, &application), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func testAccCheckTrustedTokenIssuerExists(ctx context.Context, name string, trustedTokenIssuer *ssoadmin.DescribeTrustedTokenIssuerOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.SSOAdmin, create.ErrActionCheckingExistence, tfssoadmin.ResNameTrustedTokenIssuer, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.SSOAdmin, create.ErrActionCheckingExistence, tfssoadmin.ResNameTrustedTokenIssuer, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).SSOAdminClient(ctx) + resp, err := tfssoadmin.FindTrustedTokenIssuerByARN(ctx, conn, rs.Primary.ID) + if err != nil { + return create.Error(names.SSOAdmin, create.ErrActionCheckingExistence, tfssoadmin.ResNameTrustedTokenIssuer, rs.Primary.ID, err) + } + + *trustedTokenIssuer = *resp + + return nil + } +} + +func testAccCheckTrustedTokenIssuerDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).SSOAdminClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ssoadmin_trusted_token_issuer" { + continue + } + + _, err := tfssoadmin.FindTrustedTokenIssuerByARN(ctx, conn, rs.Primary.ID) + if errs.IsA[*types.ResourceNotFoundException](err) { + return nil + } + if err != nil { + return create.Error(names.SSOAdmin, create.ErrActionCheckingDestroyed, tfssoadmin.ResNameTrustedTokenIssuer, rs.Primary.ID, err) + } + + return create.Error(names.SSOAdmin, create.ErrActionCheckingDestroyed, tfssoadmin.ResNameTrustedTokenIssuer, rs.Primary.ID, errors.New("not destroyed")) + } + + return nil + } +} + +func testAccTrustedTokenIssuerConfigBase_basic(rName string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_ssoadmin_trusted_token_issuer" "test" { + name = %[1]q + instance_arn = tolist(data.aws_ssoadmin_instances.test.arns)[0] + trusted_token_issuer_type = "OIDC_JWT" + + trusted_token_issuer_configuration { + oidc_jwt_configuration { + claim_attribute_path = "email" + identity_store_attribute_path = "emails.value" + issuer_url = "https://example.com" + jwks_retrieval_option = "OPEN_ID_DISCOVERY" + } + } +} +`, rName) +} + +func testAccTrustedTokenIssuerConfigBase_basicUpdated(rNameUpdated, claimAttributePath, identityStoreAttributePath string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_ssoadmin_trusted_token_issuer" "test" { + name = %[1]q + instance_arn = tolist(data.aws_ssoadmin_instances.test.arns)[0] + trusted_token_issuer_type = "OIDC_JWT" + + trusted_token_issuer_configuration { + oidc_jwt_configuration { + claim_attribute_path = %[2]q + identity_store_attribute_path = %[3]q + issuer_url = "https://example.com" + jwks_retrieval_option = "OPEN_ID_DISCOVERY" + } + } +} +`, rNameUpdated, claimAttributePath, identityStoreAttributePath) +} + +func testAccTrustedTokenIssuerConfigBase_tags(rName, tagKey, tagValue string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_ssoadmin_trusted_token_issuer" "test" { + name = %[1]q + instance_arn = tolist(data.aws_ssoadmin_instances.test.arns)[0] + trusted_token_issuer_type = "OIDC_JWT" + + trusted_token_issuer_configuration { + oidc_jwt_configuration { + claim_attribute_path = "email" + identity_store_attribute_path = "emails.value" + issuer_url = "https://example.com" + jwks_retrieval_option = "OPEN_ID_DISCOVERY" + } + } + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey, tagValue) +} + +func testAccTrustedTokenIssuerConfigBase_tags2(rName, tagKey, tagValue, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_ssoadmin_trusted_token_issuer" "test" { + name = %[1]q + instance_arn = tolist(data.aws_ssoadmin_instances.test.arns)[0] + trusted_token_issuer_type = "OIDC_JWT" + + trusted_token_issuer_configuration { + oidc_jwt_configuration { + claim_attribute_path = "email" + identity_store_attribute_path = "emails.value" + issuer_url = "https://example.com" + jwks_retrieval_option = "OPEN_ID_DISCOVERY" + } + } + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey, tagValue, tagKey2, tagValue2) +} diff --git a/website/docs/r/ssoadmin_trusted_token_issuer.html.markdown b/website/docs/r/ssoadmin_trusted_token_issuer.html.markdown new file mode 100644 index 000000000000..eb4e58c1b090 --- /dev/null +++ b/website/docs/r/ssoadmin_trusted_token_issuer.html.markdown @@ -0,0 +1,83 @@ +--- +subcategory: "SSO Admin" +layout: "aws" +page_title: "AWS: aws_ssoadmin_trusted_token_issuer" +description: |- + Terraform resource for managing an AWS SSO Admin Trusted Token Issuer. +--- +# Resource: aws_ssoadmin_trusted_token_issuer + +Terraform resource for managing an AWS SSO Admin Trusted Token Issuer. + +## Example Usage + +### Basic Usage + +```terraform +data "aws_ssoadmin_instances" "example" {} + +resource "aws_ssoadmin_trusted_token_issuer" "example" { + name = "example" + instance_arn = tolist(data.aws_ssoadmin_instances.example.arns)[0] + trusted_token_issuer_type = "OIDC_JWT" + + trusted_token_issuer_configuration { + oidc_jwt_configuration { + claim_attribute_path = "email" + identity_store_attribute_path = "emails.value" + issuer_url = "https://example.com" + jwks_retrieval_option = "OPEN_ID_DISCOVERY" + } + } +} +``` + +## Argument Reference + +The following arguments are required: + +* `instance_arn` - (Required) ARN of the instance of IAM Identity Center. +* `name` - (Required) Name of the trusted token issuer. +* `trusted_token_issuer_configuration` - (Required) A block that specifies settings that apply to the trusted token issuer, these change depending on the type you specify in `trusted_token_issuer_type`. [Documented below](#trusted_token_issuer_configuration-argument-reference). +* `trusted_token_issuer_type` - (Required) Specifies the type of the trusted token issuer. Valid values are `OIDC_JWT` + +The following arguments are optional: + +* `client_token` - (Optional) A unique, case-sensitive ID that you provide to ensure the idempotency of the request. AWS generates a random value when not provided. +* `tags` - (Optional) Key-value mapping of resource tags. If configured with a provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. + +### `trusted_token_issuer_configuration` Argument Reference + +* `oidc_jwt_configuration` - (Optional) A block that describes the settings for a trusted token issuer that works with OpenID Connect (OIDC) by using JSON Web Tokens (JWT). See [Documented below](#oidc_jwt_configuration-argument-reference) below. + +### `oidc_jwt_configuration` Argument Reference + +* `claim_attribute_path` - (Required) Specifies the path of the source attribute in the JWT from the trusted token issuer. +* `identity_store_attribute_path` - (Required) Specifies path of the destination attribute in a JWT from IAM Identity Center. The attribute mapped by this JMESPath expression is compared against the attribute mapped by `claim_attribute_path` when a trusted token issuer token is exchanged for an IAM Identity Center token. +* `issuer_url` - (Required) Specifies the URL that IAM Identity Center uses for OpenID Discovery. OpenID Discovery is used to obtain the information required to verify the tokens that the trusted token issuer generates. +* `jwks_retrieval_option` - (Required) The method that the trusted token issuer can use to retrieve the JSON Web Key Set used to verify a JWT. Valid values are `OPEN_ID_DISCOVERY` + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `arn` - ARN of the trusted token issuer. +* `id` - ARN of the trusted token issuer. +* `tags_all` - Map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block). + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import SSO Admin Trusted Token Issuer using the `id`. For example: + +```terraform +import { + to = aws_ssoadmin_trusted_token_issuer.example + id = "arn:aws:sso::012345678901:trustedTokenIssuer/ssoins-lu1ye3gew4mbc7ju/tti-2657c556-9707-11ee-b9d1-0242ac120002" +} +``` + +Using `terraform import`, import SSO Admin Trusted Token Issuer using the `id`. For example: + +```console +% terraform import aws_ssoadmin_trusted_token_issuer.example arn:aws:sso::012345678901:trustedTokenIssuer/ssoins-lu1ye3gew4mbc7ju/tti-2657c556-9707-11ee-b9d1-0242ac120002 +```