Skip to content

Commit

Permalink
feat(dynamodb): support for Customer-managed CMK (#7425)
Browse files Browse the repository at this point in the history
Deprecated `serverSideEncryption` and introduced new `encryption` and
`encryptionKey` properties to support Customer-Managed CMK on
DynamoDB Tables. The purpose-specific `grant*` methods (`grantReadData`, ...)
have been updated to automatically extend the relevant KMS key grants
transparently, while the generic `grant` methods are only adding grants on the
table or stream (depending on the `grant` method used) itself.

The new `encryption` and `encryptionKey` properties are mutually exclusive
with the now-deprecated `serverSideEncryption` property.

Fixes #7142

Co-Authored-By: rmuller@amazon.com
  • Loading branch information
shreyasdamle committed May 14, 2020
1 parent 3dd21b9 commit ff8219b
Show file tree
Hide file tree
Showing 10 changed files with 1,675 additions and 57 deletions.
50 changes: 50 additions & 0 deletions packages/@aws-cdk/aws-dynamodb/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,53 @@ const globalTable = new dynamodb.Table(this, 'Table', {

When doing so, a CloudFormation Custom Resource will be added to the stack in order to create the replica tables in the
selected regions.

### Encryption

All user data stored in Amazon DynamoDB is fully encrypted at rest. When creating a new table, you can choose to encrypt using the following customer master keys (CMK) to encrypt your table:
* AWS owned CMK - By default, all tables are encrypted under an AWS owned customer master key (CMK) in the DynamoDB service account (no additional charges apply).
* AWS managed CMK - AWS KMS keys (one per region) are created in your account, managed, and used on your behalf by AWS DynamoDB (AWS KMS chages apply).
* Customer managed CMK - You have full control over the KMS key used to encrypt the DynamoDB Table (AWS KMS charges apply).

Creating a Table encrypted with a customer managed CMK:

```ts
import dynamodb = require('@aws-cdk/aws-dynamodb');

const table = new dynamodb.Table(stack, 'MyTable', {
partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
encryption: TableEncryption.CUSTOMER_MANAGED,
});

// You can access the CMK that was added to the stack on your behalf by the Table construct via:
const tableEncryptionKey = table.encryptionKey;
```

You can also supply your own key:

```ts
import dynamodb = require('@aws-cdk/aws-dynamodb');
import kms = require('@aws-cdk/aws-kms');

const encryptionKey = new kms.Key(stack, 'Key', {
enableKeyRotation: true
});
const table = new dynamodb.Table(stack, 'MyTable', {
partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
encryption: TableEncryption.CUSTOMER_MANAGED,
encryptionKey, // This will be exposed as table.encryptionKey
});
```

In order to use the AWS managed CMK instead, change the code to:

```ts
import dynamodb = require('@aws-cdk/aws-dynamodb');

const table = new dynamodb.Table(stack, 'MyTable', {
partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
encryption: TableEncryption.AWS_MANAGED,
});

// In this case, the CMK _cannot_ be accessed through table.encryptionKey.
```
30 changes: 30 additions & 0 deletions packages/@aws-cdk/aws-dynamodb/lib/perms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export const READ_DATA_ACTIONS = [
'dynamodb:BatchGetItem',
'dynamodb:GetRecords',
'dynamodb:GetShardIterator',
'dynamodb:Query',
'dynamodb:GetItem',
'dynamodb:Scan',
];
export const KEY_READ_ACTIONS = [
'kms:Decrypt',
'kms:DescribeKey',
];

export const WRITE_DATA_ACTIONS = [
'dynamodb:BatchWriteItem',
'dynamodb:PutItem',
'dynamodb:UpdateItem',
'dynamodb:DeleteItem',
];
export const KEY_WRITE_ACTIONS = [
'kms:Encrypt',
'kms:ReEncrypt*',
'kms:GenerateDataKey*',
];

export const READ_STREAM_DATA_ACTIONS = [
'dynamodb:DescribeStream',
'dynamodb:GetRecords',
'dynamodb:GetShardIterator',
];
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* tslint:disable no-console */
import { IsCompleteRequest, IsCompleteResponse, OnEventRequest, OnEventResponse } from '@aws-cdk/custom-resources/lib/provider-framework/types';
import type { IsCompleteRequest, IsCompleteResponse, OnEventRequest, OnEventResponse } from '@aws-cdk/custom-resources/lib/provider-framework/types';
import { DynamoDB } from 'aws-sdk'; // eslint-disable-line import/no-extraneous-dependencies

export async function onEventHandler(event: OnEventRequest): Promise<OnEventResponse> {
Expand Down
6 changes: 4 additions & 2 deletions packages/@aws-cdk/aws-dynamodb/lib/replica-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,19 @@ export class ReplicaProvider extends NestedStack {
private constructor(scope: Construct, id: string) {
super(scope, id);

const code = lambda.Code.fromAsset(path.join(__dirname, 'replica-handler'));

// Issues UpdateTable API calls
this.onEventHandler = new lambda.Function(this, 'OnEventHandler', {
code: lambda.Code.fromAsset(path.join(__dirname, 'replica-handler')),
code,
runtime: lambda.Runtime.NODEJS_12_X,
handler: 'index.onEventHandler',
timeout: Duration.minutes(5),
});

// Checks if table is back to `ACTIVE` state
this.isCompleteHandler = new lambda.Function(this, 'IsCompleteHandler', {
code: lambda.Code.fromAsset(path.join(__dirname, 'replica-handler')),
code,
runtime: lambda.Runtime.NODEJS_12_X,
handler: 'index.isCompleteHandler',
timeout: Duration.seconds(30),
Expand Down

0 comments on commit ff8219b

Please sign in to comment.