diff --git a/.changelog/45217.txt b/.changelog/45217.txt new file mode 100644 index 00000000000..a81dc0c52be --- /dev/null +++ b/.changelog/45217.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_iam_outbound_web_identity_federation +``` \ No newline at end of file diff --git a/internal/service/iam/exports_test.go b/internal/service/iam/exports_test.go index 142d1ebd5bd..b89642ac01e 100644 --- a/internal/service/iam/exports_test.go +++ b/internal/service/iam/exports_test.go @@ -63,6 +63,7 @@ var ( FindUserPolicyAttachmentsByName = findUserPolicyAttachmentsByName FindUserPolicyByTwoPartKey = findUserPolicyByTwoPartKey FindVirtualMFADeviceBySerialNumber = findVirtualMFADeviceBySerialNumber + GetOutboundWebIdentityFederation = getOutboundWebIdentityFederation AttachPolicyToUser = attachPolicyToUser CheckPwdPolicy = checkPwdPolicy diff --git a/internal/service/iam/outbound_web_identity_federation.go b/internal/service/iam/outbound_web_identity_federation.go new file mode 100644 index 00000000000..3dc0efcf2d7 --- /dev/null +++ b/internal/service/iam/outbound_web_identity_federation.go @@ -0,0 +1,149 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package iam + +import ( + "context" + "fmt" + + "github.com/aws/aws-sdk-go-v2/service/iam" + awstypes "github.com/aws/aws-sdk-go-v2/service/iam/types" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "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" + "github.com/hashicorp/terraform-provider-aws/internal/smerr" +) + +// @FrameworkResource("aws_iam_outbound_web_identity_federation", name="Outbound Web Identity Federation") +func newResourceOutboundWebIdentityFederation(_ context.Context) (resource.ResourceWithConfigure, error) { + r := &resourceOutboundWebIdentityFederation{} + + return r, nil +} + +const ( + ResNameOutboundWebIdentityFederation = "Outbound Web Identity Federation" +) + +type resourceOutboundWebIdentityFederation struct { + framework.ResourceWithModel[resourceOutboundWebIdentityFederationModel] + framework.WithNoUpdate +} + +func (r *resourceOutboundWebIdentityFederation) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "issuer_identifier": schema.StringAttribute{ + Computed: true, + }, + "jwt_vending_enabled": schema.BoolAttribute{ + Computed: true, + }, + }, + } +} + +func (r *resourceOutboundWebIdentityFederation) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + conn := r.Meta().IAMClient(ctx) + + var plan resourceOutboundWebIdentityFederationModel + smerr.AddEnrich(ctx, &resp.Diagnostics, req.Plan.Get(ctx, &plan)) + if resp.Diagnostics.HasError() { + return + } + + out, err := conn.EnableOutboundWebIdentityFederation(ctx, &iam.EnableOutboundWebIdentityFederationInput{}) + if errs.IsA[*awstypes.FeatureEnabledException](err) { + // Feature is already enabled, adopt existing state + outAlreadyEnabled, err := getOutboundWebIdentityFederation(ctx, conn) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err) + return + } + if outAlreadyEnabled == nil { + smerr.AddError(ctx, &resp.Diagnostics, fmt.Errorf("expected non-nil response from GetOutboundWebIdentityFederationInfo")) + return + } + smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Flatten(ctx, outAlreadyEnabled, &plan)) + if resp.Diagnostics.HasError() { + return + } + } else { + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err) + return + } + if out == nil { + smerr.AddError(ctx, &resp.Diagnostics, fmt.Errorf("expected non-nil response from EnableOutboundWebIdentityFederation")) + return + } + plan.JwtVendingEnabled = types.BoolValue(true) + smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Flatten(ctx, out, &plan)) + if resp.Diagnostics.HasError() { + return + } + } + smerr.AddEnrich(ctx, &resp.Diagnostics, resp.State.Set(ctx, plan)) +} + +func (r *resourceOutboundWebIdentityFederation) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + conn := r.Meta().IAMClient(ctx) + + var state resourceOutboundWebIdentityFederationModel + smerr.AddEnrich(ctx, &resp.Diagnostics, req.State.Get(ctx, &state)) + if resp.Diagnostics.HasError() { + return + } + + out, err := getOutboundWebIdentityFederation(ctx, conn) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID) + return + } + + smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Flatten(ctx, out, &state)) + if resp.Diagnostics.HasError() { + return + } + + smerr.AddEnrich(ctx, &resp.Diagnostics, resp.State.Set(ctx, &state)) +} + +func (r *resourceOutboundWebIdentityFederation) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + conn := r.Meta().IAMClient(ctx) + + var state resourceOutboundWebIdentityFederationModel + smerr.AddEnrich(ctx, &resp.Diagnostics, req.State.Get(ctx, &state)) + if resp.Diagnostics.HasError() { + return + } + + _, err := conn.DisableOutboundWebIdentityFederation(ctx, &iam.DisableOutboundWebIdentityFederationInput{}) + if errs.IsA[*awstypes.FeatureDisabledException](err) { + return + } + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID) + return + } +} + +type resourceOutboundWebIdentityFederationModel struct { + JwtVendingEnabled types.Bool `tfsdk:"jwt_vending_enabled"` + IssuerIdentifier types.String `tfsdk:"issuer_identifier"` +} + +func getOutboundWebIdentityFederation(ctx context.Context, conn *iam.Client) (*iam.GetOutboundWebIdentityFederationInfoOutput, error) { + out, err := conn.GetOutboundWebIdentityFederationInfo(ctx, &iam.GetOutboundWebIdentityFederationInfoInput{}) + if err != nil { + if errs.IsA[*awstypes.FeatureDisabledException](err) { + return nil, nil + } + return nil, err + } + return out, nil +} diff --git a/internal/service/iam/outbound_web_identity_federation_test.go b/internal/service/iam/outbound_web_identity_federation_test.go new file mode 100644 index 00000000000..4dc686170eb --- /dev/null +++ b/internal/service/iam/outbound_web_identity_federation_test.go @@ -0,0 +1,133 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package iam_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/iam" + "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" + tfiam "github.com/hashicorp/terraform-provider-aws/internal/service/iam" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccIAMOutboundWebIdentityFederation_serial(t *testing.T) { + t.Helper() + + testCases := map[string]func(t *testing.T){ + acctest.CtBasic: testAccIAMOutboundWebIdentityFederation_basic, + "alreadyEnabled": testAccIAMOutboundWebIdentityFederation_alreadyEnabled, + } + + acctest.RunSerialTests1Level(t, testCases, 0) +} + +func testAccIAMOutboundWebIdentityFederation_basic(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_iam_outbound_web_identity_federation.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.IAMServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckOutboundWebIdentityFederationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccOutboundWebIdentityFederationConfig_basic(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "jwt_vending_enabled", acctest.CtTrue), + resource.TestCheckResourceAttrSet(resourceName, "issuer_identifier"), + ), + }, + }, + }) +} + +func testAccIAMOutboundWebIdentityFederation_alreadyEnabled(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_iam_outbound_web_identity_federation.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.IAMServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckOutboundWebIdentityFederationDestroy(ctx), + Steps: []resource.TestStep{ + { + PreConfig: func() { + conn := acctest.Provider.Meta().(*conns.AWSClient).IAMClient(ctx) + + _, err := conn.EnableOutboundWebIdentityFederation(ctx, &iam.EnableOutboundWebIdentityFederationInput{}) + if err != nil { + t.Fatalf("error enabling outbound web identity federation: %s", err) + } + }, + Config: testAccOutboundWebIdentityFederationConfig_basic(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "jwt_vending_enabled", acctest.CtTrue), + resource.TestCheckResourceAttrSet(resourceName, "issuer_identifier"), + ), + }, + }, + }) +} + +func testAccCheckOutboundWebIdentityFederationDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).IAMClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_iam_outbound_web_identity_federation" { + continue + } + + out, err := tfiam.GetOutboundWebIdentityFederation(ctx, conn) + + if out == nil { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("IAM Outbound Web Identity Federation still exists") + } + + return nil + } +} + +func testAccPreCheck(ctx context.Context, t *testing.T) { + conn := acctest.Provider.Meta().(*conns.AWSClient).IAMClient(ctx) + + _, err := tfiam.GetOutboundWebIdentityFederation(ctx, conn) + + if acctest.PreCheckSkipError(err) { + t.Skipf("skipping acceptance testing: %s", err) + } + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } +} + +func testAccOutboundWebIdentityFederationConfig_basic() string { + return ` +resource "aws_iam_outbound_web_identity_federation" "test" { +} +` +} diff --git a/internal/service/iam/service_package_gen.go b/internal/service/iam/service_package_gen.go index 9d251ec2b39..fb8440e06ea 100644 --- a/internal/service/iam/service_package_gen.go +++ b/internal/service/iam/service_package_gen.go @@ -43,6 +43,12 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*inttypes.Ser Name: "Organizations Features", Region: unique.Make(inttypes.ResourceRegionDisabled()), }, + { + Factory: newResourceOutboundWebIdentityFederation, + TypeName: "aws_iam_outbound_web_identity_federation", + Name: "Outbound Web Identity Federation", + Region: unique.Make(inttypes.ResourceRegionDisabled()), + }, { Factory: newRolePoliciesExclusiveResource, TypeName: "aws_iam_role_policies_exclusive", diff --git a/website/docs/r/iam_outbound_web_identity_federation.html.markdown b/website/docs/r/iam_outbound_web_identity_federation.html.markdown new file mode 100644 index 00000000000..f87a7154e8a --- /dev/null +++ b/website/docs/r/iam_outbound_web_identity_federation.html.markdown @@ -0,0 +1,36 @@ +--- +subcategory: "IAM (Identity & Access Management)" +layout: "aws" +page_title: "AWS: aws_iam_outbound_web_identity_federation" +description: |- + Manages an AWS IAM (Identity & Access Management) Outbound Web Identity Federation. +--- + +# Resource: aws_iam_outbound_web_identity_federation + +Manages an AWS IAM (Identity & Access Management) Outbound Web Identity Federation. + +~> **NOTE:** This resource will enable IAM Outbound Web Identity Federation on the account when created and disable when destroyed. + +## Example Usage + +```terraform +resource "aws_iam_outbound_web_identity_federation" "example" {} +``` + +## Argument Reference + +This resource does not support any arguments. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `issuer_identifier` - A unique issuer URL for your AWS account that hosts the OpenID Connect (OIDC) discovery endpoints. +* `jwt_vending_enabled` - Indicates whether outbound identity federation is currently enabled for your AWS account. + +## Import + +You cannot import this resource. + +~> **NOTE:** This resource will adopt the IAM Outbound Web Identity Federation setting in the account if this setting is already enabled.