diff --git a/.changelog/30736.txt b/.changelog/30736.txt new file mode 100644 index 000000000000..97d082582476 --- /dev/null +++ b/.changelog/30736.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +resource/aws_guardduty_organization_configuration: Add `auto_enable_organization_members` attribute +``` + +```release-note:note +resource/aws_guardduty_organization_configuration: The `auto_enable` argument has been deprecated. Use the `auto_enable_organization_members` argument instead. + ``` diff --git a/internal/service/guardduty/guardduty_test.go b/internal/service/guardduty/guardduty_test.go index 5a1f89106171..d9b8e2c90696 100644 --- a/internal/service/guardduty/guardduty_test.go +++ b/internal/service/guardduty/guardduty_test.go @@ -38,10 +38,11 @@ func TestAccGuardDuty_serial(t *testing.T) { "basic": testAccOrganizationAdminAccount_basic, }, "OrganizationConfiguration": { - "basic": testAccOrganizationConfiguration_basic, - "s3Logs": testAccOrganizationConfiguration_s3logs, - "kubernetes": testAccOrganizationConfiguration_kubernetes, - "malwareProtection": testAccOrganizationConfiguration_malwareprotection, + "basic": testAccOrganizationConfiguration_basic, + "autoEnableOrganizationMembers": testAccOrganizationConfiguration_autoEnableOrganizationMembers, + "s3Logs": testAccOrganizationConfiguration_s3logs, + "kubernetes": testAccOrganizationConfiguration_kubernetes, + "malwareProtection": testAccOrganizationConfiguration_malwareprotection, }, "ThreatIntelSet": { "basic": testAccThreatIntelSet_basic, diff --git a/internal/service/guardduty/organization_admin_account_test.go b/internal/service/guardduty/organization_admin_account_test.go index e0b223bf8097..4f3142b7278d 100644 --- a/internal/service/guardduty/organization_admin_account_test.go +++ b/internal/service/guardduty/organization_admin_account_test.go @@ -110,7 +110,7 @@ resource "aws_organizations_organization" "test" { resource "aws_guardduty_detector" "test" {} resource "aws_guardduty_organization_admin_account" "test" { - depends_on = [aws_organizations_organization.test] + depends_on = [aws_organizations_organization.test, aws_guardduty_detector.test] admin_account_id = data.aws_caller_identity.current.account_id } diff --git a/internal/service/guardduty/organization_configuration.go b/internal/service/guardduty/organization_configuration.go index b1c5f071a962..a42284093b32 100644 --- a/internal/service/guardduty/organization_configuration.go +++ b/internal/service/guardduty/organization_configuration.go @@ -8,6 +8,7 @@ import ( "github.com/aws/aws-sdk-go/service/guardduty" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "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" @@ -28,8 +29,19 @@ func ResourceOrganizationConfiguration() *schema.Resource { Schema: map[string]*schema.Schema{ "auto_enable": { - Type: schema.TypeBool, - Required: true, + Type: schema.TypeBool, + Optional: true, + Computed: true, + ExactlyOneOf: []string{"auto_enable", "auto_enable_organization_members"}, + Deprecated: "Use auto_enable_organization_members instead", + }, + + "auto_enable_organization_members": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ExactlyOneOf: []string{"auto_enable", "auto_enable_organization_members"}, + ValidateFunc: validation.StringInSlice(guardduty.AutoEnableMembers_Values(), false), }, "datasources": { @@ -119,6 +131,35 @@ func ResourceOrganizationConfiguration() *schema.Resource { ValidateFunc: validation.NoZeroValues, }, }, + + CustomizeDiff: customdiff.Sequence( + func(_ context.Context, d *schema.ResourceDiff, _ interface{}) error { + // When creating an organization configuration with AutoEnable=true, + // AWS will automatically set AutoEnableOrganizationMembers=NEW. + // + // When configuring AutoEnableOrganizationMembers=ALL or NEW, + // AWS will automatically set AutoEnable=true. + // + // This diff customization keeps things consistent when configuring + // the resource against deprecation advice from AutoEnableOrganizationMembers=ALL + // to AutoEnable=true, and it also removes the need to use + // AutoEnable in the resource update function. + + if attr := d.GetRawConfig().GetAttr("auto_enable_organization_members"); attr.IsKnown() && !attr.IsNull() { + return d.SetNew("auto_enable", attr.AsString() != guardduty.AutoEnableMembersNone) + } + + if attr := d.GetRawConfig().GetAttr("auto_enable"); attr.IsKnown() && !attr.IsNull() { + if attr.True() { + return d.SetNew("auto_enable_organization_members", guardduty.AutoEnableMembersNew) + } else { + return d.SetNew("auto_enable_organization_members", guardduty.AutoEnableMembersNone) + } + } + + return nil + }, + ), } } @@ -129,8 +170,8 @@ func resourceOrganizationConfigurationUpdate(ctx context.Context, d *schema.Reso detectorID := d.Get("detector_id").(string) input := &guardduty.UpdateOrganizationConfigurationInput{ - AutoEnable: aws.Bool(d.Get("auto_enable").(bool)), - DetectorId: aws.String(detectorID), + AutoEnableOrganizationMembers: aws.String(d.Get("auto_enable_organization_members").(string)), + DetectorId: aws.String(detectorID), } if v, ok := d.GetOk("datasources"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { @@ -173,6 +214,7 @@ func resourceOrganizationConfigurationRead(ctx context.Context, d *schema.Resour } d.Set("auto_enable", output.AutoEnable) + d.Set("auto_enable_organization_members", output.AutoEnableOrganizationMembers) if output.DataSources != nil { if err := d.Set("datasources", []interface{}{flattenOrganizationDataSourceConfigurationsResult(output.DataSources)}); err != nil { diff --git a/internal/service/guardduty/organization_configuration_test.go b/internal/service/guardduty/organization_configuration_test.go index a3824b9ef52e..ba887d1f7b40 100644 --- a/internal/service/guardduty/organization_configuration_test.go +++ b/internal/service/guardduty/organization_configuration_test.go @@ -29,6 +29,7 @@ func testAccOrganizationConfiguration_basic(t *testing.T) { Config: testAccOrganizationConfigurationConfig_autoEnable(true), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "auto_enable", "true"), + resource.TestCheckResourceAttr(resourceName, "auto_enable_organization_members", "NEW"), resource.TestCheckResourceAttrPair(resourceName, "detector_id", detectorResourceName, "id"), ), }, @@ -41,6 +42,7 @@ func testAccOrganizationConfiguration_basic(t *testing.T) { Config: testAccOrganizationConfigurationConfig_autoEnable(false), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "auto_enable", "false"), + resource.TestCheckResourceAttr(resourceName, "auto_enable_organization_members", "NONE"), resource.TestCheckResourceAttrPair(resourceName, "detector_id", detectorResourceName, "id"), ), }, @@ -48,6 +50,104 @@ func testAccOrganizationConfiguration_basic(t *testing.T) { }) } +func testAccOrganizationConfiguration_autoEnableOrganizationMembers(t *testing.T) { + ctx := acctest.Context(t) + detectorResourceName := "aws_guardduty_detector.test" + resourceName := "aws_guardduty_organization_configuration.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckOrganizationsAccount(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + // GuardDuty Organization Configuration cannot be deleted separately. + // Ensure parent resource is destroyed instead. + CheckDestroy: testAccCheckDetectorDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccOrganizationConfigurationConfig_autoEnableOrganizationMembers("ALL"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "auto_enable_organization_members", "ALL"), + resource.TestCheckResourceAttr(resourceName, "auto_enable", "true"), + resource.TestCheckResourceAttrPair(resourceName, "detector_id", detectorResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccOrganizationConfigurationConfig_autoEnableOrganizationMembers("NONE"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "auto_enable_organization_members", "NONE"), + resource.TestCheckResourceAttr(resourceName, "auto_enable", "false"), + resource.TestCheckResourceAttrPair(resourceName, "detector_id", detectorResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccOrganizationConfigurationConfig_autoEnableOrganizationMembers("ALL"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "auto_enable_organization_members", "ALL"), + resource.TestCheckResourceAttr(resourceName, "auto_enable", "true"), + resource.TestCheckResourceAttrPair(resourceName, "detector_id", detectorResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccOrganizationConfigurationConfig_autoEnable(true), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "auto_enable_organization_members", "NEW"), + resource.TestCheckResourceAttr(resourceName, "auto_enable", "true"), + resource.TestCheckResourceAttrPair(resourceName, "detector_id", detectorResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccOrganizationConfigurationConfig_autoEnableOrganizationMembers("NONE"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "auto_enable_organization_members", "NONE"), + resource.TestCheckResourceAttr(resourceName, "auto_enable", "false"), + resource.TestCheckResourceAttrPair(resourceName, "detector_id", detectorResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccOrganizationConfigurationConfig_autoEnable(false), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "auto_enable_organization_members", "NONE"), + resource.TestCheckResourceAttr(resourceName, "auto_enable", "false"), + resource.TestCheckResourceAttrPair(resourceName, "detector_id", detectorResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccOrganizationConfiguration_s3logs(t *testing.T) { ctx := acctest.Context(t) detectorResourceName := "aws_guardduty_detector.test" @@ -183,53 +283,59 @@ func testAccOrganizationConfiguration_malwareprotection(t *testing.T) { }) } -func testAccOrganizationConfigurationConfig_autoEnable(autoEnable bool) string { - return fmt.Sprintf(` +const testAccOrganizationConfigurationConfigBase = ` data "aws_caller_identity" "current" {} data "aws_partition" "current" {} resource "aws_organizations_organization" "test" { - aws_service_access_principals = ["guardduty.${data.aws_partition.current.dns_suffix}"] - feature_set = "ALL" + aws_service_access_principals = [ + "guardduty.${data.aws_partition.current.dns_suffix}", + "malware-protection.guardduty.${data.aws_partition.current.dns_suffix}", + ] + + feature_set = "ALL" } resource "aws_guardduty_detector" "test" {} resource "aws_guardduty_organization_admin_account" "test" { - depends_on = [aws_organizations_organization.test] + depends_on = [aws_organizations_organization.test, aws_guardduty_detector.test] admin_account_id = data.aws_caller_identity.current.account_id } +` +func testAccOrganizationConfigurationConfig_autoEnable(autoEnable bool) string { + return acctest.ConfigCompose( + testAccOrganizationConfigurationConfigBase, + fmt.Sprintf(` resource "aws_guardduty_organization_configuration" "test" { depends_on = [aws_guardduty_organization_admin_account.test] auto_enable = %[1]t detector_id = aws_guardduty_detector.test.id } -`, autoEnable) +`, autoEnable)) } -func testAccOrganizationConfigurationConfig_s3Logs(autoEnable bool) string { - return fmt.Sprintf(` -data "aws_caller_identity" "current" {} - -data "aws_partition" "current" {} +func testAccOrganizationConfigurationConfig_autoEnableOrganizationMembers(value string) string { + return acctest.ConfigCompose( + testAccOrganizationConfigurationConfigBase, + fmt.Sprintf(` +resource "aws_guardduty_organization_configuration" "test" { + depends_on = [aws_guardduty_organization_admin_account.test] -resource "aws_organizations_organization" "test" { - aws_service_access_principals = ["guardduty.${data.aws_partition.current.dns_suffix}"] - feature_set = "ALL" + auto_enable_organization_members = %[1]q + detector_id = aws_guardduty_detector.test.id } - -resource "aws_guardduty_detector" "test" {} - -resource "aws_guardduty_organization_admin_account" "test" { - depends_on = [aws_organizations_organization.test] - - admin_account_id = data.aws_caller_identity.current.account_id +`, value)) } +func testAccOrganizationConfigurationConfig_s3Logs(autoEnable bool) string { + return acctest.ConfigCompose( + testAccOrganizationConfigurationConfigBase, + fmt.Sprintf(` resource "aws_guardduty_organization_configuration" "test" { depends_on = [aws_guardduty_organization_admin_account.test] @@ -242,28 +348,13 @@ resource "aws_guardduty_organization_configuration" "test" { } } } -`, autoEnable) +`, autoEnable)) } func testAccOrganizationConfigurationConfig_kubernetes(autoEnable bool) string { - return fmt.Sprintf(` -data "aws_caller_identity" "current" {} - -data "aws_partition" "current" {} - -resource "aws_organizations_organization" "test" { - aws_service_access_principals = ["guardduty.${data.aws_partition.current.dns_suffix}"] - feature_set = "ALL" -} - -resource "aws_guardduty_detector" "test" {} - -resource "aws_guardduty_organization_admin_account" "test" { - depends_on = [aws_organizations_organization.test] - - admin_account_id = data.aws_caller_identity.current.account_id -} - + return acctest.ConfigCompose( + testAccOrganizationConfigurationConfigBase, + fmt.Sprintf(` resource "aws_guardduty_organization_configuration" "test" { depends_on = [aws_guardduty_organization_admin_account.test] @@ -278,28 +369,13 @@ resource "aws_guardduty_organization_configuration" "test" { } } } -`, autoEnable) +`, autoEnable)) } func testAccOrganizationConfigurationConfig_malwareprotection(autoEnable bool) string { - return fmt.Sprintf(` -data "aws_caller_identity" "current" {} - -data "aws_partition" "current" {} - -resource "aws_organizations_organization" "test" { - aws_service_access_principals = ["guardduty.${data.aws_partition.current.dns_suffix}", "malware-protection.guardduty.${data.aws_partition.current.dns_suffix}"] - feature_set = "ALL" -} - -resource "aws_guardduty_detector" "test" {} - -resource "aws_guardduty_organization_admin_account" "test" { - depends_on = [aws_organizations_organization.test] - - admin_account_id = data.aws_caller_identity.current.account_id -} - + return acctest.ConfigCompose( + testAccOrganizationConfigurationConfigBase, + fmt.Sprintf(` resource "aws_guardduty_organization_configuration" "test" { depends_on = [aws_guardduty_organization_admin_account.test] @@ -316,5 +392,5 @@ resource "aws_guardduty_organization_configuration" "test" { } } } -`, autoEnable) +`, autoEnable)) } diff --git a/website/docs/r/guardduty_organization_configuration.html.markdown b/website/docs/r/guardduty_organization_configuration.html.markdown index 0d49f4d1ce19..eb5af6bca217 100644 --- a/website/docs/r/guardduty_organization_configuration.html.markdown +++ b/website/docs/r/guardduty_organization_configuration.html.markdown @@ -20,7 +20,8 @@ resource "aws_guardduty_detector" "example" { } resource "aws_guardduty_organization_configuration" "example" { - auto_enable = true + auto_enable_organization_members = "ALL" + detector_id = aws_guardduty_detector.example.id datasources { @@ -45,9 +46,12 @@ resource "aws_guardduty_organization_configuration" "example" { ## Argument Reference +~> **NOTE:** One of `auto_enable` or `auto_enable_organization_members` must be specified. + The following arguments are supported: -* `auto_enable` - (Required) When this setting is enabled, all new accounts that are created in, or added to, the organization are added as a member accounts of the organization’s GuardDuty delegated administrator and GuardDuty is enabled in that AWS Region. +* `auto_enable` - (Optional) *Deprecated:* Use `auto_enable_organization_members` instead. When this setting is enabled, all new accounts that are created in, or added to, the organization are added as a member accounts of the organization’s GuardDuty delegated administrator and GuardDuty is enabled in that AWS Region. +* `auto_enable_organization_members` - (Optional) Indicates the auto-enablement configuration of GuardDuty for the member accounts in the organization. Valid values are `ALL`, `NEW`, `NONE`. * `detector_id` - (Required) The detector ID of the GuardDuty account. * `datasources` - (Optional) Configuration for the collected datasources.