...

Source file src/github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/iam/partialpolicy/bindings.go

Documentation: github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/iam/partialpolicy

     1  // Copyright 2022 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package partialpolicy
    16  
    17  import (
    18  	"fmt"
    19  	"sort"
    20  
    21  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/iam/v1beta1"
    22  )
    23  
    24  type MemberIdentityResolver interface {
    25  	Resolve(v1beta1.Member, *v1beta1.MemberSource, string) (string, error)
    26  }
    27  
    28  type iamBindingKey struct {
    29  	Role      string
    30  	Condition v1beta1.IAMCondition
    31  }
    32  
    33  // ComputePartialPolicyWithMergedBindings returns the IAMPartialPolicy that results after the user's intent (as specified by the input
    34  // IAMPartialPolicy) is merged with the underlying IAM policy (as specified by the input IAMPolicy). This function also resolves all memberFrom
    35  // fields to member fields and ensures the returned IAMPartialPolicy only contains member fields.
    36  
    37  // The status.AllBindings in the returned IAMPartialPolicy reflects a mix of user specified bindings and the existing bindings associated with the GCP resource.
    38  // The merge strategy takes effect on the member level with {role, condition} tuples as keys.
    39  // The status.LastAppliedBindings in the returned IAMPartialPolicy reflects a list of canonical bindings that specified by users.
    40  func ComputePartialPolicyWithMergedBindings(partialPolicy *v1beta1.IAMPartialPolicy, livePolicy *v1beta1.IAMPolicy, resolver MemberIdentityResolver) (*v1beta1.IAMPartialPolicy, error) {
    41  	desiredPartialPolicy := partialPolicy.DeepCopy()
    42  	specifiedBindings, err := ConvertIAMPartialBindingsToIAMPolicyBindings(partialPolicy, resolver)
    43  	if err != nil {
    44  		return nil, fmt.Errorf("error converting IAMPartialPolicy bindings to IAMPolicy bindings: %w", err)
    45  	}
    46  
    47  	// merge live bindings with user specified bindings
    48  	mergeBindings := mergeBindingSlices(specifiedBindings, livePolicy.Spec.Bindings)
    49  	// compute members that users intend to delete per binding
    50  	toRemove := computeDeletedMembersPerBinding(specifiedBindings, partialPolicy.Status.LastAppliedBindings)
    51  	// remove deleted members per binding
    52  	desiredAllBindings := removeMembersPerBinding(mergeBindings, toRemove)
    53  
    54  	// record lastAppliedBinding as user specified bindings
    55  	sortBindingSlice(specifiedBindings)
    56  	desiredPartialPolicy.Status.LastAppliedBindings = specifiedBindings
    57  	sortBindingSlice(desiredAllBindings)
    58  	desiredPartialPolicy.Status.AllBindings = desiredAllBindings
    59  	return desiredPartialPolicy, nil
    60  }
    61  
    62  // ComputePartialPolicyWithRemainingBindings returns the IAMPartialPolicy that results after the user's last applied bindings (as specified by the input
    63  // IAMPartialPolicy) are deleted from the underlying IAM Policy (as specified by the input IAMPolicy). This function is intended to be called on IAMPartialPolicy
    64  // resources deletion.
    65  //
    66  // The status.AllBindings in the returned IAMPartialPolicy reflects the remaining bindings that are computed by pruning last applied bindings (bindings managed by KCC)
    67  // from all the existing bindings from the underlying IAM Policy.
    68  // The status.LastAppliedBindings in the returned IAMPartialPolicy will be cleared.
    69  func ComputePartialPolicyWithRemainingBindings(partialPolicy *v1beta1.IAMPartialPolicy, livePolicy *v1beta1.IAMPolicy) *v1beta1.IAMPartialPolicy {
    70  	desiredPartialPolicy := partialPolicy.DeepCopy()
    71  	remainingBindings := removeMembersPerBinding(livePolicy.Spec.Bindings, partialPolicy.Status.LastAppliedBindings)
    72  	// record the remaining bindings as (new) all bindings.
    73  	sortBindingSlice(remainingBindings)
    74  	desiredPartialPolicy.Status.AllBindings = remainingBindings
    75  	// clear last applied bindings
    76  	desiredPartialPolicy.Status.LastAppliedBindings = make([]v1beta1.IAMPolicyBinding, 0)
    77  	return desiredPartialPolicy
    78  }
    79  
    80  func ConvertIAMPartialBindingsToIAMPolicyBindings(partialPolicy *v1beta1.IAMPartialPolicy, resolver MemberIdentityResolver) (bindings []v1beta1.IAMPolicyBinding, err error) {
    81  	res := make([]v1beta1.IAMPolicyBinding, 0)
    82  	for _, binding := range partialPolicy.Spec.Bindings {
    83  		convertedBinding, err := toIAMPolicyBinding(binding, resolver, partialPolicy.Namespace)
    84  		if err != nil {
    85  			return bindings, fmt.Errorf("error converting IAMPartialPolicy binding to IAMPolicy binding: %w", err)
    86  		}
    87  		res = append(res, convertedBinding)
    88  	}
    89  	return mergeBindingsWithSameRoleAndCondition(res), nil
    90  }
    91  
    92  func toIAMPolicyBinding(b v1beta1.IAMPartialPolicyBinding, resolver MemberIdentityResolver, defaultNamespace string) (binding v1beta1.IAMPolicyBinding, err error) {
    93  	members := make([]v1beta1.Member, 0)
    94  	for _, m := range b.Members {
    95  		resolvedMember, err := resolver.Resolve(m.Member, m.MemberFrom, defaultNamespace)
    96  		if err != nil {
    97  			return binding, fmt.Errorf("error resolving member identity of IAMPartialPolicy binding: %w", err)
    98  		}
    99  		members = append(members, v1beta1.Member(resolvedMember))
   100  	}
   101  
   102  	return v1beta1.IAMPolicyBinding{
   103  		Role:      b.Role,
   104  		Condition: b.Condition,
   105  		Members:   members,
   106  	}, nil
   107  }
   108  
   109  func mergeBindingsWithSameRoleAndCondition(bindings []v1beta1.IAMPolicyBinding) []v1beta1.IAMPolicyBinding {
   110  	bindingMap := mergeBindings(bindings)
   111  	mergedBindings := make([]v1beta1.IAMPolicyBinding, 0)
   112  	for _, v := range bindingMap {
   113  		if len(v.Members) > 0 {
   114  			mergedBindings = append(mergedBindings, v)
   115  		}
   116  	}
   117  	return mergedBindings
   118  }
   119  
   120  func mergeBindingSlices(bindingSlice1, bindingSlice2 []v1beta1.IAMPolicyBinding) []v1beta1.IAMPolicyBinding {
   121  	mergedBindings := make([]v1beta1.IAMPolicyBinding, 0)
   122  	mergedBindings = append(mergedBindings, bindingSlice1...)
   123  	mergedBindings = append(mergedBindings, bindingSlice2...)
   124  	return mergeBindingsWithSameRoleAndCondition(mergedBindings)
   125  }
   126  
   127  func mergeBindings(bindings []v1beta1.IAMPolicyBinding) map[iamBindingKey]v1beta1.IAMPolicyBinding {
   128  	bindingMap := make(map[iamBindingKey]v1beta1.IAMPolicyBinding)
   129  	for _, a := range bindings {
   130  		k := getIamBindingKey(a)
   131  		b, ok := bindingMap[k]
   132  		if !ok {
   133  			bindingMap[k] = *a.DeepCopy()
   134  			continue
   135  		}
   136  		b.Members = mergeMembers(b.Members, a.Members)
   137  		bindingMap[k] = b
   138  	}
   139  	return bindingMap
   140  }
   141  
   142  func computeDeletedMembersPerBinding(bindings, lastAppliedBindings []v1beta1.IAMPolicyBinding) []v1beta1.IAMPolicyBinding {
   143  	res := make([]v1beta1.IAMPolicyBinding, 0)
   144  	bindingMap := mergeBindings(bindings)
   145  	lastAppliedBindingMap := mergeBindings(lastAppliedBindings)
   146  	for k, a := range lastAppliedBindingMap {
   147  		b, ok := bindingMap[k]
   148  		if !ok {
   149  			res = append(res, *a.DeepCopy())
   150  			continue
   151  		}
   152  		removedMembers := computeDeletedMembers(b.Members, a.Members)
   153  		if len(removedMembers) > 0 {
   154  			b.Members = removedMembers
   155  			res = append(res, b)
   156  		}
   157  	}
   158  	return res
   159  }
   160  
   161  func getIamBindingKey(binding v1beta1.IAMPolicyBinding) iamBindingKey {
   162  	k := iamBindingKey{}
   163  	k.Role = binding.Role
   164  	if binding.Condition != nil {
   165  		k.Condition = *binding.Condition
   166  	}
   167  	return k
   168  }
   169  
   170  func removeMembersPerBinding(bindings, deletedBindings []v1beta1.IAMPolicyBinding) []v1beta1.IAMPolicyBinding {
   171  	bindingMap := mergeBindings(bindings)
   172  	for _, a := range deletedBindings {
   173  		k := getIamBindingKey(a)
   174  		if b, ok := bindingMap[k]; ok {
   175  			b.Members = removeDeletedMembers(b.Members, a.Members)
   176  			bindingMap[k] = b
   177  		}
   178  	}
   179  	res := make([]v1beta1.IAMPolicyBinding, 0)
   180  	for _, b := range bindingMap {
   181  		if len(b.Members) > 0 {
   182  			res = append(res, b)
   183  		}
   184  	}
   185  	return res
   186  }
   187  
   188  func removeDeletedMembers(members, deletedMembers []v1beta1.Member) []v1beta1.Member {
   189  	memberMap := make(map[v1beta1.Member]bool)
   190  	for _, m := range deletedMembers {
   191  		memberMap[m] = true
   192  	}
   193  	res := make([]v1beta1.Member, 0)
   194  	for _, m := range members {
   195  		if _, ok := memberMap[m]; !ok {
   196  			res = append(res, m)
   197  		}
   198  	}
   199  	return res
   200  }
   201  
   202  func computeDeletedMembers(members, lastAppliedMembers []v1beta1.Member) []v1beta1.Member {
   203  	memberMap := make(map[v1beta1.Member]bool)
   204  	res := make([]v1beta1.Member, 0)
   205  	for _, m := range members {
   206  		memberMap[m] = true
   207  	}
   208  	for _, m := range lastAppliedMembers {
   209  		if _, ok := memberMap[m]; !ok {
   210  			res = append(res, m)
   211  		}
   212  	}
   213  	return res
   214  }
   215  
   216  func mergeMembers(memberSlice1, memberSlice2 []v1beta1.Member) []v1beta1.Member {
   217  	memberMap := make(map[v1beta1.Member]bool)
   218  	for _, m := range memberSlice1 {
   219  		memberMap[m] = true
   220  	}
   221  	for _, m := range memberSlice2 {
   222  		memberMap[m] = true
   223  	}
   224  	res := make([]v1beta1.Member, 0)
   225  	for k, _ := range memberMap {
   226  		res = append(res, k)
   227  	}
   228  	sort.Slice(res, func(i, j int) bool {
   229  		return res[i] < res[j]
   230  	})
   231  	return res
   232  }
   233  
   234  func sortBindingSlice(bindings []v1beta1.IAMPolicyBinding) {
   235  	sort.Slice(bindings, func(i, j int) bool {
   236  		k1 := getIamBindingKey(bindings[i])
   237  		k2 := getIamBindingKey(bindings[j])
   238  		if k1.Role != k2.Role {
   239  			return k1.Role < k2.Role
   240  		}
   241  		if k1.Condition.Title != k2.Condition.Title {
   242  			return k1.Condition.Title < k2.Condition.Title
   243  		}
   244  		if k1.Condition.Description != k2.Condition.Description {
   245  			return k1.Condition.Description < k2.Condition.Description
   246  		}
   247  		if k1.Condition.Expression != k2.Condition.Expression {
   248  			return k1.Condition.Expression < k2.Condition.Expression
   249  		}
   250  		return false
   251  	})
   252  }
   253  

View as plain text