Skip to content

Commit 1561a4d

Browse files
author
Elad Ben-Israel
authored
feat(aws-dynamodb): IAM grants support (#870)
Adds the following grant APIs to dynamodb.Table: - grant(principal, ...actions) - grantReadData(principal) - grantWriteData(principal) - grantReadWriteData(principal) - grantFullAccess(principal)
1 parent 6495e3c commit 1561a4d

File tree

2 files changed

+140
-6
lines changed

2 files changed

+140
-6
lines changed

Diff for: packages/@aws-cdk/aws-dynamodb/lib/table.ts

+73-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,27 @@
11
import { cloudformation as applicationautoscaling } from '@aws-cdk/aws-applicationautoscaling';
2-
import { PolicyStatement, PolicyStatementEffect, Role, ServicePrincipal } from '@aws-cdk/aws-iam';
2+
import iam = require('@aws-cdk/aws-iam');
33
import { Construct, TagManager, Tags } from '@aws-cdk/cdk';
44
import { cloudformation as dynamodb } from './dynamodb.generated';
55

66
const HASH_KEY_TYPE = 'HASH';
77
const RANGE_KEY_TYPE = 'RANGE';
88

9+
const READ_DATA_ACTIONS = [
10+
'dynamodb:BatchGetItem',
11+
'dynamodb:GetRecords',
12+
'dynamodb:GetShardIterator',
13+
'dynamodb:Query',
14+
'dynamodb:GetItem',
15+
'dynamodb:Scan'
16+
];
17+
18+
const WRITE_DATA_ACTIONS = [
19+
'dynamodb:BatchWriteItem',
20+
'dynamodb:PutItem',
21+
'dynamodb:UpdateItem',
22+
'dynamodb:DeleteItem'
23+
];
24+
925
export interface Attribute {
1026
/**
1127
* The name of an attribute.
@@ -314,6 +330,57 @@ export class Table extends Construct {
314330
this.writeScalingPolicyResource = this.buildAutoScaling(this.writeScalingPolicyResource, 'Write', props);
315331
}
316332

333+
/**
334+
* Adds an IAM policy statement associated with this table to an IAM
335+
* principal's policy.
336+
* @param principal The principal (no-op if undefined)
337+
* @param actions The set of actions to allow (i.e. "dynamodb:PutItem", "dynamodb:GetItem", ...)
338+
*/
339+
public grant(principal?: iam.IPrincipal, ...actions: string[]) {
340+
if (!principal) {
341+
return;
342+
}
343+
principal.addToPolicy(new iam.PolicyStatement()
344+
.addResource(this.tableArn)
345+
.addActions(...actions));
346+
}
347+
348+
/**
349+
* Permits an IAM principal all data read operations from this table:
350+
* BatchGetItem, GetRecords, GetShardIterator, Query, GetItem, Scan.
351+
* @param principal The principal to grant access to
352+
*/
353+
public grantReadData(principal?: iam.IPrincipal) {
354+
this.grant(principal, ...READ_DATA_ACTIONS);
355+
}
356+
357+
/**
358+
* Permits an IAM principal all data write operations to this table:
359+
* BatchWriteItem, PutItem, UpdateItem, DeleteItem.
360+
* @param principal The principal to grant access to
361+
*/
362+
public grantWriteData(principal?: iam.IPrincipal) {
363+
this.grant(principal, ...WRITE_DATA_ACTIONS);
364+
}
365+
366+
/**
367+
* Permits an IAM principal to all data read/write operations to this table.
368+
* BatchGetItem, GetRecords, GetShardIterator, Query, GetItem, Scan,
369+
* BatchWriteItem, PutItem, UpdateItem, DeleteItem
370+
* @param principal The principal to grant access to
371+
*/
372+
public grantReadWriteData(principal?: iam.IPrincipal) {
373+
this.grant(principal, ...READ_DATA_ACTIONS, ...WRITE_DATA_ACTIONS);
374+
}
375+
376+
/**
377+
* Permits all DynamoDB operations ("dynamodb:*") to an IAM principal.
378+
* @param principal The principal to grant access to
379+
*/
380+
public grantFullAccess(principal?: iam.IPrincipal) {
381+
this.grant(principal, 'dynamodb:*');
382+
}
383+
317384
/**
318385
* Validate the table construct.
319386
*
@@ -443,21 +510,21 @@ export class Table extends Construct {
443510
}
444511

445512
private buildAutoScalingRole(roleResourceName: string) {
446-
const autoScalingRole = new Role(this, roleResourceName, {
447-
assumedBy: new ServicePrincipal('application-autoscaling.amazonaws.com')
513+
const autoScalingRole = new iam.Role(this, roleResourceName, {
514+
assumedBy: new iam.ServicePrincipal('application-autoscaling.amazonaws.com')
448515
});
449-
autoScalingRole.addToPolicy(new PolicyStatement(PolicyStatementEffect.Allow)
516+
autoScalingRole.addToPolicy(new iam.PolicyStatement(iam.PolicyStatementEffect.Allow)
450517
.addActions("dynamodb:DescribeTable", "dynamodb:UpdateTable")
451518
.addResource(this.tableArn));
452-
autoScalingRole.addToPolicy(new PolicyStatement(PolicyStatementEffect.Allow)
519+
autoScalingRole.addToPolicy(new iam.PolicyStatement(iam.PolicyStatementEffect.Allow)
453520
.addActions("cloudwatch:PutMetricAlarm", "cloudwatch:DescribeAlarms", "cloudwatch:GetMetricStatistics",
454521
"cloudwatch:SetAlarmState", "cloudwatch:DeleteAlarms")
455522
.addAllResources());
456523
return autoScalingRole;
457524
}
458525

459526
private buildScalableTargetResourceProps(scalableDimension: string,
460-
scalingRole: Role,
527+
scalingRole: iam.Role,
461528
props: AutoScalingProps) {
462529
return {
463530
maxCapacity: props.maxCapacity,

Diff for: packages/@aws-cdk/aws-dynamodb/test/test.dynamodb.ts

+67
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { expect, haveResource } from '@aws-cdk/assert';
2+
import iam = require('@aws-cdk/aws-iam');
13
import { App, Stack } from '@aws-cdk/cdk';
24
import { Test } from 'nodeunit';
35
import {
@@ -10,6 +12,8 @@ import {
1012
Table
1113
} from '../lib';
1214

15+
// tslint:disable:object-literal-key-quotes
16+
1317
// CDK parameters
1418
const STACK_NAME = 'MyStack';
1519
const CONSTRUCT_NAME = 'MyTable';
@@ -2024,6 +2028,34 @@ export = {
20242028
}), /minimumCapacity must be greater than or equal to 0; Provided value is: -5/);
20252029

20262030
test.done();
2031+
},
2032+
2033+
'grants': {
2034+
2035+
'"grant" allows adding arbitrary actions associated with this table resource'(test: Test) {
2036+
testGrant(test,
2037+
[ 'action1', 'action2' ], (p, t) => t.grant(p, 'dynamodb:action1', 'dynamodb:action2'));
2038+
},
2039+
2040+
'"grantReadData" allows the principal to read data from the table'(test: Test) {
2041+
testGrant(test,
2042+
[ 'BatchGetItem', 'GetRecords', 'GetShardIterator', 'Query', 'GetItem', 'Scan' ], (p, t) => t.grantReadData(p));
2043+
},
2044+
2045+
'"grantWriteData" allows the principal to write data to the table'(test: Test) {
2046+
testGrant(test, [
2047+
'BatchWriteItem', 'PutItem', 'UpdateItem', 'DeleteItem' ], (p, t) => t.grantWriteData(p));
2048+
},
2049+
2050+
'"grantReadWriteData" allows the principal to read/write data'(test: Test) {
2051+
testGrant(test, [
2052+
'BatchGetItem', 'GetRecords', 'GetShardIterator', 'Query', 'GetItem', 'Scan',
2053+
'BatchWriteItem', 'PutItem', 'UpdateItem', 'DeleteItem' ], (p, t) => t.grantReadWriteData(p));
2054+
},
2055+
2056+
'"grantFullAccess" allows the principal to perform any action on the table ("*")'(test: Test) {
2057+
testGrant(test, [ '*' ], (p, t) => t.grantFullAccess(p));
2058+
}
20272059
}
20282060
};
20292061

@@ -2036,3 +2068,38 @@ class TestApp {
20362068
return this.app.synthesizeStack(this.stack.name).template;
20372069
}
20382070
}
2071+
2072+
function testGrant(test: Test, expectedActions: string[], invocation: (user: iam.IPrincipal, table: Table) => void) {
2073+
// GIVEN
2074+
const stack = new Stack();
2075+
2076+
const table = new Table(stack, 'my-table');
2077+
table.addPartitionKey({ name: 'ID', type: AttributeType.String });
2078+
2079+
const user = new iam.User(stack, 'user');
2080+
2081+
// WHEN
2082+
invocation(user, table);
2083+
2084+
// THEN
2085+
const action = expectedActions.length > 1 ? expectedActions.map(a => `dynamodb:${a}`) : `dynamodb:${expectedActions[0]}`;
2086+
expect(stack).to(haveResource('AWS::IAM::Policy', {
2087+
"PolicyDocument": {
2088+
"Statement": [
2089+
{
2090+
"Action": action,
2091+
"Effect": "Allow",
2092+
"Resource": {
2093+
"Fn::GetAtt": [
2094+
"mytable0324D45C",
2095+
"Arn"
2096+
]
2097+
}
2098+
}
2099+
],
2100+
"Version": "2012-10-17"
2101+
},
2102+
"Users": [ { "Ref": "user2C2B57AE" } ]
2103+
}));
2104+
test.done();
2105+
}

0 commit comments

Comments
 (0)