Skip to content

Commit

Permalink
Add IAM Conditions support; enable it in service account IAM (#4541)
Browse files Browse the repository at this point in the history
Signed-off-by: Modular Magician <magic-modules@google.com>
  • Loading branch information
modular-magician authored and danawillow committed Oct 28, 2019
1 parent 7f460c8 commit b86a93f
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 108 deletions.
69 changes: 49 additions & 20 deletions google/iam.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,51 +152,79 @@ func iamPolicyReadModifyWrite(updater ResourceIamUpdater, modify iamPolicyModify
return nil
}

// Flattens AuditConfigs so each role has a single Binding with combined members
// Flattens a list of Bindings so each role+condition has a single Binding with combined members
func mergeBindings(bindings []*cloudresourcemanager.Binding) []*cloudresourcemanager.Binding {
bm := createIamBindingsMap(bindings)
return listFromIamBindingMap(bm)
}

// Flattens Bindings so each role has a single Binding with combined members
func removeAllBindingsWithRole(b []*cloudresourcemanager.Binding, role string) []*cloudresourcemanager.Binding {
type conditionKey struct {
Description string
Expression string
Title string
}

func conditionKeyFromCondition(condition *cloudresourcemanager.Expr) conditionKey {
if condition == nil {
return conditionKey{}
}
return conditionKey{condition.Description, condition.Expression, condition.Title}
}

func (k conditionKey) Empty() bool {
return k == conditionKey{}
}

func (k conditionKey) String() string {
return fmt.Sprintf("%s/%s/%s", k.Title, k.Description, k.Expression)
}

type iamBindingKey struct {
Role string
Condition conditionKey
}

// Removes a single role+condition binding from a list of Bindings
func filterBindingsWithRoleAndCondition(b []*cloudresourcemanager.Binding, role string, condition *cloudresourcemanager.Expr) []*cloudresourcemanager.Binding {
bMap := createIamBindingsMap(b)
delete(bMap, role)
key := iamBindingKey{role, conditionKeyFromCondition(condition)}
delete(bMap, key)
return listFromIamBindingMap(bMap)
}

// Removes given role/bound-member pairs from the given Bindings (i.e subtraction).
// Removes given role+condition/bound-member pairs from the given Bindings (i.e subtraction).
func subtractFromBindings(bindings []*cloudresourcemanager.Binding, toRemove ...*cloudresourcemanager.Binding) []*cloudresourcemanager.Binding {
currMap := createIamBindingsMap(bindings)
toRemoveMap := createIamBindingsMap(toRemove)

for role, removeSet := range toRemoveMap {
members, ok := currMap[role]
for key, removeSet := range toRemoveMap {
members, ok := currMap[key]
if !ok {
continue
}
// Remove all removed members
for m := range removeSet {
delete(members, m)
}
// Remove role from bindings
// Remove role+condition from bindings
if len(members) == 0 {
delete(currMap, role)
delete(currMap, key)
}
}

return listFromIamBindingMap(currMap)
}

// Construct map of role to set of members from list of bindings.
func createIamBindingsMap(bindings []*cloudresourcemanager.Binding) map[string]map[string]struct{} {
bm := make(map[string]map[string]struct{})
func createIamBindingsMap(bindings []*cloudresourcemanager.Binding) map[iamBindingKey]map[string]struct{} {
bm := make(map[iamBindingKey]map[string]struct{})
// Get each binding
for _, b := range bindings {
members := make(map[string]struct{})
key := iamBindingKey{b.Role, conditionKeyFromCondition(b.Condition)}
// Initialize members map
if _, ok := bm[b.Role]; ok {
members = bm[b.Role]
if _, ok := bm[key]; ok {
members = bm[key]
}
// Get each member (user/principal) for the binding
for _, m := range b.Members {
Expand All @@ -214,25 +242,26 @@ func createIamBindingsMap(bindings []*cloudresourcemanager.Binding) map[string]m
members[m] = struct{}{}
}
if len(members) > 0 {
bm[b.Role] = members
bm[key] = members
} else {
delete(bm, b.Role)
delete(bm, key)
}
}
return bm
}

// Return list of Bindings for a map of role to member sets
func listFromIamBindingMap(bm map[string]map[string]struct{}) []*cloudresourcemanager.Binding {
func listFromIamBindingMap(bm map[iamBindingKey]map[string]struct{}) []*cloudresourcemanager.Binding {
rb := make([]*cloudresourcemanager.Binding, 0, len(bm))
for role, members := range bm {
for key, members := range bm {
if len(members) == 0 {
continue
}
rb = append(rb, &cloudresourcemanager.Binding{
Role: role,
b := &cloudresourcemanager.Binding{
Role: key.Role,
Members: stringSliceFromGolangSet(members),
})
}
rb = append(rb, b)
}
return rb
}
Expand Down
1 change: 1 addition & 0 deletions google/iam_service_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package google

import (
"fmt"

"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"google.golang.org/api/cloudresourcemanager/v1"
Expand Down
64 changes: 33 additions & 31 deletions google/iam_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package google

import (
"encoding/json"
"google.golang.org/api/cloudresourcemanager/v1"
"reflect"
"testing"

"google.golang.org/api/cloudresourcemanager/v1"
)

func TestIamMergeBindings(t *testing.T) {
Expand Down Expand Up @@ -163,11 +164,12 @@ func TestIamMergeBindings(t *testing.T) {
}
}

func TestIamRemoveAllBindingsWithRole(t *testing.T) {
func TestIamFilterBindingsWithRoleAndCondition(t *testing.T) {
testCases := []struct {
input []*cloudresourcemanager.Binding
role string
expect []*cloudresourcemanager.Binding
input []*cloudresourcemanager.Binding
role string
conditionTitle string
expect []*cloudresourcemanager.Binding
}{
// No-op
{
Expand Down Expand Up @@ -244,7 +246,7 @@ func TestIamRemoveAllBindingsWithRole(t *testing.T) {
}

for _, tc := range testCases {
got := removeAllBindingsWithRole(tc.input, tc.role)
got := filterBindingsWithRoleAndCondition(tc.input, tc.role, &cloudresourcemanager.Expr{Title: tc.conditionTitle})
if !compareBindings(got, tc.expect) {
t.Errorf("Got unexpected value for removeAllBindingsWithRole(%s, %s).\nActual: %s\nExpected: %s",
debugPrintBindings(tc.input), tc.role, debugPrintBindings(got), debugPrintBindings(tc.expect))
Expand Down Expand Up @@ -410,11 +412,11 @@ func TestIamSubtractFromBindings(t *testing.T) {
func TestIamCreateIamBindingsMap(t *testing.T) {
testCases := []struct {
input []*cloudresourcemanager.Binding
expect map[string]map[string]struct{}
expect map[iamBindingKey]map[string]struct{}
}{
{
input: []*cloudresourcemanager.Binding{},
expect: map[string]map[string]struct{}{},
expect: map[iamBindingKey]map[string]struct{}{},
},
{
input: []*cloudresourcemanager.Binding{
Expand All @@ -423,8 +425,8 @@ func TestIamCreateIamBindingsMap(t *testing.T) {
Members: []string{"user-1", "user-2"},
},
},
expect: map[string]map[string]struct{}{
"role-1": {"user-1": {}, "user-2": {}},
expect: map[iamBindingKey]map[string]struct{}{
{"role-1", conditionKey{}}: {"user-1": {}, "user-2": {}},
},
},
{
Expand All @@ -438,8 +440,8 @@ func TestIamCreateIamBindingsMap(t *testing.T) {
Members: []string{"user-3"},
},
},
expect: map[string]map[string]struct{}{
"role-1": {"user-1": {}, "user-2": {}, "user-3": {}},
expect: map[iamBindingKey]map[string]struct{}{
{"role-1", conditionKey{}}: {"user-1": {}, "user-2": {}, "user-3": {}},
},
},
{
Expand All @@ -453,9 +455,9 @@ func TestIamCreateIamBindingsMap(t *testing.T) {
Members: []string{"user-1"},
},
},
expect: map[string]map[string]struct{}{
"role-1": {"user-1": {}, "user-2": {}},
"role-2": {"user-1": {}},
expect: map[iamBindingKey]map[string]struct{}{
{"role-1", conditionKey{}}: {"user-1": {}, "user-2": {}},
{"role-2", conditionKey{}}: {"user-1": {}},
},
},
{
Expand All @@ -481,35 +483,35 @@ func TestIamCreateIamBindingsMap(t *testing.T) {
Members: []string{"user-3"},
},
},
expect: map[string]map[string]struct{}{
"role-1": {"user-1": {}, "user-2": {}, "user-3": {}},
"role-2": {"user-1": {}, "user-2": {}},
"role-3": {"user-3": {}},
expect: map[iamBindingKey]map[string]struct{}{
{"role-1", conditionKey{}}: {"user-1": {}, "user-2": {}, "user-3": {}},
{"role-2", conditionKey{}}: {"user-1": {}, "user-2": {}},
{"role-3", conditionKey{}}: {"user-3": {}},
},
},
}

for _, tc := range testCases {
got := createIamBindingsMap(tc.input)
if !reflect.DeepEqual(got, tc.expect) {
t.Errorf("Unexpected value for subtractFromBindings(%s).\nActual: %#v\nExpected: %#v\n",
t.Errorf("Unexpected value for createIamBindingsMap(%s).\nActual: %#v\nExpected: %#v\n",
debugPrintBindings(tc.input), got, tc.expect)
}
}
}

func TestIamListFromIamBindingMap(t *testing.T) {
testCases := []struct {
input map[string]map[string]struct{}
input map[iamBindingKey]map[string]struct{}
expect []*cloudresourcemanager.Binding
}{
{
input: map[string]map[string]struct{}{},
input: map[iamBindingKey]map[string]struct{}{},
expect: []*cloudresourcemanager.Binding{},
},
{
input: map[string]map[string]struct{}{
"role-1": {"user-1": {}, "user-2": {}},
input: map[iamBindingKey]map[string]struct{}{
{"role-1", conditionKey{}}: {"user-1": {}, "user-2": {}},
},
expect: []*cloudresourcemanager.Binding{
{
Expand All @@ -519,9 +521,9 @@ func TestIamListFromIamBindingMap(t *testing.T) {
},
},
{
input: map[string]map[string]struct{}{
"role-1": {"user-1": {}},
"role-2": {"user-1": {}, "user-2": {}},
input: map[iamBindingKey]map[string]struct{}{
{"role-1", conditionKey{}}: {"user-1": {}},
{"role-2", conditionKey{}}: {"user-1": {}, "user-2": {}},
},
expect: []*cloudresourcemanager.Binding{
{
Expand All @@ -535,9 +537,9 @@ func TestIamListFromIamBindingMap(t *testing.T) {
},
},
{
input: map[string]map[string]struct{}{
"role-1": {"user-1": {}, "user-2": {}},
"role-2": {},
input: map[iamBindingKey]map[string]struct{}{
{"role-1", conditionKey{}}: {"user-1": {}, "user-2": {}},
{"role-2", conditionKey{}}: {},
},
expect: []*cloudresourcemanager.Binding{
{
Expand All @@ -551,7 +553,7 @@ func TestIamListFromIamBindingMap(t *testing.T) {
for _, tc := range testCases {
got := listFromIamBindingMap(tc.input)
if !compareBindings(got, tc.expect) {
t.Errorf("Unexpected value for subtractFromBindings(%s).\nActual: %#v\nExpected: %#v\n",
t.Errorf("Unexpected value for subtractFromBindings(%v).\nActual: %#v\nExpected: %#v\n",
tc.input, debugPrintBindings(got), debugPrintBindings(tc.expect))
}
}
Expand Down
Loading

0 comments on commit b86a93f

Please sign in to comment.