Skip to content

Commit

Permalink
feat(rds): cluster retention, reference KMS key by object (#2063)
Browse files Browse the repository at this point in the history
Add control over DeletionPolicy and UpdateReplacePolicy, both defaulting to Retain
to avoid data loss.

Reference encryption key reference with key interface.

BREAKING CHANGE: Replaced `kmsKeyArn: string` by `kmsKey: kms.IEncryptionKey` in `DatabaseClusterProps`
  • Loading branch information
jogold authored and rix0rrr committed Mar 26, 2019
1 parent 1e3d41e commit 99ab46d
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 21 deletions.
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-rds/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ When the master password is generated and stored in AWS Secrets Manager, it can

Rotation of the master password is also supported for an existing cluster:
```ts
new rds.RotationSingleUser(stack, 'Rotation', {
new RotationSingleUser(stack, 'Rotation', {
secret: importedSecret,
engine: DatabaseEngine.Oracle,
target: importedCluster,
Expand Down
34 changes: 30 additions & 4 deletions packages/@aws-cdk/aws-rds/lib/cluster.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ec2 = require('@aws-cdk/aws-ec2');
import kms = require('@aws-cdk/aws-kms');
import secretsmanager = require('@aws-cdk/aws-secretsmanager');
import cdk = require('@aws-cdk/cdk');
import { IClusterParameterGroup } from './cluster-parameter-group';
Expand Down Expand Up @@ -72,9 +73,19 @@ export interface DatabaseClusterProps {
defaultDatabaseName?: string;

/**
* ARN of KMS key if you want to enable storage encryption
* Whether to enable storage encryption
*
* @default false
*/
storageEncrypted?: boolean

/**
* The KMS key for storage encryption. If specified `storageEncrypted`
* will be set to `true`.
*
* @default default master key
*/
kmsKeyArn?: string;
kmsKey?: kms.IEncryptionKey;

/**
* A daily time range in 24-hours UTC format in which backups preferably execute.
Expand All @@ -91,6 +102,14 @@ export interface DatabaseClusterProps {
* @default No parameter group
*/
parameterGroup?: IClusterParameterGroup;

/**
* The CloudFormation policy to apply when the cluster and its instances
* are removed from the stack or replaced during an update.
*
* @default Retain
*/
deleteReplacePolicy?: cdk.DeletionPolicy
}

/**
Expand Down Expand Up @@ -261,10 +280,14 @@ export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseClu
preferredMaintenanceWindow: props.preferredMaintenanceWindow,
databaseName: props.defaultDatabaseName,
// Encryption
kmsKeyId: props.kmsKeyArn,
storageEncrypted: props.kmsKeyArn ? true : false,
kmsKeyId: props.kmsKey && props.kmsKey.keyArn,
storageEncrypted: props.kmsKey ? true : props.storageEncrypted
});

const deleteReplacePolicy = props.deleteReplacePolicy || cdk.DeletionPolicy.Retain;
cluster.options.deletionPolicy = deleteReplacePolicy;
cluster.options.updateReplacePolicy = deleteReplacePolicy;

this.clusterIdentifier = cluster.ref;
this.clusterEndpoint = new Endpoint(cluster.dbClusterEndpointAddress, cluster.dbClusterEndpointPort);
this.readerEndpoint = new Endpoint(cluster.dbClusterReadEndpointAddress, cluster.dbClusterEndpointPort);
Expand Down Expand Up @@ -303,6 +326,9 @@ export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseClu
dbSubnetGroupName: subnetGroup.ref,
});

instance.options.deletionPolicy = deleteReplacePolicy;
instance.options.updateReplacePolicy = deleteReplacePolicy;

// We must have a dependency on the NAT gateway provider here to create
// things in the right order.
instance.node.addDependency(internetConnected);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,6 @@
]
]
},
"StorageEncrypted": false,
"VpcSecurityGroupIds": [
{
"Fn::GetAtt": [
Expand All @@ -659,7 +658,9 @@
]
}
]
}
},
"DeletionPolicy": "Retain",
"UpdateReplacePolicy": "Retain"
},
"DatabaseInstance1844F58FD": {
"Type": "AWS::RDS::DBInstance",
Expand All @@ -677,7 +678,9 @@
"VPCPrivateSubnet1DefaultRouteAE1D6490",
"VPCPrivateSubnet2DefaultRouteF4F5CFD2",
"VPCPrivateSubnet3DefaultRoute27F311AE"
]
],
"DeletionPolicy": "Retain",
"UpdateReplacePolicy": "Retain"
},
"DatabaseInstance2AA380DEE": {
"Type": "AWS::RDS::DBInstance",
Expand All @@ -695,7 +698,9 @@
"VPCPrivateSubnet1DefaultRouteAE1D6490",
"VPCPrivateSubnet2DefaultRouteF4F5CFD2",
"VPCPrivateSubnet3DefaultRoute27F311AE"
]
],
"DeletionPolicy": "Retain",
"UpdateReplacePolicy": "Retain"
},
"DatabaseRotationSecurityGroup17736B63": {
"Type": "AWS::EC2::SecurityGroup",
Expand Down
14 changes: 10 additions & 4 deletions packages/@aws-cdk/aws-rds/test/integ.cluster.expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,9 @@
]
}
]
}
},
"DeletionPolicy": "Retain",
"UpdateReplacePolicy": "Retain"
},
"DatabaseInstance1844F58FD": {
"Type": "AWS::RDS::DBInstance",
Expand All @@ -502,7 +504,9 @@
"DependsOn": [
"VPCPublicSubnet1DefaultRoute91CEF279",
"VPCPublicSubnet2DefaultRouteB7481BBA"
]
],
"DeletionPolicy": "Retain",
"UpdateReplacePolicy": "Retain"
},
"DatabaseInstance2AA380DEE": {
"Type": "AWS::RDS::DBInstance",
Expand All @@ -520,7 +524,9 @@
"DependsOn": [
"VPCPublicSubnet1DefaultRoute91CEF279",
"VPCPublicSubnet2DefaultRouteB7481BBA"
]
],
"DeletionPolicy": "Retain",
"UpdateReplacePolicy": "Retain"
}
}
}
}
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-rds/test/integ.cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const cluster = new DatabaseCluster(stack, 'Database', {
vpc
},
parameterGroup: params,
kmsKeyArn: kmsKey.keyArn,
kmsKey,
});

cluster.connections.allowDefaultPortFromAnyIpv4('Open to the world');
Expand Down
55 changes: 48 additions & 7 deletions packages/@aws-cdk/aws-rds/test/test.cluster.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { expect, haveResource } from '@aws-cdk/assert';
import { expect, haveResource, ResourcePart } from '@aws-cdk/assert';
import ec2 = require('@aws-cdk/aws-ec2');
import kms = require('@aws-cdk/aws-kms');
import cdk = require('@aws-cdk/cdk');
import { Test } from 'nodeunit';
import { ClusterParameterGroup, DatabaseCluster, DatabaseClusterEngine } from '../lib';
Expand All @@ -25,12 +26,21 @@ export = {

// THEN
expect(stack).to(haveResource('AWS::RDS::DBCluster', {
Engine: "aurora",
DBSubnetGroupName: { Ref: "DatabaseSubnets56F17B9A" },
MasterUsername: "admin",
MasterUserPassword: "tooshort",
VpcSecurityGroupIds: [ {"Fn::GetAtt": ["DatabaseSecurityGroup5C91FDCB", "GroupId"]}]
}));
Properties: {
Engine: "aurora",
DBSubnetGroupName: { Ref: "DatabaseSubnets56F17B9A" },
MasterUsername: "admin",
MasterUserPassword: "tooshort",
VpcSecurityGroupIds: [ {"Fn::GetAtt": ["DatabaseSecurityGroup5C91FDCB", "GroupId"]}]
},
DeletionPolicy: 'Retain',
UpdateReplacePolicy: 'Retain'
}, ResourcePart.CompleteDefinition));

expect(stack).to(haveResource('AWS::RDS::DBInstance', {
DeletionPolicy: 'Retain',
UpdateReplacePolicy: 'Retain'
}, ResourcePart.CompleteDefinition));

test.done();
},
Expand Down Expand Up @@ -232,6 +242,37 @@ export = {
}
}));

test.done();
},

'create an encrypted cluster with custom KMS key'(test: Test) {
// GIVEN
const stack = testStack();
const vpc = new ec2.VpcNetwork(stack, 'VPC');

// WHEN
new DatabaseCluster(stack, 'Database', {
engine: DatabaseClusterEngine.AuroraMysql,
masterUser: {
username: 'admin'
},
instanceProps: {
instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small),
vpc
},
kmsKey: new kms.EncryptionKey(stack, 'Key')
});

// THEN
expect(stack).to(haveResource('AWS::RDS::DBCluster', {
KmsKeyId: {
'Fn::GetAtt': [
'Key961B73FD',
'Arn'
]
}
}));

test.done();
}
};
Expand Down

0 comments on commit 99ab46d

Please sign in to comment.