From 10dc6f523542b51f698ba50a2af6581b2316f39e Mon Sep 17 00:00:00 2001 From: Farhan Angullia Date: Fri, 25 Jun 2021 02:46:42 +0800 Subject: [PATCH 1/5] feat(s3logs): added support for datasources and added acc tests --- aws/resource_aws_guardduty_detector.go | 94 ++++++++++++++++++++- aws/resource_aws_guardduty_detector_test.go | 48 +++++++++++ aws/resource_aws_guardduty_test.go | 9 +- 3 files changed, 146 insertions(+), 5 deletions(-) diff --git a/aws/resource_aws_guardduty_detector.go b/aws/resource_aws_guardduty_detector.go index f3cd98b16bd6..cf9b6bd6da4c 100644 --- a/aws/resource_aws_guardduty_detector.go +++ b/aws/resource_aws_guardduty_detector.go @@ -45,6 +45,32 @@ func resourceAwsGuardDutyDetector() *schema.Resource { Optional: true, Computed: true, }, + + "datasources": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + 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, + }, + }, + }, + }, + }, + }, + }, + "tags": tagsSchema(), "tags_all": tagsSchemaComputed(), @@ -67,6 +93,10 @@ func resourceAwsGuardDutyDetectorCreate(d *schema.ResourceData, meta interface{} input.FindingPublishingFrequency = aws.String(v.(string)) } + if v, ok := d.GetOk("datasources"); ok { + input.DataSources = expandDataSourceConfigurations(v.([]interface{})) + } + if len(tags) > 0 { input.Tags = tags.IgnoreAws().GuarddutyTags() } @@ -114,6 +144,10 @@ func resourceAwsGuardDutyDetectorRead(d *schema.ResourceData, meta interface{}) d.Set("enable", *gdo.Status == guardduty.DetectorStatusEnabled) d.Set("finding_publishing_frequency", gdo.FindingPublishingFrequency) + if err := d.Set("datasources", flattenDataSourceConfigurations(gdo.DataSources)); err != nil { + return fmt.Errorf("error setting datasources: %s", err) + } + tags := keyvaluetags.GuarddutyKeyValueTags(gdo.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig) //lintignore:AWSR002 @@ -131,13 +165,17 @@ func resourceAwsGuardDutyDetectorRead(d *schema.ResourceData, meta interface{}) func resourceAwsGuardDutyDetectorUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).guarddutyconn - if d.HasChanges("enable", "finding_publishing_frequency") { + if d.HasChanges("enable", "finding_publishing_frequency", "datasources") { input := guardduty.UpdateDetectorInput{ DetectorId: aws.String(d.Id()), Enable: aws.Bool(d.Get("enable").(bool)), FindingPublishingFrequency: aws.String(d.Get("finding_publishing_frequency").(string)), } + if d.HasChange("datasources") { + input.DataSources = expandDataSourceConfigurations(d.Get("datasources").([]interface{})) + } + log.Printf("[DEBUG] Update GuardDuty Detector: %s", input) _, err := conn.UpdateDetector(&input) if err != nil { @@ -187,3 +225,57 @@ func resourceAwsGuardDutyDetectorDelete(d *schema.ResourceData, meta interface{} return nil } + +func expandDataSourceConfigurations(dsc []interface{}) *guardduty.DataSourceConfigurations { + if len(dsc) < 1 || dsc[0] == nil { + return nil + } + + m := dsc[0].(map[string]interface{}) + + dataSourceConfigurations := &guardduty.DataSourceConfigurations{} + + if v, ok := m["s3_logs"]; ok && v != "" && (len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil) { + dataSourceConfigurations.S3Logs = expandS3LogsConfiguration(v.([]interface{})) + } + + return dataSourceConfigurations +} + +func expandS3LogsConfiguration(slc []interface{}) *guardduty.S3LogsConfiguration { + if len(slc) < 1 || slc[0] == nil { + return nil + } + + m := slc[0].(map[string]interface{}) + + s3LogsConfiguration := &guardduty.S3LogsConfiguration{ + Enable: aws.Bool(m["enable"].(bool)), + } + + return s3LogsConfiguration +} + +func flattenDataSourceConfigurations(dsc *guardduty.DataSourceConfigurationsResult) []interface{} { + if dsc == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "s3_logs": flattenS3LogsConfiguration(dsc.S3Logs), + } + + return []interface{}{m} +} + +func flattenS3LogsConfiguration(slc *guardduty.S3LogsConfigurationResult) []interface{} { + if slc == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "enable": aws.StringValue(slc.Status) == guardduty.DataSourceStatusEnabled, + } + + return []interface{}{m} +} diff --git a/aws/resource_aws_guardduty_detector_test.go b/aws/resource_aws_guardduty_detector_test.go index cdb389191266..d51c794d26b7 100644 --- a/aws/resource_aws_guardduty_detector_test.go +++ b/aws/resource_aws_guardduty_detector_test.go @@ -160,6 +160,42 @@ func testAccAwsGuardDutyDetector_tags(t *testing.T) { }) } +func testAccAwsGuardDutyDetector_datasources_s3logs(t *testing.T) { + resourceName := "aws_guardduty_detector.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, guardduty.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsGuardDutyDetectorDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGuardDutyDetectorConfigDatasourcesS3Logs(true), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsGuardDutyDetectorExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "datasources.#", "1"), + resource.TestCheckResourceAttr(resourceName, "datasources.0.s3_logs.#", "1"), + resource.TestCheckResourceAttr(resourceName, "datasources.0.s3_logs.0.enable", "true"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccGuardDutyDetectorConfigDatasourcesS3Logs(false), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsGuardDutyDetectorExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "datasources.#", "1"), + resource.TestCheckResourceAttr(resourceName, "datasources.0.s3_logs.#", "1"), + resource.TestCheckResourceAttr(resourceName, "datasources.0.s3_logs.0.enable", "false"), + ), + }, + }, + }) +} + func testAccCheckAwsGuardDutyDetectorDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).guarddutyconn @@ -239,3 +275,15 @@ resource "aws_guardduty_detector" "test" { } `, tagKey1, tagValue1, tagKey2, tagValue2) } + +func testAccGuardDutyDetectorConfigDatasourcesS3Logs(enable bool) string { + return fmt.Sprintf(` +resource "aws_guardduty_detector" "test" { + datasources { + s3_logs { + enable = %[1]t + } + } +} +`, enable) +} diff --git a/aws/resource_aws_guardduty_test.go b/aws/resource_aws_guardduty_test.go index 596655cb7aaf..9d09d258a174 100644 --- a/aws/resource_aws_guardduty_test.go +++ b/aws/resource_aws_guardduty_test.go @@ -8,10 +8,11 @@ import ( func TestAccAWSGuardDuty_serial(t *testing.T) { testCases := map[string]map[string]func(t *testing.T){ "Detector": { - "basic": testAccAwsGuardDutyDetector_basic, - "tags": testAccAwsGuardDutyDetector_tags, - "datasource_basic": testAccAWSGuarddutyDetectorDataSource_basic, - "datasource_id": testAccAWSGuarddutyDetectorDataSource_Id, + "basic": testAccAwsGuardDutyDetector_basic, + "datasources_s3logs": testAccAwsGuardDutyDetector_datasources_s3logs, + "tags": testAccAwsGuardDutyDetector_tags, + "datasource_basic": testAccAWSGuarddutyDetectorDataSource_basic, + "datasource_id": testAccAWSGuarddutyDetectorDataSource_Id, }, "Filter": { "basic": testAccAwsGuardDutyFilter_basic, From dd37fee59f777450a7a5c77c02d8512f230ac362 Mon Sep 17 00:00:00 2001 From: Farhan Angullia Date: Fri, 25 Jun 2021 02:47:47 +0800 Subject: [PATCH 2/5] docs: updated site for new args --- .../docs/r/guardduty_detector.html.markdown | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/website/docs/r/guardduty_detector.html.markdown b/website/docs/r/guardduty_detector.html.markdown index 8431be3e1b28..f9d5cf5f5bf4 100644 --- a/website/docs/r/guardduty_detector.html.markdown +++ b/website/docs/r/guardduty_detector.html.markdown @@ -17,6 +17,12 @@ Provides a resource to manage a GuardDuty detector. ```terraform resource "aws_guardduty_detector" "MyDetector" { enable = true + + datasources { + s3_logs { + enable = true + } + } } ``` @@ -26,8 +32,21 @@ The following arguments are supported: * `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. * `tags` - (Optional) Key-value map of resource tags. If configured with a provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. +### Data Sources + +The `datasources` block supports the following: + +* `s3_logs` - (Optional) Describes whether S3 data event logs are enabled as a data source. See [S3 Logs](#s3-logs) below for more details. + +### S3 Logs + +This `s3_logs` block supports the following: + +* `enable` - (Required) If true, enables [S3 Protection](https://docs.aws.amazon.com/guardduty/latest/ug/s3_detection.html). Defaults to `true`. + ## Attributes Reference In addition to all arguments above, the following attributes are exported: From c7133ae6ce4a37fe4300cbe48ab63f63126212ff Mon Sep 17 00:00:00 2001 From: Farhan Angullia Date: Fri, 25 Jun 2021 03:26:47 +0800 Subject: [PATCH 3/5] fix terrafmt for acc tests --- aws/resource_aws_guardduty_detector_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/aws/resource_aws_guardduty_detector_test.go b/aws/resource_aws_guardduty_detector_test.go index d51c794d26b7..4a3fc48a6505 100644 --- a/aws/resource_aws_guardduty_detector_test.go +++ b/aws/resource_aws_guardduty_detector_test.go @@ -279,11 +279,11 @@ resource "aws_guardduty_detector" "test" { func testAccGuardDutyDetectorConfigDatasourcesS3Logs(enable bool) string { return fmt.Sprintf(` resource "aws_guardduty_detector" "test" { - datasources { - s3_logs { - enable = %[1]t - } - } + datasources { + s3_logs { + enable = %[1]t + } + } } `, enable) } From 66433d3ccc8a2a28103ad29bcdae07955702746c Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 9 Jul 2021 16:54:30 -0400 Subject: [PATCH 4/5] Add CHANGELOG entry. --- .changelog/19954.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/19954.txt diff --git a/.changelog/19954.txt b/.changelog/19954.txt new file mode 100644 index 000000000000..1823e0216e5b --- /dev/null +++ b/.changelog/19954.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_guardduty_detector: Add `datasources` argument +``` \ No newline at end of file From 242f45c646cddd23bd6c8f78bc836bad29a4e035 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 9 Jul 2021 16:57:06 -0400 Subject: [PATCH 5/5] r/aws_guardduty_detector: More idiomatic expand/flatten functions. --- aws/resource_aws_guardduty_detector.go | 103 ++++++++++++++----------- 1 file changed, 56 insertions(+), 47 deletions(-) diff --git a/aws/resource_aws_guardduty_detector.go b/aws/resource_aws_guardduty_detector.go index cf9b6bd6da4c..23866b0eb343 100644 --- a/aws/resource_aws_guardduty_detector.go +++ b/aws/resource_aws_guardduty_detector.go @@ -25,26 +25,15 @@ func resourceAwsGuardDutyDetector() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "enable": { - Type: schema.TypeBool, - Optional: true, - Default: true, - }, "account_id": { Type: schema.TypeString, Computed: true, }, + "arn": { Type: schema.TypeString, Computed: true, }, - // finding_publishing_frequency is marked as Computed:true since - // GuardDuty member accounts inherit setting from master account - "finding_publishing_frequency": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, "datasources": { Type: schema.TypeList, @@ -71,8 +60,21 @@ func resourceAwsGuardDutyDetector() *schema.Resource { }, }, - "tags": tagsSchema(), + "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": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "tags": tagsSchema(), "tags_all": tagsSchemaComputed(), }, @@ -93,8 +95,8 @@ func resourceAwsGuardDutyDetectorCreate(d *schema.ResourceData, meta interface{} input.FindingPublishingFrequency = aws.String(v.(string)) } - if v, ok := d.GetOk("datasources"); ok { - input.DataSources = expandDataSourceConfigurations(v.([]interface{})) + if v, ok := d.GetOk("datasources"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.DataSources = expandGuardDutyDataSourceConfigurations(v.([]interface{})[0].(map[string]interface{})) } if len(tags) > 0 { @@ -141,13 +143,18 @@ func resourceAwsGuardDutyDetectorRead(d *schema.ResourceData, meta interface{}) d.Set("arn", arn) d.Set("account_id", meta.(*AWSClient).accountid) - d.Set("enable", *gdo.Status == guardduty.DetectorStatusEnabled) - d.Set("finding_publishing_frequency", gdo.FindingPublishingFrequency) - if err := d.Set("datasources", flattenDataSourceConfigurations(gdo.DataSources)); err != nil { - return fmt.Errorf("error setting datasources: %s", err) + if gdo.DataSources != nil { + if err := d.Set("datasources", []interface{}{flattenGuardDutyDataSourceConfigurationsResult(gdo.DataSources)}); err != nil { + return fmt.Errorf("error setting datasources: %w", err) + } + } else { + d.Set("datasources", nil) } + d.Set("enable", aws.StringValue(gdo.Status) == guardduty.DetectorStatusEnabled) + d.Set("finding_publishing_frequency", gdo.FindingPublishingFrequency) + tags := keyvaluetags.GuarddutyKeyValueTags(gdo.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig) //lintignore:AWSR002 @@ -165,7 +172,7 @@ func resourceAwsGuardDutyDetectorRead(d *schema.ResourceData, meta interface{}) func resourceAwsGuardDutyDetectorUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).guarddutyconn - if d.HasChanges("enable", "finding_publishing_frequency", "datasources") { + if d.HasChangesExcept("tags", "tags_all") { input := guardduty.UpdateDetectorInput{ DetectorId: aws.String(d.Id()), Enable: aws.Bool(d.Get("enable").(bool)), @@ -173,7 +180,7 @@ func resourceAwsGuardDutyDetectorUpdate(d *schema.ResourceData, meta interface{} } if d.HasChange("datasources") { - input.DataSources = expandDataSourceConfigurations(d.Get("datasources").([]interface{})) + input.DataSources = expandGuardDutyDataSourceConfigurations(d.Get("datasources").([]interface{})[0].(map[string]interface{})) } log.Printf("[DEBUG] Update GuardDuty Detector: %s", input) @@ -226,56 +233,58 @@ func resourceAwsGuardDutyDetectorDelete(d *schema.ResourceData, meta interface{} return nil } -func expandDataSourceConfigurations(dsc []interface{}) *guardduty.DataSourceConfigurations { - if len(dsc) < 1 || dsc[0] == nil { +func expandGuardDutyDataSourceConfigurations(tfMap map[string]interface{}) *guardduty.DataSourceConfigurations { + if tfMap == nil { return nil } - m := dsc[0].(map[string]interface{}) - - dataSourceConfigurations := &guardduty.DataSourceConfigurations{} + apiObject := &guardduty.DataSourceConfigurations{} - if v, ok := m["s3_logs"]; ok && v != "" && (len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil) { - dataSourceConfigurations.S3Logs = expandS3LogsConfiguration(v.([]interface{})) + if v, ok := tfMap["s3_logs"].([]interface{}); ok && len(v) > 0 { + apiObject.S3Logs = expandGuardDutyS3LogsConfiguration(v[0].(map[string]interface{})) } - return dataSourceConfigurations + return apiObject } -func expandS3LogsConfiguration(slc []interface{}) *guardduty.S3LogsConfiguration { - if len(slc) < 1 || slc[0] == nil { +func expandGuardDutyS3LogsConfiguration(tfMap map[string]interface{}) *guardduty.S3LogsConfiguration { + if tfMap == nil { return nil } - m := slc[0].(map[string]interface{}) + apiObject := &guardduty.S3LogsConfiguration{} - s3LogsConfiguration := &guardduty.S3LogsConfiguration{ - Enable: aws.Bool(m["enable"].(bool)), + if v, ok := tfMap["enable"].(bool); ok { + apiObject.Enable = aws.Bool(v) } - return s3LogsConfiguration + return apiObject } -func flattenDataSourceConfigurations(dsc *guardduty.DataSourceConfigurationsResult) []interface{} { - if dsc == nil { - return []interface{}{} +func flattenGuardDutyDataSourceConfigurationsResult(apiObject *guardduty.DataSourceConfigurationsResult) map[string]interface{} { + if apiObject == nil { + return nil } - m := map[string]interface{}{ - "s3_logs": flattenS3LogsConfiguration(dsc.S3Logs), + tfMap := map[string]interface{}{} + + if v := apiObject.S3Logs; v != nil { + tfMap["s3_logs"] = []interface{}{flattenGuardDutyS3LogsConfigurationResult(v)} } - return []interface{}{m} + return tfMap } -func flattenS3LogsConfiguration(slc *guardduty.S3LogsConfigurationResult) []interface{} { - if slc == nil { - return []interface{}{} +func flattenGuardDutyS3LogsConfigurationResult(apiObject *guardduty.S3LogsConfigurationResult) map[string]interface{} { + if apiObject == nil { + return nil } - m := map[string]interface{}{ - "enable": aws.StringValue(slc.Status) == guardduty.DataSourceStatusEnabled, + tfMap := map[string]interface{}{} + + if v := apiObject.Status; v != nil { + tfMap["enable"] = aws.StringValue(v) == guardduty.DataSourceStatusEnabled } - return []interface{}{m} + return tfMap }