Skip to content

Commit 2266683

Browse files
author
skagalwala
committed
[feat]: allow adding resource policy to dynamodb tables
1 parent 156aeb3 commit 2266683

File tree

11 files changed

+295
-39
lines changed

11 files changed

+295
-39
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: "2025-09-19T17:14:03Z"
2+
build_date: "2025-10-17T18:47:29Z"
33
build_hash: 6b4211163dcc34776b01da9a18217bac0f4103fd
44
go_version: go1.24.6
55
version: v0.52.0
6-
api_directory_checksum: bcdceff2d7ddf7c98141572260ef2e6cee8bf23f
6+
api_directory_checksum: d2887bf57c4e94a2687e17c41f74c875131c0beb
77
api_version: v1alpha1
88
aws_sdk_go_version: v1.32.6
99
generator_config_info:
10-
file_checksum: cc3489b53a45170d339a4de0d7d7ec0aa788955e
10+
file_checksum: 3c4832feff83bc9c29b40bc73bafc1d7e75ab1cd
1111
original_file_name: generator.yaml
1212
last_modification:
1313
reason: API generation

apis/v1alpha1/generator.yaml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ ignore:
1111
# Replica of Spec.SSESpecification
1212
- TableDescription.SSEDescription
1313
- TableDescription.TableClassSummary
14-
- CreateTableInput.ResourcePolicy
1514
- CreateTableInput.WarmThroughput
1615
operations:
1716
UpdateGlobalTable:
@@ -29,7 +28,13 @@ resources:
2928
custom_field:
3029
list_of: CreateReplicationGroupMemberAction
3130
compare:
32-
is_ignored: true
31+
is_ignored: true
32+
ResourcePolicy:
33+
from:
34+
operation: PutResourcePolicy
35+
path: Policy
36+
compare:
37+
is_ignored: true
3338
GlobalSecondaryIndexesDescriptions:
3439
custom_field:
3540
list_of: GlobalSecondaryIndexDescription
@@ -74,7 +79,7 @@ resources:
7479
from:
7580
operation: UpdateContributorInsights
7681
path: ContributorInsightsAction
77-
compare:
82+
compare:
7883
is_ignored: true
7984
exceptions:
8085
errors:

apis/v1alpha1/table.go

Lines changed: 13 additions & 0 deletions
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: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/dynamodb.services.k8s.aws_tables.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,21 @@ spec:
325325
format: int64
326326
type: integer
327327
type: object
328+
resourcePolicy:
329+
description: |-
330+
An Amazon Web Services resource-based policy document in JSON format.
331+
332+
* The maximum size supported for a resource-based policy document is 20
333+
KB. DynamoDB counts whitespaces when calculating the size of a policy
334+
against this limit.
335+
336+
* Within a resource-based policy, if the action for a DynamoDB service-linked
337+
role (SLR) to replicate data for a global table is denied, adding or deleting
338+
a replica will fail with an error.
339+
340+
For a full list of all considerations that apply while attaching a resource-based
341+
policy, see Resource-based policy considerations (https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/rbac-considerations.html).
342+
type: string
328343
sseSpecification:
329344
description: Represents the settings used to enable server-side encryption.
330345
properties:

generator.yaml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ ignore:
1111
# Replica of Spec.SSESpecification
1212
- TableDescription.SSEDescription
1313
- TableDescription.TableClassSummary
14-
- CreateTableInput.ResourcePolicy
1514
- CreateTableInput.WarmThroughput
1615
operations:
1716
UpdateGlobalTable:
@@ -29,7 +28,13 @@ resources:
2928
custom_field:
3029
list_of: CreateReplicationGroupMemberAction
3130
compare:
32-
is_ignored: true
31+
is_ignored: true
32+
ResourcePolicy:
33+
from:
34+
operation: PutResourcePolicy
35+
path: Policy
36+
compare:
37+
is_ignored: true
3338
GlobalSecondaryIndexesDescriptions:
3439
custom_field:
3540
list_of: GlobalSecondaryIndexDescription
@@ -74,7 +79,7 @@ resources:
7479
from:
7580
operation: UpdateContributorInsights
7681
path: ContributorInsightsAction
77-
compare:
82+
compare:
7883
is_ignored: true
7984
exceptions:
8085
errors:

helm/crds/dynamodb.services.k8s.aws_tables.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,21 @@ spec:
329329
format: int64
330330
type: integer
331331
type: object
332+
resourcePolicy:
333+
description: |-
334+
An Amazon Web Services resource-based policy document in JSON format.
335+
336+
- The maximum size supported for a resource-based policy document is 20
337+
KB. DynamoDB counts whitespaces when calculating the size of a policy
338+
against this limit.
339+
340+
- Within a resource-based policy, if the action for a DynamoDB service-linked
341+
role (SLR) to replicate data for a global table is denied, adding or deleting
342+
a replica will fail with an error.
343+
344+
For a full list of all considerations that apply while attaching a resource-based
345+
policy, see Resource-based policy considerations (https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/rbac-considerations.html).
346+
type: string
332347
sseSpecification:
333348
description: Represents the settings used to enable server-side encryption.
334349
properties:

