Skip to content

Commit f9fb34e

Browse files
paddycarvermodular-magician
authored andcommitted
Add support for Audit Configs
1 parent 222a47a commit f9fb34e

5 files changed

+397
-57
lines changed

google/data_source_google_iam_policy.go

+74-20
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,6 @@ import (
99
"google.golang.org/api/cloudresourcemanager/v1"
1010
)
1111

12-
var iamBinding *schema.Schema = &schema.Schema{
13-
Type: schema.TypeSet,
14-
Required: true,
15-
Elem: &schema.Resource{
16-
Schema: map[string]*schema.Schema{
17-
"role": {
18-
Type: schema.TypeString,
19-
Required: true,
20-
},
21-
"members": {
22-
Type: schema.TypeSet,
23-
Required: true,
24-
Elem: &schema.Schema{Type: schema.TypeString},
25-
Set: schema.HashString,
26-
},
27-
},
28-
},
29-
}
30-
3112
// dataSourceGoogleIamPolicy returns a *schema.Resource that allows a customer
3213
// to express a Google Cloud IAM policy in a data resource. This is an example
3314
// of how the schema would be used in a config:
@@ -44,11 +25,57 @@ func dataSourceGoogleIamPolicy() *schema.Resource {
4425
return &schema.Resource{
4526
Read: dataSourceGoogleIamPolicyRead,
4627
Schema: map[string]*schema.Schema{
47-
"binding": iamBinding,
28+
"binding": &schema.Schema{
29+
Type: schema.TypeSet,
30+
Required: true,
31+
Elem: &schema.Resource{
32+
Schema: map[string]*schema.Schema{
33+
"role": {
34+
Type: schema.TypeString,
35+
Required: true,
36+
},
37+
"members": {
38+
Type: schema.TypeSet,
39+
Required: true,
40+
Elem: &schema.Schema{Type: schema.TypeString},
41+
Set: schema.HashString,
42+
},
43+
},
44+
},
45+
},
4846
"policy_data": {
4947
Type: schema.TypeString,
5048
Computed: true,
5149
},
50+
"audit_config": &schema.Schema{
51+
Type: schema.TypeSet,
52+
Optional: true,
53+
Elem: &schema.Resource{
54+
Schema: map[string]*schema.Schema{
55+
"service": {
56+
Type: schema.TypeString,
57+
Required: true,
58+
},
59+
"audit_log_configs": {
60+
Type: schema.TypeSet,
61+
Required: true,
62+
Elem: &schema.Resource{
63+
Schema: map[string]*schema.Schema{
64+
"log_type": {
65+
Type: schema.TypeString,
66+
Required: true,
67+
},
68+
"exempted_members": {
69+
Type: schema.TypeSet,
70+
Elem: &schema.Schema{Type: schema.TypeString},
71+
Optional: true,
72+
},
73+
},
74+
},
75+
},
76+
},
77+
},
78+
},
5279
},
5380
}
5481
}
@@ -61,6 +88,7 @@ func dataSourceGoogleIamPolicyRead(d *schema.ResourceData, meta interface{}) err
6188

6289
// The schema supports multiple binding{} blocks
6390
bset := d.Get("binding").(*schema.Set)
91+
aset := d.Get("audit_config").(*schema.Set)
6492

6593
// All binding{} blocks will be converted and stored in an array
6694
bindings = make([]*cloudresourcemanager.Binding, bset.Len())
@@ -75,6 +103,9 @@ func dataSourceGoogleIamPolicyRead(d *schema.ResourceData, meta interface{}) err
75103
}
76104
}
77105

106+
// Convert each audit_config into a cloudresourcemanager.AuditConfig
107+
policy.AuditConfigs = expandAuditConfig(aset)
108+
78109
// Marshal cloudresourcemanager.Policy to JSON suitable for storing in state
79110
pjson, err := json.Marshal(&policy)
80111
if err != nil {
@@ -88,3 +119,26 @@ func dataSourceGoogleIamPolicyRead(d *schema.ResourceData, meta interface{}) err
88119

89120
return nil
90121
}
122+
123+
func expandAuditConfig(set *schema.Set) []*cloudresourcemanager.AuditConfig {
124+
auditConfigs := make([]*cloudresourcemanager.AuditConfig, 0, set.Len())
125+
for _, v := range set.List() {
126+
config := v.(map[string]interface{})
127+
// build list of audit configs first
128+
auditLogConfigSet := config["audit_log_configs"].(*schema.Set)
129+
// the array we're going to add to the outgoing resource
130+
auditLogConfigs := make([]*cloudresourcemanager.AuditLogConfig, 0, auditLogConfigSet.Len())
131+
for _, y := range auditLogConfigSet.List() {
132+
logConfig := y.(map[string]interface{})
133+
auditLogConfigs = append(auditLogConfigs, &cloudresourcemanager.AuditLogConfig{
134+
LogType: logConfig["log_type"].(string),
135+
ExemptedMembers: convertStringArr(logConfig["exempted_members"].(*schema.Set).List()),
136+
})
137+
}
138+
auditConfigs = append(auditConfigs, &cloudresourcemanager.AuditConfig{
139+
Service: config["service"].(string),
140+
AuditLogConfigs: auditLogConfigs,
141+
})
142+
}
143+
return auditConfigs
144+
}

