Skip to content

Commit c36d0b5

Browse files
bigquery: support for IAM conditions in all google_bigquery_dataset_iam_* resources (#15337) (#24778)
[upstream:3680ab8c2eb6276bcb09eea8f401032c8d2a5402] Signed-off-by: Modular Magician <[email protected]>
1 parent 0207a18 commit c36d0b5

File tree

6 files changed

+471
-34
lines changed

6 files changed

+471
-34
lines changed

.changelog/15337.txt

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
```release-note:enhancement
2+
bigquery: supported for IAM conditions in all `google_bigquery_dataset_iam_*` resources
3+
```
4+
5+
**Local test execution:**
6+
```console
7+
➜ make testacc TEST=./google/services/bigquery TESTARGS='-run=TestAccBigqueryDatasetIam -parallel 3'
8+
9+
sh -c "'/Users/ramon/go/src/github.com/hashicorp/terraform-provider-google/scripts/gofmtcheck.sh'"
10+
==> Checking that code complies with gofmt requirements...
11+
go vet
12+
TF_ACC_REFRESH_AFTER_APPLY=1 TF_ACC=1 TF_SCHEMA_PANIC_ON_ERROR=1 go test ./google/services/bigquery -v -run=TestAccBigqueryDatasetIam -parallel 3 -timeout 240m -ldflags="-X=github.com/hashicorp/terraform-provider-google/version.ProviderVersion=acc"
13+
=== RUN TestAccBigqueryDatasetIamMember_afterDatasetCreation
14+
=== PAUSE TestAccBigqueryDatasetIamMember_afterDatasetCreation
15+
=== RUN TestAccBigqueryDatasetIamMember_serviceAccount
16+
=== PAUSE TestAccBigqueryDatasetIamMember_serviceAccount
17+
=== RUN TestAccBigqueryDatasetIamMember_iamMemberWithIAMCondition
18+
=== PAUSE TestAccBigqueryDatasetIamMember_iamMemberWithIAMCondition
19+
=== RUN TestAccBigqueryDatasetIamMember_iamMember
20+
=== PAUSE TestAccBigqueryDatasetIamMember_iamMember
21+
=== RUN TestAccBigqueryDatasetIamMember_withDeletedServiceAccount
22+
=== PAUSE TestAccBigqueryDatasetIamMember_withDeletedServiceAccount
23+
=== RUN TestAccBigqueryDatasetIamBinding
24+
=== PAUSE TestAccBigqueryDatasetIamBinding
25+
=== RUN TestAccBigqueryDatasetIamMember
26+
=== PAUSE TestAccBigqueryDatasetIamMember
27+
=== RUN TestAccBigqueryDatasetIamPolicy
28+
=== PAUSE TestAccBigqueryDatasetIamPolicy
29+
=== RUN TestAccBigqueryDatasetIamBindingWithIAMCondition
30+
=== PAUSE TestAccBigqueryDatasetIamBindingWithIAMCondition
31+
=== RUN TestAccBigqueryDatasetIamPolicyWithIAMCondition
32+
=== PAUSE TestAccBigqueryDatasetIamPolicyWithIAMCondition
33+
=== CONT TestAccBigqueryDatasetIamMember_afterDatasetCreation
34+
=== CONT TestAccBigqueryDatasetIamBinding
35+
=== CONT TestAccBigqueryDatasetIamMember_iamMember
36+
--- PASS: TestAccBigqueryDatasetIamMember_afterDatasetCreation (45.90s)
37+
=== CONT TestAccBigqueryDatasetIamMember_withDeletedServiceAccount
38+
--- PASS: TestAccBigqueryDatasetIamBinding (49.72s)
39+
=== CONT TestAccBigqueryDatasetIamBindingWithIAMCondition
40+
--- PASS: TestAccBigqueryDatasetIamMember_iamMember (53.58s)
41+
=== CONT TestAccBigqueryDatasetIamPolicyWithIAMCondition
42+
--- PASS: TestAccBigqueryDatasetIamPolicyWithIAMCondition (27.04s)
43+
=== CONT TestAccBigqueryDatasetIamMember_iamMemberWithIAMCondition
44+
--- PASS: TestAccBigqueryDatasetIamMember_withDeletedServiceAccount (36.13s)
45+
=== CONT TestAccBigqueryDatasetIamMember_serviceAccount
46+
--- PASS: TestAccBigqueryDatasetIamBindingWithIAMCondition (63.05s)
47+
=== CONT TestAccBigqueryDatasetIamPolicy
48+
--- PASS: TestAccBigqueryDatasetIamPolicy (30.21s)
49+
=== CONT TestAccBigqueryDatasetIamMember
50+
--- PASS: TestAccBigqueryDatasetIamMember (28.03s)
51+
--- PASS: TestAccBigqueryDatasetIamMember_serviceAccount (94.86s)
52+
--- PASS: TestAccBigqueryDatasetIamMember_iamMemberWithIAMCondition (106.98s)
53+
PASS
54+
ok github.com/hashicorp/terraform-provider-google/google/services/bigquery 188.391s
55+
```

google/services/bigquery/iam_bigquery_dataset.go

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,24 @@ import (
3030
"google.golang.org/api/cloudresourcemanager/v1"
3131
)
3232

33+
type conditionKey struct {
34+
Description string
35+
Expression string
36+
Title string
37+
}
38+
39+
type iamBindingKey struct {
40+
Role string
41+
Condition conditionKey
42+
}
43+
44+
func conditionKeyFromCondition(condition *cloudresourcemanager.Expr) conditionKey {
45+
if condition == nil {
46+
return conditionKey{}
47+
}
48+
return conditionKey{Description: condition.Description, Expression: condition.Expression, Title: condition.Title}
49+
}
50+
3351
var IamBigqueryDatasetSchema = map[string]*schema.Schema{
3452
"dataset_id": {
3553
Type: schema.TypeString,
@@ -93,8 +111,12 @@ func BigqueryDatasetIdParseFunc(d *schema.ResourceData, config *transport_tpg.Co
93111
return nil
94112
}
95113

114+
func (u *BigqueryDatasetIamUpdater) policyURL() string {
115+
return fmt.Sprintf("%s%s?accessPolicyVersion=3", u.Config.BigQueryBasePath, u.GetResourceId())
116+
}
117+
96118
func (u *BigqueryDatasetIamUpdater) GetResourceIamPolicy() (*cloudresourcemanager.Policy, error) {
97-
url := fmt.Sprintf("%s%s", u.Config.BigQueryBasePath, u.GetResourceId())
119+
url := u.policyURL()
98120

99121
userAgent, err := tpgresource.GenerateUserAgentString(u.d, u.Config.UserAgent)
100122
if err != nil {
@@ -120,7 +142,7 @@ func (u *BigqueryDatasetIamUpdater) GetResourceIamPolicy() (*cloudresourcemanage
120142
}
121143

122144
func (u *BigqueryDatasetIamUpdater) SetResourceIamPolicy(policy *cloudresourcemanager.Policy) error {
123-
url := fmt.Sprintf("%s%s", u.Config.BigQueryBasePath, u.GetResourceId())
145+
url := u.policyURL()
124146

125147
access, err := policyToAccess(policy)
126148
if err != nil {
@@ -154,7 +176,7 @@ func accessToPolicy(access interface{}) (*cloudresourcemanager.Policy, error) {
154176
if access == nil {
155177
return nil, nil
156178
}
157-
roleToBinding := make(map[string]*cloudresourcemanager.Binding)
179+
roleToBinding := make(map[iamBindingKey]*cloudresourcemanager.Binding)
158180

159181
accessArr := access.([]interface{})
160182
for _, v := range accessArr {
@@ -174,20 +196,32 @@ func accessToPolicy(access interface{}) (*cloudresourcemanager.Policy, error) {
174196
if err != nil {
175197
return nil, err
176198
}
177-
// We have to combine bindings manually
178-
binding, ok := roleToBinding[role]
199+
200+
var condition *cloudresourcemanager.Expr
201+
if rawCondition, ok := memberRole["condition"]; ok {
202+
conditionMap := rawCondition.(map[string]interface{})
203+
expr := conditionMap["expression"].(string)
204+
condition = &cloudresourcemanager.Expr{Expression: expr}
205+
if title, ok := conditionMap["title"].(string); ok {
206+
condition.Title = title
207+
}
208+
if desc, ok := conditionMap["description"].(string); ok {
209+
condition.Description = desc
210+
}
211+
}
212+
213+
key := iamBindingKey{Role: role, Condition: conditionKeyFromCondition(condition)}
214+
binding, ok := roleToBinding[key]
179215
if !ok {
180-
binding = &cloudresourcemanager.Binding{Role: role, Members: []string{}}
216+
binding = &cloudresourcemanager.Binding{Role: role, Members: []string{}, Condition: condition}
181217
}
182218
binding.Members = append(binding.Members, member)
183-
184-
roleToBinding[role] = binding
219+
roleToBinding[key] = binding
185220
}
186-
bindings := make([]*cloudresourcemanager.Binding, 0)
221+
bindings := make([]*cloudresourcemanager.Binding, 0, len(roleToBinding))
187222
for _, v := range roleToBinding {
188223
bindings = append(bindings, v)
189224
}
190-
191225
return &cloudresourcemanager.Policy{Bindings: bindings}, nil
192226
}
193227

@@ -197,9 +231,6 @@ func policyToAccess(policy *cloudresourcemanager.Policy) ([]map[string]interface
197231
return nil, errors.New("Access policies not allowed on BigQuery Dataset IAM policies")
198232
}
199233
for _, binding := range policy.Bindings {
200-
if binding.Condition != nil {
201-
return nil, errors.New("IAM conditions not allowed on BigQuery Dataset IAM")
202-
}
203234
if fullRole, ok := bigqueryAccessPrimitiveToRoleMap[binding.Role]; ok {
204235
return nil, fmt.Errorf("BigQuery Dataset legacy role %s is not allowed when using google_bigquery_dataset_iam resources. Please use the full form: %s", binding.Role, fullRole)
205236
}
@@ -211,6 +242,9 @@ func policyToAccess(policy *cloudresourcemanager.Policy) ([]map[string]interface
211242
access := map[string]interface{}{
212243
"role": binding.Role,
213244
}
245+
if binding.Condition != nil {
246+
access["condition"] = binding.Condition
247+
}
214248
memberType, member, err := iamMemberToAccess(member)
215249
if err != nil {
216250
return nil, err
@@ -279,11 +313,14 @@ func accessToIamMember(access map[string]interface{}) (string, error) {
279313
return "", fmt.Errorf("Failed to convert BigQuery Dataset access to IAM member. To use views with a dataset, please use dataset_access")
280314
}
281315
if member, ok := access["userByEmail"]; ok {
282-
// service accounts have "gservice" in their email. This is best guess due to lost information
283-
if strings.Contains(member.(string), "gserviceaccount") {
284-
return fmt.Sprintf("serviceAccount:%s", member.(string)), nil
316+
ms := member.(string)
317+
if strings.HasPrefix(ms, "deleted:serviceAccount:") {
318+
return ms, nil
319+
}
320+
if strings.Contains(ms, "gserviceaccount") {
321+
return fmt.Sprintf("serviceAccount:%s", ms), nil
285322
}
286-
return fmt.Sprintf("user:%s", member.(string)), nil
323+
return fmt.Sprintf("user:%s", ms), nil
287324
}
288325
return "", fmt.Errorf("Failed to identify IAM member from BigQuery Dataset access: %v", access)
289326
}

google/services/bigquery/iam_bigquery_member_dataset.go

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,12 @@ func NewBigqueryDatasetIamMemberUpdater(d tpgresource.TerraformResourceData, con
8181
}, nil
8282
}
8383

84+
func (u *BigqueryDatasetIamMemberUpdater) policyURL() string {
85+
return fmt.Sprintf("%s%s?accessPolicyVersion=3", u.Config.BigQueryBasePath, u.GetResourceId())
86+
}
87+
8488
func (u *BigqueryDatasetIamMemberUpdater) GetResourceIamPolicy() (*cloudresourcemanager.Policy, error) {
85-
url := fmt.Sprintf("%s%s", u.Config.BigQueryBasePath, u.GetResourceId())
89+
url := u.policyURL()
8690

8791
userAgent, err := tpgresource.GenerateUserAgentString(u.d, u.Config.UserAgent)
8892
if err != nil {
@@ -108,7 +112,7 @@ func (u *BigqueryDatasetIamMemberUpdater) GetResourceIamPolicy() (*cloudresource
108112
}
109113

110114
func GetCurrentResourceAccess(u *BigqueryDatasetIamMemberUpdater) ([]interface{}, error) {
111-
url := fmt.Sprintf("%s%s", u.Config.BigQueryBasePath, u.GetResourceId())
115+
url := u.policyURL()
112116

113117
userAgent, err := tpgresource.GenerateUserAgentString(u.d, u.Config.UserAgent)
114118
if err != nil {
@@ -157,7 +161,7 @@ func mergeAccess(newAccess []map[string]interface{}, currAccess []interface{}) [
157161
}
158162

159163
func (u *BigqueryDatasetIamMemberUpdater) SetResourceIamPolicy(policy *cloudresourcemanager.Policy) error {
160-
url := fmt.Sprintf("%s%s", u.Config.BigQueryBasePath, u.GetResourceId())
164+
url := u.policyURL()
161165

162166
newAccess, err := policyToAccessForIamMember(policy)
163167
if err != nil {
@@ -199,7 +203,7 @@ func accessToPolicyForIamMember(access interface{}) (*cloudresourcemanager.Polic
199203
if access == nil {
200204
return nil, nil
201205
}
202-
roleToBinding := make(map[string]*cloudresourcemanager.Binding)
206+
roleToBinding := make(map[iamBindingKey]*cloudresourcemanager.Binding)
203207

204208
accessArr := access.([]interface{})
205209
for _, v := range accessArr {
@@ -219,16 +223,29 @@ func accessToPolicyForIamMember(access interface{}) (*cloudresourcemanager.Polic
219223
if err != nil {
220224
return nil, err
221225
}
222-
// We have to combine bindings manually
223-
binding, ok := roleToBinding[role]
226+
227+
var condition *cloudresourcemanager.Expr
228+
if rawCondition, ok := memberRole["condition"]; ok {
229+
conditionMap := rawCondition.(map[string]interface{})
230+
expr := conditionMap["expression"].(string)
231+
condition = &cloudresourcemanager.Expr{Expression: expr}
232+
if title, ok := conditionMap["title"].(string); ok {
233+
condition.Title = title
234+
}
235+
if desc, ok := conditionMap["description"].(string); ok {
236+
condition.Description = desc
237+
}
238+
}
239+
240+
key := iamBindingKey{Role: role, Condition: conditionKeyFromCondition(condition)}
241+
binding, ok := roleToBinding[key]
224242
if !ok {
225-
binding = &cloudresourcemanager.Binding{Role: role, Members: []string{}}
243+
binding = &cloudresourcemanager.Binding{Role: role, Members: []string{}, Condition: condition}
226244
}
227245
binding.Members = append(binding.Members, member)
228-
229-
roleToBinding[role] = binding
246+
roleToBinding[key] = binding
230247
}
231-
bindings := make([]*cloudresourcemanager.Binding, 0)
248+
bindings := make([]*cloudresourcemanager.Binding, 0, len(roleToBinding))
232249
for _, v := range roleToBinding {
233250
bindings = append(bindings, v)
234251
}
@@ -242,9 +259,6 @@ func policyToAccessForIamMember(policy *cloudresourcemanager.Policy) ([]map[stri
242259
return nil, errors.New("Access policies not allowed on BigQuery Dataset IAM policies")
243260
}
244261
for _, binding := range policy.Bindings {
245-
if binding.Condition != nil {
246-
return nil, errors.New("IAM conditions not allowed on BigQuery Dataset IAM")
247-
}
248262
if fullRole, ok := bigqueryIamMemberAccessPrimitiveToRoleMap[binding.Role]; ok {
249263
return nil, fmt.Errorf("BigQuery Dataset legacy role %s is not allowed when using google_bigquery_dataset_iam resources. Please use the full form: %s", binding.Role, fullRole)
250264
}
@@ -256,6 +270,9 @@ func policyToAccessForIamMember(policy *cloudresourcemanager.Policy) ([]map[stri
256270
access := map[string]interface{}{
257271
"role": binding.Role,
258272
}
273+
if binding.Condition != nil {
274+
access["condition"] = binding.Condition
275+
}
259276
memberType, member, err := iamMemberToAccessForIamMember(member)
260277
if err != nil {
261278
return nil, err
@@ -324,11 +341,14 @@ func accessToIamMemberForIamMember(access map[string]interface{}) (string, error
324341
return "", fmt.Errorf("Failed to convert BigQuery Dataset access to IAM member. To use views with a dataset, please use dataset_access")
325342
}
326343
if member, ok := access["userByEmail"]; ok {
327-
// service accounts have "gservice" in their email. This is best guess due to lost information
328-
if strings.Contains(member.(string), "gserviceaccount") {
329-
return fmt.Sprintf("serviceAccount:%s", member.(string)), nil
344+
ms := member.(string)
345+
if strings.HasPrefix(ms, "deleted:") || strings.Contains(ms, "?uid=") {
346+
return ms, nil
347+
}
348+
if strings.Contains(ms, "gserviceaccount") {
349+
return fmt.Sprintf("serviceAccount:%s", ms), nil
330350
}
331-
return fmt.Sprintf("user:%s", member.(string)), nil
351+
return fmt.Sprintf("user:%s", ms), nil
332352
}
333353
return "", fmt.Errorf("Failed to identify IAM member from BigQuery Dataset access: %v", access)
334354
}

0 commit comments

Comments
 (0)