pkg/resource/table/hooks.go

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -166,17 +166,6 @@ func (rm *resourceManager) customUpdateTable(
166166
setSyncedCondition(desired, corev1.ConditionFalse, &msg, nil)
167167
return desired, requeueWaitWhileCreating
168168
}
169-
if isTableUpdating(latest) {
170-
msg := "table is currently being updated"
171-
setSyncedCondition(desired, corev1.ConditionFalse, &msg, nil)
172-
return desired, requeueWaitWhileUpdating
173-
}
174-
if tableHasTerminalStatus(latest) {
175-
msg := "table is in '" + *latest.ko.Status.TableStatus + "' status"
176-
setTerminalCondition(desired, corev1.ConditionTrue, &msg, nil)
177-
setSyncedCondition(desired, corev1.ConditionTrue, nil, nil)
178-
return desired, nil
179-
}
180169

181170
// Merge in the information we read from the API call above to the copy of
182171
// the original Kubernetes object we passed to the function
@@ -188,10 +177,36 @@ func (rm *resourceManager) customUpdateTable(
188177
return nil, err
189178
}
190179
}
191-
if !delta.DifferentExcept("Spec.Tags") {
180+
181+
// ResourcePolicy can be updated independently of table state
182+
if delta.DifferentAt("Spec.ResourcePolicy") {
183+
if latest.ko.Status.ACKResourceMetadata == nil || latest.ko.Status.ACKResourceMetadata.ARN == nil {
184+
rlog.Debug("skipping ResourcePolicy sync - table ARN not available yet")
185+
return &resource{ko}, requeueWaitWhileCreating
186+
}
187+
188+
err = rm.syncResourcePolicy(ctx, desired, latest)
189+
if err != nil {
190+
return nil, fmt.Errorf("cannot update table resource policy %v", err)
191+
}
192+
}
193+
194+
if !delta.DifferentExcept("Spec.Tags", "Spec.ResourcePolicy") {
192195
return &resource{ko}, nil
193196
}
194197

198+
if isTableUpdating(latest) {
199+
msg := "table is currently being updated"
200+
setSyncedCondition(desired, corev1.ConditionFalse, &msg, nil)
201+
return desired, requeueWaitWhileUpdating
202+
}
203+
if tableHasTerminalStatus(latest) {
204+
msg := "table is in '" + *latest.ko.Status.TableStatus + "' status"
205+
setTerminalCondition(desired, corev1.ConditionTrue, &msg, nil)
206+
setSyncedCondition(desired, corev1.ConditionTrue, nil, nil)
207+
return desired, nil
208+
}
209+
195210
if delta.DifferentAt("Spec.TimeToLive") {
196211
if err := rm.syncTTL(ctx, desired, latest); err != nil {
197212
// Ignore "already disabled errors"
@@ -522,6 +537,15 @@ func (rm *resourceManager) setResourceAdditionalFields(
522537
} else {
523538
ko.Spec.ContinuousBackups = pitrSpec
524539
}
540+
541+
if ko.Status.ACKResourceMetadata != nil && ko.Status.ACKResourceMetadata.ARN != nil {
542+
policy, err := rm.getResourcePolicyWithContext(ctx, (*string)(ko.Status.ACKResourceMetadata.ARN))
543+
if err != nil {
544+
return err
545+
}
546+
ko.Spec.ResourcePolicy = policy
547+
}
548+
525549
return nil
526550
}
527551

@@ -671,6 +695,14 @@ func customPreCompare(
671695
}
672696
}
673697

698+
if ackcompare.HasNilDifference(a.ko.Spec.ResourcePolicy, b.ko.Spec.ResourcePolicy) {
699+
delta.Add("Spec.ResourcePolicy", a.ko.Spec.ResourcePolicy, b.ko.Spec.ResourcePolicy)
700+
} else if a.ko.Spec.ResourcePolicy != nil && b.ko.Spec.ResourcePolicy != nil {
701+
if *a.ko.Spec.ResourcePolicy != *b.ko.Spec.ResourcePolicy {
702+
delta.Add("Spec.ResourcePolicy", a.ko.Spec.ResourcePolicy, b.ko.Spec.ResourcePolicy)
703+
}
704+
}
705+
674706
}
675707