google/iam.go

+51
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,54 @@ func rolesToMembersMap(bindings []*cloudresourcemanager.Binding) map[string]map[
161161
}
162162
return bm
163163
}
164+
165+
// Merge multiple Audit Configs such that configs with the same service result in
166+
// a single exemption list with combined members
167+
func mergeAuditConfigs(auditConfigs []*cloudresourcemanager.AuditConfig) []*cloudresourcemanager.AuditConfig {
168+
am := auditConfigToServiceMap(auditConfigs)
169+
var ac []*cloudresourcemanager.AuditConfig
170+
for service, auditLogConfigs := range am {
171+
var a cloudresourcemanager.AuditConfig
172+
a.Service = service
173+
a.AuditLogConfigs = make([]*cloudresourcemanager.AuditLogConfig, 0, len(auditLogConfigs))
174+
for k, v := range auditLogConfigs {
175+
var alc cloudresourcemanager.AuditLogConfig
176+
alc.LogType = k
177+
for member := range v {
178+
alc.ExemptedMembers = append(alc.ExemptedMembers, member)
179+
}
180+
a.AuditLogConfigs = append(a.AuditLogConfigs, &alc)
181+
}
182+
if len(a.AuditLogConfigs) > 0 {
183+
ac = append(ac, &a)
184+
}
185+
}
186+
return ac
187+
}
188+
189+
// Build a service map with the log_type and bindings below it
190+
func auditConfigToServiceMap(auditConfig []*cloudresourcemanager.AuditConfig) map[string]map[string]map[string]bool {
191+
ac := make(map[string]map[string]map[string]bool)
192+
// Get each config
193+
for _, c := range auditConfig {
194+
// Initialize service map
195+
if _, ok := ac[c.Service]; !ok {
196+
ac[c.Service] = map[string]map[string]bool{}
197+
}
198+
// loop through audit log configs
199+
for _, lc := range c.AuditLogConfigs {
200+
// Initialize service map
201+
if _, ok := ac[c.Service][lc.LogType]; !ok {
202+
ac[c.Service][lc.LogType] = map[string]bool{}
203+
}
204+
// Get each member (user/principal) for the binding
205+
for _, m := range lc.ExemptedMembers {
206+
// Add the member
207+
if _, ok := ac[c.Service][lc.LogType][m]; !ok {
208+
ac[c.Service][lc.LogType][m] = true
209+
}
210+
}
211+
}
212+
}
213+
return ac
214+
}

google/resource_google_project_iam_policy.go

+76-19
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/json"
55
"fmt"
66
"log"
7+
"reflect"
78
"sort"
89

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

91-
// we only marshal the bindings, because only the bindings get set in the config
92-
policyBytes, err := json.Marshal(&cloudresourcemanager.Policy{Bindings: policy.Bindings})
92+
policyBytes, err := json.Marshal(&cloudresourcemanager.Policy{Bindings: policy.Bindings, AuditConfigs: policy.AuditConfigs})
9393
if err != nil {
9494
return fmt.Errorf("Error marshaling IAM policy: %v", err)
9595
}
@@ -157,7 +157,7 @@ func setProjectIamPolicy(policy *cloudresourcemanager.Policy, config *Config, pi
157157
pbytes, _ := json.Marshal(policy)
158158
log.Printf("[DEBUG] Setting policy %#v for project: %s", string(pbytes), pid)
159159
_, err := config.clientResourceManager.Projects.SetIamPolicy(pid,
160-
&cloudresourcemanager.SetIamPolicyRequest{Policy: policy}).Do()
160+
&cloudresourcemanager.SetIamPolicyRequest{Policy: policy, UpdateMask: "bindings,etag,auditConfigs"}).Do()
161161

