Skip to content

Commit

Permalink
feat(aws-dynamodb): IAM grants support (#870)
Browse files Browse the repository at this point in the history
Adds the following grant APIs to dynamodb.Table:

- grant(principal, ...actions)
- grantReadData(principal)
- grantWriteData(principal)
- grantReadWriteData(principal)
- grantFullAccess(principal)
  • Loading branch information
Elad Ben-Israel authored Oct 8, 2018
1 parent 6495e3c commit 1561a4d
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 6 deletions.
79 changes: 73 additions & 6 deletions packages/@aws-cdk/aws-dynamodb/lib/table.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
import { cloudformation as applicationautoscaling } from '@aws-cdk/aws-applicationautoscaling';
import { PolicyStatement, PolicyStatementEffect, Role, ServicePrincipal } from '@aws-cdk/aws-iam';
import iam = require('@aws-cdk/aws-iam');
import { Construct, TagManager, Tags } from '@aws-cdk/cdk';
import { cloudformation as dynamodb } from './dynamodb.generated';

const HASH_KEY_TYPE = 'HASH';
const RANGE_KEY_TYPE = 'RANGE';

const READ_DATA_ACTIONS = [
'dynamodb:BatchGetItem',
'dynamodb:GetRecords',
'dynamodb:GetShardIterator',
'dynamodb:Query',
'dynamodb:GetItem',
'dynamodb:Scan'
];

const WRITE_DATA_ACTIONS = [
'dynamodb:BatchWriteItem',
'dynamodb:PutItem',
'dynamodb:UpdateItem',
'dynamodb:DeleteItem'
];

export interface Attribute {
/**
* The name of an attribute.
Expand Down Expand Up @@ -314,6 +330,57 @@ export class Table extends Construct {
this.writeScalingPolicyResource = this.buildAutoScaling(this.writeScalingPolicyResource, 'Write', props);
}

/**
* Adds an IAM policy statement associated with this table to an IAM
* principal's policy.
* @param principal The principal (no-op if undefined)
* @param actions The set of actions to allow (i.e. "dynamodb:PutItem", "dynamodb:GetItem", ...)
*/
public grant(principal?: iam.IPrincipal, ...actions: string[]) {
if (!principal) {
return;
}
principal.addToPolicy(new iam.PolicyStatement()
.addResource(this.tableArn)
.addActions(...actions));
}

/**
* Permits an IAM principal all data read operations from this table:
* BatchGetItem, GetRecords, GetShardIterator, Query, GetItem, Scan.
* @param principal The principal to grant access to
*/
public grantReadData(principal?: iam.IPrincipal) {
this.grant(principal, ...READ_DATA_ACTIONS);
}

/**
* Permits an IAM principal all data write operations to this table:
* BatchWriteItem, PutItem, UpdateItem, DeleteItem.
* @param principal The principal to grant access to
*/
public grantWriteData(principal?: iam.IPrincipal) {
this.grant(principal, ...WRITE_DATA_ACTIONS);
}

/**
* Permits an IAM principal to all data read/write operations to this table.
* BatchGetItem, GetRecords, GetShardIterator, Query, GetItem, Scan,
* BatchWriteItem, PutItem, UpdateItem, DeleteItem
* @param principal The principal to grant access to
*/
public grantReadWriteData(principal?: iam.IPrincipal) {
this.grant(principal, ...READ_DATA_ACTIONS, ...WRITE_DATA_ACTIONS);
}

/**
* Permits all DynamoDB operations ("dynamodb:*") to an IAM principal.
* @param principal The principal to grant access to
*/
public grantFullAccess(principal?: iam.IPrincipal) {
this.grant(principal, 'dynamodb:*');
}

/**
* Validate the table construct.
*
Expand Down Expand Up @@ -443,21 +510,21 @@ export class Table extends Construct {
}

private buildAutoScalingRole(roleResourceName: string) {
const autoScalingRole = new Role(this, roleResourceName, {
assumedBy: new ServicePrincipal('application-autoscaling.amazonaws.com')
const autoScalingRole = new iam.Role(this, roleResourceName, {
assumedBy: new iam.ServicePrincipal('application-autoscaling.amazonaws.com')
});
autoScalingRole.addToPolicy(new PolicyStatement(PolicyStatementEffect.Allow)
autoScalingRole.addToPolicy(new iam.PolicyStatement(iam.PolicyStatementEffect.Allow)
.addActions("dynamodb:DescribeTable", "dynamodb:UpdateTable")
.addResource(this.tableArn));
autoScalingRole.addToPolicy(new PolicyStatement(PolicyStatementEffect.Allow)
autoScalingRole.addToPolicy(new iam.PolicyStatement(iam.PolicyStatementEffect.Allow)
.addActions("cloudwatch:PutMetricAlarm", "cloudwatch:DescribeAlarms", "cloudwatch:GetMetricStatistics",
"cloudwatch:SetAlarmState", "cloudwatch:DeleteAlarms")
.addAllResources());
return autoScalingRole;
}

private buildScalableTargetResourceProps(scalableDimension: string,
scalingRole: Role,
scalingRole: iam.Role,
props: AutoScalingProps) {
return {
maxCapacity: props.maxCapacity,
Expand Down
67 changes: 67 additions & 0 deletions packages/@aws-cdk/aws-dynamodb/test/test.dynamodb.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { expect, haveResource } from '@aws-cdk/assert';
import iam = require('@aws-cdk/aws-iam');
import { App, Stack } from '@aws-cdk/cdk';
import { Test } from 'nodeunit';
import {
Expand All @@ -10,6 +12,8 @@ import {
Table
} from '../lib';

// tslint:disable:object-literal-key-quotes

// CDK parameters
const STACK_NAME = 'MyStack';
const CONSTRUCT_NAME = 'MyTable';
Expand Down Expand Up @@ -2024,6 +2028,34 @@ export = {
}), /minimumCapacity must be greater than or equal to 0; Provided value is: -5/);

test.done();
},

'grants': {

'"grant" allows adding arbitrary actions associated with this table resource'(test: Test) {
testGrant(test,
[ 'action1', 'action2' ], (p, t) => t.grant(p, 'dynamodb:action1', 'dynamodb:action2'));
},

'"grantReadData" allows the principal to read data from the table'(test: Test) {
testGrant(test,
[ 'BatchGetItem', 'GetRecords', 'GetShardIterator', 'Query', 'GetItem', 'Scan' ], (p, t) => t.grantReadData(p));
},

'"grantWriteData" allows the principal to write data to the table'(test: Test) {
testGrant(test, [
'BatchWriteItem', 'PutItem', 'UpdateItem', 'DeleteItem' ], (p, t) => t.grantWriteData(p));
},

'"grantReadWriteData" allows the principal to read/write data'(test: Test) {
testGrant(test, [
'BatchGetItem', 'GetRecords', 'GetShardIterator', 'Query', 'GetItem', 'Scan',
'BatchWriteItem', 'PutItem', 'UpdateItem', 'DeleteItem' ], (p, t) => t.grantReadWriteData(p));
},

'"grantFullAccess" allows the principal to perform any action on the table ("*")'(test: Test) {
testGrant(test, [ '*' ], (p, t) => t.grantFullAccess(p));
}
}
};

Expand All @@ -2036,3 +2068,38 @@ class TestApp {
return this.app.synthesizeStack(this.stack.name).template;
}
}

function testGrant(test: Test, expectedActions: string[], invocation: (user: iam.IPrincipal, table: Table) => void) {
// GIVEN
const stack = new Stack();

const table = new Table(stack, 'my-table');
table.addPartitionKey({ name: 'ID', type: AttributeType.String });

const user = new iam.User(stack, 'user');

// WHEN
invocation(user, table);

// THEN
const action = expectedActions.length > 1 ? expectedActions.map(a => `dynamodb:${a}`) : `dynamodb:${expectedActions[0]}`;
expect(stack).to(haveResource('AWS::IAM::Policy', {
"PolicyDocument": {
"Statement": [
{
"Action": action,
"Effect": "Allow",
"Resource": {
"Fn::GetAtt": [
"mytable0324D45C",
"Arn"
]
}
}
],
"Version": "2012-10-17"
},
"Users": [ { "Ref": "user2C2B57AE" } ]
}));
test.done();
}

0 comments on commit 1561a4d

Please sign in to comment.