diff --git a/.changelog/21030.txt b/.changelog/21030.txt new file mode 100644 index 000000000000..8b0aa9a536e6 --- /dev/null +++ b/.changelog/21030.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_guardduty_filter: Add plan-time validation of `name` +``` \ No newline at end of file diff --git a/.changelog/31463.txt b/.changelog/31463.txt new file mode 100644 index 000000000000..8a5bf815242c --- /dev/null +++ b/.changelog/31463.txt @@ -0,0 +1,7 @@ +```release-note:new-resource +aws_guardduty_detector_feature +``` + +```release-note:enhancement +data-source/aws_guardduty_detector: Add `features` attribute +``` \ No newline at end of file diff --git a/internal/service/guardduty/detector.go b/internal/service/guardduty/detector.go index 77b2bc1d5913..239a2294c444 100644 --- a/internal/service/guardduty/detector.go +++ b/internal/service/guardduty/detector.go @@ -41,12 +41,10 @@ func ResourceDetector() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "arn": { Type: schema.TypeString, Computed: true, }, - "datasources": { Type: schema.TypeList, MaxItems: 1, @@ -54,20 +52,6 @@ func ResourceDetector() *schema.Resource { Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "s3_logs": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "enable": { - Type: schema.TypeBool, - Required: true, - }, - }, - }, - }, "kubernetes": { Type: schema.TypeList, Optional: true, @@ -123,16 +107,28 @@ func ResourceDetector() *schema.Resource { }, }, }, + "s3_logs": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enable": { + Type: schema.TypeBool, + Required: true, + }, + }, + }, + }, }, }, }, - "enable": { Type: schema.TypeBool, Optional: true, Default: true, }, - // finding_publishing_frequency is marked as Computed:true since // GuardDuty member accounts inherit setting from master account "finding_publishing_frequency": { @@ -140,7 +136,6 @@ func ResourceDetector() *schema.Resource { Optional: true, Computed: true, }, - names.AttrTags: tftags.TagsSchema(), names.AttrTagsAll: tftags.TagsSchemaComputed(), }, @@ -153,24 +148,25 @@ func resourceDetectorCreate(ctx context.Context, d *schema.ResourceData, meta in var diags diag.Diagnostics conn := meta.(*conns.AWSClient).GuardDutyConn(ctx) - input := guardduty.CreateDetectorInput{ + input := &guardduty.CreateDetectorInput{ Enable: aws.Bool(d.Get("enable").(bool)), Tags: getTagsIn(ctx), } + if v, ok := d.GetOk("datasources"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.DataSources = expandDataSourceConfigurations(v.([]interface{})[0].(map[string]interface{})) + } + if v, ok := d.GetOk("finding_publishing_frequency"); ok { input.FindingPublishingFrequency = aws.String(v.(string)) } - if v, ok := d.GetOk("datasources"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { - input.DataSources = expandDataSourceConfigurations(v.([]interface{})[0].(map[string]interface{})) - } + output, err := conn.CreateDetectorWithContext(ctx, input) - log.Printf("[DEBUG] Creating GuardDuty Detector: %s", input) - output, err := conn.CreateDetectorWithContext(ctx, &input) if err != nil { - return sdkdiag.AppendErrorf(diags, "Creating GuardDuty Detector failed: %s", err) + return sdkdiag.AppendErrorf(diags, "creating GuardDuty Detector: %s", err) } + d.SetId(aws.StringValue(output.DetectorId)) return append(diags, resourceDetectorRead(ctx, d, meta)...) @@ -180,21 +176,19 @@ func resourceDetectorRead(ctx context.Context, d *schema.ResourceData, meta inte var diags diag.Diagnostics conn := meta.(*conns.AWSClient).GuardDutyConn(ctx) - input := guardduty.GetDetectorInput{ - DetectorId: aws.String(d.Id()), + gdo, err := FindDetectorByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] GuardDuty Detector (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags } - log.Printf("[DEBUG] Reading GuardDuty Detector: %s", input) - gdo, err := conn.GetDetectorWithContext(ctx, &input) if err != nil { - if tfawserr.ErrMessageContains(err, guardduty.ErrCodeBadRequestException, "The request is rejected because the input detectorId is not owned by the current account.") { - log.Printf("[WARN] GuardDuty detector %q not found, removing from state", d.Id()) - d.SetId("") - return diags - } - return sdkdiag.AppendErrorf(diags, "Reading GuardDuty Detector '%s' failed: %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "reading GuardDuty Detector (%s): %s", d.Id(), err) } + d.Set("account_id", meta.(*conns.AWSClient).AccountID) arn := arn.ARN{ Partition: meta.(*conns.AWSClient).Partition, Region: meta.(*conns.AWSClient).Region, @@ -204,8 +198,6 @@ func resourceDetectorRead(ctx context.Context, d *schema.ResourceData, meta inte }.String() d.Set("arn", arn) - d.Set("account_id", meta.(*conns.AWSClient).AccountID) - if gdo.DataSources != nil { if err := d.Set("datasources", []interface{}{flattenDataSourceConfigurationsResult(gdo.DataSources)}); err != nil { return sdkdiag.AppendErrorf(diags, "setting datasources: %s", err) @@ -213,7 +205,6 @@ func resourceDetectorRead(ctx context.Context, d *schema.ResourceData, meta inte } else { d.Set("datasources", nil) } - d.Set("enable", aws.StringValue(gdo.Status) == guardduty.DetectorStatusEnabled) d.Set("finding_publishing_frequency", gdo.FindingPublishingFrequency) @@ -227,7 +218,7 @@ func resourceDetectorUpdate(ctx context.Context, d *schema.ResourceData, meta in conn := meta.(*conns.AWSClient).GuardDutyConn(ctx) if d.HasChangesExcept("tags", "tags_all") { - input := guardduty.UpdateDetectorInput{ + input := &guardduty.UpdateDetectorInput{ DetectorId: aws.String(d.Id()), Enable: aws.Bool(d.Get("enable").(bool)), FindingPublishingFrequency: aws.String(d.Get("finding_publishing_frequency").(string)), @@ -237,8 +228,8 @@ func resourceDetectorUpdate(ctx context.Context, d *schema.ResourceData, meta in input.DataSources = expandDataSourceConfigurations(d.Get("datasources").([]interface{})[0].(map[string]interface{})) } - log.Printf("[DEBUG] Update GuardDuty Detector: %s", input) - _, err := conn.UpdateDetectorWithContext(ctx, &input) + _, err := conn.UpdateDetectorWithContext(ctx, input) + if err != nil { return sdkdiag.AppendErrorf(diags, "updating GuardDuty Detector (%s): %s", d.Id(), err) } @@ -251,26 +242,15 @@ func resourceDetectorDelete(ctx context.Context, d *schema.ResourceData, meta in var diags diag.Diagnostics conn := meta.(*conns.AWSClient).GuardDutyConn(ctx) - input := &guardduty.DeleteDetectorInput{ - DetectorId: aws.String(d.Id()), - } - - err := retry.RetryContext(ctx, membershipPropagationTimeout, func() *retry.RetryError { - _, err := conn.DeleteDetectorWithContext(ctx, input) + log.Printf("[DEBUG] Deleting GuardDuty Detector: %s", d.Id()) + _, err := tfresource.RetryWhenAWSErrMessageContains(ctx, membershipPropagationTimeout, func() (interface{}, error) { + return conn.DeleteDetectorWithContext(ctx, &guardduty.DeleteDetectorInput{ + DetectorId: aws.String(d.Id()), + }) + }, guardduty.ErrCodeBadRequestException, "cannot delete detector while it has invited or associated members") - if tfawserr.ErrMessageContains(err, guardduty.ErrCodeBadRequestException, "cannot delete detector while it has invited or associated members") { - return retry.RetryableError(err) - } - - if err != nil { - return retry.NonRetryableError(err) - } - - return nil - }) - - if tfresource.TimedOut(err) { - _, err = conn.DeleteDetectorWithContext(ctx, input) + if tfawserr.ErrMessageContains(err, guardduty.ErrCodeBadRequestException, "The request is rejected because the input detectorId is not owned by the current account.") { + return diags } if err != nil { @@ -287,27 +267,16 @@ func expandDataSourceConfigurations(tfMap map[string]interface{}) *guardduty.Dat apiObject := &guardduty.DataSourceConfigurations{} - if v, ok := tfMap["s3_logs"].([]interface{}); ok && len(v) > 0 { - apiObject.S3Logs = expandS3LogsConfiguration(v[0].(map[string]interface{})) - } if v, ok := tfMap["kubernetes"].([]interface{}); ok && len(v) > 0 { apiObject.Kubernetes = expandKubernetesConfiguration(v[0].(map[string]interface{})) } + if v, ok := tfMap["malware_protection"].([]interface{}); ok && len(v) > 0 { apiObject.MalwareProtection = expandMalwareProtectionConfiguration(v[0].(map[string]interface{})) } - return apiObject -} - -func expandS3LogsConfiguration(tfMap map[string]interface{}) *guardduty.S3LogsConfiguration { - if tfMap == nil { - return nil - } - - apiObject := &guardduty.S3LogsConfiguration{} - if v, ok := tfMap["enable"].(bool); ok { - apiObject.Enable = aws.Bool(v) + if v, ok := tfMap["s3_logs"].([]interface{}); ok && len(v) > 0 { + apiObject.S3Logs = expandS3LogsConfiguration(v[0].(map[string]interface{})) } return apiObject @@ -363,11 +332,11 @@ func expandMalwareProtectionConfiguration(tfMap map[string]interface{}) *guarddu } return &guardduty.MalwareProtectionConfiguration{ - ScanEc2InstanceWithFindings: expandMalwareProtectionScanEC2InstanceWithFindingsConfiguration(m), + ScanEc2InstanceWithFindings: expandScanEc2InstanceWithFindings(m), } } -func expandMalwareProtectionScanEC2InstanceWithFindingsConfiguration(tfMap map[string]interface{}) *guardduty.ScanEc2InstanceWithFindings { +func expandScanEc2InstanceWithFindings(tfMap map[string]interface{}) *guardduty.ScanEc2InstanceWithFindings { // nosemgrep:ci.caps3-in-func-name if tfMap == nil { return nil } @@ -385,6 +354,7 @@ func expandMalwareProtectionScanEC2InstanceWithFindingsConfiguration(tfMap map[s apiObject := &guardduty.ScanEc2InstanceWithFindings{ EbsVolumes: expandMalwareProtectionEBSVolumesConfiguration(m), } + return apiObject } @@ -402,6 +372,20 @@ func expandMalwareProtectionEBSVolumesConfiguration(tfMap map[string]interface{} return apiObject } +func expandS3LogsConfiguration(tfMap map[string]interface{}) *guardduty.S3LogsConfiguration { + if tfMap == nil { + return nil + } + + apiObject := &guardduty.S3LogsConfiguration{} + + if v, ok := tfMap["enable"].(bool); ok { + apiObject.Enable = aws.Bool(v) + } + + return apiObject +} + func flattenDataSourceConfigurationsResult(apiObject *guardduty.DataSourceConfigurationsResult) map[string]interface{} { if apiObject == nil { return nil @@ -409,10 +393,6 @@ func flattenDataSourceConfigurationsResult(apiObject *guardduty.DataSourceConfig tfMap := map[string]interface{}{} - if v := apiObject.S3Logs; v != nil { - tfMap["s3_logs"] = []interface{}{flattenS3LogsConfigurationResult(v)} - } - if v := apiObject.Kubernetes; v != nil { tfMap["kubernetes"] = []interface{}{flattenKubernetesConfiguration(v)} } @@ -421,18 +401,8 @@ func flattenDataSourceConfigurationsResult(apiObject *guardduty.DataSourceConfig tfMap["malware_protection"] = []interface{}{flattenMalwareProtectionConfiguration(v)} } - return tfMap -} - -func flattenS3LogsConfigurationResult(apiObject *guardduty.S3LogsConfigurationResult) map[string]interface{} { - if apiObject == nil { - return nil - } - - tfMap := map[string]interface{}{} - - if v := apiObject.Status; v != nil { - tfMap["enable"] = aws.StringValue(v) == guardduty.DataSourceStatusEnabled + if v := apiObject.S3Logs; v != nil { + tfMap["s3_logs"] = []interface{}{flattenS3LogsConfigurationResult(v)} } return tfMap @@ -474,13 +444,13 @@ func flattenMalwareProtectionConfiguration(apiObject *guardduty.MalwareProtectio tfMap := map[string]interface{}{} if v := apiObject.ScanEc2InstanceWithFindings; v != nil { - tfMap["scan_ec2_instance_with_findings"] = []interface{}{flattenMalwareProtectionScanEC2InstanceWithFindingsConfigurationResult(v)} + tfMap["scan_ec2_instance_with_findings"] = []interface{}{flattenScanEc2InstanceWithFindingsResult(v)} } return tfMap } -func flattenMalwareProtectionScanEC2InstanceWithFindingsConfigurationResult(apiObject *guardduty.ScanEc2InstanceWithFindingsResult) map[string]interface{} { +func flattenScanEc2InstanceWithFindingsResult(apiObject *guardduty.ScanEc2InstanceWithFindingsResult) map[string]interface{} { // nosemgrep:ci.caps3-in-func-name if apiObject == nil { return nil } @@ -488,13 +458,13 @@ func flattenMalwareProtectionScanEC2InstanceWithFindingsConfigurationResult(apiO tfMap := map[string]interface{}{} if v := apiObject.EbsVolumes; v != nil { - tfMap["ebs_volumes"] = []interface{}{flattenMalwareProtectionEBSVolumesConfigurationResult(v)} + tfMap["ebs_volumes"] = []interface{}{flattenEbsVolumesResult(v)} } return tfMap } -func flattenMalwareProtectionEBSVolumesConfigurationResult(apiObject *guardduty.EbsVolumesResult) map[string]interface{} { +func flattenEbsVolumesResult(apiObject *guardduty.EbsVolumesResult) map[string]interface{} { // nosemgrep:ci.caps3-in-func-name if apiObject == nil { return nil } @@ -507,3 +477,70 @@ func flattenMalwareProtectionEBSVolumesConfigurationResult(apiObject *guardduty. return tfMap } + +func flattenS3LogsConfigurationResult(apiObject *guardduty.S3LogsConfigurationResult) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.Status; v != nil { + tfMap["enable"] = aws.StringValue(v) == guardduty.DataSourceStatusEnabled + } + + return tfMap +} + +func FindDetectorByID(ctx context.Context, conn *guardduty.GuardDuty, id string) (*guardduty.GetDetectorOutput, error) { + input := &guardduty.GetDetectorInput{ + DetectorId: aws.String(id), + } + + output, err := conn.GetDetectorWithContext(ctx, input) + + if tfawserr.ErrMessageContains(err, guardduty.ErrCodeBadRequestException, "The request is rejected because the input detectorId is not owned by the current account.") { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +// FindDetector returns the ID of the current account's active GuardDuty detector. +func FindDetector(ctx context.Context, conn *guardduty.GuardDuty) (*string, error) { + output, err := findDetectors(ctx, conn) + + if err != nil { + return nil, err + } + + return tfresource.AssertSinglePtrResult(output) +} + +func findDetectors(ctx context.Context, conn *guardduty.GuardDuty) ([]*string, error) { + input := &guardduty.ListDetectorsInput{} + var output []*string + + err := conn.ListDetectorsPagesWithContext(ctx, input, func(page *guardduty.ListDetectorsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + output = append(output, page.DetectorIds...) + + return !lastPage + }) + + if err != nil { + return nil, err + } + + return output, nil +} diff --git a/internal/service/guardduty/detector_data_source.go b/internal/service/guardduty/detector_data_source.go index ca6eb8b51cb2..fb83731ef116 100644 --- a/internal/service/guardduty/detector_data_source.go +++ b/internal/service/guardduty/detector_data_source.go @@ -7,7 +7,6 @@ import ( "context" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/guardduty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -20,20 +19,52 @@ func DataSourceDetector() *schema.Resource { ReadWithoutTimeout: dataSourceDetectorRead, Schema: map[string]*schema.Schema{ - "id": { + "features": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "additional_configuration": { + Computed: true, + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "finding_publishing_frequency": { Type: schema.TypeString, - Optional: true, Computed: true, }, - "status": { + "id": { Type: schema.TypeString, + Optional: true, Computed: true, }, "service_role_arn": { Type: schema.TypeString, Computed: true, }, - "finding_publishing_frequency": { + "status": { Type: schema.TypeString, Computed: true, }, @@ -45,43 +76,35 @@ func dataSourceDetectorRead(ctx context.Context, d *schema.ResourceData, meta in var diags diag.Diagnostics conn := meta.(*conns.AWSClient).GuardDutyConn(ctx) - detectorId := d.Get("id").(string) + detectorID := d.Get("id").(string) - if detectorId == "" { - input := &guardduty.ListDetectorsInput{} + if detectorID == "" { + output, err := FindDetector(ctx, conn) - resp, err := conn.ListDetectorsWithContext(ctx, input) if err != nil { - return sdkdiag.AppendErrorf(diags, "listing GuardDuty Detectors: %s", err) - } - - if resp == nil || len(resp.DetectorIds) == 0 { - return sdkdiag.AppendErrorf(diags, "no GuardDuty Detectors found") - } - if len(resp.DetectorIds) > 1 { - return sdkdiag.AppendErrorf(diags, "multiple GuardDuty Detectors found; please use the `id` argument to look up a single detector") + return sdkdiag.AppendErrorf(diags, "reading this account's single GuardDuty Detector: %s", err) } - detectorId = aws.StringValue(resp.DetectorIds[0]) + detectorID = aws.StringValue(output) } - getInput := &guardduty.GetDetectorInput{ - DetectorId: aws.String(detectorId), - } + gdo, err := FindDetectorByID(ctx, conn, detectorID) - getResp, err := conn.GetDetectorWithContext(ctx, getInput) if err != nil { - return sdkdiag.AppendErrorf(diags, "reading GuardDuty Detector (%s): %s", detectorId, err) + return sdkdiag.AppendErrorf(diags, "reading GuardDuty Detector (%s): %s", detectorID, err) } - if getResp == nil { - return sdkdiag.AppendErrorf(diags, "reading GuardDuty Detector (%s): empty result", detectorId) + d.SetId(detectorID) + if gdo.Features != nil { + if err := d.Set("features", flattenDetectorFeatureConfigurationResults(gdo.Features)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting features: %s", err) + } + } else { + d.Set("features", nil) } - - d.SetId(detectorId) - d.Set("status", getResp.Status) - d.Set("service_role_arn", getResp.ServiceRole) - d.Set("finding_publishing_frequency", getResp.FindingPublishingFrequency) + d.Set("finding_publishing_frequency", gdo.FindingPublishingFrequency) + d.Set("service_role_arn", gdo.ServiceRole) + d.Set("status", gdo.Status) return diags } diff --git a/internal/service/guardduty/detector_data_source_test.go b/internal/service/guardduty/detector_data_source_test.go index 5892b939b9df..0b20056ef193 100644 --- a/internal/service/guardduty/detector_data_source_test.go +++ b/internal/service/guardduty/detector_data_source_test.go @@ -13,23 +13,26 @@ import ( func testAccDetectorDataSource_basic(t *testing.T) { ctx := acctest.Context(t) + datasourceName := "data.aws_guardduty_detector.test" + resourceName := "aws_guardduty_detector.test" + resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, PreventPostDestroyRefresh: true, Steps: []resource.TestStep{ { - Config: testAccDetectorDataSourceConfig_basicResource(), - Check: resource.ComposeTestCheckFunc(), - }, - { - Config: testAccDetectorDataSourceConfig_basicResource2(), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrPair("data.aws_guardduty_detector.test", "id", "aws_guardduty_detector.test", "id"), - resource.TestCheckResourceAttr("data.aws_guardduty_detector.test", "status", "ENABLED"), - acctest.CheckResourceAttrGlobalARN("data.aws_guardduty_detector.test", "service_role_arn", "iam", "role/aws-service-role/guardduty.amazonaws.com/AWSServiceRoleForAmazonGuardDuty"), - resource.TestCheckResourceAttrPair("data.aws_guardduty_detector.test", "finding_publishing_frequency", "aws_guardduty_detector.test", "finding_publishing_frequency"), + Config: testAccDetectorDataSourceConfig_basic, + Check: resource.ComposeAggregateTestCheckFunc( + acctest.CheckResourceAttrGreaterThanValue(datasourceName, "features.#", 0), + resource.TestCheckResourceAttrPair(datasourceName, "finding_publishing_frequency", resourceName, "finding_publishing_frequency"), + resource.TestCheckResourceAttrPair(datasourceName, "id", resourceName, "id"), + acctest.CheckResourceAttrGlobalARN(datasourceName, "service_role_arn", "iam", "role/aws-service-role/guardduty.amazonaws.com/AWSServiceRoleForAmazonGuardDuty"), + resource.TestCheckResourceAttr(datasourceName, "status", "ENABLED"), ), }, }, @@ -38,44 +41,43 @@ func testAccDetectorDataSource_basic(t *testing.T) { func testAccDetectorDataSource_ID(t *testing.T) { ctx := acctest.Context(t) + datasourceName := "data.aws_guardduty_detector.test" + resourceName := "aws_guardduty_detector.test" + resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccDetectorDataSourceConfig_explicit(), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrPair("data.aws_guardduty_detector.test", "id", "aws_guardduty_detector.test", "id"), - resource.TestCheckResourceAttr("data.aws_guardduty_detector.test", "status", "ENABLED"), - acctest.CheckResourceAttrGlobalARN("data.aws_guardduty_detector.test", "service_role_arn", "iam", "role/aws-service-role/guardduty.amazonaws.com/AWSServiceRoleForAmazonGuardDuty"), - resource.TestCheckResourceAttrPair("data.aws_guardduty_detector.test", "finding_publishing_frequency", "aws_guardduty_detector.test", "finding_publishing_frequency"), + Config: testAccDetectorDataSourceConfig_id, + Check: resource.ComposeAggregateTestCheckFunc( + acctest.CheckResourceAttrGreaterThanValue(datasourceName, "features.#", 0), + resource.TestCheckResourceAttrPair(datasourceName, "finding_publishing_frequency", resourceName, "finding_publishing_frequency"), + resource.TestCheckResourceAttrPair(datasourceName, "id", resourceName, "id"), + acctest.CheckResourceAttrGlobalARN(datasourceName, "service_role_arn", "iam", "role/aws-service-role/guardduty.amazonaws.com/AWSServiceRoleForAmazonGuardDuty"), + resource.TestCheckResourceAttr(datasourceName, "status", "ENABLED"), ), }, }, }) } -func testAccDetectorDataSourceConfig_basicResource() string { - return ` -resource "aws_guardduty_detector" "test" {} -` -} - -func testAccDetectorDataSourceConfig_basicResource2() string { - return ` +const testAccDetectorDataSourceConfig_basic = ` resource "aws_guardduty_detector" "test" {} -data "aws_guardduty_detector" "test" {} -` +data "aws_guardduty_detector" "test" { + depends_on = [aws_guardduty_detector.test] } +` -func testAccDetectorDataSourceConfig_explicit() string { - return ` +const testAccDetectorDataSourceConfig_id = ` resource "aws_guardduty_detector" "test" {} data "aws_guardduty_detector" "test" { id = aws_guardduty_detector.test.id } ` -} diff --git a/internal/service/guardduty/detector_feature.go b/internal/service/guardduty/detector_feature.go new file mode 100644 index 000000000000..b69ec950975b --- /dev/null +++ b/internal/service/guardduty/detector_feature.go @@ -0,0 +1,284 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package guardduty + +import ( + "context" + "fmt" + "log" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/guardduty" + "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/errs/sdkdiag" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +// @SDKResource("aws_guardduty_detector_feature", name="Detector Feature") +func ResourceDetectorFeature() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceDetectorFeaturePut, + ReadWithoutTimeout: resourceDetectorFeatureRead, + UpdateWithoutTimeout: resourceDetectorFeaturePut, + DeleteWithoutTimeout: schema.NoopContext, + + Schema: map[string]*schema.Schema{ + "additional_configuration": { + Optional: true, + ForceNew: true, + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(guardduty.FeatureAdditionalConfiguration_Values(), false), + }, + "status": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(guardduty.FeatureStatus_Values(), false), + }, + }, + }, + }, + "detector_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(guardduty.DetectorFeature_Values(), false), + }, + "status": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(guardduty.FeatureStatus_Values(), false), + }, + }, + } +} + +func resourceDetectorFeaturePut(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).GuardDutyConn(ctx) + + detectorID, name := d.Get("detector_id").(string), d.Get("name").(string) + feature := &guardduty.DetectorFeatureConfiguration{ + Name: aws.String(name), + Status: aws.String(d.Get("status").(string)), + } + + if v, ok := d.GetOk("additional_configuration"); ok && len(v.([]interface{})) > 0 { + feature.AdditionalConfiguration = expandDetectorAdditionalConfigurations(v.([]interface{})) + } + + input := &guardduty.UpdateDetectorInput{ + DetectorId: aws.String(detectorID), + Features: []*guardduty.DetectorFeatureConfiguration{feature}, + } + + _, err := conn.UpdateDetectorWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "updating GuardDuty Detector (%s) Feature (%s): %s", detectorID, name, err) + } + + if d.IsNewResource() { + d.SetId(detectorFeatureCreateResourceID(detectorID, name)) + } + + return append(diags, resourceDetectorFeatureRead(ctx, d, meta)...) +} + +func resourceDetectorFeatureRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).GuardDutyConn(ctx) + + detectorID, name, err := detectorFeatureParseResourceID(d.Id()) + if err != nil { + return sdkdiag.AppendFromErr(diags, err) + } + + feature, err := FindDetectorFeatureByTwoPartKey(ctx, conn, detectorID, name) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] GuardDuty Detector Feature (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags + } + + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading GuardDuty Detector Feature (%s): %s", d.Id(), err) + } + + if err := d.Set("additional_configuration", flattenDetectorAdditionalConfigurationResults(feature.AdditionalConfiguration)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting additional_configuration: %s", err) + } + d.Set("detector_id", detectorID) + d.Set("name", feature.Name) + d.Set("status", feature.Status) + + return diags +} + +const detectorFeatureResourceIDSeparator = "/" + +func detectorFeatureCreateResourceID(detectorID, name string) string { + parts := []string{detectorID, name} + id := strings.Join(parts, detectorFeatureResourceIDSeparator) + + return id +} + +func detectorFeatureParseResourceID(id string) (string, string, error) { + parts := strings.Split(id, detectorFeatureResourceIDSeparator) + + if len(parts) == 2 && parts[0] != "" && parts[1] != "" { + return parts[0], parts[1], nil + } + + return "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected DETECTORID%[2]sFEATURENAME", id, detectorFeatureResourceIDSeparator) +} + +func FindDetectorFeatureByTwoPartKey(ctx context.Context, conn *guardduty.GuardDuty, detectorID, name string) (*guardduty.DetectorFeatureConfigurationResult, error) { + output, err := FindDetectorByID(ctx, conn, detectorID) + + if err != nil { + return nil, err + } + + return tfresource.AssertSinglePtrResult(tfslices.Filter(output.Features, func(v *guardduty.DetectorFeatureConfigurationResult) bool { + return aws.StringValue(v.Name) == name + })) +} + +func expandDetectorAdditionalConfiguration(tfMap map[string]interface{}) *guardduty.DetectorAdditionalConfiguration { + if tfMap == nil { + return nil + } + + apiObject := &guardduty.DetectorAdditionalConfiguration{} + + if v, ok := tfMap["name"].(string); ok && v != "" { + apiObject.Name = aws.String(v) + } + + if v, ok := tfMap["status"].(string); ok && v != "" { + apiObject.Status = aws.String(v) + } + + return apiObject +} + +func expandDetectorAdditionalConfigurations(tfList []interface{}) []*guardduty.DetectorAdditionalConfiguration { + if len(tfList) == 0 { + return nil + } + + var apiObjects []*guardduty.DetectorAdditionalConfiguration + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject := expandDetectorAdditionalConfiguration(tfMap) + + if apiObject == nil { + continue + } + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func flattenDetectorFeatureConfigurationResult(apiObject *guardduty.DetectorFeatureConfigurationResult) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.AdditionalConfiguration; v != nil { + tfMap["additional_configuration"] = flattenDetectorAdditionalConfigurationResults(v) + } + + if v := apiObject.Name; v != nil { + tfMap["name"] = aws.StringValue(v) + } + + if v := apiObject.Status; v != nil { + tfMap["status"] = aws.StringValue(v) + } + + return tfMap +} + +func flattenDetectorFeatureConfigurationResults(apiObjects []*guardduty.DetectorFeatureConfigurationResult) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, flattenDetectorFeatureConfigurationResult(apiObject)) + } + + return tfList +} + +func flattenDetectorAdditionalConfigurationResult(apiObject *guardduty.DetectorAdditionalConfigurationResult) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.Name; v != nil { + tfMap["name"] = aws.StringValue(v) + } + + if v := apiObject.Status; v != nil { + tfMap["status"] = aws.StringValue(v) + } + + return tfMap +} + +func flattenDetectorAdditionalConfigurationResults(apiObjects []*guardduty.DetectorAdditionalConfigurationResult) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, flattenDetectorAdditionalConfigurationResult(apiObject)) + } + + return tfList +} diff --git a/internal/service/guardduty/detector_feature_test.go b/internal/service/guardduty/detector_feature_test.go new file mode 100644 index 000000000000..43e5d626489f --- /dev/null +++ b/internal/service/guardduty/detector_feature_test.go @@ -0,0 +1,238 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package guardduty_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/guardduty" + "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" + tfguardduty "github.com/hashicorp/terraform-provider-aws/internal/service/guardduty" +) + +func testAccDetectorFeature_basic(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_guardduty_detector_feature.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + { + Config: testAccDetectorFeatureConfig_basic("RDS_LOGIN_EVENTS", "ENABLED"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckDetectorFeatureExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "additional_configuration.#", "0"), + resource.TestCheckResourceAttrSet(resourceName, "detector_id"), + resource.TestCheckResourceAttr(resourceName, "name", "RDS_LOGIN_EVENTS"), + resource.TestCheckResourceAttr(resourceName, "status", "ENABLED"), + ), + }, + }, + }) +} + +func testAccDetectorFeature_additionalConfiguration(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_guardduty_detector_feature.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + { + Config: testAccDetectorFeatureConfig_additionalConfiguration("ENABLED", "ENABLED"), + Check: resource.ComposeTestCheckFunc( + testAccCheckDetectorFeatureExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "additional_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "additional_configuration.0.name", "EKS_ADDON_MANAGEMENT"), + resource.TestCheckResourceAttr(resourceName, "additional_configuration.0.status", "ENABLED"), + resource.TestCheckResourceAttr(resourceName, "name", "EKS_RUNTIME_MONITORING"), + resource.TestCheckResourceAttr(resourceName, "status", "ENABLED"), + ), + }, + { + Config: testAccDetectorFeatureConfig_additionalConfiguration("DISABLED", "DISABLED"), + Check: resource.ComposeTestCheckFunc( + testAccCheckDetectorFeatureExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "additional_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "additional_configuration.0.name", "EKS_ADDON_MANAGEMENT"), + resource.TestCheckResourceAttr(resourceName, "additional_configuration.0.status", "DISABLED"), + resource.TestCheckResourceAttr(resourceName, "name", "EKS_RUNTIME_MONITORING"), + resource.TestCheckResourceAttr(resourceName, "status", "DISABLED"), + ), + }, + { + Config: testAccDetectorFeatureConfig_additionalConfiguration("ENABLED", "DISABLED"), + Check: resource.ComposeTestCheckFunc( + testAccCheckDetectorFeatureExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "additional_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "additional_configuration.0.name", "EKS_ADDON_MANAGEMENT"), + resource.TestCheckResourceAttr(resourceName, "additional_configuration.0.status", "DISABLED"), + resource.TestCheckResourceAttr(resourceName, "name", "EKS_RUNTIME_MONITORING"), + resource.TestCheckResourceAttr(resourceName, "status", "ENABLED"), + ), + }, + }, + }) +} + +func testAccDetectorFeature_multiple(t *testing.T) { + ctx := acctest.Context(t) + resource1Name := "aws_guardduty_detector_feature.test1" + resource2Name := "aws_guardduty_detector_feature.test2" + resource3Name := "aws_guardduty_detector_feature.test3" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + { + Config: testAccDetectorFeatureConfig_multiple("ENABLED", "DISABLED", "ENABLED"), + Check: resource.ComposeTestCheckFunc( + testAccCheckDetectorFeatureExists(ctx, resource1Name), + testAccCheckDetectorFeatureExists(ctx, resource2Name), + testAccCheckDetectorFeatureExists(ctx, resource3Name), + resource.TestCheckResourceAttr(resource1Name, "additional_configuration.#", "0"), + resource.TestCheckResourceAttr(resource1Name, "name", "EBS_MALWARE_PROTECTION"), + resource.TestCheckResourceAttr(resource1Name, "status", "ENABLED"), + resource.TestCheckResourceAttr(resource2Name, "additional_configuration.#", "0"), + resource.TestCheckResourceAttr(resource2Name, "name", "LAMBDA_NETWORK_LOGS"), + resource.TestCheckResourceAttr(resource2Name, "status", "DISABLED"), + resource.TestCheckResourceAttr(resource3Name, "additional_configuration.#", "0"), + resource.TestCheckResourceAttr(resource3Name, "name", "S3_DATA_EVENTS"), + resource.TestCheckResourceAttr(resource3Name, "status", "ENABLED"), + ), + }, + { + Config: testAccDetectorFeatureConfig_multiple("DISABLED", "ENABLED", "ENABLED"), + Check: resource.ComposeTestCheckFunc( + testAccCheckDetectorFeatureExists(ctx, resource1Name), + testAccCheckDetectorFeatureExists(ctx, resource2Name), + testAccCheckDetectorFeatureExists(ctx, resource3Name), + resource.TestCheckResourceAttr(resource1Name, "additional_configuration.#", "0"), + resource.TestCheckResourceAttr(resource1Name, "name", "EBS_MALWARE_PROTECTION"), + resource.TestCheckResourceAttr(resource1Name, "status", "DISABLED"), + resource.TestCheckResourceAttr(resource2Name, "additional_configuration.#", "0"), + resource.TestCheckResourceAttr(resource2Name, "name", "LAMBDA_NETWORK_LOGS"), + resource.TestCheckResourceAttr(resource2Name, "status", "ENABLED"), + resource.TestCheckResourceAttr(resource3Name, "additional_configuration.#", "0"), + resource.TestCheckResourceAttr(resource3Name, "name", "S3_DATA_EVENTS"), + resource.TestCheckResourceAttr(resource3Name, "status", "ENABLED"), + ), + }, + { + Config: testAccDetectorFeatureConfig_multiple("DISABLED", "DISABLED", "DISABLED"), + Check: resource.ComposeTestCheckFunc( + testAccCheckDetectorFeatureExists(ctx, resource1Name), + testAccCheckDetectorFeatureExists(ctx, resource2Name), + testAccCheckDetectorFeatureExists(ctx, resource3Name), + resource.TestCheckResourceAttr(resource1Name, "additional_configuration.#", "0"), + resource.TestCheckResourceAttr(resource1Name, "name", "EBS_MALWARE_PROTECTION"), + resource.TestCheckResourceAttr(resource1Name, "status", "DISABLED"), + resource.TestCheckResourceAttr(resource2Name, "additional_configuration.#", "0"), + resource.TestCheckResourceAttr(resource2Name, "name", "LAMBDA_NETWORK_LOGS"), + resource.TestCheckResourceAttr(resource2Name, "status", "DISABLED"), + resource.TestCheckResourceAttr(resource3Name, "additional_configuration.#", "0"), + resource.TestCheckResourceAttr(resource3Name, "name", "S3_DATA_EVENTS"), + resource.TestCheckResourceAttr(resource3Name, "status", "DISABLED"), + ), + }, + }, + }) +} + +func testAccCheckDetectorFeatureExists(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).GuardDutyConn(ctx) + + _, err := tfguardduty.FindDetectorFeatureByTwoPartKey(ctx, conn, rs.Primary.Attributes["detector_id"], rs.Primary.Attributes["name"]) + + return err + } +} + +func testAccDetectorFeatureConfig_basic(name, status string) string { + return fmt.Sprintf(` +resource "aws_guardduty_detector" "test" { + enable = true +} + +resource "aws_guardduty_detector_feature" "test" { + detector_id = aws_guardduty_detector.test.id + name = %[1]q + status = %[2]q +} +`, name, status) +} + +func testAccDetectorFeatureConfig_additionalConfiguration(featureStatus, additionalConfigurationStatus string) string { + return fmt.Sprintf(` +resource "aws_guardduty_detector" "test" { + enable = true +} + +resource "aws_guardduty_detector_feature" "test" { + detector_id = aws_guardduty_detector.test.id + name = "EKS_RUNTIME_MONITORING" + status = %[1]q + + additional_configuration { + name = "EKS_ADDON_MANAGEMENT" + status = %[2]q + } +} +`, featureStatus, additionalConfigurationStatus) +} + +func testAccDetectorFeatureConfig_multiple(status1, status2, status3 string) string { + return fmt.Sprintf(` +resource "aws_guardduty_detector" "test" { + enable = true +} + +resource "aws_guardduty_detector_feature" "test1" { + detector_id = aws_guardduty_detector.test.id + name = "EBS_MALWARE_PROTECTION" + status = %[1]q +} + +resource "aws_guardduty_detector_feature" "test2" { + detector_id = aws_guardduty_detector.test.id + name = "LAMBDA_NETWORK_LOGS" + status = %[2]q +} + +resource "aws_guardduty_detector_feature" "test3" { + detector_id = aws_guardduty_detector.test.id + name = "S3_DATA_EVENTS" + status = %[3]q +} +`, status1, status2, status3) +} diff --git a/internal/service/guardduty/detector_test.go b/internal/service/guardduty/detector_test.go index ee3e39e31760..b2a667e013e3 100644 --- a/internal/service/guardduty/detector_test.go +++ b/internal/service/guardduty/detector_test.go @@ -9,13 +9,13 @@ import ( "testing" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/guardduty" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" "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" + tfguardduty "github.com/hashicorp/terraform-provider-aws/internal/service/guardduty" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func testAccDetector_basic(t *testing.T) { @@ -23,7 +23,10 @@ func testAccDetector_basic(t *testing.T) { resourceName := "aws_guardduty_detector.test" resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckDetectorDestroy(ctx), @@ -77,7 +80,10 @@ func testAccDetector_tags(t *testing.T) { resourceName := "aws_guardduty_detector.test" resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckDetectorDestroy(ctx), @@ -121,7 +127,10 @@ func testAccDetector_datasources_s3logs(t *testing.T) { resourceName := "aws_guardduty_detector.test" resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckDetectorDestroy(ctx), @@ -158,7 +167,10 @@ func testAccDetector_datasources_kubernetes_audit_logs(t *testing.T) { resourceName := "aws_guardduty_detector.test" resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckDetectorDestroy(ctx), @@ -195,7 +207,10 @@ func testAccDetector_datasources_malware_protection(t *testing.T) { resourceName := "aws_guardduty_detector.test" resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckDetectorDestroy(ctx), @@ -235,7 +250,10 @@ func testAccDetector_datasources_all(t *testing.T) { resourceName := "aws_guardduty_detector.test" resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckDetectorDestroy(ctx), @@ -310,51 +328,35 @@ func testAccCheckDetectorDestroy(ctx context.Context) resource.TestCheckFunc { continue } - input := &guardduty.GetDetectorInput{ - DetectorId: aws.String(rs.Primary.ID), + _, err := tfguardduty.FindDetectorByID(ctx, conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue } - _, err := conn.GetDetectorWithContext(ctx, input) if err != nil { - if tfawserr.ErrMessageContains(err, guardduty.ErrCodeBadRequestException, "The request is rejected because the input detectorId is not owned by the current account.") { - return nil - } return err } - return fmt.Errorf("Expected GuardDuty Detector to be destroyed, %s found", rs.Primary.ID) + return fmt.Errorf("GuardDuty Detector %s still exists", rs.Primary.ID) } return nil } } -func testAccCheckDetectorExists(ctx context.Context, name string) resource.TestCheckFunc { +func testAccCheckDetectorExists(ctx context.Context, n string) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[name] + rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("Not found: %s", name) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("Resource (%s) has empty ID", name) + return fmt.Errorf("Not found: %s", n) } conn := acctest.Provider.Meta().(*conns.AWSClient).GuardDutyConn(ctx) - output, err := conn.GetDetectorWithContext(ctx, &guardduty.GetDetectorInput{ - DetectorId: aws.String(rs.Primary.ID), - }) - - if err != nil { - return err - } + _, err := tfguardduty.FindDetectorByID(ctx, conn, rs.Primary.ID) - if output == nil { - return fmt.Errorf("GuardDuty Detector not found: %s", name) - } - - return nil + return err } } diff --git a/internal/service/guardduty/filter.go b/internal/service/guardduty/filter.go index c443cf15c811..e773dc93ae33 100644 --- a/internal/service/guardduty/filter.go +++ b/internal/service/guardduty/filter.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/YakDriver/regexache" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/service/guardduty" @@ -37,29 +38,27 @@ func ResourceFilter() *schema.Resource { Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, + Schema: map[string]*schema.Schema{ + "action": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(guardduty.FilterAction_Values(), false), + }, "arn": { Type: schema.TypeString, Computed: true, }, - "detector_id": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.StringLenBetween(3, 64), - }, "description": { Type: schema.TypeString, Optional: true, ValidateFunc: validation.StringLenBetween(0, 512), }, - names.AttrTags: tftags.TagsSchema(), - names.AttrTagsAll: tftags.TagsSchemaComputed(), + "detector_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, "finding_criteria": { Type: schema.TypeList, MaxItems: 1, @@ -72,21 +71,15 @@ func ResourceFilter() *schema.Resource { Required: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "field": { - Type: schema.TypeString, - Required: true, - }, "equals": { Type: schema.TypeList, Optional: true, MinItems: 1, Elem: &schema.Schema{Type: schema.TypeString}, }, - "not_equals": { - Type: schema.TypeList, - Optional: true, - MinItems: 1, - Elem: &schema.Schema{Type: schema.TypeString}, + "field": { + Type: schema.TypeString, + Required: true, }, "greater_than": { Type: schema.TypeString, @@ -108,24 +101,34 @@ func ResourceFilter() *schema.Resource { Optional: true, ValidateFunc: verify.ValidStringDateOrPositiveInt, }, + "not_equals": { + Type: schema.TypeList, + Optional: true, + MinItems: 1, + Elem: &schema.Schema{Type: schema.TypeString}, + }, }, }, }, }, }, }, - "action": { + "name": { Type: schema.TypeString, Required: true, - ValidateFunc: validation.StringInSlice([]string{ - guardduty.FilterActionNoop, - guardduty.FilterActionArchive, - }, false), + ForceNew: true, + ValidateFunc: validation.All( + validation.StringLenBetween(3, 64), + validation.StringMatch(regexache.MustCompile(`^[a-zA-Z0-9_.-]+$`), + "only alphanumeric characters, hyphens, underscores, and periods are allowed"), + ), }, "rank": { Type: schema.TypeInt, Required: true, }, + names.AttrTags: tftags.TagsSchema(), + names.AttrTagsAll: tftags.TagsSchemaComputed(), }, CustomizeDiff: verify.SetTagsDiff, diff --git a/internal/service/guardduty/filter_test.go b/internal/service/guardduty/filter_test.go index d390f5c25c1e..6a90cf9c3870 100644 --- a/internal/service/guardduty/filter_test.go +++ b/internal/service/guardduty/filter_test.go @@ -30,7 +30,10 @@ func testAccFilter_basic(t *testing.T) { endDate := "2020-02-01T00:00:00Z" resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckFilterDestroy(ctx), @@ -97,7 +100,10 @@ func testAccFilter_update(t *testing.T) { endDate := "2020-02-01T00:00:00Z" resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckFilterDestroy(ctx), @@ -142,7 +148,10 @@ func testAccFilter_tags(t *testing.T) { endDate := "2020-02-01T00:00:00Z" resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckFilterDestroy(ctx), @@ -184,7 +193,10 @@ func testAccFilter_disappears(t *testing.T) { endDate := "2020-02-01T00:00:00Z" resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckACMPCACertificateAuthorityDestroy(ctx), diff --git a/internal/service/guardduty/finding_ids_data_source_test.go b/internal/service/guardduty/finding_ids_data_source_test.go index 546f6c864246..2c6c5fb0f2ea 100644 --- a/internal/service/guardduty/finding_ids_data_source_test.go +++ b/internal/service/guardduty/finding_ids_data_source_test.go @@ -11,12 +11,12 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" ) -func TestAccGuardDutyFindingIdsDataSource_basic(t *testing.T) { +func testAccFindingIDsDataSource_basic(t *testing.T) { ctx := acctest.Context(t) dataSourceName := "data.aws_guardduty_finding_ids.test" detectorDataSourceName := "data.aws_guardduty_detector.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) testAccPreCheckDetectorExists(ctx, t) @@ -25,7 +25,7 @@ func TestAccGuardDutyFindingIdsDataSource_basic(t *testing.T) { ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccFindingIdsDataSourceConfig_basic(), + Config: testAccFindingIDsDataSourceConfig_basic(), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrPair(dataSourceName, "detector_id", detectorDataSourceName, "id"), resource.TestCheckResourceAttrSet(dataSourceName, "has_findings"), @@ -36,7 +36,7 @@ func TestAccGuardDutyFindingIdsDataSource_basic(t *testing.T) { }) } -func testAccFindingIdsDataSourceConfig_basic() string { +func testAccFindingIDsDataSourceConfig_basic() string { return ` data "aws_guardduty_detector" "test" {} diff --git a/internal/service/guardduty/guardduty_test.go b/internal/service/guardduty/guardduty_test.go index 4e79096acbea..2eed345735de 100644 --- a/internal/service/guardduty/guardduty_test.go +++ b/internal/service/guardduty/guardduty_test.go @@ -8,9 +8,10 @@ import ( "os" "testing" - "github.com/aws/aws-sdk-go/service/guardduty" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfguardduty "github.com/hashicorp/terraform-provider-aws/internal/service/guardduty" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccGuardDuty_serial(t *testing.T) { @@ -27,12 +28,20 @@ func TestAccGuardDuty_serial(t *testing.T) { "datasource_basic": testAccDetectorDataSource_basic, "datasource_id": testAccDetectorDataSource_ID, }, + "DetectorFeature": { + "basic": testAccDetectorFeature_basic, + "additional_configuration": testAccDetectorFeature_additionalConfiguration, + "multiple": testAccDetectorFeature_multiple, + }, "Filter": { "basic": testAccFilter_basic, "update": testAccFilter_update, "tags": testAccFilter_tags, "disappears": testAccFilter_disappears, }, + "FindingIDs": { + "datasource_basic": testAccFindingIDsDataSource_basic, + }, "InviteAccepter": { "basic": testAccInviteAccepter_basic, }, @@ -87,20 +96,34 @@ func testAccMemberFromEnv(t *testing.T) (string, string) { return accountID, email } -// testAccPreCheckDetectorExists verifies the current account has a single active -// GuardDuty detector configured. +// testAccPreCheckDetectorExists verifies the current account has a single active GuardDuty detector configured. func testAccPreCheckDetectorExists(ctx context.Context, t *testing.T) { conn := acctest.Provider.Meta().(*conns.AWSClient).GuardDutyConn(ctx) - out, err := conn.ListDetectorsWithContext(ctx, &guardduty.ListDetectorsInput{}) - if out == nil || len(out.DetectorIds) == 0 { - t.Skip("this AWS account must have an existing GuardDuty detector configured") + _, err := tfguardduty.FindDetector(ctx, conn) + + if tfresource.NotFound(err) { + t.Skipf("reading this AWS account's single GuardDuty Detector: %s", err) } - if len(out.DetectorIds) > 1 { - t.Skipf("this AWS account must have a single existing GuardDuty detector configured. Found %d.", len(out.DetectorIds)) + + if err != nil { + t.Fatalf("listing GuardDuty Detectors: %s", err) + } +} + +// testAccPreCheckDetectorNotExists verifies the current account has no active GuardDuty detector configured. +func testAccPreCheckDetectorNotExists(ctx context.Context, t *testing.T) { + conn := acctest.Provider.Meta().(*conns.AWSClient).GuardDutyConn(ctx) + + _, err := tfguardduty.FindDetector(ctx, conn) + + if tfresource.NotFound(err) { + return } if err != nil { t.Fatalf("listing GuardDuty Detectors: %s", err) } + + t.Skip("this AWS account has a GuardDuty Detector") } diff --git a/internal/service/guardduty/invite_accepter_test.go b/internal/service/guardduty/invite_accepter_test.go index 41b98378bb49..c9bab1c33d58 100644 --- a/internal/service/guardduty/invite_accepter_test.go +++ b/internal/service/guardduty/invite_accepter_test.go @@ -28,6 +28,7 @@ func testAccInviteAccepter_basic(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckAlternateAccount(t) + testAccPreCheckDetectorNotExists(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), diff --git a/internal/service/guardduty/ipset_test.go b/internal/service/guardduty/ipset_test.go index 0bb529063cd3..ac859203d1fb 100644 --- a/internal/service/guardduty/ipset_test.go +++ b/internal/service/guardduty/ipset_test.go @@ -30,7 +30,10 @@ func testAccIPSet_basic(t *testing.T) { resourceName := "aws_guardduty_ipset.test" resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckIPSetDestroy(ctx), @@ -70,7 +73,10 @@ func testAccIPSet_tags(t *testing.T) { resourceName := "aws_guardduty_ipset.test" resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckIPSetDestroy(ctx), diff --git a/internal/service/guardduty/member_test.go b/internal/service/guardduty/member_test.go index 00c59fcf9dd4..c5eb68e86bac 100644 --- a/internal/service/guardduty/member_test.go +++ b/internal/service/guardduty/member_test.go @@ -24,7 +24,10 @@ func testAccMember_basic(t *testing.T) { accountID := "111111111111" resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckMemberDestroy(ctx), @@ -54,7 +57,10 @@ func testAccMember_invite_disassociate(t *testing.T) { accountID, email := testAccMemberFromEnv(t) resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckMemberDestroy(ctx), @@ -94,7 +100,10 @@ func testAccMember_invite_onUpdate(t *testing.T) { accountID, email := testAccMemberFromEnv(t) resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckMemberDestroy(ctx), @@ -135,7 +144,10 @@ func testAccMember_invitationMessage(t *testing.T) { invitationMessage := "inviting" resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckMemberDestroy(ctx), diff --git a/internal/service/guardduty/organization_admin_account_test.go b/internal/service/guardduty/organization_admin_account_test.go index 03c500166125..ff05880d3fd8 100644 --- a/internal/service/guardduty/organization_admin_account_test.go +++ b/internal/service/guardduty/organization_admin_account_test.go @@ -25,6 +25,7 @@ func testAccOrganizationAdminAccount_basic(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckOrganizationsAccount(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, diff --git a/internal/service/guardduty/organization_configuration_test.go b/internal/service/guardduty/organization_configuration_test.go index 3d600f553cfb..5743d933b150 100644 --- a/internal/service/guardduty/organization_configuration_test.go +++ b/internal/service/guardduty/organization_configuration_test.go @@ -21,6 +21,7 @@ func testAccOrganizationConfiguration_basic(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckOrganizationsAccount(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -62,6 +63,7 @@ func testAccOrganizationConfiguration_autoEnableOrganizationMembers(t *testing.T PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckOrganizationsAccount(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -160,6 +162,7 @@ func testAccOrganizationConfiguration_s3logs(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckOrganizationsAccount(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -203,6 +206,7 @@ func testAccOrganizationConfiguration_kubernetes(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckOrganizationsAccount(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -248,6 +252,7 @@ func testAccOrganizationConfiguration_malwareprotection(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckOrganizationsAccount(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, diff --git a/internal/service/guardduty/publishing_destination_test.go b/internal/service/guardduty/publishing_destination_test.go index 2ce3bfb4830a..8967fe67ff1d 100644 --- a/internal/service/guardduty/publishing_destination_test.go +++ b/internal/service/guardduty/publishing_destination_test.go @@ -27,7 +27,10 @@ func testAccPublishingDestination_basic(t *testing.T) { kmsKeyResourceName := "aws_kms_key.gd_key" resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckPublishingDestinationDestroy(ctx), @@ -56,7 +59,10 @@ func testAccPublishingDestination_disappears(t *testing.T) { bucketName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckPublishingDestinationDestroy(ctx), diff --git a/internal/service/guardduty/service_package_gen.go b/internal/service/guardduty/service_package_gen.go index bcd3713e47fa..ed2dbc93187a 100644 --- a/internal/service/guardduty/service_package_gen.go +++ b/internal/service/guardduty/service_package_gen.go @@ -47,6 +47,11 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka IdentifierAttribute: "arn", }, }, + { + Factory: ResourceDetectorFeature, + TypeName: "aws_guardduty_detector_feature", + Name: "Detector Feature", + }, { Factory: ResourceFilter, TypeName: "aws_guardduty_filter", diff --git a/internal/service/guardduty/threatintelset_test.go b/internal/service/guardduty/threatintelset_test.go index ca95f676c0a6..985c0ea96464 100644 --- a/internal/service/guardduty/threatintelset_test.go +++ b/internal/service/guardduty/threatintelset_test.go @@ -30,7 +30,10 @@ func testAccThreatIntelSet_basic(t *testing.T) { resourceName := "aws_guardduty_threatintelset.test" resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckThreatIntelSetDestroy(ctx), @@ -70,7 +73,10 @@ func testAccThreatIntelSet_tags(t *testing.T) { resourceName := "aws_guardduty_threatintelset.test" resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckDetectorNotExists(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, guardduty.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckThreatIntelSetDestroy(ctx), diff --git a/website/docs/d/guardduty_detector.html.markdown b/website/docs/d/guardduty_detector.html.markdown index 99f3598446d6..bce278dcdbfb 100644 --- a/website/docs/d/guardduty_detector.html.markdown +++ b/website/docs/d/guardduty_detector.html.markdown @@ -24,6 +24,12 @@ data "aws_guardduty_detector" "example" {} This data source exports the following attributes in addition to the arguments above: +* `features` - Current configuration of the detector features. + * `additional_configuration` - Additional feature configuration. + * `name` - The name of the additional configuration. + * `status` - The status of the additional configuration. + * `name` - The name of the detector feature. + * `status` - The status of the detector feature. * `finding_publishing_frequency` - The frequency of notifications sent about subsequent finding occurrences. * `service_role_arn` - Service-linked role that grants GuardDuty access to the resources in the AWS account. * `status` - Current status of the detector. diff --git a/website/docs/r/guardduty_detector.html.markdown b/website/docs/r/guardduty_detector.html.markdown index 2f0b6c13b2cc..26f20120faff 100644 --- a/website/docs/r/guardduty_detector.html.markdown +++ b/website/docs/r/guardduty_detector.html.markdown @@ -3,12 +3,12 @@ subcategory: "GuardDuty" layout: "aws" page_title: "AWS: aws_guardduty_detector" description: |- - Provides a resource to manage a GuardDuty detector + Provides a resource to manage an Amazon GuardDuty detector --- # Resource: aws_guardduty_detector -Provides a resource to manage a GuardDuty detector. +Provides a resource to manage an Amazon GuardDuty detector. ~> **NOTE:** Deleting this resource is equivalent to "disabling" GuardDuty for an AWS region, which removes all existing findings. You can set the `enable` attribute to `false` to instead "suspend" monitoring and feedback reporting while keeping existing data. See the [Suspending or Disabling Amazon GuardDuty documentation](https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_suspend-disable.html) for more information. @@ -44,7 +44,7 @@ This resource supports the following arguments: * `enable` - (Optional) Enable monitoring and feedback reporting. Setting to `false` is equivalent to "suspending" GuardDuty. Defaults to `true`. * `finding_publishing_frequency` - (Optional) Specifies the frequency of notifications sent for subsequent finding occurrences. If the detector is a GuardDuty member account, the value is determined by the GuardDuty primary account and cannot be modified, otherwise defaults to `SIX_HOURS`. For standalone and GuardDuty primary accounts, it must be configured in Terraform to enable drift detection. Valid values for standalone and primary accounts: `FIFTEEN_MINUTES`, `ONE_HOUR`, `SIX_HOURS`. See [AWS Documentation](https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_findings_cloudwatch.html#guardduty_findings_cloudwatch_notification_frequency) for more information. -* `datasources` - (Optional) Describes which data sources will be enabled for the detector. See [Data Sources](#data-sources) below for more details. +* `datasources` - (Optional) Describes which data sources will be enabled for the detector. See [Data Sources](#data-sources) below for more details. [Deprecated](https://docs.aws.amazon.com/guardduty/latest/ug/guardduty-feature-object-api-changes-march2023.html) in favor of [`aws_guardduty_detector_feature` resources](guardduty_detector_feature.html). * `tags` - (Optional) Key-value map of resource tags. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. ### Data Sources @@ -58,6 +58,8 @@ The `datasources` block supports the following: * `malware_protection` - (Optional) Configures [Malware Protection](https://docs.aws.amazon.com/guardduty/latest/ug/malware-protection.html). See [Malware Protection](#malware-protection), [Scan EC2 instance with findings](#scan-ec2-instance-with-findings) and [EBS volumes](#ebs-volumes) below for more details. +The `datasources` block is deprecated since March 2023. Use the `features` block instead and [map each `datasources` block to the corresponding `features` block](https://docs.aws.amazon.com/guardduty/latest/ug/guardduty-feature-object-api-changes-march2023.html#guardduty-feature-enablement-datasource-relation). + ### S3 Logs The `s3_logs` block supports the following: diff --git a/website/docs/r/guardduty_detector_feature.html.markdown b/website/docs/r/guardduty_detector_feature.html.markdown new file mode 100644 index 000000000000..66ee1b5a6488 --- /dev/null +++ b/website/docs/r/guardduty_detector_feature.html.markdown @@ -0,0 +1,52 @@ +--- +subcategory: "GuardDuty" +layout: "aws" +page_title: "AWS: aws_guardduty_detector_feature" +description: |- + Provides a resource to manage an Amazon GuardDuty detector feature +--- + +# Resource: aws_guardduty_detector_feature + +Provides a resource to manage a single Amazon GuardDuty [detector feature](https://docs.aws.amazon.com/guardduty/latest/ug/guardduty-features-activation-model.html#guardduty-features). + +~> **NOTE:** Deleting this resource does not disable the detector feature, the resource in simply removed from state instead. + +## Example Usage + +```terraform +resource "aws_guardduty_detector" "example" { + enable = true +} + +resource "aws_guardduty_detector_feature" "eks_runtime_monitoring" { + detector_id = aws_guardduty_detector.example.id + name = "EKS_RUNTIME_MONITORING" + status = "ENABLED" + + additional_configuration { + name = "EKS_ADDON_MANAGEMENT" + status = "ENABLED" + } +} +``` + +## Argument Reference + +This resource supports the following arguments: + +* `detector_id` - (Required) Amazon GuardDuty detector ID. +* `name` - (Required) The name of the detector feature. Valid values: `S3_DATA_EVENTS`, `EKS_AUDIT_LOGS`, `EBS_MALWARE_PROTECTION`, `RDS_LOGIN_EVENTS`, `EKS_RUNTIME_MONITORING`, `LAMBDA_NETWORK_LOGS`. +* `status` - (Required) The status of the detector feature. Valid values: `ENABLED`, `DISABLED`. +* `additional_configuration` - (Optional) Additional feature configuration block. See [below](#additional-configuration). + +### Additional Configuration + +The `additional_configuration` block supports the following: + +* `name` - (Required) The name of the additional configuration. Valid values: `EKS_ADDON_MANAGEMENT`. +* `status` - (Required) The status of the additional configuration. Valid values: `ENABLED`, `DISABLED`. + +## Attribute Reference + +This resource exports no additional attributes.