diff --git a/google/data_source_google_iam_policy.go b/google/data_source_google_iam_policy.go index d917c42f1b6..aedd7ce421e 100644 --- a/google/data_source_google_iam_policy.go +++ b/google/data_source_google_iam_policy.go @@ -28,6 +28,36 @@ var iamBinding *schema.Schema = &schema.Schema{ }, } +var auditConfig *schema.Schema = &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "service": { + Type: schema.TypeString, + Required: true, + }, + "audit_log_configs": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "log_type": { + Type: schema.TypeString, + Required: true, + }, + "exempted_members": { + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + }, + }, + }, + }, + }, + }, +} + // dataSourceGoogleIamPolicy returns a *schema.Resource that allows a customer // to express a Google Cloud IAM policy in a data resource. This is an example // of how the schema would be used in a config: @@ -49,6 +79,7 @@ func dataSourceGoogleIamPolicy() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "audit_config": auditConfig, }, } } @@ -58,13 +89,17 @@ func dataSourceGoogleIamPolicy() *schema.Resource { func dataSourceGoogleIamPolicyRead(d *schema.ResourceData, meta interface{}) error { var policy cloudresourcemanager.Policy var bindings []*cloudresourcemanager.Binding + var audit_configs []*cloudresourcemanager.AuditConfig // The schema supports multiple binding{} blocks bset := d.Get("binding").(*schema.Set) + aset := d.Get("audit_config").(*schema.Set) // All binding{} blocks will be converted and stored in an array bindings = make([]*cloudresourcemanager.Binding, bset.Len()) + audit_configs = make([]*cloudresourcemanager.AuditConfig, aset.Len()) policy.Bindings = bindings + policy.AuditConfigs = audit_configs // Convert each config binding into a cloudresourcemanager.Binding for i, v := range bset.List() { @@ -75,6 +110,28 @@ func dataSourceGoogleIamPolicyRead(d *schema.ResourceData, meta interface{}) err } } + // Convert each audit_config into a cloudresourcemanager.AuditConfig + for i, v := range aset.List() { + config := v.(map[string]interface{}) + + // build list of audit configs first + audit_log_config_set := config["audit_log_configs"].(*schema.Set) + // the array we're going to add to the outgoing resource + audit_log_configs := make([]*cloudresourcemanager.AuditLogConfig, audit_log_config_set.Len()) + for x, y := range audit_log_config_set.List() { + log_config := y.(map[string]interface{}) + audit_log_configs[x] = &cloudresourcemanager.AuditLogConfig{ + LogType: log_config["log_type"].(string), + ExemptedMembers: convertStringArr(log_config["exempted_members"].([]interface{})), + } + } + + policy.AuditConfigs[i] = &cloudresourcemanager.AuditConfig{ + Service: config["service"].(string), + AuditLogConfigs: audit_log_configs, + } + } + // Marshal cloudresourcemanager.Policy to JSON suitable for storing in state pjson, err := json.Marshal(&policy) if err != nil { diff --git a/google/iam.go b/google/iam.go index 43789f01289..bbbb0301490 100644 --- a/google/iam.go +++ b/google/iam.go @@ -154,3 +154,67 @@ func rolesToMembersMap(bindings []*cloudresourcemanager.Binding) map[string]map[ } return bm } + +// Merge multiple Audit Configs such that configs with the same service result in +// a single exemption list with combined members +func mergeAuditConfigs(audit_configs []*cloudresourcemanager.AuditConfig) []*cloudresourcemanager.AuditConfig { + am := auditConfigToServiceMap(audit_configs) + ac := make([]*cloudresourcemanager.AuditConfig, 0) + + for service, audit_log_configs := range am { + var a cloudresourcemanager.AuditConfig + a.Service = service + a.AuditLogConfigs = make([]*cloudresourcemanager.AuditLogConfig, 0) + + for k, v := range audit_log_configs { + + var alc cloudresourcemanager.AuditLogConfig + alc.LogType = k + for member, _ := range v { + alc.ExemptedMembers = append(alc.ExemptedMembers, member) + } + + a.AuditLogConfigs = append(a.AuditLogConfigs, &alc) + } + + if len(a.AuditLogConfigs) > 0 { + ac = append(ac, &a) + } + } + + return ac +} + +// Build a service map with the log_type and bindings below it +func auditConfigToServiceMap(audit_config []*cloudresourcemanager.AuditConfig) map[string]map[string]map[string]bool { + ac := make(map[string]map[string]map[string]bool) + + // Get each config + for _, c := range audit_config { + + // Initialize service map + if _, ok := ac[c.Service]; !ok { + ac[c.Service] = make(map[string]map[string]bool) + } + + // loop through audit log configs + for _, lc := range c.AuditLogConfigs { + + // Initialize service map + if _, ok := ac[c.Service][lc.LogType]; !ok { + ac[c.Service][lc.LogType] = make(map[string]bool) + } + + // Get each member (user/principal) for the binding + for _, m := range lc.ExemptedMembers { + // Add the member + if _, ok := ac[c.Service][lc.LogType][m]; !ok { + ac[c.Service][lc.LogType][m] = true + } + + } + } + } + + return ac +} diff --git a/google/resource_google_project_iam_policy.go b/google/resource_google_project_iam_policy.go index 306037e881a..8649e5396b6 100644 --- a/google/resource_google_project_iam_policy.go +++ b/google/resource_google_project_iam_policy.go @@ -98,7 +98,9 @@ func resourceGoogleProjectIamPolicyCreate(d *schema.ResourceData, meta interface // Merge the policies together mb := mergeBindings(append(p.Bindings, rp.Bindings...)) + mc := mergeAuditConfigs(append(p.AuditConfigs, rp.AuditConfigs...)) ep.Bindings = mb + ep.AuditConfigs = mc if err = setProjectIamPolicy(ep, config, pid); err != nil { return fmt.Errorf("Error applying IAM policy to project: %v", err) } @@ -121,6 +123,7 @@ func resourceGoogleProjectIamPolicyRead(d *schema.ResourceData, meta interface{} } var bindings []*cloudresourcemanager.Binding + var audit_configs []*cloudresourcemanager.AuditConfig if v, ok := d.GetOk("restore_policy"); ok { var restored cloudresourcemanager.Policy // if there's a restore policy, subtract it from the policy_data @@ -130,11 +133,13 @@ func resourceGoogleProjectIamPolicyRead(d *schema.ResourceData, meta interface{} } subtracted := subtractIamPolicy(p, &restored) bindings = subtracted.Bindings + audit_configs = subtracted.AuditConfigs } else { bindings = p.Bindings + audit_configs = p.AuditConfigs } // we only marshal the bindings, because only the bindings get set in the config - pBytes, err := json.Marshal(&cloudresourcemanager.Policy{Bindings: bindings}) + pBytes, err := json.Marshal(&cloudresourcemanager.Policy{Bindings: bindings, AuditConfigs: audit_configs}) if err != nil { return fmt.Errorf("Error marshaling IAM policy: %v", err) } @@ -203,7 +208,9 @@ func resourceGoogleProjectIamPolicyUpdate(d *schema.ResourceData, meta interface // Merge the policies together mb := mergeBindings(append(p.Bindings, rp.Bindings...)) + mc := mergeAuditConfigs(append(p.AuditConfigs, rp.AuditConfigs...)) ep.Bindings = mb + ep.AuditConfigs = mc if err = setProjectIamPolicy(ep, config, pid); err != nil { return fmt.Errorf("Error applying IAM policy to project: %v", err) } @@ -243,6 +250,7 @@ func resourceGoogleProjectIamPolicyDelete(d *schema.ResourceData, meta interface return fmt.Errorf("Error retrieving previous version of changed project IAM policy: %v", err) } ep.Bindings = rp.Bindings + ep.AuditConfigs = rp.AuditConfigs } if err = setProjectIamPolicy(ep, config, pid); err != nil { return fmt.Errorf("Error applying IAM policy to project: %v", err) @@ -254,6 +262,7 @@ func resourceGoogleProjectIamPolicyDelete(d *schema.ResourceData, meta interface // Subtract all bindings in policy b from policy a, and return the result func subtractIamPolicy(a, b *cloudresourcemanager.Policy) *cloudresourcemanager.Policy { am := rolesToMembersMap(a.Bindings) + ac := auditConfigToServiceMap(a.AuditConfigs) for _, b := range b.Bindings { if _, ok := am[b.Role]; ok { @@ -265,7 +274,24 @@ func subtractIamPolicy(a, b *cloudresourcemanager.Policy) *cloudresourcemanager. } } } + + for _, b := range b.AuditConfigs { + if _, ok := ac[b.Service]; ok { + for _, c := range b.AuditLogConfigs { + if _, ok := ac[b.Service][c.LogType]; ok { + for m, _ := range ac[b.Service][c.LogType] { + delete(ac[b.Service][c.LogType], m) + } + } + if len(ac[b.Service][c.LogType]) == 0 { + delete(ac[b.Service][c.LogType], c.LogType) + } + } + } + } + a.Bindings = rolesToMembersBinding(am) + a.AuditConfigs = servicesToAuditConfig(ac) return a } @@ -274,7 +300,7 @@ func setProjectIamPolicy(policy *cloudresourcemanager.Policy, config *Config, pi pbytes, _ := json.Marshal(policy) log.Printf("[DEBUG] Setting policy %#v for project: %s", string(pbytes), pid) _, err := config.clientResourceManager.Projects.SetIamPolicy(pid, - &cloudresourcemanager.SetIamPolicyRequest{Policy: policy}).Do() + &cloudresourcemanager.SetIamPolicyRequest{Policy: policy, UpdateMask: "bindings,etag,auditConfigs"}).Do() if err != nil { return errwrap.Wrapf(fmt.Sprintf("Error applying IAM policy for project %q. Policy is %#v, error is {{err}}", pid, policy), err) @@ -346,6 +372,36 @@ func rolesToMembersBinding(m map[string]map[string]bool) []*cloudresourcemanager return bindings } +// Convert a map of audit_configs->services to a list of Binding +func servicesToAuditConfig(ac map[string]map[string]map[string]bool) []*cloudresourcemanager.AuditConfig { + temp_audit_config := make([]*cloudresourcemanager.AuditConfig, 0) + + for service, audit_log_config := range ac { + alc := make([]*cloudresourcemanager.AuditLogConfig, 0) + members := make([]string, 0) + for k, v := range audit_log_config { + for m, _ := range v { + members = append(members, m) + } + + lc := cloudresourcemanager.AuditLogConfig{ + LogType: k, + ExemptedMembers: members, + } + + alc = append(alc, &lc) + } + + tc := cloudresourcemanager.AuditConfig{ + Service: service, + AuditLogConfigs: alc, + } + + temp_audit_config = append(temp_audit_config, &tc) + } + return temp_audit_config +} + func jsonPolicyDiffSuppress(k, old, new string, d *schema.ResourceData) bool { var oldPolicy, newPolicy cloudresourcemanager.Policy if err := json.Unmarshal([]byte(old), &oldPolicy); err != nil { diff --git a/website/docs/d/google_iam_policy.html.markdown b/website/docs/d/google_iam_policy.html.markdown index 7468a61504c..b03482f4422 100644 --- a/website/docs/d/google_iam_policy.html.markdown +++ b/website/docs/d/google_iam_policy.html.markdown @@ -29,6 +29,25 @@ data "google_iam_policy" "admin" { "user:jane@example.com", ] } + + audit_config { + service = "cloudkms.googleapis.com" + audit_log_configs = [ + { + log_type = "DATA_READ", + exempted_members = [ + "user:you@domain.com", + ] + }, + { + "logType": "DATA_WRITE", + }, + { + "logType": "ADMIN_READ", + } + ] + } + } ``` @@ -64,6 +83,12 @@ each accept the following arguments: * **group:{emailid}**: An email address that represents a Google group. For example, admins@example.com. * **domain:{domain}**: A Google Apps domain name that represents all the users of that domain. For example, google.com or example.com. +* `audit_config` (Optional) - A nested configuration block that defines logging additional configuration for your project. + * `service` (Required) Defines a service that will be enabled for audit logging. For example, `storage.googleapis.com`, `cloudsql.googleapis.com`. `allServices` is a special value that covers all services. + * `audit_log_configs` (Required) A nested block that defines the operations you'd like to log. + * `log_type` (Required) Defines the logging level. `DATA_READ`, `DATA_WRITE` and `ADMIN_READ` capture different types of events. See [the audit configuration documentation](https://cloud.google.com/resource-manager/reference/rest/Shared.Types/AuditConfig) for more details. + * `exempted_members` (Optional) Specifies the identities that are exempt from these types of logging operations. Follows the same format of the `members` array for `binding`. + ## Attributes Reference The following attribute is exported: