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

Add support for Audit Configs #243

Merged
merged 1 commit into from
Dec 21, 2018
Merged
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
94 changes: 74 additions & 20 deletions google-beta/data_source_google_iam_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,6 @@ import (
"google.golang.org/api/cloudresourcemanager/v1"
)

var iamBinding *schema.Schema = &schema.Schema{
Type: schema.TypeSet,
Required: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"role": {
Type: schema.TypeString,
Required: true,
},
"members": {
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
},
},
}

// 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 @@ -44,11 +25,57 @@ func dataSourceGoogleIamPolicy() *schema.Resource {
return &schema.Resource{
Read: dataSourceGoogleIamPolicyRead,
Schema: map[string]*schema.Schema{
"binding": iamBinding,
"binding": {
Type: schema.TypeSet,
Required: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"role": {
Type: schema.TypeString,
Required: true,
},
"members": {
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
},
},
},
"policy_data": {
Type: schema.TypeString,
Computed: true,
},
"audit_config": {
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.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
},
},
},
},
},
},
},
}
}
Expand All @@ -61,6 +88,7 @@ func dataSourceGoogleIamPolicyRead(d *schema.ResourceData, meta interface{}) err

// 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())
Expand All @@ -75,6 +103,9 @@ func dataSourceGoogleIamPolicyRead(d *schema.ResourceData, meta interface{}) err
}
}

// Convert each audit_config into a cloudresourcemanager.AuditConfig
policy.AuditConfigs = expandAuditConfig(aset)

// Marshal cloudresourcemanager.Policy to JSON suitable for storing in state
pjson, err := json.Marshal(&policy)
if err != nil {
Expand All @@ -88,3 +119,26 @@ func dataSourceGoogleIamPolicyRead(d *schema.ResourceData, meta interface{}) err

return nil
}

