1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package iamclient
16
17 import (
18 "context"
19 "fmt"
20 "reflect"
21
22 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/iam/v1beta1"
23 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl"
24 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/conversion"
25 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/extension"
26 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/kcclite"
27 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/schema/dclschemaloader"
28 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/gvks/externalonlygvks"
29 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
30 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/servicemapping/servicemappingloader"
31 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util"
32
33 dcliam "github.com/GoogleCloudPlatform/declarative-resource-client-library/services/google/iam"
34 dclunstruct "github.com/GoogleCloudPlatform/declarative-resource-client-library/unstructured"
35 dclunstructiam "github.com/GoogleCloudPlatform/declarative-resource-client-library/unstructured/google/iam"
36 "github.com/nasa9084/go-openapi"
37 "k8s.io/apimachinery/pkg/api/errors"
38 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
39 "k8s.io/apimachinery/pkg/types"
40 "sigs.k8s.io/controller-runtime/pkg/client"
41 )
42
43 type DCLIAMClient struct {
44 dclClient *dcliam.Client
45 kubeClient client.Client
46 converter *conversion.Converter
47 smLoader *servicemappingloader.ServiceMappingLoader
48 }
49
50 func (d *DCLIAMClient) SetPolicyMember(ctx context.Context, tfIAMClient *TFIAMClient, policyMember *v1beta1.IAMPolicyMember) (*v1beta1.IAMPolicyMember, error) {
51 dclResource, nn, err := d.getDCLResourceAndNamespacedNameFromPolicyMember(ctx, policyMember)
52 if err != nil {
53 return nil, err
54 }
55
56 dclPolicyMember, err := newDCLPolicyMemberFromKRMPolicyMember(ctx, policyMember, dclResource, nn.Namespace, tfIAMClient)
57 if err != nil {
58 return nil, fmt.Errorf("error converting DCL Policy Member from KRM Policy Member: %w", err)
59 }
60 dclPolicyMemberResource := dclunstructiam.MemberToUnstructured(dclPolicyMember)
61
62 _, err = dclunstruct.SetPolicyMember(ctx, d.dclClient.Config, dclResource, dclPolicyMemberResource)
63 if err != nil {
64 return nil, fmt.Errorf("error setting IAMPolicyMember for resource %v: %w", nn, err)
65 }
66 return policyMember, nil
67 }
68
69 func (d *DCLIAMClient) GetPolicyMember(ctx context.Context, tfIAMClient *TFIAMClient, policyMember *v1beta1.IAMPolicyMember) (*v1beta1.IAMPolicyMember, error) {
70 dclResource, nn, err := d.getDCLResourceAndNamespacedNameFromPolicyMember(ctx, policyMember)
71 if err != nil {
72 return nil, err
73 }
74 id, err := ResolveMemberIdentity(ctx, policyMember.Spec.Member, policyMember.Spec.MemberFrom, nn.Namespace, tfIAMClient)
75 if err != nil {
76 return nil, err
77 }
78 dclPolicyResource, err := dclunstruct.GetPolicyMember(ctx, d.dclClient.Config,
79 dclResource, policyMember.Spec.Role, id)
80 if err != nil {
81 return nil, fmt.Errorf("error getting IAMPolicyMember for resource %v: %w", nn, err)
82 }
83 return newIAMPolicyMemberFromDCLResource(dclPolicyResource, policyMember)
84 }
85
86 func (d *DCLIAMClient) DeletePolicyMember(ctx context.Context, tfIAMClient *TFIAMClient, policyMember *v1beta1.IAMPolicyMember) error {
87 dclResource, nn, err := d.getDCLResourceAndNamespacedNameFromPolicyMember(ctx, policyMember)
88 if err != nil {
89 return err
90 }
91
92 dclPolicyMember, err := newDCLPolicyMemberFromKRMPolicyMember(ctx, policyMember, dclResource, nn.Namespace, tfIAMClient)
93 if err != nil {
94 return fmt.Errorf("error converting DCL Policy Member from KRM Policy Member: %w", err)
95 }
96 dclPolicyMemberResource := dclunstructiam.MemberToUnstructured(dclPolicyMember)
97
98 err = dclunstruct.DeletePolicyMember(ctx, d.dclClient.Config, dclResource, dclPolicyMemberResource)
99 if err != nil {
100 return fmt.Errorf("error deleting IAMPolicyMember for resource: %w", err)
101 }
102 return nil
103 }
104
105 func (d *DCLIAMClient) SetPolicy(ctx context.Context, policy *v1beta1.IAMPolicy) (*v1beta1.IAMPolicy, error) {
106 namespace, resourceRef := extractNamespaceAndResourceReference(policy)
107 nn := types.NamespacedName{
108 Namespace: useIfNonEmptyElseDefaultTo(resourceRef.Namespace, namespace),
109 Name: resourceRef.Name,
110 }
111 dclSchema, err := d.getSchemaFromResourceReference(resourceRef)
112 if err != nil {
113 return nil, err
114 }
115
116
117 supportsIAM, err := extension.HasIam(dclSchema)
118 if err != nil {
119 return nil, err
120 }
121 if !supportsIAM {
122 return nil, fmt.Errorf("invalid resource reference: kind %v does not support IAM policies", resourceRef.Kind)
123 }
124
125 dclResource, err := d.getDCLResource(ctx, resourceRef, dclSchema, namespace)
126 if err != nil {
127 return nil, fmt.Errorf("error getting referenced DCL resource, with reference %v: %w", nn, err)
128 }
129
130 dclPolicy, err := newDCLPolicyFromKRMPolicy(policy, dclResource)
131 if err != nil {
132 return nil, fmt.Errorf("error converting DCLPolicy from KRM Policy: %w", err)
133 }
134 dclPolicyResource := dclunstructiam.PolicyToUnstructured(dclPolicy)
135 liveDCLResource, err := dclunstruct.GetPolicy(ctx, d.dclClient.Config, dclResource)
136 if err != nil {
137 return nil, fmt.Errorf("error getting IAMPolicy for resource %v: %w", nn, err)
138 }
139
140 if reflect.DeepEqual(dclPolicyResource, liveDCLResource) {
141 logger.Info("underlying resource is already up to date", "resource", k8s.GetNamespacedName(policy))
142 return newIAMPolicyFromDCLResource(liveDCLResource, policy)
143 }
144 dclPolicyResource, err = dclunstruct.SetPolicyWithEtag(ctx, d.dclClient.Config, dclResource, dclPolicyResource)
145 if err != nil {
146 return nil, fmt.Errorf("error setting IAMPolicy for resource %v: %w", nn, err)
147 }
148
149 return newIAMPolicyFromDCLResource(dclPolicyResource, policy)
150 }
151
152 func (d *DCLIAMClient) GetPolicy(ctx context.Context, policy *v1beta1.IAMPolicy) (*v1beta1.IAMPolicy, error) {
153 namespace, resourceRef := extractNamespaceAndResourceReference(policy)
154 nn := types.NamespacedName{
155 Namespace: useIfNonEmptyElseDefaultTo(resourceRef.Namespace, namespace),
156 Name: resourceRef.Name,
157 }
158 dclSchema, err := d.getSchemaFromResourceReference(resourceRef)
159 if err != nil {
160 return nil, err
161 }
162
163
164 supportsIAM, err := extension.HasIam(dclSchema)
165 if err != nil {
166 return nil, err
167 }
168 if !supportsIAM {
169 return nil, fmt.Errorf("invalid resource reference: kind %v does not support IAM policies", resourceRef.Kind)
170 }
171
172 dclResource, err := d.getDCLResource(ctx, resourceRef, dclSchema, namespace)
173 if err != nil {
174 return nil, fmt.Errorf("error getting referenced DCL resource, with reference %v: %w", nn, err)
175 }
176
177 dclPolicyResource, err := dclunstruct.GetPolicy(ctx, d.dclClient.Config, dclResource)
178 if err != nil {
179 return nil, fmt.Errorf("error getting IAMPolicy for resource %v: %w", nn, err)
180 }
181 return newIAMPolicyFromDCLResource(dclPolicyResource, policy)
182 }
183
184 func (d *DCLIAMClient) DeletePolicy(ctx context.Context, policy *v1beta1.IAMPolicy) error {
185 namespace, resourceRef := extractNamespaceAndResourceReference(policy)
186 nn := types.NamespacedName{
187 Namespace: useIfNonEmptyElseDefaultTo(resourceRef.Namespace, namespace),
188 Name: resourceRef.Name,
189 }
190 dclSchema, err := d.getSchemaFromResourceReference(resourceRef)
191 if err != nil {
192 return err
193 }
194
195
196 supportsIAM, err := extension.HasIam(dclSchema)
197 if err != nil {
198 return err
199 }
200 if !supportsIAM {
201 return fmt.Errorf("invalid resource reference: kind %v does not support IAM policies", resourceRef.Kind)
202 }
203
204 dclResource, err := d.getDCLResource(ctx, resourceRef, dclSchema, namespace)
205 if err != nil {
206 return fmt.Errorf("error getting referenced DCL resource, with reference %v: %w", nn, err)
207 }
208
209 dclPolicyResource, err := dclunstruct.GetPolicy(ctx, d.dclClient.Config, dclResource)
210 if err != nil {
211 return fmt.Errorf("error getting IAMPolicy for resource%v: %w", nn, err)
212 }
213
214
215
216 emptyPolicy := setEmptyPolicyForDeletion(dclPolicyResource)
217 _, err = dclunstruct.SetPolicy(ctx, d.dclClient.Config, dclResource, emptyPolicy)
218 if err != nil {
219 return fmt.Errorf("error deleting IAMPolicy for resource: %w", err)
220 }
221 return nil
222 }
223
224 func (d *DCLIAMClient) getSchemaFromResourceReference(resourceRef v1beta1.ResourceReference) (*openapi.Schema, error) {
225 gvk := resourceRef.GroupVersionKind()
226
227 if externalonlygvks.IsExternalOnlyGVK(gvk) {
228 return nil, fmt.Errorf("invalid DCL resource reference type: kind %v is not supported", resourceRef.Kind)
229 }
230
231 dclSchema, err := dclschemaloader.GetDCLSchemaForGVK(gvk,
232 d.converter.MetadataLoader, d.converter.SchemaLoader)
233 if err != nil {
234 return nil, err
235 }
236 return dclSchema, nil
237 }
238
239 func (d *DCLIAMClient) getDCLResourceAndNamespacedNameFromPolicyMember(ctx context.Context, policyMember *v1beta1.IAMPolicyMember) (*dclunstruct.Resource, types.NamespacedName, error) {
240 namespace, resourceRef := extractNamespaceAndResourceReference(policyMember)
241 nn := types.NamespacedName{
242 Namespace: useIfNonEmptyElseDefaultTo(resourceRef.Namespace, namespace),
243 Name: resourceRef.Name,
244 }
245 dclSchema, err := d.getSchemaFromResourceReference(resourceRef)
246 if err != nil {
247 return nil, nn, err
248 }
249
250
251 supportsIAM, err := extension.HasIam(dclSchema)
252 if err != nil {
253 return nil, nn, err
254 }
255 if !supportsIAM {
256 return nil, nn, fmt.Errorf("invalid resource reference: kind %v does not support IAM policy member", resourceRef.Kind)
257 }
258
259 dclResource, err := d.getDCLResource(ctx, resourceRef, dclSchema, namespace)
260 if err != nil {
261 return nil, nn, fmt.Errorf("error getting referenced DCL resource, with reference %v: %w", nn, err)
262 }
263 return dclResource, nn, nil
264 }
265
266 func (d *DCLIAMClient) getDCLResource(ctx context.Context, resourceRef v1beta1.ResourceReference,
267 dclSchema *openapi.Schema, namespace string) (*dclunstruct.Resource, error) {
268 gvk := resourceRef.GroupVersionKind()
269 u := &unstructured.Unstructured{}
270 u.SetGroupVersionKind(gvk)
271 nn := types.NamespacedName{
272 Namespace: useIfNonEmptyElseDefaultTo(resourceRef.Namespace, namespace),
273 Name: resourceRef.Name,
274 }
275 if err := d.kubeClient.Get(ctx, nn, u); err != nil {
276 if errors.IsNotFound(err) {
277 return nil, k8s.NewReferenceNotFoundError(gvk, nn)
278 }
279 return nil, fmt.Errorf("error retrieving resource '%v' with GroupVersionKind '%v': %w", nn, gvk, err)
280 }
281 resource, err := dcl.NewResource(u, dclSchema)
282 if err != nil {
283 return nil, err
284 }
285 lite, err := kcclite.ToKCCLiteBestEffort(resource, d.converter.MetadataLoader, d.converter.SchemaLoader, d.smLoader, d.kubeClient)
286 if err != nil {
287 return nil, fmt.Errorf("error converting KCC full to KCC lite: %w", err)
288 }
289 dclResource, err := d.converter.KRMObjectToDCLObject(lite)
290 if err != nil {
291 return nil, err
292 }
293 return dclResource, nil
294 }
295
296 func setEmptyPolicyForDeletion(dclPolicyResource *dclunstruct.Resource) *dclunstruct.Resource {
297 dclPolicyResource.Object["bindings"] = []interface{}{}
298 dclPolicyResource.Object["etag"] = ""
299 return dclPolicyResource
300 }
301
302
303
304 func newDCLPolicyFromKRMPolicy(policy *v1beta1.IAMPolicy, dclResource *dclunstruct.Resource) (*dcliam.Policy, error) {
305 if policy.Spec.AuditConfigs != nil {
306 return nil, fmt.Errorf("policy resource contains AuditConfigs which are not currently supported by DCL-based resources")
307 }
308 return &dcliam.Policy{
309 Bindings: kccToDCLBindings(policy.Spec.Bindings),
310 Etag: &policy.Spec.Etag,
311 Resource: dclResource,
312 }, nil
313 }
314
315
316 func newIAMPolicyFromDCLResource(dclResource *dclunstruct.Resource, origPolicy *v1beta1.IAMPolicy) (*v1beta1.IAMPolicy, error) {
317 if dclResource.STV.Service != "iam" || dclResource.STV.Type != "Policy" {
318 return nil, fmt.Errorf("dclResource was not IAMPolicy instead was service %s and type %s", dclResource.STV.Service, dclResource.STV.Type)
319 }
320 iamPolicy := v1beta1.IAMPolicy{}
321 iamPolicy.ObjectMeta = origPolicy.ObjectMeta
322 iamPolicy.Spec.ResourceReference = origPolicy.Spec.ResourceReference
323 if dclResource.Object["bindings"] != nil {
324 binds, err := dclToKCCBindings(dclResource.Object["bindings"].([]interface{}))
325 if err != nil {
326 return nil, fmt.Errorf("error converting DCL Bindings to KCC Bindings: %w", err)
327 }
328 iamPolicy.Spec.Bindings = binds
329 }
330 etag := dclResource.Object["etag"].(string)
331 if etag == "" {
332 return nil, fmt.Errorf("unexpected empty etag read from the IAMPolicy resource")
333 }
334 iamPolicy.Spec.Etag = etag
335 return &iamPolicy, nil
336 }
337
338
339 func newIAMPolicyMemberFromDCLResource(dclMemberResource *dclunstruct.Resource, origPolicyMember *v1beta1.IAMPolicyMember) (*v1beta1.IAMPolicyMember, error) {
340 if dclMemberResource.STV.Service != "iam" || dclMemberResource.STV.Type != "PolicyMember" {
341 return nil, fmt.Errorf("dclResource was not IAM Member instead was service %s and type %s", dclMemberResource.STV.Service, dclMemberResource.STV.Type)
342 }
343 iamPolicyMember := v1beta1.IAMPolicyMember{}
344 iamPolicyMember.ObjectMeta = origPolicyMember.ObjectMeta
345 iamPolicyMember.Spec = v1beta1.IAMPolicyMemberSpec{
346 ResourceReference: origPolicyMember.Spec.ResourceReference,
347 Role: dclMemberResource.Object["role"].(string),
348 }
349 if dclMemberResource.Object["member"] != nil {
350 member := dclMemberResource.Object["member"].(string)
351 iamPolicyMember.Spec.Member = v1beta1.Member(member)
352 }
353 if origPolicyMember.Spec.Member == "" {
354 iamPolicyMember.Spec.Member = ""
355 iamPolicyMember.Spec.MemberFrom = origPolicyMember.Spec.MemberFrom
356 }
357 return &iamPolicyMember, nil
358 }
359
360
361
362 func newDCLPolicyMemberFromKRMPolicyMember(ctx context.Context, policyMember *v1beta1.IAMPolicyMember,
363 dclResource *dclunstruct.Resource, namespace string,
364 tfIAMClient *TFIAMClient) (*dcliam.Member, error) {
365 member, err := ResolveMemberIdentity(ctx, policyMember.Spec.Member, policyMember.Spec.MemberFrom, namespace, tfIAMClient)
366 if err != nil {
367 return nil, err
368 }
369
370 if policyMember.Spec.Condition != nil {
371 return nil, fmt.Errorf("the IAMPolicyMember for this resource of gvk %v does not currently support conditions", policyMember.Spec.ResourceReference.GroupVersionKind())
372 }
373 return &dcliam.Member{
374 Role: &policyMember.Spec.Role,
375 Member: &member,
376 Resource: dclResource,
377 }, nil
378 }
379
380 func kccToDCLBindings(kccBindings []v1beta1.IAMPolicyBinding) []dcliam.Binding {
381 ret := make([]dcliam.Binding, len(kccBindings))
382 for i, bind := range kccBindings {
383 role := bind.Role
384 ret[i] = dcliam.Binding{
385 Role: &role,
386 Members: convertMembersToStringArr(bind.Members),
387 Condition: kccToDCLCondition(bind.Condition),
388 }
389 }
390 return ret
391 }
392
393 func kccToDCLCondition(condition *v1beta1.IAMCondition) *dcliam.Condition {
394 if condition == nil {
395 return nil
396 }
397 return &dcliam.Condition{
398 Title: &condition.Title,
399 Description: &condition.Description,
400 Expression: &condition.Expression,
401 }
402 }
403
404 func dclToKCCBindings(dclBindings []interface{}) ([]v1beta1.IAMPolicyBinding, error) {
405 if dclBindings == nil {
406 return nil, nil
407 }
408 ret := make([]v1beta1.IAMPolicyBinding, len(dclBindings))
409 for i, bind := range dclBindings {
410 binding := bind.(map[string]interface{})
411 var cond *v1beta1.IAMCondition
412 if binding["condition"] != nil {
413 cond = &v1beta1.IAMCondition{}
414 if err := util.Marshal(binding["condition"], cond); err != nil {
415 return nil, err
416 }
417 }
418 ret[i] = v1beta1.IAMPolicyBinding{
419 Members: convertMapToMemberArr(binding),
420 Role: binding["role"].(string),
421 Condition: cond,
422 }
423 }
424 return ret, nil
425 }
426
427 func convertMapToMemberArr(binding map[string]interface{}) []v1beta1.Member {
428 if binding["members"] == nil {
429 return nil
430 }
431 members := binding["members"].([]interface{})
432 var ret []v1beta1.Member
433 for _, val := range members {
434 memberStr := val.(string)
435 ret = append(ret, v1beta1.Member(memberStr))
436 }
437 return ret
438 }
439
440 func convertMembersToStringArr(members []v1beta1.Member) []string {
441 var ret []string
442 for _, val := range members {
443 ret = append(ret, string(val))
444 }
445 return ret
446 }
447
View as plain text