From dc4a30c7f278d4332b9598a922c7e3bd2032cd29 Mon Sep 17 00:00:00 2001 From: Paddy Carver Date: Thu, 20 Dec 2018 13:45:16 -0800 Subject: [PATCH 1/3] [Terraform] Add fine-grained audit config for projects. Add a new IAM resource level, audit_config, to go with binding, member, and policy. Implement it for the google_project resource, so IAM audit configs can be managed without managing the entire IAM policy for a project. --- .../resources/resource_iam_audit_config.go | 257 +++++++++++ ...ce_google_project_iam_audit_config_test.go | 434 ++++++++++++++++++ third_party/terraform/utils/iam.go | 3 + third_party/terraform/utils/iam_project.go | 3 +- third_party/terraform/utils/provider.go.erb | 1 + 5 files changed, 697 insertions(+), 1 deletion(-) create mode 100644 third_party/terraform/resources/resource_iam_audit_config.go create mode 100644 third_party/terraform/tests/resource_google_project_iam_audit_config_test.go diff --git a/third_party/terraform/resources/resource_iam_audit_config.go b/third_party/terraform/resources/resource_iam_audit_config.go new file mode 100644 index 000000000000..736a7eb531f8 --- /dev/null +++ b/third_party/terraform/resources/resource_iam_audit_config.go @@ -0,0 +1,257 @@ +package google + +import ( + "errors" + "fmt" + "log" + "strings" + + "github.com/hashicorp/terraform/helper/schema" + "google.golang.org/api/cloudresourcemanager/v1" +) + +var iamAuditConfigSchema = map[string]*schema.Schema{ + "service": { + Type: schema.TypeString, + Required: true, + }, + "audit_log_config": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "log_type": { + Type: schema.TypeString, + Required: true, + }, + "exempted_members": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + }, + }, + }, + }, + "etag": { + Type: schema.TypeString, + Computed: true, + }, +} + +func ResourceIamAuditConfig(parentSpecificSchema map[string]*schema.Schema, newUpdaterFunc newResourceIamUpdaterFunc) *schema.Resource { + return &schema.Resource{ + Create: resourceIamAuditConfigCreate(newUpdaterFunc), + Read: resourceIamAuditConfigRead(newUpdaterFunc), + Update: resourceIamAuditConfigUpdate(newUpdaterFunc), + Delete: resourceIamAuditConfigDelete(newUpdaterFunc), + Schema: mergeSchemas(iamAuditConfigSchema, parentSpecificSchema), + } +} + +func ResourceIamAuditConfigWithImport(parentSpecificSchema map[string]*schema.Schema, newUpdaterFunc newResourceIamUpdaterFunc, resourceIdParser resourceIdParserFunc) *schema.Resource { + r := ResourceIamAuditConfig(parentSpecificSchema, newUpdaterFunc) + r.Importer = &schema.ResourceImporter{ + State: iamAuditConfigImport(resourceIdParser), + } + return r +} + +func resourceIamAuditConfigCreate(newUpdaterFunc newResourceIamUpdaterFunc) schema.CreateFunc { + return func(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + updater, err := newUpdaterFunc(d, config) + if err != nil { + return err + } + + p := getResourceIamAuditConfig(d) + err = iamPolicyReadModifyWrite(updater, func(ep *cloudresourcemanager.Policy) error { + ep.AuditConfigs = mergeAuditConfigs(append(ep.AuditConfigs, p)) + return nil + }) + if err != nil { + return err + } + d.SetId(updater.GetResourceId() + "/audit_config/" + p.Service) + return resourceIamAuditConfigRead(newUpdaterFunc)(d, meta) + } +} + +func resourceIamAuditConfigRead(newUpdaterFunc newResourceIamUpdaterFunc) schema.ReadFunc { + return func(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + updater, err := newUpdaterFunc(d, config) + if err != nil { + return err + } + + eAuditConfig := getResourceIamAuditConfig(d) + p, err := updater.GetResourceIamPolicy() + if err != nil { + if isGoogleApiErrorWithCode(err, 404) { + log.Printf("[DEBUG]: AuditConfig for service %q not found for non-existent resource %s, removing from state file.", eAuditConfig.Service, updater.DescribeResource()) + d.SetId("") + return nil + } + + return err + } + log.Printf("[DEBUG]: Retrieved policy for %s: %+v", updater.DescribeResource(), p) + + var ac *cloudresourcemanager.AuditConfig + for _, b := range p.AuditConfigs { + if b.Service != eAuditConfig.Service { + continue + } + ac = b + break + } + if ac == nil { + log.Printf("[DEBUG]: AuditConfig for service %q not found in policy for %s, removing from state file.", eAuditConfig.Service, updater.DescribeResource()) + d.SetId("") + return nil + } + d.Set("etag", p.Etag) + err = d.Set("audit_log_config", flattenAuditLogConfigs(ac.AuditLogConfigs)) + if err != nil { + return fmt.Errorf("Error flattening audit log config: %s", err) + } + d.Set("service", ac.Service) + return nil + } +} + +func iamAuditConfigImport(resourceIdParser resourceIdParserFunc) schema.StateFunc { + return func(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + if resourceIdParser == nil { + return nil, errors.New("Import not supported for this IAM resource.") + } + config := m.(*Config) + s := strings.Fields(d.Id()) + if len(s) != 2 { + d.SetId("") + return nil, fmt.Errorf("Wrong number of parts to AuditConfig id %s; expected 'resource_name service'.", s) + } + id, service := s[0], s[1] + + // Set the ID only to the first part so all IAM types can share the same resourceIdParserFunc. + d.SetId(id) + d.Set("service", service) + err := resourceIdParser(d, config) + if err != nil { + return nil, err + } + + // Set the ID again so that the ID matches the ID it would have if it had been created via TF. + // Use the current ID in case it changed in the resourceIdParserFunc. + d.SetId(d.Id() + "/audit_config/" + service) + // It is possible to return multiple audit configs, since we can learn about all the audit configs + // for this resource here. Unfortunately, `terraform import` has some messy behavior here - + // there's no way to know at this point which resource is being imported, so it's not possible + // to order this list in a useful way. In the event of a complex set of audit configs, the user + // will have a terribly confusing set of imported resources and no way to know what matches + // up to what. And since the only users who will do a terraform import on their IAM audit configs + // are users who aren't too familiar with Google Cloud IAM (because a "create" for audit configs is + // idempotent), it's reasonable to expect that the user will be very alarmed by the plan that + // terraform will output which mentions destroying a dozen-plus audit configs. With that + // in mind, we return only the audit config that matters. + return []*schema.ResourceData{d}, nil + } +} + +func resourceIamAuditConfigUpdate(newUpdaterFunc newResourceIamUpdaterFunc) schema.UpdateFunc { + return func(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + updater, err := newUpdaterFunc(d, config) + if err != nil { + return err + } + + ac := getResourceIamAuditConfig(d) + err = iamPolicyReadModifyWrite(updater, func(p *cloudresourcemanager.Policy) error { + var found bool + for pos, b := range p.AuditConfigs { + if b.Service != ac.Service { + continue + } + found = true + p.AuditConfigs[pos] = ac + break + } + if !found { + p.AuditConfigs = append(p.AuditConfigs, ac) + } + return nil + }) + if err != nil { + return err + } + + return resourceIamAuditConfigRead(newUpdaterFunc)(d, meta) + } +} + +func resourceIamAuditConfigDelete(newUpdaterFunc newResourceIamUpdaterFunc) schema.DeleteFunc { + return func(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + updater, err := newUpdaterFunc(d, config) + if err != nil { + return err + } + + ac := getResourceIamAuditConfig(d) + err = iamPolicyReadModifyWrite(updater, func(p *cloudresourcemanager.Policy) error { + toRemove := -1 + for pos, b := range p.AuditConfigs { + if b.Service != ac.Service { + continue + } + toRemove = pos + break + } + if toRemove < 0 { + log.Printf("[DEBUG]: Policy audit configs for %s did not include an audit config for service %q", updater.DescribeResource(), ac.Service) + return nil + } + + p.AuditConfigs = append(p.AuditConfigs[:toRemove], p.AuditConfigs[toRemove+1:]...) + return nil + }) + if err != nil { + if isGoogleApiErrorWithCode(err, 404) { + log.Printf("[DEBUG]: Resource %s is missing or deleted, marking policy audit config as deleted", updater.DescribeResource()) + return nil + } + return err + } + + return resourceIamAuditConfigRead(newUpdaterFunc)(d, meta) + } +} + +func getResourceIamAuditConfig(d *schema.ResourceData) *cloudresourcemanager.AuditConfig { + auditLogConfigSet := d.Get("audit_log_config").(*schema.Set) + auditLogConfigs := make([]*cloudresourcemanager.AuditLogConfig, auditLogConfigSet.Len()) + for x, y := range auditLogConfigSet.List() { + logConfig := y.(map[string]interface{}) + auditLogConfigs[x] = &cloudresourcemanager.AuditLogConfig{ + LogType: logConfig["log_type"].(string), + ExemptedMembers: convertStringArr(logConfig["exempted_members"].(*schema.Set).List()), + } + } + return &cloudresourcemanager.AuditConfig{ + AuditLogConfigs: auditLogConfigs, + Service: d.Get("service").(string), + } +} + +func flattenAuditLogConfigs(configs []*cloudresourcemanager.AuditLogConfig) *schema.Set { + res := schema.NewSet(schema.HashResource(iamAuditConfigSchema["audit_log_config"].Elem.(*schema.Resource)), []interface{}{}) + for _, conf := range configs { + res.Add(map[string]interface{}{ + "log_type": conf.LogType, + "exempted_members": schema.NewSet(schema.HashSchema(iamAuditConfigSchema["audit_log_config"].Elem.(*schema.Resource).Schema["exempted_members"].Elem.(*schema.Schema)), convertStringArrToInterface(conf.ExemptedMembers)), + }) + } + return res +} diff --git a/third_party/terraform/tests/resource_google_project_iam_audit_config_test.go b/third_party/terraform/tests/resource_google_project_iam_audit_config_test.go new file mode 100644 index 000000000000..1a86b121138f --- /dev/null +++ b/third_party/terraform/tests/resource_google_project_iam_audit_config_test.go @@ -0,0 +1,434 @@ +package google + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func projectIamAuditConfigImportStep(resourceName, pid, service string) resource.TestStep { + return resource.TestStep{ + ResourceName: resourceName, + ImportStateId: fmt.Sprintf("%s %s", pid, service), + ImportState: true, + ImportStateVerify: true, + } +} + +// Test that an IAM audit config can be applied to a project +func TestAccProjectIamAuditConfig_basic(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + service := "cloudkms.googleapis.com" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // Create a new project + { + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccProjectExistingPolicy(pid), + ), + }, + // Apply an IAM audit config + { + Config: testAccProjectAssociateAuditConfigBasic(pid, pname, org, service), + }, + projectIamAuditConfigImportStep("google_project_iam_audit_config.acceptance", pid, service), + }, + }) +} + +// Test that multiple IAM audit configs can be applied to a project, one at a time +func TestAccProjectIamAuditConfig_multiple(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + service := "cloudkms.googleapis.com" + service2 := "cloudsql.googleapis.com" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // Create a new project + { + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccProjectExistingPolicy(pid), + ), + }, + // Apply an IAM audit config + { + Config: testAccProjectAssociateAuditConfigBasic(pid, pname, org, service), + }, + // Apply another IAM audit config + { + Config: testAccProjectAssociateAuditConfigMultiple(pid, pname, org, service, service2), + }, + projectIamAuditConfigImportStep("google_project_iam_audit_config.acceptance", pid, service), + projectIamAuditConfigImportStep("google_project_iam_audit_config.multiple", pid, service2), + }, + }) +} + +// Test that multiple IAM audit configs can be applied to a project all at once +func TestAccProjectIamAuditConfig_multipleAtOnce(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + service := "cloudkms.googleapis.com" + service2 := "cloudsql.googleapis.com" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // Create a new project + { + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccProjectExistingPolicy(pid), + ), + }, + // Apply an IAM audit config + { + Config: testAccProjectAssociateAuditConfigMultiple(pid, pname, org, service, service2), + }, + projectIamAuditConfigImportStep("google_project_iam_audit_config.acceptance", pid, service), + projectIamAuditConfigImportStep("google_project_iam_audit_config.multiple", pid, service2), + }, + }) +} + +// Test that an IAM audit config can be updated once applied to a project +func TestAccProjectIamAuditConfig_update(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + service := "cloudkms.googleapis.com" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // Create a new project + { + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccProjectExistingPolicy(pid), + ), + }, + // Apply an IAM audit config + { + Config: testAccProjectAssociateAuditConfigBasic(pid, pname, org, service), + }, + projectIamAuditConfigImportStep("google_project_iam_audit_config.acceptance", pid, service), + + // Apply an updated IAM audit config + { + Config: testAccProjectAssociateAuditConfigUpdated(pid, pname, org, service), + }, + projectIamAuditConfigImportStep("google_project_iam_audit_config.acceptance", pid, service), + + // Drop the original member + { + Config: testAccProjectAssociateAuditConfigDropMemberFromBasic(pid, pname, org, service), + }, + projectIamAuditConfigImportStep("google_project_iam_audit_config.acceptance", pid, service), + }, + }) +} + +// Test that an IAM audit config can be removed from a project +func TestAccProjectIamAuditConfig_remove(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + service := "cloudkms.googleapis.com" + service2 := "cloudsql.googleapis.com" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // Create a new project + { + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccProjectExistingPolicy(pid), + ), + }, + // Apply multiple IAM audit configs + { + Config: testAccProjectAssociateAuditConfigMultiple(pid, pname, org, service, service2), + }, + projectIamAuditConfigImportStep("google_project_iam_audit_config.acceptance", pid, service), + projectIamAuditConfigImportStep("google_project_iam_audit_config.multiple", pid, service2), + + // Remove the audit configs + { + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccProjectExistingPolicy(pid), + ), + }, + }, + }) +} + +// Test adding exempt first exempt member +func TestAccProjectIamAuditConfig_addFirstExemptMember(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + service := "cloudkms.googleapis.com" + members := []string{} + members2 := []string{"user:paddy@hashicorp.com"} + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // Create a new project + { + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccProjectExistingPolicy(pid), + ), + }, + // Apply IAM audit config with no members + { + Config: testAccProjectAssociateAuditConfigMembers(pid, pname, org, service, members), + }, + projectIamAuditConfigImportStep("google_project_iam_audit_config.acceptance", pid, service), + + // Apply IAM audit config with one member + { + Config: testAccProjectAssociateAuditConfigMembers(pid, pname, org, service, members2), + }, + projectIamAuditConfigImportStep("google_project_iam_audit_config.acceptance", pid, service), + }, + }) +} + +// test removing last exempt member +func TestAccProjectIamAuditConfig_removeLastExemptMember(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + service := "cloudkms.googleapis.com" + members2 := []string{} + members := []string{"user:paddy@hashicorp.com"} + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // Create a new project + { + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccProjectExistingPolicy(pid), + ), + }, + // Apply IAM audit config with member + { + Config: testAccProjectAssociateAuditConfigMembers(pid, pname, org, service, members), + }, + projectIamAuditConfigImportStep("google_project_iam_audit_config.acceptance", pid, service), + + // Apply IAM audit config with no members + { + Config: testAccProjectAssociateAuditConfigMembers(pid, pname, org, service, members2), + }, + projectIamAuditConfigImportStep("google_project_iam_audit_config.acceptance", pid, service), + }, + }) +} + +// test changing service with no exempt members +func TestAccProjectIamAuditConfig_updateNoExemptMembers(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + logType := "DATA_READ" + logType2 := "DATA_WRITE" + service := "cloudkms.googleapis.com" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // Create a new project + { + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccProjectExistingPolicy(pid), + ), + }, + // Apply IAM audit config with DATA_READ + { + Config: testAccProjectAssociateAuditConfigLogType(pid, pname, org, service, logType), + }, + projectIamAuditConfigImportStep("google_project_iam_audit_config.acceptance", pid, service), + + // Apply IAM audit config with DATA_WRITe + { + Config: testAccProjectAssociateAuditConfigLogType(pid, pname, org, service, logType2), + }, + projectIamAuditConfigImportStep("google_project_iam_audit_config.acceptance", pid, service), + }, + }) +} + +func testAccProjectAssociateAuditConfigBasic(pid, name, org, service string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" +} + +resource "google_project_iam_audit_config" "acceptance" { + project = "${google_project.acceptance.project_id}" + service = "%s" + audit_log_config { + log_type = "DATA_READ" + exempted_members = [ + "user:paddy@hashicorp.com", + "user:paddy@carvers.co" + ] + } +} +`, pid, name, org, service) +} + +func testAccProjectAssociateAuditConfigMultiple(pid, name, org, service, service2 string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" +} + +resource "google_project_iam_audit_config" "acceptance" { + project = "${google_project.acceptance.project_id}" + service = "%s" + audit_log_config { + log_type = "DATA_READ" + exempted_members = [ + "user:paddy@hashicorp.com", + "user:paddy@carvers.co" + ] + } +} + +resource "google_project_iam_audit_config" "multiple" { + project = "${google_project.acceptance.project_id}" + service = "%s" + audit_log_config { + log_type = "DATA_WRITE" + } +} +`, pid, name, org, service, service2) +} + +func testAccProjectAssociateAuditConfigUpdated(pid, name, org, service string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" +} + +resource "google_project_iam_audit_config" "acceptance" { + project = "${google_project.acceptance.project_id}" + service = "%s" + audit_log_config { + log_type = "DATA_WRITE" + exempted_members = [ + "user:admin@hashicorptest.com", + "user:paddy@carvers.co" + ] + } +} +`, pid, name, org, service) +} + +func testAccProjectAssociateAuditConfigDropMemberFromBasic(pid, name, org, service string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" +} + +resource "google_project_iam_audit_config" "acceptance" { + project = "${google_project.acceptance.project_id}" + service = "%s" + audit_log_config { + log_type = "DATA_READ" + exempted_members = [ + "user:paddy@hashicorp.com", + ] + } +} +`, pid, name, org, service) +} + +func testAccProjectAssociateAuditConfigMembers(pid, name, org, service string, members []string) string { + var memberStr string + if len(members) > 0 { + for pos, member := range members { + members[pos] = "\"" + member + "\"," + } + memberStr = "\n exempted_members = [" + strings.Join(members, "\n") + "\n ]" + } + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" +} + +resource "google_project_iam_audit_config" "acceptance" { + project = "${google_project.acceptance.project_id}" + service = "%s" + audit_log_config { + log_type = "DATA_READ"%s + } +} +`, pid, name, org, service, memberStr) +} + +func testAccProjectAssociateAuditConfigLogType(pid, name, org, service, logType string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" +} + +resource "google_project_iam_audit_config" "acceptance" { + project = "${google_project.acceptance.project_id}" + service = "%s" + audit_log_config { + log_type = "%s" + } +} +`, pid, name, org, service, logType) +} diff --git a/third_party/terraform/utils/iam.go b/third_party/terraform/utils/iam.go index e80d907c78f8..1b37f5d54cb8 100644 --- a/third_party/terraform/utils/iam.go +++ b/third_party/terraform/utils/iam.go @@ -191,6 +191,9 @@ func auditConfigToServiceMap(auditConfig []*cloudresourcemanager.AuditConfig) ma ac := make(map[string]map[string]map[string]bool) // Get each config for _, c := range auditConfig { + if c == nil { + continue + } // Initialize service map if _, ok := ac[c.Service]; !ok { ac[c.Service] = map[string]map[string]bool{} diff --git a/third_party/terraform/utils/iam_project.go b/third_party/terraform/utils/iam_project.go index 476a458c4a8e..4463ca4a2ad8 100644 --- a/third_party/terraform/utils/iam_project.go +++ b/third_party/terraform/utils/iam_project.go @@ -51,7 +51,8 @@ func (u *ProjectIamUpdater) GetResourceIamPolicy() (*cloudresourcemanager.Policy func (u *ProjectIamUpdater) SetResourceIamPolicy(policy *cloudresourcemanager.Policy) error { _, err := u.Config.clientResourceManager.Projects.SetIamPolicy(u.resourceId, &cloudresourcemanager.SetIamPolicyRequest{ - Policy: policy, + Policy: policy, + UpdateMask: "bindings,etag,auditConfigs", }).Do() if err != nil { diff --git a/third_party/terraform/utils/provider.go.erb b/third_party/terraform/utils/provider.go.erb index 08f7edde32f4..04412ffe304f 100644 --- a/third_party/terraform/utils/provider.go.erb +++ b/third_party/terraform/utils/provider.go.erb @@ -223,6 +223,7 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_project_iam_policy": resourceGoogleProjectIamPolicy(), "google_project_iam_binding": ResourceIamBindingWithImport(IamProjectSchema, NewProjectIamUpdater, ProjectIdParseFunc), "google_project_iam_member": ResourceIamMemberWithImport(IamProjectSchema, NewProjectIamUpdater, ProjectIdParseFunc), + "google_project_iam_audit_config": ResourceIamAuditConfigWithImport(IamProjectSchema, NewProjectIamUpdater, ProjectIdParseFunc), "google_project_service": resourceGoogleProjectService(), "google_project_iam_custom_role": resourceGoogleProjectIamCustomRole(), "google_project_organization_policy": resourceGoogleProjectOrganizationPolicy(), From b87382e5e1d2cc04741ce482612ef53f304009dc Mon Sep 17 00:00:00 2001 From: Paddy Carver Date: Fri, 21 Dec 2018 15:15:57 -0800 Subject: [PATCH 2/3] Fix review comments. --- .../terraform/resources/resource_iam_audit_config.go | 10 ---------- third_party/terraform/utils/iam.go | 3 --- third_party/terraform/utils/provider.go.erb | 2 +- 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/third_party/terraform/resources/resource_iam_audit_config.go b/third_party/terraform/resources/resource_iam_audit_config.go index 736a7eb531f8..77efa7a20264 100644 --- a/third_party/terraform/resources/resource_iam_audit_config.go +++ b/third_party/terraform/resources/resource_iam_audit_config.go @@ -145,16 +145,6 @@ func iamAuditConfigImport(resourceIdParser resourceIdParserFunc) schema.StateFun // Set the ID again so that the ID matches the ID it would have if it had been created via TF. // Use the current ID in case it changed in the resourceIdParserFunc. d.SetId(d.Id() + "/audit_config/" + service) - // It is possible to return multiple audit configs, since we can learn about all the audit configs - // for this resource here. Unfortunately, `terraform import` has some messy behavior here - - // there's no way to know at this point which resource is being imported, so it's not possible - // to order this list in a useful way. In the event of a complex set of audit configs, the user - // will have a terribly confusing set of imported resources and no way to know what matches - // up to what. And since the only users who will do a terraform import on their IAM audit configs - // are users who aren't too familiar with Google Cloud IAM (because a "create" for audit configs is - // idempotent), it's reasonable to expect that the user will be very alarmed by the plan that - // terraform will output which mentions destroying a dozen-plus audit configs. With that - // in mind, we return only the audit config that matters. return []*schema.ResourceData{d}, nil } } diff --git a/third_party/terraform/utils/iam.go b/third_party/terraform/utils/iam.go index 1b37f5d54cb8..e80d907c78f8 100644 --- a/third_party/terraform/utils/iam.go +++ b/third_party/terraform/utils/iam.go @@ -191,9 +191,6 @@ func auditConfigToServiceMap(auditConfig []*cloudresourcemanager.AuditConfig) ma ac := make(map[string]map[string]map[string]bool) // Get each config for _, c := range auditConfig { - if c == nil { - continue - } // Initialize service map if _, ok := ac[c.Service]; !ok { ac[c.Service] = map[string]map[string]bool{} diff --git a/third_party/terraform/utils/provider.go.erb b/third_party/terraform/utils/provider.go.erb index 04412ffe304f..41011ac4e8a4 100644 --- a/third_party/terraform/utils/provider.go.erb +++ b/third_party/terraform/utils/provider.go.erb @@ -223,7 +223,7 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_project_iam_policy": resourceGoogleProjectIamPolicy(), "google_project_iam_binding": ResourceIamBindingWithImport(IamProjectSchema, NewProjectIamUpdater, ProjectIdParseFunc), "google_project_iam_member": ResourceIamMemberWithImport(IamProjectSchema, NewProjectIamUpdater, ProjectIdParseFunc), - "google_project_iam_audit_config": ResourceIamAuditConfigWithImport(IamProjectSchema, NewProjectIamUpdater, ProjectIdParseFunc), + "google_project_iam_audit_config": ResourceIamAuditConfigWithImport(IamProjectSchema, NewProjectIamUpdater, ProjectIdParseFunc), "google_project_service": resourceGoogleProjectService(), "google_project_iam_custom_role": resourceGoogleProjectIamCustomRole(), "google_project_organization_policy": resourceGoogleProjectOrganizationPolicy(), From 52c9a1a136b0615b70b1248e8348c67384210e3b Mon Sep 17 00:00:00 2001 From: Modular Magician Date: Fri, 21 Dec 2018 23:43:48 +0000 Subject: [PATCH 3/3] Update tracked submodules -> HEAD on Fri Dec 21 23:43:48 UTC 2018 Tracked submodules are build/terraform-beta build/terraform build/ansible build/inspec. --- build/terraform | 2 +- build/terraform-beta | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/terraform b/build/terraform index 8e515c93631f..3ee26a1184e3 160000 --- a/build/terraform +++ b/build/terraform @@ -1 +1 @@ -Subproject commit 8e515c93631f0e4d3480987b3da613036462e276 +Subproject commit 3ee26a1184e34b86a9c1cc850f745e708cdb716b diff --git a/build/terraform-beta b/build/terraform-beta index 97025afaf9f3..7b70cdb6cbcc 160000 --- a/build/terraform-beta +++ b/build/terraform-beta @@ -1 +1 @@ -Subproject commit 97025afaf9f30c0013ab3627062edb46818a6430 +Subproject commit 7b70cdb6cbccb23035c0890e38baf42acbf46028