Skip to content

Commit f35b70b

Browse files
authored
fix(dynamodb): add missing iam permissions to custom resource for deleting dynamodb replica table (#24682)
> Adding missing iam permissions to custom resource for deleting replicated regions. Since DeleteTableReplica is not enough on its own, DeleteTable permission has also been added. > > > is not authorized to perform: dynamodb:DeleteTableReplica on resource: arn:aws:dynamodb:us-east-2::table/global-replica-region-table because no identity-based policy allows the dynamodb:DeleteTableReplica action > is not authorized to perform: dynamodb:DeleteTable on resource: arn:aws:dynamodb:us-east-2::table/global-replica-region-table because no identity-based policy allows the dynamodb:DeleteTable action Closes #22069. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 45bc57a commit f35b70b

File tree

33 files changed

+1131
-653
lines changed

33 files changed

+1131
-653
lines changed

packages/@aws-cdk/aws-dynamodb/lib/replica-provider.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@ import { Construct } from 'constructs';
99
* Properties for a ReplicaProvider
1010
*/
1111
export interface ReplicaProviderProps {
12+
/**
13+
* The table name
14+
*
15+
*/
16+
readonly tableName: string;
17+
/**
18+
* Regions where replica tables will be created
19+
*
20+
*/
21+
readonly regions: string[];
1222
/**
1323
* The timeout for the replication operation.
1424
*
@@ -21,7 +31,7 @@ export class ReplicaProvider extends NestedStack {
2131
/**
2232
* Creates a stack-singleton resource provider nested stack.
2333
*/
24-
public static getOrCreate(scope: Construct, props: ReplicaProviderProps = {}) {
34+
public static getOrCreate(scope: Construct, props: ReplicaProviderProps) {
2535
const stack = Stack.of(scope);
2636
const uid = '@aws-cdk/aws-dynamodb.ReplicaProvider';
2737
return stack.node.tryFindChild(uid) as ReplicaProvider ?? new ReplicaProvider(stack, uid, props);
@@ -42,7 +52,7 @@ export class ReplicaProvider extends NestedStack {
4252
*/
4353
public readonly isCompleteHandler: lambda.Function;
4454

45-
private constructor(scope: Construct, id: string, props: ReplicaProviderProps = {}) {
55+
private constructor(scope: Construct, id: string, props: ReplicaProviderProps) {
4656
super(scope, id);
4757

4858
const code = lambda.Code.fromAsset(path.join(__dirname, 'replica-handler'));
@@ -84,6 +94,19 @@ export class ReplicaProvider extends NestedStack {
8494
}),
8595
);
8696

97+
// Required for replica table deletion
98+
let resources: string[] = [];
99+
props.regions.forEach((region) => {
100+
resources.push(`arn:aws:dynamodb:${region}:${this.account}:table/${props.tableName}`);
101+
});
102+
103+
this.onEventHandler.addToRolePolicy(
104+
new iam.PolicyStatement({
105+
actions: ['dynamodb:DeleteTable', 'dynamodb:DeleteTableReplica'],
106+
resources: resources,
107+
}),
108+
);
109+
87110
this.provider = new cr.Provider(this, 'Provider', {
88111
onEventHandler: this.onEventHandler,
89112
isCompleteHandler: this.isCompleteHandler,

packages/@aws-cdk/aws-dynamodb/lib/table.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1624,7 +1624,7 @@ export class Table extends TableBase {
16241624
throw new Error('`replicationRegions` cannot include the region where this stack is deployed.');
16251625
}
16261626

1627-
const provider = ReplicaProvider.getOrCreate(this, { timeout });
1627+
const provider = ReplicaProvider.getOrCreate(this, { tableName: this.tableName, regions, timeout });
16281628

16291629
// Documentation at https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/V2gt_IAM.html
16301630
// is currently incorrect. AWS Support recommends `dynamodb:*` in both source and destination regions

packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
Operation,
2323
CfnTable,
2424
} from '../lib';
25+
import { ReplicaProvider } from '../lib/replica-provider';
2526

2627
jest.mock('@aws-cdk/custom-resources');
2728

@@ -567,6 +568,66 @@ test('if an encryption key is included, encrypt/decrypt permissions are added to
567568
});
568569
});
569570

571+
test('replica-handler permission check', () => {
572+
// GIVEN
573+
const app = new App();
574+
const stack = new Stack(app, 'Stack');
575+
576+
// WHEN
577+
const provider = ReplicaProvider.getOrCreate(stack, {
578+
tableName: 'test',
579+
regions: ['eu-central-1', 'eu-west-1'],
580+
});
581+
582+
// THEN
583+
Template.fromStack(provider).hasResourceProperties('AWS::IAM::Policy', {
584+
'PolicyDocument': {
585+
'Statement': [
586+
{
587+
'Action': 'iam:CreateServiceLinkedRole',
588+
'Effect': 'Allow',
589+
'Resource': {
590+
'Fn::Join': [
591+
'',
592+
[
593+
'arn:',
594+
{
595+
Ref: 'AWS::Partition',
596+
},
597+
':iam::',
598+
{
599+
Ref: 'AWS::AccountId',
600+
},
601+
':role/aws-service-role/replication.dynamodb.amazonaws.com/AWSServiceRoleForDynamoDBReplication',
602+
],
603+
],
604+
},
605+
},
606+
{
607+
'Action': 'dynamodb:DescribeLimits',
608+
'Effect': 'Allow',
609+
'Resource': '*',
610+
},
611+
{
612+
'Action': [
613+
'dynamodb:DeleteTable',
614+
'dynamodb:DeleteTableReplica',
615+
],
616+
'Effect': 'Allow',
617+
'Resource': [
618+
{
619+
'Fn::Join': ['', ['arn:aws:dynamodb:eu-central-1:', { Ref: 'AWS::AccountId' }, ':table/test']],
620+
},
621+
{
622+
'Fn::Join': ['', ['arn:aws:dynamodb:eu-west-1:', { Ref: 'AWS::AccountId' }, ':table/test']],
623+
},
624+
],
625+
},
626+
],
627+
},
628+
});
629+
});
630+
570631
test('when specifying STANDARD_INFREQUENT_ACCESS table class', () => {
571632
const stack = new Stack();
572633
new Table(stack, CONSTRUCT_NAME, {

packages/@aws-cdk/aws-dynamodb/test/integ.global-replicas-provisioned.js.snapshot/asset.7215c88dd3e638d28329d4538b36cdbfb54233a4d972181795814f8b904d1037/cfn-response.js

Lines changed: 0 additions & 87 deletions
This file was deleted.

0 commit comments

Comments
 (0)