676708
// equalAttributeDefinitions return whether two AttributeDefinition arrays are equal or not.
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package table
15+
16+
import (
17+
"context"
18+
"errors"
19+
20+
ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors"
21+
ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log"
22+
svcsdk "github.com/aws/aws-sdk-go-v2/service/dynamodb"
23+
svcsdktypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
24+
)
25+
26+
// syncResourcePolicy updates a DynamoDB table's resource-based policy.
27+
func (rm *resourceManager) syncResourcePolicy(
28+
ctx context.Context,
29+
desired *resource,
30+
latest *resource,
31+
) (err error) {
32+
rlog := ackrtlog.FromContext(ctx)
33+
exit := rlog.Trace("rm.syncResourcePolicy")
34+
defer func(err error) { exit(err) }(err)
35+
36+
if desired.ko.Spec.ResourcePolicy == nil {
37+
return rm.deleteResourcePolicy(ctx, latest)
38+
}
39+
40+
return rm.putResourcePolicy(ctx, desired)
41+
}
42+
43+
// putResourcePolicy attaches or updates a resource-based policy to a DynamoDB table.
44+
func (rm *resourceManager) putResourcePolicy(
45+
ctx context.Context,
46+
r *resource,
47+
) (err error) {
48+
rlog := ackrtlog.FromContext(ctx)
49+
exit := rlog.Trace("rm.putResourcePolicy")
50+
defer func(err error) { exit(err) }(err)
51+
52+
if r.ko.Spec.ResourcePolicy == nil {
53+
return nil
54+
}
55+
56+
tableARN := (*string)(r.ko.Status.ACKResourceMetadata.ARN)
57+
if tableARN == nil || *tableARN == "" {
58+
return errors.New("table ARN is required to put resource policy")
59+
}
60+
61+
_, err = rm.sdkapi.PutResourcePolicy(
62+
ctx,
63+
&svcsdk.PutResourcePolicyInput{
64+
ResourceArn: tableARN,
65+
Policy: r.ko.Spec.ResourcePolicy,
66+
},
67+
)
68+
rm.metrics.RecordAPICall("UPDATE", "PutResourcePolicy", err)
69+
return err
70+
}
71+
72+
// deleteResourcePolicy removes a resource-based policy from a DynamoDB table.
73+
func (rm *resourceManager) deleteResourcePolicy(
74+
ctx context.Context,
75+
r *resource,
76+
) (err error) {
77+
rlog := ackrtlog.FromContext(ctx)
78+
exit := rlog.Trace("rm.deleteResourcePolicy")
79+
defer func(err error) { exit(err) }(err)
80+
81+
tableARN := (*string)(r.ko.Status.ACKResourceMetadata.ARN)
82+
if tableARN == nil || *tableARN == "" {
83+
return errors.New("table ARN is required to delete resource policy")
84+
}
85+
86+
_, err = rm.sdkapi.DeleteResourcePolicy(
87+
ctx,
88+
&svcsdk.DeleteResourcePolicyInput{
89+
ResourceArn: tableARN,
90+
},
91+
)
92+
93+
if err != nil {
94+
var policyNotFoundErr *svcsdktypes.PolicyNotFoundException
95+
if errors.As(err, &policyNotFoundErr) {
96+
// Policy already doesn't exist, this is a success case
97+
return nil
98+
}
99+
}
100+
101+
rm.metrics.RecordAPICall("DELETE", "DeleteResourcePolicy", err)
102+
return err
103+
}
104+
105+
// getResourcePolicyWithContext retrieves the resource-based policy of a DynamoDB table.
106+
func (rm *resourceManager) getResourcePolicyWithContext(
107+
ctx context.Context,
108+
tableARN *string,
109+
) (*string, error) {
110+
var err error
111+
rlog := ackrtlog.FromContext(ctx)
112+
exit := rlog.Trace("rm.getResourcePolicyWithContext")
113+
defer func(err error) { exit(err) }(err)
114+
115+
if tableARN == nil || *tableARN == "" {
116+
return nil, errors.New("table ARN is required to get resource policy")
117+
}
118+
119+
res, err := rm.sdkapi.GetResourcePolicy(
120+
ctx,
121+
&svcsdk.GetResourcePolicyInput{
122+
ResourceArn: tableARN,
123+
},
124+
)
125+
126+
if err != nil {
127+
if awsErr, ok := ackerr.AWSError(err); ok && awsErr.ErrorCode() == "PolicyNotFoundException" {
128+
return nil, nil
129+
}
130+
rm.metrics.RecordAPICall("GET", "GetResourcePolicy", err)
131+
return nil, err
132+
}
133+
134+
rm.metrics.RecordAPICall("GET", "GetResourcePolicy", nil)
135+
return res.Policy, nil
136+
}

0 commit comments

Comments
 (0)