...

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

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

     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 iamclient
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"fmt"
    21  	"reflect"
    22  	"strings"
    23  
    24  	corekccv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
    25  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/iam/v1beta1"
    26  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/metadata"
    27  
    28  	"k8s.io/apimachinery/pkg/runtime/schema"
    29  )
    30  
    31  func (c *IAMClient) isDCLBasedIAMResource(iamInterface interface{}) bool {
    32  	_, resourceRef := extractNamespaceAndResourceReference(iamInterface)
    33  	return c.isDCLBasedResource(resourceRef.GroupVersionKind())
    34  }
    35  
    36  func (c *IAMClient) isDCLBasedResource(gvk schema.GroupVersionKind) bool {
    37  	return metadata.IsDCLBasedResourceKind(gvk, c.DCLIAMClient.converter.MetadataLoader)
    38  }
    39  
    40  // ResolveMemberIdentity checks only one of Member/MemberFrom is provided, and then tries to resolve identity.
    41  // MemberFrom can only have oneOf a ServiceAccountRef, a LogSinkRef, a SQLInstanceRef, so to resolve these
    42  // values, it is necessary to call on the TFIAMClient
    43  func ResolveMemberIdentity(ctx context.Context, member v1beta1.Member,
    44  	memberFrom *v1beta1.MemberSource, namespace string, tfIAMClient *TFIAMClient) (id string, err error) {
    45  	if member != "" && memberFrom != nil {
    46  		return id, fmt.Errorf("both 'member' and 'memberFrom' are used. Exactly one of them must be used")
    47  	}
    48  
    49  	if member == "" && memberFrom == nil {
    50  		return id, fmt.Errorf("both 'member' and 'memberFrom' are empty. Exactly one of them must be used")
    51  	}
    52  
    53  	if member != "" {
    54  		return string(member), nil
    55  	}
    56  
    57  	var refs []*v1beta1.MemberReference
    58  	var gvks []schema.GroupVersionKind
    59  
    60  	if memberFrom.ServiceAccountRef != nil {
    61  		refs = append(refs, memberFrom.ServiceAccountRef)
    62  		gvks = append(gvks, IAMServiceAccountGVK)
    63  	}
    64  
    65  	if memberFrom.LogSinkRef != nil {
    66  		refs = append(refs, memberFrom.LogSinkRef)
    67  		gvks = append(gvks, LoggingLogSinkGVK)
    68  	}
    69  
    70  	if memberFrom.SQLInstanceRef != nil {
    71  		refs = append(refs, memberFrom.SQLInstanceRef)
    72  		gvks = append(gvks, SQLInstanceGVK)
    73  	}
    74  
    75  	if memberFrom.ServiceIdentityRef != nil {
    76  		refs = append(refs, memberFrom.ServiceIdentityRef)
    77  		gvks = append(gvks, ServiceIdentityGVK)
    78  	}
    79  
    80  	if len(refs) == 1 {
    81  		return tfIAMClient.resolveMemberReference(ctx, refs[0], gvks[0], namespace)
    82  	} else {
    83  		return id, fmt.Errorf("%v memberFrom refs found. Exactly one Of 'logSinkRef', 'serviceAccountRef', 'sqlInstanceRef', 'serviceIdentityRef' must be used", len(refs))
    84  	}
    85  }
    86  
    87  func extractNamespaceAndResourceReference(iamInterface interface{}) (string, v1beta1.ResourceReference) {
    88  	switch iamObject := iamInterface.(type) {
    89  	case *v1beta1.IAMPolicy:
    90  		return iamObject.Namespace, iamObject.Spec.ResourceReference
    91  	case *v1beta1.IAMPolicyMember:
    92  		return iamObject.Namespace, iamObject.Spec.ResourceReference
    93  	case *v1beta1.IAMAuditConfig:
    94  		return iamObject.Namespace, iamObject.Spec.ResourceReference
    95  	}
    96  	panic(fmt.Errorf("unknown type: %v", reflect.TypeOf(iamInterface).Name()))
    97  }
    98  
    99  func embedPolicyData(spec map[string]interface{}) error {
   100  	policyData, ok := spec["policyData"].(string)
   101  	if !ok {
   102  		return nil
   103  	}
   104  	delete(spec, "policyData")
   105  	m := make(map[string]interface{})
   106  	if err := json.Unmarshal([]byte(policyData), &m); err != nil {
   107  		return fmt.Errorf("error converting policyData '%v' to map: %w", policyData, err)
   108  	}
   109  	for k, v := range m {
   110  		spec[k] = v
   111  	}
   112  	return nil
   113  }
   114  
   115  // An unfortunate reality is that the GVK is not always properly filled in when
   116  // reading a resource from the K8s API server, and there are functions that
   117  // need the Kind to be filled in to work (e.g. krmtotf.NewResource,
   118  // k8s.MarshalAsUnstructured, etc.). The Kind is not set because the TypeMeta
   119  // is empty. The reason why the TypeMeta is empty is because in
   120  // k8s.io/apimachinery/pkg/runtime/serializer/versioning/versioning.go the
   121  // GVK is cleared inside of Decode(...)
   122  func SetGVK(iamInterface interface{}) {
   123  	switch iamObject := iamInterface.(type) {
   124  	case *v1beta1.IAMPolicy:
   125  		setPolicyGVK(iamObject)
   126  	case *v1beta1.IAMPartialPolicy:
   127  		setPartialPolicyGVK(iamObject)
   128  	case *v1beta1.IAMPolicyMember:
   129  		setPolicyMemberGVK(iamObject)
   130  	case *v1beta1.IAMAuditConfig:
   131  		setAuditConfigGVK(iamObject)
   132  	default:
   133  		panic(fmt.Errorf("unknown type: %v", reflect.TypeOf(iamInterface).Name()))
   134  	}
   135  }
   136  
   137  func setPolicyGVK(policy *v1beta1.IAMPolicy) {
   138  	policy.SetGroupVersionKind(v1beta1.IAMPolicyGVK)
   139  }
   140  
   141  func setPartialPolicyGVK(partialPolicy *v1beta1.IAMPartialPolicy) {
   142  	partialPolicy.SetGroupVersionKind(v1beta1.IAMPartialPolicyGVK)
   143  }
   144  
   145  func setPolicyMemberGVK(policyMember *v1beta1.IAMPolicyMember) {
   146  	policyMember.SetGroupVersionKind(v1beta1.IAMPolicyMemberGVK)
   147  }
   148  
   149  func setAuditConfigGVK(auditConfig *v1beta1.IAMAuditConfig) {
   150  	auditConfig.SetGroupVersionKind(v1beta1.IAMAuditConfigGVK)
   151  }
   152  
   153  func resourceSupportsIAMPolicy(rc *corekccv1alpha1.ResourceConfig) bool {
   154  	return rc.IAMConfig.PolicyName != ""
   155  }
   156  
   157  func resourceSupportsIAMPolicyMember(rc *corekccv1alpha1.ResourceConfig) bool {
   158  	return rc.IAMConfig.PolicyMemberName != ""
   159  }
   160  
   161  func resourceSupportsIAMAuditConfigs(rc *corekccv1alpha1.ResourceConfig) bool {
   162  	return rc.IAMConfig.AuditConfigName != ""
   163  }
   164  
   165  func useIfNonEmptyElseDefaultTo(str, backup string) string {
   166  	if str != "" {
   167  		return str
   168  	}
   169  	return backup
   170  }
   171  
   172  func parseNameFromId(id string) (string, error) {
   173  	if strings.TrimSpace(id) == "" {
   174  		return "", fmt.Errorf("error parsing name from id: id is empty")
   175  	}
   176  	parts := strings.Split(id, "/")
   177  	return parts[len(parts)-1], nil
   178  }
   179  
   180  func idMatchesTemplate(id, idTemplate string) bool {
   181  	idTokens := strings.Split(id, "/")
   182  	idTemplateTokens := strings.Split(idTemplate, "/")
   183  	if len(idTokens) != len(idTemplateTokens) {
   184  		return false
   185  	}
   186  	for i := range idTokens {
   187  		if idTokens[i] != idTemplateTokens[i] &&
   188  			!idTemplateVarsRegex.MatchString(idTemplateTokens[i]) {
   189  			return false
   190  		}
   191  	}
   192  	return true
   193  }
   194  

View as plain text