Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#936 adding support for AuditLogConfigs in IAM Policies #1531

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions google/data_source_google_iam_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the order of the exempted members matter? Would you ever want to reference them using interpolation? If the answer to both is "no", this would probably be better off as a Set.

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:
Expand All @@ -49,6 +79,7 @@ func dataSourceGoogleIamPolicy() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"audit_config": auditConfig,
},
}
}
Expand All @@ -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() {
Expand All @@ -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 {
Expand Down
64 changes: 64 additions & 0 deletions google/iam.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a general rule of thumb, camelCase, not snake_case, is preferred. :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! I'll get this fixed up.

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
}
60 changes: 58 additions & 2 deletions google/resource_google_project_iam_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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
Expand All @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
Expand All @@ -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 {
Expand All @@ -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
}

Expand All @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down
25 changes: 25 additions & 0 deletions website/docs/d/google_iam_policy.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,25 @@ data "google_iam_policy" "admin" {
"user:[email protected]",
]
}

audit_config {
service = "cloudkms.googleapis.com"
audit_log_configs = [
{
log_type = "DATA_READ",
exempted_members = [
"user:[email protected]",
]
},
{
"logType": "DATA_WRITE",
},
{
"logType": "ADMIN_READ",
}
]
}

}
```

Expand Down Expand Up @@ -64,6 +83,12 @@ each accept the following arguments:
* **group:{emailid}**: An email address that represents a Google group. For example, [email protected].
* **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:
Expand Down