162162
if err != nil {
163163
return errwrap.Wrapf(fmt.Sprintf("Error applying IAM policy for project %q. Policy is %#v, error is {{err}}", pid, policy), err)
@@ -197,34 +197,67 @@ func jsonPolicyDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
197197
log.Printf("[ERROR] Could not unmarshal new policy %s: %v", new, err)
198198
return false
199199
}
200-
oldPolicy.Bindings = mergeBindings(oldPolicy.Bindings)
201-
newPolicy.Bindings = mergeBindings(newPolicy.Bindings)
202200
if newPolicy.Etag != oldPolicy.Etag {
203201
return false
204202
}
205203
if newPolicy.Version != oldPolicy.Version {
206204
return false
207205
}
208-
if len(newPolicy.Bindings) != len(oldPolicy.Bindings) {
206+
if !compareBindings(oldPolicy.Bindings, newPolicy.Bindings) {
209207
return false
210208
}
211-
sort.Sort(sortableBindings(newPolicy.Bindings))
212-
sort.Sort(sortableBindings(oldPolicy.Bindings))
213-
for pos, newBinding := range newPolicy.Bindings {
214-
oldBinding := oldPolicy.Bindings[pos]
215-
if oldBinding.Role != newBinding.Role {
216-
return false
217-
}
218-
if len(oldBinding.Members) != len(newBinding.Members) {
209+
if !compareAuditConfigs(oldPolicy.AuditConfigs, newPolicy.AuditConfigs) {
210+
return false
211+
}
212+
return true
213+
}
214+
215+
func derefBindings(b []*cloudresourcemanager.Binding) []cloudresourcemanager.Binding {
216+
db := make([]cloudresourcemanager.Binding, len(b))
217+
218+
for i, v := range b {
219+
db[i] = *v
220+
sort.Strings(db[i].Members)
221+
}
222+
return db
223+
}
224+
225+
func compareBindings(a, b []*cloudresourcemanager.Binding) bool {
226+
a = mergeBindings(a)
227+
b = mergeBindings(b)
228+
sort.Sort(sortableBindings(a))
229+
sort.Sort(sortableBindings(b))
230+
return reflect.DeepEqual(derefBindings(a), derefBindings(b))
231+
}
232+
233+
func compareAuditConfigs(a, b []*cloudresourcemanager.AuditConfig) bool {
234+
a = mergeAuditConfigs(a)
235+
b = mergeAuditConfigs(b)
236+
sort.Sort(sortableAuditConfigs(a))
237+
sort.Sort(sortableAuditConfigs(b))
238+
if len(a) != len(b) {
239+
return false
240+
}
241+
for i, v := range a {
242+
if len(v.AuditLogConfigs) != len(b[i].AuditLogConfigs) {
219243
return false
220244
}
221-
sort.Strings(oldBinding.Members)
222-
sort.Strings(newBinding.Members)
223-
for i, newMember := range newBinding.Members {
224-
oldMember := oldBinding.Members[i]
225-
if newMember != oldMember {
245+
sort.Sort(sortableAuditLogConfigs(v.AuditLogConfigs))
246+
sort.Sort(sortableAuditLogConfigs(b[i].AuditLogConfigs))
247+
for x, logConfig := range v.AuditLogConfigs {
248+
if b[i].AuditLogConfigs[x].LogType != logConfig.LogType {
249+
return false
250+
}
251+
sort.Strings(logConfig.ExemptedMembers)
252+
sort.Strings(b[i].AuditLogConfigs[x].ExemptedMembers)
253+
if len(logConfig.ExemptedMembers) != len(b[i].AuditLogConfigs[x].ExemptedMembers) {
226254
return false
227255
}
256+
for pos, exemptedMember := range logConfig.ExemptedMembers {
257+
if b[i].AuditLogConfigs[x].ExemptedMembers[pos] != exemptedMember {
258+
return false
259+
}
260+
}
228261
}
229262
}
230263
return true
@@ -242,6 +275,30 @@ func (b sortableBindings) Less(i, j int) bool {
242275
return b[i].Role < b[j].Role
243276
}
244277

278+
type sortableAuditConfigs []*cloudresourcemanager.AuditConfig
279+
280+
func (b sortableAuditConfigs) Len() int {
281+
return len(b)
282+
}
283+
func (b sortableAuditConfigs) Swap(i, j int) {
284+
b[i], b[j] = b[j], b[i]
285+
}
286+
func (b sortableAuditConfigs) Less(i, j int) bool {
287+
return b[i].Service < b[j].Service
288+
}
289+
290+
type sortableAuditLogConfigs []*cloudresourcemanager.AuditLogConfig
291+
292+
func (b sortableAuditLogConfigs) Len() int {
293+
return len(b)
294+
}
295+
func (b sortableAuditLogConfigs) Swap(i, j int) {
296+
b[i], b[j] = b[j], b[i]
297+
}
298+
func (b sortableAuditLogConfigs) Less(i, j int) bool {
299+
return b[i].LogType < b[j].LogType
300+
}
301+
245302
func getProjectIamPolicyMutexKey(pid string) string {
246303
return fmt.Sprintf("iam-project-%s", pid)
247304
}

0 commit comments

Comments
 (0)