Skip to content

Commit 2ad6169

Browse files
authored
support Group.inlinePolicies (#66)
Adds support for inline policies for Group resources. The new Group.Spec.inlinePolicies field is a map[string]*string, keyed by the inline policy name and valued with the inline policy document (serialized JSON string). Inline policies are actually added and removed from a Group using different IAM API calls than their managed policy counterparts. Whereas managed policies (contained in the Group.Spec.Policies field) are attached and detached from the Group using the AttachPolicyGroup and DetachGroupPolicy API calls, the inline policies are attached and detached from the Group using the AddGroupPolicy and DeleteGroupPolicy API calls. Yes, this is confusing and why I hadn't actually included inline policies from the very beginning (I did not realize these were separate things and separate API calls). Similar to managed policies, all inline policies are removed from the Group prior to group deletion. Issue aws-controllers-k8s/community#1644 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 6aba0d1 commit 2ad6169

16 files changed

+356
-48
lines changed
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
ack_generate_info:
2-
build_date: "2023-03-01T17:11:46Z"
2+
build_date: "2023-03-02T15:55:10Z"
33
build_hash: d0f3d78cbea8061f822cbceac3786128f091efe6
44
go_version: go1.19.4
55
version: v0.24.2
6-
api_directory_checksum: a80aaa82b401436fd8d17f6b1fe931e484c62fb8
6+
api_directory_checksum: 26341f700d12dfcd4033cf4203492fa381daa7b0
77
api_version: v1alpha1
88
aws_sdk_go_version: v1.44.93
99
generator_config_info:
10-
file_checksum: a20dca352b45b74c1ea3bd4350ce3ab9d2d5c23e
10+
file_checksum: 0a343bca2be29176e9388a2cc3d54461162c5984
1111
original_file_name: generator.yaml
1212
last_modification:
1313
reason: API generation

apis/v1alpha1/generator.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ resources:
6262
references:
6363
resource: Policy
6464
path: Status.ACKResourceMetadata.ARN
65+
# These are policy documents that are added to the Group using the
66+
# Put/DeleteGroupPolicy APIs, as compared to the Attach/DetachGroupPolicy
67+
# APIs that are for non-inline managed policies.
68+
#
69+
# The map key is the PolicyDocumentName and the map value is the JSON
70+
# policy document.
71+
InlinePolicies:
72+
type: map[string]*string
6573
tags:
6674
ignore: true
6775
Policy:

apis/v1alpha1/group.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apis/v1alpha1/zz_generated.deepcopy.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/iam.services.k8s.aws_groups.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ spec:
3838
a response element in the following operations: \n * CreateGroup \n
3939
* GetGroup \n * ListGroups"
4040
properties:
41+
inlinePolicies:
42+
additionalProperties:
43+
type: string
44+
type: object
4145
name:
4246
description: "The name of the group to create. Do not include the
4347
path in this value. \n IAM user, group, role, and policy names must

generator.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ resources:
6262
references:
6363
resource: Policy
6464
path: Status.ACKResourceMetadata.ARN
65+
# These are policy documents that are added to the Group using the
66+
# Put/DeleteGroupPolicy APIs, as compared to the Attach/DetachGroupPolicy
67+
# APIs that are for non-inline managed policies.
68+
#
69+
# The map key is the PolicyDocumentName and the map value is the JSON
70+
# policy document.
71+
InlinePolicies:
72+
type: map[string]*string
6573
tags:
6674
ignore: true
6775
Policy:

helm/crds/iam.services.k8s.aws_groups.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ spec:
3838
a response element in the following operations: \n - CreateGroup \n
3939
- GetGroup \n - ListGroups"
4040
properties:
41+
inlinePolicies:
42+
additionalProperties:
43+
type: string
44+
type: object
4145
name:
4246
description: "The name of the group to create. Do not include the
4347
path in this value. \n IAM user, group, role, and policy names must

pkg/resource/group/delta.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/resource/group/hooks.go

Lines changed: 178 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,26 @@ package group
1515

1616
import (
1717
"context"
18+
"net/url"
1819

1920
ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log"
2021
ackutil "github.com/aws-controllers-k8s/runtime/pkg/util"
2122
svcsdk "github.com/aws/aws-sdk-go/service/iam"
23+
"github.com/samber/lo"
2224
)
2325

24-
// syncPolicies examines the PolicyARNs in the supplied Group and calls the
25-
// ListGroupPolicies, AttachGroupPolicy and DetachGroupPolicy APIs to ensure
26-
// that the set of attached policies stays in sync with the Group.Spec.Policies
27-
// field, which is a list of strings containing Policy ARNs.
28-
func (rm *resourceManager) syncPolicies(
26+
// syncManagedPolicies examines the managed PolicyARNs in the supplied Group
27+
// and calls the ListAttachedGroupPolicies, AttachGroupPolicy and
28+
// DetachGroupPolicy APIs to ensure that the set of attached policies stays in
29+
// sync with the Group.Spec.Policies field, which is a list of strings
30+
// containing Policy ARNs.
31+
func (rm *resourceManager) syncManagedPolicies(
2932
ctx context.Context,
3033
desired *resource,
3134
latest *resource,
3235
) (err error) {
3336
rlog := ackrtlog.FromContext(ctx)
34-
exit := rlog.Trace("rm.syncPolicies")
37+
exit := rlog.Trace("rm.syncManagedPolicies")
3538
defer func() { exit(err) }()
3639
toAdd := []*string{}
3740
toDelete := []*string{}
@@ -51,29 +54,30 @@ func (rm *resourceManager) syncPolicies(
5154
}
5255

5356
for _, p := range toAdd {
54-
rlog.Debug("adding policy to group", "policy_arn", *p)
55-
if err = rm.addPolicy(ctx, desired, p); err != nil {
57+
rlog.Debug("adding managed policy to group", "policy_arn", *p)
58+
if err = rm.addManagedPolicy(ctx, desired, p); err != nil {
5659
return err
5760
}
5861
}
5962
for _, p := range toDelete {
60-
rlog.Debug("removing policy from group", "policy_arn", *p)
61-
if err = rm.removePolicy(ctx, desired, p); err != nil {
63+
rlog.Debug("removing managed policy from group", "policy_arn", *p)
64+
if err = rm.removeManagedPolicy(ctx, desired, p); err != nil {
6265
return err
6366
}
6467
}
6568

6669
return nil
6770
}
6871

69-
// getPolicies returns the list of Policy ARNs currently attached to the Group
70-
func (rm *resourceManager) getPolicies(
72+
// getManagedPolicies returns the list of managed Policy ARNs currently
73+
// attached to the Group
74+
func (rm *resourceManager) getManagedPolicies(
7175
ctx context.Context,
7276
r *resource,
7377
) ([]*string, error) {
7478
var err error
7579
rlog := ackrtlog.FromContext(ctx)
76-
exit := rlog.Trace("rm.getPolicies")
80+
exit := rlog.Trace("rm.getManagedPolicies")
7781
defer func() { exit(err) }()
7882

7983
input := &svcsdk.ListAttachedGroupPoliciesInput{}
@@ -95,14 +99,15 @@ func (rm *resourceManager) getPolicies(
9599
return res, err
96100
}
97101

98-
// addPolicy adds the supplied Policy to the supplied Group resource
99-
func (rm *resourceManager) addPolicy(
102+
// addManagedPolicy adds the supplied managed Policy to the supplied Group
103+
// resource
104+
func (rm *resourceManager) addManagedPolicy(
100105
ctx context.Context,
101106
r *resource,
102107
policyARN *string,
103108
) (err error) {
104109
rlog := ackrtlog.FromContext(ctx)
105-
exit := rlog.Trace("rm.addPolicy")
110+
exit := rlog.Trace("rm.addManagedPolicy")
106111
defer func() { exit(err) }()
107112

108113
input := &svcsdk.AttachGroupPolicyInput{}
@@ -113,14 +118,15 @@ func (rm *resourceManager) addPolicy(
113118
return err
114119
}
115120

116-
// removePolicy removes the supplied Policy from the supplied Group resource
117-
func (rm *resourceManager) removePolicy(
121+
// removeManagedPolicy removes the supplied managed Policy from the supplied
122+
// Group resource
123+
func (rm *resourceManager) removeManagedPolicy(
118124
ctx context.Context,
119125
r *resource,
120126
policyARN *string,
121127
) (err error) {
122128
rlog := ackrtlog.FromContext(ctx)
123-
exit := rlog.Trace("rm.removePolicy")
129+
exit := rlog.Trace("rm.removeManagedPolicy")
124130
defer func() { exit(err) }()
125131

126132
input := &svcsdk.DetachGroupPolicyInput{}
@@ -130,3 +136,156 @@ func (rm *resourceManager) removePolicy(
130136
rm.metrics.RecordAPICall("UPDATE", "DetachGroupPolicy", err)
131137
return err
132138
}
139+
140+
// syncInlinePolicies examines the InlinePolicies in the supplied Group and
141+
// calls the ListGroupPolicies, PutGroupPolicy and DeleteGroupPolicy APIs to
142+
// ensure that the set of attached policies stays in sync with the
143+
// Group.Spec.InlinePolicies field, which is a map of policy names to policy
144+
// documents.
145+
func (rm *resourceManager) syncInlinePolicies(
146+
ctx context.Context,
147+
desired *resource,
148+
latest *resource,
149+
) (err error) {
150+
rlog := ackrtlog.FromContext(ctx)
151+
exit := rlog.Trace("rm.syncInlinePolicies")
152+
defer func() { exit(err) }()
153+
154+
existingPolicies := latest.ko.Spec.InlinePolicies
155+
156+
existingPairs := lo.ToPairs(existingPolicies)
157+
desiredPairs := lo.ToPairs(desired.ko.Spec.InlinePolicies)
158+
159+
toDelete, toAdd := lo.Difference(existingPairs, desiredPairs)
160+
161+
for _, pair := range toAdd {
162+
polName := pair.Key
163+
polDoc := pair.Value
164+
rlog.Debug(
165+
"adding inline policy to group",
166+
"policy_name", polName,
167+
)
168+
err = rm.addInlinePolicy(ctx, desired, polName, polDoc)
169+
if err != nil {
170+
return err
171+
}
172+
}
173+
for _, pair := range toDelete {
174+
polName := pair.Key
175+
rlog.Debug(
176+
"removing inline policy from group",
177+
"policy_name", polName,
178+
)
179+
if err = rm.removeInlinePolicy(ctx, desired, polName); err != nil {
180+
return err
181+
}
182+
}
183+
184+
return nil
185+
}
186+
187+
// getInlinePolicies returns a map of inline policy name and policy docs
188+
// currently attached to the Group.
189+
//
190+
// NOTE(jaypipes): There's no way around the inefficiencies of this method
191+
// without caching stuff, and I don't think it's useful to have an unbounded
192+
// cache for these inline policy documents :( IAM's ListGroupPolicies API call
193+
// only returns the *policy names* of inline policies. You need to call
194+
// GetGroupPolicy API call for each inline policy name in order to get the
195+
// policy document. Yes, they force an O(N) time complexity for this
196+
// operation...
197+
func (rm *resourceManager) getInlinePolicies(
198+
ctx context.Context,
199+
r *resource,
200+
) (map[string]*string, error) {
201+
var err error
202+
rlog := ackrtlog.FromContext(ctx)
203+
exit := rlog.Trace("rm.getInlinePolicies")
204+
defer func() { exit(err) }()
205+
206+
groupName := r.ko.Spec.Name
207+
208+
input := &svcsdk.ListGroupPoliciesInput{}
209+
input.GroupName = groupName
210+
res := map[string]*string{}
211+
212+
err = rm.sdkapi.ListGroupPoliciesPagesWithContext(
213+
ctx, input, func(page *svcsdk.ListGroupPoliciesOutput, _ bool) bool {
214+
if page == nil {
215+
return true
216+
}
217+
for _, p := range page.PolicyNames {
218+
res[*p] = nil
219+
}
220+
return page.IsTruncated != nil && *page.IsTruncated
221+
},
222+
)
223+
rm.metrics.RecordAPICall("READ_MANY", "ListGroupPolicies", err)
224+
225+
// Now we need to grab the policy documents for each policy name
226+
for polName, _ := range res {
227+
input := &svcsdk.GetGroupPolicyInput{}
228+
input.GroupName = groupName
229+
input.PolicyName = &polName
230+
resp, err := rm.sdkapi.GetGroupPolicyWithContext(ctx, input)
231+
rm.metrics.RecordAPICall("READ_ONE", "GetGroupPolicy", err)
232+
if err != nil {
233+
return nil, err
234+
}
235+
cleanedDoc, err := decodeDocument(*resp.PolicyDocument)
236+
if err != nil {
237+
return nil, err
238+
}
239+
res[polName] = &cleanedDoc
240+
241+
}
242+
return res, nil
243+
}
244+
245+
// addInlinePolicy adds the supplied inline Policy to the supplied Group
246+
// resource
247+
func (rm *resourceManager) addInlinePolicy(
248+
ctx context.Context,
249+
r *resource,
250+
policyName string,
251+
policyDoc *string,
252+
) (err error) {
253+
rlog := ackrtlog.FromContext(ctx)
254+
exit := rlog.Trace("rm.addInlinePolicy")
255+
defer func() { exit(err) }()
256+
257+
input := &svcsdk.PutGroupPolicyInput{}
258+
input.GroupName = r.ko.Spec.Name
259+
input.PolicyName = &policyName
260+
cleanedDoc, err := decodeDocument(*policyDoc)
261+
if err != nil {
262+
return err
263+
}
264+
input.PolicyDocument = &cleanedDoc
265+
_, err = rm.sdkapi.PutGroupPolicyWithContext(ctx, input)
266+
rm.metrics.RecordAPICall("UPDATE", "PutGroupPolicy", err)
267+
return err
268+
}
269+
270+
// removeInlinePolicy removes the supplied inline Policy from the supplied
271+
// Group resource
272+
func (rm *resourceManager) removeInlinePolicy(
273+
ctx context.Context,
274+
r *resource,
275+
policyName string,
276+
) (err error) {
277+
rlog := ackrtlog.FromContext(ctx)
278+
exit := rlog.Trace("rm.removeInlinePolicy")
279+
defer func() { exit(err) }()
280+
281+
input := &svcsdk.DeleteGroupPolicyInput{}
282+
input.GroupName = r.ko.Spec.Name
283+
input.PolicyName = &policyName
284+
_, err = rm.sdkapi.DeleteGroupPolicyWithContext(ctx, input)
285+
rm.metrics.RecordAPICall("UPDATE", "DeleteGroupPolicy", err)
286+
return err
287+
}
288+
289+
func decodeDocument(encoded string) (string, error) {
290+
return url.QueryUnescape(encoded)
291+
}

0 commit comments

Comments
 (0)