Skip to content

Commit

Permalink
New Resource: aws_guardduty_publishing_destination (#13894)
Browse files Browse the repository at this point in the history
* Implementation of resource new resource type for GuardDuty S3 Export (#10920)

* Added tests and documentation

* Fixed test namings

* Fixed linter issues and removed explicit import test case

* Fixed HCL formatting in documentation

* Fixed some namings and sidebar link

* Update/refactor publishing destination (#1)

* Merged latest master changes and resolved conflicts

* Merged from Upstream master and squashed commits

* Delete defaults.go

* Removed changes from vendor subdirectory and synched with master.

* Refactor GuardDuty to waiter pattern

* Remove unused constant

* Small refactor based on PR review

* Add disappears test and refactor based on PR review

* Refactor based on PR review

* Refactor according to PR review

* Additional fix wrt tf 0.12

Co-authored-by: Sebastian Häpe <[email protected]>
Co-authored-by: shaepe <[email protected]>
  • Loading branch information
3 people authored Aug 25, 2020
1 parent ad72a4d commit 3096084
Show file tree
Hide file tree
Showing 7 changed files with 667 additions and 2 deletions.
40 changes: 40 additions & 0 deletions aws/internal/service/guardduty/waiter/waiter.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package waiter
import (
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/guardduty"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)
Expand All @@ -14,12 +15,19 @@ const (
// Maximum amount of time to wait for an AdminAccount to return NotFound
AdminAccountNotFoundTimeout = 5 * time.Minute

// Maximum amount of time to wait for a PublishingDestination to return Publishing
PublishingDestinationCreatedTimeout = 5 * time.Minute
PublishingDestinationCreatedMinTimeout = 3 * time.Second

// 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

// Constants not currently provided by the AWS Go SDK
guardDutyPublishingStatusFailed = "FAILED"
)

// AdminAccountEnabled waits for an AdminAccount to return Enabled
Expand Down Expand Up @@ -57,3 +65,35 @@ func AdminAccountNotFound(conn *guardduty.GuardDuty, adminAccountID string) (*gu

return nil, err
}

// PublishingDestinationCreated waits for GuardDuty to return Publishing
func PublishingDestinationCreated(conn *guardduty.GuardDuty, destinationID, detectorID string) (*guardduty.CreatePublishingDestinationOutput, error) {
stateConf := &resource.StateChangeConf{
Pending: []string{guardduty.PublishingStatusPendingVerification},
Target: []string{guardduty.PublishingStatusPublishing},
Refresh: guardDutyPublishingDestinationRefreshStatusFunc(conn, destinationID, detectorID),
Timeout: PublishingDestinationCreatedTimeout,
}

outputRaw, err := stateConf.WaitForState()

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

return nil, err
}

func guardDutyPublishingDestinationRefreshStatusFunc(conn *guardduty.GuardDuty, destinationID, detectorID string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
input := &guardduty.DescribePublishingDestinationInput{
DetectorId: aws.String(detectorID),
DestinationId: aws.String(destinationID),
}
resp, err := conn.DescribePublishingDestination(input)
if err != nil {
return nil, guardDutyPublishingStatusFailed, err
}
return resp, aws.StringValue(resp.Status), nil
}
}
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,7 @@ func Provider() *schema.Provider {
"aws_glue_trigger": resourceAwsGlueTrigger(),
"aws_glue_workflow": resourceAwsGlueWorkflow(),
"aws_guardduty_detector": resourceAwsGuardDutyDetector(),
"aws_guardduty_publishing_destination": resourceAwsGuardDutyPublishingDestination(),
"aws_guardduty_invite_accepter": resourceAwsGuardDutyInviteAccepter(),
"aws_guardduty_ipset": resourceAwsGuardDutyIpset(),
"aws_guardduty_member": resourceAwsGuardDutyMember(),
Expand Down
5 changes: 3 additions & 2 deletions aws/resource_aws_guardduty_detector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import (

func init() {
resource.AddTestSweepers("aws_guardduty_detector", &resource.Sweeper{
Name: "aws_guardduty_detector",
F: testSweepGuarddutyDetectors,
Name: "aws_guardduty_detector",
F: testSweepGuarddutyDetectors,
Dependencies: []string{"aws_guardduty_publishing_destination"},
})
}

Expand Down
181 changes: 181 additions & 0 deletions aws/resource_aws_guardduty_publishing_destination.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package aws

import (
"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/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/guardduty/waiter"
)

func resourceAwsGuardDutyPublishingDestination() *schema.Resource {
return &schema.Resource{
Create: resourceAwsGuardDutyPublishingDestinationCreate,
Read: resourceAwsGuardDutyPublishingDestinationRead,
Update: resourceAwsGuardDutyPublishingDestinationUpdate,
Delete: resourceAwsGuardDutyPublishingDestinationDelete,

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

Schema: map[string]*schema.Schema{
"detector_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"destination_type": {
Type: schema.TypeString,
Optional: true,
Default: guardduty.DestinationTypeS3,
ValidateFunc: validation.StringInSlice([]string{
guardduty.DestinationTypeS3,
}, false),
},
"destination_arn": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validateArn,
},
"kms_key_arn": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validateArn,
},
},
}
}

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

detectorID := d.Get("detector_id").(string)
input := guardduty.CreatePublishingDestinationInput{
DetectorId: aws.String(detectorID),
DestinationProperties: &guardduty.DestinationProperties{
DestinationArn: aws.String(d.Get("destination_arn").(string)),
KmsKeyArn: aws.String(d.Get("kms_key_arn").(string)),
},
DestinationType: aws.String(d.Get("destination_type").(string)),
}

log.Printf("[DEBUG] Creating GuardDuty publishing destination: %s", input)
output, err := conn.CreatePublishingDestination(&input)
if err != nil {
return fmt.Errorf("Creating GuardDuty publishing destination failed: %w", err)
}

_, err = waiter.PublishingDestinationCreated(conn, *output.DestinationId, detectorID)

if err != nil {
return fmt.Errorf("Error waiting for GuardDuty PublishingDestination status to be \"%s\": %w",
guardduty.PublishingStatusPublishing, err)
}

d.SetId(fmt.Sprintf("%s:%s", d.Get("detector_id"), aws.StringValue(output.DestinationId)))

return resourceAwsGuardDutyPublishingDestinationRead(d, meta)
}

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

destinationId, detectorId, errStateRead := decodeGuardDutyPublishDestinationID(d.Id())

if errStateRead != nil {
return errStateRead
}

input := &guardduty.DescribePublishingDestinationInput{
DetectorId: aws.String(detectorId),
DestinationId: aws.String(destinationId),
}

log.Printf("[DEBUG] Reading GuardDuty publishing destination: %s", input)
gdo, err := conn.DescribePublishingDestination(input)
if err != nil {
if isAWSErr(err, guardduty.ErrCodeBadRequestException, "The request is rejected because the one or more input parameters have invalid values.") {
log.Printf("[WARN] GuardDuty publishing destination: %q not found, removing from state", d.Id())
d.SetId("")
return nil
}
return fmt.Errorf("Reading GuardDuty publishing destination: '%s' failed: %w", d.Id(), err)
}

d.Set("detector_id", detectorId)
d.Set("destination_type", gdo.DestinationType)
d.Set("kms_key_arn", gdo.DestinationProperties.KmsKeyArn)
d.Set("destination_arn", gdo.DestinationProperties.DestinationArn)
return nil
}

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

