Skip to content

Commit

Permalink
service/guardduty: Support GuardDuty Organizations (hashicorp#13034)
Browse files Browse the repository at this point in the history
* service/guardduty: Support GuardDuty Organizations

Reference: hashicorp#13005
Reference: hashicorp#13006

Changes:

```
FEATURES:

* **New Resource:** `aws_guardduty_organization_admin_account`
* **New Resource:** `aws_guardduty_organization_configuration`
```

Output from acceptance testing:

```
--- PASS: TestAccAWSGuardDuty (137.70s)
    --- PASS: TestAccAWSGuardDuty/Detector (58.97s)
        --- PASS: TestAccAWSGuardDuty/Detector/basic (41.45s)
        --- PASS: TestAccAWSGuardDuty/Detector/import (17.52s)
    --- PASS: TestAccAWSGuardDuty/OrganizationAdminAccount (31.74s)
        --- PASS: TestAccAWSGuardDuty/OrganizationAdminAccount/basic (31.74s)
    --- PASS: TestAccAWSGuardDuty/OrganizationConfiguration (46.99s)
        --- PASS: TestAccAWSGuardDuty/OrganizationConfiguration/basic (46.99s)
```

* resource/aws_guardduty_organization_admin_account: Adjust for review comments

Reference: hashicorp#13034 (review)

Output from acceptance testing:

```
    --- PASS: TestAccAWSGuardDuty/OrganizationAdminAccount (44.91s)
        --- PASS: TestAccAWSGuardDuty/OrganizationAdminAccount/basic (44.91s)
```
  • Loading branch information
bflad authored and adamdecaf committed May 28, 2020
1 parent 518ec42 commit 0480fa8
Show file tree
Hide file tree
Showing 12 changed files with 642 additions and 4 deletions.
59 changes: 59 additions & 0 deletions aws/internal/service/guardduty/waiter/status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package waiter

import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/guardduty"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
)

const (
// AdminStatus NotFound
AdminStatusNotFound = "NotFound"

// AdminStatus Unknown
AdminStatusUnknown = "Unknown"
)

// AdminAccountAdminStatus fetches the AdminAccount and its AdminStatus
func AdminAccountAdminStatus(conn *guardduty.GuardDuty, adminAccountID string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
adminAccount, err := getOrganizationAdminAccount(conn, adminAccountID)

if err != nil {
return nil, AdminStatusUnknown, err
}

if adminAccount == nil {
return adminAccount, AdminStatusNotFound, nil
}

return adminAccount, aws.StringValue(adminAccount.AdminStatus), nil
}
}

// TODO: Migrate to shared internal package for aws package and this package
func getOrganizationAdminAccount(conn *guardduty.GuardDuty, adminAccountID string) (*guardduty.AdminAccount, error) {
input := &guardduty.ListOrganizationAdminAccountsInput{}
var result *guardduty.AdminAccount

err := conn.ListOrganizationAdminAccountsPages(input, func(page *guardduty.ListOrganizationAdminAccountsOutput, lastPage bool) bool {
if page == nil {
return !lastPage
}

for _, adminAccount := range page.AdminAccounts {
if adminAccount == nil {
continue
}

if aws.StringValue(adminAccount.AdminAccountId) == adminAccountID {
result = adminAccount
return false
}
}

return !lastPage
})

return result, err
}
59 changes: 59 additions & 0 deletions aws/internal/service/guardduty/waiter/waiter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package waiter

import (
"time"

"github.com/aws/aws-sdk-go/service/guardduty"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
)

const (
// Maximum amount of time to wait for an AdminAccount to return Enabled
AdminAccountEnabledTimeout = 5 * time.Minute

// Maximum amount of time to wait for an AdminAccount to return NotFound
AdminAccountNotFoundTimeout = 5 * time.Minute

// Maximum amount of time to wait for membership to propagate
// When removing Organization Admin Accounts, there is eventual
// consistency even after the account is no longer listed.
// Reference error message:
// BadRequestException: The request is rejected because the current account cannot delete detector while it has invited or associated members.
MembershipPropagationTimeout = 2 * time.Minute
)

// AdminAccountEnabled waits for an AdminAccount to return Enabled
func AdminAccountEnabled(conn *guardduty.GuardDuty, adminAccountID string) (*guardduty.AdminAccount, error) {
stateConf := &resource.StateChangeConf{
Pending: []string{AdminStatusNotFound},
Target: []string{guardduty.AdminStatusEnabled},
Refresh: AdminAccountAdminStatus(conn, adminAccountID),
Timeout: AdminAccountNotFoundTimeout,
}

outputRaw, err := stateConf.WaitForState()

if output, ok := outputRaw.(*guardduty.AdminAccount); ok {
return output, err
}

return nil, err
}

