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 19, 2018
1 parent 6acdbb3 commit b2874e5
Show file tree
Hide file tree
Showing 40 changed files with 414 additions and 72 deletions.
55 changes: 55 additions & 0 deletions google/data_source_google_iam_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,36 @@ import (
"google.golang.org/api/cloudresourcemanager/v1"
)

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,
},
},
},
},
},
},
}

var iamBinding *schema.Schema = &schema.Schema{
Type: schema.TypeSet,
Required: true,
Expand Down Expand Up @@ -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 auditConfigs []*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())
auditConfigs = make([]*cloudresourcemanager.AuditConfig, aset.Len())
policy.Bindings = bindings
policy.AuditConfigs = auditConfigs

// Convert each config binding into a cloudresourcemanager.Binding
for i, v := range bset.List() {
Expand All @@ -75,6 +110,26 @@ 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
auditLogConfigSet := config["audit_log_configs"].(*schema.Set)
// the array we're going to add to the outgoing resource
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"].([]interface{})),
}
}
policy.AuditConfigs[i] = &cloudresourcemanager.AuditConfig{
Service: config["service"].(string),
AuditLogConfigs: auditLogConfigs,
}
}

// Marshal cloudresourcemanager.Policy to JSON suitable for storing in state
pjson, err := json.Marshal(&policy)
if err != nil {
Expand Down
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
}
96 changes: 77 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,8 @@ 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})
// we only marshal the bindings and audit configs, because only the bindings and audit configs get set in the config
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 +158,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 +198,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, mem := range logConfig.ExemptedMembers {
if b[i].AuditLogConfigs[x].ExemptedMembers[pos] != mem {
return false
}
}
}
}
return true
Expand All @@ -242,6 +276,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 b2874e5

Please sign in to comment.