Skip to content

Commit 5cd73e0

Browse files
Allow iam member to utilize casing for principle and principleSet (#4595) (#3133)
* Allow iam member to utilize casing for principle and principleSet * Add integration test * add testcases and custom diff supress for case sensitive value * resolve comments * normalizeCasing in id, add test for binding * normalize iam import member casing * add test for iam policy * probably best to also support deleted behavior for princple and principleSet * quantify teh set of values where this not the case in the function definition * remove default go value Signed-off-by: Modular Magician <[email protected]>
1 parent 76662ca commit 5cd73e0

File tree

6 files changed

+331
-32
lines changed

6 files changed

+331
-32
lines changed

.changelog/4595.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:bug
2+
iam: fixed issue with principle and principleSet members not retaining their casing
3+
```

google-beta/iam.go

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,39 @@ func subtractFromBindings(bindings []*cloudresourcemanager.Binding, toRemove ...
242242
return listFromIamBindingMap(currMap)
243243
}
244244

245+
func iamMemberIsCaseSensitive(member string) bool {
246+
return strings.HasPrefix(member, "principalSet:") || strings.HasPrefix(member, "principal:") || strings.HasPrefix(member, "principalHierarchy:")
247+
}
248+
249+
// normalizeIamMemberCasing returns the case adjusted value of an iamMember
250+
// this is important as iam will ignore casing unless it is one of the following
251+
// member types: principalSet, principal, principalHierarchy
252+
// members are in <type>:<value> format
253+
// <type> is case sensitive
254+
// <value> isn't in most cases
255+
// so lowercase the value unless iamMemberIsCaseSensitive and leave the type alone
256+
// since Dec '19 members can be prefixed with "deleted:" to indicate the principal
257+
// has been deleted
258+
func normalizeIamMemberCasing(member string) string {
259+
var pieces []string
260+
if strings.HasPrefix(member, "deleted:") {
261+
pieces = strings.SplitN(member, ":", 3)
262+
if len(pieces) > 2 && !iamMemberIsCaseSensitive(strings.TrimPrefix(member, "deleted:")) {
263+
pieces[2] = strings.ToLower(pieces[2])
264+
}
265+
} else if !iamMemberIsCaseSensitive(member) {
266+
pieces = strings.SplitN(member, ":", 2)
267+
if len(pieces) > 1 {
268+
pieces[1] = strings.ToLower(pieces[1])
269+
}
270+
}
271+
272+
if len(pieces) > 0 {
273+
member = strings.Join(pieces, ":")
274+
}
275+
return member
276+
}
277+
245278
// Construct map of role to set of members from list of bindings.
246279
func createIamBindingsMap(bindings []*cloudresourcemanager.Binding) map[iamBindingKey]map[string]struct{} {
247280
bm := make(map[iamBindingKey]map[string]struct{})
@@ -255,27 +288,7 @@ func createIamBindingsMap(bindings []*cloudresourcemanager.Binding) map[iamBindi
255288
}
256289
// Get each member (user/principal) for the binding
257290
for _, m := range b.Members {
258-
// members are in <type>:<value> format
259-
// <type> is case sensitive
260-
// <value> isn't
261-
// so let's lowercase the value and leave the type alone
262-
// since Dec '19 members can be prefixed with "deleted:" to indicate the principal
263-
// has been deleted
264-
var pieces []string
265-
if strings.HasPrefix(m, "deleted:") {
266-
pieces = strings.SplitN(m, ":", 3)
267-
if len(pieces) > 2 {
268-
pieces[2] = strings.ToLower(pieces[2])
269-
}
270-
} else {
271-
pieces = strings.SplitN(m, ":", 2)
272-
if len(pieces) > 1 {
273-
pieces[1] = strings.ToLower(pieces[1])
274-
}
275-
}
276-
277-
m = strings.Join(pieces, ":")
278-
291+
m = normalizeIamMemberCasing(m)
279292
// Add the member
280293
members[m] = struct{}{}
281294
}

google-beta/iam_test.go

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"reflect"
66
"testing"
77

8+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
89
"google.golang.org/api/cloudresourcemanager/v1"
910
)
1011

@@ -658,7 +659,7 @@ func TestIamCreateIamBindingsMap(t *testing.T) {
658659
input: []*cloudresourcemanager.Binding{
659660
{
660661
Role: "role-1",
661-
Members: []string{"deleted:serviceAccount:user-1", "user-2"},
662+
Members: []string{"deleted:serviceAccount:useR-1", "user-2"},
662663
},
663664
{
664665
Role: "role-2",
@@ -676,11 +677,49 @@ func TestIamCreateIamBindingsMap(t *testing.T) {
676677
Role: "role-3",
677678
Members: []string{"user-3"},
678679
},
680+
{
681+
Role: "role-4",
682+
Members: []string{"deleted:principal:useR-1"},
683+
},
679684
},
680685
expect: map[iamBindingKey]map[string]struct{}{
681686
{"role-1", conditionKey{}}: {"deleted:serviceAccount:user-1": {}, "user-2": {}, "serviceAccount:user-3": {}},
682687
{"role-2", conditionKey{}}: {"deleted:user:user-1": {}, "user-2": {}},
683688
{"role-3", conditionKey{}}: {"user-3": {}},
689+
{"role-4", conditionKey{}}: {"deleted:principal:useR-1": {}},
690+
},
691+
},
692+
{
693+
input: []*cloudresourcemanager.Binding{
694+
{
695+
Role: "role-1",
696+
Members: []string{"principalSet://iam.googleapis.com/projects/1066737951711/locations/global/workloadIdentityPools/example-pool/attribute.aws_role/arn:aws:sts::999999999999:assumed-role/some-eu-central-1-lambdaRole"},
697+
},
698+
{
699+
Role: "role-2",
700+
Members: []string{"principal://iam.googleapis.com/projects/1066737951711/locations/global/workloadIdentityPools/example-pool/attribute.aws_role/arn:aws:sts::999999999999:assumed-role/some-eu-central-1-lambdaRole"},
701+
},
702+
{
703+
Role: "role-1",
704+
Members: []string{"serviceAccount:useR-3"},
705+
},
706+
{
707+
Role: "role-2",
708+
Members: []string{"user-2"},
709+
},
710+
{
711+
Role: "role-3",
712+
Members: []string{"user-3"},
713+
},
714+
{
715+
Role: "role-3",
716+
Members: []string{"principalHierarchy://iam.googleapis.com/projects/1066737951711/locations/global/workloadIdentityPools"},
717+
},
718+
},
719+
expect: map[iamBindingKey]map[string]struct{}{
720+
{"role-1", conditionKey{}}: {"principalSet://iam.googleapis.com/projects/1066737951711/locations/global/workloadIdentityPools/example-pool/attribute.aws_role/arn:aws:sts::999999999999:assumed-role/some-eu-central-1-lambdaRole": {}, "serviceAccount:user-3": {}},
721+
{"role-2", conditionKey{}}: {"principal://iam.googleapis.com/projects/1066737951711/locations/global/workloadIdentityPools/example-pool/attribute.aws_role/arn:aws:sts::999999999999:assumed-role/some-eu-central-1-lambdaRole": {}, "user-2": {}},
722+
{"role-3", conditionKey{}}: {"principalHierarchy://iam.googleapis.com/projects/1066737951711/locations/global/workloadIdentityPools": {}, "user-3": {}},
684723
},
685724
},
686725
{
@@ -748,6 +787,84 @@ func TestIamCreateIamBindingsMap(t *testing.T) {
748787
}
749788
}
750789

790+
func TestIamMember_MemberDiffSuppress(t *testing.T) {
791+
type IamMemberTestcase struct {
792+
name string
793+
old string
794+
new string
795+
equal bool
796+
}
797+
var iamMemberTestcases = []IamMemberTestcase{
798+
{
799+
name: "control",
800+
old: "somevalue",
801+
new: "somevalue",
802+
equal: true,
803+
},
804+
{
805+
name: "principal same casing",
806+
old: "principal:someValueHere",
807+
new: "principal:someValueHere",
808+
equal: true,
809+
},
810+
{
811+
name: "principal not same casing",
812+
old: "principal:somevalueHere",
813+
new: "principal:someValuehere",
814+
equal: false,
815+
},
816+
{
817+
name: "principalSet same casing",
818+
old: "principalSet:someValueHere",
819+
new: "principalSet:someValueHere",
820+
equal: true,
821+
},
822+
{
823+
name: "principalSet not same casing",
824+
old: "principalSet:somevalueHere",
825+
new: "principalSet:someValuehere",
826+
equal: false,
827+
},
828+
{
829+
name: "principalHierarchy same casing",
830+
old: "principalHierarchy:someValueHere",
831+
new: "principalHierarchy:someValueHere",
832+
equal: true,
833+
},
834+
{
835+
name: "principalHierarchy not same casing",
836+
old: "principalHierarchy:somevalueHere",
837+
new: "principalHierarchy:someValuehere",
838+
equal: false,
839+
},
840+
{
841+
name: "serviceAccount same casing",
842+
old: "serviceAccount:[email protected]",
843+
new: "serviceAccount:[email protected]",
844+
equal: true,
845+
},
846+
{
847+
name: "serviceAccount diff casing",
848+
old: "serviceAccount:[email protected]",
849+
new: "serviceAccount:[email protected]",
850+
equal: true,
851+
},
852+
{
853+
name: "random diff",
854+
old: "serviasfsfljJKLSD",
855+
new: "servicsFDJKLSFJdfjdlkfsf",
856+
equal: false,
857+
},
858+
}
859+
860+
for _, testcase := range iamMemberTestcases {
861+
areEqual := iamMemberCaseDiffSuppress("", testcase.old, testcase.new, &schema.ResourceData{})
862+
if areEqual != testcase.equal {
863+
t.Errorf("Testcase %s failed: expected equality to be %t but got %t", testcase.name, testcase.equal, areEqual)
864+
}
865+
}
866+
}
867+
751868
func TestIamListFromIamBindingMap(t *testing.T) {
752869
testCases := []struct {
753870
input map[iamBindingKey]map[string]struct{}

google-beta/resource_dataflow_flex_template_job_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88

99
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
1010
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
11-
compute "google.golang.org/api/compute/v1"
11+
"google.golang.org/api/compute/v1"
1212
)
1313

1414
func TestAccDataflowFlexTemplateJob_basic(t *testing.T) {

0 commit comments

Comments
 (0)