func expandAuditConfig(set *schema.Set) []*cloudresourcemanager.AuditConfig {
auditConfigs := make([]*cloudresourcemanager.AuditConfig, 0, set.Len())
for _, v := range set.List() {
config := v.(map[string]interface{})
// build list of audit configs first
auditLogConfigSet := config["audit_log_configs"].(*schema.Set)
// the array we're going to add to the outgoing resource
auditLogConfigs := make([]*cloudresourcemanager.AuditLogConfig, 0, auditLogConfigSet.Len())
for _, y := range auditLogConfigSet.List() {
logConfig := y.(map[string]interface{})
auditLogConfigs = append(auditLogConfigs, &cloudresourcemanager.AuditLogConfig{
LogType: logConfig["log_type"].(string),
ExemptedMembers: convertStringArr(logConfig["exempted_members"].(*schema.Set).List()),
})
}
auditConfigs = append(auditConfigs, &cloudresourcemanager.AuditConfig{
Service: config["service"].(string),
AuditLogConfigs: auditLogConfigs,
})
}
return auditConfigs
}
51 changes: 51 additions & 0 deletions google-beta/iam.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,54 @@ 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(auditConfigs []*cloudresourcemanager.AuditConfig) []*cloudresourcemanager.AuditConfig {
am := auditConfigToServiceMap(auditConfigs)
var ac []*cloudresourcemanager.AuditConfig
for service, auditLogConfigs := range am {
var a cloudresourcemanager.AuditConfig
a.Service = service
a.AuditLogConfigs = make([]*cloudresourcemanager.AuditLogConfig, 0, len(auditLogConfigs))
for k, v := range auditLogConfigs {
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(auditConfig []*cloudresourcemanager.AuditConfig) map[string]map[string]map[string]bool {
ac := make(map[string]map[string]map[string]bool)
// Get each config
for _, c := range auditConfig {
// Initialize service map
if _, ok := ac[c.Service]; !ok {
ac[c.Service] = 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] = 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
}
95 changes: 76 additions & 19 deletions google-beta/resource_google_project_iam_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"log"
"reflect"
"sort"

"github.com/hashicorp/errwrap"
Expand Down Expand Up @@ -88,8 +89,7 @@ func resourceGoogleProjectIamPolicyRead(d *schema.ResourceData, meta interface{}
return err
}

// we only marshal the bindings, because only the bindings get set in the config
policyBytes, err := json.Marshal(&cloudresourcemanager.Policy{Bindings: policy.Bindings})
policyBytes, err := json.Marshal(&cloudresourcemanager.Policy{Bindings: policy.Bindings, AuditConfigs: policy.AuditConfigs})
if err != nil {
return fmt.Errorf("Error marshaling IAM policy: %v", err)
}
Expand Down Expand Up @@ -157,7 +157,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 @@ -197,34 +197,67 @@ func jsonPolicyDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
log.Printf("[ERROR] Could not unmarshal new policy %s: %v", new, err)
return false
}
oldPolicy.Bindings = mergeBindings(oldPolicy.Bindings)
newPolicy.Bindings = mergeBindings(newPolicy.Bindings)
if newPolicy.Etag != oldPolicy.Etag {
return false
}
if newPolicy.Version != oldPolicy.Version {
return false
}
if len(newPolicy.Bindings) != len(oldPolicy.Bindings) {
if !compareBindings(oldPolicy.Bindings, newPolicy.Bindings) {
return false
}
sort.Sort(sortableBindings(newPolicy.Bindings))
sort.Sort(sortableBindings(oldPolicy.Bindings))
for pos, newBinding := range newPolicy.Bindings {
oldBinding := oldPolicy.Bindings[pos]
if oldBinding.Role != newBinding.Role {
return false
}
if len(oldBinding.Members) != len(newBinding.Members) {
if !compareAuditConfigs(oldPolicy.AuditConfigs, newPolicy.AuditConfigs) {
return false
}
return true
}

func derefBindings(b []*cloudresourcemanager.Binding) []cloudresourcemanager.Binding {
db := make([]cloudresourcemanager.Binding, len(b))

for i, v := range b {
db[i] = *v
sort.Strings(db[i].Members)
}
return db
}

func compareBindings(a, b []*cloudresourcemanager.Binding) bool {
a = mergeBindings(a)
b = mergeBindings(b)
sort.Sort(sortableBindings(a))
sort.Sort(sortableBindings(b))
return reflect.DeepEqual(derefBindings(a), derefBindings(b))
}

func compareAuditConfigs(a, b []*cloudresourcemanager.AuditConfig) bool {
a = mergeAuditConfigs(a)
b = mergeAuditConfigs(b)
sort.Sort(sortableAuditConfigs(a))
sort.Sort(sortableAuditConfigs(b))
if len(a) != len(b) {
return false
}
for i, v := range a {
if len(v.AuditLogConfigs) != len(b[i].AuditLogConfigs) {
return false
}
sort.Strings(oldBinding.Members)
sort.Strings(newBinding.Members)
for i, newMember := range newBinding.Members {
oldMember := oldBinding.Members[i]
if newMember != oldMember {
sort.Sort(sortableAuditLogConfigs(v.AuditLogConfigs))
sort.Sort(sortableAuditLogConfigs(b[i].AuditLogConfigs))
for x, logConfig := range v.AuditLogConfigs {
if b[i].AuditLogConfigs[x].LogType != logConfig.LogType {
return false
}
sort.Strings(logConfig.ExemptedMembers)
sort.Strings(b[i].AuditLogConfigs[x].ExemptedMembers)
if len(logConfig.ExemptedMembers) != len(b[i].AuditLogConfigs[x].ExemptedMembers) {
return false
}
for pos, exemptedMember := range logConfig.ExemptedMembers {
if b[i].AuditLogConfigs[x].ExemptedMembers[pos] != exemptedMember {
return false
}
}
}
}
return true
Expand All @@ -242,6 +275,30 @@ func (b sortableBindings) Less(i, j int) bool {
return b[i].Role < b[j].Role
}

type sortableAuditConfigs []*cloudresourcemanager.AuditConfig

func (b sortableAuditConfigs) Len() int {
return len(b)
}
func (b sortableAuditConfigs) Swap(i, j int) {
b[i], b[j] = b[j], b[i]
}
func (b sortableAuditConfigs) Less(i, j int) bool {
return b[i].Service < b[j].Service
}

type sortableAuditLogConfigs []*cloudresourcemanager.AuditLogConfig

func (b sortableAuditLogConfigs) Len() int {
return len(b)
}
func (b sortableAuditLogConfigs) Swap(i, j int) {
b[i], b[j] = b[j], b[i]
}
func (b sortableAuditLogConfigs) Less(i, j int) bool {
return b[i].LogType < b[j].LogType
}

func getProjectIamPolicyMutexKey(pid string) string {
return fmt.Sprintf("iam-project-%s", pid)
}
Loading