Skip to content

Commit

Permalink
Merge pull request #33913 from hashicorp/f-aws_guardduty_organization…
Browse files Browse the repository at this point in the history
…_configuration.features

New resource: `aws_guardduty_organization_configuration_feature`
  • Loading branch information
ewbankkit committed Oct 12, 2023
2 parents f3794b1 + 6956b3b commit 1477108
Show file tree
Hide file tree
Showing 9 changed files with 678 additions and 95 deletions.
3 changes: 3 additions & 0 deletions .changelog/33913.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
aws_guardduty_organization_configuration_feature
```
4 changes: 4 additions & 0 deletions internal/service/guardduty/detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,10 @@ func FindDetectorByID(ctx context.Context, conn *guardduty.GuardDuty, id string)
return nil, err
}

if output == nil {
return nil, tfresource.NewEmptyResultError(input)
}

return output, nil
}

Expand Down
5 changes: 5 additions & 0 deletions internal/service/guardduty/guardduty_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ func TestAccGuardDuty_serial(t *testing.T) {
"kubernetes": testAccOrganizationConfiguration_kubernetes,
"malwareProtection": testAccOrganizationConfiguration_malwareprotection,
},
"OrganizationConfigurationFeature": {
"basic": testAccOrganizationConfigurationFeature_basic,
"additional_configuration": testAccOrganizationConfigurationFeature_additionalConfiguration,
"multiple": testAccOrganizationConfigurationFeature_multiple,
},
"ThreatIntelSet": {
"basic": testAccThreatIntelSet_basic,
"tags": testAccThreatIntelSet_tags,
Expand Down
114 changes: 69 additions & 45 deletions internal/service/guardduty/organization_configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,20 @@ import (
"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/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
"github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
)

// @SDKResource("aws_guardduty_organization_configuration")
// @SDKResource("aws_guardduty_organization_configuration", name="Organization Configuration")
func ResourceOrganizationConfiguration() *schema.Resource {
return &schema.Resource{
CreateWithoutTimeout: resourceOrganizationConfigurationUpdate,
CreateWithoutTimeout: resourceOrganizationConfigurationPut,
ReadWithoutTimeout: resourceOrganizationConfigurationRead,
UpdateWithoutTimeout: resourceOrganizationConfigurationUpdate,
UpdateWithoutTimeout: resourceOrganizationConfigurationPut,
DeleteWithoutTimeout: schema.NoopContext,

Importer: &schema.ResourceImporter{
Expand All @@ -38,36 +40,20 @@ func ResourceOrganizationConfiguration() *schema.Resource {
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": {
Type: schema.TypeList,
Optional: true,
Computed: true,
MaxItems: 1,
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{
"auto_enable": {
Type: schema.TypeBool,
Required: true,
},
},
},
},
"kubernetes": {
Type: schema.TypeList,
Optional: true,
Expand Down Expand Up @@ -123,10 +109,23 @@ func ResourceOrganizationConfiguration() *schema.Resource {
},
},
},
"s3_logs": {
Type: schema.TypeList,
Optional: true,
Computed: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"auto_enable": {
Type: schema.TypeBool,
Required: true,
},
},
},
},
},
},
},

"detector_id": {
Type: schema.TypeString,
Required: true,
Expand Down Expand Up @@ -166,12 +165,11 @@ func ResourceOrganizationConfiguration() *schema.Resource {
}
}

func resourceOrganizationConfigurationUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
func resourceOrganizationConfigurationPut(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
var diags diag.Diagnostics
conn := meta.(*conns.AWSClient).GuardDutyConn(ctx)

detectorID := d.Get("detector_id").(string)

input := &guardduty.UpdateOrganizationConfigurationInput{
AutoEnableOrganizationMembers: aws.String(d.Get("auto_enable_organization_members").(string)),
DetectorId: aws.String(detectorID),
Expand All @@ -181,13 +179,20 @@ func resourceOrganizationConfigurationUpdate(ctx context.Context, d *schema.Reso
input.DataSources = expandOrganizationDataSourceConfigurations(v.([]interface{})[0].(map[string]interface{}))
}

// We have seen occasional acceptance test failures when updating multiple features on the same detector concurrently,
// so use a mutex to ensure that multiple features being updated concurrently don't trample on each other.
conns.GlobalMutexKV.Lock(detectorID)
defer conns.GlobalMutexKV.Unlock(detectorID)

_, err := conn.UpdateOrganizationConfigurationWithContext(ctx, input)

if err != nil {
return sdkdiag.AppendErrorf(diags, "updating GuardDuty Organization Configuration (%s): %s", detectorID, err)
}

d.SetId(detectorID)
if d.IsNewResource() {
d.SetId(detectorID)
}

return append(diags, resourceOrganizationConfigurationRead(ctx, d, meta)...)
}
Expand All @@ -196,13 +201,9 @@ func resourceOrganizationConfigurationRead(ctx context.Context, d *schema.Resour
var diags diag.Diagnostics
conn := meta.(*conns.AWSClient).GuardDutyConn(ctx)

input := &guardduty.DescribeOrganizationConfigurationInput{
DetectorId: aws.String(d.Id()),
}

output, err := conn.DescribeOrganizationConfigurationWithContext(ctx, input)
output, err := FindOrganizationConfigurationByID(ctx, conn, d.Id())

if tfawserr.ErrMessageContains(err, guardduty.ErrCodeBadRequestException, "The request is rejected because the input detectorId is not owned by the current account.") {
if !d.IsNewResource() && tfresource.NotFound(err) {
log.Printf("[WARN] GuardDuty Organization Configuration (%s) not found, removing from state", d.Id())
d.SetId("")
return diags
Expand All @@ -218,15 +219,13 @@ 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 {
return sdkdiag.AppendErrorf(diags, "setting datasources: %s", err)
}
} else {
d.Set("datasources", nil)
}

d.Set("detector_id", d.Id())

return diags
Expand All @@ -239,10 +238,6 @@ func expandOrganizationDataSourceConfigurations(tfMap map[string]interface{}) *g

apiObject := &guardduty.OrganizationDataSourceConfigurations{}

if v, ok := tfMap["s3_logs"].([]interface{}); ok && len(v) > 0 {
apiObject.S3Logs = expandOrganizationS3LogsConfiguration(v[0].(map[string]interface{}))
}

if v, ok := tfMap["kubernetes"].([]interface{}); ok && len(v) > 0 {
apiObject.Kubernetes = expandOrganizationKubernetesConfiguration(v[0].(map[string]interface{}))
}
Expand All @@ -251,6 +246,10 @@ func expandOrganizationDataSourceConfigurations(tfMap map[string]interface{}) *g
apiObject.MalwareProtection = expandOrganizationMalwareProtectionConfiguration(v[0].(map[string]interface{}))
}

if v, ok := tfMap["s3_logs"].([]interface{}); ok && len(v) > 0 {
apiObject.S3Logs = expandOrganizationS3LogsConfiguration(v[0].(map[string]interface{}))
}

return apiObject
}

Expand Down Expand Up @@ -304,11 +303,11 @@ func expandOrganizationMalwareProtectionConfiguration(tfMap map[string]interface
}

return &guardduty.OrganizationMalwareProtectionConfiguration{
ScanEc2InstanceWithFindings: expandOrganizationScanEC2InstanceWithFindingsConfiguration(m),
ScanEc2InstanceWithFindings: expandOrganizationScanEc2InstanceWithFindings(m),
}
}

func expandOrganizationScanEC2InstanceWithFindingsConfiguration(tfMap map[string]interface{}) *guardduty.OrganizationScanEc2InstanceWithFindings {
func expandOrganizationScanEc2InstanceWithFindings(tfMap map[string]interface{}) *guardduty.OrganizationScanEc2InstanceWithFindings { // nosemgrep:ci.caps3-in-func-name
if tfMap == nil {
return nil
}
Expand All @@ -324,11 +323,11 @@ func expandOrganizationScanEC2InstanceWithFindingsConfiguration(tfMap map[string
}

return &guardduty.OrganizationScanEc2InstanceWithFindings{
EbsVolumes: expandOrganizationEBSVolumesConfiguration(m),
EbsVolumes: expandOrganizationEbsVolumes(m),
}
}

func expandOrganizationEBSVolumesConfiguration(tfMap map[string]interface{}) *guardduty.OrganizationEbsVolumes {
func expandOrganizationEbsVolumes(tfMap map[string]interface{}) *guardduty.OrganizationEbsVolumes { // nosemgrep:ci.caps3-in-func-name
if tfMap == nil {
return nil
}
Expand Down Expand Up @@ -397,13 +396,13 @@ func flattenOrganizationKubernetesConfigurationResult(apiObject *guardduty.Organ
tfMap := map[string]interface{}{}

if v := apiObject.AuditLogs; v != nil {
tfMap["audit_logs"] = []interface{}{flattenOrganizationKubernetesAuditLogsConfiguration(v)}
tfMap["audit_logs"] = []interface{}{flattenOrganizationKubernetesAuditLogsConfigurationResult(v)}
}

return tfMap
}

func flattenOrganizationKubernetesAuditLogsConfiguration(apiObject *guardduty.OrganizationKubernetesAuditLogsConfigurationResult) map[string]interface{} {
func flattenOrganizationKubernetesAuditLogsConfigurationResult(apiObject *guardduty.OrganizationKubernetesAuditLogsConfigurationResult) map[string]interface{} {
if apiObject == nil {
return nil
}
Expand All @@ -425,27 +424,27 @@ func flattenOrganizationMalwareProtectionConfigurationResult(apiObject *guarddut
tfMap := map[string]interface{}{}

if v := apiObject.ScanEc2InstanceWithFindings; v != nil {
tfMap["scan_ec2_instance_with_findings"] = []interface{}{flattenOrganizationMalwareProtectionScanEC2InstanceWithFindingsResult(v)}
tfMap["scan_ec2_instance_with_findings"] = []interface{}{flattenOrganizationScanEc2InstanceWithFindingsResult(v)}
}

return tfMap
}

func flattenOrganizationMalwareProtectionScanEC2InstanceWithFindingsResult(apiObject *guardduty.OrganizationScanEc2InstanceWithFindingsResult) map[string]interface{} {
func flattenOrganizationScanEc2InstanceWithFindingsResult(apiObject *guardduty.OrganizationScanEc2InstanceWithFindingsResult) map[string]interface{} { // nosemgrep:ci.caps3-in-func-name
if apiObject == nil {
return nil
}

tfMap := map[string]interface{}{}

if v := apiObject.EbsVolumes; v != nil {
tfMap["ebs_volumes"] = []interface{}{flattenOrganizationMalwareProtectionEBSVolumesResult(v)}
tfMap["ebs_volumes"] = []interface{}{flattenOrganizationEbsVolumesResult(v)}
}

return tfMap
}

func flattenOrganizationMalwareProtectionEBSVolumesResult(apiObject *guardduty.OrganizationEbsVolumesResult) map[string]interface{} {
func flattenOrganizationEbsVolumesResult(apiObject *guardduty.OrganizationEbsVolumesResult) map[string]interface{} { // nosemgrep:ci.caps3-in-func-name
if apiObject == nil {
return nil
}
Expand All @@ -458,3 +457,28 @@ func flattenOrganizationMalwareProtectionEBSVolumesResult(apiObject *guardduty.O

return tfMap
}

func FindOrganizationConfigurationByID(ctx context.Context, conn *guardduty.GuardDuty, id string) (*guardduty.DescribeOrganizationConfigurationOutput, error) {
input := &guardduty.DescribeOrganizationConfigurationInput{
DetectorId: aws.String(id),
}

output, err := conn.DescribeOrganizationConfigurationWithContext(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
}

if output == nil {
return nil, tfresource.NewEmptyResultError(input)
}

return output, nil
}
Loading

0 comments on commit 1477108

Please sign in to comment.