Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IAM Conditions support; enable it in service account IAM #1188

Merged
merged 1 commit into from
Oct 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions google-beta/data_source_google_iam_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,27 @@ func dataSourceGoogleIamPolicy() *schema.Resource {
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"condition": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"expression": {
Type: schema.TypeString,
Required: true,
},
"title": {
Type: schema.TypeString,
Required: true,
},
"description": {
Type: schema.TypeString,
Optional: true,
},
},
},
},
},
},
},
Expand Down Expand Up @@ -99,13 +120,15 @@ func dataSourceGoogleIamPolicyRead(d *schema.ResourceData, meta interface{}) err
for i, v := range bset.List() {
binding := v.(map[string]interface{})
members := convertStringSet(binding["members"].(*schema.Set))
condition := expandIamCondition(binding["condition"])

// Sort members to get simpler diffs as it's what the API does
sort.Strings(members)

policy.Bindings[i] = &cloudresourcemanager.Binding{
Role: binding["role"].(string),
Members: members,
Role: binding["role"].(string),
Members: members,
Condition: condition,
}
}

Expand Down
77 changes: 57 additions & 20 deletions google-beta/iam.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
)

const maxBackoffSeconds = 30
const iamPolicyVersion = 3

// These types are implemented per GCP resource type and specify how to do per-resource IAM operations.
// They are used in the generic Terraform IAM resource definitions
Expand Down Expand Up @@ -152,51 +153,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 +243,33 @@ 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),
})
}
if !key.Condition.Empty() {
b.Condition = &cloudresourcemanager.Expr{
Description: key.Condition.Description,
Expression: key.Condition.Expression,
Title: key.Condition.Title,
}
}
rb = append(rb, b)
}
return rb
}
Expand Down
3 changes: 2 additions & 1 deletion google-beta/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 Expand Up @@ -35,7 +36,7 @@ func ServiceAccountIdParseFunc(d *schema.ResourceData, _ *Config) error {
}

func (u *ServiceAccountIamUpdater) GetResourceIamPolicy() (*cloudresourcemanager.Policy, error) {
p, err := u.Config.clientIAM.Projects.ServiceAccounts.GetIamPolicy(u.serviceAccountId).Do()
p, err := u.Config.clientIAM.Projects.ServiceAccounts.GetIamPolicy(u.serviceAccountId).OptionsRequestedPolicyVersion(iamPolicyVersion).Do()

if err != nil {
return nil, errwrap.Wrapf(fmt.Sprintf("Error retrieving IAM policy for %s: {{err}}", u.DescribeResource()), err)
Expand Down
Loading