Skip to content

Commit

Permalink
Add support for Audit Configs
Browse files Browse the repository at this point in the history
  • Loading branch information
paddycarver authored and modular-magician committed Dec 21, 2018
1 parent 961c878 commit 139c851
Show file tree
Hide file tree
Showing 5 changed files with 397 additions and 57 deletions.
94 changes: 74 additions & 20 deletions google/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/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/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

0 comments on commit 139c851

Please sign in to comment.