diff --git a/.changelog/33328.txt b/.changelog/33328.txt new file mode 100644 index 00000000000..edb5974d7bf --- /dev/null +++ b/.changelog/33328.txt @@ -0,0 +1,7 @@ +```release-note:new-resource +aws_shield_drt_access_log_bucket_association +``` + +```release-note:new-resource +aws_shield_drt_access_role_arn_association +``` diff --git a/internal/service/shield/drt_access_log_bucket_association.go b/internal/service/shield/drt_access_log_bucket_association.go new file mode 100644 index 00000000000..faa84be8648 --- /dev/null +++ b/internal/service/shield/drt_access_log_bucket_association.go @@ -0,0 +1,379 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package shield + +import ( + "context" + "errors" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/shield" + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "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/create" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkResource(name="DRT Access Log Bucket Association") +func newResourceDRTAccessLogBucketAssociation(_ context.Context) (resource.ResourceWithConfigure, error) { + r := &resourceDRTAccessLogBucketAssociation{} + + r.SetDefaultCreateTimeout(30 * time.Minute) + r.SetDefaultUpdateTimeout(30 * time.Minute) + r.SetDefaultDeleteTimeout(30 * time.Minute) + + return r, nil +} + +const ( + ResNameDRTAccessLogBucketAssociation = "DRT Access Log Bucket Association" +) + +type resourceDRTAccessLogBucketAssociation struct { + framework.ResourceWithConfigure + framework.WithTimeouts +} + +func (r *resourceDRTAccessLogBucketAssociation) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "aws_shield_drt_access_log_bucket_association" +} + +func (r *resourceDRTAccessLogBucketAssociation) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ // required by hashicorps terraform plugin testing framework + DeprecationMessage: "id is only for framework compatibility and not used by the provider", + MarkdownDescription: "The ID of the directory.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "log_bucket": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + // Validate string value length must be at least 3 characters and max 63. + stringvalidator.LengthBetween(3, 63), + }, + }, + "role_arn_association_id": schema.StringAttribute{ + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "timeouts": timeouts.Block(ctx, timeouts.Opts{ + Create: true, + Delete: true, + Read: true, + }), + }, + } +} + +func (r *resourceDRTAccessLogBucketAssociation) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + conn := r.Meta().ShieldConn(ctx) + + var plan resourceDRTAccessLogBucketAssociationData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + in := &shield.AssociateDRTLogBucketInput{ + LogBucket: aws.String(plan.LogBucket.ValueString()), + } + out, err := conn.AssociateDRTLogBucketWithContext(ctx, in) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionCreating, ResNameDRTAccessLogBucketAssociation, plan.LogBucket.String(), err), + err.Error(), + ) + return + } + if out == nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionCreating, ResNameDRTAccessLogBucketAssociation, plan.LogBucket.String(), nil), + errors.New("empty output").Error(), + ) + return + } + + createTimeout := r.CreateTimeout(ctx, plan.Timeouts) + _, err = waitDRTAccessLogBucketAssociationCreated(ctx, conn, plan.LogBucket.ValueString(), createTimeout) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForCreation, ResNameDRTAccessLogBucketAssociation, plan.LogBucket.String(), err), + err.Error(), + ) + return + } + plan.ID = types.StringValue(plan.LogBucket.ValueString()) + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func (r *resourceDRTAccessLogBucketAssociation) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + conn := r.Meta().ShieldConn(ctx) + + var state resourceDRTAccessLogBucketAssociationData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + in := &shield.DescribeDRTAccessInput{} + + out, err := conn.DescribeDRTAccessWithContext(ctx, in) + if tfresource.NotFound(err) { + resp.State.RemoveResource(ctx) + return + } + + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionSetting, ResNameDRTAccessLogBucketAssociation, state.LogBucket.String(), err), + err.Error(), + ) + return + } + var associatedLogBucket *string + if out != nil { + associatedLogBucket = getAssociatedLogBucket(state.LogBucket.ValueString(), out.LogBucketList) + if len(out.LogBucketList) > 0 && associatedLogBucket == nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionSetting, ResNameDRTAccessLogBucketAssociation, state.LogBucket.String(), nil), + errors.New("Log Bucket not in list").Error(), + ) + } + } + + if state.ID.IsNull() || state.ID.IsUnknown() { + // Setting ID of state - required by hashicorps terraform plugin testing framework for Import. See issue https://github.com/hashicorp/terraform-plugin-testing/issues/84 + state.ID = types.StringValue(state.LogBucket.ValueString()) + } + state.LogBucket = flex.StringToFramework(ctx, associatedLogBucket) + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func getAssociatedLogBucket(bucket string, bucketList []*string) *string { + for _, bkt := range bucketList { + if aws.StringValue(bkt) == bucket { + return bkt + } + } + return nil +} + +func (r *resourceDRTAccessLogBucketAssociation) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + conn := r.Meta().ShieldConn(ctx) + + var plan, state resourceDRTAccessLogBucketAssociationData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + if !plan.LogBucket.Equal(state.LogBucket) { + in := &shield.AssociateDRTLogBucketInput{ + LogBucket: aws.String(plan.LogBucket.ValueString()), + } + out, err := conn.AssociateDRTLogBucketWithContext(ctx, in) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionUpdating, ResNameDRTAccessLogBucketAssociation, plan.LogBucket.String(), err), + err.Error(), + ) + return + } + if out == nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionUpdating, ResNameDRTAccessLogBucketAssociation, plan.LogBucket.String(), nil), + errors.New("empty output").Error(), + ) + return + } + } + + updateTimeout := r.UpdateTimeout(ctx, plan.Timeouts) + _, err := waitDRTAccessLogBucketAssociationUpdated(ctx, conn, plan.LogBucket.ValueString(), updateTimeout) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForUpdate, ResNameDRTAccessLogBucketAssociation, plan.LogBucket.String(), err), + err.Error(), + ) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *resourceDRTAccessLogBucketAssociation) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + conn := r.Meta().ShieldConn(ctx) + + var state resourceDRTAccessLogBucketAssociationData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + if state.LogBucket.ValueString() == "" { + return + } + + in := &shield.DisassociateDRTLogBucketInput{ + LogBucket: aws.String(state.LogBucket.ValueString()), + } + + _, err := conn.DisassociateDRTLogBucketWithContext(ctx, in) + if err != nil { + var nfe *shield.ResourceNotFoundException + if errors.As(err, &nfe) { + return + } + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionDeleting, ResNameDRTAccessLogBucketAssociation, state.LogBucket.String(), err), + err.Error(), + ) + return + } + deleteTimeout := r.DeleteTimeout(ctx, state.Timeouts) + _, err = waitDRTAccessLogBucketAssociationDeleted(ctx, conn, state.LogBucket.ValueString(), deleteTimeout) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForDeletion, ResNameDRTAccessLogBucketAssociation, state.LogBucket.String(), err), + err.Error(), + ) + return + } +} + +const ( + statusChangePending = "Pending" + statusDeleting = "Deleting" + statusNormal = "Normal" + statusUpdated = "Updated" +) + +func waitDRTAccessLogBucketAssociationCreated(ctx context.Context, conn *shield.Shield, bucket string, timeout time.Duration) (*shield.DescribeDRTAccessOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: []string{}, + Target: []string{statusNormal}, + Refresh: statusDRTAccessLogBucketAssociation(ctx, conn, bucket), + Timeout: timeout, + NotFoundChecks: 2, + ContinuousTargetOccurence: 2, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*shield.DescribeDRTAccessOutput); ok { + return out, err + } + + return nil, err +} + +func waitDRTAccessLogBucketAssociationUpdated(ctx context.Context, conn *shield.Shield, bucket string, timeout time.Duration) (*shield.DescribeDRTAccessOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: []string{statusChangePending}, + Target: []string{statusUpdated}, + Refresh: statusDRTAccessLogBucketAssociation(ctx, conn, bucket), + Timeout: timeout, + NotFoundChecks: 2, + ContinuousTargetOccurence: 2, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*shield.DescribeDRTAccessOutput); ok { + return out, err + } + + return nil, err +} + +func waitDRTAccessLogBucketAssociationDeleted(ctx context.Context, conn *shield.Shield, bucket string, timeout time.Duration) (*shield.DescribeDRTAccessOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: []string{statusDeleting, statusNormal}, + Target: []string{}, + Refresh: statusDRTAccessLogBucketAssociation(ctx, conn, bucket), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*shield.DescribeDRTAccessOutput); ok { + return out, err + } + + return nil, err +} + +func statusDRTAccessLogBucketAssociation(ctx context.Context, conn *shield.Shield, bucket string) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + out, err := describeDRTAccessLogBucketAssociation(ctx, conn, bucket) + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + if out == nil || out.LogBucketList == nil || len(out.LogBucketList) == 0 { + return nil, "", nil + } + + if out != nil && len(out.LogBucketList) > 0 { + for _, bkt := range out.LogBucketList { + if aws.StringValue(bkt) == bucket { + return out, statusNormal, nil + } + } + return nil, "", nil + } + + return out, statusNormal, nil + } +} + +func describeDRTAccessLogBucketAssociation(ctx context.Context, conn *shield.Shield, bucketName string) (*shield.DescribeDRTAccessOutput, error) { + in := &shield.DescribeDRTAccessInput{} + + out, err := conn.DescribeDRTAccessWithContext(ctx, in) + if err != nil { + var nfe *shield.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: in, + } + } + } + + if out == nil || out.LogBucketList == nil || len(out.LogBucketList) == 0 { + return nil, tfresource.NewEmptyResultError(in) + } + + for _, bucket := range out.LogBucketList { + if aws.StringValue(bucket) == bucketName { + return out, nil + } + } + return nil, err +} + +type resourceDRTAccessLogBucketAssociationData struct { + ID types.String `tfsdk:"id"` + RoleArnAssociationID types.String `tfsdk:"role_arn_association_id"` + LogBucket types.String `tfsdk:"log_bucket"` + Timeouts timeouts.Value `tfsdk:"timeouts"` +} diff --git a/internal/service/shield/drt_access_log_bucket_association_test.go b/internal/service/shield/drt_access_log_bucket_association_test.go new file mode 100644 index 00000000000..e824e9c73a1 --- /dev/null +++ b/internal/service/shield/drt_access_log_bucket_association_test.go @@ -0,0 +1,290 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package shield_test + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/shield" + 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" + tfshield "github.com/hashicorp/terraform-provider-aws/internal/service/shield" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccShieldDRTAccessLogBucketAssociation_basic(t *testing.T) { + ctx := acctest.Context(t) + + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var drtaccesslogbucketassociation shield.DescribeDRTAccessOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + bucketName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_shield_drt_access_log_bucket_association.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckLogBucket(ctx, t) + }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckDRTAccessLogBucketAssociationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccDRTAccessLogBucketAssociationConfig_basic(rName, bucketName), + Check: testAccCheckDRTAccessLogBucketAssociationExists(ctx, resourceName, &drtaccesslogbucketassociation), + }, + }, + }) +} + +func TestAccShieldDRTAccessLogBucketAssociation_multibucket(t *testing.T) { + ctx := acctest.Context(t) + + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var drtaccesslogbucketassociation shield.DescribeDRTAccessOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + var buckets = []string{} + for i := 0; i < 2; i++ { + buckets = append(buckets, sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)) + } + resourceName1 := "aws_shield_drt_access_log_bucket_association.test1" + resourceName2 := "aws_shield_drt_access_log_bucket_association.test2" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckLogBucket(ctx, t) + }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckDRTAccessLogBucketAssociationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccDRTAccessLogBucketAssociationConfig_multibucket(rName, buckets), + Check: resource.ComposeTestCheckFunc( + testAccCheckDRTAccessLogBucketAssociationExists(ctx, resourceName1, &drtaccesslogbucketassociation), + testAccCheckDRTAccessLogBucketAssociationExists(ctx, resourceName2, &drtaccesslogbucketassociation), + ), + }, + }, + }) +} + +func TestAccShieldDRTAccessLogBucketAssociation_disappears(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var drtaccesslogbucketassociation shield.DescribeDRTAccessOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + bucketName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_shield_drt_access_log_bucket_association.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckLogBucket(ctx, t) + }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckDRTAccessLogBucketAssociationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccDRTAccessLogBucketAssociationConfig_basic(rName, bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckDRTAccessLogBucketAssociationExists(ctx, resourceName, &drtaccesslogbucketassociation), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfshield.ResourceDRTAccessLogBucketAssociation, resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckDRTAccessLogBucketAssociationDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).ShieldConn(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_shield_drt_access_log_bucket_association" { + continue + } + + input := &shield.DescribeDRTAccessInput{} + resp, err := conn.DescribeDRTAccessWithContext(ctx, input) + + if errs.IsA[*shield.ResourceNotFoundException](err) { + return nil + } + if err != nil { + return err + } + if resp != nil { + if resp.LogBucketList != nil && len(resp.LogBucketList) > 0 { + for _, bucket := range resp.LogBucketList { + if *bucket == rs.Primary.Attributes["log_bucket"] { + return create.Error(names.Shield, create.ErrActionCheckingDestroyed, tfshield.ResNameDRTAccessLogBucketAssociation, rs.Primary.ID, errors.New("bucket association not destroyed")) + } + } + } + return nil + } + + return create.Error(names.Shield, create.ErrActionCheckingDestroyed, tfshield.ResNameDRTAccessLogBucketAssociation, rs.Primary.ID, errors.New("not destroyed")) + } + + return nil + } +} + +func testAccCheckDRTAccessLogBucketAssociationExists(ctx context.Context, name string, drtaccesslogbucketassociation *shield.DescribeDRTAccessOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + + if !ok { + return create.Error(names.Shield, create.ErrActionCheckingExistence, tfshield.ResNameDRTAccessLogBucketAssociation, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.Shield, create.ErrActionCheckingExistence, tfshield.ResNameDRTAccessLogBucketAssociation, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).ShieldConn(ctx) + resp, err := conn.DescribeDRTAccessWithContext(ctx, &shield.DescribeDRTAccessInput{}) + if err != nil { + return create.Error(names.Shield, create.ErrActionCheckingExistence, tfshield.ResNameDRTAccessLogBucketAssociation, rs.Primary.ID, err) + } + + *drtaccesslogbucketassociation = *resp + + return nil + } +} + +func testAccPreCheckLogBucket(ctx context.Context, t *testing.T) { + conn := acctest.Provider.Meta().(*conns.AWSClient).ShieldConn(ctx) + + input := &shield.DescribeDRTAccessInput{} + _, err := conn.DescribeDRTAccessWithContext(ctx, input) + + if acctest.PreCheckSkipError(err) { + t.Skipf("skipping acceptance testing: %s", err) + } + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } +} + +func testAccDRTAccessLogBucketAssociationConfig_basic(rName string, bucket string) string { + return fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_s3_bucket" "test" { + bucket = %[2]q +} + +resource "aws_iam_role" "test" { + name = %[1]q + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + "Sid" : "", + "Effect" : "Allow", + "Principal" : { + "Service" : "drt.shield.amazonaws.com" + }, + "Action" : "sts:AssumeRole" + }] + }) +} + +resource "aws_iam_role_policy_attachment" "test" { + role = aws_iam_role.test.name + policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AWSShieldDRTAccessPolicy" +} + +resource "aws_shield_protection_group" "test" { + protection_group_id = %[1]q + aggregation = "MAX" + pattern = "ALL" +} + +resource "aws_shield_drt_access_role_arn_association" "test" { + role_arn = aws_iam_role.test.arn +} + +resource "aws_shield_drt_access_log_bucket_association" "test" { + log_bucket = aws_s3_bucket.test.id + role_arn_association_id = aws_shield_drt_access_role_arn_association.test.id +} +`, rName, bucket) +} + +func testAccDRTAccessLogBucketAssociationConfig_multibucket(rName string, buckets []string) string { + return fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_s3_bucket" "test1" { + bucket = %[2]q +} + +resource "aws_s3_bucket" "test2" { + bucket = %[3]q +} + +resource "aws_iam_role" "test" { + name = %[1]q + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + "Sid" : "", + "Effect" : "Allow", + "Principal" : { + "Service" : "drt.shield.amazonaws.com" + }, + "Action" : "sts:AssumeRole" + }] + }) +} + +resource "aws_iam_role_policy_attachment" "test" { + role = aws_iam_role.test.name + policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AWSShieldDRTAccessPolicy" +} + +resource "aws_shield_protection_group" "test" { + protection_group_id = %[1]q + aggregation = "MAX" + pattern = "ALL" +} + +resource "aws_shield_drt_access_role_arn_association" "test" { + role_arn = aws_iam_role.test.arn +} + +resource "aws_shield_drt_access_log_bucket_association" "test1" { + log_bucket = aws_s3_bucket.test1.id + role_arn_association_id = aws_shield_drt_access_role_arn_association.test.id +} + +resource "aws_shield_drt_access_log_bucket_association" "test2" { + log_bucket = aws_s3_bucket.test2.id + role_arn_association_id = aws_shield_drt_access_role_arn_association.test.id + + depends_on = [aws_shield_drt_access_log_bucket_association.test1] +} +`, rName, buckets[0], buckets[1]) +} diff --git a/internal/service/shield/drt_access_role_arn_association.go b/internal/service/shield/drt_access_role_arn_association.go new file mode 100644 index 00000000000..8b7aa76d859 --- /dev/null +++ b/internal/service/shield/drt_access_role_arn_association.go @@ -0,0 +1,359 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package shield + +import ( + "context" + "errors" + "time" + + "github.com/YakDriver/regexache" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/shield" + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "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/create" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// Function annotations are used for resource registration to the Provider. DO NOT EDIT. +// @FrameworkResource(name="DRT Access Role ARN Association") +func newResourceDRTAccessRoleARNAssociation(_ context.Context) (resource.ResourceWithConfigure, error) { + r := &resourceDRTAccessRoleARNAssociation{} + + r.SetDefaultCreateTimeout(30 * time.Minute) + r.SetDefaultUpdateTimeout(30 * time.Minute) + r.SetDefaultDeleteTimeout(30 * time.Minute) + + return r, nil +} + +const ( + ResNameDRTAccessRoleARNAssociation = "DRT Access Role ARN Association" +) + +type resourceDRTAccessRoleARNAssociation struct { + framework.ResourceWithConfigure + framework.WithTimeouts +} + +func (r *resourceDRTAccessRoleARNAssociation) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "aws_shield_drt_access_role_arn_association" +} + +func (r *resourceDRTAccessRoleARNAssociation) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ // required by hashicorps terraform plugin testing framework + DeprecationMessage: "id is only for framework compatibility and not used by the provider", + MarkdownDescription: "The ID of the directory.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "role_arn": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 2048), + stringvalidator.RegexMatches( + regexache.MustCompile(`^arn:?[a-zA-Z\-]+:iam::\d{12}:role/?[a-zA-Z_0-9+=,.@\-_/]+`), + "must match arn pattern", + ), + }, + }, + }, + Blocks: map[string]schema.Block{ + "timeouts": timeouts.Block(ctx, timeouts.Opts{ + Create: true, + Delete: true, + Read: true, + }), + }, + } +} + +func (r *resourceDRTAccessRoleARNAssociation) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + conn := r.Meta().ShieldConn(ctx) + + var plan resourceDRTAccessRoleARNAssociationData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + in := &shield.AssociateDRTRoleInput{ + RoleArn: aws.String(plan.RoleARN.ValueString()), + } + + out, err := conn.AssociateDRTRoleWithContext(ctx, in) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionCreating, ResNameDRTAccessRoleARNAssociation, plan.RoleARN.String(), err), + err.Error(), + ) + return + } + if out == nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionCreating, ResNameDRTAccessRoleARNAssociation, plan.RoleARN.String(), nil), + errors.New("empty output").Error(), + ) + return + } + + createTimeout := r.CreateTimeout(ctx, plan.Timeouts) + _, err = waitDRTAccessRoleARNAssociationCreated(ctx, conn, plan.RoleARN.ValueString(), createTimeout) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForCreation, ResNameDRTAccessRoleARNAssociation, plan.RoleARN.String(), err), + err.Error(), + ) + return + } + + plan.ID = types.StringValue(plan.RoleARN.ValueString()) + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func (r *resourceDRTAccessRoleARNAssociation) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + conn := r.Meta().ShieldConn(ctx) + + var state resourceDRTAccessRoleARNAssociationData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + in := &shield.DescribeDRTAccessInput{} + out, err := conn.DescribeDRTAccessWithContext(ctx, in) + + if tfresource.NotFound(err) { + resp.State.RemoveResource(ctx) + return + } + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionSetting, ResNameDRTAccessRoleARNAssociation, state.RoleARN.String(), err), + err.Error(), + ) + return + } + if state.ID.IsNull() || state.ID.IsUnknown() { + // Setting ID of state - required by hashicorps terraform plugin testing framework for Import. See issue https://github.com/hashicorp/terraform-plugin-testing/issues/84 + state.ID = types.StringValue(state.RoleARN.ValueString()) + } + + state.RoleARN = flex.StringToFramework(ctx, out.RoleArn) + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *resourceDRTAccessRoleARNAssociation) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + conn := r.Meta().ShieldConn(ctx) + + // TIP: -- 2. Fetch the plan + var plan, state resourceDRTAccessRoleARNAssociationData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + if !plan.RoleARN.Equal(state.RoleARN) { + in := &shield.AssociateDRTRoleInput{ + RoleArn: aws.String(plan.RoleARN.ValueString()), + } + + out, err := conn.AssociateDRTRoleWithContext(ctx, in) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionUpdating, ResNameDRTAccessRoleARNAssociation, plan.RoleARN.String(), err), + err.Error(), + ) + return + } + if out == nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionUpdating, ResNameDRTAccessRoleARNAssociation, plan.RoleARN.String(), nil), + errors.New("empty output").Error(), + ) + return + } + } + + updateTimeout := r.UpdateTimeout(ctx, plan.Timeouts) + _, err := waitDRTAccessRoleARNAssociationUpdated(ctx, conn, plan.RoleARN.ValueString(), updateTimeout) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForUpdate, ResNameDRTAccessRoleARNAssociation, plan.RoleARN.String(), err), + err.Error(), + ) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *resourceDRTAccessRoleARNAssociation) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + conn := r.Meta().ShieldConn(ctx) + + var state resourceDRTAccessRoleARNAssociationData + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + in := &shield.DisassociateDRTRoleInput{} + + _, err := conn.DisassociateDRTRoleWithContext(ctx, in) + if err != nil { + var nfe *shield.ResourceNotFoundException + if errors.As(err, &nfe) { + return + } + + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionDeleting, ResNameDRTAccessRoleARNAssociation, state.RoleARN.String(), err), + err.Error(), + ) + return + } + + deleteTimeout := r.DeleteTimeout(ctx, state.Timeouts) + _, err = waitDRTAccessRoleARNAssociationDeleted(ctx, conn, state.RoleARN.ValueString(), deleteTimeout) + + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForDeletion, ResNameDRTAccessRoleARNAssociation, state.RoleARN.String(), err), + err.Error(), + ) + return + } +} + +func waitDRTAccessRoleARNAssociationCreated(ctx context.Context, conn *shield.Shield, roleARN string, timeout time.Duration) (*shield.DescribeDRTAccessOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: []string{}, + Target: []string{statusNormal}, + Refresh: statusDRTAccessRoleARNAssociation(ctx, conn, roleARN), + Timeout: timeout, + NotFoundChecks: 20, + ContinuousTargetOccurence: 2, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*shield.DescribeDRTAccessOutput); ok { + return out, err + } + + return nil, err +} + +func waitDRTAccessRoleARNAssociationUpdated(ctx context.Context, conn *shield.Shield, roleARN string, timeout time.Duration) (*shield.DescribeDRTAccessOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: []string{statusChangePending}, + Target: []string{statusUpdated}, + Refresh: statusDRTAccessRoleARNAssociation(ctx, conn, roleARN), + Timeout: timeout, + NotFoundChecks: 20, + ContinuousTargetOccurence: 2, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*shield.DescribeDRTAccessOutput); ok { + return out, err + } + + return nil, err +} + +func waitDRTAccessRoleARNAssociationDeleted(ctx context.Context, conn *shield.Shield, roleARN string, timeout time.Duration) (*shield.DescribeDRTAccessOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: []string{statusDeleting, statusNormal}, + Target: []string{}, + Refresh: statusDRTAccessRoleARNAssociationDeleted(ctx, conn, roleARN), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if out, ok := outputRaw.(*shield.DescribeDRTAccessOutput); ok { + return out, err + } + + return nil, err +} + +func statusDRTAccessRoleARNAssociation(ctx context.Context, conn *shield.Shield, roleARN string) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + out, err := describeDRTAccessRoleARNAssociation(ctx, conn, roleARN) + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return out, statusNormal, nil + } +} + +func statusDRTAccessRoleARNAssociationDeleted(ctx context.Context, conn *shield.Shield, roleARN string) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + out, err := describeDRTAccessRoleARNAssociation(ctx, conn, roleARN) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + if out.RoleArn != nil && aws.StringValue(out.RoleArn) == roleARN { + return out, statusDeleting, nil + } + + return out, statusDeleting, nil + } +} + +func describeDRTAccessRoleARNAssociation(ctx context.Context, conn *shield.Shield, roleARN string) (*shield.DescribeDRTAccessOutput, error) { + in := &shield.DescribeDRTAccessInput{} + + out, err := conn.DescribeDRTAccessWithContext(ctx, in) + if err != nil { + var nfe *shield.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: in, + } + } + } + + if out == nil || out.RoleArn == nil || aws.StringValue(out.RoleArn) != roleARN { + return nil, tfresource.NewEmptyResultError(in) + } + + return out, nil +} + +type resourceDRTAccessRoleARNAssociationData struct { + ID types.String `tfsdk:"id"` + RoleARN types.String `tfsdk:"role_arn"` + Timeouts timeouts.Value `tfsdk:"timeouts"` +} diff --git a/internal/service/shield/drt_access_role_arn_association_test.go b/internal/service/shield/drt_access_role_arn_association_test.go new file mode 100644 index 00000000000..c2162d52822 --- /dev/null +++ b/internal/service/shield/drt_access_role_arn_association_test.go @@ -0,0 +1,181 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package shield_test + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/shield" + 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" + tfshield "github.com/hashicorp/terraform-provider-aws/internal/service/shield" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// Acceptance test access AWS and cost money to run. +func TestAccShieldDRTAccessRoleARNAssociation_basic(t *testing.T) { + ctx := acctest.Context(t) + + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var drtaccessrolearnassociation shield.DescribeDRTAccessOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_shield_drt_access_role_arn_association.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckRoleARN(ctx, t) + }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckDRTAccessRoleARNAssociationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccDRTAccessRoleARNAssociationConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckDRTAccessRoleARNAssociationExists(ctx, resourceName, &drtaccessrolearnassociation), + ), + }, + }, + }) +} + +func TestAccShieldDRTAccessRoleARNAssociation_disappears(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var drtaccessrolearnassociation shield.DescribeDRTAccessOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_shield_drt_access_role_arn_association.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckRoleARN(ctx, t) + }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckDRTAccessRoleARNAssociationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccDRTAccessRoleARNAssociationConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckDRTAccessRoleARNAssociationExists(ctx, resourceName, &drtaccessrolearnassociation), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfshield.ResourceDRTAccessRoleARNAssociation, resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckDRTAccessRoleARNAssociationDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).ShieldConn(ctx) + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_shield_drt_access_role_arn_association" { + continue + } + + input := &shield.DescribeDRTAccessInput{} + resp, err := conn.DescribeDRTAccessWithContext(ctx, input) + + if errs.IsA[*shield.ResourceNotFoundException](err) { + return nil + } + + if resp != nil && (resp.RoleArn == nil || *resp.RoleArn == "") { + return nil + } + + return create.Error(names.Shield, create.ErrActionCheckingDestroyed, tfshield.ResNameDRTAccessRoleARNAssociation, rs.Primary.ID, errors.New("not destroyed")) + } + return nil + } +} + +func testAccCheckDRTAccessRoleARNAssociationExists(ctx context.Context, name string, drtaccessrolearnassociation *shield.DescribeDRTAccessOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.Shield, create.ErrActionCheckingExistence, tfshield.ResNameDRTAccessLogBucketAssociation, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.Shield, create.ErrActionCheckingExistence, tfshield.ResNameDRTAccessLogBucketAssociation, name, errors.New("not set")) + } + conn := acctest.Provider.Meta().(*conns.AWSClient).ShieldConn(ctx) + resp, err := conn.DescribeDRTAccessWithContext(ctx, &shield.DescribeDRTAccessInput{}) + if err != nil { + return create.Error(names.Shield, create.ErrActionCheckingExistence, tfshield.ResNameDRTAccessRoleARNAssociation, "testing", err) + } + + *drtaccessrolearnassociation = *resp + return nil + } +} + +func testAccPreCheckRoleARN(ctx context.Context, t *testing.T) { + conn := acctest.Provider.Meta().(*conns.AWSClient).ShieldConn(ctx) + + input := &shield.DescribeDRTAccessInput{} + _, err := conn.DescribeDRTAccessWithContext(ctx, input) + if acctest.PreCheckSkipError(err) { + t.Skipf("skipping acceptance testing: %s", err) + } + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } +} + +func testAccDRTAccessRoleARNAssociationConfig_basic(rName string) string { + return fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_iam_role" "test" { + name = %[1]q + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + "Sid" : "", + "Effect" : "Allow", + "Principal" : { + "Service" : "drt.shield.amazonaws.com" + }, + "Action" : "sts:AssumeRole" + }, + ] + }) +} + +resource "aws_shield_protection_group" "test" { + protection_group_id = %[1]q + aggregation = "MAX" + pattern = "ALL" +} + +resource "aws_iam_role_policy_attachment" "test" { + role = aws_iam_role.test.name + policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AWSShieldDRTAccessPolicy" +} + +resource "aws_shield_drt_access_role_arn_association" "test" { + role_arn = aws_iam_role.test.arn + + depends_on = [aws_iam_role_policy_attachment.test] +} +`, rName) +} diff --git a/internal/service/shield/exports_test.go b/internal/service/shield/exports_test.go new file mode 100644 index 00000000000..4b16a87d49d --- /dev/null +++ b/internal/service/shield/exports_test.go @@ -0,0 +1,10 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package shield + +// Exports for use in tests only. +var ( + ResourceDRTAccessRoleARNAssociation = newResourceDRTAccessRoleARNAssociation + ResourceDRTAccessLogBucketAssociation = newResourceDRTAccessLogBucketAssociation +) diff --git a/internal/service/shield/service_package_gen.go b/internal/service/shield/service_package_gen.go index 91d134d2748..8d9b5a41054 100644 --- a/internal/service/shield/service_package_gen.go +++ b/internal/service/shield/service_package_gen.go @@ -17,7 +17,16 @@ func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.Serv } func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { - return []*types.ServicePackageFrameworkResource{} + return []*types.ServicePackageFrameworkResource{ + { + Factory: newResourceDRTAccessLogBucketAssociation, + Name: "DRT Access Log Bucket Association", + }, + { + Factory: newResourceDRTAccessRoleARNAssociation, + Name: "DRT Access Role ARN Association", + }, + } } func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePackageSDKDataSource { diff --git a/website/docs/r/shield_drt_access_log_bucket_association.html.markdown b/website/docs/r/shield_drt_access_log_bucket_association.html.markdown new file mode 100644 index 00000000000..bc391f86ba3 --- /dev/null +++ b/website/docs/r/shield_drt_access_log_bucket_association.html.markdown @@ -0,0 +1,36 @@ +--- +subcategory: "Shield" +layout: "aws" +page_title: "AWS: aws_shield_drt_access_log_bucket_association" +description: |- + Terraform resource for managing an AWS Shield DRT Access Log Bucket Association. +--- +# Resource: aws_shield_drt_access_log_bucket_association + +Terraform resource for managing an AWS Shield DRT Access Log Bucket Association. Up to 10 log buckets can be associated for DRT Access sharing with the Shield Response Team (SRT). + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_shield_drt_access_role_arn_association" "test" { + role_arn = "arn:aws:iam:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:${var.shield_drt_access_role_name}" +} + +resource "aws_shield_drt_access_log_bucket_association" "test" { + log_bucket = var.shield_drt_access_log_bucket + role_arn_association_id = aws_shield_drt_access_role_arn_association.test.id +} +``` + +## Argument Reference + +The following arguments are required: + +* `log_bucket` - (Required) The Amazon S3 bucket that contains the logs that you want to share. +* `role_arn_association_id` - (Required) The ID of the Role Arn association used for allowing Shield DRT Access. + +## Attribute Reference + +This resource exports no additional attributes. diff --git a/website/docs/r/shield_drt_access_role_arn_association.html.markdown b/website/docs/r/shield_drt_access_role_arn_association.html.markdown new file mode 100644 index 00000000000..efdc1437e7d --- /dev/null +++ b/website/docs/r/shield_drt_access_role_arn_association.html.markdown @@ -0,0 +1,52 @@ +--- +subcategory: "Shield" +layout: "aws" +page_title: "AWS: aws_shield_drt_access_role_arn_association" +description: |- + Terraform resource for managing an AWS Shield DRT Access Role Arn Association. +--- +# Resource: aws_shield_drt_access_role_arn_association + +Authorizes the Shield Response Team (SRT) using the specified role, to access your AWS account to assist with DDoS attack mitigation during potential attacks. For more information see [Configure AWS SRT Support](https://docs.aws.amazon.com/waf/latest/developerguide/authorize-srt.html) + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_iam_role" "test" { + name = var.aws_shield_drt_access_role_arn + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + "Sid" : "", + "Effect" : "Allow", + "Principal" : { + "Service" : "drt.shield.amazonaws.com" + }, + "Action" : "sts:AssumeRole" + }, + ] + }) +} + +resource "aws_iam_role_policy_attachment" "test" { + role = aws_iam_role.test.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSShieldDRTAccessPolicy" +} + +resource "aws_shield_drt_access_role_arn_association" "test" { + role_arn = aws_iam_role.test.arn +} +``` + +## Argument Reference + +The following arguments are required: + +* `role_arn` - (Required) The Amazon Resource Name (ARN) of the role the SRT will use to access your AWS account. Prior to making the AssociateDRTRole request, you must attach the `AWSShieldDRTAccessPolicy` managed policy to this role. + +## Attribute Reference + +This resource exports no additional attributes.