// AdminAccountNotFound waits for an AdminAccount to return NotFound
func AdminAccountNotFound(conn *guardduty.GuardDuty, adminAccountID string) (*guardduty.AdminAccount, error) {
stateConf := &resource.StateChangeConf{
Pending: []string{guardduty.AdminStatusDisableInProgress},
Target: []string{AdminStatusNotFound},
Refresh: AdminAccountAdminStatus(conn, adminAccountID),
Timeout: AdminAccountNotFoundTimeout,
}

outputRaw, err := stateConf.WaitForState()

if output, ok := outputRaw.(*guardduty.AdminAccount); ok {
return output, err
}

return nil, err
}
2 changes: 2 additions & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,8 @@ func Provider() terraform.ResourceProvider {
"aws_guardduty_invite_accepter": resourceAwsGuardDutyInviteAccepter(),
"aws_guardduty_ipset": resourceAwsGuardDutyIpset(),
"aws_guardduty_member": resourceAwsGuardDutyMember(),
"aws_guardduty_organization_admin_account": resourceAwsGuardDutyOrganizationAdminAccount(),
"aws_guardduty_organization_configuration": resourceAwsGuardDutyOrganizationConfiguration(),
"aws_guardduty_threatintelset": resourceAwsGuardDutyThreatintelset(),
"aws_iam_access_key": resourceAwsIamAccessKey(),
"aws_iam_account_alias": resourceAwsIamAccountAlias(),
Expand Down
28 changes: 24 additions & 4 deletions aws/resource_aws_guardduty_detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/guardduty"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/guardduty/waiter"
)

func resourceAwsGuardDutyDetector() *schema.Resource {
Expand Down Expand Up @@ -106,14 +108,32 @@ func resourceAwsGuardDutyDetectorUpdate(d *schema.ResourceData, meta interface{}

func resourceAwsGuardDutyDetectorDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).guarddutyconn
input := guardduty.DeleteDetectorInput{

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

log.Printf("[DEBUG] Delete GuardDuty Detector: %s", input)
_, err := conn.DeleteDetector(&input)
err := resource.Retry(waiter.MembershipPropagationTimeout, func() *resource.RetryError {
_, err := conn.DeleteDetector(input)

if isAWSErr(err, guardduty.ErrCodeBadRequestException, "cannot delete detector while it has invited or associated members") {
return resource.RetryableError(err)
}

if err != nil {
return resource.NonRetryableError(err)
}

return nil
})

if isResourceTimeoutError(err) {
_, err = conn.DeleteDetector(input)
}

if err != nil {
return fmt.Errorf("Deleting GuardDuty Detector '%s' failed: %s", d.Id(), err.Error())
return fmt.Errorf("error deleting GuardDuty Detector (%s): %w", d.Id(), err)
}

return nil
}
122 changes: 122 additions & 0 deletions aws/resource_aws_guardduty_organization_admin_account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package aws

import (
"fmt"
"log"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/guardduty"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/guardduty/waiter"
)

func resourceAwsGuardDutyOrganizationAdminAccount() *schema.Resource {
return &schema.Resource{
Create: resourceAwsGuardDutyOrganizationAdminAccountCreate,
Read: resourceAwsGuardDutyOrganizationAdminAccountRead,
Delete: resourceAwsGuardDutyOrganizationAdminAccountDelete,

Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"admin_account_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateAwsAccountId,
},
},
}
}

func resourceAwsGuardDutyOrganizationAdminAccountCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).guarddutyconn

adminAccountID := d.Get("admin_account_id").(string)

input := &guardduty.EnableOrganizationAdminAccountInput{
AdminAccountId: aws.String(adminAccountID),
}

_, err := conn.EnableOrganizationAdminAccount(input)

if err != nil {
return fmt.Errorf("error enabling GuardDuty Organization Admin Account (%s): %w", adminAccountID, err)
}

d.SetId(adminAccountID)

if _, err := waiter.AdminAccountEnabled(conn, d.Id()); err != nil {
return fmt.Errorf("error waiting for GuardDuty Organization Admin Account (%s) to enable: %w", d.Id(), err)
}

return resourceAwsGuardDutyOrganizationAdminAccountRead(d, meta)
}

func resourceAwsGuardDutyOrganizationAdminAccountRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).guarddutyconn

adminAccount, err := getGuardDutyOrganizationAdminAccount(conn, d.Id())

if err != nil {
return fmt.Errorf("error reading GuardDuty Organization Admin Account (%s): %w", d.Id(), err)
}

if adminAccount == nil {
log.Printf("[WARN] GuardDuty Organization Admin Account (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

d.Set("admin_account_id", adminAccount.AdminAccountId)

return nil
}

func resourceAwsGuardDutyOrganizationAdminAccountDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).guarddutyconn

input := &guardduty.DisableOrganizationAdminAccountInput{
AdminAccountId: aws.String(d.Id()),
}

_, err := conn.DisableOrganizationAdminAccount(input)

if err != nil {
return fmt.Errorf("error disabling GuardDuty Organization Admin Account (%s): %w", d.Id(), err)
}

if _, err := waiter.AdminAccountNotFound(conn, d.Id()); err != nil {
return fmt.Errorf("error waiting for GuardDuty Organization Admin Account (%s) to disable: %w", d.Id(), err)
}

return nil
}

func getGuardDutyOrganizationAdminAccount(conn *guardduty.GuardDuty, adminAccountID string) (*guardduty.AdminAccount, error) {
input := &guardduty.ListOrganizationAdminAccountsInput{}
var result *guardduty.AdminAccount

err := conn.ListOrganizationAdminAccountsPages(input, func(page *guardduty.ListOrganizationAdminAccountsOutput, lastPage bool) bool {
if page == nil {
return !lastPage
}

for _, adminAccount := range page.AdminAccounts {
if adminAccount == nil {
continue
}

if aws.StringValue(adminAccount.AdminAccountId) == adminAccountID {
result = adminAccount
return false
}
}

return !lastPage
})

return result, err
}
Loading

0 comments on commit 0480fa8

Please sign in to comment.