From 6d15af759aed73af870ab320d764a41920af275a Mon Sep 17 00:00:00 2001 From: twelsh-aw <84401379+twelsh-aw@users.noreply.github.com> Date: Sat, 10 Feb 2024 09:59:38 -0500 Subject: [PATCH 01/71] Add Security Hub organization_configuration support --- .../securityhub/organization_configuration.go | 121 ++++++++++++++++-- 1 file changed, 108 insertions(+), 13 deletions(-) diff --git a/internal/service/securityhub/organization_configuration.go b/internal/service/securityhub/organization_configuration.go index aa9b6d6f918..16f84e3d2ff 100644 --- a/internal/service/securityhub/organization_configuration.go +++ b/internal/service/securityhub/organization_configuration.go @@ -5,7 +5,9 @@ package securityhub import ( "context" + "fmt" "log" + "time" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/securityhub" @@ -43,6 +45,25 @@ func ResourceOrganizationConfiguration() *schema.Resource { Computed: true, ValidateDiagFunc: enum.Validate[types.AutoEnableStandards](), }, + "organization_configuration": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "configuration_type": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[types.OrganizationConfigurationConfigurationType](), + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, }, } } @@ -59,6 +80,10 @@ func resourceOrganizationConfigurationUpdate(ctx context.Context, d *schema.Reso input.AutoEnableStandards = types.AutoEnableStandards(v.(string)) } + if v, ok := d.GetOk("organization_configuration"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.OrganizationConfiguration = expandOrganizationConfiguration(v.([]interface{})[0].(map[string]interface{})) + } + _, err := conn.UpdateOrganizationConfiguration(ctx, input) if err != nil { @@ -76,7 +101,7 @@ func resourceOrganizationConfigurationRead(ctx context.Context, d *schema.Resour var diags diag.Diagnostics conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) - output, err := FindOrganizationConfiguration(ctx, conn) + output, err := waitOrganizationConfigurationEnabled(ctx, conn, d.Timeout(schema.TimeoutDefault)) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Security Hub Organization Configuration %s not found, removing from state", d.Id()) @@ -91,28 +116,98 @@ func resourceOrganizationConfigurationRead(ctx context.Context, d *schema.Resour d.Set("auto_enable", output.AutoEnable) d.Set("auto_enable_standards", output.AutoEnableStandards) + if err := d.Set("organization_configuration", []interface{}{flattenOrganizationConfiguration(output.OrganizationConfiguration)}); err != nil { + return sdkdiag.AppendErrorf(diags, "setting organization_configuration: %s", err) + } else { + d.Set("organization_configuration", nil) + } + return diags } -func FindOrganizationConfiguration(ctx context.Context, conn *securityhub.Client) (*securityhub.DescribeOrganizationConfigurationOutput, error) { - input := &securityhub.DescribeOrganizationConfigurationInput{} +func findOrganizationConfiguration(ctx context.Context, conn *securityhub.Client) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + input := &securityhub.DescribeOrganizationConfigurationInput{} + output, err := conn.DescribeOrganizationConfiguration(ctx, input) - output, err := conn.DescribeOrganizationConfiguration(ctx, input) + if tfawserr.ErrCodeEquals(err, errCodeResourceNotFoundException) || tfawserr.ErrMessageContains(err, errCodeInvalidAccessException, "not subscribed to AWS Security Hub") { + return nil, "", &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } - if tfawserr.ErrCodeEquals(err, errCodeResourceNotFoundException) || tfawserr.ErrMessageContains(err, errCodeInvalidAccessException, "not subscribed to AWS Security Hub") { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: input, + if err != nil { + return nil, "", err + } + + if output == nil || output.OrganizationConfiguration == nil { + return nil, "", tfresource.NewEmptyResultError(input) + } + + switch output.OrganizationConfiguration.Status { + case types.OrganizationConfigurationStatusPending: + return nil, "", nil + case types.OrganizationConfigurationStatusEnabled: + return output, string(output.OrganizationConfiguration.Status), nil + default: + var statusErr error + if msg := output.OrganizationConfiguration.StatusMessage; msg != nil && len(*msg) > 0 { + statusErr = fmt.Errorf("StatusMessage: %s", *msg) + } + return nil, "", &retry.UnexpectedStateError{ + LastError: statusErr, + State: string(output.OrganizationConfiguration.Status), + ExpectedState: []string{ + string(types.OrganizationConfigurationStatusEnabled), + string(types.OrganizationConfigurationStatusPending), + }, + } } } +} - if err != nil { - return nil, err +func waitOrganizationConfigurationEnabled(ctx context.Context, conn *securityhub.Client, timeout time.Duration) (*securityhub.DescribeOrganizationConfigurationOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(types.OrganizationConfigurationStatusPending), + Target: enum.Slice(types.OrganizationConfigurationStatusEnabled), + Refresh: findOrganizationConfiguration(ctx, conn), + Timeout: timeout, + NotFoundChecks: 20, + ContinuousTargetOccurence: 2, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*securityhub.DescribeOrganizationConfigurationOutput); ok { + return out, err + } + + return nil, err +} + +func expandOrganizationConfiguration(tfMap map[string]interface{}) *types.OrganizationConfiguration { + if tfMap == nil { + return nil + } + + apiObject := &types.OrganizationConfiguration{} + + if v, ok := tfMap["configuration_type"].(string); ok && len(v) > 0 { + apiObject.ConfigurationType = types.OrganizationConfigurationConfigurationType(v) + } + + return apiObject +} + +func flattenOrganizationConfiguration(apiObject *types.OrganizationConfiguration) map[string]interface{} { + if apiObject == nil { + return nil } - if output == nil || output.OrganizationConfiguration == nil { - return nil, tfresource.NewEmptyResultError(input) + tfMap := map[string]interface{}{ + "configuration_type": apiObject.ConfigurationType, + "status": apiObject.Status, } - return output, nil + return tfMap } From 89576dd4b08799ae8e599cc7563d86040e6692b9 Mon Sep 17 00:00:00 2001 From: twelsh-aw <84401379+twelsh-aw@users.noreply.github.com> Date: Sat, 10 Feb 2024 16:50:53 -0500 Subject: [PATCH 02/71] Add tests and fixes --- .../securityhub/organization_configuration.go | 11 +- .../organization_configuration_test.go | 117 +++++++++++++++++- .../service/securityhub/securityhub_test.go | 5 +- 3 files changed, 123 insertions(+), 10 deletions(-) diff --git a/internal/service/securityhub/organization_configuration.go b/internal/service/securityhub/organization_configuration.go index 16f84e3d2ff..9744196afe7 100644 --- a/internal/service/securityhub/organization_configuration.go +++ b/internal/service/securityhub/organization_configuration.go @@ -48,7 +48,6 @@ func ResourceOrganizationConfiguration() *schema.Resource { "organization_configuration": { Type: schema.TypeList, Optional: true, - Computed: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -101,7 +100,7 @@ func resourceOrganizationConfigurationRead(ctx context.Context, d *schema.Resour var diags diag.Diagnostics conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) - output, err := waitOrganizationConfigurationEnabled(ctx, conn, d.Timeout(schema.TimeoutDefault)) + output, err := WaitOrganizationConfigurationEnabled(ctx, conn, d.Timeout(schema.TimeoutDefault)) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Security Hub Organization Configuration %s not found, removing from state", d.Id()) @@ -118,8 +117,6 @@ func resourceOrganizationConfigurationRead(ctx context.Context, d *schema.Resour if err := d.Set("organization_configuration", []interface{}{flattenOrganizationConfiguration(output.OrganizationConfiguration)}); err != nil { return sdkdiag.AppendErrorf(diags, "setting organization_configuration: %s", err) - } else { - d.Set("organization_configuration", nil) } return diags @@ -148,7 +145,7 @@ func findOrganizationConfiguration(ctx context.Context, conn *securityhub.Client switch output.OrganizationConfiguration.Status { case types.OrganizationConfigurationStatusPending: return nil, "", nil - case types.OrganizationConfigurationStatusEnabled: + case types.OrganizationConfigurationStatusEnabled, "": return output, string(output.OrganizationConfiguration.Status), nil default: var statusErr error @@ -167,10 +164,10 @@ func findOrganizationConfiguration(ctx context.Context, conn *securityhub.Client } } -func waitOrganizationConfigurationEnabled(ctx context.Context, conn *securityhub.Client, timeout time.Duration) (*securityhub.DescribeOrganizationConfigurationOutput, error) { +func WaitOrganizationConfigurationEnabled(ctx context.Context, conn *securityhub.Client, timeout time.Duration) (*securityhub.DescribeOrganizationConfigurationOutput, error) { stateConf := &retry.StateChangeConf{ Pending: enum.Slice(types.OrganizationConfigurationStatusPending), - Target: enum.Slice(types.OrganizationConfigurationStatusEnabled), + Target: append(enum.Slice(types.OrganizationConfigurationStatusEnabled), ""), Refresh: findOrganizationConfiguration(ctx, conn), Timeout: timeout, NotFoundChecks: 20, diff --git a/internal/service/securityhub/organization_configuration_test.go b/internal/service/securityhub/organization_configuration_test.go index c53450af29e..f55244f398a 100644 --- a/internal/service/securityhub/organization_configuration_test.go +++ b/internal/service/securityhub/organization_configuration_test.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "testing" + "time" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" @@ -32,6 +33,7 @@ func testAccOrganizationConfiguration_basic(t *testing.T) { testAccOrganizationConfigurationExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "auto_enable", "true"), resource.TestCheckResourceAttr(resourceName, "auto_enable_standards", "DEFAULT"), + resource.TestCheckResourceAttr(resourceName, "organization_configuration.#", "0"), ), }, { @@ -45,6 +47,7 @@ func testAccOrganizationConfiguration_basic(t *testing.T) { testAccOrganizationConfigurationExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "auto_enable", "false"), resource.TestCheckResourceAttr(resourceName, "auto_enable_standards", "DEFAULT"), + resource.TestCheckResourceAttr(resourceName, "organization_configuration.#", "0"), ), }, }, @@ -67,6 +70,7 @@ func testAccOrganizationConfiguration_autoEnableStandards(t *testing.T) { testAccOrganizationConfigurationExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "auto_enable", "true"), resource.TestCheckResourceAttr(resourceName, "auto_enable_standards", "DEFAULT"), + resource.TestCheckResourceAttr(resourceName, "organization_configuration.#", "0"), ), }, { @@ -80,6 +84,87 @@ func testAccOrganizationConfiguration_autoEnableStandards(t *testing.T) { testAccOrganizationConfigurationExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "auto_enable", "true"), resource.TestCheckResourceAttr(resourceName, "auto_enable_standards", "NONE"), + resource.TestCheckResourceAttr(resourceName, "organization_configuration.#", "0"), + ), + }, + }, + }) +} + +// CENTRAL configuration has unique set of constraints vs other SecurityHub organization_config: +// +// 1. Must be done from a *member* delegated admin account: +// "Central configuration couldn't be enabled because the organization management account is designated as the delegated Security Hub administrator account." +// "Designate a different account as the delegated administrator, and retry." +// +// To allow for this the following is a multi-account test: +// The primary provider is expected to be a member account and alternate provider a management account. +// +// 2. Dependencies on DelegatedAdmin and FindingAggregators invert (!) after central config is created. +// "Finding Aggregator must be created to enable Central Configuration" +// "You must [...] disable central configuration in order to remove or change your aggregation Region." +// "You must [...] disable central configuration in order to remove or change the delegated Security Hub administrator" +// +// Due to this API behaviour, this resource isn't very terraform friendly. +func TestAccOrganizationConfiguration_centralConfiguration(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_securityhub_organization_configuration.test" + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckAlternateAccount(t) + acctest.PreCheckAlternateRegionIs(t, acctest.Region()) + acctest.PreCheckOrganizationMemberAccount(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + { + // Start with LOCAL e.g default behaviour + // This allows us to create an finding_aggregator in test without breaking dependency flow on destroy + Config: testAccOrganizationConfigurationConfig_centralConfiguration(true, "DEFAULT", "LOCAL"), + Check: resource.ComposeTestCheckFunc( + testAccOrganizationConfigurationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "auto_enable", "true"), + resource.TestCheckResourceAttr(resourceName, "auto_enable_standards", "DEFAULT"), + resource.TestCheckResourceAttr(resourceName, "organization_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "organization_configuration.0.configuration_type", "LOCAL"), + resource.TestCheckResourceAttr(resourceName, "organization_configuration.0.status", "ENABLED"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + // Enable CENTRAL configuration + { + Config: testAccOrganizationConfigurationConfig_centralConfiguration(false, "NONE", "CENTRAL"), + Check: resource.ComposeTestCheckFunc( + testAccOrganizationConfigurationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "auto_enable", "false"), + resource.TestCheckResourceAttr(resourceName, "auto_enable_standards", "NONE"), + resource.TestCheckResourceAttr(resourceName, "organization_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "organization_configuration.0.configuration_type", "CENTRAL"), + resource.TestCheckResourceAttr(resourceName, "organization_configuration.0.status", "ENABLED"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + // Go back to LOCAL; this allows us to destroy the delegated admin with the given dependency flow that is necessary for creates. + { + Config: testAccOrganizationConfigurationConfig_centralConfiguration(true, "DEFAULT", "LOCAL"), + Check: resource.ComposeTestCheckFunc( + testAccOrganizationConfigurationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "auto_enable", "true"), + resource.TestCheckResourceAttr(resourceName, "auto_enable_standards", "DEFAULT"), + resource.TestCheckResourceAttr(resourceName, "organization_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "organization_configuration.0.configuration_type", "LOCAL"), + resource.TestCheckResourceAttr(resourceName, "organization_configuration.0.status", "ENABLED"), ), }, }, @@ -95,7 +180,7 @@ func testAccOrganizationConfigurationExists(ctx context.Context, n string) resou conn := acctest.Provider.Meta().(*conns.AWSClient).SecurityHubClient(ctx) - _, err := tfsecurityhub.FindOrganizationConfiguration(ctx, conn) + _, err := tfsecurityhub.WaitOrganizationConfigurationEnabled(ctx, conn, 2*time.Minute) return err } @@ -133,3 +218,33 @@ resource "aws_securityhub_organization_configuration" "test" { } `, autoEnableStandards)) } + +func testAccOrganizationConfigurationConfig_centralConfiguration(autoEnable bool, autoEnableStandards, configType string) string { + return acctest.ConfigCompose( + acctest.ConfigAlternateAccountProvider(), + fmt.Sprintf(` +data "aws_caller_identity" "member" {} + +resource "aws_securityhub_organization_admin_account" "test" { + admin_account_id = data.aws_caller_identity.member.account_id + + provider = awsalternate +} + +resource "aws_securityhub_finding_aggregator" "test" { + linking_mode = "ALL_REGIONS" + + depends_on = [aws_securityhub_organization_admin_account.test] +} + +resource "aws_securityhub_organization_configuration" "test" { + auto_enable = %[1]t + auto_enable_standards = %[2]q + organization_configuration { + configuration_type = %[3]q + } + + depends_on = [aws_securityhub_organization_admin_account.test] +} +`, autoEnable, autoEnableStandards, configType)) +} diff --git a/internal/service/securityhub/securityhub_test.go b/internal/service/securityhub/securityhub_test.go index 189af9b1c0c..68fe1baf21d 100644 --- a/internal/service/securityhub/securityhub_test.go +++ b/internal/service/securityhub/securityhub_test.go @@ -53,8 +53,9 @@ func TestAccSecurityHub_serial(t *testing.T) { "MultiRegion": testAccOrganizationAdminAccount_MultiRegion, }, "OrganizationConfiguration": { - "basic": testAccOrganizationConfiguration_basic, - "AutoEnableStandards": testAccOrganizationConfiguration_autoEnableStandards, + "basic": testAccOrganizationConfiguration_basic, + "AutoEnableStandards": testAccOrganizationConfiguration_autoEnableStandards, + "CentralConfiguration": TestAccOrganizationConfiguration_centralConfiguration, }, "ProductSubscription": { "basic": testAccProductSubscription_basic, From 77ff562e9bceb87c67ef7285d7fe56f88eb4a8f6 Mon Sep 17 00:00:00 2001 From: twelsh-aw <84401379+twelsh-aw@users.noreply.github.com> Date: Sat, 10 Feb 2024 17:43:44 -0500 Subject: [PATCH 03/71] Implement delete and cleanup misleading comments The dependency order is sound here... we just didn't code anything to destroy. --- .../securityhub/organization_configuration.go | 4 +- .../organization_configuration_test.go | 37 ++----------------- 2 files changed, 6 insertions(+), 35 deletions(-) diff --git a/internal/service/securityhub/organization_configuration.go b/internal/service/securityhub/organization_configuration.go index 9744196afe7..137d7ff4ced 100644 --- a/internal/service/securityhub/organization_configuration.go +++ b/internal/service/securityhub/organization_configuration.go @@ -28,7 +28,9 @@ func ResourceOrganizationConfiguration() *schema.Resource { CreateWithoutTimeout: resourceOrganizationConfigurationUpdate, ReadWithoutTimeout: resourceOrganizationConfigurationRead, UpdateWithoutTimeout: resourceOrganizationConfigurationUpdate, - DeleteWithoutTimeout: schema.NoopContext, + // on delete reset to a default configuration. + // non-default CENTRAL configuration blocks dependent resources from being destroyed. + DeleteWithoutTimeout: resourceOrganizationConfigurationUpdate, Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, diff --git a/internal/service/securityhub/organization_configuration_test.go b/internal/service/securityhub/organization_configuration_test.go index f55244f398a..392bebcdde0 100644 --- a/internal/service/securityhub/organization_configuration_test.go +++ b/internal/service/securityhub/organization_configuration_test.go @@ -91,21 +91,10 @@ func testAccOrganizationConfiguration_autoEnableStandards(t *testing.T) { }) } -// CENTRAL configuration has unique set of constraints vs other SecurityHub organization_config: -// -// 1. Must be done from a *member* delegated admin account: +// CENTRAL configuration must be done from a *member* delegated admin account: // "Central configuration couldn't be enabled because the organization management account is designated as the delegated Security Hub administrator account." // "Designate a different account as the delegated administrator, and retry." -// -// To allow for this the following is a multi-account test: -// The primary provider is expected to be a member account and alternate provider a management account. -// -// 2. Dependencies on DelegatedAdmin and FindingAggregators invert (!) after central config is created. -// "Finding Aggregator must be created to enable Central Configuration" -// "You must [...] disable central configuration in order to remove or change your aggregation Region." -// "You must [...] disable central configuration in order to remove or change the delegated Security Hub administrator" -// -// Due to this API behaviour, this resource isn't very terraform friendly. +// The primary provider is expected to be a member account and the alternate provider a management account. func TestAccOrganizationConfiguration_centralConfiguration(t *testing.T) { ctx := acctest.Context(t) resourceName := "aws_securityhub_organization_configuration.test" @@ -120,25 +109,6 @@ func TestAccOrganizationConfiguration_centralConfiguration(t *testing.T) { ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ - { - // Start with LOCAL e.g default behaviour - // This allows us to create an finding_aggregator in test without breaking dependency flow on destroy - Config: testAccOrganizationConfigurationConfig_centralConfiguration(true, "DEFAULT", "LOCAL"), - Check: resource.ComposeTestCheckFunc( - testAccOrganizationConfigurationExists(ctx, resourceName), - resource.TestCheckResourceAttr(resourceName, "auto_enable", "true"), - resource.TestCheckResourceAttr(resourceName, "auto_enable_standards", "DEFAULT"), - resource.TestCheckResourceAttr(resourceName, "organization_configuration.#", "1"), - resource.TestCheckResourceAttr(resourceName, "organization_configuration.0.configuration_type", "LOCAL"), - resource.TestCheckResourceAttr(resourceName, "organization_configuration.0.status", "ENABLED"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - // Enable CENTRAL configuration { Config: testAccOrganizationConfigurationConfig_centralConfiguration(false, "NONE", "CENTRAL"), Check: resource.ComposeTestCheckFunc( @@ -155,7 +125,6 @@ func TestAccOrganizationConfiguration_centralConfiguration(t *testing.T) { ImportState: true, ImportStateVerify: true, }, - // Go back to LOCAL; this allows us to destroy the delegated admin with the given dependency flow that is necessary for creates. { Config: testAccOrganizationConfigurationConfig_centralConfiguration(true, "DEFAULT", "LOCAL"), Check: resource.ComposeTestCheckFunc( @@ -244,7 +213,7 @@ resource "aws_securityhub_organization_configuration" "test" { configuration_type = %[3]q } - depends_on = [aws_securityhub_organization_admin_account.test] + depends_on = [aws_securityhub_finding_aggregator.test] } `, autoEnable, autoEnableStandards, configType)) } From 4786a7c3410e298b0508c83269e5388becbea2d5 Mon Sep 17 00:00:00 2001 From: twelsh-aw <84401379+twelsh-aw@users.noreply.github.com> Date: Sun, 18 Feb 2024 15:22:04 -0500 Subject: [PATCH 04/71] Cleanups for organization_configuration - separate test suite for future resources - dedicated destroy function for clarity - refactor some common test setup for member account delegated admin --- .../securityhub/organization_configuration.go | 43 +++++++++--- .../organization_configuration_test.go | 67 +++++++++++-------- .../service/securityhub/securityhub_test.go | 32 ++++++++- 3 files changed, 100 insertions(+), 42 deletions(-) diff --git a/internal/service/securityhub/organization_configuration.go b/internal/service/securityhub/organization_configuration.go index 137d7ff4ced..db9b08eeb65 100644 --- a/internal/service/securityhub/organization_configuration.go +++ b/internal/service/securityhub/organization_configuration.go @@ -28,9 +28,7 @@ func ResourceOrganizationConfiguration() *schema.Resource { CreateWithoutTimeout: resourceOrganizationConfigurationUpdate, ReadWithoutTimeout: resourceOrganizationConfigurationRead, UpdateWithoutTimeout: resourceOrganizationConfigurationUpdate, - // on delete reset to a default configuration. - // non-default CENTRAL configuration blocks dependent resources from being destroyed. - DeleteWithoutTimeout: resourceOrganizationConfigurationUpdate, + DeleteWithoutTimeout: resourceOrganizationConfigurationDelete, Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, @@ -58,10 +56,6 @@ func ResourceOrganizationConfiguration() *schema.Resource { Required: true, ValidateDiagFunc: enum.Validate[types.OrganizationConfigurationConfigurationType](), }, - "status": { - Type: schema.TypeString, - Computed: true, - }, }, }, }, @@ -98,11 +92,41 @@ func resourceOrganizationConfigurationUpdate(ctx context.Context, d *schema.Reso return append(diags, resourceOrganizationConfigurationRead(ctx, d, meta)...) } +// resourceOrganizationConfigurationDelete destroys the organizations configuration resource by updating it to a disabled configuration. +// If orgnanization configuration is of type central, then dependent resources (i.e finding_aggregator, delegated_admin) cannot be removed from AWS. +// Updating the organization configuration on destroy is necessary to allow dependent resources to be able to be cleaned up. +func resourceOrganizationConfigurationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) + + input := &securityhub.UpdateOrganizationConfigurationInput{ + AutoEnable: aws.Bool(false), + AutoEnableStandards: types.AutoEnableStandardsNone, + OrganizationConfiguration: &types.OrganizationConfiguration{ + ConfigurationType: types.OrganizationConfigurationConfigurationTypeLocal, + }, + } + _, err := conn.UpdateOrganizationConfiguration(ctx, input) + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting Security Hub Organization Configuration (%s): %s", d.Id(), err) + } + + _, err = waitOrganizationConfigurationEnabled(ctx, conn, organizationsConfigurationStatusTimeout) + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting Security Hub Organization Configuration (%s): %s", d.Id(), err) + } + + d.SetId("") + return diags +} + +const organizationsConfigurationStatusTimeout = 300 * time.Second + func resourceOrganizationConfigurationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) - output, err := WaitOrganizationConfigurationEnabled(ctx, conn, d.Timeout(schema.TimeoutDefault)) + output, err := waitOrganizationConfigurationEnabled(ctx, conn, organizationsConfigurationStatusTimeout) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Security Hub Organization Configuration %s not found, removing from state", d.Id()) @@ -166,7 +190,7 @@ func findOrganizationConfiguration(ctx context.Context, conn *securityhub.Client } } -func WaitOrganizationConfigurationEnabled(ctx context.Context, conn *securityhub.Client, timeout time.Duration) (*securityhub.DescribeOrganizationConfigurationOutput, error) { +func waitOrganizationConfigurationEnabled(ctx context.Context, conn *securityhub.Client, timeout time.Duration) (*securityhub.DescribeOrganizationConfigurationOutput, error) { stateConf := &retry.StateChangeConf{ Pending: enum.Slice(types.OrganizationConfigurationStatusPending), Target: append(enum.Slice(types.OrganizationConfigurationStatusEnabled), ""), @@ -205,7 +229,6 @@ func flattenOrganizationConfiguration(apiObject *types.OrganizationConfiguration tfMap := map[string]interface{}{ "configuration_type": apiObject.ConfigurationType, - "status": apiObject.Status, } return tfMap diff --git a/internal/service/securityhub/organization_configuration_test.go b/internal/service/securityhub/organization_configuration_test.go index 392bebcdde0..36ac314b1d4 100644 --- a/internal/service/securityhub/organization_configuration_test.go +++ b/internal/service/securityhub/organization_configuration_test.go @@ -7,13 +7,13 @@ import ( "context" "fmt" "testing" - "time" + "github.com/aws/aws-sdk-go-v2/service/securityhub" + "github.com/aws/aws-sdk-go-v2/service/securityhub/types" "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" - tfsecurityhub "github.com/hashicorp/terraform-provider-aws/internal/service/securityhub" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -25,12 +25,12 @@ func testAccOrganizationConfiguration_basic(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckOrganizationManagementAccount(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: acctest.CheckDestroyNoop, + CheckDestroy: testAccCheckOrganizationConfigurationDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccOrganizationConfigurationConfig_basic(true), Check: resource.ComposeTestCheckFunc( - testAccOrganizationConfigurationExists(ctx, resourceName), + testAccCheckOrganizationConfigurationExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "auto_enable", "true"), resource.TestCheckResourceAttr(resourceName, "auto_enable_standards", "DEFAULT"), resource.TestCheckResourceAttr(resourceName, "organization_configuration.#", "0"), @@ -44,7 +44,7 @@ func testAccOrganizationConfiguration_basic(t *testing.T) { { Config: testAccOrganizationConfigurationConfig_basic(false), Check: resource.ComposeTestCheckFunc( - testAccOrganizationConfigurationExists(ctx, resourceName), + testAccCheckOrganizationConfigurationExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "auto_enable", "false"), resource.TestCheckResourceAttr(resourceName, "auto_enable_standards", "DEFAULT"), resource.TestCheckResourceAttr(resourceName, "organization_configuration.#", "0"), @@ -62,12 +62,12 @@ func testAccOrganizationConfiguration_autoEnableStandards(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckOrganizationManagementAccount(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: acctest.CheckDestroyNoop, + CheckDestroy: testAccCheckOrganizationConfigurationDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccOrganizationConfigurationConfig_autoEnableStandards("DEFAULT"), Check: resource.ComposeTestCheckFunc( - testAccOrganizationConfigurationExists(ctx, resourceName), + testAccCheckOrganizationConfigurationExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "auto_enable", "true"), resource.TestCheckResourceAttr(resourceName, "auto_enable_standards", "DEFAULT"), resource.TestCheckResourceAttr(resourceName, "organization_configuration.#", "0"), @@ -81,7 +81,7 @@ func testAccOrganizationConfiguration_autoEnableStandards(t *testing.T) { { Config: testAccOrganizationConfigurationConfig_autoEnableStandards("NONE"), Check: resource.ComposeTestCheckFunc( - testAccOrganizationConfigurationExists(ctx, resourceName), + testAccCheckOrganizationConfigurationExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "auto_enable", "true"), resource.TestCheckResourceAttr(resourceName, "auto_enable_standards", "NONE"), resource.TestCheckResourceAttr(resourceName, "organization_configuration.#", "0"), @@ -91,11 +91,7 @@ func testAccOrganizationConfiguration_autoEnableStandards(t *testing.T) { }) } -// CENTRAL configuration must be done from a *member* delegated admin account: -// "Central configuration couldn't be enabled because the organization management account is designated as the delegated Security Hub administrator account." -// "Designate a different account as the delegated administrator, and retry." -// The primary provider is expected to be a member account and the alternate provider a management account. -func TestAccOrganizationConfiguration_centralConfiguration(t *testing.T) { +func testAccOrganizationConfiguration_centralConfiguration(t *testing.T) { ctx := acctest.Context(t) resourceName := "aws_securityhub_organization_configuration.test" resource.Test(t, resource.TestCase{ @@ -107,17 +103,16 @@ func TestAccOrganizationConfiguration_centralConfiguration(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), - CheckDestroy: acctest.CheckDestroyNoop, + CheckDestroy: testAccCheckOrganizationConfigurationDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccOrganizationConfigurationConfig_centralConfiguration(false, "NONE", "CENTRAL"), Check: resource.ComposeTestCheckFunc( - testAccOrganizationConfigurationExists(ctx, resourceName), + testAccCheckOrganizationConfigurationExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "auto_enable", "false"), resource.TestCheckResourceAttr(resourceName, "auto_enable_standards", "NONE"), resource.TestCheckResourceAttr(resourceName, "organization_configuration.#", "1"), resource.TestCheckResourceAttr(resourceName, "organization_configuration.0.configuration_type", "CENTRAL"), - resource.TestCheckResourceAttr(resourceName, "organization_configuration.0.status", "ENABLED"), ), }, { @@ -128,19 +123,18 @@ func TestAccOrganizationConfiguration_centralConfiguration(t *testing.T) { { Config: testAccOrganizationConfigurationConfig_centralConfiguration(true, "DEFAULT", "LOCAL"), Check: resource.ComposeTestCheckFunc( - testAccOrganizationConfigurationExists(ctx, resourceName), + testAccCheckOrganizationConfigurationExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "auto_enable", "true"), resource.TestCheckResourceAttr(resourceName, "auto_enable_standards", "DEFAULT"), resource.TestCheckResourceAttr(resourceName, "organization_configuration.#", "1"), resource.TestCheckResourceAttr(resourceName, "organization_configuration.0.configuration_type", "LOCAL"), - resource.TestCheckResourceAttr(resourceName, "organization_configuration.0.status", "ENABLED"), ), }, }, }) } -func testAccOrganizationConfigurationExists(ctx context.Context, n string) resource.TestCheckFunc { +func testAccCheckOrganizationConfigurationExists(ctx context.Context, n string) resource.TestCheckFunc { return func(s *terraform.State) error { _, ok := s.RootModule().Resources[n] if !ok { @@ -149,12 +143,34 @@ func testAccOrganizationConfigurationExists(ctx context.Context, n string) resou conn := acctest.Provider.Meta().(*conns.AWSClient).SecurityHubClient(ctx) - _, err := tfsecurityhub.WaitOrganizationConfigurationEnabled(ctx, conn, 2*time.Minute) - + _, err := conn.DescribeOrganizationConfiguration(ctx, &securityhub.DescribeOrganizationConfigurationInput{}) return err } } +func testAccCheckOrganizationConfigurationDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).SecurityHubClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_securityhub_organization_configuration" { + continue + } + + out, err := conn.DescribeOrganizationConfiguration(ctx, &securityhub.DescribeOrganizationConfigurationInput{}) + if err != nil { + return err + } + + if out != nil && out.OrganizationConfiguration != nil && out.OrganizationConfiguration.ConfigurationType == types.OrganizationConfigurationConfigurationTypeCentral { + return fmt.Errorf("Security Hub Organization Configuration (%s) still exists", rs.Primary.ID) + } + } + + return nil + } +} + const testAccOrganizationConfigurationConfig_base = ` resource "aws_securityhub_account" "test" {} @@ -191,15 +207,8 @@ resource "aws_securityhub_organization_configuration" "test" { func testAccOrganizationConfigurationConfig_centralConfiguration(autoEnable bool, autoEnableStandards, configType string) string { return acctest.ConfigCompose( acctest.ConfigAlternateAccountProvider(), + testAccMemberAccountDelegatedAdminConfig_base, fmt.Sprintf(` -data "aws_caller_identity" "member" {} - -resource "aws_securityhub_organization_admin_account" "test" { - admin_account_id = data.aws_caller_identity.member.account_id - - provider = awsalternate -} - resource "aws_securityhub_finding_aggregator" "test" { linking_mode = "ALL_REGIONS" diff --git a/internal/service/securityhub/securityhub_test.go b/internal/service/securityhub/securityhub_test.go index 68fe1baf21d..4a92389cf01 100644 --- a/internal/service/securityhub/securityhub_test.go +++ b/internal/service/securityhub/securityhub_test.go @@ -53,9 +53,8 @@ func TestAccSecurityHub_serial(t *testing.T) { "MultiRegion": testAccOrganizationAdminAccount_MultiRegion, }, "OrganizationConfiguration": { - "basic": testAccOrganizationConfiguration_basic, - "AutoEnableStandards": testAccOrganizationConfiguration_autoEnableStandards, - "CentralConfiguration": TestAccOrganizationConfiguration_centralConfiguration, + "basic": testAccOrganizationConfiguration_basic, + "AutoEnableStandards": testAccOrganizationConfiguration_autoEnableStandards, }, "ProductSubscription": { "basic": testAccProductSubscription_basic, @@ -77,3 +76,30 @@ func TestAccSecurityHub_serial(t *testing.T) { acctest.RunSerialTests2Levels(t, testCases, 0) } + +// TestAccSecurityHub_centralConfiguration is a multi-account test stuite for central configuration features. +// Central configuration can only be enabled from a *member* delegated admin account. +// The primary provider is expected to be an organizations member account and the alternate provider is expected to be the organizations management account. +func TestAccSecurityHub_centralConfiguration(t *testing.T) { + t.Parallel() + testCases := map[string]map[string]func(t *testing.T){ + "OrganizationConfiguration": { + "CentralConfiguration": testAccOrganizationConfiguration_centralConfiguration, + }, + } + acctest.RunSerialTests2Levels(t, testCases, 0) +} + +const testAccMemberAccountDelegatedAdminConfig_base = ` +resource "aws_securityhub_account" "test" {} + +data "aws_caller_identity" "member" {} + +resource "aws_securityhub_organization_admin_account" "test" { + provider = awsalternate + + admin_account_id = data.aws_caller_identity.member.account_id + + depends_on = [aws_securityhub_account.test] +} +` From 6c5ee7b0bdac8cb4b287af70a020e342839b5b2a Mon Sep 17 00:00:00 2001 From: twelsh-aw <84401379+twelsh-aw@users.noreply.github.com> Date: Mon, 19 Feb 2024 20:24:11 -0500 Subject: [PATCH 05/71] Add resource for configuration_policy --- .../securityhub/configuration_policy.go | 621 ++++++++++++++++++ .../securityhub/configuration_policy_test.go | 292 ++++++++ .../service/securityhub/securityhub_test.go | 4 + .../securityhub/service_package_gen.go | 4 + 4 files changed, 921 insertions(+) create mode 100644 internal/service/securityhub/configuration_policy.go create mode 100644 internal/service/securityhub/configuration_policy_test.go diff --git a/internal/service/securityhub/configuration_policy.go b/internal/service/securityhub/configuration_policy.go new file mode 100644 index 00000000000..41838093656 --- /dev/null +++ b/internal/service/securityhub/configuration_policy.go @@ -0,0 +1,621 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package securityhub + +import ( + "context" + "encoding/json" + "errors" + "log" + "math" + + "github.com/YakDriver/regexache" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/securityhub" + "github.com/aws/aws-sdk-go-v2/service/securityhub/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/enum" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +// @SDKResource("aws_securityhub_configuration_policy") +func ResourceConfigurationPolicy() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceConfigurationPolicyCreate, + ReadWithoutTimeout: resourceConfigurationPolicyRead, + UpdateWithoutTimeout: resourceConfigurationPolicyUpdate, + DeleteWithoutTimeout: resourceConfigurationPolicyDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringMatch( + regexache.MustCompile(`[A-Za-z0-9\-\.!*/]+`), + "Only alphanumeric characters and the following ASCII characters are permitted: -, ., !, *, /", + ), + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "security_hub_policy": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "service_enabled": { + Type: schema.TypeBool, + Required: true, + }, + "enabled_standard_arns": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: verify.ValidARN, + }, + }, + "security_controls_configuration": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "disabled_control_identifiers": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringIsNotEmpty, + }, + ConflictsWith: []string{ + "security_hub_policy.0.security_controls_configuration.0.enabled_control_identifiers", + }, + }, + "enabled_control_identifiers": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringIsNotEmpty, + }, + ConflictsWith: []string{ + "security_hub_policy.0.security_controls_configuration.0.disabled_control_identifiers", + }, + }, + "control_custom_parameter": { + Type: schema.TypeList, + Optional: true, + Description: "https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-controls-reference.html", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "control_identifier": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "parameter": { + Type: schema.TypeSet, + Required: true, + MinItems: 1, + Elem: customParameterResource(), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func customParameterResource() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "value_type": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[types.ParameterValueType](), + }, + "bool": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Required: true, + Type: schema.TypeBool, + }, + }, + }, + }, + "double": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Required: true, + Type: schema.TypeFloat, + }, + }, + }, + }, + "enum": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Required: true, + Type: schema.TypeString, + }, + }, + }, + }, + "enum_list": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Required: true, + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "int": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Required: true, + Type: schema.TypeInt, + ValidateFunc: validation.IntAtMost(math.MaxInt32), + }, + }, + }, + }, + "int_list": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Required: true, + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeInt, + ValidateFunc: validation.IntAtMost(math.MaxInt32), + }, + }, + }, + }, + }, + "string": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Required: true, + Type: schema.TypeString, + }, + }, + }, + }, + "string_list": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Required: true, + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + }, + } +} + +func resourceConfigurationPolicyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) + + input := &securityhub.CreateConfigurationPolicyInput{ + Name: aws.String(d.Get("name").(string)), + } + + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) + } + + if v, ok := d.Get("security_hub_policy").([]interface{}); ok && len(v) > 0 { + policy := expandSecurityHubPolicy(v[0].(map[string]interface{})) + if err := validateSecurityHubPolicy(policy); err != nil { + requestBody, _ := json.MarshalIndent(policy, "", " ") + return sdkdiag.AppendErrorf(diags, "creating Security Hub Configuration Policy (%s): %s, %s", *input.Name, err, string(requestBody)) + } + input.ConfigurationPolicy = policy + } + + out, err := conn.CreateConfigurationPolicy(ctx, input) + if err != nil { + requestBody, _ := json.MarshalIndent(input, "", " ") + return sdkdiag.AppendErrorf(diags, "creating Security Hub Configuration Policy (%s): %s for request %s", *input.Name, err, string(requestBody)) + } + + if d.IsNewResource() { + d.SetId(*out.Arn) + } + + return append(diags, resourceConfigurationPolicyRead(ctx, d, meta)...) +} + +func resourceConfigurationPolicyUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) + + input := &securityhub.UpdateConfigurationPolicyInput{ + Identifier: aws.String(d.Id()), + Name: aws.String(d.Get("name").(string)), + } + + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) + } + + if v, ok := d.Get("security_hub_policy").([]interface{}); ok && len(v) > 0 { + policy := expandSecurityHubPolicy(v[0].(map[string]interface{})) + if err := validateSecurityHubPolicy(policy); err != nil { + requestBody, _ := json.MarshalIndent(policy, "", " ") + return sdkdiag.AppendErrorf(diags, "updating Security Hub Configuration Policy (%s): %s, %s", d.Id(), err, requestBody) + } + input.ConfigurationPolicy = policy + } + + _, err := conn.UpdateConfigurationPolicy(ctx, input) + if err != nil { + requestBody, _ := json.MarshalIndent(input, "", " ") + return sdkdiag.AppendErrorf(diags, "updating Security Hub Configuration Policy (%s): %s from request:\n%s", d.Id(), err, string(requestBody)) + } + + return append(diags, resourceConfigurationPolicyRead(ctx, d, meta)...) +} + +func resourceConfigurationPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) + + input := &securityhub.GetConfigurationPolicyInput{ + Identifier: aws.String(d.Id()), + } + + out, err := conn.GetConfigurationPolicy(ctx, input) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Security Hub Configuration Policy (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags + } + + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading Security Hub Configuration Policy (%s): %s", d.Id(), err) + } + + d.Set("name", out.Name) + d.Set("description", out.Description) + if err := d.Set("security_hub_policy", []interface{}{flattenSecurityHubPolicy(out.ConfigurationPolicy)}); err != nil { + return sdkdiag.AppendErrorf(diags, "setting security_hub_policy: %s", err) + } + + return diags +} + +func resourceConfigurationPolicyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) + + input := &securityhub.DeleteConfigurationPolicyInput{ + Identifier: aws.String(d.Id()), + } + + _, err := conn.DeleteConfigurationPolicy(ctx, input) + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting Security Hub Configuration Policy (%s): %s", d.Id(), err) + } + + return diags +} + +// validateSecurityHubPolicy performs validation before running creates/updates to prevent certain issues with state. +func validateSecurityHubPolicy(apiPolicy *types.PolicyMemberSecurityHub) error { + // security_controls_configuration can be specified in Creates/Updates and accepted by the APIs, + // but the resources returned by subsequent Get API call will be nil instead of non-nil. + // This leaves terraform in perpetual drift and so we prevent this case explicitly. + if !*apiPolicy.Value.ServiceEnabled && apiPolicy.Value.SecurityControlsConfiguration != nil { + return errors.New("security_controls_configuration cannot be defined when service_enabled is false") + } else if *apiPolicy.Value.ServiceEnabled && apiPolicy.Value.SecurityControlsConfiguration == nil { + return errors.New("security_controls_configuration must be defined when service_enabled is true") + } + + // If ServiceEnabled is true, then Create/Update APIs require exactly one of enabled or disable control fields to be non-nil. + // If terraform defaults are set for both, then we choose to set DisabledSecurityControlIdentifiers to the empty struct. + if *apiPolicy.Value.ServiceEnabled && apiPolicy.Value.SecurityControlsConfiguration != nil && + apiPolicy.Value.SecurityControlsConfiguration.DisabledSecurityControlIdentifiers == nil && + apiPolicy.Value.SecurityControlsConfiguration.EnabledSecurityControlIdentifiers == nil { + apiPolicy.Value.SecurityControlsConfiguration.DisabledSecurityControlIdentifiers = []string{} + } + + return nil +} + +func expandSecurityHubPolicy(tfMap map[string]interface{}) *types.PolicyMemberSecurityHub { + if tfMap == nil { + return nil + } + + apiObject := types.SecurityHubPolicy{} + apiObject.ServiceEnabled = aws.Bool(tfMap["service_enabled"].(bool)) + for _, s := range tfMap["enabled_standard_arns"].([]interface{}) { + apiObject.EnabledStandardIdentifiers = append(apiObject.EnabledStandardIdentifiers, s.(string)) + } + apiObject.SecurityControlsConfiguration = expandSecurityControlsConfiguration(tfMap["security_controls_configuration"]) + return &types.PolicyMemberSecurityHub{ + Value: apiObject, + } +} + +func expandSecurityControlsConfiguration(tfSecurityControlsConfig interface{}) *types.SecurityControlsConfiguration { + var apiSecurityControlsConfig *types.SecurityControlsConfiguration + if v, ok := tfSecurityControlsConfig.([]interface{}); ok && len(v) > 0 && v[0] != nil { + apiControlsConfig := &types.SecurityControlsConfiguration{} + + tfControlsConfig := v[0].(map[string]interface{}) + if v, ok := tfControlsConfig["disabled_control_identifiers"]; ok && v != nil { + for _, c := range v.([]interface{}) { + apiControlsConfig.DisabledSecurityControlIdentifiers = append(apiControlsConfig.DisabledSecurityControlIdentifiers, c.(string)) + } + } + if v, ok := tfControlsConfig["enabled_control_identifiers"]; ok && v != nil { + for _, c := range v.([]interface{}) { + apiControlsConfig.EnabledSecurityControlIdentifiers = append(apiControlsConfig.EnabledSecurityControlIdentifiers, c.(string)) + } + } + + if v, ok := tfControlsConfig["control_custom_parameter"].([]interface{}); ok && len(v) > 0 { + for _, param := range v { + apiControlsConfig.SecurityControlCustomParameters = append(apiControlsConfig.SecurityControlCustomParameters, expandControlCustomParameter(param.(map[string]interface{}))) + } + } + apiSecurityControlsConfig = apiControlsConfig + } else if ok && len(v) > 0 && v[0] == nil { // resource defined, but with defaults + apiSecurityControlsConfig = &types.SecurityControlsConfiguration{} + } // else resource undefined yields nil + return apiSecurityControlsConfig +} + +func expandControlCustomParameter(tfCustomParam map[string]interface{}) types.SecurityControlCustomParameter { + apiCustomParam := types.SecurityControlCustomParameter{ + Parameters: make(map[string]types.ParameterConfiguration), + } + if v, ok := tfCustomParam["control_identifier"].(string); ok { + apiCustomParam.SecurityControlId = aws.String(v) + } + if v, ok := tfCustomParam["parameter"].(*schema.Set); ok && v.Len() > 0 { + for _, vp := range v.List() { + param, ok := vp.(map[string]interface{}) + if !ok { + continue + } + apiParamConfig := types.ParameterConfiguration{} + if v, ok := param["value_type"].(string); ok && len(v) > 0 { + apiParamConfig.ValueType = types.ParameterValueType(v) + } + + var apiParamValue types.ParameterValue + if v, ok := param["bool"].([]interface{}); ok && len(v) > 0 { // block defined + apiParamValue = &types.ParameterValueMemberBoolean{} + if v[0] != nil { // block defined with non-defaults + val := v[0].(map[string]interface{})["value"] + apiParamValue = &types.ParameterValueMemberBoolean{Value: val.(bool)} + } + } else if v, ok := param["double"].([]interface{}); ok && len(v) > 0 { + apiParamValue = &types.ParameterValueMemberDouble{} + if v[0] != nil { + val := v[0].(map[string]interface{})["value"] + apiParamValue = &types.ParameterValueMemberDouble{Value: val.(float64)} + } + } else if v, ok := param["enum"].([]interface{}); ok && len(v) > 0 { + apiParamValue = &types.ParameterValueMemberEnum{} + if v[0] != nil { + val := v[0].(map[string]interface{})["value"] + apiParamValue = &types.ParameterValueMemberEnum{Value: val.(string)} + } + } else if v, ok := param["string"].([]interface{}); ok && len(v) > 0 { + apiParamValue = &types.ParameterValueMemberString{} + if v[0] != nil { + val := v[0].(map[string]interface{})["value"] + apiParamValue = &types.ParameterValueMemberString{Value: val.(string)} + } + } else if v, ok := param["int"].([]interface{}); ok && len(v) > 0 { + apiParamValue = &types.ParameterValueMemberInteger{} + if v[0] != nil { + val := v[0].(map[string]interface{})["value"] + apiParamValue = &types.ParameterValueMemberInteger{Value: int32(val.(int))} + } + } else if v, ok := param["int_list"].([]interface{}); ok && len(v) > 0 { + apiParamValue = &types.ParameterValueMemberIntegerList{} + if v[0] != nil { + val := v[0].(map[string]interface{})["value"] + var vals []int32 + for _, s := range val.([]interface{}) { + vals = append(vals, int32(s.(int))) + } + apiParamValue = &types.ParameterValueMemberIntegerList{Value: vals} + } + } else if v, ok := param["enum_list"].([]interface{}); ok && len(v) > 0 { + apiParamValue = &types.ParameterValueMemberEnumList{} + if v[0] != nil { + val := v[0].(map[string]interface{})["value"] + var vals []string + for _, s := range val.([]interface{}) { + vals = append(vals, s.(string)) + } + apiParamValue = &types.ParameterValueMemberEnumList{Value: vals} + } + } else if v, ok := param["string_list"].([]interface{}); ok && len(v) > 0 { + apiParamValue = &types.ParameterValueMemberStringList{} + if v[0] != nil { + val := v[0].(map[string]interface{})["value"] + var vals []string + for _, s := range val.([]interface{}) { + vals = append(vals, s.(string)) + } + apiParamValue = &types.ParameterValueMemberStringList{Value: vals} + } + } + apiParamConfig.Value = apiParamValue + if key, ok := param["name"].(string); ok && len(key) > 0 { + apiCustomParam.Parameters[key] = apiParamConfig + } + } + } + return apiCustomParam +} + +func flattenSecurityHubPolicy(policy types.Policy) map[string]interface{} { + apiObject, ok := policy.(*types.PolicyMemberSecurityHub) + if !ok || apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + tfMap["service_enabled"] = apiObject.Value.ServiceEnabled + tfMap["enabled_standard_arns"] = apiObject.Value.EnabledStandardIdentifiers + tfMap["security_controls_configuration"] = flattenSecurityControlsConfiguration(apiObject.Value.SecurityControlsConfiguration) + return tfMap +} + +func flattenSecurityControlsConfiguration(apiSecurityControlsConfig *types.SecurityControlsConfiguration) []interface{} { + if apiSecurityControlsConfig == nil { + return nil + } + tfSecurityControlsConfig := map[string]interface{}{} + tfSecurityControlsConfig["disabled_control_identifiers"] = apiSecurityControlsConfig.DisabledSecurityControlIdentifiers + tfSecurityControlsConfig["enabled_control_identifiers"] = apiSecurityControlsConfig.EnabledSecurityControlIdentifiers + tfControlCustomParams := []interface{}{} + for _, apiControlCustomParam := range apiSecurityControlsConfig.SecurityControlCustomParameters { + tfControlCustomParams = append(tfControlCustomParams, flattenControlCustomParameter(apiControlCustomParam)) + } + tfSecurityControlsConfig["control_custom_parameter"] = tfControlCustomParams + return []interface{}{tfSecurityControlsConfig} +} + +func flattenControlCustomParameter(apiControlCustomParam types.SecurityControlCustomParameter) map[string]interface{} { + tfControlCustomParam := map[string]interface{}{} + tfControlCustomParam["control_identifier"] = apiControlCustomParam.SecurityControlId + tfParametersForControl := []interface{}{} + for paramName, param := range apiControlCustomParam.Parameters { + tfParameter := map[string]interface{}{ + "name": paramName, + "value_type": string(param.ValueType), + } + if param.Value != nil { + switch casted := param.Value.(type) { + case *types.ParameterValueMemberBoolean: + tfParameter["bool"] = []interface{}{ + map[string]interface{}{ + "value": casted.Value, + }, + } + case *types.ParameterValueMemberDouble: + tfParameter["double"] = []interface{}{ + map[string]interface{}{ + "value": casted.Value, + }, + } + case *types.ParameterValueMemberEnum: + tfParameter["enum"] = []interface{}{ + map[string]interface{}{ + "value": casted.Value, + }, + } + case *types.ParameterValueMemberEnumList: + tfParameter["enum_list"] = []interface{}{ + map[string]interface{}{ + "value": casted.Value, + }, + } + case *types.ParameterValueMemberInteger: + tfParameter["int"] = []interface{}{ + map[string]interface{}{ + "value": casted.Value, + }, + } + case *types.ParameterValueMemberIntegerList: + tfParameter["int_list"] = []interface{}{ + map[string]interface{}{ + "value": casted.Value, + }, + } + case *types.ParameterValueMemberString: + tfParameter["string"] = []interface{}{ + map[string]interface{}{ + "value": casted.Value, + }, + } + case *types.ParameterValueMemberStringList: + tfParameter["string_list"] = []interface{}{ + map[string]interface{}{ + "value": casted.Value, + }, + } + default: + log.Printf("[WARN] Security Hub Configuration Policy (%T) unknown type of parameter value", casted) + } + } + + tfParametersForControl = append(tfParametersForControl, tfParameter) + } + tfControlCustomParam["parameter"] = tfParametersForControl + return tfControlCustomParam +} diff --git a/internal/service/securityhub/configuration_policy_test.go b/internal/service/securityhub/configuration_policy_test.go new file mode 100644 index 00000000000..fdef011fcb2 --- /dev/null +++ b/internal/service/securityhub/configuration_policy_test.go @@ -0,0 +1,292 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package securityhub_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/securityhub" + "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/names" +) + +func testAccConfigurationPolicy_basic(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_securityhub_configuration_policy.test" + const exampleStandardsArn = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0" + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckAlternateAccount(t) + acctest.PreCheckAlternateRegionIs(t, acctest.Region()) + acctest.PreCheckOrganizationMemberAccount(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + { + Config: testAccConfigurationPolicyConfig_baseDisabled("TestPolicy", "This is a disabled policy"), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationPolicyExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "name", "TestPolicy"), + resource.TestCheckResourceAttr(resourceName, "description", "This is a disabled policy"), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.#", "1"), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.service_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.enabled_standard_arns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.#", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccConfigurationPolicyConfig_baseEnabled("TestPolicy", "This is an enabled policy", exampleStandardsArn), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationPolicyExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "name", "TestPolicy"), + resource.TestCheckResourceAttr(resourceName, "description", "This is an enabled policy"), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.#", "1"), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.service_enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.enabled_standard_arns.#", "1"), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.enabled_standard_arns.0", exampleStandardsArn), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.#", "1"), + ), + }, + }, + }) +} + +func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_securityhub_configuration_policy.test" + standardsArn := fmt.Sprintf("arn:aws:securityhub:%s::standards/aws-foundational-security-best-practices/v/1.0.0", acctest.Region()) + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckAlternateAccount(t) + acctest.PreCheckAlternateRegionIs(t, acctest.Region()) + acctest.PreCheckOrganizationMemberAccount(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + { + Config: testAccConfigurationPolicyConfig_controlCustomParametersMulti(standardsArn), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationPolicyExists(ctx, resourceName), + + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.#", "1"), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.#", "2"), + + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "APIGateway.1"), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.parameter.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ + "name": "loggingLevel", + "value_type": "CUSTOM", + "enum.0.value": "INFO", + }), + + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.1.control_identifier", "IAM.7"), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.1.parameter.#", "3"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.1.parameter.*", map[string]string{ + "name": "RequireLowercaseCharacters", + "value_type": "CUSTOM", + "bool.0.value": "false", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.1.parameter.*", map[string]string{ + "name": "RequireUppercaseCharacters", + "value_type": "DEFAULT", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.1.parameter.*", map[string]string{ + "name": "MaxPasswordAge", + "value_type": "CUSTOM", + "int.0.value": "60", + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + // { + // Config: testAccConfigurationPolicyConfig_controlCustomParametersSingle(standardsArn, "id", "name", "type", "value"), + // Check: resource.ComposeTestCheckFunc( + // testAccCheckConfigurationPolicyExists(ctx, resourceName), + // ), + // }, + }, + }) +} + +func testAccCheckConfigurationPolicyExists(ctx context.Context, n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).SecurityHubClient(ctx) + _, err := conn.GetConfigurationPolicy(ctx, &securityhub.GetConfigurationPolicyInput{ + Identifier: &rs.Primary.ID, + }) + return err + } +} + +func testAccConfigurationPolicyConfig_baseDisabled(name, description string) string { + return acctest.ConfigCompose( + acctest.ConfigAlternateAccountProvider(), + testAccMemberAccountDelegatedAdminConfig_base, + testAccCentralConfigurationEnabledConfig_base, + fmt.Sprintf(` + resource "aws_securityhub_configuration_policy" "test" { + name = %[1]q + description = %[2]q + security_hub_policy { + service_enabled = false + enabled_standard_arns = [] + } + + depends_on = [aws_securityhub_organization_configuration.test] + }`, name, description)) +} + +func testAccConfigurationPolicyConfig_baseEnabled(name, description string, enabledStandard string) string { + return acctest.ConfigCompose( + acctest.ConfigAlternateAccountProvider(), + testAccMemberAccountDelegatedAdminConfig_base, + testAccCentralConfigurationEnabledConfig_base, + fmt.Sprintf(` + resource "aws_securityhub_configuration_policy" "test" { + name = %[1]q + description = %[2]q + security_hub_policy { + service_enabled = true + enabled_standard_arns = [ + %[3]q + ] + security_controls_configuration { + disabled_control_identifiers = [] + } + } + + depends_on = [aws_securityhub_organization_configuration.test] + }`, name, description, enabledStandard)) +} + +func testAccConfigurationPolicyConfig_controlCustomParametersMulti(standardsArn string) string { + return acctest.ConfigCompose( + acctest.ConfigAlternateAccountProvider(), + testAccMemberAccountDelegatedAdminConfig_base, + testAccCentralConfigurationEnabledConfig_base, + fmt.Sprintf(` + resource "aws_securityhub_configuration_policy" "test" { + name = "ControlCustomParametersPolicy" + security_hub_policy { + service_enabled = true + enabled_standard_arns = [ + %[1]q + ] + security_controls_configuration { + disabled_control_identifiers = [] + control_custom_parameter { + control_identifier = "APIGateway.1" + parameter { + name = "loggingLevel" + value_type = "CUSTOM" + enum { + value = "INFO" + } + } + } + control_custom_parameter { + control_identifier = "IAM.7" + parameter { + name = "RequireUppercaseCharacters" + value_type = "DEFAULT" + } + parameter { + name = "RequireLowercaseCharacters" + value_type = "CUSTOM" + bool { + value = false + } + } + parameter { + name = "MaxPasswordAge" + value_type = "CUSTOM" + int { + value = 60 + } + } + } + } + } + + depends_on = [aws_securityhub_organization_configuration.test] + }`, standardsArn), + ) +} + +func testAccConfigurationPolicyConfig_controlCustomParametersSingle(standardsArn, controlID, paramName, paramType, paramValue string) string { + return acctest.ConfigCompose( + acctest.ConfigAlternateAccountProvider(), + testAccMemberAccountDelegatedAdminConfig_base, + testAccCentralConfigurationEnabledConfig_base, + fmt.Sprintf(` + resource "aws_securityhub_configuration_policy" "test" { + name = "ControlCustomParametersPolicy" + security_hub_policy { + service_enabled = true + enabled_standard_arns = [ + %[1]q + ] + security_controls_configuration { + disabled_control_identifiers = [] + control_custom_parameter { + control_identifier = %[2]q + parameter { + name = %[3]q + value_type = "CUSTOM" + %[4]s { + value = %[5]q + } + } + } + } + } + + depends_on = [aws_securityhub_organization_configuration.test] + }`, standardsArn, controlID, paramName, paramType, paramValue), + ) +} + +const testAccCentralConfigurationEnabledConfig_base = ` +resource "aws_securityhub_finding_aggregator" "test" { + linking_mode = "ALL_REGIONS" + + depends_on = [aws_securityhub_organization_admin_account.test] +} + +resource "aws_securityhub_organization_configuration" "test" { + auto_enable = false + auto_enable_standards = "NONE" + organization_configuration { + configuration_type = "CENTRAL" + } + + depends_on = [aws_securityhub_finding_aggregator.test] +} +` diff --git a/internal/service/securityhub/securityhub_test.go b/internal/service/securityhub/securityhub_test.go index 4a92389cf01..5f4aedda0ef 100644 --- a/internal/service/securityhub/securityhub_test.go +++ b/internal/service/securityhub/securityhub_test.go @@ -86,6 +86,10 @@ func TestAccSecurityHub_centralConfiguration(t *testing.T) { "OrganizationConfiguration": { "CentralConfiguration": testAccOrganizationConfiguration_centralConfiguration, }, + "ConfigurationPolicy": { + "basic": testAccConfigurationPolicy_basic, + "customParameters": testAccConfigurationPolicy_controlCustomParameters, + }, } acctest.RunSerialTests2Levels(t, testCases, 0) } diff --git a/internal/service/securityhub/service_package_gen.go b/internal/service/securityhub/service_package_gen.go index 2384fe6e2ca..86b9c9b97e3 100644 --- a/internal/service/securityhub/service_package_gen.go +++ b/internal/service/securityhub/service_package_gen.go @@ -36,6 +36,10 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Factory: ResourceActionTarget, TypeName: "aws_securityhub_action_target", }, + { + Factory: ResourceConfigurationPolicy, + TypeName: "aws_securityhub_configuration_policy", + }, { Factory: ResourceFindingAggregator, TypeName: "aws_securityhub_finding_aggregator", From 10981bb455fdca8a6b57f29d86dc97eb0d4d15f6 Mon Sep 17 00:00:00 2001 From: twelsh-aw <84401379+twelsh-aw@users.noreply.github.com> Date: Mon, 19 Feb 2024 21:34:38 -0500 Subject: [PATCH 06/71] Add more acceptance tests \+ some fixes to destroy checks added. Can't call Gets/Describes on these resources once not delegated admin (which is always the case when suite is properly destroyed) --- .../securityhub/configuration_policy_test.go | 179 +++++++++++++++++- .../organization_configuration_test.go | 34 +--- .../service/securityhub/securityhub_test.go | 7 +- 3 files changed, 176 insertions(+), 44 deletions(-) diff --git a/internal/service/securityhub/configuration_policy_test.go b/internal/service/securityhub/configuration_policy_test.go index fdef011fcb2..8105224d6ed 100644 --- a/internal/service/securityhub/configuration_policy_test.go +++ b/internal/service/securityhub/configuration_policy_test.go @@ -68,7 +68,8 @@ func testAccConfigurationPolicy_basic(t *testing.T) { func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { ctx := acctest.Context(t) resourceName := "aws_securityhub_configuration_policy.test" - standardsArn := fmt.Sprintf("arn:aws:securityhub:%s::standards/aws-foundational-security-best-practices/v/1.0.0", acctest.Region()) + foundationalStandardsArn := fmt.Sprintf("arn:aws:securityhub:%s::standards/aws-foundational-security-best-practices/v/1.0.0", acctest.Region()) + nistStandardsArn := fmt.Sprintf("arn:aws:securityhub:%s::standards/nist-800-53/v/5.0.0", acctest.Region()) resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) @@ -81,7 +82,7 @@ func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { - Config: testAccConfigurationPolicyConfig_controlCustomParametersMulti(standardsArn), + Config: testAccConfigurationPolicyConfig_controlCustomParametersMulti(foundationalStandardsArn), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyExists(ctx, resourceName), @@ -120,12 +121,139 @@ func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { ImportState: true, ImportStateVerify: true, }, - // { - // Config: testAccConfigurationPolicyConfig_controlCustomParametersSingle(standardsArn, "id", "name", "type", "value"), - // Check: resource.ComposeTestCheckFunc( - // testAccCheckConfigurationPolicyExists(ctx, resourceName), - // ), - // }, + { + // bool type + Config: testAccConfigurationPolicyConfig_controlCustomParametersSingle(nistStandardsArn, "CloudWatch.15", "insufficientDataActionRequired", "bool", "true"), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationPolicyExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "CloudWatch.15"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ + "name": "insufficientDataActionRequired", + "value_type": "CUSTOM", + "bool.0.value": "true", + }), + ), + }, + { + // double type + Config: testAccConfigurationPolicyConfig_controlCustomParametersSingle(foundationalStandardsArn, "RDS.14", "BacktrackWindowInHours", "double", "20.25"), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationPolicyExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "RDS.14"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ + "name": "BacktrackWindowInHours", + "value_type": "CUSTOM", + "double.0.value": "20.25", + }), + ), + }, + { + // enum type + Config: testAccConfigurationPolicyConfig_controlCustomParametersSingle(foundationalStandardsArn, "APIGateway.1", "loggingLevel", "enum", `"ERROR"`), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationPolicyExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "APIGateway.1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ + "name": "loggingLevel", + "value_type": "CUSTOM", + "enum.0.value": "ERROR", + }), + ), + }, + { + // enum_list type + Config: testAccConfigurationPolicyConfig_controlCustomParametersSingle(foundationalStandardsArn, "S3.11", "eventTypes", "enum_list", `["s3:IntelligentTiering", "s3:LifecycleExpiration:*"]`), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationPolicyExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "S3.11"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ + "name": "eventTypes", + "value_type": "CUSTOM", + "enum_list.0.value.#": "2", + "enum_list.0.value.0": "s3:IntelligentTiering", + "enum_list.0.value.1": "s3:LifecycleExpiration:*", + }), + ), + }, + { + // int type + Config: testAccConfigurationPolicyConfig_controlCustomParametersSingle(foundationalStandardsArn, "DocumentDB.2", "minimumBackupRetentionPeriod", "int", "20"), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationPolicyExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "DocumentDB.2"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ + "name": "minimumBackupRetentionPeriod", + "value_type": "CUSTOM", + "int.0.value": "20", + }), + ), + }, + { + // int_list type + Config: testAccConfigurationPolicyConfig_controlCustomParametersSingle(foundationalStandardsArn, "EC2.18", "authorizedTcpPorts", "int_list", "[443, 8080]"), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationPolicyExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "EC2.18"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ + "name": "authorizedTcpPorts", + "value_type": "CUSTOM", + "int_list.0.value.#": "2", + "int_list.0.value.0": "443", + "int_list.0.value.1": "8080", + }), + ), + }, + // TODO: add string, string_list type tests when controls exist + }, + }) +} + +func testAccConfigurationPolicy_specificControlIdentifiers(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_securityhub_configuration_policy.test" + foundationalStandardsArn := fmt.Sprintf("arn:aws:securityhub:%s::standards/aws-foundational-security-best-practices/v/1.0.0", acctest.Region()) + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckAlternateAccount(t) + acctest.PreCheckAlternateRegionIs(t, acctest.Region()) + acctest.PreCheckOrganizationMemberAccount(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + { + Config: testAccConfigurationPolicyConfig_specifcControlIdentifiers(foundationalStandardsArn, "IAM.7", "APIGateway.1", false), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationPolicyExists(ctx, resourceName), + + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.#", "1"), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.disabled_control_identifiers.#", "2"), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.disabled_control_identifiers.0", "IAM.7"), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.disabled_control_identifiers.1", "APIGateway.1"), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.enabled_control_identifiers.#", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccConfigurationPolicyConfig_specifcControlIdentifiers(foundationalStandardsArn, "APIGateway.1", "IAM.7", true), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationPolicyExists(ctx, resourceName), + + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.#", "1"), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.enabled_control_identifiers.#", "2"), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.enabled_control_identifiers.0", "APIGateway.1"), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.enabled_control_identifiers.1", "IAM.7"), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.disabled_control_identifiers.#", "0"), + ), + }, }, }) } @@ -193,7 +321,7 @@ func testAccConfigurationPolicyConfig_controlCustomParametersMulti(standardsArn testAccCentralConfigurationEnabledConfig_base, fmt.Sprintf(` resource "aws_securityhub_configuration_policy" "test" { - name = "ControlCustomParametersPolicy" + name = "MultipleControlCustomParametersPolicy" security_hub_policy { service_enabled = true enabled_standard_arns = [ @@ -261,7 +389,7 @@ func testAccConfigurationPolicyConfig_controlCustomParametersSingle(standardsArn name = %[3]q value_type = "CUSTOM" %[4]s { - value = %[5]q + value = %[5]s } } } @@ -273,6 +401,37 @@ func testAccConfigurationPolicyConfig_controlCustomParametersSingle(standardsArn ) } +func testAccConfigurationPolicyConfig_specifcControlIdentifiers(standardsArn, control1, control2 string, enabledOnly bool) string { + controlIDAttr := "disabled_control_identifiers" + if enabledOnly { + controlIDAttr = "enabled_control_identifiers" + } + + return acctest.ConfigCompose( + acctest.ConfigAlternateAccountProvider(), + testAccMemberAccountDelegatedAdminConfig_base, + testAccCentralConfigurationEnabledConfig_base, + fmt.Sprintf(` + resource "aws_securityhub_configuration_policy" "test" { + name = "ControlCustomParametersPolicy" + security_hub_policy { + service_enabled = true + enabled_standard_arns = [ + %[1]q + ] + security_controls_configuration { + %[2]s = [ + %[3]q, + %[4]q + ] + } + } + + depends_on = [aws_securityhub_organization_configuration.test] + }`, standardsArn, controlIDAttr, control1, control2), + ) +} + const testAccCentralConfigurationEnabledConfig_base = ` resource "aws_securityhub_finding_aggregator" "test" { linking_mode = "ALL_REGIONS" diff --git a/internal/service/securityhub/organization_configuration_test.go b/internal/service/securityhub/organization_configuration_test.go index 36ac314b1d4..1af83be32e5 100644 --- a/internal/service/securityhub/organization_configuration_test.go +++ b/internal/service/securityhub/organization_configuration_test.go @@ -9,7 +9,6 @@ import ( "testing" "github.com/aws/aws-sdk-go-v2/service/securityhub" - "github.com/aws/aws-sdk-go-v2/service/securityhub/types" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" @@ -25,7 +24,7 @@ func testAccOrganizationConfiguration_basic(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckOrganizationManagementAccount(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckOrganizationConfigurationDestroy(ctx), + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { Config: testAccOrganizationConfigurationConfig_basic(true), @@ -33,7 +32,6 @@ func testAccOrganizationConfiguration_basic(t *testing.T) { testAccCheckOrganizationConfigurationExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "auto_enable", "true"), resource.TestCheckResourceAttr(resourceName, "auto_enable_standards", "DEFAULT"), - resource.TestCheckResourceAttr(resourceName, "organization_configuration.#", "0"), ), }, { @@ -47,7 +45,6 @@ func testAccOrganizationConfiguration_basic(t *testing.T) { testAccCheckOrganizationConfigurationExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "auto_enable", "false"), resource.TestCheckResourceAttr(resourceName, "auto_enable_standards", "DEFAULT"), - resource.TestCheckResourceAttr(resourceName, "organization_configuration.#", "0"), ), }, }, @@ -62,7 +59,7 @@ func testAccOrganizationConfiguration_autoEnableStandards(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckOrganizationManagementAccount(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckOrganizationConfigurationDestroy(ctx), + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { Config: testAccOrganizationConfigurationConfig_autoEnableStandards("DEFAULT"), @@ -70,7 +67,6 @@ func testAccOrganizationConfiguration_autoEnableStandards(t *testing.T) { testAccCheckOrganizationConfigurationExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "auto_enable", "true"), resource.TestCheckResourceAttr(resourceName, "auto_enable_standards", "DEFAULT"), - resource.TestCheckResourceAttr(resourceName, "organization_configuration.#", "0"), ), }, { @@ -84,7 +80,6 @@ func testAccOrganizationConfiguration_autoEnableStandards(t *testing.T) { testAccCheckOrganizationConfigurationExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "auto_enable", "true"), resource.TestCheckResourceAttr(resourceName, "auto_enable_standards", "NONE"), - resource.TestCheckResourceAttr(resourceName, "organization_configuration.#", "0"), ), }, }, @@ -103,7 +98,7 @@ func testAccOrganizationConfiguration_centralConfiguration(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), - CheckDestroy: testAccCheckOrganizationConfigurationDestroy(ctx), + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { Config: testAccOrganizationConfigurationConfig_centralConfiguration(false, "NONE", "CENTRAL"), @@ -148,29 +143,6 @@ func testAccCheckOrganizationConfigurationExists(ctx context.Context, n string) } } -func testAccCheckOrganizationConfigurationDestroy(ctx context.Context) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).SecurityHubClient(ctx) - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_securityhub_organization_configuration" { - continue - } - - out, err := conn.DescribeOrganizationConfiguration(ctx, &securityhub.DescribeOrganizationConfigurationInput{}) - if err != nil { - return err - } - - if out != nil && out.OrganizationConfiguration != nil && out.OrganizationConfiguration.ConfigurationType == types.OrganizationConfigurationConfigurationTypeCentral { - return fmt.Errorf("Security Hub Organization Configuration (%s) still exists", rs.Primary.ID) - } - } - - return nil - } -} - const testAccOrganizationConfigurationConfig_base = ` resource "aws_securityhub_account" "test" {} diff --git a/internal/service/securityhub/securityhub_test.go b/internal/service/securityhub/securityhub_test.go index 5f4aedda0ef..d784d8143c0 100644 --- a/internal/service/securityhub/securityhub_test.go +++ b/internal/service/securityhub/securityhub_test.go @@ -84,11 +84,12 @@ func TestAccSecurityHub_centralConfiguration(t *testing.T) { t.Parallel() testCases := map[string]map[string]func(t *testing.T){ "OrganizationConfiguration": { - "CentralConfiguration": testAccOrganizationConfiguration_centralConfiguration, + "centralConfiguration": testAccOrganizationConfiguration_centralConfiguration, }, "ConfigurationPolicy": { - "basic": testAccConfigurationPolicy_basic, - "customParameters": testAccConfigurationPolicy_controlCustomParameters, + "basic": testAccConfigurationPolicy_basic, + "customParameters": testAccConfigurationPolicy_controlCustomParameters, + "controlIdentifiers": testAccConfigurationPolicy_specificControlIdentifiers, }, } acctest.RunSerialTests2Levels(t, testCases, 0) From c1e30419f995ce0a8e5163292dc9a5eb2abfd129 Mon Sep 17 00:00:00 2001 From: twelsh-aw <84401379+twelsh-aw@users.noreply.github.com> Date: Mon, 19 Feb 2024 21:58:16 -0500 Subject: [PATCH 07/71] Fix TestAccSecurityHub_serial We want this new property to be Optional and Computed so that it doesn't break anything on upgrade. --- .../securityhub/configuration_policy_test.go | 6 +++--- .../securityhub/organization_configuration.go | 14 +++++++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/internal/service/securityhub/configuration_policy_test.go b/internal/service/securityhub/configuration_policy_test.go index 8105224d6ed..8bfa5fa0f72 100644 --- a/internal/service/securityhub/configuration_policy_test.go +++ b/internal/service/securityhub/configuration_policy_test.go @@ -321,7 +321,7 @@ func testAccConfigurationPolicyConfig_controlCustomParametersMulti(standardsArn testAccCentralConfigurationEnabledConfig_base, fmt.Sprintf(` resource "aws_securityhub_configuration_policy" "test" { - name = "MultipleControlCustomParametersPolicy" + name = "MultipleControlCustomParametersPolicy" security_hub_policy { service_enabled = true enabled_standard_arns = [ @@ -375,7 +375,7 @@ func testAccConfigurationPolicyConfig_controlCustomParametersSingle(standardsArn testAccCentralConfigurationEnabledConfig_base, fmt.Sprintf(` resource "aws_securityhub_configuration_policy" "test" { - name = "ControlCustomParametersPolicy" + name = "ControlCustomParametersPolicy" security_hub_policy { service_enabled = true enabled_standard_arns = [ @@ -413,7 +413,7 @@ func testAccConfigurationPolicyConfig_specifcControlIdentifiers(standardsArn, co testAccCentralConfigurationEnabledConfig_base, fmt.Sprintf(` resource "aws_securityhub_configuration_policy" "test" { - name = "ControlCustomParametersPolicy" + name = "ControlIdentifiersPolicy" security_hub_policy { service_enabled = true enabled_standard_arns = [ diff --git a/internal/service/securityhub/organization_configuration.go b/internal/service/securityhub/organization_configuration.go index db9b08eeb65..7d7c8f1dc9a 100644 --- a/internal/service/securityhub/organization_configuration.go +++ b/internal/service/securityhub/organization_configuration.go @@ -46,9 +46,11 @@ func ResourceOrganizationConfiguration() *schema.Resource { ValidateDiagFunc: enum.Validate[types.AutoEnableStandards](), }, "organization_configuration": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + Computed: true, + DefaultFunc: func() (interface{}, error) { return defaultOrganizationConfiguration, nil }, + MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "configuration_type": { @@ -233,3 +235,9 @@ func flattenOrganizationConfiguration(apiObject *types.OrganizationConfiguration return tfMap } + +var defaultOrganizationConfiguration = []interface{}{ + map[string]interface{}{ + "configuration_type": types.OrganizationConfigurationConfigurationTypeLocal, + }, +} From 115d73177e535788a063912f5ccc0cedd1d6a742 Mon Sep 17 00:00:00 2001 From: twelsh-aw <84401379+twelsh-aw@users.noreply.github.com> Date: Sat, 24 Feb 2024 15:38:04 -0500 Subject: [PATCH 08/71] Add configuration policy association resource --- .../securityhub/configuration_policy.go | 20 +- .../configuration_policy_association.go | 210 ++++++++++++++++++ .../configuration_policy_association_test.go | 158 +++++++++++++ .../service/securityhub/securityhub_test.go | 17 +- .../securityhub/service_package_gen.go | 4 + 5 files changed, 392 insertions(+), 17 deletions(-) create mode 100644 internal/service/securityhub/configuration_policy_association.go create mode 100644 internal/service/securityhub/configuration_policy_association_test.go diff --git a/internal/service/securityhub/configuration_policy.go b/internal/service/securityhub/configuration_policy.go index 41838093656..0f356b5f4ab 100644 --- a/internal/service/securityhub/configuration_policy.go +++ b/internal/service/securityhub/configuration_policy.go @@ -5,7 +5,6 @@ package securityhub import ( "context" - "encoding/json" "errors" "log" "math" @@ -121,6 +120,10 @@ func ResourceConfigurationPolicy() *schema.Resource { }, }, }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, }, } } @@ -272,20 +275,18 @@ func resourceConfigurationPolicyCreate(ctx context.Context, d *schema.ResourceDa if v, ok := d.Get("security_hub_policy").([]interface{}); ok && len(v) > 0 { policy := expandSecurityHubPolicy(v[0].(map[string]interface{})) if err := validateSecurityHubPolicy(policy); err != nil { - requestBody, _ := json.MarshalIndent(policy, "", " ") - return sdkdiag.AppendErrorf(diags, "creating Security Hub Configuration Policy (%s): %s, %s", *input.Name, err, string(requestBody)) + return sdkdiag.AppendErrorf(diags, "creating Security Hub Configuration Policy (%s): %s", *input.Name, err) } input.ConfigurationPolicy = policy } out, err := conn.CreateConfigurationPolicy(ctx, input) if err != nil { - requestBody, _ := json.MarshalIndent(input, "", " ") - return sdkdiag.AppendErrorf(diags, "creating Security Hub Configuration Policy (%s): %s for request %s", *input.Name, err, string(requestBody)) + return sdkdiag.AppendErrorf(diags, "creating Security Hub Configuration Policy (%s): %s", *input.Name, err) } if d.IsNewResource() { - d.SetId(*out.Arn) + d.SetId(*out.Id) } return append(diags, resourceConfigurationPolicyRead(ctx, d, meta)...) @@ -307,16 +308,14 @@ func resourceConfigurationPolicyUpdate(ctx context.Context, d *schema.ResourceDa if v, ok := d.Get("security_hub_policy").([]interface{}); ok && len(v) > 0 { policy := expandSecurityHubPolicy(v[0].(map[string]interface{})) if err := validateSecurityHubPolicy(policy); err != nil { - requestBody, _ := json.MarshalIndent(policy, "", " ") - return sdkdiag.AppendErrorf(diags, "updating Security Hub Configuration Policy (%s): %s, %s", d.Id(), err, requestBody) + return sdkdiag.AppendErrorf(diags, "updating Security Hub Configuration Policy (%s): %s", d.Id(), err) } input.ConfigurationPolicy = policy } _, err := conn.UpdateConfigurationPolicy(ctx, input) if err != nil { - requestBody, _ := json.MarshalIndent(input, "", " ") - return sdkdiag.AppendErrorf(diags, "updating Security Hub Configuration Policy (%s): %s from request:\n%s", d.Id(), err, string(requestBody)) + return sdkdiag.AppendErrorf(diags, "updating Security Hub Configuration Policy (%s): %s", d.Id(), err) } return append(diags, resourceConfigurationPolicyRead(ctx, d, meta)...) @@ -343,6 +342,7 @@ func resourceConfigurationPolicyRead(ctx context.Context, d *schema.ResourceData d.Set("name", out.Name) d.Set("description", out.Description) + d.Set("arn", out.Arn) if err := d.Set("security_hub_policy", []interface{}{flattenSecurityHubPolicy(out.ConfigurationPolicy)}); err != nil { return sdkdiag.AppendErrorf(diags, "setting security_hub_policy: %s", err) } diff --git a/internal/service/securityhub/configuration_policy_association.go b/internal/service/securityhub/configuration_policy_association.go new file mode 100644 index 00000000000..969fecf4a06 --- /dev/null +++ b/internal/service/securityhub/configuration_policy_association.go @@ -0,0 +1,210 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package securityhub + +import ( + "context" + "errors" + "fmt" + "log" + "strings" + "time" + + "github.com/YakDriver/regexache" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/securityhub" + "github.com/aws/aws-sdk-go-v2/service/securityhub/types" + "github.com/hashicorp/aws-sdk-go-base/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/enum" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +// @SDKResource("aws_securityhub_configuration_policy_association") +func ResourceConfigurationPolicyAssociation() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceConfigurationPolicyAssociationCreateOrUpdate, + ReadWithoutTimeout: resourceConfigurationPolicyAssociationRead, + UpdateWithoutTimeout: resourceConfigurationPolicyAssociationCreateOrUpdate, + DeleteWithoutTimeout: resourceConfigurationPolicyAssociationDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "target_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The identifier of the target account, organizational unit, or the root to associate with the specified configuration.", + ValidateFunc: validation.StringMatch( + regexache.MustCompile(`^(r-[a-z0-9]{4,32})$|^(ou-[a-z0-9]{4,32}-[a-z0-9]{8,32})$|^([0-9]{12})$`), + "Target ID must be a valid root, organizational unit or account id.", + ), + }, + "policy_id": { + Type: schema.TypeString, + Required: true, + Description: "The universally unique identifier (UUID) of the configuration policy.", + ValidateFunc: validation.IsUUID, + }, + }, + } +} + +// GetTarget converts a target id string into proper types.Target struct. +func GetTarget(targetID string) types.Target { + if strings.HasPrefix(targetID, "r-") { + return &types.TargetMemberRootId{ + Value: targetID, + } + } + + if strings.HasPrefix(targetID, "ou-") { + return &types.TargetMemberOrganizationalUnitId{ + Value: targetID, + } + } + + return &types.TargetMemberAccountId{ + Value: targetID, + } +} + +func resourceConfigurationPolicyAssociationCreateOrUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) + + targetID := d.Get("target_id").(string) + input := &securityhub.StartConfigurationPolicyAssociationInput{ + ConfigurationPolicyIdentifier: aws.String(d.Get("policy_id").(string)), + Target: GetTarget(targetID), + } + + _, err := conn.StartConfigurationPolicyAssociation(ctx, input) + if err != nil { + return sdkdiag.AppendErrorf(diags, "starting Security Hub Configuration Policy Association (%s): %s", targetID, err) + } + + if d.IsNewResource() { + d.SetId(targetID) + } + + return append(diags, resourceConfigurationPolicyAssociationRead(ctx, d, meta)...) +} + +func resourceConfigurationPolicyAssociationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) + + input := &securityhub.StartConfigurationPolicyDisassociationInput{ + ConfigurationPolicyIdentifier: aws.String(d.Get("policy_id").(string)), + Target: GetTarget(d.Get("target_id").(string)), + } + + _, err := conn.StartConfigurationPolicyDisassociation(ctx, input) + if err != nil { + return sdkdiag.AppendErrorf(diags, "starting Security Hub Configuration Policy Disassociation (%s): %s", d.Id(), err) + } + + return diags +} + +const ( + configurationPolicyAssociationTimeout = 60 * time.Second +) + +func resourceConfigurationPolicyAssociationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) + + out, err := waitConfigurationPolicyAssociationSuccess(ctx, conn, GetTarget(d.Id()), configurationPolicyAssociationTimeout) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Security Hub Configuration Policy Association (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags + } + + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading Security Hub Configuration Policy Association (%s): %s", d.Id(), err) + } + + d.Set("policy_id", out.ConfigurationPolicyId) + d.Set("target_id", out.TargetId) + return diags +} + +func waitConfigurationPolicyAssociationSuccess(ctx context.Context, conn *securityhub.Client, target types.Target, timeout time.Duration) (*securityhub.GetConfigurationPolicyAssociationOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(types.ConfigurationPolicyAssociationStatusPending), + Target: enum.Slice(types.ConfigurationPolicyAssociationStatusSuccess), + Refresh: getConfigurationPolicyAssociation(ctx, conn, target), + Timeout: timeout, + NotFoundChecks: 20, + ContinuousTargetOccurence: 2, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + var timeoutErr *retry.TimeoutError + if tfresource.TimedOut(err) && errors.As(err, &timeoutErr) { + log.Printf("[WARN] Security Hub Configuration Policy Association still in state: %s. It can take up to 24 hours for the status to change from PENDING to SUCCESS or FAILURE", timeoutErr.LastState) + // We try to wait until SUCCESS state is reached but don't error if still in PENDING state. + // We must attempt to wait/retry in order for Policy Disassociations to take effect + return conn.GetConfigurationPolicyAssociation(ctx, &securityhub.GetConfigurationPolicyAssociationInput{ + Target: target, + }) + } + + return outputRaw.(*securityhub.GetConfigurationPolicyAssociationOutput), err +} + +func getConfigurationPolicyAssociation(ctx context.Context, conn *securityhub.Client, target types.Target) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + input := &securityhub.GetConfigurationPolicyAssociationInput{ + Target: target, + } + output, err := conn.GetConfigurationPolicyAssociation(ctx, input) + if tfawserr.ErrCodeEquals(err, errCodeResourceNotFoundException) || tfawserr.ErrMessageContains(err, errCodeInvalidAccessException, "not subscribed to AWS Security Hub") { + return nil, "", &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, "", err + } + + if output == nil || output.TargetId == nil { + return nil, "", tfresource.NewEmptyResultError(input) + } + + switch output.AssociationStatus { + case types.ConfigurationPolicyAssociationStatusPending: + return output, string(output.AssociationStatus), nil + case types.ConfigurationPolicyAssociationStatusSuccess, "": + return output, string(output.AssociationStatus), nil + default: + var statusErr error + if msg := output.AssociationStatusMessage; msg != nil && len(*msg) > 0 { + statusErr = fmt.Errorf("StatusMessage: %s", *msg) + } + return nil, "", &retry.UnexpectedStateError{ + LastError: statusErr, + State: string(output.AssociationStatus), + ExpectedState: []string{ + string(types.ConfigurationPolicyAssociationStatusPending), + string(types.ConfigurationPolicyAssociationStatusSuccess), + }, + } + } + } +} diff --git a/internal/service/securityhub/configuration_policy_association_test.go b/internal/service/securityhub/configuration_policy_association_test.go new file mode 100644 index 00000000000..93a36585483 --- /dev/null +++ b/internal/service/securityhub/configuration_policy_association_test.go @@ -0,0 +1,158 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package securityhub_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/securityhub" + "github.com/aws/aws-sdk-go-v2/service/securityhub/types" + "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" + tfsecurityhub "github.com/hashicorp/terraform-provider-aws/internal/service/securityhub" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func testAccConfigurationPolicyAssociation_basic(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_securityhub_configuration_policy_association.test" + accountTarget := "data.aws_caller_identity.member.account_id" + ouTarget := "aws_organizations_organizational_unit.test.id" + rootTarget := "data.aws_organizations_organization.test.roots[0].id" + policy1 := "aws_securityhub_configuration_policy.test_1.id" + policy2 := "aws_securityhub_configuration_policy.test_2.id" + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckAlternateAccount(t) + acctest.PreCheckAlternateRegionIs(t, acctest.Region()) + acctest.PreCheckOrganizationMemberAccount(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + { + Config: testAccConfigurationPolicyAssociationConfig_base(accountTarget, policy1), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationPolicyAssociationExists(ctx, resourceName), + resource.TestCheckResourceAttrPair(resourceName, "target_id", "data.aws_caller_identity.member", "account_id"), + resource.TestCheckResourceAttrPair(resourceName, "policy_id", "aws_securityhub_configuration_policy.test_1", "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccConfigurationPolicyAssociationConfig_base(accountTarget, policy2), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationPolicyAssociationExists(ctx, resourceName), + resource.TestCheckResourceAttrPair(resourceName, "target_id", "data.aws_caller_identity.member", "account_id"), + resource.TestCheckResourceAttrPair(resourceName, "policy_id", "aws_securityhub_configuration_policy.test_2", "id"), + ), + }, + { + Config: testAccConfigurationPolicyAssociationConfig_base(rootTarget, policy2), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationPolicyAssociationExists(ctx, resourceName), + resource.TestCheckResourceAttrPair(resourceName, "target_id", "aws_organizations_organizational_unit.test", "parent_id"), + resource.TestCheckResourceAttrPair(resourceName, "policy_id", "aws_securityhub_configuration_policy.test_2", "id"), + ), + }, + { + Config: testAccConfigurationPolicyAssociationConfig_base(ouTarget, policy2), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationPolicyAssociationExists(ctx, resourceName), + resource.TestCheckResourceAttrPair(resourceName, "target_id", "aws_organizations_organizational_unit.test", "id"), + resource.TestCheckResourceAttrPair(resourceName, "policy_id", "aws_securityhub_configuration_policy.test_2", "id"), + ), + }, + }, + }) +} + +func testAccCheckConfigurationPolicyAssociationExists(ctx context.Context, n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).SecurityHubClient(ctx) + out, err := conn.GetConfigurationPolicyAssociation(ctx, &securityhub.GetConfigurationPolicyAssociationInput{ + Target: tfsecurityhub.GetTarget(rs.Primary.ID), + }) + if err != nil { + return err + } + + if out.AssociationStatus == types.ConfigurationPolicyAssociationStatusFailed { + return fmt.Errorf("unexpected association status: %s %s", out.AssociationStatus, *out.AssociationStatusMessage) + } + + return nil + } +} + +const testAccOrganizationalUnitConfig_base = ` +data "aws_organizations_organization" "test" { + provider = awsalternate +} + +resource "aws_organizations_organizational_unit" "test" { + provider = awsalternate + + name = "testAccConfigurationPolicyOrgUnitConfig_base" + parent_id = data.aws_organizations_organization.test.roots[0].id +} +` + +const testAccConfigurationPoliciesConfig_base = ` +resource "aws_securityhub_configuration_policy" "test_1" { + name = "test1" + security_hub_policy { + service_enabled = true + enabled_standard_arns = ["arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"] + security_controls_configuration { + disabled_control_identifiers = [] + } + } + + depends_on = [aws_securityhub_organization_configuration.test] +} + +resource "aws_securityhub_configuration_policy" "test_2" { + name = "test2" + security_hub_policy { + service_enabled = true + enabled_standard_arns = ["arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"] + security_controls_configuration { + enabled_control_identifiers = ["CloudTrail.1"] + } + } + + depends_on = [aws_securityhub_organization_configuration.test] +} +` + +func testAccConfigurationPolicyAssociationConfig_base(targetID, policyID string) string { + return acctest.ConfigCompose( + acctest.ConfigAlternateAccountProvider(), + testAccMemberAccountDelegatedAdminConfig_base, + testAccOrganizationalUnitConfig_base, + testAccCentralConfigurationEnabledConfig_base, + testAccConfigurationPoliciesConfig_base, + fmt.Sprintf(` + resource "aws_securityhub_configuration_policy_association" "test" { + target_id = %[1]s + policy_id = %[2]s + } + `, targetID, policyID)) +} diff --git a/internal/service/securityhub/securityhub_test.go b/internal/service/securityhub/securityhub_test.go index d784d8143c0..904b5ca855d 100644 --- a/internal/service/securityhub/securityhub_test.go +++ b/internal/service/securityhub/securityhub_test.go @@ -83,13 +83,16 @@ func TestAccSecurityHub_serial(t *testing.T) { func TestAccSecurityHub_centralConfiguration(t *testing.T) { t.Parallel() testCases := map[string]map[string]func(t *testing.T){ - "OrganizationConfiguration": { - "centralConfiguration": testAccOrganizationConfiguration_centralConfiguration, - }, - "ConfigurationPolicy": { - "basic": testAccConfigurationPolicy_basic, - "customParameters": testAccConfigurationPolicy_controlCustomParameters, - "controlIdentifiers": testAccConfigurationPolicy_specificControlIdentifiers, + // "OrganizationConfiguration": { + // "centralConfiguration": testAccOrganizationConfiguration_centralConfiguration, + // }, + // "ConfigurationPolicy": { + // "basic": testAccConfigurationPolicy_basic, + // "customParameters": testAccConfigurationPolicy_controlCustomParameters, + // "controlIdentifiers": testAccConfigurationPolicy_specificControlIdentifiers, + // }, + "ConfigurationPolicyAssociation": { + "basic": testAccConfigurationPolicyAssociation_basic, }, } acctest.RunSerialTests2Levels(t, testCases, 0) diff --git a/internal/service/securityhub/service_package_gen.go b/internal/service/securityhub/service_package_gen.go index 86b9c9b97e3..7bfbdb973ae 100644 --- a/internal/service/securityhub/service_package_gen.go +++ b/internal/service/securityhub/service_package_gen.go @@ -40,6 +40,10 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Factory: ResourceConfigurationPolicy, TypeName: "aws_securityhub_configuration_policy", }, + { + Factory: ResourceConfigurationPolicyAssociation, + TypeName: "aws_securityhub_configuration_policy_association", + }, { Factory: ResourceFindingAggregator, TypeName: "aws_securityhub_finding_aggregator", From 8dd49f654784d345776e3704a847aa40a47758d2 Mon Sep 17 00:00:00 2001 From: twelsh-aw <84401379+twelsh-aw@users.noreply.github.com> Date: Sat, 24 Feb 2024 19:01:55 -0500 Subject: [PATCH 09/71] Add configurable timeout settings --- .../configuration_policy_association.go | 10 +++++----- .../securityhub/organization_configuration.go | 11 +++++++---- internal/service/securityhub/securityhub_test.go | 16 ++++++++-------- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/internal/service/securityhub/configuration_policy_association.go b/internal/service/securityhub/configuration_policy_association.go index 969fecf4a06..5be004391da 100644 --- a/internal/service/securityhub/configuration_policy_association.go +++ b/internal/service/securityhub/configuration_policy_association.go @@ -38,6 +38,10 @@ func ResourceConfigurationPolicyAssociation() *schema.Resource { StateContext: schema.ImportStatePassthroughContext, }, + Timeouts: &schema.ResourceTimeout{ + Read: schema.DefaultTimeout(180 * time.Second), + }, + Schema: map[string]*schema.Schema{ "target_id": { Type: schema.TypeString, @@ -117,15 +121,11 @@ func resourceConfigurationPolicyAssociationDelete(ctx context.Context, d *schema return diags } -const ( - configurationPolicyAssociationTimeout = 60 * time.Second -) - func resourceConfigurationPolicyAssociationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) - out, err := waitConfigurationPolicyAssociationSuccess(ctx, conn, GetTarget(d.Id()), configurationPolicyAssociationTimeout) + out, err := waitConfigurationPolicyAssociationSuccess(ctx, conn, GetTarget(d.Id()), d.Timeout(schema.TimeoutRead)) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Security Hub Configuration Policy Association (%s) not found, removing from state", d.Id()) diff --git a/internal/service/securityhub/organization_configuration.go b/internal/service/securityhub/organization_configuration.go index 7d7c8f1dc9a..c348a1d5917 100644 --- a/internal/service/securityhub/organization_configuration.go +++ b/internal/service/securityhub/organization_configuration.go @@ -34,6 +34,11 @@ func ResourceOrganizationConfiguration() *schema.Resource { StateContext: schema.ImportStatePassthroughContext, }, + Timeouts: &schema.ResourceTimeout{ + Read: schema.DefaultTimeout(180 * time.Second), + Delete: schema.DefaultTimeout(180 * time.Second), + }, + Schema: map[string]*schema.Schema{ "auto_enable": { Type: schema.TypeBool, @@ -113,7 +118,7 @@ func resourceOrganizationConfigurationDelete(ctx context.Context, d *schema.Reso return sdkdiag.AppendErrorf(diags, "deleting Security Hub Organization Configuration (%s): %s", d.Id(), err) } - _, err = waitOrganizationConfigurationEnabled(ctx, conn, organizationsConfigurationStatusTimeout) + _, err = waitOrganizationConfigurationEnabled(ctx, conn, d.Timeout(schema.TimeoutDelete)) if err != nil { return sdkdiag.AppendErrorf(diags, "deleting Security Hub Organization Configuration (%s): %s", d.Id(), err) } @@ -122,13 +127,11 @@ func resourceOrganizationConfigurationDelete(ctx context.Context, d *schema.Reso return diags } -const organizationsConfigurationStatusTimeout = 300 * time.Second - func resourceOrganizationConfigurationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) - output, err := waitOrganizationConfigurationEnabled(ctx, conn, organizationsConfigurationStatusTimeout) + output, err := waitOrganizationConfigurationEnabled(ctx, conn, d.Timeout(schema.TimeoutRead)) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Security Hub Organization Configuration %s not found, removing from state", d.Id()) diff --git a/internal/service/securityhub/securityhub_test.go b/internal/service/securityhub/securityhub_test.go index 904b5ca855d..edbb2c20f78 100644 --- a/internal/service/securityhub/securityhub_test.go +++ b/internal/service/securityhub/securityhub_test.go @@ -83,14 +83,14 @@ func TestAccSecurityHub_serial(t *testing.T) { func TestAccSecurityHub_centralConfiguration(t *testing.T) { t.Parallel() testCases := map[string]map[string]func(t *testing.T){ - // "OrganizationConfiguration": { - // "centralConfiguration": testAccOrganizationConfiguration_centralConfiguration, - // }, - // "ConfigurationPolicy": { - // "basic": testAccConfigurationPolicy_basic, - // "customParameters": testAccConfigurationPolicy_controlCustomParameters, - // "controlIdentifiers": testAccConfigurationPolicy_specificControlIdentifiers, - // }, + "OrganizationConfiguration": { + "centralConfiguration": testAccOrganizationConfiguration_centralConfiguration, + }, + "ConfigurationPolicy": { + "basic": testAccConfigurationPolicy_basic, + "customParameters": testAccConfigurationPolicy_controlCustomParameters, + "controlIdentifiers": testAccConfigurationPolicy_specificControlIdentifiers, + }, "ConfigurationPolicyAssociation": { "basic": testAccConfigurationPolicyAssociation_basic, }, From 5a2bcd10c6f6d83d4b67cb1a6b6c22be16f84964 Mon Sep 17 00:00:00 2001 From: twelsh-aw <84401379+twelsh-aw@users.noreply.github.com> Date: Mon, 26 Feb 2024 08:54:25 -0500 Subject: [PATCH 10/71] Shorten association timeout --- .../service/securityhub/configuration_policy_association.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/securityhub/configuration_policy_association.go b/internal/service/securityhub/configuration_policy_association.go index 5be004391da..daa2fee6c2f 100644 --- a/internal/service/securityhub/configuration_policy_association.go +++ b/internal/service/securityhub/configuration_policy_association.go @@ -39,7 +39,7 @@ func ResourceConfigurationPolicyAssociation() *schema.Resource { }, Timeouts: &schema.ResourceTimeout{ - Read: schema.DefaultTimeout(180 * time.Second), + Read: schema.DefaultTimeout(90 * time.Second), }, Schema: map[string]*schema.Schema{ From cef74d506cc915d86db6ff6b8cb62838a752e212 Mon Sep 17 00:00:00 2001 From: twelsh-aw <84401379+twelsh-aw@users.noreply.github.com> Date: Sun, 3 Mar 2024 12:35:41 -0500 Subject: [PATCH 11/71] Add documentation Passes `make docs-lint` --- .../securityhub/configuration_policy.go | 5 +- ..._configuration_policy_association.markdown | 87 ++++++++ ...rityhub_configuration_policy.html.markdown | 187 ++++++++++++++++++ ...b_organization_configuration.html.markdown | 39 +++- 4 files changed, 314 insertions(+), 4 deletions(-) create mode 100644 website/docs/r/securithub_configuration_policy_association.markdown create mode 100644 website/docs/r/securityhub_configuration_policy.html.markdown diff --git a/internal/service/securityhub/configuration_policy.go b/internal/service/securityhub/configuration_policy.go index 0f356b5f4ab..eafcf7dde30 100644 --- a/internal/service/securityhub/configuration_policy.go +++ b/internal/service/securityhub/configuration_policy.go @@ -95,9 +95,8 @@ func ResourceConfigurationPolicy() *schema.Resource { }, }, "control_custom_parameter": { - Type: schema.TypeList, - Optional: true, - Description: "https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-controls-reference.html", + Type: schema.TypeList, + Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "control_identifier": { diff --git a/website/docs/r/securithub_configuration_policy_association.markdown b/website/docs/r/securithub_configuration_policy_association.markdown new file mode 100644 index 00000000000..6f3e1affab9 --- /dev/null +++ b/website/docs/r/securithub_configuration_policy_association.markdown @@ -0,0 +1,87 @@ +--- +subcategory: "Security Hub" +layout: "aws" +page_title: "AWS: aws_securityhub_configuration_policy_association" +description: |- + Provides a resource to associate Security Hub configuration policy to a target. +--- + +# Resource: aws_securityhub_configuration_policy + +Manages Security Hub configuration policy associations. + +~> **NOTE:** This resource requires [`aws_securityhub_organization_configuration`](/docs/providers/aws/r/securityhub_organization_admin_account.html) to be configured with type `CENTRAL`. More information about Security Hub central configuration and configuration policies can be found in the [How Security Hub configuration policies work](https://docs.aws.amazon.com/securityhub/latest/userguide/configuration-policies-overview.html) documentation. + +## Example Usage + +```terraform +resource "aws_securityhub_finding_aggregator" "example" { + linking_mode = "ALL_REGIONS" +} + +resource "aws_securityhub_organization_configuration" "example" { + auto_enable = false + auto_enable_standards = "NONE" + organization_configuration { + configuration_type = "CENTRAL" + } + + depends_on = [aws_securityhub_finding_aggregator.example] +} + +resource "aws_securityhub_configuration_policy" "example" { + name = "Example" + description = "This is an example configuration policy" + security_hub_policy { + service_enabled = true + enabled_standard_arns = [ + "arn:aws:securityhub:us-east-1::standards/aws-foundational-security-best-practices/v/1.0.0", + "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0", + ] + security_controls_configuration { + disabled_control_identifiers = [] + } + } + + depends_on = [aws_securityhub_organization_configuration.example] +} + +resource "aws_securityhub_configuration_policy_association" "account_example" { + target_id = "123456789012" + policy_id = aws_securityhub_configuration_policy.example.id +} + +resource "aws_securityhub_configuration_policy_association" "root_example" { + target_id = "r-abcd" + policy_id = aws_securityhub_configuration_policy.example.id +} + +resource "aws_securityhub_configuration_policy_association" "ou_example" { + target_id = "ou-abcd-12345678" + policy_id = aws_securityhub_configuration_policy.example.id +} +``` + +## Argument Reference + +This resource supports the following arguments: + +* `target_id` - (Required, Forces new resource) The identifier of the target account, organizational unit, or the root to associate with the specified configuration. +* `policy_id` - (Required) The universally unique identifier (UUID) of the configuration policy. + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import an existing Security Hub configuration policy association using the target id. For example: + +```terraform +import { + to = aws_securityhub_configuration_policy_association.example_account_association + id = "123456789012" +} +``` + +Using `terraform import`, import an existing Security Hub enabled account using the universally unique identifier (UUID) of the policy. For example: + +```console +% terraform import aws_securityhub_configuration_policy_association.example_account_association 123456789012 +``` diff --git a/website/docs/r/securityhub_configuration_policy.html.markdown b/website/docs/r/securityhub_configuration_policy.html.markdown new file mode 100644 index 00000000000..b1725451547 --- /dev/null +++ b/website/docs/r/securityhub_configuration_policy.html.markdown @@ -0,0 +1,187 @@ +--- +subcategory: "Security Hub" +layout: "aws" +page_title: "AWS: aws_securityhub_configuration_policy" +description: |- + Provides a resource to manage Security Hub configuration policy +--- + +# Resource: aws_securityhub_configuration_policy + +Manages Security Hub configuration policy + +~> **NOTE:** This resource requires [`aws_securityhub_organization_configuration`](/docs/providers/aws/r/securityhub_organization_admin_account.html) to be configured of type `CENTRAL`. More information about Security Hub central configuration and configuration policies can be found in the [How Security Hub configuration policies work](https://docs.aws.amazon.com/securityhub/latest/userguide/configuration-policies-overview.html) documentation. + +## Example Usage + +### Default standards enabled + +```terraform +resource "aws_securityhub_finding_aggregator" "example" { + linking_mode = "ALL_REGIONS" +} + +resource "aws_securityhub_organization_configuration" "example" { + auto_enable = false + auto_enable_standards = "NONE" + organization_configuration { + configuration_type = "CENTRAL" + } + + depends_on = [aws_securityhub_finding_aggregator.example] +} + +resource "aws_securityhub_configuration_policy" "example" { + name = "Example" + description = "This is an example configuration policy" + security_hub_policy { + service_enabled = true + enabled_standard_arns = [ + "arn:aws:securityhub:us-east-1::standards/aws-foundational-security-best-practices/v/1.0.0", + "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0", + ] + security_controls_configuration { + disabled_control_identifiers = [] + } + } + + depends_on = [aws_securityhub_organization_configuration.example] +} +``` + +### Disabled Policy + +```terraform +resource "aws_securityhub_configuration_policy" "disabled" { + name = "Disabled" + description = "This is an example of disabled configuration policy" + security_hub_policy { + service_enabled = false + enabled_standard_arns = [] + } + + depends_on = [aws_securityhub_organization_configuration.example] +} +``` + +### Custom Control Configuration + +```terraform +resource "aws_securityhub_configuration_policy" "disabled" { + name = "Custom Controls" + description = "This is an example of configuration policy with custom control settings" + security_hub_policy { + service_enabled = true + enabled_standard_arns = [ + "arn:aws:securityhub:us-east-1::standards/aws-foundational-security-best-practices/v/1.0.0", + "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0", + ] + security_controls_configuration { + enabled_control_identifiers = [ + "APIGateway.1", + "IAM.7", + ] + control_custom_parameter { + control_identifier = "APIGateway.1" + parameter { + name = "loggingLevel" + value_type = "CUSTOM" + enum { + value = "INFO" + } + } + } + control_custom_parameter { + control_identifier = "IAM.7" + parameter { + name = "RequireLowercaseCharacters" + value_type = "CUSTOM" + bool { + value = false + } + } + parameter { + name = "MaxPasswordAge" + value_type = "CUSTOM" + int { + value = 60 + } + } + } + } + } + + depends_on = [aws_securityhub_organization_configuration.example] +} +``` + +## Argument Reference + +This resource supports the following arguments: + +* `name` - (Required) The name of the configuration policy. +* `description` - (Optional) The description of the configuration policy. +* `security_hub_policy` - (Required) Defines how Security Hub is configured. See [below](#security_hub_policy). + +### security_hub_policy + +The `security_hub_policy` block supports the following: + +* `service_enabled` - (Required) Indicates whether Security Hub is enabled in the policy. +* `enabled_standard_arns` - (Required) A list that defines which security standards are enabled in the configuration policy. +* `security_controls_configuration` - (Optional) Defines which security controls are enabled in the configuration policy and any customizations to parameters affecting them. See [below](#security_controls_configuration). + +### security_controls_configuration + +The `security_controls_configuration` block supports the following: + +* `disabled_control_identifiers` - (Optional) A list of security controls that are disabled in the configuration policy Security Hub enables all other controls (including newly released controls) other than the listed controls. Conflicts with `enabled_control_identifiers`. +* `enabled_control_identifiers` - (Optional) A list of security controls that are enabled in the configuration policy. Security Hub disables all other controls (including newly released controls) other than the listed controls. Conflicts with `disabled_control_identifiers`. +* `control_custom_parameter` - (Optional) A list of control parameter customizations that are included in a configuration policy. Include multiple blocks to define multiple control custom parameters. See [below](#control_custom_parameter). + +### control_custom_parameter + +The `control_custom_parameter` block supports the following: + +* `control_identifier` - (Required) The ID of the security control. For more information see the [Security Hub controls reference] documentation. +* `parameter` - (Required) An object that specifies parameter values for a control in a configuration policy. See [below](#parameter). + +### parameter + +The `parameter` block supports the following: + +* `name`: (Required) The name of the control parameter. For more information see the [Security Hub controls reference] documentation. +* `value_type`: (Required) Identifies whether a control parameter uses a custom user-defined value or subscribes to the default Security Hub behavior. Valid values: `DEFAULT`, `CUSTOM`. +* `bool`: (Optional) The bool `value` for a Boolean-typed Security Hub Control Parameter. +* `double`: (Optional) The float `value` for a Double-typed Security Hub Control Parameter. +* `enum`: (Optional) The string `value` for a Enum-typed Security Hub Control Parameter. +* `enum_list`: (Optional) The string list `value` for a EnumList-typed Security Hub Control Parameter. +* `int`: (Optional) The int `value` for a Int-typed Security Hub Control Parameter. +* `int_list`: (Optional) The int list `value` for a IntList-typed Security Hub Control Parameter. +* `int_list`: (Optional) The int list `value` for a IntList-typed Security Hub Control Parameter. +* `string`: (Optional) The string `value` for a String-typed Security Hub Control Parameter. +* `string_list`: (Optional) The string list `value` for a StringList-typed Security Hub Control Parameter. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `id` - The UUID of the configuration policy. +* `arn ` - The ARN of the configuration policy. + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import an existing Security Hub configuration policy using the universally unique identifier (UUID) of the policy. For example: + +```terraform +import { + to = aws_securityhub_configuration_policy.example + id = "00000000-1111-2222-3333-444444444444" +} +``` + +Using `terraform import`, import an existing Security Hub enabled account using the universally unique identifier (UUID) of the policy. For example: + +```console +% terraform import aws_securityhub_configuration_policy.example "00000000-1111-2222-3333-444444444444" +``` diff --git a/website/docs/r/securityhub_organization_configuration.html.markdown b/website/docs/r/securityhub_organization_configuration.html.markdown index 416038853e1..7d339fd4dc9 100644 --- a/website/docs/r/securityhub_organization_configuration.html.markdown +++ b/website/docs/r/securityhub_organization_configuration.html.markdown @@ -10,12 +10,18 @@ description: |- Manages the Security Hub Organization Configuration. -~> **NOTE:** This resource requires an [`aws_securityhub_organization_admin_account`](/docs/providers/aws/r/securityhub_organization_admin_account.html) to be configured (not necessarily with Terraform). More information about managing Security Hub in an organization can be found in the [Managing administrator and member accounts](https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-accounts.html) documentation +~> **NOTE:** This resource requires an [`aws_securityhub_organization_admin_account`](/docs/providers/aws/r/securityhub_organization_admin_account.html) to be configured (not necessarily with Terraform). More information about managing Security Hub in an organization can be found in the [Managing administrator and member accounts](https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-accounts.html) documentation. + +~> **NOTE:** In order to set the `configuration_type` to `CENTRAL`, the delegated admin must be a member account of the organization and not the management account. Central configuration also requires an [`aws_securityhub_finding_aggregator`](/docs/providers/aws/r/securityhub_finding_aggregator.html) to be configured. ~> **NOTE:** This is an advanced Terraform resource. Terraform will automatically assume management of the Security Hub Organization Configuration without import and perform no actions on removal from the Terraform configuration. +~> **NOTE:** Deleting this resource resets security hub to a local organization configuration with auto enable false. + ## Example Usage +### Local Configuration + ```terraform resource "aws_organizations_organization" "example" { aws_service_access_principals = ["securityhub.amazonaws.com"] @@ -33,12 +39,43 @@ resource "aws_securityhub_organization_configuration" "example" { } ``` +### Central Configuration + +```terraform +resource "aws_securityhub_organization_admin_account" "example" { + depends_on = [aws_organizations_organization.example] + + admin_account_id = "123456789012" +} + +resource "aws_securityhub_finding_aggregator" "example" { + linking_mode = "ALL_REGIONS" + + depends_on = [aws_securityhub_organization_admin_account.example] +} + +resource "aws_securityhub_organization_configuration" "example" { + auto_enable = false + auto_enable_standards = "NONE" + organization_configuration { + configuration_type = "CENTRAL" + } + + depends_on = [aws_securityhub_finding_aggregator.example] +} +``` + ## Argument Reference This resource supports the following arguments: * `auto_enable` - (Required) Whether to automatically enable Security Hub for new accounts in the organization. * `auto_enable_standards` - (Optional) Whether to automatically enable Security Hub default standards for new member accounts in the organization. By default, this parameter is equal to `DEFAULT`, and new member accounts are automatically enabled with default Security Hub standards. To opt out of enabling default standards for new member accounts, set this parameter equal to `NONE`. +* `organization_configuration` - (Optional) Provides information about the way an organization is configured in Security Hub. + +`organization_configuration` supports the following: + +* `configuration_type` - (Required) Indicates whether the organization uses local or central configuration. If using central configuration, `auto_enable` must be set to `false` and `auto_enable_standards` set to `NONE`. More information can be found in the [documentation for central configuration](https://docs.aws.amazon.com/securityhub/latest/userguide/central-configuration-intro.html). Valid values: `LOCAL`, `CENTRAL`. ## Attribute Reference From 933063420e2662caab8157608704bc96e6f36aa3 Mon Sep 17 00:00:00 2001 From: twelsh-aw <84401379+twelsh-aw@users.noreply.github.com> Date: Sun, 3 Mar 2024 12:42:53 -0500 Subject: [PATCH 12/71] Update error check configuration after merges This got re-named --- .../securityhub/configuration_policy_association_test.go | 2 +- internal/service/securityhub/configuration_policy_test.go | 6 +++--- .../service/securityhub/organization_configuration_test.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/service/securityhub/configuration_policy_association_test.go b/internal/service/securityhub/configuration_policy_association_test.go index 93a36585483..046929bbc8f 100644 --- a/internal/service/securityhub/configuration_policy_association_test.go +++ b/internal/service/securityhub/configuration_policy_association_test.go @@ -33,7 +33,7 @@ func testAccConfigurationPolicyAssociation_basic(t *testing.T) { acctest.PreCheckAlternateRegionIs(t, acctest.Region()) acctest.PreCheckOrganizationMemberAccount(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubEndpointID), + ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubServiceID), ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ diff --git a/internal/service/securityhub/configuration_policy_test.go b/internal/service/securityhub/configuration_policy_test.go index 8bfa5fa0f72..2bd6dbb0496 100644 --- a/internal/service/securityhub/configuration_policy_test.go +++ b/internal/service/securityhub/configuration_policy_test.go @@ -27,7 +27,7 @@ func testAccConfigurationPolicy_basic(t *testing.T) { acctest.PreCheckAlternateRegionIs(t, acctest.Region()) acctest.PreCheckOrganizationMemberAccount(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubEndpointID), + ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubServiceID), ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ @@ -77,7 +77,7 @@ func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { acctest.PreCheckAlternateRegionIs(t, acctest.Region()) acctest.PreCheckOrganizationMemberAccount(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubEndpointID), + ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubServiceID), ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ @@ -219,7 +219,7 @@ func testAccConfigurationPolicy_specificControlIdentifiers(t *testing.T) { acctest.PreCheckAlternateRegionIs(t, acctest.Region()) acctest.PreCheckOrganizationMemberAccount(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubEndpointID), + ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubServiceID), ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ diff --git a/internal/service/securityhub/organization_configuration_test.go b/internal/service/securityhub/organization_configuration_test.go index 0f830281ddc..6cef8ccf64e 100644 --- a/internal/service/securityhub/organization_configuration_test.go +++ b/internal/service/securityhub/organization_configuration_test.go @@ -96,7 +96,7 @@ func testAccOrganizationConfiguration_centralConfiguration(t *testing.T) { acctest.PreCheckAlternateRegionIs(t, acctest.Region()) acctest.PreCheckOrganizationMemberAccount(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubEndpointID), + ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubServiceID), ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ From a0515f580d4f6bbef3f53a60b027cf4f9efbfe0a Mon Sep 17 00:00:00 2001 From: twelsh-aw <84401379+twelsh-aw@users.noreply.github.com> Date: Mon, 4 Mar 2024 08:29:50 -0500 Subject: [PATCH 13/71] Remove security hub account resource in central config test setup Delegating as admin implicitly creates the security hub account so this is not needed. We remove this to make tests less flaky, as sometimes security hub would remain active in the region after test cleanup, which would interfere with subsequent tests --- internal/service/securityhub/securityhub_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/internal/service/securityhub/securityhub_test.go b/internal/service/securityhub/securityhub_test.go index a55ea913d4c..547d202f4c3 100644 --- a/internal/service/securityhub/securityhub_test.go +++ b/internal/service/securityhub/securityhub_test.go @@ -109,15 +109,11 @@ func TestAccSecurityHub_centralConfiguration(t *testing.T) { } const testAccMemberAccountDelegatedAdminConfig_base = ` -resource "aws_securityhub_account" "test" {} - data "aws_caller_identity" "member" {} resource "aws_securityhub_organization_admin_account" "test" { provider = awsalternate admin_account_id = data.aws_caller_identity.member.account_id - - depends_on = [aws_securityhub_account.test] } ` From fa30e8e6eb446ad880f879687a60f29ebafcbfdc Mon Sep 17 00:00:00 2001 From: twelsh-aw <84401379+twelsh-aw@users.noreply.github.com> Date: Mon, 4 Mar 2024 08:52:18 -0500 Subject: [PATCH 14/71] Speed up configuration_policy_association_test.go The OU attachment is much faster since it's empty --- .../configuration_policy_association_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/service/securityhub/configuration_policy_association_test.go b/internal/service/securityhub/configuration_policy_association_test.go index 046929bbc8f..f801ee97c13 100644 --- a/internal/service/securityhub/configuration_policy_association_test.go +++ b/internal/service/securityhub/configuration_policy_association_test.go @@ -38,10 +38,10 @@ func testAccConfigurationPolicyAssociation_basic(t *testing.T) { CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { - Config: testAccConfigurationPolicyAssociationConfig_base(accountTarget, policy1), + Config: testAccConfigurationPolicyAssociationConfig_base(ouTarget, policy1), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyAssociationExists(ctx, resourceName), - resource.TestCheckResourceAttrPair(resourceName, "target_id", "data.aws_caller_identity.member", "account_id"), + resource.TestCheckResourceAttrPair(resourceName, "target_id", "aws_organizations_organizational_unit.test", "id"), resource.TestCheckResourceAttrPair(resourceName, "policy_id", "aws_securityhub_configuration_policy.test_1", "id"), ), }, @@ -51,10 +51,10 @@ func testAccConfigurationPolicyAssociation_basic(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccConfigurationPolicyAssociationConfig_base(accountTarget, policy2), + Config: testAccConfigurationPolicyAssociationConfig_base(ouTarget, policy2), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyAssociationExists(ctx, resourceName), - resource.TestCheckResourceAttrPair(resourceName, "target_id", "data.aws_caller_identity.member", "account_id"), + resource.TestCheckResourceAttrPair(resourceName, "target_id", "aws_organizations_organizational_unit.test", "id"), resource.TestCheckResourceAttrPair(resourceName, "policy_id", "aws_securityhub_configuration_policy.test_2", "id"), ), }, @@ -67,10 +67,10 @@ func testAccConfigurationPolicyAssociation_basic(t *testing.T) { ), }, { - Config: testAccConfigurationPolicyAssociationConfig_base(ouTarget, policy2), + Config: testAccConfigurationPolicyAssociationConfig_base(accountTarget, policy2), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyAssociationExists(ctx, resourceName), - resource.TestCheckResourceAttrPair(resourceName, "target_id", "aws_organizations_organizational_unit.test", "id"), + resource.TestCheckResourceAttrPair(resourceName, "target_id", "data.aws_caller_identity.member", "account_id"), resource.TestCheckResourceAttrPair(resourceName, "policy_id", "aws_securityhub_configuration_policy.test_2", "id"), ), }, From d163ce9e7f6ab80192d007691e72cee13de3f25e Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 6 Mar 2024 15:12:35 -0500 Subject: [PATCH 15/71] Add CHANGELOG entry. --- .changelog/35752.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .changelog/35752.txt diff --git a/.changelog/35752.txt b/.changelog/35752.txt new file mode 100644 index 00000000000..1fb0c0ba81d --- /dev/null +++ b/.changelog/35752.txt @@ -0,0 +1,11 @@ +```release-note:new-resource +aws_securityhub_configuration_policy +``` + +```release-note:new-resource +aws_securityhub_configuration_policy_association +``` + +```release-note:enhancement +resource/aws_securityhub_organization_configuration: Add `organization_configuration` configuration block to support [central configuration](https://docs.aws.amazon.com/securityhub/latest/userguide/start-central-configuration.html) +``` \ No newline at end of file From 07dd6dedd374c767e07f174c80acfb13a5a27a32 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 6 Mar 2024 15:16:15 -0500 Subject: [PATCH 16/71] Fix semgrep 'ci.semgrep.aws.pointer-conversion-ResourceData-SetId'. --- internal/service/securityhub/configuration_policy.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/internal/service/securityhub/configuration_policy.go b/internal/service/securityhub/configuration_policy.go index eafcf7dde30..2162b9c8500 100644 --- a/internal/service/securityhub/configuration_policy.go +++ b/internal/service/securityhub/configuration_policy.go @@ -263,8 +263,9 @@ func resourceConfigurationPolicyCreate(ctx context.Context, d *schema.ResourceDa var diags diag.Diagnostics conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) + name := d.Get("name").(string) input := &securityhub.CreateConfigurationPolicyInput{ - Name: aws.String(d.Get("name").(string)), + Name: aws.String(name), } if v, ok := d.GetOk("description"); ok { @@ -274,18 +275,19 @@ func resourceConfigurationPolicyCreate(ctx context.Context, d *schema.ResourceDa if v, ok := d.Get("security_hub_policy").([]interface{}); ok && len(v) > 0 { policy := expandSecurityHubPolicy(v[0].(map[string]interface{})) if err := validateSecurityHubPolicy(policy); err != nil { - return sdkdiag.AppendErrorf(diags, "creating Security Hub Configuration Policy (%s): %s", *input.Name, err) + return sdkdiag.AppendFromErr(diags, err) } input.ConfigurationPolicy = policy } - out, err := conn.CreateConfigurationPolicy(ctx, input) + output, err := conn.CreateConfigurationPolicy(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "creating Security Hub Configuration Policy (%s): %s", *input.Name, err) + return sdkdiag.AppendErrorf(diags, "creating Security Hub Configuration Policy (%s): %s", name, err) } if d.IsNewResource() { - d.SetId(*out.Id) + d.SetId(aws.ToString(output.Id)) } return append(diags, resourceConfigurationPolicyRead(ctx, d, meta)...) From 80e8be57537d966473d39e4b0035236e4202fe84 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 6 Mar 2024 15:18:09 -0500 Subject: [PATCH 17/71] Fix semgrep 'ci.typed-enum-conversion'. --- .../securityhub/configuration_policy_association.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/internal/service/securityhub/configuration_policy_association.go b/internal/service/securityhub/configuration_policy_association.go index daa2fee6c2f..96f6ae33b8a 100644 --- a/internal/service/securityhub/configuration_policy_association.go +++ b/internal/service/securityhub/configuration_policy_association.go @@ -198,12 +198,9 @@ func getConfigurationPolicyAssociation(ctx context.Context, conn *securityhub.Cl statusErr = fmt.Errorf("StatusMessage: %s", *msg) } return nil, "", &retry.UnexpectedStateError{ - LastError: statusErr, - State: string(output.AssociationStatus), - ExpectedState: []string{ - string(types.ConfigurationPolicyAssociationStatusPending), - string(types.ConfigurationPolicyAssociationStatusSuccess), - }, + LastError: statusErr, + State: string(output.AssociationStatus), + ExpectedState: enum.Slice(types.ConfigurationPolicyAssociationStatusPending, types.ConfigurationPolicyAssociationStatusSuccess), } } } From 34bd792aa595c8e9dab31724f3704c1d4c9ec0c8 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 6 Mar 2024 15:19:07 -0500 Subject: [PATCH 18/71] Fix semgrep 'ci.calling-SetId-in-resource-delete'. --- internal/service/securityhub/organization_configuration.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/service/securityhub/organization_configuration.go b/internal/service/securityhub/organization_configuration.go index c348a1d5917..007a33aa93b 100644 --- a/internal/service/securityhub/organization_configuration.go +++ b/internal/service/securityhub/organization_configuration.go @@ -123,7 +123,6 @@ func resourceOrganizationConfigurationDelete(ctx context.Context, d *schema.Reso return sdkdiag.AppendErrorf(diags, "deleting Security Hub Organization Configuration (%s): %s", d.Id(), err) } - d.SetId("") return diags } From 15c350faa62c74cd0bbc9979a297f3dc1c31ee84 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 6 Mar 2024 15:20:17 -0500 Subject: [PATCH 19/71] Fix semgrep 'ci.typed-enum-conversion'. --- .../service/securityhub/organization_configuration.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/internal/service/securityhub/organization_configuration.go b/internal/service/securityhub/organization_configuration.go index 007a33aa93b..492b896c861 100644 --- a/internal/service/securityhub/organization_configuration.go +++ b/internal/service/securityhub/organization_configuration.go @@ -183,12 +183,9 @@ func findOrganizationConfiguration(ctx context.Context, conn *securityhub.Client statusErr = fmt.Errorf("StatusMessage: %s", *msg) } return nil, "", &retry.UnexpectedStateError{ - LastError: statusErr, - State: string(output.OrganizationConfiguration.Status), - ExpectedState: []string{ - string(types.OrganizationConfigurationStatusEnabled), - string(types.OrganizationConfigurationStatusPending), - }, + LastError: statusErr, + State: string(output.OrganizationConfiguration.Status), + ExpectedState: enum.Slice(types.OrganizationConfigurationStatusEnabled, types.OrganizationConfigurationStatusPending), } } } From 4caecc5f4b5c599b600543fc49497fb7b316a021 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 6 Mar 2024 16:27:59 -0500 Subject: [PATCH 20/71] Fix semgrep 'ci.caps2-in-var-name'. --- internal/service/securityhub/configuration_policy_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/service/securityhub/configuration_policy_test.go b/internal/service/securityhub/configuration_policy_test.go index 2bd6dbb0496..b3adbf661b1 100644 --- a/internal/service/securityhub/configuration_policy_test.go +++ b/internal/service/securityhub/configuration_policy_test.go @@ -19,7 +19,7 @@ import ( func testAccConfigurationPolicy_basic(t *testing.T) { ctx := acctest.Context(t) resourceName := "aws_securityhub_configuration_policy.test" - const exampleStandardsArn = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0" + const exampleStandardsARN = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) @@ -49,7 +49,7 @@ func testAccConfigurationPolicy_basic(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccConfigurationPolicyConfig_baseEnabled("TestPolicy", "This is an enabled policy", exampleStandardsArn), + Config: testAccConfigurationPolicyConfig_baseEnabled("TestPolicy", "This is an enabled policy", exampleStandardsARN), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "name", "TestPolicy"), @@ -57,7 +57,7 @@ func testAccConfigurationPolicy_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "security_hub_policy.#", "1"), resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.service_enabled", "true"), resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.enabled_standard_arns.#", "1"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.enabled_standard_arns.0", exampleStandardsArn), + resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.enabled_standard_arns.0", exampleStandardsARN), resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.#", "1"), ), }, From 84296576d9af16d0729e17c54892db3d1acbec47 Mon Sep 17 00:00:00 2001 From: twelsh-aw <84401379+twelsh-aw@users.noreply.github.com> Date: Wed, 6 Mar 2024 16:22:30 -0500 Subject: [PATCH 21/71] Lint fixes --- .../securityhub/configuration_policy.go | 24 +- .../configuration_policy_association.go | 8 +- .../configuration_policy_association_test.go | 59 ++-- .../securityhub/configuration_policy_test.go | 322 +++++++++--------- .../securityhub/organization_configuration.go | 9 +- ...rityhub_configuration_policy.html.markdown | 22 +- ...configuration_policy_association.markdown} | 6 +- 7 files changed, 222 insertions(+), 228 deletions(-) rename website/docs/r/{securithub_configuration_policy_association.markdown => securityhub_configuration_policy_association.markdown} (98%) diff --git a/internal/service/securityhub/configuration_policy.go b/internal/service/securityhub/configuration_policy.go index eafcf7dde30..7b7557184a7 100644 --- a/internal/service/securityhub/configuration_policy.go +++ b/internal/service/securityhub/configuration_policy.go @@ -48,7 +48,7 @@ func ResourceConfigurationPolicy() *schema.Resource { Type: schema.TypeString, Optional: true, }, - "security_hub_policy": { + "policy_member": { Type: schema.TypeList, Required: true, MaxItems: 1, @@ -80,7 +80,7 @@ func ResourceConfigurationPolicy() *schema.Resource { ValidateFunc: validation.StringIsNotEmpty, }, ConflictsWith: []string{ - "security_hub_policy.0.security_controls_configuration.0.enabled_control_identifiers", + "policy_member.0.security_controls_configuration.0.enabled_control_identifiers", }, }, "enabled_control_identifiers": { @@ -91,7 +91,7 @@ func ResourceConfigurationPolicy() *schema.Resource { ValidateFunc: validation.StringIsNotEmpty, }, ConflictsWith: []string{ - "security_hub_policy.0.security_controls_configuration.0.disabled_control_identifiers", + "policy_member.0.security_controls_configuration.0.disabled_control_identifiers", }, }, "control_custom_parameter": { @@ -271,9 +271,9 @@ func resourceConfigurationPolicyCreate(ctx context.Context, d *schema.ResourceDa input.Description = aws.String(v.(string)) } - if v, ok := d.Get("security_hub_policy").([]interface{}); ok && len(v) > 0 { + if v, ok := d.Get("policy_member").([]interface{}); ok && len(v) > 0 { policy := expandSecurityHubPolicy(v[0].(map[string]interface{})) - if err := validateSecurityHubPolicy(policy); err != nil { + if err := validatePolicyMember(policy); err != nil { return sdkdiag.AppendErrorf(diags, "creating Security Hub Configuration Policy (%s): %s", *input.Name, err) } input.ConfigurationPolicy = policy @@ -285,7 +285,7 @@ func resourceConfigurationPolicyCreate(ctx context.Context, d *schema.ResourceDa } if d.IsNewResource() { - d.SetId(*out.Id) + d.SetId(aws.ToString(out.Id)) } return append(diags, resourceConfigurationPolicyRead(ctx, d, meta)...) @@ -304,9 +304,9 @@ func resourceConfigurationPolicyUpdate(ctx context.Context, d *schema.ResourceDa input.Description = aws.String(v.(string)) } - if v, ok := d.Get("security_hub_policy").([]interface{}); ok && len(v) > 0 { + if v, ok := d.Get("policy_member").([]interface{}); ok && len(v) > 0 { policy := expandSecurityHubPolicy(v[0].(map[string]interface{})) - if err := validateSecurityHubPolicy(policy); err != nil { + if err := validatePolicyMember(policy); err != nil { return sdkdiag.AppendErrorf(diags, "updating Security Hub Configuration Policy (%s): %s", d.Id(), err) } input.ConfigurationPolicy = policy @@ -342,8 +342,8 @@ func resourceConfigurationPolicyRead(ctx context.Context, d *schema.ResourceData d.Set("name", out.Name) d.Set("description", out.Description) d.Set("arn", out.Arn) - if err := d.Set("security_hub_policy", []interface{}{flattenSecurityHubPolicy(out.ConfigurationPolicy)}); err != nil { - return sdkdiag.AppendErrorf(diags, "setting security_hub_policy: %s", err) + if err := d.Set("policy_member", []interface{}{flattenSecurityHubPolicy(out.ConfigurationPolicy)}); err != nil { + return sdkdiag.AppendErrorf(diags, "setting policy_member: %s", err) } return diags @@ -365,8 +365,8 @@ func resourceConfigurationPolicyDelete(ctx context.Context, d *schema.ResourceDa return diags } -// validateSecurityHubPolicy performs validation before running creates/updates to prevent certain issues with state. -func validateSecurityHubPolicy(apiPolicy *types.PolicyMemberSecurityHub) error { +// validatePolicyMember performs validation before running creates/updates to prevent certain issues with state. +func validatePolicyMember(apiPolicy *types.PolicyMemberSecurityHub) error { // security_controls_configuration can be specified in Creates/Updates and accepted by the APIs, // but the resources returned by subsequent Get API call will be nil instead of non-nil. // This leaves terraform in perpetual drift and so we prevent this case explicitly. diff --git a/internal/service/securityhub/configuration_policy_association.go b/internal/service/securityhub/configuration_policy_association.go index daa2fee6c2f..e27043c6ffe 100644 --- a/internal/service/securityhub/configuration_policy_association.go +++ b/internal/service/securityhub/configuration_policy_association.go @@ -200,10 +200,10 @@ func getConfigurationPolicyAssociation(ctx context.Context, conn *securityhub.Cl return nil, "", &retry.UnexpectedStateError{ LastError: statusErr, State: string(output.AssociationStatus), - ExpectedState: []string{ - string(types.ConfigurationPolicyAssociationStatusPending), - string(types.ConfigurationPolicyAssociationStatusSuccess), - }, + ExpectedState: enum.Slice( + types.ConfigurationPolicyAssociationStatusPending, + types.ConfigurationPolicyAssociationStatusSuccess, + ), } } } diff --git a/internal/service/securityhub/configuration_policy_association_test.go b/internal/service/securityhub/configuration_policy_association_test.go index f801ee97c13..95d6af09d7a 100644 --- a/internal/service/securityhub/configuration_policy_association_test.go +++ b/internal/service/securityhub/configuration_policy_association_test.go @@ -103,42 +103,43 @@ func testAccCheckConfigurationPolicyAssociationExists(ctx context.Context, n str const testAccOrganizationalUnitConfig_base = ` data "aws_organizations_organization" "test" { - provider = awsalternate + provider = awsalternate } resource "aws_organizations_organizational_unit" "test" { - provider = awsalternate + provider = awsalternate - name = "testAccConfigurationPolicyOrgUnitConfig_base" - parent_id = data.aws_organizations_organization.test.roots[0].id + name = "testAccConfigurationPolicyOrgUnitConfig_base" + parent_id = data.aws_organizations_organization.test.roots[0].id } ` +//lintignore:AWSAT005 const testAccConfigurationPoliciesConfig_base = ` resource "aws_securityhub_configuration_policy" "test_1" { - name = "test1" - security_hub_policy { - service_enabled = true - enabled_standard_arns = ["arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"] - security_controls_configuration { - disabled_control_identifiers = [] - } - } - - depends_on = [aws_securityhub_organization_configuration.test] + name = "test1" + policy_member { + service_enabled = true + enabled_standard_arns = ["arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"] + security_controls_configuration { + disabled_control_identifiers = [] + } + } + + depends_on = [aws_securityhub_organization_configuration.test] } resource "aws_securityhub_configuration_policy" "test_2" { - name = "test2" - security_hub_policy { - service_enabled = true - enabled_standard_arns = ["arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"] - security_controls_configuration { - enabled_control_identifiers = ["CloudTrail.1"] - } - } - - depends_on = [aws_securityhub_organization_configuration.test] + name = "test2" + policy_member { + service_enabled = true + enabled_standard_arns = ["arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"] + security_controls_configuration { + enabled_control_identifiers = ["CloudTrail.1"] + } + } + + depends_on = [aws_securityhub_organization_configuration.test] } ` @@ -150,9 +151,9 @@ func testAccConfigurationPolicyAssociationConfig_base(targetID, policyID string) testAccCentralConfigurationEnabledConfig_base, testAccConfigurationPoliciesConfig_base, fmt.Sprintf(` - resource "aws_securityhub_configuration_policy_association" "test" { - target_id = %[1]s - policy_id = %[2]s - } - `, targetID, policyID)) +resource "aws_securityhub_configuration_policy_association" "test" { + target_id = %[1]s + policy_id = %[2]s +} +`, targetID, policyID)) } diff --git a/internal/service/securityhub/configuration_policy_test.go b/internal/service/securityhub/configuration_policy_test.go index 2bd6dbb0496..9c1a25c805e 100644 --- a/internal/service/securityhub/configuration_policy_test.go +++ b/internal/service/securityhub/configuration_policy_test.go @@ -19,7 +19,7 @@ import ( func testAccConfigurationPolicy_basic(t *testing.T) { ctx := acctest.Context(t) resourceName := "aws_securityhub_configuration_policy.test" - const exampleStandardsArn = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0" + const exampleStandardsARN = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0" //lintignore:AWSAT005 resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) @@ -37,10 +37,10 @@ func testAccConfigurationPolicy_basic(t *testing.T) { testAccCheckConfigurationPolicyExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "name", "TestPolicy"), resource.TestCheckResourceAttr(resourceName, "description", "This is a disabled policy"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.#", "1"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.service_enabled", "false"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.enabled_standard_arns.#", "0"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.#", "0"), + resource.TestCheckResourceAttr(resourceName, "policy_member.#", "1"), + resource.TestCheckResourceAttr(resourceName, "policy_member.0.service_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "policy_member.0.enabled_standard_arns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.#", "0"), ), }, { @@ -49,16 +49,16 @@ func testAccConfigurationPolicy_basic(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccConfigurationPolicyConfig_baseEnabled("TestPolicy", "This is an enabled policy", exampleStandardsArn), + Config: testAccConfigurationPolicyConfig_baseEnabled("TestPolicy", "This is an enabled policy", exampleStandardsARN), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "name", "TestPolicy"), resource.TestCheckResourceAttr(resourceName, "description", "This is an enabled policy"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.#", "1"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.service_enabled", "true"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.enabled_standard_arns.#", "1"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.enabled_standard_arns.0", exampleStandardsArn), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "policy_member.#", "1"), + resource.TestCheckResourceAttr(resourceName, "policy_member.0.service_enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "policy_member.0.enabled_standard_arns.#", "1"), + resource.TestCheckResourceAttr(resourceName, "policy_member.0.enabled_standard_arns.0", exampleStandardsARN), + resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.#", "1"), ), }, }, @@ -68,8 +68,8 @@ func testAccConfigurationPolicy_basic(t *testing.T) { func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { ctx := acctest.Context(t) resourceName := "aws_securityhub_configuration_policy.test" - foundationalStandardsArn := fmt.Sprintf("arn:aws:securityhub:%s::standards/aws-foundational-security-best-practices/v/1.0.0", acctest.Region()) - nistStandardsArn := fmt.Sprintf("arn:aws:securityhub:%s::standards/nist-800-53/v/5.0.0", acctest.Region()) + foundationalStandardsARN := fmt.Sprintf("arn:aws:securityhub:%s::standards/aws-foundational-security-best-practices/v/1.0.0", acctest.Region()) //lintignore:AWSAT005 + nistStandardsARN := fmt.Sprintf("arn:aws:securityhub:%s::standards/nist-800-53/v/5.0.0", acctest.Region()) //lintignore:AWSAT005 resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) @@ -82,34 +82,34 @@ func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { - Config: testAccConfigurationPolicyConfig_controlCustomParametersMulti(foundationalStandardsArn), + Config: testAccConfigurationPolicyConfig_controlCustomParametersMulti(foundationalStandardsARN), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyExists(ctx, resourceName), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.#", "1"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.#", "1"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.#", "2"), + resource.TestCheckResourceAttr(resourceName, "policy_member.#", "1"), + resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.#", "2"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "APIGateway.1"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.parameter.#", "1"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ + resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "APIGateway.1"), + resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.parameter.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ "name": "loggingLevel", "value_type": "CUSTOM", "enum.0.value": "INFO", }), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.1.control_identifier", "IAM.7"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.1.parameter.#", "3"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.1.parameter.*", map[string]string{ + resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.1.control_identifier", "IAM.7"), + resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.1.parameter.#", "3"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.1.parameter.*", map[string]string{ "name": "RequireLowercaseCharacters", "value_type": "CUSTOM", "bool.0.value": "false", }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.1.parameter.*", map[string]string{ + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.1.parameter.*", map[string]string{ "name": "RequireUppercaseCharacters", "value_type": "DEFAULT", }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.1.parameter.*", map[string]string{ + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.1.parameter.*", map[string]string{ "name": "MaxPasswordAge", "value_type": "CUSTOM", "int.0.value": "60", @@ -123,11 +123,11 @@ func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { }, { // bool type - Config: testAccConfigurationPolicyConfig_controlCustomParametersSingle(nistStandardsArn, "CloudWatch.15", "insufficientDataActionRequired", "bool", "true"), + Config: testAccConfigurationPolicyConfig_controlCustomParametersSingle(nistStandardsARN, "CloudWatch.15", "insufficientDataActionRequired", "bool", "true"), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyExists(ctx, resourceName), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "CloudWatch.15"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ + resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "CloudWatch.15"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ "name": "insufficientDataActionRequired", "value_type": "CUSTOM", "bool.0.value": "true", @@ -136,11 +136,11 @@ func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { }, { // double type - Config: testAccConfigurationPolicyConfig_controlCustomParametersSingle(foundationalStandardsArn, "RDS.14", "BacktrackWindowInHours", "double", "20.25"), + Config: testAccConfigurationPolicyConfig_controlCustomParametersSingle(foundationalStandardsARN, "RDS.14", "BacktrackWindowInHours", "double", "20.25"), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyExists(ctx, resourceName), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "RDS.14"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ + resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "RDS.14"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ "name": "BacktrackWindowInHours", "value_type": "CUSTOM", "double.0.value": "20.25", @@ -149,11 +149,11 @@ func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { }, { // enum type - Config: testAccConfigurationPolicyConfig_controlCustomParametersSingle(foundationalStandardsArn, "APIGateway.1", "loggingLevel", "enum", `"ERROR"`), + Config: testAccConfigurationPolicyConfig_controlCustomParametersSingle(foundationalStandardsARN, "APIGateway.1", "loggingLevel", "enum", `"ERROR"`), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyExists(ctx, resourceName), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "APIGateway.1"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ + resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "APIGateway.1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ "name": "loggingLevel", "value_type": "CUSTOM", "enum.0.value": "ERROR", @@ -162,11 +162,11 @@ func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { }, { // enum_list type - Config: testAccConfigurationPolicyConfig_controlCustomParametersSingle(foundationalStandardsArn, "S3.11", "eventTypes", "enum_list", `["s3:IntelligentTiering", "s3:LifecycleExpiration:*"]`), + Config: testAccConfigurationPolicyConfig_controlCustomParametersSingle(foundationalStandardsARN, "S3.11", "eventTypes", "enum_list", `["s3:IntelligentTiering", "s3:LifecycleExpiration:*"]`), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyExists(ctx, resourceName), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "S3.11"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ + resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "S3.11"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ "name": "eventTypes", "value_type": "CUSTOM", "enum_list.0.value.#": "2", @@ -177,11 +177,11 @@ func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { }, { // int type - Config: testAccConfigurationPolicyConfig_controlCustomParametersSingle(foundationalStandardsArn, "DocumentDB.2", "minimumBackupRetentionPeriod", "int", "20"), + Config: testAccConfigurationPolicyConfig_controlCustomParametersSingle(foundationalStandardsARN, "DocumentDB.2", "minimumBackupRetentionPeriod", "int", "20"), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyExists(ctx, resourceName), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "DocumentDB.2"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ + resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "DocumentDB.2"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ "name": "minimumBackupRetentionPeriod", "value_type": "CUSTOM", "int.0.value": "20", @@ -190,11 +190,11 @@ func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { }, { // int_list type - Config: testAccConfigurationPolicyConfig_controlCustomParametersSingle(foundationalStandardsArn, "EC2.18", "authorizedTcpPorts", "int_list", "[443, 8080]"), + Config: testAccConfigurationPolicyConfig_controlCustomParametersSingle(foundationalStandardsARN, "EC2.18", "authorizedTcpPorts", "int_list", "[443, 8080]"), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyExists(ctx, resourceName), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "EC2.18"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "security_hub_policy.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ + resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "EC2.18"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ "name": "authorizedTcpPorts", "value_type": "CUSTOM", "int_list.0.value.#": "2", @@ -211,7 +211,7 @@ func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { func testAccConfigurationPolicy_specificControlIdentifiers(t *testing.T) { ctx := acctest.Context(t) resourceName := "aws_securityhub_configuration_policy.test" - foundationalStandardsArn := fmt.Sprintf("arn:aws:securityhub:%s::standards/aws-foundational-security-best-practices/v/1.0.0", acctest.Region()) + foundationalStandardsARN := fmt.Sprintf("arn:aws:securityhub:%s::standards/aws-foundational-security-best-practices/v/1.0.0", acctest.Region()) //lintignore:AWSAT005 resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) @@ -224,16 +224,16 @@ func testAccConfigurationPolicy_specificControlIdentifiers(t *testing.T) { CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { - Config: testAccConfigurationPolicyConfig_specifcControlIdentifiers(foundationalStandardsArn, "IAM.7", "APIGateway.1", false), + Config: testAccConfigurationPolicyConfig_specifcControlIdentifiers(foundationalStandardsARN, "IAM.7", "APIGateway.1", false), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyExists(ctx, resourceName), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.#", "1"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.#", "1"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.disabled_control_identifiers.#", "2"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.disabled_control_identifiers.0", "IAM.7"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.disabled_control_identifiers.1", "APIGateway.1"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.enabled_control_identifiers.#", "0"), + resource.TestCheckResourceAttr(resourceName, "policy_member.#", "1"), + resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.disabled_control_identifiers.#", "2"), + resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.disabled_control_identifiers.0", "IAM.7"), + resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.disabled_control_identifiers.1", "APIGateway.1"), + resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.enabled_control_identifiers.#", "0"), ), }, { @@ -242,16 +242,16 @@ func testAccConfigurationPolicy_specificControlIdentifiers(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccConfigurationPolicyConfig_specifcControlIdentifiers(foundationalStandardsArn, "APIGateway.1", "IAM.7", true), + Config: testAccConfigurationPolicyConfig_specifcControlIdentifiers(foundationalStandardsARN, "APIGateway.1", "IAM.7", true), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyExists(ctx, resourceName), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.#", "1"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.#", "1"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.enabled_control_identifiers.#", "2"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.enabled_control_identifiers.0", "APIGateway.1"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.enabled_control_identifiers.1", "IAM.7"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.0.disabled_control_identifiers.#", "0"), + resource.TestCheckResourceAttr(resourceName, "policy_member.#", "1"), + resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.enabled_control_identifiers.#", "2"), + resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.enabled_control_identifiers.0", "APIGateway.1"), + resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.enabled_control_identifiers.1", "IAM.7"), + resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.disabled_control_identifiers.#", "0"), ), }, }, @@ -282,7 +282,7 @@ func testAccConfigurationPolicyConfig_baseDisabled(name, description string) str resource "aws_securityhub_configuration_policy" "test" { name = %[1]q description = %[2]q - security_hub_policy { + policy_member { service_enabled = false enabled_standard_arns = [] } @@ -297,111 +297,109 @@ func testAccConfigurationPolicyConfig_baseEnabled(name, description string, enab testAccMemberAccountDelegatedAdminConfig_base, testAccCentralConfigurationEnabledConfig_base, fmt.Sprintf(` - resource "aws_securityhub_configuration_policy" "test" { - name = %[1]q - description = %[2]q - security_hub_policy { - service_enabled = true - enabled_standard_arns = [ - %[3]q - ] - security_controls_configuration { - disabled_control_identifiers = [] - } - } - - depends_on = [aws_securityhub_organization_configuration.test] - }`, name, description, enabledStandard)) +resource "aws_securityhub_configuration_policy" "test" { + name = %[1]q + description = %[2]q + policy_member { + service_enabled = true + enabled_standard_arns = [ + %[3]q + ] + security_controls_configuration { + disabled_control_identifiers = [] + } + } + + depends_on = [aws_securityhub_organization_configuration.test] +}`, name, description, enabledStandard)) } -func testAccConfigurationPolicyConfig_controlCustomParametersMulti(standardsArn string) string { +func testAccConfigurationPolicyConfig_controlCustomParametersMulti(standardsARN string) string { return acctest.ConfigCompose( acctest.ConfigAlternateAccountProvider(), testAccMemberAccountDelegatedAdminConfig_base, testAccCentralConfigurationEnabledConfig_base, fmt.Sprintf(` - resource "aws_securityhub_configuration_policy" "test" { - name = "MultipleControlCustomParametersPolicy" - security_hub_policy { - service_enabled = true - enabled_standard_arns = [ - %[1]q - ] - security_controls_configuration { - disabled_control_identifiers = [] - control_custom_parameter { - control_identifier = "APIGateway.1" - parameter { - name = "loggingLevel" - value_type = "CUSTOM" - enum { - value = "INFO" - } - } - } - control_custom_parameter { - control_identifier = "IAM.7" - parameter { - name = "RequireUppercaseCharacters" - value_type = "DEFAULT" - } - parameter { - name = "RequireLowercaseCharacters" - value_type = "CUSTOM" - bool { - value = false - } - } - parameter { - name = "MaxPasswordAge" - value_type = "CUSTOM" - int { - value = 60 - } - } - } - } - } +resource "aws_securityhub_configuration_policy" "test" { + name = "MultipleControlCustomParametersPolicy" + policy_member { + service_enabled = true + enabled_standard_arns = [ + %[1]q + ] + security_controls_configuration { + disabled_control_identifiers = [] + control_custom_parameter { + control_identifier = "APIGateway.1" + parameter { + name = "loggingLevel" + value_type = "CUSTOM" + enum { + value = "INFO" + } + } + } + control_custom_parameter { + control_identifier = "IAM.7" + parameter { + name = "RequireUppercaseCharacters" + value_type = "DEFAULT" + } + parameter { + name = "RequireLowercaseCharacters" + value_type = "CUSTOM" + bool { + value = false + } + } + parameter { + name = "MaxPasswordAge" + value_type = "CUSTOM" + int { + value = 60 + } + } + } + } + } - depends_on = [aws_securityhub_organization_configuration.test] - }`, standardsArn), - ) + depends_on = [aws_securityhub_organization_configuration.test] +}`, standardsARN)) } -func testAccConfigurationPolicyConfig_controlCustomParametersSingle(standardsArn, controlID, paramName, paramType, paramValue string) string { +func testAccConfigurationPolicyConfig_controlCustomParametersSingle(standardsARN, controlID, paramName, paramType, paramValue string) string { return acctest.ConfigCompose( acctest.ConfigAlternateAccountProvider(), testAccMemberAccountDelegatedAdminConfig_base, testAccCentralConfigurationEnabledConfig_base, fmt.Sprintf(` - resource "aws_securityhub_configuration_policy" "test" { - name = "ControlCustomParametersPolicy" - security_hub_policy { - service_enabled = true - enabled_standard_arns = [ - %[1]q - ] - security_controls_configuration { - disabled_control_identifiers = [] - control_custom_parameter { - control_identifier = %[2]q - parameter { - name = %[3]q - value_type = "CUSTOM" - %[4]s { - value = %[5]s - } - } - } - } - } +resource "aws_securityhub_configuration_policy" "test" { + name = "ControlCustomParametersPolicy" + policy_member { + service_enabled = true + enabled_standard_arns = [ + %[1]q + ] + security_controls_configuration { + disabled_control_identifiers = [] + control_custom_parameter { + control_identifier = %[2]q + parameter { + name = %[3]q + value_type = "CUSTOM" + %[4]s { + value = %[5]s + } + } + } + } + } - depends_on = [aws_securityhub_organization_configuration.test] - }`, standardsArn, controlID, paramName, paramType, paramValue), - ) + depends_on = [aws_securityhub_organization_configuration.test] +}`, standardsARN, controlID, paramName, paramType, paramValue)) } -func testAccConfigurationPolicyConfig_specifcControlIdentifiers(standardsArn, control1, control2 string, enabledOnly bool) string { +func testAccConfigurationPolicyConfig_specifcControlIdentifiers(standardsARN, control1, control2 string, enabledOnly bool) string { controlIDAttr := "disabled_control_identifiers" if enabledOnly { controlIDAttr = "enabled_control_identifiers" @@ -412,24 +410,20 @@ func testAccConfigurationPolicyConfig_specifcControlIdentifiers(standardsArn, co testAccMemberAccountDelegatedAdminConfig_base, testAccCentralConfigurationEnabledConfig_base, fmt.Sprintf(` - resource "aws_securityhub_configuration_policy" "test" { - name = "ControlIdentifiersPolicy" - security_hub_policy { - service_enabled = true - enabled_standard_arns = [ - %[1]q - ] - security_controls_configuration { - %[2]s = [ - %[3]q, - %[4]q - ] - } - } - - depends_on = [aws_securityhub_organization_configuration.test] - }`, standardsArn, controlIDAttr, control1, control2), - ) +resource "aws_securityhub_configuration_policy" "test" { + name = "ControlIdentifiersPolicy" + policy_member { + service_enabled = true + enabled_standard_arns = [%[1]q] + security_controls_configuration { + %[2]s = [ + %[3]q, + %[4]q + ] + } + } + depends_on = [aws_securityhub_organization_configuration.test] +}`, standardsARN, controlIDAttr, control1, control2)) } const testAccCentralConfigurationEnabledConfig_base = ` diff --git a/internal/service/securityhub/organization_configuration.go b/internal/service/securityhub/organization_configuration.go index c348a1d5917..52bddc23b51 100644 --- a/internal/service/securityhub/organization_configuration.go +++ b/internal/service/securityhub/organization_configuration.go @@ -123,7 +123,6 @@ func resourceOrganizationConfigurationDelete(ctx context.Context, d *schema.Reso return sdkdiag.AppendErrorf(diags, "deleting Security Hub Organization Configuration (%s): %s", d.Id(), err) } - d.SetId("") return diags } @@ -186,10 +185,10 @@ func findOrganizationConfiguration(ctx context.Context, conn *securityhub.Client return nil, "", &retry.UnexpectedStateError{ LastError: statusErr, State: string(output.OrganizationConfiguration.Status), - ExpectedState: []string{ - string(types.OrganizationConfigurationStatusEnabled), - string(types.OrganizationConfigurationStatusPending), - }, + ExpectedState: enum.Slice( + types.OrganizationConfigurationStatusEnabled, + types.OrganizationConfigurationStatusPending, + ), } } } diff --git a/website/docs/r/securityhub_configuration_policy.html.markdown b/website/docs/r/securityhub_configuration_policy.html.markdown index b1725451547..7572001063e 100644 --- a/website/docs/r/securityhub_configuration_policy.html.markdown +++ b/website/docs/r/securityhub_configuration_policy.html.markdown @@ -34,8 +34,8 @@ resource "aws_securityhub_organization_configuration" "example" { resource "aws_securityhub_configuration_policy" "example" { name = "Example" description = "This is an example configuration policy" - security_hub_policy { - service_enabled = true + policy_member { + service_enabled = true enabled_standard_arns = [ "arn:aws:securityhub:us-east-1::standards/aws-foundational-security-best-practices/v/1.0.0", "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0", @@ -44,7 +44,7 @@ resource "aws_securityhub_configuration_policy" "example" { disabled_control_identifiers = [] } } - + depends_on = [aws_securityhub_organization_configuration.example] } ``` @@ -55,11 +55,11 @@ resource "aws_securityhub_configuration_policy" "example" { resource "aws_securityhub_configuration_policy" "disabled" { name = "Disabled" description = "This is an example of disabled configuration policy" - security_hub_policy { + policy_member { service_enabled = false enabled_standard_arns = [] } - + depends_on = [aws_securityhub_organization_configuration.example] } ``` @@ -70,8 +70,8 @@ resource "aws_securityhub_configuration_policy" "disabled" { resource "aws_securityhub_configuration_policy" "disabled" { name = "Custom Controls" description = "This is an example of configuration policy with custom control settings" - security_hub_policy { - service_enabled = true + policy_member { + service_enabled = true enabled_standard_arns = [ "arn:aws:securityhub:us-east-1::standards/aws-foundational-security-best-practices/v/1.0.0", "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0", @@ -110,7 +110,7 @@ resource "aws_securityhub_configuration_policy" "disabled" { } } } - + depends_on = [aws_securityhub_organization_configuration.example] } ``` @@ -121,11 +121,11 @@ This resource supports the following arguments: * `name` - (Required) The name of the configuration policy. * `description` - (Optional) The description of the configuration policy. -* `security_hub_policy` - (Required) Defines how Security Hub is configured. See [below](#security_hub_policy). +* `policy_member` - (Required) Defines how Security Hub is configured. See [below](#policy_member). -### security_hub_policy +### policy_member -The `security_hub_policy` block supports the following: +The `policy_member` block supports the following: * `service_enabled` - (Required) Indicates whether Security Hub is enabled in the policy. * `enabled_standard_arns` - (Required) A list that defines which security standards are enabled in the configuration policy. diff --git a/website/docs/r/securithub_configuration_policy_association.markdown b/website/docs/r/securityhub_configuration_policy_association.markdown similarity index 98% rename from website/docs/r/securithub_configuration_policy_association.markdown rename to website/docs/r/securityhub_configuration_policy_association.markdown index 6f3e1affab9..ace14766830 100644 --- a/website/docs/r/securithub_configuration_policy_association.markdown +++ b/website/docs/r/securityhub_configuration_policy_association.markdown @@ -32,8 +32,8 @@ resource "aws_securityhub_organization_configuration" "example" { resource "aws_securityhub_configuration_policy" "example" { name = "Example" description = "This is an example configuration policy" - security_hub_policy { - service_enabled = true + policy_member { + service_enabled = true enabled_standard_arns = [ "arn:aws:securityhub:us-east-1::standards/aws-foundational-security-best-practices/v/1.0.0", "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0", @@ -42,7 +42,7 @@ resource "aws_securityhub_configuration_policy" "example" { disabled_control_identifiers = [] } } - + depends_on = [aws_securityhub_organization_configuration.example] } From 13685314dfe015809ec7f23d5eb7d274dda2b6c0 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 6 Mar 2024 16:32:48 -0500 Subject: [PATCH 22/71] Fix semgrep 'ci.securityhub-in-func-name'. --- .../securityhub/configuration_policy.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/internal/service/securityhub/configuration_policy.go b/internal/service/securityhub/configuration_policy.go index 2162b9c8500..bad1aec8694 100644 --- a/internal/service/securityhub/configuration_policy.go +++ b/internal/service/securityhub/configuration_policy.go @@ -273,8 +273,8 @@ func resourceConfigurationPolicyCreate(ctx context.Context, d *schema.ResourceDa } if v, ok := d.Get("security_hub_policy").([]interface{}); ok && len(v) > 0 { - policy := expandSecurityHubPolicy(v[0].(map[string]interface{})) - if err := validateSecurityHubPolicy(policy); err != nil { + policy := expandPolicyMemberSecurityHub(v[0].(map[string]interface{})) + if err := validatePolicyMemberSecurityHub(policy); err != nil { return sdkdiag.AppendFromErr(diags, err) } input.ConfigurationPolicy = policy @@ -307,8 +307,8 @@ func resourceConfigurationPolicyUpdate(ctx context.Context, d *schema.ResourceDa } if v, ok := d.Get("security_hub_policy").([]interface{}); ok && len(v) > 0 { - policy := expandSecurityHubPolicy(v[0].(map[string]interface{})) - if err := validateSecurityHubPolicy(policy); err != nil { + policy := expandPolicyMemberSecurityHub(v[0].(map[string]interface{})) + if err := validatePolicyMemberSecurityHub(policy); err != nil { return sdkdiag.AppendErrorf(diags, "updating Security Hub Configuration Policy (%s): %s", d.Id(), err) } input.ConfigurationPolicy = policy @@ -344,7 +344,7 @@ func resourceConfigurationPolicyRead(ctx context.Context, d *schema.ResourceData d.Set("name", out.Name) d.Set("description", out.Description) d.Set("arn", out.Arn) - if err := d.Set("security_hub_policy", []interface{}{flattenSecurityHubPolicy(out.ConfigurationPolicy)}); err != nil { + if err := d.Set("security_hub_policy", []interface{}{flattenPolicy(out.ConfigurationPolicy)}); err != nil { return sdkdiag.AppendErrorf(diags, "setting security_hub_policy: %s", err) } @@ -367,8 +367,8 @@ func resourceConfigurationPolicyDelete(ctx context.Context, d *schema.ResourceDa return diags } -// validateSecurityHubPolicy performs validation before running creates/updates to prevent certain issues with state. -func validateSecurityHubPolicy(apiPolicy *types.PolicyMemberSecurityHub) error { +// validatePolicyMemberSecurityHub performs validation before running creates/updates to prevent certain issues with state. +func validatePolicyMemberSecurityHub(apiPolicy *types.PolicyMemberSecurityHub) error { // security_controls_configuration can be specified in Creates/Updates and accepted by the APIs, // but the resources returned by subsequent Get API call will be nil instead of non-nil. // This leaves terraform in perpetual drift and so we prevent this case explicitly. @@ -389,7 +389,7 @@ func validateSecurityHubPolicy(apiPolicy *types.PolicyMemberSecurityHub) error { return nil } -func expandSecurityHubPolicy(tfMap map[string]interface{}) *types.PolicyMemberSecurityHub { +func expandPolicyMemberSecurityHub(tfMap map[string]interface{}) *types.PolicyMemberSecurityHub { if tfMap == nil { return nil } @@ -523,7 +523,7 @@ func expandControlCustomParameter(tfCustomParam map[string]interface{}) types.Se return apiCustomParam } -func flattenSecurityHubPolicy(policy types.Policy) map[string]interface{} { +func flattenPolicy(policy types.Policy) map[string]interface{} { apiObject, ok := policy.(*types.PolicyMemberSecurityHub) if !ok || apiObject == nil { return nil From fd1b9e504ac995cafed39acd9b80f85736dd82d5 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 6 Mar 2024 16:45:04 -0500 Subject: [PATCH 23/71] Run 'make fmt'. --- .../securityhub/configuration_policy_association_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/securityhub/configuration_policy_association_test.go b/internal/service/securityhub/configuration_policy_association_test.go index 95d6af09d7a..35fcf261c48 100644 --- a/internal/service/securityhub/configuration_policy_association_test.go +++ b/internal/service/securityhub/configuration_policy_association_test.go @@ -114,7 +114,7 @@ resource "aws_organizations_organizational_unit" "test" { } ` -//lintignore:AWSAT005 +// lintignore:AWSAT005 const testAccConfigurationPoliciesConfig_base = ` resource "aws_securityhub_configuration_policy" "test_1" { name = "test1" From 5a517f703d668850f6bd465d923f2974c0c2787b Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 6 Mar 2024 16:46:23 -0500 Subject: [PATCH 24/71] Fix tfproviderdocs 'missing title section'. --- .../r/securityhub_configuration_policy_association.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/securityhub_configuration_policy_association.markdown b/website/docs/r/securityhub_configuration_policy_association.markdown index ace14766830..a022f7bbaf9 100644 --- a/website/docs/r/securityhub_configuration_policy_association.markdown +++ b/website/docs/r/securityhub_configuration_policy_association.markdown @@ -6,7 +6,7 @@ description: |- Provides a resource to associate Security Hub configuration policy to a target. --- -# Resource: aws_securityhub_configuration_policy +# Resource: aws_securityhub_configuration_policy_association Manages Security Hub configuration policy associations. From 93109ea75ed3e6d5aec486014af4b138bc3e8cc5 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 6 Mar 2024 17:10:29 -0500 Subject: [PATCH 25/71] Fix terrafmt errors in acceptance test configurations. --- .../configuration_policy_association_test.go | 10 ++- .../securityhub/configuration_policy_test.go | 64 ++++++++++++------- 2 files changed, 47 insertions(+), 27 deletions(-) diff --git a/internal/service/securityhub/configuration_policy_association_test.go b/internal/service/securityhub/configuration_policy_association_test.go index 35fcf261c48..e84ab0aac72 100644 --- a/internal/service/securityhub/configuration_policy_association_test.go +++ b/internal/service/securityhub/configuration_policy_association_test.go @@ -107,7 +107,7 @@ data "aws_organizations_organization" "test" { } resource "aws_organizations_organizational_unit" "test" { - provider = awsalternate + provider = awsalternate name = "testAccConfigurationPolicyOrgUnitConfig_base" parent_id = data.aws_organizations_organization.test.roots[0].id @@ -118,27 +118,31 @@ resource "aws_organizations_organizational_unit" "test" { const testAccConfigurationPoliciesConfig_base = ` resource "aws_securityhub_configuration_policy" "test_1" { name = "test1" + policy_member { service_enabled = true enabled_standard_arns = ["arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"] + security_controls_configuration { disabled_control_identifiers = [] } } - + depends_on = [aws_securityhub_organization_configuration.test] } resource "aws_securityhub_configuration_policy" "test_2" { name = "test2" + policy_member { service_enabled = true enabled_standard_arns = ["arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"] + security_controls_configuration { enabled_control_identifiers = ["CloudTrail.1"] } } - + depends_on = [aws_securityhub_organization_configuration.test] } ` diff --git a/internal/service/securityhub/configuration_policy_test.go b/internal/service/securityhub/configuration_policy_test.go index 1efc555e538..ba95cdc958f 100644 --- a/internal/service/securityhub/configuration_policy_test.go +++ b/internal/service/securityhub/configuration_policy_test.go @@ -279,16 +279,17 @@ func testAccConfigurationPolicyConfig_baseDisabled(name, description string) str testAccMemberAccountDelegatedAdminConfig_base, testAccCentralConfigurationEnabledConfig_base, fmt.Sprintf(` - resource "aws_securityhub_configuration_policy" "test" { - name = %[1]q - description = %[2]q - policy_member { - service_enabled = false - enabled_standard_arns = [] - } - - depends_on = [aws_securityhub_organization_configuration.test] - }`, name, description)) +resource "aws_securityhub_configuration_policy" "test" { + name = %[1]q + description = %[2]q + + policy_member { + service_enabled = false + enabled_standard_arns = [] + } + + depends_on = [aws_securityhub_organization_configuration.test] +}`, name, description)) } func testAccConfigurationPolicyConfig_baseEnabled(name, description string, enabledStandard string) string { @@ -298,10 +299,11 @@ func testAccConfigurationPolicyConfig_baseEnabled(name, description string, enab testAccCentralConfigurationEnabledConfig_base, fmt.Sprintf(` resource "aws_securityhub_configuration_policy" "test" { - name = %[1]q + name = %[1]q description = %[2]q + policy_member { - service_enabled = true + service_enabled = true enabled_standard_arns = [ %[3]q ] @@ -309,7 +311,7 @@ resource "aws_securityhub_configuration_policy" "test" { disabled_control_identifiers = [] } } - + depends_on = [aws_securityhub_organization_configuration.test] }`, name, description, enabledStandard)) } @@ -322,15 +324,19 @@ func testAccConfigurationPolicyConfig_controlCustomParametersMulti(standardsARN fmt.Sprintf(` resource "aws_securityhub_configuration_policy" "test" { name = "MultipleControlCustomParametersPolicy" + policy_member { - service_enabled = true + service_enabled = true enabled_standard_arns = [ %[1]q ] + security_controls_configuration { disabled_control_identifiers = [] + control_custom_parameter { control_identifier = "APIGateway.1" + parameter { name = "loggingLevel" value_type = "CUSTOM" @@ -339,12 +345,15 @@ resource "aws_securityhub_configuration_policy" "test" { } } } + control_custom_parameter { control_identifier = "IAM.7" + parameter { name = "RequireUppercaseCharacters" value_type = "DEFAULT" } + parameter { name = "RequireLowercaseCharacters" value_type = "CUSTOM" @@ -352,6 +361,7 @@ resource "aws_securityhub_configuration_policy" "test" { value = false } } + parameter { name = "MaxPasswordAge" value_type = "CUSTOM" @@ -375,15 +385,19 @@ func testAccConfigurationPolicyConfig_controlCustomParametersSingle(standardsARN fmt.Sprintf(` resource "aws_securityhub_configuration_policy" "test" { name = "ControlCustomParametersPolicy" + policy_member { - service_enabled = true + service_enabled = true enabled_standard_arns = [ %[1]q ] + security_controls_configuration { disabled_control_identifiers = [] + control_custom_parameter { control_identifier = %[2]q + parameter { name = %[3]q value_type = "CUSTOM" @@ -413,15 +427,17 @@ func testAccConfigurationPolicyConfig_specifcControlIdentifiers(standardsARN, co resource "aws_securityhub_configuration_policy" "test" { name = "ControlIdentifiersPolicy" policy_member { - service_enabled = true - enabled_standard_arns = [%[1]q] - security_controls_configuration { - %[2]s = [ - %[3]q, - %[4]q - ] + service_enabled = true + enabled_standard_arns = [%[1]q] + + security_controls_configuration { + %[2]s = [ + %[3]q, + %[4]q + ] } } + depends_on = [aws_securityhub_organization_configuration.test] }`, standardsARN, controlIDAttr, control1, control2)) } @@ -429,7 +445,7 @@ resource "aws_securityhub_configuration_policy" "test" { const testAccCentralConfigurationEnabledConfig_base = ` resource "aws_securityhub_finding_aggregator" "test" { linking_mode = "ALL_REGIONS" - + depends_on = [aws_securityhub_organization_admin_account.test] } @@ -439,7 +455,7 @@ resource "aws_securityhub_organization_configuration" "test" { organization_configuration { configuration_type = "CENTRAL" } - + depends_on = [aws_securityhub_finding_aggregator.test] } ` From 97c99d6e1a9d5566feb7e5a63b1eb8e0cb738ac9 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 6 Mar 2024 17:12:32 -0500 Subject: [PATCH 26/71] Fix semgrep 'ci.securityhub-in-func-name'. --- internal/service/securityhub/configuration_policy.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/service/securityhub/configuration_policy.go b/internal/service/securityhub/configuration_policy.go index bad1aec8694..55d7d89ebf2 100644 --- a/internal/service/securityhub/configuration_policy.go +++ b/internal/service/securityhub/configuration_policy.go @@ -368,7 +368,7 @@ func resourceConfigurationPolicyDelete(ctx context.Context, d *schema.ResourceDa } // validatePolicyMemberSecurityHub performs validation before running creates/updates to prevent certain issues with state. -func validatePolicyMemberSecurityHub(apiPolicy *types.PolicyMemberSecurityHub) error { +func validatePolicyMemberSecurityHub(apiPolicy *types.PolicyMemberSecurityHub) error { // nosemgrep:ci.securityhub-in-func-name // security_controls_configuration can be specified in Creates/Updates and accepted by the APIs, // but the resources returned by subsequent Get API call will be nil instead of non-nil. // This leaves terraform in perpetual drift and so we prevent this case explicitly. @@ -389,7 +389,7 @@ func validatePolicyMemberSecurityHub(apiPolicy *types.PolicyMemberSecurityHub) e return nil } -func expandPolicyMemberSecurityHub(tfMap map[string]interface{}) *types.PolicyMemberSecurityHub { +func expandPolicyMemberSecurityHub(tfMap map[string]interface{}) *types.PolicyMemberSecurityHub { // nosemgrep:ci.securityhub-in-func-name if tfMap == nil { return nil } From 0647f25e82c2b0e18ade7b93a82fe98452910240 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 6 Mar 2024 17:14:33 -0500 Subject: [PATCH 27/71] Fix terrafmt errors in acceptance test configurations. --- internal/service/securityhub/configuration_policy_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/securityhub/configuration_policy_test.go b/internal/service/securityhub/configuration_policy_test.go index ba95cdc958f..f0d2ee4f778 100644 --- a/internal/service/securityhub/configuration_policy_test.go +++ b/internal/service/securityhub/configuration_policy_test.go @@ -427,7 +427,7 @@ func testAccConfigurationPolicyConfig_specifcControlIdentifiers(standardsARN, co resource "aws_securityhub_configuration_policy" "test" { name = "ControlIdentifiersPolicy" policy_member { - service_enabled = true + service_enabled = true enabled_standard_arns = [%[1]q] security_controls_configuration { From 6a8b98b50e5021ff8617b9429f4ae190a3fc4400 Mon Sep 17 00:00:00 2001 From: twelsh-aw <84401379+twelsh-aw@users.noreply.github.com> Date: Thu, 7 Mar 2024 06:55:38 -0500 Subject: [PATCH 28/71] Add missing attribute reference in docs Every resource exports an id field. This one matches the target id. --- .../r/securityhub_configuration_policy_association.markdown | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/website/docs/r/securityhub_configuration_policy_association.markdown b/website/docs/r/securityhub_configuration_policy_association.markdown index a022f7bbaf9..402260bf166 100644 --- a/website/docs/r/securityhub_configuration_policy_association.markdown +++ b/website/docs/r/securityhub_configuration_policy_association.markdown @@ -69,6 +69,12 @@ This resource supports the following arguments: * `target_id` - (Required, Forces new resource) The identifier of the target account, organizational unit, or the root to associate with the specified configuration. * `policy_id` - (Required) The universally unique identifier (UUID) of the configuration policy. +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `id` - The identifier of the target account, organizational unit, or the root that is associated with the configuration. + ## Import In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import an existing Security Hub configuration policy association using the target id. For example: From 669e4af21dd8273d73f3131f05dfe9a900d72fca Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 7 Mar 2024 11:02:59 -0500 Subject: [PATCH 29/71] Move functions around. --- internal/framework/types/attrtypes.go | 5 +++++ internal/framework/types/setof.go | 5 ----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/framework/types/attrtypes.go b/internal/framework/types/attrtypes.go index 1d909b8ac5b..8231276bc93 100644 --- a/internal/framework/types/attrtypes.go +++ b/internal/framework/types/attrtypes.go @@ -58,3 +58,8 @@ func AttributeTypes[T any](ctx context.Context) (map[string]attr.Type, diag.Diag func AttributeTypesMust[T any](ctx context.Context) map[string]attr.Type { return fwdiag.Must(AttributeTypes[T](ctx)) } + +func newAttrTypeOf[T attr.Value](ctx context.Context) attr.Type { + var zero T + return zero.Type(ctx) +} diff --git a/internal/framework/types/setof.go b/internal/framework/types/setof.go index bc6720f6583..4bce50a2816 100644 --- a/internal/framework/types/setof.go +++ b/internal/framework/types/setof.go @@ -28,11 +28,6 @@ var ( _ basetypes.SetValuable = (*SetValueOf[basetypes.StringValue])(nil) ) -func newAttrTypeOf[T attr.Value](ctx context.Context) attr.Type { - var zero T - return zero.Type(ctx) -} - func NewSetTypeOf[T attr.Value](ctx context.Context) setTypeOf[T] { return setTypeOf[T]{basetypes.SetType{ElemType: newAttrTypeOf[T](ctx)}} } From 6be25d663a9cfdc845bf97312c91fe1162f3a3c5 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 7 Mar 2024 11:09:12 -0500 Subject: [PATCH 30/71] Cosmetics. --- internal/framework/types/listof.go | 4 ++-- internal/framework/types/mapof.go | 4 ++-- internal/framework/types/setof.go | 10 +++++----- internal/framework/types/string_enum.go | 24 ++++++++++++------------ 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/internal/framework/types/listof.go b/internal/framework/types/listof.go index 9d567b4f505..aa936b4d057 100644 --- a/internal/framework/types/listof.go +++ b/internal/framework/types/listof.go @@ -16,8 +16,8 @@ import ( ) var ( - _ basetypes.ListValuable = ListValueOf[basetypes.StringValue]{} - _ basetypes.ListTypable = listTypeOf[basetypes.StringValue]{} + _ basetypes.ListValuable = (*ListValueOf[basetypes.StringValue])(nil) + _ basetypes.ListTypable = (*listTypeOf[basetypes.StringValue])(nil) ) var ( diff --git a/internal/framework/types/mapof.go b/internal/framework/types/mapof.go index 0bde7ac705f..d7c4341c498 100644 --- a/internal/framework/types/mapof.go +++ b/internal/framework/types/mapof.go @@ -16,8 +16,8 @@ import ( ) var ( - _ basetypes.MapTypable = mapTypeOf[basetypes.StringValue]{} - _ basetypes.MapValuable = MapValueOf[basetypes.StringValue]{} + _ basetypes.MapTypable = (*mapTypeOf[basetypes.StringValue])(nil) + _ basetypes.MapValuable = (*MapValueOf[basetypes.StringValue])(nil) ) var ( diff --git a/internal/framework/types/setof.go b/internal/framework/types/setof.go index 4bce50a2816..357f1f63f8b 100644 --- a/internal/framework/types/setof.go +++ b/internal/framework/types/setof.go @@ -14,6 +14,11 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" ) +var ( + _ basetypes.SetTypable = (*setTypeOf[basetypes.StringValue])(nil) + _ basetypes.SetValuable = (*SetValueOf[basetypes.StringValue])(nil) +) + // setTypeOf is the attribute type of a SetValueOf. type setTypeOf[T attr.Value] struct { basetypes.SetType @@ -23,11 +28,6 @@ var ( SetOfStringType = setTypeOf[basetypes.StringValue]{basetypes.SetType{ElemType: basetypes.StringType{}}} ) -var ( - _ basetypes.SetTypable = (*setTypeOf[basetypes.StringValue])(nil) - _ basetypes.SetValuable = (*SetValueOf[basetypes.StringValue])(nil) -) - func NewSetTypeOf[T attr.Value](ctx context.Context) setTypeOf[T] { return setTypeOf[T]{basetypes.SetType{ElemType: newAttrTypeOf[T](ctx)}} } diff --git a/internal/framework/types/string_enum.go b/internal/framework/types/string_enum.go index d85527512e9..a30e32184c6 100644 --- a/internal/framework/types/string_enum.go +++ b/internal/framework/types/string_enum.go @@ -22,6 +22,18 @@ import ( tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" ) +type dummyValueser string + +func (dummyValueser) Values() []dummyValueser { + return nil +} + +var ( + _ xattr.TypeWithValidate = (*stringEnumType[dummyValueser])(nil) + _ basetypes.StringTypable = (*stringEnumType[dummyValueser])(nil) + _ basetypes.StringValuable = (*StringEnum[dummyValueser])(nil) +) + type customStringTypeWithValidator struct { basetypes.StringType validator validator.String @@ -73,18 +85,6 @@ func StringEnumType[T enum.Valueser[T]]() stringEnumTypeWithAttributeDefault[T] return stringEnumType[T]{customStringTypeWithValidator: customStringTypeWithValidator{validator: stringvalidator.OneOf(tfslices.AppendUnique(enum.Values[T](), "")...)}} } -type dummyValueser string - -func (dummyValueser) Values() []dummyValueser { - return nil -} - -var ( - _ xattr.TypeWithValidate = (*stringEnumType[dummyValueser])(nil) - _ basetypes.StringTypable = (*stringEnumType[dummyValueser])(nil) - _ basetypes.StringValuable = (*StringEnum[dummyValueser])(nil) -) - func (t stringEnumType[T]) Equal(o attr.Type) bool { other, ok := o.(stringEnumType[T]) From 1a82250fd982d0499f6fe76ec42b7dba043d1d57 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 7 Mar 2024 11:15:27 -0500 Subject: [PATCH 31/71] Cosmetics. --- internal/framework/types/listof.go | 2 +- internal/framework/types/mapof.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/framework/types/listof.go b/internal/framework/types/listof.go index aa936b4d057..c35a4549ee2 100644 --- a/internal/framework/types/listof.go +++ b/internal/framework/types/listof.go @@ -48,7 +48,7 @@ func (t listTypeOf[T]) Equal(o attr.Type) bool { func (t listTypeOf[T]) String() string { var zero T - return fmt.Sprintf("%T", zero) + return fmt.Sprintf("ListTypeOf[%T]", zero) } func (t listTypeOf[T]) ValueFromList(ctx context.Context, in basetypes.ListValue) (basetypes.ListValuable, diag.Diagnostics) { diff --git a/internal/framework/types/mapof.go b/internal/framework/types/mapof.go index d7c4341c498..d7d2e9aee4a 100644 --- a/internal/framework/types/mapof.go +++ b/internal/framework/types/mapof.go @@ -45,7 +45,7 @@ func (t mapTypeOf[T]) Equal(o attr.Type) bool { func (t mapTypeOf[T]) String() string { var zero T - return fmt.Sprintf("%T", zero) + return fmt.Sprintf("MapTypeOf[%T]", zero) } func (t mapTypeOf[T]) ValueFromMap(ctx context.Context, in basetypes.MapValue) (basetypes.MapValuable, diag.Diagnostics) { From 818cbf0abc366c6cb33ed65fc06e097472d71926 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 7 Mar 2024 11:21:11 -0500 Subject: [PATCH 32/71] Cosmetics. --- internal/framework/types/arn.go | 12 ++++++------ internal/framework/types/cidr_block.go | 12 ++++++------ internal/framework/types/duration.go | 12 ++++++------ internal/framework/types/iam_policy.go | 14 +++++++------- internal/framework/types/list_nested_objectof.go | 10 +++++----- internal/framework/types/listof.go | 2 +- internal/framework/types/objectof.go | 10 +++++----- internal/framework/types/regexp.go | 12 ++++++------ internal/framework/types/set_nested_objectof.go | 10 +++++----- 9 files changed, 47 insertions(+), 47 deletions(-) diff --git a/internal/framework/types/arn.go b/internal/framework/types/arn.go index 58e9985c70c..942f09b00dc 100644 --- a/internal/framework/types/arn.go +++ b/internal/framework/types/arn.go @@ -18,6 +18,12 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" ) +var ( + _ xattr.TypeWithValidate = (*arnType)(nil) + _ basetypes.StringTypable = (*arnType)(nil) + _ basetypes.StringValuable = (*ARN)(nil) +) + type arnType struct { basetypes.StringType } @@ -26,12 +32,6 @@ var ( ARNType = arnType{} ) -var ( - _ xattr.TypeWithValidate = (*arnType)(nil) - _ basetypes.StringTypable = (*arnType)(nil) - _ basetypes.StringValuable = (*ARN)(nil) -) - func (t arnType) Equal(o attr.Type) bool { other, ok := o.(arnType) diff --git a/internal/framework/types/cidr_block.go b/internal/framework/types/cidr_block.go index bc8a0de3859..c8c4ede09df 100644 --- a/internal/framework/types/cidr_block.go +++ b/internal/framework/types/cidr_block.go @@ -17,6 +17,12 @@ import ( itypes "github.com/hashicorp/terraform-provider-aws/internal/types" ) +var ( + _ xattr.TypeWithValidate = (*cidrBlockType)(nil) + _ basetypes.StringTypable = (*cidrBlockType)(nil) + _ basetypes.StringValuable = (*CIDRBlock)(nil) +) + type cidrBlockType struct { basetypes.StringType } @@ -25,12 +31,6 @@ var ( CIDRBlockType = cidrBlockType{} ) -var ( - _ xattr.TypeWithValidate = (*cidrBlockType)(nil) - _ basetypes.StringTypable = (*cidrBlockType)(nil) - _ basetypes.StringValuable = (*CIDRBlock)(nil) -) - func (t cidrBlockType) Equal(o attr.Type) bool { other, ok := o.(cidrBlockType) diff --git a/internal/framework/types/duration.go b/internal/framework/types/duration.go index 7bcdf5f9cd4..4dcf236d77f 100644 --- a/internal/framework/types/duration.go +++ b/internal/framework/types/duration.go @@ -18,6 +18,12 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/errs" ) +var ( + _ xattr.TypeWithValidate = (*durationType)(nil) + _ basetypes.StringTypable = (*durationType)(nil) + _ basetypes.StringValuable = (*Duration)(nil) +) + type durationType struct { basetypes.StringType } @@ -26,12 +32,6 @@ var ( DurationType = durationType{} ) -var ( - _ xattr.TypeWithValidate = (*durationType)(nil) - _ basetypes.StringTypable = (*durationType)(nil) - _ basetypes.StringValuable = (*Duration)(nil) -) - func (t durationType) Equal(o attr.Type) bool { other, ok := o.(durationType) diff --git a/internal/framework/types/iam_policy.go b/internal/framework/types/iam_policy.go index aaad1134429..e9349ee6f38 100644 --- a/internal/framework/types/iam_policy.go +++ b/internal/framework/types/iam_policy.go @@ -19,6 +19,13 @@ import ( "github.com/hashicorp/terraform-plugin-go/tftypes" ) +var ( + _ xattr.TypeWithValidate = (*iamPolicyType)(nil) + _ basetypes.StringTypable = (*iamPolicyType)(nil) + _ basetypes.StringValuable = (*IAMPolicy)(nil) + _ basetypes.StringValuableWithSemanticEquals = (*IAMPolicy)(nil) +) + type iamPolicyType struct { basetypes.StringType } @@ -27,13 +34,6 @@ var ( IAMPolicyType = iamPolicyType{} ) -var ( - _ xattr.TypeWithValidate = (*iamPolicyType)(nil) - _ basetypes.StringTypable = (*iamPolicyType)(nil) - _ basetypes.StringValuable = (*IAMPolicy)(nil) - _ basetypes.StringValuableWithSemanticEquals = (*IAMPolicy)(nil) -) - func (t iamPolicyType) Equal(o attr.Type) bool { other, ok := o.(iamPolicyType) diff --git a/internal/framework/types/list_nested_objectof.go b/internal/framework/types/list_nested_objectof.go index f9ff4d15d50..a24823f467e 100644 --- a/internal/framework/types/list_nested_objectof.go +++ b/internal/framework/types/list_nested_objectof.go @@ -14,11 +14,6 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" ) -// listNestedObjectTypeOf is the attribute type of a ListNestedObjectValueOf. -type listNestedObjectTypeOf[T any] struct { - basetypes.ListType -} - var ( _ basetypes.ListTypable = (*listNestedObjectTypeOf[struct{}])(nil) _ NestedObjectCollectionType = (*listNestedObjectTypeOf[struct{}])(nil) @@ -26,6 +21,11 @@ var ( _ NestedObjectCollectionValue = (*ListNestedObjectValueOf[struct{}])(nil) ) +// listNestedObjectTypeOf is the attribute type of a ListNestedObjectValueOf. +type listNestedObjectTypeOf[T any] struct { + basetypes.ListType +} + func NewListNestedObjectTypeOf[T any](ctx context.Context) listNestedObjectTypeOf[T] { return listNestedObjectTypeOf[T]{basetypes.ListType{ElemType: NewObjectTypeOf[T](ctx)}} } diff --git a/internal/framework/types/listof.go b/internal/framework/types/listof.go index c35a4549ee2..f1974e7d878 100644 --- a/internal/framework/types/listof.go +++ b/internal/framework/types/listof.go @@ -16,8 +16,8 @@ import ( ) var ( - _ basetypes.ListValuable = (*ListValueOf[basetypes.StringValue])(nil) _ basetypes.ListTypable = (*listTypeOf[basetypes.StringValue])(nil) + _ basetypes.ListValuable = (*ListValueOf[basetypes.StringValue])(nil) ) var ( diff --git a/internal/framework/types/objectof.go b/internal/framework/types/objectof.go index 4a98584c614..22f31873850 100644 --- a/internal/framework/types/objectof.go +++ b/internal/framework/types/objectof.go @@ -14,11 +14,6 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" ) -// objectTypeOf is the attribute type of an ObjectValueOf. -type objectTypeOf[T any] struct { - basetypes.ObjectType -} - var ( _ basetypes.ObjectTypable = (*objectTypeOf[struct{}])(nil) _ NestedObjectType = (*objectTypeOf[struct{}])(nil) @@ -26,6 +21,11 @@ var ( _ NestedObjectValue = (*ObjectValueOf[struct{}])(nil) ) +// objectTypeOf is the attribute type of an ObjectValueOf. +type objectTypeOf[T any] struct { + basetypes.ObjectType +} + func newObjectTypeOf[T any](ctx context.Context) (objectTypeOf[T], diag.Diagnostics) { var diags diag.Diagnostics diff --git a/internal/framework/types/regexp.go b/internal/framework/types/regexp.go index 1fabdbf2311..530e6ccf1d3 100644 --- a/internal/framework/types/regexp.go +++ b/internal/framework/types/regexp.go @@ -18,6 +18,12 @@ import ( "github.com/hashicorp/terraform-plugin-go/tftypes" ) +var ( + _ xattr.TypeWithValidate = (*regexpType)(nil) + _ basetypes.StringTypable = (*regexpType)(nil) + _ basetypes.StringValuable = (*Regexp)(nil) +) + type regexpType struct { basetypes.StringType } @@ -26,12 +32,6 @@ var ( RegexpType = regexpType{} ) -var ( - _ xattr.TypeWithValidate = (*regexpType)(nil) - _ basetypes.StringTypable = (*regexpType)(nil) - _ basetypes.StringValuable = (*Regexp)(nil) -) - func (t regexpType) Equal(o attr.Type) bool { other, ok := o.(regexpType) diff --git a/internal/framework/types/set_nested_objectof.go b/internal/framework/types/set_nested_objectof.go index 2aa215dd4ed..7922ebbcf8d 100644 --- a/internal/framework/types/set_nested_objectof.go +++ b/internal/framework/types/set_nested_objectof.go @@ -14,11 +14,6 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" ) -// setNestedObjectTypeOf is the attribute type of a SetNestedObjectValueOf. -type setNestedObjectTypeOf[T any] struct { - basetypes.SetType -} - var ( _ basetypes.SetTypable = (*setNestedObjectTypeOf[struct{}])(nil) _ NestedObjectCollectionType = (*setNestedObjectTypeOf[struct{}])(nil) @@ -26,6 +21,11 @@ var ( _ NestedObjectCollectionValue = (*SetNestedObjectValueOf[struct{}])(nil) ) +// setNestedObjectTypeOf is the attribute type of a SetNestedObjectValueOf. +type setNestedObjectTypeOf[T any] struct { + basetypes.SetType +} + func NewSetNestedObjectTypeOf[T any](ctx context.Context) setNestedObjectTypeOf[T] { return setNestedObjectTypeOf[T]{basetypes.SetType{ElemType: NewObjectTypeOf[T](ctx)}} } From 67f0a32a0694db704a3d6b5f5d5527f71d9f717a Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 7 Mar 2024 11:32:36 -0500 Subject: [PATCH 33/71] Fix error handling. --- internal/framework/types/listof.go | 3 +-- internal/framework/types/mapof.go | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/internal/framework/types/listof.go b/internal/framework/types/listof.go index f1974e7d878..11771cf45a5 100644 --- a/internal/framework/types/listof.go +++ b/internal/framework/types/listof.go @@ -9,7 +9,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-go/tftypes" "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" @@ -65,7 +64,7 @@ func (t listTypeOf[T]) ValueFromList(ctx context.Context, in basetypes.ListValue v, d := basetypes.NewListValue(newAttrTypeOf[T](ctx), in.Elements()) diags.Append(d...) if diags.HasError() { - return basetypes.NewListUnknown(types.StringType), diags + return NewListValueOfUnknown[T](ctx), diags } return ListValueOf[T]{ListValue: v}, diags diff --git a/internal/framework/types/mapof.go b/internal/framework/types/mapof.go index d7d2e9aee4a..21a53b29b2b 100644 --- a/internal/framework/types/mapof.go +++ b/internal/framework/types/mapof.go @@ -9,7 +9,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-go/tftypes" "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" @@ -66,7 +65,7 @@ func (t mapTypeOf[T]) ValueFromMap(ctx context.Context, in basetypes.MapValue) ( mapValue, d := basetypes.NewMapValue(newAttrTypeOf[T](ctx), in.Elements()) diags.Append(d...) if diags.HasError() { - return basetypes.NewMapUnknown(types.StringType), diags + return NewMapValueOfUnknown[T](ctx), diags } return MapValueOf[T]{MapValue: mapValue}, diags From d4a209950fa0500caf15633e89a9928300d99a95 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 7 Mar 2024 13:47:36 -0500 Subject: [PATCH 34/71] r/aws_securityhub_configuration_policy: Rename some attributes. --- .../securityhub/configuration_policy.go | 728 +++++++++--------- internal/service/securityhub/exports_test.go | 3 +- .../securityhub/service_package_gen.go | 3 +- ...rityhub_configuration_policy.html.markdown | 35 +- 4 files changed, 407 insertions(+), 362 deletions(-) diff --git a/internal/service/securityhub/configuration_policy.go b/internal/service/securityhub/configuration_policy.go index 55d7d89ebf2..2a82d63edb4 100644 --- a/internal/service/securityhub/configuration_policy.go +++ b/internal/service/securityhub/configuration_policy.go @@ -19,12 +19,145 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/enum" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/flex" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" ) -// @SDKResource("aws_securityhub_configuration_policy") -func ResourceConfigurationPolicy() *schema.Resource { +// @SDKResource("aws_securityhub_configuration_policy", name="Configuration Policy") +func resourceConfigurationPolicy() *schema.Resource { + customParameterResource := func() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "bool": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Required: true, + Type: schema.TypeBool, + }, + }, + }, + }, + "double": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Required: true, + Type: schema.TypeFloat, + }, + }, + }, + }, + "enum": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Required: true, + Type: schema.TypeString, + }, + }, + }, + }, + "enum_list": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Required: true, + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "int": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Required: true, + Type: schema.TypeInt, + ValidateFunc: validation.IntAtMost(math.MaxInt32), + }, + }, + }, + }, + "int_list": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Required: true, + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeInt, + ValidateFunc: validation.IntAtMost(math.MaxInt32), + }, + }, + }, + }, + }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "string": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Required: true, + Type: schema.TypeString, + }, + }, + }, + }, + "string_list": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Required: true, + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "value_type": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[types.ParameterValueType](), + }, + }, + } + } + return &schema.Resource{ CreateWithoutTimeout: resourceConfigurationPolicyCreate, ReadWithoutTimeout: resourceConfigurationPolicyRead, @@ -36,30 +169,18 @@ func ResourceConfigurationPolicy() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringMatch( - regexache.MustCompile(`[A-Za-z0-9\-\.!*/]+`), - "Only alphanumeric characters and the following ASCII characters are permitted: -, ., !, *, /", - ), - }, - "description": { + "arn": { Type: schema.TypeString, - Optional: true, + Computed: true, }, - "security_hub_policy": { + "configuration_policy": { Type: schema.TypeList, Required: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "service_enabled": { - Type: schema.TypeBool, - Required: true, - }, "enabled_standard_arns": { - Type: schema.TypeList, + Type: schema.TypeSet, Required: true, Elem: &schema.Schema{ Type: schema.TypeString, @@ -73,7 +194,7 @@ func ResourceConfigurationPolicy() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "disabled_control_identifiers": { - Type: schema.TypeList, + Type: schema.TypeSet, Optional: true, Elem: &schema.Schema{ Type: schema.TypeString, @@ -84,7 +205,7 @@ func ResourceConfigurationPolicy() *schema.Resource { }, }, "enabled_control_identifiers": { - Type: schema.TypeList, + Type: schema.TypeSet, Optional: true, Elem: &schema.Schema{ Type: schema.TypeString, @@ -94,166 +215,46 @@ func ResourceConfigurationPolicy() *schema.Resource { "security_hub_policy.0.security_controls_configuration.0.disabled_control_identifiers", }, }, - "control_custom_parameter": { + "security_control_custom_parameter": { Type: schema.TypeList, Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "control_identifier": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, "parameter": { Type: schema.TypeSet, Required: true, MinItems: 1, Elem: customParameterResource(), }, + "security_control_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, }, }, }, }, }, }, - }, - }, - }, - "arn": { - Type: schema.TypeString, - Computed: true, - }, - }, - } -} - -func customParameterResource() *schema.Resource { - return &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "value_type": { - Type: schema.TypeString, - Required: true, - ValidateDiagFunc: enum.Validate[types.ParameterValueType](), - }, - "bool": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "value": { - Required: true, + "service_enabled": { Type: schema.TypeBool, - }, - }, - }, - }, - "double": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "value": { - Required: true, - Type: schema.TypeFloat, - }, - }, - }, - }, - "enum": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "value": { Required: true, - Type: schema.TypeString, }, }, }, }, - "enum_list": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "value": { - Required: true, - Type: schema.TypeList, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - }, - }, - }, - "int": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "value": { - Required: true, - Type: schema.TypeInt, - ValidateFunc: validation.IntAtMost(math.MaxInt32), - }, - }, - }, - }, - "int_list": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "value": { - Required: true, - Type: schema.TypeList, - Elem: &schema.Schema{ - Type: schema.TypeInt, - ValidateFunc: validation.IntAtMost(math.MaxInt32), - }, - }, - }, - }, - }, - "string": { - Type: schema.TypeList, - MaxItems: 1, + "description": { + Type: schema.TypeString, Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "value": { - Required: true, - Type: schema.TypeString, - }, - }, - }, }, - "string_list": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "value": { - Required: true, - Type: schema.TypeList, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - }, - }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringMatch( + regexache.MustCompile(`[A-Za-z0-9\-\.!*/]+`), + "Only alphanumeric characters and the following ASCII characters are permitted: -, ., !, *, /", + ), }, }, } @@ -268,98 +269,97 @@ func resourceConfigurationPolicyCreate(ctx context.Context, d *schema.ResourceDa Name: aws.String(name), } - if v, ok := d.GetOk("description"); ok { - input.Description = aws.String(v.(string)) - } - - if v, ok := d.Get("security_hub_policy").([]interface{}); ok && len(v) > 0 { - policy := expandPolicyMemberSecurityHub(v[0].(map[string]interface{})) + if v, ok := d.GetOk("configuration_policy"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + policy := expandPolicyMemberSecurityHub(v.([]interface{})[0].(map[string]interface{})) if err := validatePolicyMemberSecurityHub(policy); err != nil { return sdkdiag.AppendFromErr(diags, err) } input.ConfigurationPolicy = policy } + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) + } + output, err := conn.CreateConfigurationPolicy(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "creating Security Hub Configuration Policy (%s): %s", name, err) } - if d.IsNewResource() { - d.SetId(aws.ToString(output.Id)) - } + d.SetId(aws.ToString(output.Id)) return append(diags, resourceConfigurationPolicyRead(ctx, d, meta)...) } -func resourceConfigurationPolicyUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourceConfigurationPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) - input := &securityhub.UpdateConfigurationPolicyInput{ + input := &securityhub.GetConfigurationPolicyInput{ Identifier: aws.String(d.Id()), - Name: aws.String(d.Get("name").(string)), } - if v, ok := d.GetOk("description"); ok { - input.Description = aws.String(v.(string)) + out, err := conn.GetConfigurationPolicy(ctx, input) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Security Hub Configuration Policy (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags } - if v, ok := d.Get("security_hub_policy").([]interface{}); ok && len(v) > 0 { - policy := expandPolicyMemberSecurityHub(v[0].(map[string]interface{})) - if err := validatePolicyMemberSecurityHub(policy); err != nil { - return sdkdiag.AppendErrorf(diags, "updating Security Hub Configuration Policy (%s): %s", d.Id(), err) - } - input.ConfigurationPolicy = policy + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading Security Hub Configuration Policy (%s): %s", d.Id(), err) } - _, err := conn.UpdateConfigurationPolicy(ctx, input) - if err != nil { - return sdkdiag.AppendErrorf(diags, "updating Security Hub Configuration Policy (%s): %s", d.Id(), err) + d.Set("arn", out.Arn) + if err := d.Set("configuration_policy", []interface{}{flattenPolicy(out.ConfigurationPolicy)}); err != nil { + return sdkdiag.AppendErrorf(diags, "setting configuration_policy: %s", err) } + d.Set("description", out.Description) + d.Set("name", out.Name) - return append(diags, resourceConfigurationPolicyRead(ctx, d, meta)...) + return diags } -func resourceConfigurationPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourceConfigurationPolicyUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) - input := &securityhub.GetConfigurationPolicyInput{ + input := &securityhub.UpdateConfigurationPolicyInput{ Identifier: aws.String(d.Id()), + Name: aws.String(d.Get("name").(string)), } - out, err := conn.GetConfigurationPolicy(ctx, input) - if !d.IsNewResource() && tfresource.NotFound(err) { - log.Printf("[WARN] Security Hub Configuration Policy (%s) not found, removing from state", d.Id()) - d.SetId("") - return diags + if v, ok := d.GetOk("configuration_policy"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + policy := expandPolicyMemberSecurityHub(v.([]interface{})[0].(map[string]interface{})) + if err := validatePolicyMemberSecurityHub(policy); err != nil { + return sdkdiag.AppendFromErr(diags, err) + } + input.ConfigurationPolicy = policy } - if err != nil { - return sdkdiag.AppendErrorf(diags, "reading Security Hub Configuration Policy (%s): %s", d.Id(), err) + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) } - d.Set("name", out.Name) - d.Set("description", out.Description) - d.Set("arn", out.Arn) - if err := d.Set("security_hub_policy", []interface{}{flattenPolicy(out.ConfigurationPolicy)}); err != nil { - return sdkdiag.AppendErrorf(diags, "setting security_hub_policy: %s", err) + _, err := conn.UpdateConfigurationPolicy(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "updating Security Hub Configuration Policy (%s): %s", d.Id(), err) } - return diags + return append(diags, resourceConfigurationPolicyRead(ctx, d, meta)...) } func resourceConfigurationPolicyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) - input := &securityhub.DeleteConfigurationPolicyInput{ + log.Printf("[DEBUG] Deleting Security Hub Configuration Policy: %s", d.Id()) + _, err := conn.DeleteConfigurationPolicy(ctx, &securityhub.DeleteConfigurationPolicyInput{ Identifier: aws.String(d.Id()), - } + }) - _, err := conn.DeleteConfigurationPolicy(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "deleting Security Hub Configuration Policy (%s): %s", d.Id(), err) } @@ -372,15 +372,15 @@ func validatePolicyMemberSecurityHub(apiPolicy *types.PolicyMemberSecurityHub) e // security_controls_configuration can be specified in Creates/Updates and accepted by the APIs, // but the resources returned by subsequent Get API call will be nil instead of non-nil. // This leaves terraform in perpetual drift and so we prevent this case explicitly. - if !*apiPolicy.Value.ServiceEnabled && apiPolicy.Value.SecurityControlsConfiguration != nil { + if !aws.ToBool(apiPolicy.Value.ServiceEnabled) && apiPolicy.Value.SecurityControlsConfiguration != nil { return errors.New("security_controls_configuration cannot be defined when service_enabled is false") - } else if *apiPolicy.Value.ServiceEnabled && apiPolicy.Value.SecurityControlsConfiguration == nil { + } else if aws.ToBool(apiPolicy.Value.ServiceEnabled) && apiPolicy.Value.SecurityControlsConfiguration == nil { return errors.New("security_controls_configuration must be defined when service_enabled is true") } // If ServiceEnabled is true, then Create/Update APIs require exactly one of enabled or disable control fields to be non-nil. // If terraform defaults are set for both, then we choose to set DisabledSecurityControlIdentifiers to the empty struct. - if *apiPolicy.Value.ServiceEnabled && apiPolicy.Value.SecurityControlsConfiguration != nil && + if aws.ToBool(apiPolicy.Value.ServiceEnabled) && apiPolicy.Value.SecurityControlsConfiguration != nil && apiPolicy.Value.SecurityControlsConfiguration.DisabledSecurityControlIdentifiers == nil && apiPolicy.Value.SecurityControlsConfiguration.EnabledSecurityControlIdentifiers == nil { apiPolicy.Value.SecurityControlsConfiguration.DisabledSecurityControlIdentifiers = []string{} @@ -394,229 +394,269 @@ func expandPolicyMemberSecurityHub(tfMap map[string]interface{}) *types.PolicyMe return nil } - apiObject := types.SecurityHubPolicy{} - apiObject.ServiceEnabled = aws.Bool(tfMap["service_enabled"].(bool)) - for _, s := range tfMap["enabled_standard_arns"].([]interface{}) { - apiObject.EnabledStandardIdentifiers = append(apiObject.EnabledStandardIdentifiers, s.(string)) + apiObject := types.SecurityHubPolicy{ + SecurityControlsConfiguration: expandSecurityControlsConfiguration(tfMap["security_controls_configuration"]), + } + + if v, ok := tfMap["enabled_standard_arns"].(*schema.Set); ok && v.Len() > 0 { + apiObject.EnabledStandardIdentifiers = flex.ExpandStringValueSet(v) } - apiObject.SecurityControlsConfiguration = expandSecurityControlsConfiguration(tfMap["security_controls_configuration"]) + + if v, ok := tfMap["service_enabled"].(bool); ok { + apiObject.ServiceEnabled = aws.Bool(v) + } + return &types.PolicyMemberSecurityHub{ Value: apiObject, } } -func expandSecurityControlsConfiguration(tfSecurityControlsConfig interface{}) *types.SecurityControlsConfiguration { - var apiSecurityControlsConfig *types.SecurityControlsConfiguration - if v, ok := tfSecurityControlsConfig.([]interface{}); ok && len(v) > 0 && v[0] != nil { - apiControlsConfig := &types.SecurityControlsConfiguration{} +func expandSecurityControlsConfiguration(tfListRaw interface{}) *types.SecurityControlsConfiguration { + var apiObject *types.SecurityControlsConfiguration - tfControlsConfig := v[0].(map[string]interface{}) - if v, ok := tfControlsConfig["disabled_control_identifiers"]; ok && v != nil { - for _, c := range v.([]interface{}) { - apiControlsConfig.DisabledSecurityControlIdentifiers = append(apiControlsConfig.DisabledSecurityControlIdentifiers, c.(string)) - } + if v, ok := tfListRaw.([]interface{}); ok && len(v) > 0 && v[0] != nil { + tfMap := v[0].(map[string]interface{}) + apiObject = &types.SecurityControlsConfiguration{} + + if v, ok := tfMap["disabled_control_identifiers"].(*schema.Set); ok && v.Len() > 0 { + apiObject.DisabledSecurityControlIdentifiers = flex.ExpandStringValueSet(v) } - if v, ok := tfControlsConfig["enabled_control_identifiers"]; ok && v != nil { - for _, c := range v.([]interface{}) { - apiControlsConfig.EnabledSecurityControlIdentifiers = append(apiControlsConfig.EnabledSecurityControlIdentifiers, c.(string)) - } + + if v, ok := tfMap["enabled_control_identifiers"].(*schema.Set); ok && v.Len() > 0 { + apiObject.EnabledSecurityControlIdentifiers = flex.ExpandStringValueSet(v) } - if v, ok := tfControlsConfig["control_custom_parameter"].([]interface{}); ok && len(v) > 0 { - for _, param := range v { - apiControlsConfig.SecurityControlCustomParameters = append(apiControlsConfig.SecurityControlCustomParameters, expandControlCustomParameter(param.(map[string]interface{}))) + if v, ok := tfMap["security_control_custom_parameter"].([]interface{}); ok && len(v) > 0 { + for _, tfMapRaw := range v { + tfMap, ok := tfMapRaw.(map[string]interface{}) + if !ok { + continue + } + + apiObject.SecurityControlCustomParameters = append(apiObject.SecurityControlCustomParameters, expandSecurityControlCustomParameter(tfMap)) } } - apiSecurityControlsConfig = apiControlsConfig } else if ok && len(v) > 0 && v[0] == nil { // resource defined, but with defaults - apiSecurityControlsConfig = &types.SecurityControlsConfiguration{} + apiObject = &types.SecurityControlsConfiguration{} } // else resource undefined yields nil - return apiSecurityControlsConfig + + return apiObject } -func expandControlCustomParameter(tfCustomParam map[string]interface{}) types.SecurityControlCustomParameter { - apiCustomParam := types.SecurityControlCustomParameter{ +func expandSecurityControlCustomParameter(tfMap map[string]interface{}) types.SecurityControlCustomParameter { + apiObject := types.SecurityControlCustomParameter{ Parameters: make(map[string]types.ParameterConfiguration), } - if v, ok := tfCustomParam["control_identifier"].(string); ok { - apiCustomParam.SecurityControlId = aws.String(v) + + if v, ok := tfMap["security_control_id"].(string); ok { + apiObject.SecurityControlId = aws.String(v) } - if v, ok := tfCustomParam["parameter"].(*schema.Set); ok && v.Len() > 0 { - for _, vp := range v.List() { - param, ok := vp.(map[string]interface{}) + + if v, ok := tfMap["parameter"].(*schema.Set); ok && v.Len() > 0 { + for _, tfMapRaw := range v.List() { + tfMap, ok := tfMapRaw.(map[string]interface{}) if !ok { continue } - apiParamConfig := types.ParameterConfiguration{} - if v, ok := param["value_type"].(string); ok && len(v) > 0 { - apiParamConfig.ValueType = types.ParameterValueType(v) + + parameterConfiguration := types.ParameterConfiguration{} + + if v, ok := tfMap["value_type"].(string); ok { + parameterConfiguration.ValueType = types.ParameterValueType(v) } - var apiParamValue types.ParameterValue - if v, ok := param["bool"].([]interface{}); ok && len(v) > 0 { // block defined - apiParamValue = &types.ParameterValueMemberBoolean{} + var parameterValue types.ParameterValue + + if v, ok := tfMap["bool"].([]interface{}); ok && len(v) > 0 { // block defined + parameterValue = &types.ParameterValueMemberBoolean{} if v[0] != nil { // block defined with non-defaults val := v[0].(map[string]interface{})["value"] - apiParamValue = &types.ParameterValueMemberBoolean{Value: val.(bool)} + parameterValue = &types.ParameterValueMemberBoolean{Value: val.(bool)} } - } else if v, ok := param["double"].([]interface{}); ok && len(v) > 0 { - apiParamValue = &types.ParameterValueMemberDouble{} + } else if v, ok := tfMap["double"].([]interface{}); ok && len(v) > 0 { + parameterValue = &types.ParameterValueMemberDouble{} if v[0] != nil { val := v[0].(map[string]interface{})["value"] - apiParamValue = &types.ParameterValueMemberDouble{Value: val.(float64)} + parameterValue = &types.ParameterValueMemberDouble{Value: val.(float64)} } - } else if v, ok := param["enum"].([]interface{}); ok && len(v) > 0 { - apiParamValue = &types.ParameterValueMemberEnum{} + } else if v, ok := tfMap["enum"].([]interface{}); ok && len(v) > 0 { + parameterValue = &types.ParameterValueMemberEnum{} if v[0] != nil { val := v[0].(map[string]interface{})["value"] - apiParamValue = &types.ParameterValueMemberEnum{Value: val.(string)} + parameterValue = &types.ParameterValueMemberEnum{Value: val.(string)} } - } else if v, ok := param["string"].([]interface{}); ok && len(v) > 0 { - apiParamValue = &types.ParameterValueMemberString{} + } else if v, ok := tfMap["string"].([]interface{}); ok && len(v) > 0 { + parameterValue = &types.ParameterValueMemberString{} if v[0] != nil { val := v[0].(map[string]interface{})["value"] - apiParamValue = &types.ParameterValueMemberString{Value: val.(string)} + parameterValue = &types.ParameterValueMemberString{Value: val.(string)} } - } else if v, ok := param["int"].([]interface{}); ok && len(v) > 0 { - apiParamValue = &types.ParameterValueMemberInteger{} + } else if v, ok := tfMap["int"].([]interface{}); ok && len(v) > 0 { + parameterValue = &types.ParameterValueMemberInteger{} if v[0] != nil { val := v[0].(map[string]interface{})["value"] - apiParamValue = &types.ParameterValueMemberInteger{Value: int32(val.(int))} + parameterValue = &types.ParameterValueMemberInteger{Value: int32(val.(int))} } - } else if v, ok := param["int_list"].([]interface{}); ok && len(v) > 0 { - apiParamValue = &types.ParameterValueMemberIntegerList{} + } else if v, ok := tfMap["int_list"].([]interface{}); ok && len(v) > 0 { + parameterValue = &types.ParameterValueMemberIntegerList{} if v[0] != nil { val := v[0].(map[string]interface{})["value"] var vals []int32 for _, s := range val.([]interface{}) { vals = append(vals, int32(s.(int))) } - apiParamValue = &types.ParameterValueMemberIntegerList{Value: vals} + parameterValue = &types.ParameterValueMemberIntegerList{Value: vals} } - } else if v, ok := param["enum_list"].([]interface{}); ok && len(v) > 0 { - apiParamValue = &types.ParameterValueMemberEnumList{} + } else if v, ok := tfMap["enum_list"].([]interface{}); ok && len(v) > 0 { + parameterValue = &types.ParameterValueMemberEnumList{} if v[0] != nil { val := v[0].(map[string]interface{})["value"] var vals []string for _, s := range val.([]interface{}) { vals = append(vals, s.(string)) } - apiParamValue = &types.ParameterValueMemberEnumList{Value: vals} + parameterValue = &types.ParameterValueMemberEnumList{Value: vals} } - } else if v, ok := param["string_list"].([]interface{}); ok && len(v) > 0 { - apiParamValue = &types.ParameterValueMemberStringList{} + } else if v, ok := tfMap["string_list"].([]interface{}); ok && len(v) > 0 { + parameterValue = &types.ParameterValueMemberStringList{} if v[0] != nil { val := v[0].(map[string]interface{})["value"] var vals []string for _, s := range val.([]interface{}) { vals = append(vals, s.(string)) } - apiParamValue = &types.ParameterValueMemberStringList{Value: vals} + parameterValue = &types.ParameterValueMemberStringList{Value: vals} } } - apiParamConfig.Value = apiParamValue - if key, ok := param["name"].(string); ok && len(key) > 0 { - apiCustomParam.Parameters[key] = apiParamConfig + + parameterConfiguration.Value = parameterValue + + if v, ok := tfMap["name"].(string); ok && len(v) > 0 { + apiObject.Parameters[v] = parameterConfiguration } } } - return apiCustomParam + + return apiObject +} + +func flattenPolicy(apiObject types.Policy) map[string]interface{} { + switch apiObject := apiObject.(type) { + case *types.PolicyMemberSecurityHub: + return flattenPolicyMemberSecurityHub(apiObject) + } + + return nil } -func flattenPolicy(policy types.Policy) map[string]interface{} { - apiObject, ok := policy.(*types.PolicyMemberSecurityHub) - if !ok || apiObject == nil { +func flattenPolicyMemberSecurityHub(apiObject *types.PolicyMemberSecurityHub) map[string]interface{} { // nosemgrep:ci.securityhub-in-func-name + if apiObject == nil { return nil } - tfMap := map[string]interface{}{} - tfMap["service_enabled"] = apiObject.Value.ServiceEnabled - tfMap["enabled_standard_arns"] = apiObject.Value.EnabledStandardIdentifiers - tfMap["security_controls_configuration"] = flattenSecurityControlsConfiguration(apiObject.Value.SecurityControlsConfiguration) + tfMap := map[string]interface{}{ + "enabled_standard_arns": apiObject.Value.EnabledStandardIdentifiers, + "security_controls_configuration": flattenSecurityControlsConfiguration(apiObject.Value.SecurityControlsConfiguration), + } + + if v := apiObject.Value.ServiceEnabled; v != nil { + tfMap["service_enabled"] = aws.ToBool(v) + } + return tfMap } -func flattenSecurityControlsConfiguration(apiSecurityControlsConfig *types.SecurityControlsConfiguration) []interface{} { - if apiSecurityControlsConfig == nil { +func flattenSecurityControlsConfiguration(apiObject *types.SecurityControlsConfiguration) []interface{} { + if apiObject == nil { return nil } - tfSecurityControlsConfig := map[string]interface{}{} - tfSecurityControlsConfig["disabled_control_identifiers"] = apiSecurityControlsConfig.DisabledSecurityControlIdentifiers - tfSecurityControlsConfig["enabled_control_identifiers"] = apiSecurityControlsConfig.EnabledSecurityControlIdentifiers - tfControlCustomParams := []interface{}{} - for _, apiControlCustomParam := range apiSecurityControlsConfig.SecurityControlCustomParameters { - tfControlCustomParams = append(tfControlCustomParams, flattenControlCustomParameter(apiControlCustomParam)) + + tfMap := map[string]interface{}{ + "disabled_control_identifiers": apiObject.DisabledSecurityControlIdentifiers, + "enabled_control_identifiers": apiObject.EnabledSecurityControlIdentifiers, + } + + var tfList []interface{} + + for _, apiObject := range apiObject.SecurityControlCustomParameters { + tfList = append(tfList, flattenSecurityControlCustomParameter(apiObject)) } - tfSecurityControlsConfig["control_custom_parameter"] = tfControlCustomParams - return []interface{}{tfSecurityControlsConfig} + + tfMap["security_control_custom_parameter"] = tfList + + return []interface{}{tfMap} } -func flattenControlCustomParameter(apiControlCustomParam types.SecurityControlCustomParameter) map[string]interface{} { - tfControlCustomParam := map[string]interface{}{} - tfControlCustomParam["control_identifier"] = apiControlCustomParam.SecurityControlId - tfParametersForControl := []interface{}{} - for paramName, param := range apiControlCustomParam.Parameters { - tfParameter := map[string]interface{}{ - "name": paramName, - "value_type": string(param.ValueType), +func flattenSecurityControlCustomParameter(apiObject types.SecurityControlCustomParameter) map[string]interface{} { + tfMap := map[string]interface{}{} + + if v := apiObject.SecurityControlId; v != nil { + tfMap["security_control_id"] = aws.ToString(v) + } + + var tfList []interface{} + + for name, apiObject := range apiObject.Parameters { + tfMap := map[string]interface{}{ + "name": name, + "value_type": apiObject.ValueType, } - if param.Value != nil { - switch casted := param.Value.(type) { - case *types.ParameterValueMemberBoolean: - tfParameter["bool"] = []interface{}{ - map[string]interface{}{ - "value": casted.Value, - }, - } - case *types.ParameterValueMemberDouble: - tfParameter["double"] = []interface{}{ - map[string]interface{}{ - "value": casted.Value, - }, - } - case *types.ParameterValueMemberEnum: - tfParameter["enum"] = []interface{}{ - map[string]interface{}{ - "value": casted.Value, - }, - } - case *types.ParameterValueMemberEnumList: - tfParameter["enum_list"] = []interface{}{ - map[string]interface{}{ - "value": casted.Value, - }, - } - case *types.ParameterValueMemberInteger: - tfParameter["int"] = []interface{}{ - map[string]interface{}{ - "value": casted.Value, - }, - } - case *types.ParameterValueMemberIntegerList: - tfParameter["int_list"] = []interface{}{ - map[string]interface{}{ - "value": casted.Value, - }, - } - case *types.ParameterValueMemberString: - tfParameter["string"] = []interface{}{ - map[string]interface{}{ - "value": casted.Value, - }, - } - case *types.ParameterValueMemberStringList: - tfParameter["string_list"] = []interface{}{ - map[string]interface{}{ - "value": casted.Value, - }, - } - default: - log.Printf("[WARN] Security Hub Configuration Policy (%T) unknown type of parameter value", casted) + + switch apiObject := apiObject.Value.(type) { + case *types.ParameterValueMemberBoolean: + tfMap["bool"] = []interface{}{ + map[string]interface{}{ + "value": apiObject.Value, + }, + } + case *types.ParameterValueMemberDouble: + tfMap["double"] = []interface{}{ + map[string]interface{}{ + "value": apiObject.Value, + }, + } + case *types.ParameterValueMemberEnum: + tfMap["enum"] = []interface{}{ + map[string]interface{}{ + "value": apiObject.Value, + }, + } + case *types.ParameterValueMemberEnumList: + tfMap["enum_list"] = []interface{}{ + map[string]interface{}{ + "value": apiObject.Value, + }, + } + case *types.ParameterValueMemberInteger: + tfMap["int"] = []interface{}{ + map[string]interface{}{ + "value": apiObject.Value, + }, + } + case *types.ParameterValueMemberIntegerList: + tfMap["int_list"] = []interface{}{ + map[string]interface{}{ + "value": apiObject.Value, + }, + } + case *types.ParameterValueMemberString: + tfMap["string"] = []interface{}{ + map[string]interface{}{ + "value": apiObject.Value, + }, + } + case *types.ParameterValueMemberStringList: + tfMap["string_list"] = []interface{}{ + map[string]interface{}{ + "value": apiObject.Value, + }, } } - tfParametersForControl = append(tfParametersForControl, tfParameter) + tfList = append(tfList, tfMap) } - tfControlCustomParam["parameter"] = tfParametersForControl - return tfControlCustomParam + + tfMap["parameter"] = tfList + + return tfMap } diff --git a/internal/service/securityhub/exports_test.go b/internal/service/securityhub/exports_test.go index a5979c8a8a7..bfd9b4c0ae3 100644 --- a/internal/service/securityhub/exports_test.go +++ b/internal/service/securityhub/exports_test.go @@ -5,7 +5,8 @@ package securityhub // Exports for use in tests only. var ( - ResourceAutomationRule = newAutomationRuleResource + ResourceAutomationRule = newAutomationRuleResource + ResourceConfigurationPolicy = resourceConfigurationPolicy FindAutomationRuleByARN = findAutomationRuleByARN ) diff --git a/internal/service/securityhub/service_package_gen.go b/internal/service/securityhub/service_package_gen.go index 0a0afbcab79..f97ce72a743 100644 --- a/internal/service/securityhub/service_package_gen.go +++ b/internal/service/securityhub/service_package_gen.go @@ -45,8 +45,9 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka TypeName: "aws_securityhub_action_target", }, { - Factory: ResourceConfigurationPolicy, + Factory: resourceConfigurationPolicy, TypeName: "aws_securityhub_configuration_policy", + Name: "Configuration Policy", }, { Factory: ResourceConfigurationPolicyAssociation, diff --git a/website/docs/r/securityhub_configuration_policy.html.markdown b/website/docs/r/securityhub_configuration_policy.html.markdown index 7572001063e..6bca7f62385 100644 --- a/website/docs/r/securityhub_configuration_policy.html.markdown +++ b/website/docs/r/securityhub_configuration_policy.html.markdown @@ -34,7 +34,8 @@ resource "aws_securityhub_organization_configuration" "example" { resource "aws_securityhub_configuration_policy" "example" { name = "Example" description = "This is an example configuration policy" - policy_member { + + configuration_policy { service_enabled = true enabled_standard_arns = [ "arn:aws:securityhub:us-east-1::standards/aws-foundational-security-best-practices/v/1.0.0", @@ -55,7 +56,8 @@ resource "aws_securityhub_configuration_policy" "example" { resource "aws_securityhub_configuration_policy" "disabled" { name = "Disabled" description = "This is an example of disabled configuration policy" - policy_member { + + configuration_policy { service_enabled = false enabled_standard_arns = [] } @@ -70,7 +72,8 @@ resource "aws_securityhub_configuration_policy" "disabled" { resource "aws_securityhub_configuration_policy" "disabled" { name = "Custom Controls" description = "This is an example of configuration policy with custom control settings" - policy_member { + + configuration_policy { service_enabled = true enabled_standard_arns = [ "arn:aws:securityhub:us-east-1::standards/aws-foundational-security-best-practices/v/1.0.0", @@ -81,8 +84,8 @@ resource "aws_securityhub_configuration_policy" "disabled" { "APIGateway.1", "IAM.7", ] - control_custom_parameter { - control_identifier = "APIGateway.1" + security_control_custom_parameter { + security_control_id = "APIGateway.1" parameter { name = "loggingLevel" value_type = "CUSTOM" @@ -91,8 +94,8 @@ resource "aws_securityhub_configuration_policy" "disabled" { } } } - control_custom_parameter { - control_identifier = "IAM.7" + security_control_custom_parameter { + security_control_id = "IAM.7" parameter { name = "RequireLowercaseCharacters" value_type = "CUSTOM" @@ -119,17 +122,17 @@ resource "aws_securityhub_configuration_policy" "disabled" { This resource supports the following arguments: -* `name` - (Required) The name of the configuration policy. +* `configuration_policy` - (Required) Defines how Security Hub is configured. See [below](#configuration_policy). * `description` - (Optional) The description of the configuration policy. -* `policy_member` - (Required) Defines how Security Hub is configured. See [below](#policy_member). +* `name` - (Required) The name of the configuration policy. -### policy_member +### configuration_policy -The `policy_member` block supports the following: +The `configuration_policy` block supports the following: -* `service_enabled` - (Required) Indicates whether Security Hub is enabled in the policy. * `enabled_standard_arns` - (Required) A list that defines which security standards are enabled in the configuration policy. * `security_controls_configuration` - (Optional) Defines which security controls are enabled in the configuration policy and any customizations to parameters affecting them. See [below](#security_controls_configuration). +* `service_enabled` - (Required) Indicates whether Security Hub is enabled in the policy. ### security_controls_configuration @@ -137,14 +140,14 @@ The `security_controls_configuration` block supports the following: * `disabled_control_identifiers` - (Optional) A list of security controls that are disabled in the configuration policy Security Hub enables all other controls (including newly released controls) other than the listed controls. Conflicts with `enabled_control_identifiers`. * `enabled_control_identifiers` - (Optional) A list of security controls that are enabled in the configuration policy. Security Hub disables all other controls (including newly released controls) other than the listed controls. Conflicts with `disabled_control_identifiers`. -* `control_custom_parameter` - (Optional) A list of control parameter customizations that are included in a configuration policy. Include multiple blocks to define multiple control custom parameters. See [below](#control_custom_parameter). +* `security_control_custom_parameter` - (Optional) A list of control parameter customizations that are included in a configuration policy. Include multiple blocks to define multiple control custom parameters. See [below](#security_control_custom_parameter). -### control_custom_parameter +### security_control_custom_parameter -The `control_custom_parameter` block supports the following: +The `security_control_custom_parameter` block supports the following: -* `control_identifier` - (Required) The ID of the security control. For more information see the [Security Hub controls reference] documentation. * `parameter` - (Required) An object that specifies parameter values for a control in a configuration policy. See [below](#parameter). +* `security_control_id` - (Required) The ID of the security control. For more information see the [Security Hub controls reference] documentation. ### parameter From 971dc5017edd139468271bfb2439b4aa3808614c Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 7 Mar 2024 13:53:49 -0500 Subject: [PATCH 35/71] Add 'findConfigurationPolicyByID'. --- .../securityhub/configuration_policy.go | 35 ++++++++++++++----- internal/service/securityhub/exports_test.go | 3 +- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/internal/service/securityhub/configuration_policy.go b/internal/service/securityhub/configuration_policy.go index 2a82d63edb4..d525aeb3526 100644 --- a/internal/service/securityhub/configuration_policy.go +++ b/internal/service/securityhub/configuration_policy.go @@ -296,11 +296,8 @@ func resourceConfigurationPolicyRead(ctx context.Context, d *schema.ResourceData var diags diag.Diagnostics conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) - input := &securityhub.GetConfigurationPolicyInput{ - Identifier: aws.String(d.Id()), - } + output, err := findConfigurationPolicyByID(ctx, conn, d.Id()) - out, err := conn.GetConfigurationPolicy(ctx, input) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Security Hub Configuration Policy (%s) not found, removing from state", d.Id()) d.SetId("") @@ -311,12 +308,12 @@ func resourceConfigurationPolicyRead(ctx context.Context, d *schema.ResourceData return sdkdiag.AppendErrorf(diags, "reading Security Hub Configuration Policy (%s): %s", d.Id(), err) } - d.Set("arn", out.Arn) - if err := d.Set("configuration_policy", []interface{}{flattenPolicy(out.ConfigurationPolicy)}); err != nil { + d.Set("arn", output.Arn) + if err := d.Set("configuration_policy", []interface{}{flattenPolicy(output.ConfigurationPolicy)}); err != nil { return sdkdiag.AppendErrorf(diags, "setting configuration_policy: %s", err) } - d.Set("description", out.Description) - d.Set("name", out.Name) + d.Set("description", output.Description) + d.Set("name", output.Name) return diags } @@ -367,6 +364,28 @@ func resourceConfigurationPolicyDelete(ctx context.Context, d *schema.ResourceDa return diags } +func findConfigurationPolicyByID(ctx context.Context, conn *securityhub.Client, id string) (*securityhub.GetConfigurationPolicyOutput, error) { + input := &securityhub.GetConfigurationPolicyInput{ + Identifier: aws.String(id), + } + + return findConfigurationPolicy(ctx, conn, input) +} + +func findConfigurationPolicy(ctx context.Context, conn *securityhub.Client, input *securityhub.GetConfigurationPolicyInput) (*securityhub.GetConfigurationPolicyOutput, error) { + output, err := conn.GetConfigurationPolicy(ctx, input) + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} + // validatePolicyMemberSecurityHub performs validation before running creates/updates to prevent certain issues with state. func validatePolicyMemberSecurityHub(apiPolicy *types.PolicyMemberSecurityHub) error { // nosemgrep:ci.securityhub-in-func-name // security_controls_configuration can be specified in Creates/Updates and accepted by the APIs, diff --git a/internal/service/securityhub/exports_test.go b/internal/service/securityhub/exports_test.go index bfd9b4c0ae3..b581c0bcfc8 100644 --- a/internal/service/securityhub/exports_test.go +++ b/internal/service/securityhub/exports_test.go @@ -8,5 +8,6 @@ var ( ResourceAutomationRule = newAutomationRuleResource ResourceConfigurationPolicy = resourceConfigurationPolicy - FindAutomationRuleByARN = findAutomationRuleByARN + FindAutomationRuleByARN = findAutomationRuleByARN + FindConfigurationPolicyByID = findConfigurationPolicyByID ) From 3f1f706314e03acb65e40e27657300238b66af0a Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 7 Mar 2024 14:04:20 -0500 Subject: [PATCH 36/71] r/aws_securityhub_configuration_policy: Tidy up acceptance tests. --- .../securityhub/configuration_policy_test.go | 164 ++++++++++-------- 1 file changed, 96 insertions(+), 68 deletions(-) diff --git a/internal/service/securityhub/configuration_policy_test.go b/internal/service/securityhub/configuration_policy_test.go index f0d2ee4f778..6146353a7dc 100644 --- a/internal/service/securityhub/configuration_policy_test.go +++ b/internal/service/securityhub/configuration_policy_test.go @@ -8,18 +8,20 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go-v2/service/securityhub" "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" + tfsecurityhub "github.com/hashicorp/terraform-provider-aws/internal/service/securityhub" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) func testAccConfigurationPolicy_basic(t *testing.T) { ctx := acctest.Context(t) resourceName := "aws_securityhub_configuration_policy.test" - const exampleStandardsARN = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0" //lintignore:AWSAT005 + exampleStandardsARN := fmt.Sprintf("arn:%s:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0", acctest.Partition()) + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) @@ -29,7 +31,7 @@ func testAccConfigurationPolicy_basic(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubServiceID), ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), - CheckDestroy: acctest.CheckDestroyNoop, + CheckDestroy: testAccCheckConfigurationPolicyDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccConfigurationPolicyConfig_baseDisabled("TestPolicy", "This is a disabled policy"), @@ -37,10 +39,10 @@ func testAccConfigurationPolicy_basic(t *testing.T) { testAccCheckConfigurationPolicyExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "name", "TestPolicy"), resource.TestCheckResourceAttr(resourceName, "description", "This is a disabled policy"), - resource.TestCheckResourceAttr(resourceName, "policy_member.#", "1"), - resource.TestCheckResourceAttr(resourceName, "policy_member.0.service_enabled", "false"), - resource.TestCheckResourceAttr(resourceName, "policy_member.0.enabled_standard_arns.#", "0"), - resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.#", "0"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.#", "1"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.service_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.enabled_standard_arns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.#", "0"), ), }, { @@ -68,8 +70,9 @@ func testAccConfigurationPolicy_basic(t *testing.T) { func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { ctx := acctest.Context(t) resourceName := "aws_securityhub_configuration_policy.test" - foundationalStandardsARN := fmt.Sprintf("arn:aws:securityhub:%s::standards/aws-foundational-security-best-practices/v/1.0.0", acctest.Region()) //lintignore:AWSAT005 - nistStandardsARN := fmt.Sprintf("arn:aws:securityhub:%s::standards/nist-800-53/v/5.0.0", acctest.Region()) //lintignore:AWSAT005 + foundationalStandardsARN := fmt.Sprintf("arn:%s:securityhub:%s::standards/aws-foundational-security-best-practices/v/1.0.0", acctest.Partition(), acctest.Region()) + nistStandardsARN := fmt.Sprintf("arn:%s:securityhub:%s::standards/nist-800-53/v/5.0.0", acctest.Partition(), acctest.Region()) + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) @@ -79,37 +82,37 @@ func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubServiceID), ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), - CheckDestroy: acctest.CheckDestroyNoop, + CheckDestroy: testAccCheckConfigurationPolicyDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccConfigurationPolicyConfig_controlCustomParametersMulti(foundationalStandardsARN), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyExists(ctx, resourceName), - resource.TestCheckResourceAttr(resourceName, "policy_member.#", "1"), - resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.#", "1"), - resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.#", "2"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.#", "1"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.#", "2"), - resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "APIGateway.1"), - resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.parameter.#", "1"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.0.security_control_id", "APIGateway.1"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.0.parameter.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.0.parameter.*", map[string]string{ "name": "loggingLevel", "value_type": "CUSTOM", "enum.0.value": "INFO", }), - resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.1.control_identifier", "IAM.7"), - resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.1.parameter.#", "3"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.1.parameter.*", map[string]string{ + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.1.security_control_id", "IAM.7"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.1.parameter.#", "3"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.1.parameter.*", map[string]string{ "name": "RequireLowercaseCharacters", "value_type": "CUSTOM", "bool.0.value": "false", }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.1.parameter.*", map[string]string{ + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.1.parameter.*", map[string]string{ "name": "RequireUppercaseCharacters", "value_type": "DEFAULT", }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.1.parameter.*", map[string]string{ + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.1.parameter.*", map[string]string{ "name": "MaxPasswordAge", "value_type": "CUSTOM", "int.0.value": "60", @@ -126,8 +129,8 @@ func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { Config: testAccConfigurationPolicyConfig_controlCustomParametersSingle(nistStandardsARN, "CloudWatch.15", "insufficientDataActionRequired", "bool", "true"), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyExists(ctx, resourceName), - resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "CloudWatch.15"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.0.security_control_id", "CloudWatch.15"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.0.parameter.*", map[string]string{ "name": "insufficientDataActionRequired", "value_type": "CUSTOM", "bool.0.value": "true", @@ -139,8 +142,8 @@ func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { Config: testAccConfigurationPolicyConfig_controlCustomParametersSingle(foundationalStandardsARN, "RDS.14", "BacktrackWindowInHours", "double", "20.25"), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyExists(ctx, resourceName), - resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "RDS.14"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.0.security_control_id", "RDS.14"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.0.parameter.*", map[string]string{ "name": "BacktrackWindowInHours", "value_type": "CUSTOM", "double.0.value": "20.25", @@ -152,8 +155,8 @@ func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { Config: testAccConfigurationPolicyConfig_controlCustomParametersSingle(foundationalStandardsARN, "APIGateway.1", "loggingLevel", "enum", `"ERROR"`), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyExists(ctx, resourceName), - resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "APIGateway.1"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.0.security_control_id", "APIGateway.1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.0.parameter.*", map[string]string{ "name": "loggingLevel", "value_type": "CUSTOM", "enum.0.value": "ERROR", @@ -165,8 +168,8 @@ func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { Config: testAccConfigurationPolicyConfig_controlCustomParametersSingle(foundationalStandardsARN, "S3.11", "eventTypes", "enum_list", `["s3:IntelligentTiering", "s3:LifecycleExpiration:*"]`), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyExists(ctx, resourceName), - resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "S3.11"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.0.security_control_id", "S3.11"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.0.parameter.*", map[string]string{ "name": "eventTypes", "value_type": "CUSTOM", "enum_list.0.value.#": "2", @@ -180,8 +183,8 @@ func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { Config: testAccConfigurationPolicyConfig_controlCustomParametersSingle(foundationalStandardsARN, "DocumentDB.2", "minimumBackupRetentionPeriod", "int", "20"), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyExists(ctx, resourceName), - resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "DocumentDB.2"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.0.security_control_id", "DocumentDB.2"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.0.parameter.*", map[string]string{ "name": "minimumBackupRetentionPeriod", "value_type": "CUSTOM", "int.0.value": "20", @@ -193,8 +196,8 @@ func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { Config: testAccConfigurationPolicyConfig_controlCustomParametersSingle(foundationalStandardsARN, "EC2.18", "authorizedTcpPorts", "int_list", "[443, 8080]"), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyExists(ctx, resourceName), - resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.control_identifier", "EC2.18"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "policy_member.0.security_controls_configuration.0.control_custom_parameter.0.parameter.*", map[string]string{ + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.0.security_control_id", "EC2.18"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.0.parameter.*", map[string]string{ "name": "authorizedTcpPorts", "value_type": "CUSTOM", "int_list.0.value.#": "2", @@ -211,7 +214,8 @@ func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { func testAccConfigurationPolicy_specificControlIdentifiers(t *testing.T) { ctx := acctest.Context(t) resourceName := "aws_securityhub_configuration_policy.test" - foundationalStandardsARN := fmt.Sprintf("arn:aws:securityhub:%s::standards/aws-foundational-security-best-practices/v/1.0.0", acctest.Region()) //lintignore:AWSAT005 + foundationalStandardsARN := fmt.Sprintf("arn:%s:securityhub:%s::standards/aws-foundational-security-best-practices/v/1.0.0", acctest.Partition(), acctest.Region()) + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) @@ -221,19 +225,18 @@ func testAccConfigurationPolicy_specificControlIdentifiers(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubServiceID), ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), - CheckDestroy: acctest.CheckDestroyNoop, + CheckDestroy: testAccCheckConfigurationPolicyDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccConfigurationPolicyConfig_specifcControlIdentifiers(foundationalStandardsARN, "IAM.7", "APIGateway.1", false), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyExists(ctx, resourceName), - - resource.TestCheckResourceAttr(resourceName, "policy_member.#", "1"), - resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.#", "1"), - resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.disabled_control_identifiers.#", "2"), - resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.disabled_control_identifiers.0", "IAM.7"), - resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.disabled_control_identifiers.1", "APIGateway.1"), - resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.enabled_control_identifiers.#", "0"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.#", "1"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.disabled_security_control_ids.#", "2"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.disabled_security_control_ids.0", "IAM.7"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.disabled_security_control_ids.1", "APIGateway.1"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.enabled_security_control_ids.#", "0"), ), }, { @@ -245,13 +248,12 @@ func testAccConfigurationPolicy_specificControlIdentifiers(t *testing.T) { Config: testAccConfigurationPolicyConfig_specifcControlIdentifiers(foundationalStandardsARN, "APIGateway.1", "IAM.7", true), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyExists(ctx, resourceName), - - resource.TestCheckResourceAttr(resourceName, "policy_member.#", "1"), - resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.#", "1"), - resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.enabled_control_identifiers.#", "2"), - resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.enabled_control_identifiers.0", "APIGateway.1"), - resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.enabled_control_identifiers.1", "IAM.7"), - resource.TestCheckResourceAttr(resourceName, "policy_member.0.security_controls_configuration.0.disabled_control_identifiers.#", "0"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.#", "1"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.enabled_security_control_ids.#", "2"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.enabled_security_control_ids.0", "APIGateway.1"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.enabled_security_control_ids.1", "IAM.7"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.disabled_security_control_ids.#", "0"), ), }, }, @@ -266,13 +268,39 @@ func testAccCheckConfigurationPolicyExists(ctx context.Context, n string) resour } conn := acctest.Provider.Meta().(*conns.AWSClient).SecurityHubClient(ctx) - _, err := conn.GetConfigurationPolicy(ctx, &securityhub.GetConfigurationPolicyInput{ - Identifier: &rs.Primary.ID, - }) + + _, err := tfsecurityhub.FindConfigurationPolicyByID(ctx, conn, rs.Primary.ID) + return err } } +func testAccCheckConfigurationPolicyDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).SecurityHubClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_securityhub_configuration_policy" { + continue + } + + _, err := tfsecurityhub.FindConfigurationPolicyByID(ctx, conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Security Hub Configuration Policy %s still exists", rs.Primary.ID) + } + + return nil + } +} + func testAccConfigurationPolicyConfig_baseDisabled(name, description string) string { return acctest.ConfigCompose( acctest.ConfigAlternateAccountProvider(), @@ -283,7 +311,7 @@ resource "aws_securityhub_configuration_policy" "test" { name = %[1]q description = %[2]q - policy_member { + configuration_policy { service_enabled = false enabled_standard_arns = [] } @@ -302,13 +330,13 @@ resource "aws_securityhub_configuration_policy" "test" { name = %[1]q description = %[2]q - policy_member { + configuration_policy { service_enabled = true enabled_standard_arns = [ %[3]q ] security_controls_configuration { - disabled_control_identifiers = [] + disabled_security_control_ids = [] } } @@ -325,17 +353,17 @@ func testAccConfigurationPolicyConfig_controlCustomParametersMulti(standardsARN resource "aws_securityhub_configuration_policy" "test" { name = "MultipleControlCustomParametersPolicy" - policy_member { + configuration_policy { service_enabled = true enabled_standard_arns = [ %[1]q ] security_controls_configuration { - disabled_control_identifiers = [] + disabled_security_control_ids = [] - control_custom_parameter { - control_identifier = "APIGateway.1" + security_control_custom_parameter { + security_control_id = "APIGateway.1" parameter { name = "loggingLevel" @@ -346,8 +374,8 @@ resource "aws_securityhub_configuration_policy" "test" { } } - control_custom_parameter { - control_identifier = "IAM.7" + security_control_custom_parameter { + security_control_id = "IAM.7" parameter { name = "RequireUppercaseCharacters" @@ -386,17 +414,17 @@ func testAccConfigurationPolicyConfig_controlCustomParametersSingle(standardsARN resource "aws_securityhub_configuration_policy" "test" { name = "ControlCustomParametersPolicy" - policy_member { + configuration_policy { service_enabled = true enabled_standard_arns = [ %[1]q ] security_controls_configuration { - disabled_control_identifiers = [] + disabled_security_control_ids = [] - control_custom_parameter { - control_identifier = %[2]q + security_control_custom_parameter { + security_control_id = %[2]q parameter { name = %[3]q @@ -414,9 +442,9 @@ resource "aws_securityhub_configuration_policy" "test" { } func testAccConfigurationPolicyConfig_specifcControlIdentifiers(standardsARN, control1, control2 string, enabledOnly bool) string { - controlIDAttr := "disabled_control_identifiers" + controlIDAttr := "disabled_security_control_ids" if enabledOnly { - controlIDAttr = "enabled_control_identifiers" + controlIDAttr = "enabled_security_control_ids" } return acctest.ConfigCompose( @@ -426,7 +454,7 @@ func testAccConfigurationPolicyConfig_specifcControlIdentifiers(standardsARN, co fmt.Sprintf(` resource "aws_securityhub_configuration_policy" "test" { name = "ControlIdentifiersPolicy" - policy_member { + configuration_policy { service_enabled = true enabled_standard_arns = [%[1]q] From aae9c2847af92d504a722e6220046621213271fa Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 7 Mar 2024 14:45:28 -0500 Subject: [PATCH 37/71] Add 'testAccConfigurationPolicy_disappears'. --- .../securityhub/configuration_policy_test.go | 27 +++++++++++++++++++ .../service/securityhub/securityhub_test.go | 1 + 2 files changed, 28 insertions(+) diff --git a/internal/service/securityhub/configuration_policy_test.go b/internal/service/securityhub/configuration_policy_test.go index 6146353a7dc..1bebdebf3c4 100644 --- a/internal/service/securityhub/configuration_policy_test.go +++ b/internal/service/securityhub/configuration_policy_test.go @@ -67,6 +67,33 @@ func testAccConfigurationPolicy_basic(t *testing.T) { }) } +func testAccConfigurationPolicy_disappears(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_securityhub_configuration_policy.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckAlternateAccount(t) + acctest.PreCheckAlternateRegionIs(t, acctest.Region()) + acctest.PreCheckOrganizationMemberAccount(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), + CheckDestroy: testAccCheckConfigurationPolicyDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccConfigurationPolicyConfig_baseDisabled("TestPolicy", "This is a disabled policy"), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationPolicyExists(ctx, resourceName), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfsecurityhub.ResourceConfigurationPolicy(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { ctx := acctest.Context(t) resourceName := "aws_securityhub_configuration_policy.test" diff --git a/internal/service/securityhub/securityhub_test.go b/internal/service/securityhub/securityhub_test.go index 547d202f4c3..8d8d8821cc1 100644 --- a/internal/service/securityhub/securityhub_test.go +++ b/internal/service/securityhub/securityhub_test.go @@ -98,6 +98,7 @@ func TestAccSecurityHub_centralConfiguration(t *testing.T) { }, "ConfigurationPolicy": { "basic": testAccConfigurationPolicy_basic, + "disappears": testAccConfigurationPolicy_disappears, "customParameters": testAccConfigurationPolicy_controlCustomParameters, "controlIdentifiers": testAccConfigurationPolicy_specificControlIdentifiers, }, From 80d0479fe3d5a23b67f2c88a363dbb25ebc64c21 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 7 Mar 2024 17:11:15 -0500 Subject: [PATCH 38/71] r/aws_securityhub_configuration_policy_association: Tidy up. --- .../configuration_policy_association.go | 199 ++++++++++-------- internal/service/securityhub/exports_test.go | 10 +- .../securityhub/service_package_gen.go | 3 +- ..._configuration_policy_association.markdown | 12 +- 4 files changed, 125 insertions(+), 99 deletions(-) diff --git a/internal/service/securityhub/configuration_policy_association.go b/internal/service/securityhub/configuration_policy_association.go index 96f6ae33b8a..7ca3107d30e 100644 --- a/internal/service/securityhub/configuration_policy_association.go +++ b/internal/service/securityhub/configuration_policy_association.go @@ -6,7 +6,6 @@ package securityhub import ( "context" "errors" - "fmt" "log" "strings" "time" @@ -26,8 +25,8 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -// @SDKResource("aws_securityhub_configuration_policy_association") -func ResourceConfigurationPolicyAssociation() *schema.Resource { +// @SDKResource("aws_securityhub_configuration_policy_association", name="Configuration Policy Association") +func resourceConfigurationPolicyAssociation() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceConfigurationPolicyAssociationCreateOrUpdate, ReadWithoutTimeout: resourceConfigurationPolicyAssociationRead, @@ -39,10 +38,17 @@ func ResourceConfigurationPolicyAssociation() *schema.Resource { }, Timeouts: &schema.ResourceTimeout{ - Read: schema.DefaultTimeout(90 * time.Second), + Create: schema.DefaultTimeout(90 * time.Second), + Update: schema.DefaultTimeout(90 * time.Second), }, Schema: map[string]*schema.Schema{ + "policy_id": { + Type: schema.TypeString, + Required: true, + Description: "The universally unique identifier (UUID) of the configuration policy.", + ValidateFunc: validation.IsUUID, + }, "target_id": { Type: schema.TypeString, Required: true, @@ -53,35 +59,10 @@ func ResourceConfigurationPolicyAssociation() *schema.Resource { "Target ID must be a valid root, organizational unit or account id.", ), }, - "policy_id": { - Type: schema.TypeString, - Required: true, - Description: "The universally unique identifier (UUID) of the configuration policy.", - ValidateFunc: validation.IsUUID, - }, }, } } -// GetTarget converts a target id string into proper types.Target struct. -func GetTarget(targetID string) types.Target { - if strings.HasPrefix(targetID, "r-") { - return &types.TargetMemberRootId{ - Value: targetID, - } - } - - if strings.HasPrefix(targetID, "ou-") { - return &types.TargetMemberOrganizationalUnitId{ - Value: targetID, - } - } - - return &types.TargetMemberAccountId{ - Value: targetID, - } -} - func resourceConfigurationPolicyAssociationCreateOrUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) @@ -89,119 +70,153 @@ func resourceConfigurationPolicyAssociationCreateOrUpdate(ctx context.Context, d targetID := d.Get("target_id").(string) input := &securityhub.StartConfigurationPolicyAssociationInput{ ConfigurationPolicyIdentifier: aws.String(d.Get("policy_id").(string)), - Target: GetTarget(targetID), + Target: expandTarget(targetID), } _, err := conn.StartConfigurationPolicyAssociation(ctx, input) + if err != nil { return sdkdiag.AppendErrorf(diags, "starting Security Hub Configuration Policy Association (%s): %s", targetID, err) } + timeout := d.Timeout(schema.TimeoutCreate) if d.IsNewResource() { d.SetId(targetID) + } else { + timeout = d.Timeout(schema.TimeoutUpdate) + } + + if _, err := waitConfigurationPolicyAssociationSucceeded(ctx, conn, d.Id(), timeout); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for Security Hub Configuration Policy Association (%s) success: %s", targetID, err) } return append(diags, resourceConfigurationPolicyAssociationRead(ctx, d, meta)...) } -func resourceConfigurationPolicyAssociationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourceConfigurationPolicyAssociationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) - input := &securityhub.StartConfigurationPolicyDisassociationInput{ - ConfigurationPolicyIdentifier: aws.String(d.Get("policy_id").(string)), - Target: GetTarget(d.Get("target_id").(string)), + output, err := findConfigurationPolicyAssociationByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Security Hub Configuration Policy Association (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags } - _, err := conn.StartConfigurationPolicyDisassociation(ctx, input) if err != nil { - return sdkdiag.AppendErrorf(diags, "starting Security Hub Configuration Policy Disassociation (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "reading Security Hub Configuration Policy Association (%s): %s", d.Id(), err) } + d.Set("policy_id", output.ConfigurationPolicyId) + d.Set("target_id", output.TargetId) + return diags } -func resourceConfigurationPolicyAssociationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourceConfigurationPolicyAssociationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) - out, err := waitConfigurationPolicyAssociationSuccess(ctx, conn, GetTarget(d.Id()), d.Timeout(schema.TimeoutRead)) - - if !d.IsNewResource() && tfresource.NotFound(err) { - log.Printf("[WARN] Security Hub Configuration Policy Association (%s) not found, removing from state", d.Id()) - d.SetId("") - return diags - } + log.Printf("[DEBUG] Deleting Security Hub Configuration Policy Association: %s", d.Id()) + _, err := conn.StartConfigurationPolicyDisassociation(ctx, &securityhub.StartConfigurationPolicyDisassociationInput{ + ConfigurationPolicyIdentifier: aws.String(d.Get("policy_id").(string)), + Target: expandTarget(d.Id()), + }) if err != nil { - return sdkdiag.AppendErrorf(diags, "reading Security Hub Configuration Policy Association (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "starting Security Hub Configuration Policy Disassociation (%s): %s", d.Id(), err) } - d.Set("policy_id", out.ConfigurationPolicyId) - d.Set("target_id", out.TargetId) return diags } -func waitConfigurationPolicyAssociationSuccess(ctx context.Context, conn *securityhub.Client, target types.Target, timeout time.Duration) (*securityhub.GetConfigurationPolicyAssociationOutput, error) { - stateConf := &retry.StateChangeConf{ - Pending: enum.Slice(types.ConfigurationPolicyAssociationStatusPending), - Target: enum.Slice(types.ConfigurationPolicyAssociationStatusSuccess), - Refresh: getConfigurationPolicyAssociation(ctx, conn, target), - Timeout: timeout, - NotFoundChecks: 20, - ContinuousTargetOccurence: 2, +func findConfigurationPolicyAssociationByID(ctx context.Context, conn *securityhub.Client, id string) (*securityhub.GetConfigurationPolicyAssociationOutput, error) { + input := &securityhub.GetConfigurationPolicyAssociationInput{ + Target: expandTarget(id), } - outputRaw, err := stateConf.WaitForStateContext(ctx) - var timeoutErr *retry.TimeoutError - if tfresource.TimedOut(err) && errors.As(err, &timeoutErr) { - log.Printf("[WARN] Security Hub Configuration Policy Association still in state: %s. It can take up to 24 hours for the status to change from PENDING to SUCCESS or FAILURE", timeoutErr.LastState) - // We try to wait until SUCCESS state is reached but don't error if still in PENDING state. - // We must attempt to wait/retry in order for Policy Disassociations to take effect - return conn.GetConfigurationPolicyAssociation(ctx, &securityhub.GetConfigurationPolicyAssociationInput{ - Target: target, - }) + return findConfigurationPolicyAssociation(ctx, conn, input) +} + +func findConfigurationPolicyAssociation(ctx context.Context, conn *securityhub.Client, input *securityhub.GetConfigurationPolicyAssociationInput) (*securityhub.GetConfigurationPolicyAssociationOutput, error) { + output, err := conn.GetConfigurationPolicyAssociation(ctx, input) + + if tfawserr.ErrCodeEquals(err, errCodeResourceNotFoundException) || tfawserr.ErrMessageContains(err, errCodeInvalidAccessException, "not subscribed to AWS Security Hub") { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) } - return outputRaw.(*securityhub.GetConfigurationPolicyAssociationOutput), err + return output, nil } -func getConfigurationPolicyAssociation(ctx context.Context, conn *securityhub.Client, target types.Target) retry.StateRefreshFunc { +func statusConfigurationPolicyAssociation(ctx context.Context, conn *securityhub.Client, id string) retry.StateRefreshFunc { return func() (interface{}, string, error) { - input := &securityhub.GetConfigurationPolicyAssociationInput{ - Target: target, - } - output, err := conn.GetConfigurationPolicyAssociation(ctx, input) - if tfawserr.ErrCodeEquals(err, errCodeResourceNotFoundException) || tfawserr.ErrMessageContains(err, errCodeInvalidAccessException, "not subscribed to AWS Security Hub") { - return nil, "", &retry.NotFoundError{ - LastError: err, - LastRequest: input, - } + output, err := findConfigurationPolicyAssociationByID(ctx, conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil } if err != nil { return nil, "", err } - if output == nil || output.TargetId == nil { - return nil, "", tfresource.NewEmptyResultError(input) + return output, string(output.AssociationStatus), nil + } +} + +func waitConfigurationPolicyAssociationSucceeded(ctx context.Context, conn *securityhub.Client, id string, timeout time.Duration) (*securityhub.GetConfigurationPolicyAssociationOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(types.ConfigurationPolicyAssociationStatusPending), + Target: enum.Slice(types.ConfigurationPolicyAssociationStatusSuccess), + Refresh: statusConfigurationPolicyAssociation(ctx, conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if tfresource.TimedOut(err) { + log.Printf("[WARN] Security Hub Configuration Policy Association (%s) still in PENDING state. It can take up to 24 hours for the status to change from PENDING to SUCCESS or FAILURE", id) + // We try to wait until SUCCESS state is reached but don't error if still in PENDING state. + // We must attempt to wait/retry in order for Policy Disassociations to take effect + return findConfigurationPolicyAssociationByID(ctx, conn, id) + } + + if output, ok := outputRaw.(*securityhub.GetConfigurationPolicyAssociationOutput); ok { + tfresource.SetLastError(err, errors.New(aws.ToString(output.AssociationStatusMessage))) + + return output, err + } + + return nil, err +} + +func expandTarget(targetID string) types.Target { + if strings.HasPrefix(targetID, "r-") { + return &types.TargetMemberRootId{ + Value: targetID, } + } - switch output.AssociationStatus { - case types.ConfigurationPolicyAssociationStatusPending: - return output, string(output.AssociationStatus), nil - case types.ConfigurationPolicyAssociationStatusSuccess, "": - return output, string(output.AssociationStatus), nil - default: - var statusErr error - if msg := output.AssociationStatusMessage; msg != nil && len(*msg) > 0 { - statusErr = fmt.Errorf("StatusMessage: %s", *msg) - } - return nil, "", &retry.UnexpectedStateError{ - LastError: statusErr, - State: string(output.AssociationStatus), - ExpectedState: enum.Slice(types.ConfigurationPolicyAssociationStatusPending, types.ConfigurationPolicyAssociationStatusSuccess), - } + if strings.HasPrefix(targetID, "ou-") { + return &types.TargetMemberOrganizationalUnitId{ + Value: targetID, } } + + return &types.TargetMemberAccountId{ + Value: targetID, + } } diff --git a/internal/service/securityhub/exports_test.go b/internal/service/securityhub/exports_test.go index b581c0bcfc8..88097d979dc 100644 --- a/internal/service/securityhub/exports_test.go +++ b/internal/service/securityhub/exports_test.go @@ -5,9 +5,11 @@ package securityhub // Exports for use in tests only. var ( - ResourceAutomationRule = newAutomationRuleResource - ResourceConfigurationPolicy = resourceConfigurationPolicy + ResourceAutomationRule = newAutomationRuleResource + ResourceConfigurationPolicy = resourceConfigurationPolicy + ResourceConfigurationPolicyAssociation = resourceConfigurationPolicyAssociation - FindAutomationRuleByARN = findAutomationRuleByARN - FindConfigurationPolicyByID = findConfigurationPolicyByID + FindAutomationRuleByARN = findAutomationRuleByARN + FindConfigurationPolicyAssociationByID = findConfigurationPolicyAssociationByID + FindConfigurationPolicyByID = findConfigurationPolicyByID ) diff --git a/internal/service/securityhub/service_package_gen.go b/internal/service/securityhub/service_package_gen.go index f97ce72a743..8e845f1a0e4 100644 --- a/internal/service/securityhub/service_package_gen.go +++ b/internal/service/securityhub/service_package_gen.go @@ -50,8 +50,9 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Name: "Configuration Policy", }, { - Factory: ResourceConfigurationPolicyAssociation, + Factory: resourceConfigurationPolicyAssociation, TypeName: "aws_securityhub_configuration_policy_association", + Name: "Configuration Policy Association", }, { Factory: ResourceFindingAggregator, diff --git a/website/docs/r/securityhub_configuration_policy_association.markdown b/website/docs/r/securityhub_configuration_policy_association.markdown index 402260bf166..3d7392cc0ea 100644 --- a/website/docs/r/securityhub_configuration_policy_association.markdown +++ b/website/docs/r/securityhub_configuration_policy_association.markdown @@ -32,7 +32,8 @@ resource "aws_securityhub_organization_configuration" "example" { resource "aws_securityhub_configuration_policy" "example" { name = "Example" description = "This is an example configuration policy" - policy_member { + + configuration_policy { service_enabled = true enabled_standard_arns = [ "arn:aws:securityhub:us-east-1::standards/aws-foundational-security-best-practices/v/1.0.0", @@ -66,8 +67,8 @@ resource "aws_securityhub_configuration_policy_association" "ou_example" { This resource supports the following arguments: -* `target_id` - (Required, Forces new resource) The identifier of the target account, organizational unit, or the root to associate with the specified configuration. * `policy_id` - (Required) The universally unique identifier (UUID) of the configuration policy. +* `target_id` - (Required, Forces new resource) The identifier of the target account, organizational unit, or the root to associate with the specified configuration. ## Attribute Reference @@ -75,6 +76,13 @@ This resource exports the following attributes in addition to the arguments abov * `id` - The identifier of the target account, organizational unit, or the root that is associated with the configuration. +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `90s`) +* `update` - (Default `90s`) + ## Import In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import an existing Security Hub configuration policy association using the target id. For example: From 2de110298a82500e7c7c06c75a4648e35e6f087f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 7 Mar 2024 17:11:29 -0500 Subject: [PATCH 39/71] r/aws_securityhub_configuration_policy_association: Tidy up acceptance tests. --- .../configuration_policy_association_test.go | 62 ++++++++++++------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/internal/service/securityhub/configuration_policy_association_test.go b/internal/service/securityhub/configuration_policy_association_test.go index e84ab0aac72..7f9f1905f3b 100644 --- a/internal/service/securityhub/configuration_policy_association_test.go +++ b/internal/service/securityhub/configuration_policy_association_test.go @@ -8,13 +8,12 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go-v2/service/securityhub" - "github.com/aws/aws-sdk-go-v2/service/securityhub/types" "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" tfsecurityhub "github.com/hashicorp/terraform-provider-aws/internal/service/securityhub" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -26,6 +25,7 @@ func testAccConfigurationPolicyAssociation_basic(t *testing.T) { rootTarget := "data.aws_organizations_organization.test.roots[0].id" policy1 := "aws_securityhub_configuration_policy.test_1.id" policy2 := "aws_securityhub_configuration_policy.test_2.id" + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) @@ -35,14 +35,14 @@ func testAccConfigurationPolicyAssociation_basic(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubServiceID), ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), - CheckDestroy: acctest.CheckDestroyNoop, + CheckDestroy: testAccCheckConfigurationPolicyAssociationDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccConfigurationPolicyAssociationConfig_base(ouTarget, policy1), + Config: testAccConfigurationPolicyAssociationConfig_basic(ouTarget, policy1), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyAssociationExists(ctx, resourceName), - resource.TestCheckResourceAttrPair(resourceName, "target_id", "aws_organizations_organizational_unit.test", "id"), resource.TestCheckResourceAttrPair(resourceName, "policy_id", "aws_securityhub_configuration_policy.test_1", "id"), + resource.TestCheckResourceAttrPair(resourceName, "target_id", "aws_organizations_organizational_unit.test", "id"), ), }, { @@ -51,27 +51,27 @@ func testAccConfigurationPolicyAssociation_basic(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccConfigurationPolicyAssociationConfig_base(ouTarget, policy2), + Config: testAccConfigurationPolicyAssociationConfig_basic(ouTarget, policy2), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyAssociationExists(ctx, resourceName), - resource.TestCheckResourceAttrPair(resourceName, "target_id", "aws_organizations_organizational_unit.test", "id"), resource.TestCheckResourceAttrPair(resourceName, "policy_id", "aws_securityhub_configuration_policy.test_2", "id"), + resource.TestCheckResourceAttrPair(resourceName, "target_id", "aws_organizations_organizational_unit.test", "id"), ), }, { - Config: testAccConfigurationPolicyAssociationConfig_base(rootTarget, policy2), + Config: testAccConfigurationPolicyAssociationConfig_basic(rootTarget, policy2), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyAssociationExists(ctx, resourceName), - resource.TestCheckResourceAttrPair(resourceName, "target_id", "aws_organizations_organizational_unit.test", "parent_id"), resource.TestCheckResourceAttrPair(resourceName, "policy_id", "aws_securityhub_configuration_policy.test_2", "id"), + resource.TestCheckResourceAttrPair(resourceName, "target_id", "aws_organizations_organizational_unit.test", "parent_id"), ), }, { - Config: testAccConfigurationPolicyAssociationConfig_base(accountTarget, policy2), + Config: testAccConfigurationPolicyAssociationConfig_basic(accountTarget, policy2), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyAssociationExists(ctx, resourceName), - resource.TestCheckResourceAttrPair(resourceName, "target_id", "data.aws_caller_identity.member", "account_id"), resource.TestCheckResourceAttrPair(resourceName, "policy_id", "aws_securityhub_configuration_policy.test_2", "id"), + resource.TestCheckResourceAttrPair(resourceName, "target_id", "data.aws_caller_identity.member", "account_id"), ), }, }, @@ -86,15 +86,33 @@ func testAccCheckConfigurationPolicyAssociationExists(ctx context.Context, n str } conn := acctest.Provider.Meta().(*conns.AWSClient).SecurityHubClient(ctx) - out, err := conn.GetConfigurationPolicyAssociation(ctx, &securityhub.GetConfigurationPolicyAssociationInput{ - Target: tfsecurityhub.GetTarget(rs.Primary.ID), - }) - if err != nil { - return err - } - if out.AssociationStatus == types.ConfigurationPolicyAssociationStatusFailed { - return fmt.Errorf("unexpected association status: %s %s", out.AssociationStatus, *out.AssociationStatusMessage) + _, err := tfsecurityhub.FindConfigurationPolicyAssociationByID(ctx, conn, rs.Primary.ID) + + return err + } +} + +func testAccCheckConfigurationPolicyAssociationDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).SecurityHubClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_securityhub_configuration_policy_association" { + continue + } + + _, err := tfsecurityhub.FindConfigurationPolicyAssociationByID(ctx, conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Security Hub Configuration Policy Association %s still exists", rs.Primary.ID) } return nil @@ -119,7 +137,7 @@ const testAccConfigurationPoliciesConfig_base = ` resource "aws_securityhub_configuration_policy" "test_1" { name = "test1" - policy_member { + configuration_policy { service_enabled = true enabled_standard_arns = ["arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"] @@ -134,7 +152,7 @@ resource "aws_securityhub_configuration_policy" "test_1" { resource "aws_securityhub_configuration_policy" "test_2" { name = "test2" - policy_member { + configuration_policy { service_enabled = true enabled_standard_arns = ["arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"] @@ -147,7 +165,7 @@ resource "aws_securityhub_configuration_policy" "test_2" { } ` -func testAccConfigurationPolicyAssociationConfig_base(targetID, policyID string) string { +func testAccConfigurationPolicyAssociationConfig_basic(targetID, policyID string) string { return acctest.ConfigCompose( acctest.ConfigAlternateAccountProvider(), testAccMemberAccountDelegatedAdminConfig_base, From ca01f977e7e2826c1685a5dee6b43c59d6a076b7 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 7 Mar 2024 17:19:08 -0500 Subject: [PATCH 40/71] r/aws_securityhub_organization_configuration: Tidy up. --- internal/service/securityhub/exports_test.go | 1 + .../securityhub/organization_configuration.go | 70 +++++++++++-------- .../securityhub/service_package_gen.go | 3 +- ...b_organization_configuration.html.markdown | 8 +++ 4 files changed, 50 insertions(+), 32 deletions(-) diff --git a/internal/service/securityhub/exports_test.go b/internal/service/securityhub/exports_test.go index 88097d979dc..939ff46fb5e 100644 --- a/internal/service/securityhub/exports_test.go +++ b/internal/service/securityhub/exports_test.go @@ -8,6 +8,7 @@ var ( ResourceAutomationRule = newAutomationRuleResource ResourceConfigurationPolicy = resourceConfigurationPolicy ResourceConfigurationPolicyAssociation = resourceConfigurationPolicyAssociation + ResourceOrganizationConfiguration = resourceOrganizationConfiguration FindAutomationRuleByARN = findAutomationRuleByARN FindConfigurationPolicyAssociationByID = findConfigurationPolicyAssociationByID diff --git a/internal/service/securityhub/organization_configuration.go b/internal/service/securityhub/organization_configuration.go index 492b896c861..3b301232722 100644 --- a/internal/service/securityhub/organization_configuration.go +++ b/internal/service/securityhub/organization_configuration.go @@ -22,8 +22,8 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -// @SDKResource("aws_securityhub_organization_configuration") -func ResourceOrganizationConfiguration() *schema.Resource { +// @SDKResource("aws_securityhub_organization_configuration", name="Organization Configuration") +func resourceOrganizationConfiguration() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceOrganizationConfigurationUpdate, ReadWithoutTimeout: resourceOrganizationConfigurationRead, @@ -35,7 +35,8 @@ func ResourceOrganizationConfiguration() *schema.Resource { }, Timeouts: &schema.ResourceTimeout{ - Read: schema.DefaultTimeout(180 * time.Second), + Create: schema.DefaultTimeout(180 * time.Second), + Update: schema.DefaultTimeout(180 * time.Second), Delete: schema.DefaultTimeout(180 * time.Second), }, @@ -92,38 +93,18 @@ func resourceOrganizationConfigurationUpdate(ctx context.Context, d *schema.Reso return sdkdiag.AppendErrorf(diags, "updating Security Hub Organization Configuration (%s): %s", d.Id(), err) } + timeout := d.Timeout(schema.TimeoutCreate) if d.IsNewResource() { d.SetId(meta.(*conns.AWSClient).AccountID) + } else { + timeout = d.Timeout(schema.TimeoutUpdate) } - return append(diags, resourceOrganizationConfigurationRead(ctx, d, meta)...) -} - -// resourceOrganizationConfigurationDelete destroys the organizations configuration resource by updating it to a disabled configuration. -// If orgnanization configuration is of type central, then dependent resources (i.e finding_aggregator, delegated_admin) cannot be removed from AWS. -// Updating the organization configuration on destroy is necessary to allow dependent resources to be able to be cleaned up. -func resourceOrganizationConfigurationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) - - input := &securityhub.UpdateOrganizationConfigurationInput{ - AutoEnable: aws.Bool(false), - AutoEnableStandards: types.AutoEnableStandardsNone, - OrganizationConfiguration: &types.OrganizationConfiguration{ - ConfigurationType: types.OrganizationConfigurationConfigurationTypeLocal, - }, - } - _, err := conn.UpdateOrganizationConfiguration(ctx, input) - if err != nil { - return sdkdiag.AppendErrorf(diags, "deleting Security Hub Organization Configuration (%s): %s", d.Id(), err) + if _, err := waitOrganizationConfigurationEnabled(ctx, conn, timeout); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for Security Hub Organization Configuration (%s) enable: %s", d.Id(), err) } - _, err = waitOrganizationConfigurationEnabled(ctx, conn, d.Timeout(schema.TimeoutDelete)) - if err != nil { - return sdkdiag.AppendErrorf(diags, "deleting Security Hub Organization Configuration (%s): %s", d.Id(), err) - } - - return diags + return append(diags, resourceOrganizationConfigurationRead(ctx, d, meta)...) } func resourceOrganizationConfigurationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { @@ -144,7 +125,6 @@ func resourceOrganizationConfigurationRead(ctx context.Context, d *schema.Resour d.Set("auto_enable", output.AutoEnable) d.Set("auto_enable_standards", output.AutoEnableStandards) - if err := d.Set("organization_configuration", []interface{}{flattenOrganizationConfiguration(output.OrganizationConfiguration)}); err != nil { return sdkdiag.AppendErrorf(diags, "setting organization_configuration: %s", err) } @@ -152,6 +132,34 @@ func resourceOrganizationConfigurationRead(ctx context.Context, d *schema.Resour return diags } +// resourceOrganizationConfigurationDelete destroys the organizations configuration resource by updating it to a disabled configuration. +// If orgnanization configuration is of type central, then dependent resources (i.e finding_aggregator, delegated_admin) cannot be removed from AWS. +// Updating the organization configuration on destroy is necessary to allow dependent resources to be able to be cleaned up. +func resourceOrganizationConfigurationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) + + input := &securityhub.UpdateOrganizationConfigurationInput{ + AutoEnable: aws.Bool(false), + AutoEnableStandards: types.AutoEnableStandardsNone, + OrganizationConfiguration: &types.OrganizationConfiguration{ + ConfigurationType: types.OrganizationConfigurationConfigurationTypeLocal, + }, + } + + _, err := conn.UpdateOrganizationConfiguration(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting Security Hub Organization Configuration (%s): %s", d.Id(), err) + } + + if _, err := waitOrganizationConfigurationEnabled(ctx, conn, d.Timeout(schema.TimeoutDelete)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for Security Hub Organization Configuration (%s) delete: %s", d.Id(), err) + } + + return diags +} + func findOrganizationConfiguration(ctx context.Context, conn *securityhub.Client) retry.StateRefreshFunc { return func() (interface{}, string, error) { input := &securityhub.DescribeOrganizationConfigurationInput{} @@ -194,7 +202,7 @@ func findOrganizationConfiguration(ctx context.Context, conn *securityhub.Client func waitOrganizationConfigurationEnabled(ctx context.Context, conn *securityhub.Client, timeout time.Duration) (*securityhub.DescribeOrganizationConfigurationOutput, error) { stateConf := &retry.StateChangeConf{ Pending: enum.Slice(types.OrganizationConfigurationStatusPending), - Target: append(enum.Slice(types.OrganizationConfigurationStatusEnabled), ""), + Target: enum.Slice(types.OrganizationConfigurationStatusEnabled), Refresh: findOrganizationConfiguration(ctx, conn), Timeout: timeout, NotFoundChecks: 20, diff --git a/internal/service/securityhub/service_package_gen.go b/internal/service/securityhub/service_package_gen.go index 8e845f1a0e4..d2c461d6793 100644 --- a/internal/service/securityhub/service_package_gen.go +++ b/internal/service/securityhub/service_package_gen.go @@ -75,8 +75,9 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka TypeName: "aws_securityhub_organization_admin_account", }, { - Factory: ResourceOrganizationConfiguration, + Factory: resourceOrganizationConfiguration, TypeName: "aws_securityhub_organization_configuration", + Name: "Organization Configuration", }, { Factory: ResourceProductSubscription, diff --git a/website/docs/r/securityhub_organization_configuration.html.markdown b/website/docs/r/securityhub_organization_configuration.html.markdown index 7d339fd4dc9..be49b7a5699 100644 --- a/website/docs/r/securityhub_organization_configuration.html.markdown +++ b/website/docs/r/securityhub_organization_configuration.html.markdown @@ -83,6 +83,14 @@ This resource exports the following attributes in addition to the arguments abov * `id` - AWS Account ID. +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `180s`) +* `update` - (Default `180s`) +* `delete` - (Default `180s`) + ## Import In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import an existing Security Hub enabled account using the AWS account ID. For example: From c616b93eb066ab89c542821631be88a972641908 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 08:37:36 -0500 Subject: [PATCH 41/71] r/aws_securityhub_organization_configuration: Add 'statusOrganizationConfiguration'. --- internal/service/securityhub/exports_test.go | 1 + .../securityhub/organization_configuration.go | 72 ++++++++++--------- 2 files changed, 38 insertions(+), 35 deletions(-) diff --git a/internal/service/securityhub/exports_test.go b/internal/service/securityhub/exports_test.go index 939ff46fb5e..286fb32032b 100644 --- a/internal/service/securityhub/exports_test.go +++ b/internal/service/securityhub/exports_test.go @@ -13,4 +13,5 @@ var ( FindAutomationRuleByARN = findAutomationRuleByARN FindConfigurationPolicyAssociationByID = findConfigurationPolicyAssociationByID FindConfigurationPolicyByID = findConfigurationPolicyByID + FindOrganizationConfiguration = findOrganizationConfiguration ) diff --git a/internal/service/securityhub/organization_configuration.go b/internal/service/securityhub/organization_configuration.go index 3b301232722..d442f91bb14 100644 --- a/internal/service/securityhub/organization_configuration.go +++ b/internal/service/securityhub/organization_configuration.go @@ -5,7 +5,7 @@ package securityhub import ( "context" - "fmt" + "errors" "log" "time" @@ -111,7 +111,7 @@ func resourceOrganizationConfigurationRead(ctx context.Context, d *schema.Resour var diags diag.Diagnostics conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) - output, err := waitOrganizationConfigurationEnabled(ctx, conn, d.Timeout(schema.TimeoutRead)) + output, err := findOrganizationConfiguration(ctx, conn) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Security Hub Organization Configuration %s not found, removing from state", d.Id()) @@ -160,42 +160,42 @@ func resourceOrganizationConfigurationDelete(ctx context.Context, d *schema.Reso return diags } -func findOrganizationConfiguration(ctx context.Context, conn *securityhub.Client) retry.StateRefreshFunc { +func findOrganizationConfiguration(ctx context.Context, conn *securityhub.Client) (*securityhub.DescribeOrganizationConfigurationOutput, error) { + input := &securityhub.DescribeOrganizationConfigurationInput{} + + output, err := conn.DescribeOrganizationConfiguration(ctx, input) + + if tfawserr.ErrMessageContains(err, errCodeInvalidAccessException, "not subscribed to AWS Security Hub") { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.OrganizationConfiguration == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} + +func statusOrganizationConfiguration(ctx context.Context, conn *securityhub.Client) retry.StateRefreshFunc { return func() (interface{}, string, error) { - input := &securityhub.DescribeOrganizationConfigurationInput{} - output, err := conn.DescribeOrganizationConfiguration(ctx, input) - - if tfawserr.ErrCodeEquals(err, errCodeResourceNotFoundException) || tfawserr.ErrMessageContains(err, errCodeInvalidAccessException, "not subscribed to AWS Security Hub") { - return nil, "", &retry.NotFoundError{ - LastError: err, - LastRequest: input, - } + output, err := findOrganizationConfiguration(ctx, conn) + + if tfresource.NotFound(err) { + return nil, "", nil } if err != nil { return nil, "", err } - if output == nil || output.OrganizationConfiguration == nil { - return nil, "", tfresource.NewEmptyResultError(input) - } - - switch output.OrganizationConfiguration.Status { - case types.OrganizationConfigurationStatusPending: - return nil, "", nil - case types.OrganizationConfigurationStatusEnabled, "": - return output, string(output.OrganizationConfiguration.Status), nil - default: - var statusErr error - if msg := output.OrganizationConfiguration.StatusMessage; msg != nil && len(*msg) > 0 { - statusErr = fmt.Errorf("StatusMessage: %s", *msg) - } - return nil, "", &retry.UnexpectedStateError{ - LastError: statusErr, - State: string(output.OrganizationConfiguration.Status), - ExpectedState: enum.Slice(types.OrganizationConfigurationStatusEnabled, types.OrganizationConfigurationStatusPending), - } - } + return output, string(output.OrganizationConfiguration.Status), nil } } @@ -203,15 +203,17 @@ func waitOrganizationConfigurationEnabled(ctx context.Context, conn *securityhub stateConf := &retry.StateChangeConf{ Pending: enum.Slice(types.OrganizationConfigurationStatusPending), Target: enum.Slice(types.OrganizationConfigurationStatusEnabled), - Refresh: findOrganizationConfiguration(ctx, conn), + Refresh: statusOrganizationConfiguration(ctx, conn), Timeout: timeout, - NotFoundChecks: 20, ContinuousTargetOccurence: 2, } outputRaw, err := stateConf.WaitForStateContext(ctx) - if out, ok := outputRaw.(*securityhub.DescribeOrganizationConfigurationOutput); ok { - return out, err + + if output, ok := outputRaw.(*securityhub.DescribeOrganizationConfigurationOutput); ok { + tfresource.SetLastError(err, errors.New(aws.ToString(output.OrganizationConfiguration.StatusMessage))) + + return output, err } return nil, err From cb5af4c797d7ee579d1110afcbd10028c4b5fe45 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 08:39:19 -0500 Subject: [PATCH 42/71] r/aws_securityhub_organization_configuration: Tidy up acceptance tests. --- .../service/securityhub/organization_configuration_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/service/securityhub/organization_configuration_test.go b/internal/service/securityhub/organization_configuration_test.go index 6cef8ccf64e..27dcc1b511e 100644 --- a/internal/service/securityhub/organization_configuration_test.go +++ b/internal/service/securityhub/organization_configuration_test.go @@ -8,11 +8,11 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go-v2/service/securityhub" "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" + tfsecurityhub "github.com/hashicorp/terraform-provider-aws/internal/service/securityhub" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -138,7 +138,8 @@ func testAccCheckOrganizationConfigurationExists(ctx context.Context, n string) conn := acctest.Provider.Meta().(*conns.AWSClient).SecurityHubClient(ctx) - _, err := conn.DescribeOrganizationConfiguration(ctx, &securityhub.DescribeOrganizationConfigurationInput{}) + _, err := tfsecurityhub.FindOrganizationConfiguration(ctx, conn) + return err } } From 3c60106fe5ef1ebc96b44e73a0ce94d16a0dde82 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 08:49:04 -0500 Subject: [PATCH 43/71] r/aws_securityhub_account: Reduce visibility. --- internal/service/securityhub/account.go | 18 +++++++++++------- internal/service/securityhub/account_test.go | 12 ++++++++++-- internal/service/securityhub/exports_test.go | 3 +++ .../service/securityhub/service_package_gen.go | 3 ++- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/internal/service/securityhub/account.go b/internal/service/securityhub/account.go index f1700b84173..59b4aba7a97 100644 --- a/internal/service/securityhub/account.go +++ b/internal/service/securityhub/account.go @@ -22,8 +22,8 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -// @SDKResource("aws_securityhub_account") -func ResourceAccount() *schema.Resource { +// @SDKResource("aws_securityhub_account", name="Account") +func resourceAccount() *schema.Resource { resourceV0 := &schema.Resource{Schema: map[string]*schema.Schema{}} return &schema.Resource{ @@ -108,11 +108,12 @@ func resourceAccountCreate(ctx context.Context, d *schema.ResourceData, meta int return sdkdiag.AppendErrorf(diags, "updating Security Hub Account (%s): %s", d.Id(), err) } + arn := accountHubARN(meta.(*conns.AWSClient)) const ( timeout = 1 * time.Minute ) _, err = tfresource.RetryUntilEqual(ctx, timeout, autoEnableControls, func() (bool, error) { - output, err := FindHub(ctx, meta.(*conns.AWSClient)) + output, err := findHubByARN(ctx, conn, arn) if err != nil { return false, err @@ -130,8 +131,10 @@ func resourceAccountCreate(ctx context.Context, d *schema.ResourceData, meta int func resourceAccountRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) - output, err := FindHub(ctx, meta.(*conns.AWSClient)) + arn := accountHubARN(meta.(*conns.AWSClient)) + output, err := findHubByARN(ctx, conn, arn) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Security Hub Account %s not found, removing from state", d.Id()) @@ -193,11 +196,12 @@ func resourceAccountDelete(ctx context.Context, d *schema.ResourceData, meta int return diags } -func FindHub(ctx context.Context, meta *conns.AWSClient) (*securityhub.DescribeHubOutput, error) { +func findHubByARN(ctx context.Context, conn *securityhub.Client, arn string) (*securityhub.DescribeHubOutput, error) { input := &securityhub.DescribeHubInput{ - HubArn: aws.String(accountHubARN(meta)), + HubArn: aws.String(arn), } - return findHub(ctx, meta.SecurityHubClient(ctx), input) + + return findHub(ctx, conn, input) } func findHub(ctx context.Context, conn *securityhub.Client, input *securityhub.DescribeHubInput) (*securityhub.DescribeHubOutput, error) { diff --git a/internal/service/securityhub/account_test.go b/internal/service/securityhub/account_test.go index fdc66a69999..0390518d692 100644 --- a/internal/service/securityhub/account_test.go +++ b/internal/service/securityhub/account_test.go @@ -206,7 +206,11 @@ func testAccCheckAccountExists(ctx context.Context, n string) resource.TestCheck return fmt.Errorf("Not found: %s", n) } - _, err := tfsecurityhub.FindHub(ctx, acctest.Provider.Meta().(*conns.AWSClient)) + awsClient := acctest.Provider.Meta().(*conns.AWSClient) + conn := awsClient.SecurityHubClient(ctx) + + arn := tfsecurityhub.AccountHubARN(awsClient) + _, err := tfsecurityhub.FindHubByARN(ctx, conn, arn) return err } @@ -214,12 +218,16 @@ func testAccCheckAccountExists(ctx context.Context, n string) resource.TestCheck func testAccCheckAccountDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { + awsClient := acctest.Provider.Meta().(*conns.AWSClient) + conn := awsClient.SecurityHubClient(ctx) + for _, rs := range s.RootModule().Resources { if rs.Type != "aws_securityhub_account" { continue } - _, err := tfsecurityhub.FindHub(ctx, acctest.Provider.Meta().(*conns.AWSClient)) + arn := tfsecurityhub.AccountHubARN(awsClient) + _, err := tfsecurityhub.FindHubByARN(ctx, conn, arn) if tfresource.NotFound(err) { continue diff --git a/internal/service/securityhub/exports_test.go b/internal/service/securityhub/exports_test.go index 286fb32032b..6a4cd0dadf8 100644 --- a/internal/service/securityhub/exports_test.go +++ b/internal/service/securityhub/exports_test.go @@ -5,13 +5,16 @@ package securityhub // Exports for use in tests only. var ( + ResourceAccount = resourceAccount ResourceAutomationRule = newAutomationRuleResource ResourceConfigurationPolicy = resourceConfigurationPolicy ResourceConfigurationPolicyAssociation = resourceConfigurationPolicyAssociation ResourceOrganizationConfiguration = resourceOrganizationConfiguration + AccountHubARN = accountHubARN FindAutomationRuleByARN = findAutomationRuleByARN FindConfigurationPolicyAssociationByID = findConfigurationPolicyAssociationByID FindConfigurationPolicyByID = findConfigurationPolicyByID + FindHubByARN = findHubByARN FindOrganizationConfiguration = findOrganizationConfiguration ) diff --git a/internal/service/securityhub/service_package_gen.go b/internal/service/securityhub/service_package_gen.go index d2c461d6793..871a7f95303 100644 --- a/internal/service/securityhub/service_package_gen.go +++ b/internal/service/securityhub/service_package_gen.go @@ -37,8 +37,9 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePackageSDKResource { return []*types.ServicePackageSDKResource{ { - Factory: ResourceAccount, + Factory: resourceAccount, TypeName: "aws_securityhub_account", + Name: "Account", }, { Factory: ResourceActionTarget, From a160d8e2a79c6dbac28d3b7c9e6aa0c6ea634f8c Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 08:56:54 -0500 Subject: [PATCH 44/71] r/aws_securityhub_action_target: Reduce visibility. --- internal/service/securityhub/action_target.go | 45 +++++++++++++------ internal/service/securityhub/exports_test.go | 2 + .../securityhub/service_package_gen.go | 3 +- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/internal/service/securityhub/action_target.go b/internal/service/securityhub/action_target.go index 3743d983b7c..55e4bac5b57 100644 --- a/internal/service/securityhub/action_target.go +++ b/internal/service/securityhub/action_target.go @@ -23,8 +23,8 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -// @SDKResource("aws_securityhub_action_target") -func ResourceActionTarget() *schema.Resource { +// @SDKResource("aws_securityhub_action_target", name="Action Target") +func resourceActionTarget() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceActionTargetCreate, ReadWithoutTimeout: resourceActionTargetRead, @@ -94,7 +94,7 @@ func resourceActionTargetRead(ctx context.Context, d *schema.ResourceData, meta return sdkdiag.AppendFromErr(diags, err) } - output, err := FindActionTargetByARN(ctx, conn, d.Id()) + output, err := findActionTargetByARN(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Security Hub Action Target %s not found, removing from state", d.Id()) @@ -161,27 +161,44 @@ func actionTargetParseID(arn string) (string, error) { return parts[2], nil } -func FindActionTargetByARN(ctx context.Context, conn *securityhub.Client, arn string) (*types.ActionTarget, error) { +func findActionTargetByARN(ctx context.Context, conn *securityhub.Client, arn string) (*types.ActionTarget, error) { input := &securityhub.DescribeActionTargetsInput{ ActionTargetArns: []string{arn}, } - output, err := conn.DescribeActionTargets(ctx, input) + return findActionTarget(ctx, conn, input) +} - if tfawserr.ErrCodeEquals(err, errCodeResourceNotFoundException) || tfawserr.ErrMessageContains(err, errCodeInvalidAccessException, "not subscribed to AWS Security Hub") { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: input, - } - } +func findActionTarget(ctx context.Context, conn *securityhub.Client, input *securityhub.DescribeActionTargetsInput) (*types.ActionTarget, error) { + output, err := findActionTargets(ctx, conn, input) if err != nil { return nil, err } - if output == nil { - return nil, tfresource.NewEmptyResultError(input) + return tfresource.AssertSingleValueResult(output) +} + +func findActionTargets(ctx context.Context, conn *securityhub.Client, input *securityhub.DescribeActionTargetsInput) ([]types.ActionTarget, error) { + var output []types.ActionTarget + + pages := securityhub.NewDescribeActionTargetsPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if tfawserr.ErrCodeEquals(err, errCodeResourceNotFoundException) || tfawserr.ErrMessageContains(err, errCodeInvalidAccessException, "not subscribed to AWS Security Hub") { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + output = append(output, page.ActionTargets...) } - return tfresource.AssertSingleValueResult(output.ActionTargets) + return output, nil } diff --git a/internal/service/securityhub/exports_test.go b/internal/service/securityhub/exports_test.go index 6a4cd0dadf8..074ad635eb5 100644 --- a/internal/service/securityhub/exports_test.go +++ b/internal/service/securityhub/exports_test.go @@ -6,12 +6,14 @@ package securityhub // Exports for use in tests only. var ( ResourceAccount = resourceAccount + ResourceActionTarget = resourceActionTarget ResourceAutomationRule = newAutomationRuleResource ResourceConfigurationPolicy = resourceConfigurationPolicy ResourceConfigurationPolicyAssociation = resourceConfigurationPolicyAssociation ResourceOrganizationConfiguration = resourceOrganizationConfiguration AccountHubARN = accountHubARN + FindActionTargetByARN = findActionTargetByARN FindAutomationRuleByARN = findAutomationRuleByARN FindConfigurationPolicyAssociationByID = findConfigurationPolicyAssociationByID FindConfigurationPolicyByID = findConfigurationPolicyByID diff --git a/internal/service/securityhub/service_package_gen.go b/internal/service/securityhub/service_package_gen.go index 871a7f95303..b059e087a4a 100644 --- a/internal/service/securityhub/service_package_gen.go +++ b/internal/service/securityhub/service_package_gen.go @@ -42,8 +42,9 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Name: "Account", }, { - Factory: ResourceActionTarget, + Factory: resourceActionTarget, TypeName: "aws_securityhub_action_target", + Name: "Action Target", }, { Factory: resourceConfigurationPolicy, From 9165cee179ab0bb5600a0a018eaed511ed903984 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 09:00:04 -0500 Subject: [PATCH 45/71] r/aws_securityhub_finding_aggregator: Reduce visibility. --- internal/service/securityhub/exports_test.go | 2 ++ internal/service/securityhub/finding_aggregator.go | 12 ++++++++---- internal/service/securityhub/service_package_gen.go | 3 ++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/internal/service/securityhub/exports_test.go b/internal/service/securityhub/exports_test.go index 074ad635eb5..bd885d4c999 100644 --- a/internal/service/securityhub/exports_test.go +++ b/internal/service/securityhub/exports_test.go @@ -10,6 +10,7 @@ var ( ResourceAutomationRule = newAutomationRuleResource ResourceConfigurationPolicy = resourceConfigurationPolicy ResourceConfigurationPolicyAssociation = resourceConfigurationPolicyAssociation + ResourceFindingAggregator = resourceFindingAggregator ResourceOrganizationConfiguration = resourceOrganizationConfiguration AccountHubARN = accountHubARN @@ -17,6 +18,7 @@ var ( FindAutomationRuleByARN = findAutomationRuleByARN FindConfigurationPolicyAssociationByID = findConfigurationPolicyAssociationByID FindConfigurationPolicyByID = findConfigurationPolicyByID + FindFindingAggregatorByARN = findFindingAggregatorByARN FindHubByARN = findHubByARN FindOrganizationConfiguration = findOrganizationConfiguration ) diff --git a/internal/service/securityhub/finding_aggregator.go b/internal/service/securityhub/finding_aggregator.go index b32abc85d1a..7289e3c4341 100644 --- a/internal/service/securityhub/finding_aggregator.go +++ b/internal/service/securityhub/finding_aggregator.go @@ -34,8 +34,8 @@ func linkingMode_Values() []string { } } -// @SDKResource("aws_securityhub_finding_aggregator") -func ResourceFindingAggregator() *schema.Resource { +// @SDKResource("aws_securityhub_finding_aggregator", name="Finding Aggregator") +func resourceFindingAggregator() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceFindingAggregatorCreate, ReadWithoutTimeout: resourceFindingAggregatorRead, @@ -92,7 +92,7 @@ func resourceFindingAggregatorRead(ctx context.Context, d *schema.ResourceData, var diags diag.Diagnostics conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) - output, err := FindFindingAggregatorByARN(ctx, conn, d.Id()) + output, err := findFindingAggregatorByARN(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Security Hub Finding Aggregator (%s) not found, removing from state", d.Id()) @@ -155,11 +155,15 @@ func resourceFindingAggregatorDelete(ctx context.Context, d *schema.ResourceData return diags } -func FindFindingAggregatorByARN(ctx context.Context, conn *securityhub.Client, arn string) (*securityhub.GetFindingAggregatorOutput, error) { +func findFindingAggregatorByARN(ctx context.Context, conn *securityhub.Client, arn string) (*securityhub.GetFindingAggregatorOutput, error) { input := &securityhub.GetFindingAggregatorInput{ FindingAggregatorArn: aws.String(arn), } + return findFindingAggregator(ctx, conn, input) +} + +func findFindingAggregator(ctx context.Context, conn *securityhub.Client, input *securityhub.GetFindingAggregatorInput) (*securityhub.GetFindingAggregatorOutput, error) { output, err := conn.GetFindingAggregator(ctx, input) if tfawserr.ErrCodeEquals(err, errCodeResourceNotFoundException) || tfawserr.ErrMessageContains(err, errCodeInvalidAccessException, "not subscribed to AWS Security Hub") { diff --git a/internal/service/securityhub/service_package_gen.go b/internal/service/securityhub/service_package_gen.go index b059e087a4a..aa3af6c06b4 100644 --- a/internal/service/securityhub/service_package_gen.go +++ b/internal/service/securityhub/service_package_gen.go @@ -57,8 +57,9 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Name: "Configuration Policy Association", }, { - Factory: ResourceFindingAggregator, + Factory: resourceFindingAggregator, TypeName: "aws_securityhub_finding_aggregator", + Name: "Finding Aggregator", }, { Factory: ResourceInsight, From c385a79656facfe6adff55b4d20f5026ba5cf5b9 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 09:04:36 -0500 Subject: [PATCH 46/71] r/aws_securityhub_insight: Reduce visibility. --- internal/service/securityhub/exports_test.go | 2 + internal/service/securityhub/insight.go | 45 +++++++++++++------ .../securityhub/service_package_gen.go | 3 +- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/internal/service/securityhub/exports_test.go b/internal/service/securityhub/exports_test.go index bd885d4c999..d5124f1cea5 100644 --- a/internal/service/securityhub/exports_test.go +++ b/internal/service/securityhub/exports_test.go @@ -11,6 +11,7 @@ var ( ResourceConfigurationPolicy = resourceConfigurationPolicy ResourceConfigurationPolicyAssociation = resourceConfigurationPolicyAssociation ResourceFindingAggregator = resourceFindingAggregator + ResourceInsight = resourceInsight ResourceOrganizationConfiguration = resourceOrganizationConfiguration AccountHubARN = accountHubARN @@ -20,5 +21,6 @@ var ( FindConfigurationPolicyByID = findConfigurationPolicyByID FindFindingAggregatorByARN = findFindingAggregatorByARN FindHubByARN = findHubByARN + FindInsightByARN = findInsightByARN FindOrganizationConfiguration = findOrganizationConfiguration ) diff --git a/internal/service/securityhub/insight.go b/internal/service/securityhub/insight.go index 86f70e9cdf2..9b27403c461 100644 --- a/internal/service/securityhub/insight.go +++ b/internal/service/securityhub/insight.go @@ -21,8 +21,8 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/verify" ) -// @SDKResource("aws_securityhub_insight") -func ResourceInsight() *schema.Resource { +// @SDKResource("aws_securityhub_insight", name="Insight") +func resourceInsight() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceInsightCreate, ReadWithoutTimeout: resourceInsightRead, @@ -174,7 +174,7 @@ func resourceInsightCreate(ctx context.Context, d *schema.ResourceData, meta int func resourceInsightRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) - insight, err := FindInsightByARN(ctx, conn, d.Id()) + insight, err := findInsightByARN(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Security Hub Insight (%s) not found, removing from state", d.Id()) @@ -243,29 +243,46 @@ func resourceInsightDelete(ctx context.Context, d *schema.ResourceData, meta int return nil } -func FindInsightByARN(ctx context.Context, conn *securityhub.Client, arn string) (*types.Insight, error) { +func findInsightByARN(ctx context.Context, conn *securityhub.Client, arn string) (*types.Insight, error) { input := &securityhub.GetInsightsInput{ InsightArns: []string{arn}, } - output, err := conn.GetInsights(ctx, input) + return findInsight(ctx, conn, input) +} - if tfawserr.ErrCodeEquals(err, errCodeResourceNotFoundException) || tfawserr.ErrMessageContains(err, errCodeInvalidAccessException, "not subscribed to AWS Security Hub") { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: input, - } - } +func findInsight(ctx context.Context, conn *securityhub.Client, input *securityhub.GetInsightsInput) (*types.Insight, error) { + output, err := findInsights(ctx, conn, input) if err != nil { return nil, err } - if output == nil { - return nil, tfresource.NewEmptyResultError(input) + return tfresource.AssertSingleValueResult(output) +} + +func findInsights(ctx context.Context, conn *securityhub.Client, input *securityhub.GetInsightsInput) ([]types.Insight, error) { + var output []types.Insight + + pages := securityhub.NewGetInsightsPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if tfawserr.ErrCodeEquals(err, errCodeResourceNotFoundException) || tfawserr.ErrMessageContains(err, errCodeInvalidAccessException, "not subscribed to AWS Security Hub") { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + output = append(output, page.Insights...) } - return tfresource.AssertSingleValueResult(output.Insights) + return output, nil } func dateFilterSchema() *schema.Schema { diff --git a/internal/service/securityhub/service_package_gen.go b/internal/service/securityhub/service_package_gen.go index aa3af6c06b4..16fd87f474e 100644 --- a/internal/service/securityhub/service_package_gen.go +++ b/internal/service/securityhub/service_package_gen.go @@ -62,8 +62,9 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Name: "Finding Aggregator", }, { - Factory: ResourceInsight, + Factory: resourceInsight, TypeName: "aws_securityhub_insight", + Name: "Insight", }, { Factory: ResourceInviteAccepter, From 494d599e665db7077e92449ae47fec4094abf150 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 09:08:05 -0500 Subject: [PATCH 47/71] r/aws_securityhub_invite_accepter: Reduce visibility. --- internal/service/securityhub/exports_test.go | 2 ++ internal/service/securityhub/invite_accepter.go | 17 ++++++++--------- .../service/securityhub/service_package_gen.go | 3 ++- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/internal/service/securityhub/exports_test.go b/internal/service/securityhub/exports_test.go index d5124f1cea5..76119c914b5 100644 --- a/internal/service/securityhub/exports_test.go +++ b/internal/service/securityhub/exports_test.go @@ -12,6 +12,7 @@ var ( ResourceConfigurationPolicyAssociation = resourceConfigurationPolicyAssociation ResourceFindingAggregator = resourceFindingAggregator ResourceInsight = resourceInsight + ResourceInviteAccepter = resourceInviteAccepter ResourceOrganizationConfiguration = resourceOrganizationConfiguration AccountHubARN = accountHubARN @@ -22,5 +23,6 @@ var ( FindFindingAggregatorByARN = findFindingAggregatorByARN FindHubByARN = findHubByARN FindInsightByARN = findInsightByARN + FindMasterAccount = findMasterAccount FindOrganizationConfiguration = findOrganizationConfiguration ) diff --git a/internal/service/securityhub/invite_accepter.go b/internal/service/securityhub/invite_accepter.go index 45966e85fe0..2a6b7ebd40e 100644 --- a/internal/service/securityhub/invite_accepter.go +++ b/internal/service/securityhub/invite_accepter.go @@ -20,8 +20,8 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -// @SDKResource("aws_securityhub_invite_accepter") -func ResourceInviteAccepter() *schema.Resource { +// @SDKResource("aws_securityhub_invite_accepter", name="Invite Accepter") +func resourceInviteAccepter() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceInviteAccepterCreate, ReadWithoutTimeout: resourceInviteAccepterRead, @@ -79,7 +79,7 @@ func resourceInviteAccepterRead(ctx context.Context, d *schema.ResourceData, met var diags diag.Diagnostics conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) - master, err := FindMasterAccount(ctx, conn) + master, err := findMasterAccount(ctx, conn) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Security Hub Master Account (%s) not found, removing from state", d.Id()) @@ -115,7 +115,7 @@ func resourceInviteAccepterDelete(ctx context.Context, d *schema.ResourceData, m return diags } -func FindMasterAccount(ctx context.Context, conn *securityhub.Client) (*types.Invitation, error) { +func findMasterAccount(ctx context.Context, conn *securityhub.Client) (*types.Invitation, error) { input := &securityhub.GetMasterAccountInput{} output, err := conn.GetMasterAccount(ctx, input) @@ -145,11 +145,11 @@ func findInvitation(ctx context.Context, conn *securityhub.Client, input *securi return nil, err } - return tfresource.AssertSinglePtrResult(output) + return tfresource.AssertSingleValueResult(output) } -func findInvitations(ctx context.Context, conn *securityhub.Client, input *securityhub.ListInvitationsInput, filter tfslices.Predicate[*types.Invitation]) ([]*types.Invitation, error) { - var output []*types.Invitation +func findInvitations(ctx context.Context, conn *securityhub.Client, input *securityhub.ListInvitationsInput, filter tfslices.Predicate[*types.Invitation]) ([]types.Invitation, error) { + var output []types.Invitation pages := securityhub.NewListInvitationsPaginator(conn, input) for pages.HasMorePages() { @@ -167,8 +167,7 @@ func findInvitations(ctx context.Context, conn *securityhub.Client, input *secur } for _, v := range page.Invitations { - v := v - if v := &v; filter(v) { + if filter(&v) { output = append(output, v) } } diff --git a/internal/service/securityhub/service_package_gen.go b/internal/service/securityhub/service_package_gen.go index 16fd87f474e..37289e57d7d 100644 --- a/internal/service/securityhub/service_package_gen.go +++ b/internal/service/securityhub/service_package_gen.go @@ -67,8 +67,9 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Name: "Insight", }, { - Factory: ResourceInviteAccepter, + Factory: resourceInviteAccepter, TypeName: "aws_securityhub_invite_accepter", + Name: "Invite Accepter", }, { Factory: ResourceMember, From 28ec78de2c2736c69f52105739a7049b5b432830 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 09:13:07 -0500 Subject: [PATCH 48/71] r/aws_securityhub_member: Reduce visibility. --- internal/service/securityhub/exports_test.go | 2 ++ internal/service/securityhub/member.go | 24 +++++++++++++++---- .../securityhub/service_package_gen.go | 3 ++- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/internal/service/securityhub/exports_test.go b/internal/service/securityhub/exports_test.go index 76119c914b5..c88af13c935 100644 --- a/internal/service/securityhub/exports_test.go +++ b/internal/service/securityhub/exports_test.go @@ -13,6 +13,7 @@ var ( ResourceFindingAggregator = resourceFindingAggregator ResourceInsight = resourceInsight ResourceInviteAccepter = resourceInviteAccepter + ResourceMember = resourceMember ResourceOrganizationConfiguration = resourceOrganizationConfiguration AccountHubARN = accountHubARN @@ -24,5 +25,6 @@ var ( FindHubByARN = findHubByARN FindInsightByARN = findInsightByARN FindMasterAccount = findMasterAccount + FindMemberByAccountID = findMemberByAccountID FindOrganizationConfiguration = findOrganizationConfiguration ) diff --git a/internal/service/securityhub/member.go b/internal/service/securityhub/member.go index 7d7ad30a2ef..8f3a6cad5d2 100644 --- a/internal/service/securityhub/member.go +++ b/internal/service/securityhub/member.go @@ -22,8 +22,8 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/verify" ) -// @SDKResource("aws_securityhub_member") -func ResourceMember() *schema.Resource { +// @SDKResource("aws_securityhub_member", name="Member") +func resourceMember() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceMemberCreate, ReadWithoutTimeout: resourceMemberRead, @@ -112,7 +112,7 @@ func resourceMemberRead(ctx context.Context, d *schema.ResourceData, meta interf var diags diag.Diagnostics conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) - member, err := FindMemberByAccountID(ctx, conn, d.Id()) + member, err := findMemberByAccountID(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Security Hub Member (%s) not found, removing from state", d.Id()) @@ -179,11 +179,25 @@ func resourceMemberDelete(ctx context.Context, d *schema.ResourceData, meta inte return diags } -func FindMemberByAccountID(ctx context.Context, conn *securityhub.Client, accountID string) (*types.Member, error) { +func findMemberByAccountID(ctx context.Context, conn *securityhub.Client, accountID string) (*types.Member, error) { input := &securityhub.GetMembersInput{ AccountIds: []string{accountID}, } + return findMember(ctx, conn, input) +} + +func findMember(ctx context.Context, conn *securityhub.Client, input *securityhub.GetMembersInput) (*types.Member, error) { + output, err := findMembers(ctx, conn, input) + + if err != nil { + return nil, err + } + + return tfresource.AssertSingleValueResult(output) +} + +func findMembers(ctx context.Context, conn *securityhub.Client, input *securityhub.GetMembersInput) ([]types.Member, error) { output, err := conn.GetMembers(ctx, input) if tfawserr.ErrCodeEquals(err, errCodeResourceNotFoundException) || tfawserr.ErrMessageContains(err, errCodeAccessDeniedException, "The request is rejected since no such resource found") || tfawserr.ErrMessageContains(err, errCodeBadRequestException, "The request is rejected since no such resource found") { @@ -201,7 +215,7 @@ func FindMemberByAccountID(ctx context.Context, conn *securityhub.Client, accoun return nil, tfresource.NewEmptyResultError(input) } - return tfresource.AssertSingleValueResult(output.Members) + return output.Members, nil } func unprocessedAccountError(apiObject types.Result) error { diff --git a/internal/service/securityhub/service_package_gen.go b/internal/service/securityhub/service_package_gen.go index 37289e57d7d..83d7b441f5a 100644 --- a/internal/service/securityhub/service_package_gen.go +++ b/internal/service/securityhub/service_package_gen.go @@ -72,8 +72,9 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Name: "Invite Accepter", }, { - Factory: ResourceMember, + Factory: resourceMember, TypeName: "aws_securityhub_member", + Name: "Member", }, { Factory: ResourceOrganizationAdminAccount, From 7ef7998c0341150959314358719572fb5d7bea28 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 09:15:36 -0500 Subject: [PATCH 49/71] r/aws_securityhub_standards_subscription: Reduce visibility. --- internal/service/securityhub/exports_test.go | 31 ++++++++++++------- .../securityhub/organization_admin_account.go | 19 ++++++------ .../securityhub/product_subscription.go | 8 ++--- .../securityhub/service_package_gen.go | 12 ++++--- .../service/securityhub/standards_control.go | 23 +++++++------- .../securityhub/standards_subscription.go | 12 +++---- 6 files changed, 58 insertions(+), 47 deletions(-) diff --git a/internal/service/securityhub/exports_test.go b/internal/service/securityhub/exports_test.go index c88af13c935..a4965c0daf3 100644 --- a/internal/service/securityhub/exports_test.go +++ b/internal/service/securityhub/exports_test.go @@ -14,17 +14,26 @@ var ( ResourceInsight = resourceInsight ResourceInviteAccepter = resourceInviteAccepter ResourceMember = resourceMember + ResourceOrganizationAdminAccount = resourceOrganizationAdminAccount ResourceOrganizationConfiguration = resourceOrganizationConfiguration + ResourceProductSubscription = resourceProductSubscription + ResourceStandardsControl = resourceStandardsControl + ResourceStandardsSubscription = resourceStandardsSubscription - AccountHubARN = accountHubARN - FindActionTargetByARN = findActionTargetByARN - FindAutomationRuleByARN = findAutomationRuleByARN - FindConfigurationPolicyAssociationByID = findConfigurationPolicyAssociationByID - FindConfigurationPolicyByID = findConfigurationPolicyByID - FindFindingAggregatorByARN = findFindingAggregatorByARN - FindHubByARN = findHubByARN - FindInsightByARN = findInsightByARN - FindMasterAccount = findMasterAccount - FindMemberByAccountID = findMemberByAccountID - FindOrganizationConfiguration = findOrganizationConfiguration + AccountHubARN = accountHubARN + FindActionTargetByARN = findActionTargetByARN + FindAdminAccountByID = findAdminAccountByID + FindAutomationRuleByARN = findAutomationRuleByARN + FindConfigurationPolicyAssociationByID = findConfigurationPolicyAssociationByID + FindConfigurationPolicyByID = findConfigurationPolicyByID + FindFindingAggregatorByARN = findFindingAggregatorByARN + FindHubByARN = findHubByARN + FindInsightByARN = findInsightByARN + FindMasterAccount = findMasterAccount + FindMemberByAccountID = findMemberByAccountID + FindOrganizationConfiguration = findOrganizationConfiguration + FindProductSubscriptionByARN = findProductSubscriptionByARN + FindStandardsControlByTwoPartKey = findStandardsControlByTwoPartKey + FindStandardsSubscriptionByARN = findStandardsSubscriptionByARN + StandardsControlARNToStandardsSubscriptionARN = standardsControlARNToStandardsSubscriptionARN ) diff --git a/internal/service/securityhub/organization_admin_account.go b/internal/service/securityhub/organization_admin_account.go index 583d07c8be3..61c603e2aa8 100644 --- a/internal/service/securityhub/organization_admin_account.go +++ b/internal/service/securityhub/organization_admin_account.go @@ -23,8 +23,8 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/verify" ) -// @SDKResource("aws_securityhub_organization_admin_account") -func ResourceOrganizationAdminAccount() *schema.Resource { +// @SDKResource("aws_securityhub_organization_admin_account", name="Organization Admin Account") +func resourceOrganizationAdminAccount() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceOrganizationAdminAccountCreate, ReadWithoutTimeout: resourceOrganizationAdminAccountRead, @@ -78,7 +78,7 @@ func resourceOrganizationAdminAccountRead(ctx context.Context, d *schema.Resourc var diags diag.Diagnostics conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) - adminAccount, err := FindAdminAccountByID(ctx, conn, d.Id()) + adminAccount, err := findAdminAccountByID(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Security Hub Organization Admin Account (%s) not found, removing from state", d.Id()) @@ -120,7 +120,7 @@ func resourceOrganizationAdminAccountDelete(ctx context.Context, d *schema.Resou return diags } -func FindAdminAccountByID(ctx context.Context, conn *securityhub.Client, adminAccountID string) (*types.AdminAccount, error) { +func findAdminAccountByID(ctx context.Context, conn *securityhub.Client, adminAccountID string) (*types.AdminAccount, error) { input := &securityhub.ListOrganizationAdminAccountsInput{} return findAdminAccount(ctx, conn, input, func(v *types.AdminAccount) bool { @@ -135,11 +135,11 @@ func findAdminAccount(ctx context.Context, conn *securityhub.Client, input *secu return nil, err } - return tfresource.AssertSinglePtrResult(output) + return tfresource.AssertSingleValueResult(output) } -func findAdminAccounts(ctx context.Context, conn *securityhub.Client, input *securityhub.ListOrganizationAdminAccountsInput, filter tfslices.Predicate[*types.AdminAccount]) ([]*types.AdminAccount, error) { - var output []*types.AdminAccount +func findAdminAccounts(ctx context.Context, conn *securityhub.Client, input *securityhub.ListOrganizationAdminAccountsInput, filter tfslices.Predicate[*types.AdminAccount]) ([]types.AdminAccount, error) { + var output []types.AdminAccount pages := securityhub.NewListOrganizationAdminAccountsPaginator(conn, input) for pages.HasMorePages() { @@ -157,8 +157,7 @@ func findAdminAccounts(ctx context.Context, conn *securityhub.Client, input *sec } for _, v := range page.AdminAccounts { - v := v - if v := &v; filter(v) { + if filter(&v) { output = append(output, v) } } @@ -169,7 +168,7 @@ func findAdminAccounts(ctx context.Context, conn *securityhub.Client, input *sec func statusAdminAccount(ctx context.Context, conn *securityhub.Client, adminAccountID string) retry.StateRefreshFunc { return func() (interface{}, string, error) { - output, err := FindAdminAccountByID(ctx, conn, adminAccountID) + output, err := findAdminAccountByID(ctx, conn, adminAccountID) if tfresource.NotFound(err) { return nil, "", nil diff --git a/internal/service/securityhub/product_subscription.go b/internal/service/securityhub/product_subscription.go index a671c53d4c2..9d63d58f36c 100644 --- a/internal/service/securityhub/product_subscription.go +++ b/internal/service/securityhub/product_subscription.go @@ -22,8 +22,8 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/verify" ) -// @SDKResource("aws_securityhub_product_subscription") -func ResourceProductSubscription() *schema.Resource { +// @SDKResource("aws_securityhub_product_subscription", name="Product Subscription") +func resourceProductSubscription() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceProductSubscriptionCreate, ReadWithoutTimeout: resourceProductSubscriptionRead, @@ -83,7 +83,7 @@ func resourceProductSubscriptionRead(ctx context.Context, d *schema.ResourceData productARN, productSubscriptionARN := parts[0], parts[1] - _, err = FindProductSubscriptionByARN(ctx, conn, productSubscriptionARN) + _, err = findProductSubscriptionByARN(ctx, conn, productSubscriptionARN) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Security Hub Product Subscription (%s) not found, removing from state", d.Id()) @@ -128,7 +128,7 @@ func resourceProductSubscriptionDelete(ctx context.Context, d *schema.ResourceDa return diags } -func FindProductSubscriptionByARN(ctx context.Context, conn *securityhub.Client, productSubscriptionARN string) (*string, error) { +func findProductSubscriptionByARN(ctx context.Context, conn *securityhub.Client, productSubscriptionARN string) (*string, error) { input := &securityhub.ListEnabledProductsForImportInput{} return findProductSubscription(ctx, conn, input, func(v string) bool { diff --git a/internal/service/securityhub/service_package_gen.go b/internal/service/securityhub/service_package_gen.go index 83d7b441f5a..21b44bd2eba 100644 --- a/internal/service/securityhub/service_package_gen.go +++ b/internal/service/securityhub/service_package_gen.go @@ -77,8 +77,9 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Name: "Member", }, { - Factory: ResourceOrganizationAdminAccount, + Factory: resourceOrganizationAdminAccount, TypeName: "aws_securityhub_organization_admin_account", + Name: "Organization Admin Account", }, { Factory: resourceOrganizationConfiguration, @@ -86,16 +87,19 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Name: "Organization Configuration", }, { - Factory: ResourceProductSubscription, + Factory: resourceProductSubscription, TypeName: "aws_securityhub_product_subscription", + Name: "Product Subscription", }, { - Factory: ResourceStandardsControl, + Factory: resourceStandardsControl, TypeName: "aws_securityhub_standards_control", + Name: "Standards Control", }, { - Factory: ResourceStandardsSubscription, + Factory: resourceStandardsSubscription, TypeName: "aws_securityhub_standards_subscription", + Name: "Standards Subscription", }, } } diff --git a/internal/service/securityhub/standards_control.go b/internal/service/securityhub/standards_control.go index 7bd50cf9782..d8950e6c8b7 100644 --- a/internal/service/securityhub/standards_control.go +++ b/internal/service/securityhub/standards_control.go @@ -25,8 +25,8 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/verify" ) -// @SDKResource("aws_securityhub_standards_control") -func ResourceStandardsControl() *schema.Resource { +// @SDKResource("aws_securityhub_standards_control", name="Standards Control") +func resourceStandardsControl() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceStandardsControlPut, ReadWithoutTimeout: resourceStandardsControlRead, @@ -109,12 +109,12 @@ func resourceStandardsControlPut(ctx context.Context, d *schema.ResourceData, me func resourceStandardsControlRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) - standardsSubscriptionARN, err := StandardsControlARNToStandardsSubscriptionARN(d.Id()) + standardsSubscriptionARN, err := standardsControlARNToStandardsSubscriptionARN(d.Id()) if err != nil { return diag.FromErr(err) } - control, err := FindStandardsControlByTwoPartKey(ctx, conn, standardsSubscriptionARN, d.Id()) + control, err := findStandardsControlByTwoPartKey(ctx, conn, standardsSubscriptionARN, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Security Hub Standards Control (%s) not found, removing from state", d.Id()) @@ -140,8 +140,8 @@ func resourceStandardsControlRead(ctx context.Context, d *schema.ResourceData, m return nil } -// StandardsControlARNToStandardsSubscriptionARN converts a security standard control ARN to a subscription ARN. -func StandardsControlARNToStandardsSubscriptionARN(inputARN string) (string, error) { +// standardsControlARNToStandardsSubscriptionARN converts a security standard control ARN to a subscription ARN. +func standardsControlARNToStandardsSubscriptionARN(inputARN string) (string, error) { const ( resourceSeparator = "/" service = "securityhub" @@ -173,7 +173,7 @@ func StandardsControlARNToStandardsSubscriptionARN(inputARN string) (string, err return outputARN, nil } -func FindStandardsControlByTwoPartKey(ctx context.Context, conn *securityhub.Client, standardsSubscriptionARN, standardsControlARN string) (*types.StandardsControl, error) { +func findStandardsControlByTwoPartKey(ctx context.Context, conn *securityhub.Client, standardsSubscriptionARN, standardsControlARN string) (*types.StandardsControl, error) { input := &securityhub.DescribeStandardsControlsInput{ StandardsSubscriptionArn: aws.String(standardsSubscriptionARN), } @@ -190,11 +190,11 @@ func findStandardsControl(ctx context.Context, conn *securityhub.Client, input * return nil, err } - return tfresource.AssertSinglePtrResult(output) + return tfresource.AssertSingleValueResult(output) } -func findStandardsControls(ctx context.Context, conn *securityhub.Client, input *securityhub.DescribeStandardsControlsInput, filter tfslices.Predicate[*types.StandardsControl]) ([]*types.StandardsControl, error) { - var output []*types.StandardsControl +func findStandardsControls(ctx context.Context, conn *securityhub.Client, input *securityhub.DescribeStandardsControlsInput, filter tfslices.Predicate[*types.StandardsControl]) ([]types.StandardsControl, error) { + var output []types.StandardsControl pages := securityhub.NewDescribeStandardsControlsPaginator(conn, input) for pages.HasMorePages() { @@ -212,8 +212,7 @@ func findStandardsControls(ctx context.Context, conn *securityhub.Client, input } for _, v := range page.Controls { - v := v - if v := &v; filter(v) { + if filter(&v) { output = append(output, v) } } diff --git a/internal/service/securityhub/standards_subscription.go b/internal/service/securityhub/standards_subscription.go index 8fa33b21fb8..2156f1e0d71 100644 --- a/internal/service/securityhub/standards_subscription.go +++ b/internal/service/securityhub/standards_subscription.go @@ -23,8 +23,8 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/verify" ) -// @SDKResource("aws_securityhub_standards_subscription") -func ResourceStandardsSubscription() *schema.Resource { +// @SDKResource("aws_securityhub_standards_subscription", name="Standards Subscription") +func resourceStandardsSubscription() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceStandardsSubscriptionCreate, ReadWithoutTimeout: resourceStandardsSubscriptionRead, @@ -75,7 +75,7 @@ func resourceStandardsSubscriptionRead(ctx context.Context, d *schema.ResourceDa var diags diag.Diagnostics conn := meta.(*conns.AWSClient).SecurityHubClient(ctx) - output, err := FindStandardsSubscriptionByARN(ctx, conn, d.Id()) + output, err := findStandardsSubscriptionByARN(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Security Hub Standards Subscription (%s) not found, removing from state", d.Id()) @@ -112,7 +112,7 @@ func resourceStandardsSubscriptionDelete(ctx context.Context, d *schema.Resource return diags } -func FindStandardsSubscriptionByARN(ctx context.Context, conn *securityhub.Client, arn string) (*types.StandardsSubscription, error) { +func findStandardsSubscriptionByARN(ctx context.Context, conn *securityhub.Client, arn string) (*types.StandardsSubscription, error) { input := &securityhub.GetEnabledStandardsInput{ StandardsSubscriptionArns: []string{arn}, } @@ -169,7 +169,7 @@ func findStandardsSubscriptions(ctx context.Context, conn *securityhub.Client, i func statusStandardsSubscriptionCreate(ctx context.Context, conn *securityhub.Client, arn string) retry.StateRefreshFunc { return func() (interface{}, string, error) { - output, err := FindStandardsSubscriptionByARN(ctx, conn, arn) + output, err := findStandardsSubscriptionByARN(ctx, conn, arn) if tfresource.NotFound(err) { return nil, "", nil @@ -185,7 +185,7 @@ func statusStandardsSubscriptionCreate(ctx context.Context, conn *securityhub.Cl func statusStandardsSubscriptionDelete(ctx context.Context, conn *securityhub.Client, arn string) retry.StateRefreshFunc { return func() (interface{}, string, error) { - output, err := FindStandardsSubscriptionByARN(ctx, conn, arn) + output, err := findStandardsSubscriptionByARN(ctx, conn, arn) if tfresource.NotFound(err) { return nil, "", nil From 6987e5318e846f7328482148ef9444755b2c63f8 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 10:27:43 -0500 Subject: [PATCH 50/71] Fix golangci-lint 'unparam'. --- internal/service/securityhub/organization_configuration.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/securityhub/organization_configuration.go b/internal/service/securityhub/organization_configuration.go index d442f91bb14..3cb041ef436 100644 --- a/internal/service/securityhub/organization_configuration.go +++ b/internal/service/securityhub/organization_configuration.go @@ -199,7 +199,7 @@ func statusOrganizationConfiguration(ctx context.Context, conn *securityhub.Clie } } -func waitOrganizationConfigurationEnabled(ctx context.Context, conn *securityhub.Client, timeout time.Duration) (*securityhub.DescribeOrganizationConfigurationOutput, error) { +func waitOrganizationConfigurationEnabled(ctx context.Context, conn *securityhub.Client, timeout time.Duration) (*securityhub.DescribeOrganizationConfigurationOutput, error) { //nolint:unparam stateConf := &retry.StateChangeConf{ Pending: enum.Slice(types.OrganizationConfigurationStatusPending), Target: enum.Slice(types.OrganizationConfigurationStatusEnabled), From 919e7efa7e79f9147379dc8ddfa03fae3b1505fb Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 10:32:48 -0500 Subject: [PATCH 51/71] Consolidate 'TestAccSecurityHub_centralConfiguration' into 'TestAccSecurityHub_serial'. --- .../service/securityhub/securityhub_test.go | 52 +++++++------------ 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/internal/service/securityhub/securityhub_test.go b/internal/service/securityhub/securityhub_test.go index 8d8d8821cc1..23bd8c76ba4 100644 --- a/internal/service/securityhub/securityhub_test.go +++ b/internal/service/securityhub/securityhub_test.go @@ -31,16 +31,25 @@ func TestAccSecurityHub_serial(t *testing.T) { "mapFilters": testAccAutomationRule_mapFilters, "tags": testAccAutomationRule_tags, }, - "Member": { - "basic": testAccMember_basic, - "invite": testAccMember_invite, - }, "ActionTarget": { "basic": testAccActionTarget_basic, "disappears": testAccActionTarget_disappears, "Description": testAccActionTarget_Description, "Name": testAccActionTarget_Name, }, + "ConfigurationPolicy": { + "basic": testAccConfigurationPolicy_basic, + "disappears": testAccConfigurationPolicy_disappears, + "CustomParameters": testAccConfigurationPolicy_controlCustomParameters, + "ControlIdentifiers": testAccConfigurationPolicy_specificControlIdentifiers, + }, + "ConfigurationPolicyAssociation": { + "basic": testAccConfigurationPolicyAssociation_basic, + }, + "FindingAggregator": { + "basic": testAccFindingAggregator_basic, + "disappears": testAccFindingAggregator_disappears, + }, "Insight": { "basic": testAccInsight_basic, "disappears": testAccInsight_disappears, @@ -57,14 +66,19 @@ func TestAccSecurityHub_serial(t *testing.T) { "InviteAccepter": { "basic": testAccInviteAccepter_basic, }, + "Member": { + "basic": testAccMember_basic, + "invite": testAccMember_invite, + }, "OrganizationAdminAccount": { "basic": testAccOrganizationAdminAccount_basic, "disappears": testAccOrganizationAdminAccount_disappears, "MultiRegion": testAccOrganizationAdminAccount_MultiRegion, }, "OrganizationConfiguration": { - "basic": testAccOrganizationConfiguration_basic, - "AutoEnableStandards": testAccOrganizationConfiguration_autoEnableStandards, + "basic": testAccOrganizationConfiguration_basic, + "AutoEnableStandards": testAccOrganizationConfiguration_autoEnableStandards, + "CentralConfiguration": testAccOrganizationConfiguration_centralConfiguration, }, "ProductSubscription": { "basic": testAccProductSubscription_basic, @@ -78,37 +92,11 @@ func TestAccSecurityHub_serial(t *testing.T) { "basic": testAccStandardsSubscription_basic, "disappears": testAccStandardsSubscription_disappears, }, - "FindingAggregator": { - "basic": testAccFindingAggregator_basic, - "disappears": testAccFindingAggregator_disappears, - }, } acctest.RunSerialTests2Levels(t, testCases, 0) } -// TestAccSecurityHub_centralConfiguration is a multi-account test stuite for central configuration features. -// Central configuration can only be enabled from a *member* delegated admin account. -// The primary provider is expected to be an organizations member account and the alternate provider is expected to be the organizations management account. -func TestAccSecurityHub_centralConfiguration(t *testing.T) { - t.Parallel() - testCases := map[string]map[string]func(t *testing.T){ - "OrganizationConfiguration": { - "centralConfiguration": testAccOrganizationConfiguration_centralConfiguration, - }, - "ConfigurationPolicy": { - "basic": testAccConfigurationPolicy_basic, - "disappears": testAccConfigurationPolicy_disappears, - "customParameters": testAccConfigurationPolicy_controlCustomParameters, - "controlIdentifiers": testAccConfigurationPolicy_specificControlIdentifiers, - }, - "ConfigurationPolicyAssociation": { - "basic": testAccConfigurationPolicyAssociation_basic, - }, - } - acctest.RunSerialTests2Levels(t, testCases, 0) -} - const testAccMemberAccountDelegatedAdminConfig_base = ` data "aws_caller_identity" "member" {} From 20c4c99c196d44e060e81d2a9ea8c68767a25eef Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 10:42:42 -0500 Subject: [PATCH 52/71] r/aws_securityhub_configuration_policy: Fix typos. --- .../securityhub/configuration_policy.go | 389 +++++++++--------- 1 file changed, 196 insertions(+), 193 deletions(-) diff --git a/internal/service/securityhub/configuration_policy.go b/internal/service/securityhub/configuration_policy.go index d525aeb3526..ab23109e517 100644 --- a/internal/service/securityhub/configuration_policy.go +++ b/internal/service/securityhub/configuration_policy.go @@ -26,236 +26,239 @@ import ( // @SDKResource("aws_securityhub_configuration_policy", name="Configuration Policy") func resourceConfigurationPolicy() *schema.Resource { - customParameterResource := func() *schema.Resource { - return &schema.Resource{ - Schema: map[string]*schema.Schema{ - "bool": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "value": { - Required: true, - Type: schema.TypeBool, + + return &schema.Resource{ + CreateWithoutTimeout: resourceConfigurationPolicyCreate, + ReadWithoutTimeout: resourceConfigurationPolicyRead, + UpdateWithoutTimeout: resourceConfigurationPolicyUpdate, + DeleteWithoutTimeout: resourceConfigurationPolicyDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + SchemaFunc: func() map[string]*schema.Schema { + customParameterResource := func() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "bool": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Required: true, + Type: schema.TypeBool, + }, + }, }, }, - }, - }, - "double": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "value": { - Required: true, - Type: schema.TypeFloat, + "double": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Required: true, + Type: schema.TypeFloat, + }, + }, }, }, - }, - }, - "enum": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "value": { - Required: true, - Type: schema.TypeString, + "enum": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Required: true, + Type: schema.TypeString, + }, + }, }, }, - }, - }, - "enum_list": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "value": { - Required: true, - Type: schema.TypeList, - Elem: &schema.Schema{ - Type: schema.TypeString, + "enum_list": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Required: true, + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, }, }, }, - }, - }, - "int": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "value": { - Required: true, - Type: schema.TypeInt, - ValidateFunc: validation.IntAtMost(math.MaxInt32), + "int": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Required: true, + Type: schema.TypeInt, + ValidateFunc: validation.IntAtMost(math.MaxInt32), + }, + }, }, }, - }, - }, - "int_list": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "value": { - Required: true, - Type: schema.TypeList, - Elem: &schema.Schema{ - Type: schema.TypeInt, - ValidateFunc: validation.IntAtMost(math.MaxInt32), + "int_list": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Required: true, + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeInt, + ValidateFunc: validation.IntAtMost(math.MaxInt32), + }, + }, }, }, }, - }, - }, - "name": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "string": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "value": { - Required: true, - Type: schema.TypeString, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "string": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Required: true, + Type: schema.TypeString, + }, + }, + }, + }, + "string_list": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Required: true, + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, }, }, + "value_type": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[types.ParameterValueType](), + }, }, + } + } + + return map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, }, - "string_list": { + "configuration_policy": { Type: schema.TypeList, + Required: true, MaxItems: 1, - Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "value": { + "enabled_standard_arns": { + Type: schema.TypeSet, Required: true, - Type: schema.TypeList, Elem: &schema.Schema{ - Type: schema.TypeString, + Type: schema.TypeString, + ValidateFunc: verify.ValidARN, }, }, - }, - }, - }, - "value_type": { - Type: schema.TypeString, - Required: true, - ValidateDiagFunc: enum.Validate[types.ParameterValueType](), - }, - }, - } - } - - return &schema.Resource{ - CreateWithoutTimeout: resourceConfigurationPolicyCreate, - ReadWithoutTimeout: resourceConfigurationPolicyRead, - UpdateWithoutTimeout: resourceConfigurationPolicyUpdate, - DeleteWithoutTimeout: resourceConfigurationPolicyDelete, - - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - - Schema: map[string]*schema.Schema{ - "arn": { - Type: schema.TypeString, - Computed: true, - }, - "configuration_policy": { - Type: schema.TypeList, - Required: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "enabled_standard_arns": { - Type: schema.TypeSet, - Required: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: verify.ValidARN, - }, - }, - "security_controls_configuration": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "disabled_control_identifiers": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringIsNotEmpty, - }, - ConflictsWith: []string{ - "security_hub_policy.0.security_controls_configuration.0.enabled_control_identifiers", - }, - }, - "enabled_control_identifiers": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringIsNotEmpty, + "security_controls_configuration": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "disabled_control_identifiers": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringIsNotEmpty, + }, + ConflictsWith: []string{ + "configuration_policy.0.security_controls_configuration.0.enabled_control_identifiers", + }, }, - ConflictsWith: []string{ - "security_hub_policy.0.security_controls_configuration.0.disabled_control_identifiers", + "enabled_control_identifiers": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringIsNotEmpty, + }, + ConflictsWith: []string{ + "configuration_policy.0.security_controls_configuration.0.disabled_control_identifiers", + }, }, - }, - "security_control_custom_parameter": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "parameter": { - Type: schema.TypeSet, - Required: true, - MinItems: 1, - Elem: customParameterResource(), - }, - "security_control_id": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, + "security_control_custom_parameter": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "parameter": { + Type: schema.TypeSet, + Required: true, + MinItems: 1, + Elem: customParameterResource(), + }, + "security_control_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, }, }, }, }, }, }, - }, - "service_enabled": { - Type: schema.TypeBool, - Required: true, + "service_enabled": { + Type: schema.TypeBool, + Required: true, + }, }, }, }, - }, - "description": { - Type: schema.TypeString, - Optional: true, - }, - "name": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringMatch( - regexache.MustCompile(`[A-Za-z0-9\-\.!*/]+`), - "Only alphanumeric characters and the following ASCII characters are permitted: -, ., !, *, /", - ), - }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringMatch( + regexache.MustCompile(`[A-Za-z0-9\-\.!*/]+`), + "Only alphanumeric characters and the following ASCII characters are permitted: -, ., !, *, /", + ), + }, + } }, } } From 40980ceed871505cefd98e61b18455ce9a4526b3 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 11:37:06 -0500 Subject: [PATCH 53/71] r/aws_securityhub_organization_configuration: Only run waiters if 'configuration_type = "CENTRAL"'. --- .../securityhub/organization_configuration.go | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/internal/service/securityhub/organization_configuration.go b/internal/service/securityhub/organization_configuration.go index 3cb041ef436..b3463c9223c 100644 --- a/internal/service/securityhub/organization_configuration.go +++ b/internal/service/securityhub/organization_configuration.go @@ -93,15 +93,24 @@ func resourceOrganizationConfigurationUpdate(ctx context.Context, d *schema.Reso return sdkdiag.AppendErrorf(diags, "updating Security Hub Organization Configuration (%s): %s", d.Id(), err) } - timeout := d.Timeout(schema.TimeoutCreate) if d.IsNewResource() { d.SetId(meta.(*conns.AWSClient).AccountID) - } else { - timeout = d.Timeout(schema.TimeoutUpdate) } - if _, err := waitOrganizationConfigurationEnabled(ctx, conn, timeout); err != nil { - return sdkdiag.AppendErrorf(diags, "waiting for Security Hub Organization Configuration (%s) enable: %s", d.Id(), err) + configurationType := types.OrganizationConfigurationConfigurationTypeLocal + if input.OrganizationConfiguration != nil { + configurationType = input.OrganizationConfiguration.ConfigurationType + } + + if configurationType == types.OrganizationConfigurationConfigurationTypeCentral { + timeout := d.Timeout(schema.TimeoutUpdate) + if d.IsNewResource() { + timeout = d.Timeout(schema.TimeoutCreate) + } + + if _, err := waitOrganizationConfigurationEnabled(ctx, conn, timeout); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for Security Hub Organization Configuration (%s) enable: %s", d.Id(), err) + } } return append(diags, resourceOrganizationConfigurationRead(ctx, d, meta)...) @@ -150,11 +159,20 @@ func resourceOrganizationConfigurationDelete(ctx context.Context, d *schema.Reso _, err := conn.UpdateOrganizationConfiguration(ctx, input) if err != nil { - return sdkdiag.AppendErrorf(diags, "deleting Security Hub Organization Configuration (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "updating Security Hub Organization Configuration (%s): %s", d.Id(), err) } - if _, err := waitOrganizationConfigurationEnabled(ctx, conn, d.Timeout(schema.TimeoutDelete)); err != nil { - return sdkdiag.AppendErrorf(diags, "waiting for Security Hub Organization Configuration (%s) delete: %s", d.Id(), err) + configurationType := types.OrganizationConfigurationConfigurationTypeLocal + if v, ok := d.GetOk("organization_configuration"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + if organizationConfiguration := expandOrganizationConfiguration(v.([]interface{})[0].(map[string]interface{})); organizationConfiguration != nil { + configurationType = organizationConfiguration.ConfigurationType + } + } + + if configurationType == types.OrganizationConfigurationConfigurationTypeCentral { + if _, err := waitOrganizationConfigurationEnabled(ctx, conn, d.Timeout(schema.TimeoutDelete)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for Security Hub Organization Configuration (%s) delete: %s", d.Id(), err) + } } return diags From db3b85e8ef3e34d2662218ab375221ba6008a910 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 11:37:53 -0500 Subject: [PATCH 54/71] Add 'testAccCheckOrganizationConfigurationDestroy'. --- .../organization_configuration_test.go | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/internal/service/securityhub/organization_configuration_test.go b/internal/service/securityhub/organization_configuration_test.go index 27dcc1b511e..c5843a5679b 100644 --- a/internal/service/securityhub/organization_configuration_test.go +++ b/internal/service/securityhub/organization_configuration_test.go @@ -8,11 +8,13 @@ import ( "fmt" "testing" + "github.com/aws/aws-sdk-go-v2/service/securityhub/types" "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" tfsecurityhub "github.com/hashicorp/terraform-provider-aws/internal/service/securityhub" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -24,7 +26,7 @@ func testAccOrganizationConfiguration_basic(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckOrganizationManagementAccount(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: acctest.CheckDestroyNoop, + CheckDestroy: testAccCheckOrganizationConfigurationDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccOrganizationConfigurationConfig_basic(true), @@ -59,7 +61,7 @@ func testAccOrganizationConfiguration_autoEnableStandards(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckOrganizationManagementAccount(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: acctest.CheckDestroyNoop, + CheckDestroy: testAccCheckOrganizationConfigurationDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccOrganizationConfigurationConfig_autoEnableStandards("DEFAULT"), @@ -89,6 +91,7 @@ func testAccOrganizationConfiguration_autoEnableStandards(t *testing.T) { func testAccOrganizationConfiguration_centralConfiguration(t *testing.T) { ctx := acctest.Context(t) resourceName := "aws_securityhub_organization_configuration.test" + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) @@ -98,7 +101,7 @@ func testAccOrganizationConfiguration_centralConfiguration(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubServiceID), ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), - CheckDestroy: acctest.CheckDestroyNoop, + CheckDestroy: testAccCheckOrganizationConfigurationDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccOrganizationConfigurationConfig_centralConfiguration(false, "NONE", "CENTRAL"), @@ -144,6 +147,37 @@ func testAccCheckOrganizationConfigurationExists(ctx context.Context, n string) } } +func testAccCheckOrganizationConfigurationDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).SecurityHubClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_securityhub_standards_subscription" { + continue + } + + output, err := tfsecurityhub.FindOrganizationConfiguration(ctx, conn) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + // LOCAL ConfigurationType => deleted. + if output.OrganizationConfiguration.ConfigurationType == types.OrganizationConfigurationConfigurationTypeLocal { + continue + } + + return fmt.Errorf("Security Hub Standards Subscription %s still exists", rs.Primary.ID) + } + + return nil + } +} + const testAccOrganizationConfigurationConfig_base = ` resource "aws_securityhub_account" "test" {} From 2e606a43f69e5dacaa067b02122b51687c69eedb Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 11:40:39 -0500 Subject: [PATCH 55/71] r/aws_securityhub_organization_configuration: Add CHANGELOG entry for resource Delete behaviour. --- .changelog/35752.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.changelog/35752.txt b/.changelog/35752.txt index 1fb0c0ba81d..5907c0a0934 100644 --- a/.changelog/35752.txt +++ b/.changelog/35752.txt @@ -8,4 +8,8 @@ aws_securityhub_configuration_policy_association ```release-note:enhancement resource/aws_securityhub_organization_configuration: Add `organization_configuration` configuration block to support [central configuration](https://docs.aws.amazon.com/securityhub/latest/userguide/start-central-configuration.html) +``` + +```release-note:enhancement +resource/aws_securityhub_organization_configuration: Set `auto_enable` to `false`, `auto_enable_standards` to `NONE`, and `organization_configuration.configuration_type` to `LOCAL` on resource Delete ``` \ No newline at end of file From abf31cbc4fb8f47f91b11fcf8fabe257f98664af Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 11:44:18 -0500 Subject: [PATCH 56/71] testAccOrganizationConfiguration_centralConfiguration: Remove 'acctest.PreCheckAlternateRegionIs'. --- internal/service/securityhub/organization_configuration_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/service/securityhub/organization_configuration_test.go b/internal/service/securityhub/organization_configuration_test.go index c5843a5679b..6cb838f1ccc 100644 --- a/internal/service/securityhub/organization_configuration_test.go +++ b/internal/service/securityhub/organization_configuration_test.go @@ -96,7 +96,6 @@ func testAccOrganizationConfiguration_centralConfiguration(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckAlternateAccount(t) - acctest.PreCheckAlternateRegionIs(t, acctest.Region()) acctest.PreCheckOrganizationMemberAccount(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubServiceID), From 5bfe14ef7e478b7d01cde2066d5327bc986cc05d Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 12:02:52 -0500 Subject: [PATCH 57/71] Add 'acctest.PreCheckOrganizationManagementAccountWithProvider' and 'acctest.PreCheckOrganizationMemberAccountWithProvider'. --- internal/acctest/acctest.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/internal/acctest/acctest.go b/internal/acctest/acctest.go index 85e0abdebae..cc556587f85 100644 --- a/internal/acctest/acctest.go +++ b/internal/acctest/acctest.go @@ -1043,14 +1043,20 @@ func PreCheckOrganizationsEnabled(ctx context.Context, t *testing.T) { func PreCheckOrganizationManagementAccount(ctx context.Context, t *testing.T) { t.Helper() + PreCheckOrganizationManagementAccountWithProvider(ctx, t, func() *schema.Provider { return Provider }) +} + +func PreCheckOrganizationManagementAccountWithProvider(ctx context.Context, t *testing.T, providerF func() *schema.Provider) { + t.Helper() - organization, err := tforganizations.FindOrganization(ctx, Provider.Meta().(*conns.AWSClient).OrganizationsConn(ctx)) + awsClient := providerF().Meta().(*conns.AWSClient) + organization, err := tforganizations.FindOrganization(ctx, awsClient.OrganizationsConn(ctx)) if err != nil { t.Fatalf("describing AWS Organization: %s", err) } - callerIdentity, err := tfsts.FindCallerIdentity(ctx, Provider.Meta().(*conns.AWSClient).STSClient(ctx)) + callerIdentity, err := tfsts.FindCallerIdentity(ctx, awsClient.STSClient(ctx)) if err != nil { t.Fatalf("getting current identity: %s", err) @@ -1063,14 +1069,20 @@ func PreCheckOrganizationManagementAccount(ctx context.Context, t *testing.T) { func PreCheckOrganizationMemberAccount(ctx context.Context, t *testing.T) { t.Helper() + PreCheckOrganizationMemberAccountWithProvider(ctx, t, func() *schema.Provider { return Provider }) +} + +func PreCheckOrganizationMemberAccountWithProvider(ctx context.Context, t *testing.T, providerF func() *schema.Provider) { + t.Helper() - organization, err := tforganizations.FindOrganization(ctx, Provider.Meta().(*conns.AWSClient).OrganizationsConn(ctx)) + awsClient := providerF().Meta().(*conns.AWSClient) + organization, err := tforganizations.FindOrganization(ctx, awsClient.OrganizationsConn(ctx)) if err != nil { t.Fatalf("describing AWS Organization: %s", err) } - callerIdentity, err := tfsts.FindCallerIdentity(ctx, Provider.Meta().(*conns.AWSClient).STSClient(ctx)) + callerIdentity, err := tfsts.FindCallerIdentity(ctx, awsClient.STSClient(ctx)) if err != nil { t.Fatalf("getting current identity: %s", err) From fe4b2695676cae463095b6d034eedbed62192b23 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 12:05:47 -0500 Subject: [PATCH 58/71] Add 'acctest.ProviderFunc'. --- internal/acctest/acctest.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/internal/acctest/acctest.go b/internal/acctest/acctest.go index cc556587f85..bfda6e2722e 100644 --- a/internal/acctest/acctest.go +++ b/internal/acctest/acctest.go @@ -112,6 +112,8 @@ var ( // PreCheck(t) must be called before using this provider instance. var Provider *schema.Provider +type ProviderFunc func() *schema.Provider + // testAccProviderConfigure ensures Provider is only configured once // // The PreCheck(t) function is invoked for every test and this prevents @@ -1046,7 +1048,7 @@ func PreCheckOrganizationManagementAccount(ctx context.Context, t *testing.T) { PreCheckOrganizationManagementAccountWithProvider(ctx, t, func() *schema.Provider { return Provider }) } -func PreCheckOrganizationManagementAccountWithProvider(ctx context.Context, t *testing.T, providerF func() *schema.Provider) { +func PreCheckOrganizationManagementAccountWithProvider(ctx context.Context, t *testing.T, providerF ProviderFunc) { t.Helper() awsClient := providerF().Meta().(*conns.AWSClient) @@ -1072,7 +1074,7 @@ func PreCheckOrganizationMemberAccount(ctx context.Context, t *testing.T) { PreCheckOrganizationMemberAccountWithProvider(ctx, t, func() *schema.Provider { return Provider }) } -func PreCheckOrganizationMemberAccountWithProvider(ctx context.Context, t *testing.T, providerF func() *schema.Provider) { +func PreCheckOrganizationMemberAccountWithProvider(ctx context.Context, t *testing.T, providerF ProviderFunc) { t.Helper() awsClient := providerF().Meta().(*conns.AWSClient) @@ -1436,7 +1438,7 @@ provider %[1]q { `, providerName, os.Getenv(envvar.AlternateAccessKeyId), os.Getenv(envvar.AlternateProfile), AlternateRegion(), os.Getenv(envvar.AlternateSecretAccessKey)) } -func RegionProviderFunc(region string, providers *[]*schema.Provider) func() *schema.Provider { +func RegionProviderFunc(region string, providers *[]*schema.Provider) ProviderFunc { return func() *schema.Provider { if region == "" { log.Println("[DEBUG] No region given") @@ -1475,7 +1477,7 @@ func RegionProviderFunc(region string, providers *[]*schema.Provider) func() *sc } } -func NamedProviderFunc(name string, providers map[string]*schema.Provider) func() *schema.Provider { +func NamedProviderFunc(name string, providers map[string]*schema.Provider) ProviderFunc { return func() *schema.Provider { return NamedProvider(name, providers) } From 109eb819717e6038ff491d4a4b8f07c8b1c10881 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 13:07:50 -0500 Subject: [PATCH 59/71] testAccOrganizationConfiguration_centralConfiguration: Call 'acctest.PreCheckOrganizationManagementAccountWithProvider'. --- .../organization_configuration_test.go | 33 ++++++++++++++++++- .../service/securityhub/securityhub_test.go | 10 ------ 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/internal/service/securityhub/organization_configuration_test.go b/internal/service/securityhub/organization_configuration_test.go index 6cb838f1ccc..e70bc68fda6 100644 --- a/internal/service/securityhub/organization_configuration_test.go +++ b/internal/service/securityhub/organization_configuration_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/aws/aws-sdk-go-v2/service/securityhub/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" @@ -90,6 +91,7 @@ func testAccOrganizationConfiguration_autoEnableStandards(t *testing.T) { func testAccOrganizationConfiguration_centralConfiguration(t *testing.T) { ctx := acctest.Context(t) + providers := make(map[string]*schema.Provider) resourceName := "aws_securityhub_organization_configuration.test" resource.Test(t, resource.TestCase{ @@ -99,10 +101,18 @@ func testAccOrganizationConfiguration_centralConfiguration(t *testing.T) { acctest.PreCheckOrganizationMemberAccount(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesNamedAlternate(ctx, t, providers), CheckDestroy: testAccCheckOrganizationConfigurationDestroy(ctx), Steps: []resource.TestStep{ { + // Run a simple configuration to initialize the alternate providers + Config: testAccOrganizationConfigurationConfig_centralConfigurationInit, + }, + { + PreConfig: func() { + // Can only run check here because the provider is not available until the previous step. + acctest.PreCheckOrganizationManagementAccountWithProvider(ctx, t, acctest.NamedProviderFunc(acctest.ProviderNameAlternate, providers)) + }, Config: testAccOrganizationConfigurationConfig_centralConfiguration(false, "NONE", "CENTRAL"), Check: resource.ComposeTestCheckFunc( testAccCheckOrganizationConfigurationExists(ctx, resourceName), @@ -210,6 +220,27 @@ resource "aws_securityhub_organization_configuration" "test" { `, autoEnableStandards)) } +// Central configuration can only be enabled from a *member* delegated admin account. +// The primary provider is expected to be an organizations member account and the alternate provider is expected to be the organizations management account. +const testAccMemberAccountDelegatedAdminConfig_base = ` +data "aws_caller_identity" "member" {} + +resource "aws_securityhub_organization_admin_account" "test" { + provider = awsalternate + + admin_account_id = data.aws_caller_identity.member.account_id +} +` + +// Initialize all the providers used by acceptance tests. +var testAccOrganizationConfigurationConfig_centralConfigurationInit = acctest.ConfigCompose(acctest.ConfigAlternateAccountProvider(), ` +data "aws_caller_identity" "member" {} + +data "aws_caller_identity" "management" { + provider = awsalternate +} +`) + func testAccOrganizationConfigurationConfig_centralConfiguration(autoEnable bool, autoEnableStandards, configType string) string { return acctest.ConfigCompose( acctest.ConfigAlternateAccountProvider(), diff --git a/internal/service/securityhub/securityhub_test.go b/internal/service/securityhub/securityhub_test.go index 23bd8c76ba4..88c987ce403 100644 --- a/internal/service/securityhub/securityhub_test.go +++ b/internal/service/securityhub/securityhub_test.go @@ -96,13 +96,3 @@ func TestAccSecurityHub_serial(t *testing.T) { acctest.RunSerialTests2Levels(t, testCases, 0) } - -const testAccMemberAccountDelegatedAdminConfig_base = ` -data "aws_caller_identity" "member" {} - -resource "aws_securityhub_organization_admin_account" "test" { - provider = awsalternate - - admin_account_id = data.aws_caller_identity.member.account_id -} -` From 9779c21ee366843cf2caf4bd62dfba5721d4d121 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 13:24:01 -0500 Subject: [PATCH 60/71] r/aws_securityhub_organization_configuration: Retry on errors like 'DataUnavailableException: Central configuration couldn't be enabled because data from organization ... is still syncing. Retry later'. --- internal/service/securityhub/errors.go | 3 ++- .../securityhub/organization_configuration.go | 15 +++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/internal/service/securityhub/errors.go b/internal/service/securityhub/errors.go index f9524540556..c994f81fb14 100644 --- a/internal/service/securityhub/errors.go +++ b/internal/service/securityhub/errors.go @@ -9,9 +9,10 @@ import ( var ( errCodeAccessDeniedException = (*types.AccessDeniedException)(nil).ErrorCode() + errCodeBadRequestException = "BadRequestException" + errCodeDataUnavailableException = "DataUnavailableException" errCodeInvalidAccessException = (*types.InvalidAccessException)(nil).ErrorCode() errCodeInvalidInputException = (*types.InvalidInputException)(nil).ErrorCode() errCodeResourceConflictException = (*types.ResourceConflictException)(nil).ErrorCode() errCodeResourceNotFoundException = (*types.ResourceNotFoundException)(nil).ErrorCode() - errCodeBadRequestException = "BadRequestException" ) diff --git a/internal/service/securityhub/organization_configuration.go b/internal/service/securityhub/organization_configuration.go index b3463c9223c..305e2a25fbf 100644 --- a/internal/service/securityhub/organization_configuration.go +++ b/internal/service/securityhub/organization_configuration.go @@ -87,7 +87,15 @@ func resourceOrganizationConfigurationUpdate(ctx context.Context, d *schema.Reso input.OrganizationConfiguration = expandOrganizationConfiguration(v.([]interface{})[0].(map[string]interface{})) } - _, err := conn.UpdateOrganizationConfiguration(ctx, input) + timeout := d.Timeout(schema.TimeoutUpdate) + if d.IsNewResource() { + timeout = d.Timeout(schema.TimeoutCreate) + } + + // e.g. "DataUnavailableException: Central configuration couldn't be enabled because data from organization o-ira6i4k380 is still syncing. Retry later." + _, err := tfresource.RetryWhenAWSErrMessageContains(ctx, timeout, func() (interface{}, error) { + return conn.UpdateOrganizationConfiguration(ctx, input) + }, errCodeDataUnavailableException, "Retry later") if err != nil { return sdkdiag.AppendErrorf(diags, "updating Security Hub Organization Configuration (%s): %s", d.Id(), err) @@ -103,11 +111,6 @@ func resourceOrganizationConfigurationUpdate(ctx context.Context, d *schema.Reso } if configurationType == types.OrganizationConfigurationConfigurationTypeCentral { - timeout := d.Timeout(schema.TimeoutUpdate) - if d.IsNewResource() { - timeout = d.Timeout(schema.TimeoutCreate) - } - if _, err := waitOrganizationConfigurationEnabled(ctx, conn, timeout); err != nil { return sdkdiag.AppendErrorf(diags, "waiting for Security Hub Organization Configuration (%s) enable: %s", d.Id(), err) } From 835b8a5beb335f10e9ee1b5fd796a979cc98c4ba Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 13:27:07 -0500 Subject: [PATCH 61/71] Add 'testAccConfigurationPolicyAssociation_disappears'. --- .../configuration_policy_association_test.go | 29 +++++++++++++++++++ .../service/securityhub/securityhub_test.go | 3 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/internal/service/securityhub/configuration_policy_association_test.go b/internal/service/securityhub/configuration_policy_association_test.go index 7f9f1905f3b..97b13d61f0a 100644 --- a/internal/service/securityhub/configuration_policy_association_test.go +++ b/internal/service/securityhub/configuration_policy_association_test.go @@ -78,6 +78,35 @@ func testAccConfigurationPolicyAssociation_basic(t *testing.T) { }) } +func testAccConfigurationPolicyAssociation_disappears(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_securityhub_configuration_policy_association.test" + ouTarget := "aws_organizations_organizational_unit.test.id" + policy1 := "aws_securityhub_configuration_policy.test_1.id" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckAlternateAccount(t) + acctest.PreCheckAlternateRegionIs(t, acctest.Region()) + acctest.PreCheckOrganizationMemberAccount(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), + CheckDestroy: testAccCheckConfigurationPolicyAssociationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccConfigurationPolicyAssociationConfig_basic(ouTarget, policy1), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationPolicyAssociationExists(ctx, resourceName), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfsecurityhub.ResourceConfigurationPolicyAssociation(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + func testAccCheckConfigurationPolicyAssociationExists(ctx context.Context, n string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] diff --git a/internal/service/securityhub/securityhub_test.go b/internal/service/securityhub/securityhub_test.go index 88c987ce403..49bdbeb58ce 100644 --- a/internal/service/securityhub/securityhub_test.go +++ b/internal/service/securityhub/securityhub_test.go @@ -44,7 +44,8 @@ func TestAccSecurityHub_serial(t *testing.T) { "ControlIdentifiers": testAccConfigurationPolicy_specificControlIdentifiers, }, "ConfigurationPolicyAssociation": { - "basic": testAccConfigurationPolicyAssociation_basic, + "basic": testAccConfigurationPolicyAssociation_basic, + "disappears": testAccConfigurationPolicyAssociation_disappears, }, "FindingAggregator": { "basic": testAccFindingAggregator_basic, From 75eac52a0da03a703f4a67c42ce702e510660e35 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 13:59:54 -0500 Subject: [PATCH 62/71] r/aws_securityhub_configuration_policy: Better error handling. --- .../securityhub/configuration_policy.go | 13 +++ .../securityhub/configuration_policy_test.go | 88 +++++++++++++------ 2 files changed, 72 insertions(+), 29 deletions(-) diff --git a/internal/service/securityhub/configuration_policy.go b/internal/service/securityhub/configuration_policy.go index ab23109e517..df7981f4064 100644 --- a/internal/service/securityhub/configuration_policy.go +++ b/internal/service/securityhub/configuration_policy.go @@ -13,7 +13,9 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/securityhub" "github.com/aws/aws-sdk-go-v2/service/securityhub/types" + "github.com/hashicorp/aws-sdk-go-base/v2/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -360,6 +362,10 @@ func resourceConfigurationPolicyDelete(ctx context.Context, d *schema.ResourceDa Identifier: aws.String(d.Id()), }) + if tfawserr.ErrMessageContains(err, errCodeAccessDeniedException, "Must be a Security Hub delegated administrator with Central Configuration enabled") { + return diags + } + if err != nil { return sdkdiag.AppendErrorf(diags, "deleting Security Hub Configuration Policy (%s): %s", d.Id(), err) } @@ -378,6 +384,13 @@ func findConfigurationPolicyByID(ctx context.Context, conn *securityhub.Client, func findConfigurationPolicy(ctx context.Context, conn *securityhub.Client, input *securityhub.GetConfigurationPolicyInput) (*securityhub.GetConfigurationPolicyOutput, error) { output, err := conn.GetConfigurationPolicy(ctx, input) + if tfawserr.ErrMessageContains(err, errCodeAccessDeniedException, "Must be a Security Hub delegated administrator with Central Configuration enabled") { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + if err != nil { return nil, err } diff --git a/internal/service/securityhub/configuration_policy_test.go b/internal/service/securityhub/configuration_policy_test.go index 1bebdebf3c4..c6d489b0d80 100644 --- a/internal/service/securityhub/configuration_policy_test.go +++ b/internal/service/securityhub/configuration_policy_test.go @@ -8,6 +8,7 @@ import ( "fmt" "testing" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" @@ -19,6 +20,7 @@ import ( func testAccConfigurationPolicy_basic(t *testing.T) { ctx := acctest.Context(t) + providers := make(map[string]*schema.Provider) resourceName := "aws_securityhub_configuration_policy.test" exampleStandardsARN := fmt.Sprintf("arn:%s:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0", acctest.Partition()) @@ -26,14 +28,21 @@ func testAccConfigurationPolicy_basic(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckAlternateAccount(t) - acctest.PreCheckAlternateRegionIs(t, acctest.Region()) acctest.PreCheckOrganizationMemberAccount(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesNamedAlternate(ctx, t, providers), CheckDestroy: testAccCheckConfigurationPolicyDestroy(ctx), Steps: []resource.TestStep{ { + // Run a simple configuration to initialize the alternate providers + Config: testAccOrganizationConfigurationConfig_centralConfigurationInit, + }, + { + PreConfig: func() { + // Can only run check here because the provider is not available until the previous step. + acctest.PreCheckOrganizationManagementAccountWithProvider(ctx, t, acctest.NamedProviderFunc(acctest.ProviderNameAlternate, providers)) + }, Config: testAccConfigurationPolicyConfig_baseDisabled("TestPolicy", "This is a disabled policy"), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyExists(ctx, resourceName), @@ -56,11 +65,11 @@ func testAccConfigurationPolicy_basic(t *testing.T) { testAccCheckConfigurationPolicyExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "name", "TestPolicy"), resource.TestCheckResourceAttr(resourceName, "description", "This is an enabled policy"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.#", "1"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.service_enabled", "true"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.enabled_standard_arns.#", "1"), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.enabled_standard_arns.0", exampleStandardsARN), - resource.TestCheckResourceAttr(resourceName, "security_hub_policy.0.security_controls_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.#", "1"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.service_enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.enabled_standard_arns.#", "1"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.enabled_standard_arns.0", exampleStandardsARN), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.#", "1"), ), }, }, @@ -69,20 +78,28 @@ func testAccConfigurationPolicy_basic(t *testing.T) { func testAccConfigurationPolicy_disappears(t *testing.T) { ctx := acctest.Context(t) + providers := make(map[string]*schema.Provider) resourceName := "aws_securityhub_configuration_policy.test" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckAlternateAccount(t) - acctest.PreCheckAlternateRegionIs(t, acctest.Region()) acctest.PreCheckOrganizationMemberAccount(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesNamedAlternate(ctx, t, providers), CheckDestroy: testAccCheckConfigurationPolicyDestroy(ctx), Steps: []resource.TestStep{ { + // Run a simple configuration to initialize the alternate providers + Config: testAccOrganizationConfigurationConfig_centralConfigurationInit, + }, + { + PreConfig: func() { + // Can only run check here because the provider is not available until the previous step. + acctest.PreCheckOrganizationManagementAccountWithProvider(ctx, t, acctest.NamedProviderFunc(acctest.ProviderNameAlternate, providers)) + }, Config: testAccConfigurationPolicyConfig_baseDisabled("TestPolicy", "This is a disabled policy"), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyExists(ctx, resourceName), @@ -96,6 +113,7 @@ func testAccConfigurationPolicy_disappears(t *testing.T) { func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { ctx := acctest.Context(t) + providers := make(map[string]*schema.Provider) resourceName := "aws_securityhub_configuration_policy.test" foundationalStandardsARN := fmt.Sprintf("arn:%s:securityhub:%s::standards/aws-foundational-security-best-practices/v/1.0.0", acctest.Partition(), acctest.Region()) nistStandardsARN := fmt.Sprintf("arn:%s:securityhub:%s::standards/nist-800-53/v/5.0.0", acctest.Partition(), acctest.Region()) @@ -104,22 +122,27 @@ func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckAlternateAccount(t) - acctest.PreCheckAlternateRegionIs(t, acctest.Region()) acctest.PreCheckOrganizationMemberAccount(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesNamedAlternate(ctx, t, providers), CheckDestroy: testAccCheckConfigurationPolicyDestroy(ctx), Steps: []resource.TestStep{ { + // Run a simple configuration to initialize the alternate providers + Config: testAccOrganizationConfigurationConfig_centralConfigurationInit, + }, + { + PreConfig: func() { + // Can only run check here because the provider is not available until the previous step. + acctest.PreCheckOrganizationManagementAccountWithProvider(ctx, t, acctest.NamedProviderFunc(acctest.ProviderNameAlternate, providers)) + }, Config: testAccConfigurationPolicyConfig_controlCustomParametersMulti(foundationalStandardsARN), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyExists(ctx, resourceName), - resource.TestCheckResourceAttr(resourceName, "configuration_policy.#", "1"), resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.#", "1"), resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.#", "2"), - resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.0.security_control_id", "APIGateway.1"), resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.0.parameter.#", "1"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.0.parameter.*", map[string]string{ @@ -127,7 +150,6 @@ func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { "value_type": "CUSTOM", "enum.0.value": "INFO", }), - resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.1.security_control_id", "IAM.7"), resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.1.parameter.#", "3"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "configuration_policy.0.security_controls_configuration.0.security_control_custom_parameter.1.parameter.*", map[string]string{ @@ -240,6 +262,7 @@ func testAccConfigurationPolicy_controlCustomParameters(t *testing.T) { func testAccConfigurationPolicy_specificControlIdentifiers(t *testing.T) { ctx := acctest.Context(t) + providers := make(map[string]*schema.Provider) resourceName := "aws_securityhub_configuration_policy.test" foundationalStandardsARN := fmt.Sprintf("arn:%s:securityhub:%s::standards/aws-foundational-security-best-practices/v/1.0.0", acctest.Partition(), acctest.Region()) @@ -247,23 +270,30 @@ func testAccConfigurationPolicy_specificControlIdentifiers(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckAlternateAccount(t) - acctest.PreCheckAlternateRegionIs(t, acctest.Region()) acctest.PreCheckOrganizationMemberAccount(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesNamedAlternate(ctx, t, providers), CheckDestroy: testAccCheckConfigurationPolicyDestroy(ctx), Steps: []resource.TestStep{ { + // Run a simple configuration to initialize the alternate providers + Config: testAccOrganizationConfigurationConfig_centralConfigurationInit, + }, + { + PreConfig: func() { + // Can only run check here because the provider is not available until the previous step. + acctest.PreCheckOrganizationManagementAccountWithProvider(ctx, t, acctest.NamedProviderFunc(acctest.ProviderNameAlternate, providers)) + }, Config: testAccConfigurationPolicyConfig_specifcControlIdentifiers(foundationalStandardsARN, "IAM.7", "APIGateway.1", false), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "configuration_policy.#", "1"), resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.#", "1"), - resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.disabled_security_control_ids.#", "2"), - resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.disabled_security_control_ids.0", "IAM.7"), - resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.disabled_security_control_ids.1", "APIGateway.1"), - resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.enabled_security_control_ids.#", "0"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.disabled_control_identifiers.#", "2"), + resource.TestCheckTypeSetElemAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.disabled_control_identifiers.*", "IAM.7"), + resource.TestCheckTypeSetElemAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.disabled_control_identifiers.*", "APIGateway.1"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.enabled_control_identifiers.#", "0"), ), }, { @@ -277,10 +307,10 @@ func testAccConfigurationPolicy_specificControlIdentifiers(t *testing.T) { testAccCheckConfigurationPolicyExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "configuration_policy.#", "1"), resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.#", "1"), - resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.enabled_security_control_ids.#", "2"), - resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.enabled_security_control_ids.0", "APIGateway.1"), - resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.enabled_security_control_ids.1", "IAM.7"), - resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.disabled_security_control_ids.#", "0"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.enabled_control_identifiers.#", "2"), + resource.TestCheckTypeSetElemAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.enabled_control_identifiers.*", "APIGateway.1"), + resource.TestCheckTypeSetElemAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.enabled_control_identifiers.*", "IAM.7"), + resource.TestCheckResourceAttr(resourceName, "configuration_policy.0.security_controls_configuration.0.disabled_control_identifiers.#", "0"), ), }, }, @@ -363,7 +393,7 @@ resource "aws_securityhub_configuration_policy" "test" { %[3]q ] security_controls_configuration { - disabled_security_control_ids = [] + disabled_control_identifiers = [] } } @@ -387,7 +417,7 @@ resource "aws_securityhub_configuration_policy" "test" { ] security_controls_configuration { - disabled_security_control_ids = [] + disabled_control_identifiers = [] security_control_custom_parameter { security_control_id = "APIGateway.1" @@ -448,7 +478,7 @@ resource "aws_securityhub_configuration_policy" "test" { ] security_controls_configuration { - disabled_security_control_ids = [] + disabled_control_identifiers = [] security_control_custom_parameter { security_control_id = %[2]q @@ -469,9 +499,9 @@ resource "aws_securityhub_configuration_policy" "test" { } func testAccConfigurationPolicyConfig_specifcControlIdentifiers(standardsARN, control1, control2 string, enabledOnly bool) string { - controlIDAttr := "disabled_security_control_ids" + controlIDAttr := "disabled_control_identifiers" if enabledOnly { - controlIDAttr = "enabled_security_control_ids" + controlIDAttr = "enabled_control_identifiers" } return acctest.ConfigCompose( From ea9f200cf9cb4d60d5bab6a8ae1b723eb4a0d82f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 14:00:10 -0500 Subject: [PATCH 63/71] Acceptane test output: % make testacc TESTARGS='-run=TestAccSecurityHub_serial/^ConfigurationPolicy$$/basic' PKG=securityhub ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/securityhub/... -v -count 1 -parallel 20 -run=TestAccSecurityHub_serial/^ConfigurationPolicy$/basic -timeout 360m === RUN TestAccSecurityHub_serial === PAUSE TestAccSecurityHub_serial === CONT TestAccSecurityHub_serial === RUN TestAccSecurityHub_serial/ConfigurationPolicy === RUN TestAccSecurityHub_serial/ConfigurationPolicy/basic --- PASS: TestAccSecurityHub_serial (83.81s) --- PASS: TestAccSecurityHub_serial/ConfigurationPolicy (83.81s) --- PASS: TestAccSecurityHub_serial/ConfigurationPolicy/basic (83.81s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/securityhub 90.802s From c9593fe1c193b15ae82c9d728a06544b7b515215 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 14:07:58 -0500 Subject: [PATCH 64/71] r/aws_securityhub_configuration_policy: Handle 'ResourceNotFoundException' in Read. --- internal/service/securityhub/configuration_policy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/securityhub/configuration_policy.go b/internal/service/securityhub/configuration_policy.go index df7981f4064..7ffb6084071 100644 --- a/internal/service/securityhub/configuration_policy.go +++ b/internal/service/securityhub/configuration_policy.go @@ -384,7 +384,7 @@ func findConfigurationPolicyByID(ctx context.Context, conn *securityhub.Client, func findConfigurationPolicy(ctx context.Context, conn *securityhub.Client, input *securityhub.GetConfigurationPolicyInput) (*securityhub.GetConfigurationPolicyOutput, error) { output, err := conn.GetConfigurationPolicy(ctx, input) - if tfawserr.ErrMessageContains(err, errCodeAccessDeniedException, "Must be a Security Hub delegated administrator with Central Configuration enabled") { + if tfawserr.ErrCodeEquals(err, errCodeResourceNotFoundException) || tfawserr.ErrMessageContains(err, errCodeAccessDeniedException, "Must be a Security Hub delegated administrator with Central Configuration enabled") { return nil, &retry.NotFoundError{ LastError: err, LastRequest: input, From 502d8293313eb1bd670cea14a9ad051aeee36f53 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 14:08:05 -0500 Subject: [PATCH 65/71] Acceptance test output: % make testacc TESTARGS='-run=TestAccSecurityHub_serial/^ConfigurationPolicy$$/disappears' PKG=securityhub ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/securityhub/... -v -count 1 -parallel 20 -run=TestAccSecurityHub_serial/^ConfigurationPolicy$/disappears -timeout 360m === RUN TestAccSecurityHub_serial === PAUSE TestAccSecurityHub_serial === CONT TestAccSecurityHub_serial === RUN TestAccSecurityHub_serial/ConfigurationPolicy === RUN TestAccSecurityHub_serial/ConfigurationPolicy/disappears --- PASS: TestAccSecurityHub_serial (56.46s) --- PASS: TestAccSecurityHub_serial/ConfigurationPolicy (56.46s) --- PASS: TestAccSecurityHub_serial/ConfigurationPolicy/disappears (56.46s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/securityhub 63.306s From a119cb58e9c0d8e6c8d83340861019dd96f184fa Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 14:16:48 -0500 Subject: [PATCH 66/71] r/aws_securityhub_configuration_policy_association: Tidy up acceptance tests. --- .../configuration_policy_association_test.go | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/internal/service/securityhub/configuration_policy_association_test.go b/internal/service/securityhub/configuration_policy_association_test.go index 97b13d61f0a..2be38a13ceb 100644 --- a/internal/service/securityhub/configuration_policy_association_test.go +++ b/internal/service/securityhub/configuration_policy_association_test.go @@ -8,6 +8,7 @@ import ( "fmt" "testing" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" @@ -19,6 +20,7 @@ import ( func testAccConfigurationPolicyAssociation_basic(t *testing.T) { ctx := acctest.Context(t) + providers := make(map[string]*schema.Provider) resourceName := "aws_securityhub_configuration_policy_association.test" accountTarget := "data.aws_caller_identity.member.account_id" ouTarget := "aws_organizations_organizational_unit.test.id" @@ -30,14 +32,21 @@ func testAccConfigurationPolicyAssociation_basic(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckAlternateAccount(t) - acctest.PreCheckAlternateRegionIs(t, acctest.Region()) acctest.PreCheckOrganizationMemberAccount(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesNamedAlternate(ctx, t, providers), CheckDestroy: testAccCheckConfigurationPolicyAssociationDestroy(ctx), Steps: []resource.TestStep{ { + // Run a simple configuration to initialize the alternate providers + Config: testAccOrganizationConfigurationConfig_centralConfigurationInit, + }, + { + PreConfig: func() { + // Can only run check here because the provider is not available until the previous step. + acctest.PreCheckOrganizationManagementAccountWithProvider(ctx, t, acctest.NamedProviderFunc(acctest.ProviderNameAlternate, providers)) + }, Config: testAccConfigurationPolicyAssociationConfig_basic(ouTarget, policy1), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyAssociationExists(ctx, resourceName), @@ -80,6 +89,7 @@ func testAccConfigurationPolicyAssociation_basic(t *testing.T) { func testAccConfigurationPolicyAssociation_disappears(t *testing.T) { ctx := acctest.Context(t) + providers := make(map[string]*schema.Provider) resourceName := "aws_securityhub_configuration_policy_association.test" ouTarget := "aws_organizations_organizational_unit.test.id" policy1 := "aws_securityhub_configuration_policy.test_1.id" @@ -88,14 +98,21 @@ func testAccConfigurationPolicyAssociation_disappears(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckAlternateAccount(t) - acctest.PreCheckAlternateRegionIs(t, acctest.Region()) acctest.PreCheckOrganizationMemberAccount(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SecurityHubServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesNamedAlternate(ctx, t, providers), CheckDestroy: testAccCheckConfigurationPolicyAssociationDestroy(ctx), Steps: []resource.TestStep{ { + // Run a simple configuration to initialize the alternate providers + Config: testAccOrganizationConfigurationConfig_centralConfigurationInit, + }, + { + PreConfig: func() { + // Can only run check here because the provider is not available until the previous step. + acctest.PreCheckOrganizationManagementAccountWithProvider(ctx, t, acctest.NamedProviderFunc(acctest.ProviderNameAlternate, providers)) + }, Config: testAccConfigurationPolicyAssociationConfig_basic(ouTarget, policy1), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyAssociationExists(ctx, resourceName), From 74020521be657ad7afe51198caa410e3ac177449 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 14:36:03 -0500 Subject: [PATCH 67/71] r/aws_securityhub_configuration_policy_association: Better error handling. --- .../service/securityhub/configuration_policy_association.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/securityhub/configuration_policy_association.go b/internal/service/securityhub/configuration_policy_association.go index 7ca3107d30e..b66c9742dd3 100644 --- a/internal/service/securityhub/configuration_policy_association.go +++ b/internal/service/securityhub/configuration_policy_association.go @@ -143,7 +143,7 @@ func findConfigurationPolicyAssociationByID(ctx context.Context, conn *securityh func findConfigurationPolicyAssociation(ctx context.Context, conn *securityhub.Client, input *securityhub.GetConfigurationPolicyAssociationInput) (*securityhub.GetConfigurationPolicyAssociationOutput, error) { output, err := conn.GetConfigurationPolicyAssociation(ctx, input) - if tfawserr.ErrCodeEquals(err, errCodeResourceNotFoundException) || tfawserr.ErrMessageContains(err, errCodeInvalidAccessException, "not subscribed to AWS Security Hub") { + if tfawserr.ErrCodeEquals(err, errCodeResourceNotFoundException) || tfawserr.ErrMessageContains(err, errCodeAccessDeniedException, "Must be a Security Hub delegated administrator with Central Configuration enabled") || tfawserr.ErrMessageContains(err, errCodeInvalidAccessException, "not subscribed to AWS Security Hub") { return nil, &retry.NotFoundError{ LastError: err, LastRequest: input, From a0dc5c2a15c11dcfa1c822807415791b60f6538e Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 14:39:11 -0500 Subject: [PATCH 68/71] r/aws_securityhub_configuration_policy_association: Handle 'ResourceNotFoundException' on Delete. --- .../service/securityhub/configuration_policy_association.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/service/securityhub/configuration_policy_association.go b/internal/service/securityhub/configuration_policy_association.go index b66c9742dd3..c5271c5ebe1 100644 --- a/internal/service/securityhub/configuration_policy_association.go +++ b/internal/service/securityhub/configuration_policy_association.go @@ -125,6 +125,10 @@ func resourceConfigurationPolicyAssociationDelete(ctx context.Context, d *schema Target: expandTarget(d.Id()), }) + if tfawserr.ErrCodeEquals(err, errCodeResourceNotFoundException) { + return diags + } + if err != nil { return sdkdiag.AppendErrorf(diags, "starting Security Hub Configuration Policy Disassociation (%s): %s", d.Id(), err) } From 65ffc0b943d10050686e43a3483272f7aa254fe7 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 15:00:49 -0500 Subject: [PATCH 69/71] r/aws_securityhub_configuration_policy_association: Random names in acceptance tests. --- .../configuration_policy_association_test.go | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/internal/service/securityhub/configuration_policy_association_test.go b/internal/service/securityhub/configuration_policy_association_test.go index 2be38a13ceb..e27898f64bf 100644 --- a/internal/service/securityhub/configuration_policy_association_test.go +++ b/internal/service/securityhub/configuration_policy_association_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + 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" @@ -21,6 +22,7 @@ import ( func testAccConfigurationPolicyAssociation_basic(t *testing.T) { ctx := acctest.Context(t) providers := make(map[string]*schema.Provider) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_securityhub_configuration_policy_association.test" accountTarget := "data.aws_caller_identity.member.account_id" ouTarget := "aws_organizations_organizational_unit.test.id" @@ -47,7 +49,7 @@ func testAccConfigurationPolicyAssociation_basic(t *testing.T) { // Can only run check here because the provider is not available until the previous step. acctest.PreCheckOrganizationManagementAccountWithProvider(ctx, t, acctest.NamedProviderFunc(acctest.ProviderNameAlternate, providers)) }, - Config: testAccConfigurationPolicyAssociationConfig_basic(ouTarget, policy1), + Config: testAccConfigurationPolicyAssociationConfig_basic(rName, ouTarget, policy1), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyAssociationExists(ctx, resourceName), resource.TestCheckResourceAttrPair(resourceName, "policy_id", "aws_securityhub_configuration_policy.test_1", "id"), @@ -60,7 +62,7 @@ func testAccConfigurationPolicyAssociation_basic(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccConfigurationPolicyAssociationConfig_basic(ouTarget, policy2), + Config: testAccConfigurationPolicyAssociationConfig_basic(rName, ouTarget, policy2), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyAssociationExists(ctx, resourceName), resource.TestCheckResourceAttrPair(resourceName, "policy_id", "aws_securityhub_configuration_policy.test_2", "id"), @@ -68,7 +70,7 @@ func testAccConfigurationPolicyAssociation_basic(t *testing.T) { ), }, { - Config: testAccConfigurationPolicyAssociationConfig_basic(rootTarget, policy2), + Config: testAccConfigurationPolicyAssociationConfig_basic(rName, rootTarget, policy2), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyAssociationExists(ctx, resourceName), resource.TestCheckResourceAttrPair(resourceName, "policy_id", "aws_securityhub_configuration_policy.test_2", "id"), @@ -76,7 +78,7 @@ func testAccConfigurationPolicyAssociation_basic(t *testing.T) { ), }, { - Config: testAccConfigurationPolicyAssociationConfig_basic(accountTarget, policy2), + Config: testAccConfigurationPolicyAssociationConfig_basic(rName, accountTarget, policy2), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyAssociationExists(ctx, resourceName), resource.TestCheckResourceAttrPair(resourceName, "policy_id", "aws_securityhub_configuration_policy.test_2", "id"), @@ -90,6 +92,7 @@ func testAccConfigurationPolicyAssociation_basic(t *testing.T) { func testAccConfigurationPolicyAssociation_disappears(t *testing.T) { ctx := acctest.Context(t) providers := make(map[string]*schema.Provider) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_securityhub_configuration_policy_association.test" ouTarget := "aws_organizations_organizational_unit.test.id" policy1 := "aws_securityhub_configuration_policy.test_1.id" @@ -113,7 +116,7 @@ func testAccConfigurationPolicyAssociation_disappears(t *testing.T) { // Can only run check here because the provider is not available until the previous step. acctest.PreCheckOrganizationManagementAccountWithProvider(ctx, t, acctest.NamedProviderFunc(acctest.ProviderNameAlternate, providers)) }, - Config: testAccConfigurationPolicyAssociationConfig_basic(ouTarget, policy1), + Config: testAccConfigurationPolicyAssociationConfig_basic(rName, ouTarget, policy1), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationPolicyAssociationExists(ctx, resourceName), acctest.CheckResourceDisappears(ctx, acctest.Provider, tfsecurityhub.ResourceConfigurationPolicyAssociation(), resourceName), @@ -165,7 +168,8 @@ func testAccCheckConfigurationPolicyAssociationDestroy(ctx context.Context) reso } } -const testAccOrganizationalUnitConfig_base = ` +func testAccOrganizationalUnitConfig_base(rName string) string { + return fmt.Sprintf(` data "aws_organizations_organization" "test" { provider = awsalternate } @@ -173,19 +177,22 @@ data "aws_organizations_organization" "test" { resource "aws_organizations_organizational_unit" "test" { provider = awsalternate - name = "testAccConfigurationPolicyOrgUnitConfig_base" + name = %[1]q parent_id = data.aws_organizations_organization.test.roots[0].id } -` +`, rName) +} + +func testAccConfigurationPoliciesConfig_base(rName string) string { + return fmt.Sprintf(` +data "aws_partition" "current" {} -// lintignore:AWSAT005 -const testAccConfigurationPoliciesConfig_base = ` resource "aws_securityhub_configuration_policy" "test_1" { - name = "test1" + name = "%[1]s-1" configuration_policy { service_enabled = true - enabled_standard_arns = ["arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"] + enabled_standard_arns = ["arn:${data.aws_partition.current.partition}:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"] security_controls_configuration { disabled_control_identifiers = [] @@ -196,11 +203,11 @@ resource "aws_securityhub_configuration_policy" "test_1" { } resource "aws_securityhub_configuration_policy" "test_2" { - name = "test2" + name = "%[1]s-2" configuration_policy { service_enabled = true - enabled_standard_arns = ["arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"] + enabled_standard_arns = ["arn:${data.aws_partition.current.partition}:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"] security_controls_configuration { enabled_control_identifiers = ["CloudTrail.1"] @@ -208,16 +215,16 @@ resource "aws_securityhub_configuration_policy" "test_2" { } depends_on = [aws_securityhub_organization_configuration.test] +}`, rName) } -` -func testAccConfigurationPolicyAssociationConfig_basic(targetID, policyID string) string { +func testAccConfigurationPolicyAssociationConfig_basic(rName, targetID, policyID string) string { return acctest.ConfigCompose( acctest.ConfigAlternateAccountProvider(), testAccMemberAccountDelegatedAdminConfig_base, - testAccOrganizationalUnitConfig_base, + testAccOrganizationalUnitConfig_base(rName), testAccCentralConfigurationEnabledConfig_base, - testAccConfigurationPoliciesConfig_base, + testAccConfigurationPoliciesConfig_base(rName), fmt.Sprintf(` resource "aws_securityhub_configuration_policy_association" "test" { target_id = %[1]s From 38abadb03b8bdd56891fb9d710eb4448c12022f2 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 15:01:00 -0500 Subject: [PATCH 70/71] Acceptance test output: % make testacc TESTARGS='-run=TestAccSecurityHub_serial/ConfigurationPolicyAssociation' PKG=securityhub ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/securityhub/... -v -count 1 -parallel 20 -run=TestAccSecurityHub_serial/ConfigurationPolicyAssociation -timeout 360m === RUN TestAccSecurityHub_serial === PAUSE TestAccSecurityHub_serial === CONT TestAccSecurityHub_serial === RUN TestAccSecurityHub_serial/ConfigurationPolicyAssociation === RUN TestAccSecurityHub_serial/ConfigurationPolicyAssociation/basic === RUN TestAccSecurityHub_serial/ConfigurationPolicyAssociation/disappears --- PASS: TestAccSecurityHub_serial (388.18s) --- PASS: TestAccSecurityHub_serial/ConfigurationPolicyAssociation (388.18s) --- PASS: TestAccSecurityHub_serial/ConfigurationPolicyAssociation/basic (323.79s) --- PASS: TestAccSecurityHub_serial/ConfigurationPolicyAssociation/disappears (64.39s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/securityhub 395.181s From 8f3a4b53ca3485e563d1465583792fdfb928a0cf Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 8 Mar 2024 15:42:19 -0500 Subject: [PATCH 71/71] Fix golangci-lint 'whitespace'. --- internal/service/securityhub/configuration_policy.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/service/securityhub/configuration_policy.go b/internal/service/securityhub/configuration_policy.go index 7ffb6084071..6d8c2c727b2 100644 --- a/internal/service/securityhub/configuration_policy.go +++ b/internal/service/securityhub/configuration_policy.go @@ -28,7 +28,6 @@ import ( // @SDKResource("aws_securityhub_configuration_policy", name="Configuration Policy") func resourceConfigurationPolicy() *schema.Resource { - return &schema.Resource{ CreateWithoutTimeout: resourceConfigurationPolicyCreate, ReadWithoutTimeout: resourceConfigurationPolicyRead,