destinationId, detectorId, errStateRead := decodeGuardDutyPublishDestinationID(d.Id())

if errStateRead != nil {
return errStateRead
}

input := guardduty.UpdatePublishingDestinationInput{
DestinationId: aws.String(destinationId),
DetectorId: aws.String(detectorId),
DestinationProperties: &guardduty.DestinationProperties{
DestinationArn: aws.String(d.Get("destination_arn").(string)),
KmsKeyArn: aws.String(d.Get("kms_key_arn").(string)),
},
}

log.Printf("[DEBUG] Update GuardDuty publishing destination: %s", input)
_, err := conn.UpdatePublishingDestination(&input)
if err != nil {
return fmt.Errorf("Updating GuardDuty publishing destination '%s' failed: %w", d.Id(), err)
}

return resourceAwsGuardDutyPublishingDestinationRead(d, meta)
}

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

destinationId, detectorId, errStateRead := decodeGuardDutyPublishDestinationID(d.Id())

if errStateRead != nil {
return errStateRead
}

input := guardduty.DeletePublishingDestinationInput{
DestinationId: aws.String(destinationId),
DetectorId: aws.String(detectorId),
}

log.Printf("[DEBUG] Delete GuardDuty publishing destination: %s", input)
_, err := conn.DeletePublishingDestination(&input)

if isAWSErr(err, guardduty.ErrCodeBadRequestException, "") {
return nil
}

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

return nil
}

func decodeGuardDutyPublishDestinationID(id string) (destinationID, detectorID string, err error) {
parts := strings.Split(id, ":")
if len(parts) != 2 {
err = fmt.Errorf("GuardDuty Publishing Destination ID must be of the form <Detector ID>:<Publishing Destination ID>, was provided: %s", id)
return
}
destinationID = parts[1]
detectorID = parts[0]
return
}
Loading

0 comments on commit 3096084

Please sign in to comment.