diff --git a/aws/resource_aws_cloudtrail.go b/aws/resource_aws_cloudtrail.go index 78d6828f3cb..ded9e0be917 100644 --- a/aws/resource_aws_cloudtrail.go +++ b/aws/resource_aws_cloudtrail.go @@ -9,6 +9,7 @@ import ( "github.com/aws/aws-sdk-go/service/cloudtrail" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" ) func resourceAwsCloudTrail() *schema.Resource { @@ -72,6 +73,51 @@ func resourceAwsCloudTrail() *schema.Resource { Optional: true, ValidateFunc: validateArn, }, + "event_selector": { + Type: schema.TypeList, + Optional: true, + MaxItems: 5, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "read_write_type": { + Type: schema.TypeString, + Optional: true, + Default: cloudtrail.ReadWriteTypeAll, + ValidateFunc: validation.StringInSlice([]string{ + cloudtrail.ReadWriteTypeAll, + cloudtrail.ReadWriteTypeReadOnly, + cloudtrail.ReadWriteTypeWriteOnly, + }, false), + }, + + "include_management_events": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + + "data_resource": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"AWS::S3::Object", "AWS::Lambda::Function"}, false), + }, + "values": { + Type: schema.TypeList, + Required: true, + MaxItems: 250, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + }, + }, + }, "home_region": { Type: schema.TypeString, Computed: true, @@ -150,6 +196,13 @@ func resourceAwsCloudTrailCreate(d *schema.ResourceData, meta interface{}) error } } + // Event Selectors + if _, ok := d.GetOk("event_selector"); ok { + if err := cloudTrailSetEventSelectors(conn, d); err != nil { + return err + } + } + return resourceAwsCloudTrailUpdate(d, meta) } @@ -227,6 +280,18 @@ func resourceAwsCloudTrailRead(d *schema.ResourceData, meta interface{}) error { } d.Set("enable_logging", logstatus) + // Get EventSelectors + eventSelectorsOut, err := conn.GetEventSelectors(&cloudtrail.GetEventSelectorsInput{ + TrailName: aws.String(d.Id()), + }) + if err != nil { + return err + } + + if err := d.Set("event_selector", flattenAwsCloudTrailEventSelector(eventSelectorsOut.EventSelectors)); err != nil { + return err + } + return nil } @@ -300,6 +365,13 @@ func resourceAwsCloudTrailUpdate(d *schema.ResourceData, meta interface{}) error } } + if !d.IsNewResource() && d.HasChange("event_selector") { + log.Printf("[DEBUG] Updating event selector on CloudTrail: %s", input) + if err := cloudTrailSetEventSelectors(conn, d); err != nil { + return err + } + } + log.Printf("[DEBUG] CloudTrail updated: %s", t) return resourceAwsCloudTrailRead(d, meta) @@ -357,3 +429,98 @@ func cloudTrailSetLogging(conn *cloudtrail.CloudTrail, enabled bool, id string) return nil } + +func cloudTrailSetEventSelectors(conn *cloudtrail.CloudTrail, d *schema.ResourceData) error { + input := &cloudtrail.PutEventSelectorsInput{ + TrailName: aws.String(d.Id()), + } + + eventSelectors := expandAwsCloudTrailEventSelector(d.Get("event_selector").([]interface{})) + input.EventSelectors = eventSelectors + + if err := input.Validate(); err != nil { + return fmt.Errorf("Error validate CloudTrail (%s): %s", d.Id(), err) + } + + _, err := conn.PutEventSelectors(input) + if err != nil { + return fmt.Errorf("Error set event selector on CloudTrail (%s): %s", d.Id(), err) + } + + return nil +} + +func expandAwsCloudTrailEventSelector(configured []interface{}) []*cloudtrail.EventSelector { + eventSelectors := make([]*cloudtrail.EventSelector, 0, len(configured)) + + for _, raw := range configured { + data := raw.(map[string]interface{}) + dataResources := expandAwsCloudTrailEventSelectorDataResource(data["data_resource"].([]interface{})) + + es := &cloudtrail.EventSelector{ + IncludeManagementEvents: aws.Bool(data["include_management_events"].(bool)), + ReadWriteType: aws.String(data["read_write_type"].(string)), + DataResources: dataResources, + } + eventSelectors = append(eventSelectors, es) + } + + return eventSelectors +} + +func expandAwsCloudTrailEventSelectorDataResource(configured []interface{}) []*cloudtrail.DataResource { + dataResources := make([]*cloudtrail.DataResource, 0, len(configured)) + + for _, raw := range configured { + data := raw.(map[string]interface{}) + + values := make([]*string, len(data["values"].([]interface{}))) + for i, vv := range data["values"].([]interface{}) { + str := vv.(string) + values[i] = aws.String(str) + } + + dataResource := &cloudtrail.DataResource{ + Type: aws.String(data["type"].(string)), + Values: values, + } + + dataResources = append(dataResources, dataResource) + } + + return dataResources +} + +func flattenAwsCloudTrailEventSelector(configured []*cloudtrail.EventSelector) []map[string]interface{} { + eventSelectors := make([]map[string]interface{}, 0, len(configured)) + + // Prevent default configurations shows differences + if len(configured) == 1 && len(configured[0].DataResources) == 0 { + return eventSelectors + } + + for _, raw := range configured { + item := make(map[string]interface{}) + item["read_write_type"] = *raw.ReadWriteType + item["include_management_events"] = *raw.IncludeManagementEvents + item["data_resource"] = flattenAwsCloudTrailEventSelectorDataResource(raw.DataResources) + + eventSelectors = append(eventSelectors, item) + } + + return eventSelectors +} + +func flattenAwsCloudTrailEventSelectorDataResource(configured []*cloudtrail.DataResource) []map[string]interface{} { + dataResources := make([]map[string]interface{}, 0, len(configured)) + + for _, raw := range configured { + item := make(map[string]interface{}) + item["type"] = *raw.Type + item["values"] = flattenStringList(raw.Values) + + dataResources = append(dataResources, item) + } + + return dataResources +} diff --git a/aws/resource_aws_cloudtrail_test.go b/aws/resource_aws_cloudtrail_test.go index 2dd1c42c572..292db065a2e 100644 --- a/aws/resource_aws_cloudtrail_test.go +++ b/aws/resource_aws_cloudtrail_test.go @@ -24,6 +24,7 @@ func TestAccAWSCloudTrail(t *testing.T) { "logValidation": testAccAWSCloudTrail_logValidation, "kmsKey": testAccAWSCloudTrail_kmsKey, "tags": testAccAWSCloudTrail_tags, + "eventSelector": testAccAWSCloudTrail_event_selector, }, } @@ -308,6 +309,54 @@ func TestAccAWSCloudTrail_include_global_service_events(t *testing.T) { }) } +func testAccAWSCloudTrail_event_selector(t *testing.T) { + cloudTrailRandInt := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCloudTrailDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCloudTrailConfig_eventSelector(cloudTrailRandInt), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("aws_cloudtrail.foobar", "event_selector.#", "1"), + resource.TestCheckResourceAttr("aws_cloudtrail.foobar", "event_selector.0.data_resource.#", "1"), + resource.TestCheckResourceAttr("aws_cloudtrail.foobar", "event_selector.0.data_resource.0.type", "AWS::S3::Object"), + resource.TestCheckResourceAttr("aws_cloudtrail.foobar", "event_selector.0.data_resource.0.values.#", "2"), + resource.TestMatchResourceAttr("aws_cloudtrail.foobar", "event_selector.0.data_resource.0.values.0", regexp.MustCompile(`^arn:[^:]+:s3:::.+/foobar$`)), + resource.TestMatchResourceAttr("aws_cloudtrail.foobar", "event_selector.0.data_resource.0.values.1", regexp.MustCompile(`^arn:[^:]+:s3:::.+/baz$`)), + resource.TestCheckResourceAttr("aws_cloudtrail.foobar", "event_selector.0.include_management_events", "false"), + resource.TestCheckResourceAttr("aws_cloudtrail.foobar", "event_selector.0.read_write_type", "ReadOnly"), + ), + }, + { + Config: testAccAWSCloudTrailConfig_eventSelectorModified(cloudTrailRandInt), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("aws_cloudtrail.foobar", "event_selector.#", "2"), + resource.TestCheckResourceAttr("aws_cloudtrail.foobar", "event_selector.0.data_resource.#", "1"), + resource.TestCheckResourceAttr("aws_cloudtrail.foobar", "event_selector.0.data_resource.0.type", "AWS::S3::Object"), + resource.TestCheckResourceAttr("aws_cloudtrail.foobar", "event_selector.0.data_resource.0.values.#", "2"), + resource.TestMatchResourceAttr("aws_cloudtrail.foobar", "event_selector.0.data_resource.0.values.0", regexp.MustCompile(`^arn:[^:]+:s3:::.+/foobar$`)), + resource.TestMatchResourceAttr("aws_cloudtrail.foobar", "event_selector.0.data_resource.0.values.1", regexp.MustCompile(`^arn:[^:]+:s3:::.+/baz$`)), + resource.TestCheckResourceAttr("aws_cloudtrail.foobar", "event_selector.0.include_management_events", "true"), + resource.TestCheckResourceAttr("aws_cloudtrail.foobar", "event_selector.0.read_write_type", "ReadOnly"), + resource.TestCheckResourceAttr("aws_cloudtrail.foobar", "event_selector.1.data_resource.#", "2"), + resource.TestCheckResourceAttr("aws_cloudtrail.foobar", "event_selector.1.data_resource.0.type", "AWS::S3::Object"), + resource.TestCheckResourceAttr("aws_cloudtrail.foobar", "event_selector.1.data_resource.0.values.#", "2"), + resource.TestMatchResourceAttr("aws_cloudtrail.foobar", "event_selector.1.data_resource.0.values.0", regexp.MustCompile(`^arn:[^:]+:s3:::.+/tf1$`)), + resource.TestMatchResourceAttr("aws_cloudtrail.foobar", "event_selector.1.data_resource.0.values.1", regexp.MustCompile(`^arn:[^:]+:s3:::.+/tf2$`)), + resource.TestCheckResourceAttr("aws_cloudtrail.foobar", "event_selector.1.data_resource.1.type", "AWS::Lambda::Function"), + resource.TestCheckResourceAttr("aws_cloudtrail.foobar", "event_selector.1.data_resource.1.values.#", "1"), + resource.TestMatchResourceAttr("aws_cloudtrail.foobar", "event_selector.1.data_resource.1.values.0", regexp.MustCompile(`^arn:[^:]+:lambda:.+:tf-test-trail-event-select-\d+$`)), + resource.TestCheckResourceAttr("aws_cloudtrail.foobar", "event_selector.1.include_management_events", "false"), + resource.TestCheckResourceAttr("aws_cloudtrail.foobar", "event_selector.1.read_write_type", "All"), + ), + }, + }, + }) +} + func testAccCheckCloudTrailExists(n string, trail *cloudtrail.Trail) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -1029,3 +1078,166 @@ func testAccAWSCloudTrailConfig_tagsModifiedAgain(cloudTrailRandInt int) string return fmt.Sprintf(testAccAWSCloudTrailConfig_tags_tpl, cloudTrailRandInt, "", cloudTrailRandInt, cloudTrailRandInt, cloudTrailRandInt) } + +func testAccAWSCloudTrailConfig_eventSelector(cloudTrailRandInt int) string { + return fmt.Sprintf(` +resource "aws_cloudtrail" "foobar" { + name = "tf-trail-foobar-%d" + s3_bucket_name = "${aws_s3_bucket.foo.id}" + + event_selector { + read_write_type = "ReadOnly" + include_management_events = false + + data_resource { + type = "AWS::S3::Object" + values = [ + "${aws_s3_bucket.bar.arn}/foobar", + "${aws_s3_bucket.bar.arn}/baz", + ] + } + } +} + +resource "aws_s3_bucket" "foo" { + bucket = "tf-test-trail-%d" + force_destroy = true + policy = <