-
Notifications
You must be signed in to change notification settings - Fork 3.9k
/
secret-rotation.ts
263 lines (224 loc) · 9.43 KB
/
secret-rotation.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
import * as ec2 from '@aws-cdk/aws-ec2';
import * as lambda from '@aws-cdk/aws-lambda';
import * as serverless from '@aws-cdk/aws-sam';
import { Construct, Duration, Stack, Token } from '@aws-cdk/core';
import { ISecret } from './secret';
/**
* Options for a SecretRotationApplication
*/
export interface SecretRotationApplicationOptions {
/**
* Whether the rotation application uses the mutli user scheme
*
* @default false
*/
readonly isMultiUser?: boolean;
}
/**
* A secret rotation serverless application.
*/
export class SecretRotationApplication {
/**
* Conducts an AWS SecretsManager secret rotation for RDS MariaDB using the single user rotation scheme
*/
public static readonly MARIADB_ROTATION_SINGLE_USER = new SecretRotationApplication('SecretsManagerRDSMariaDBRotationSingleUser', '1.1.3');
/**
* Conducts an AWS SecretsManager secret rotation for RDS MariaDB using the multi user rotation scheme
*/
public static readonly MARIADB_ROTATION_MULTI_USER = new SecretRotationApplication('SecretsManagerRDSMariaDBRotationMultiUser', '1.1.3', {
isMultiUser: true,
});
/**
* Conducts an AWS SecretsManager secret rotation for RDS MySQL using the single user rotation scheme
*/
public static readonly MYSQL_ROTATION_SINGLE_USER = new SecretRotationApplication('SecretsManagerRDSMySQLRotationSingleUser', '1.1.3');
/**
* Conducts an AWS SecretsManager secret rotation for RDS MySQL using the multi user rotation scheme
*/
public static readonly MYSQL_ROTATION_MULTI_USER = new SecretRotationApplication('SecretsManagerRDSMySQLRotationMultiUser', '1.1.3', {
isMultiUser: true,
});
/**
* Conducts an AWS SecretsManager secret rotation for RDS Oracle using the single user rotation scheme
*/
public static readonly ORACLE_ROTATION_SINGLE_USER = new SecretRotationApplication('SecretsManagerRDSOracleRotationSingleUser', '1.1.3');
/**
* Conducts an AWS SecretsManager secret rotation for RDS Oracle using the multi user rotation scheme
*/
public static readonly ORACLE_ROTATION_MULTI_USER = new SecretRotationApplication('SecretsManagerRDSOracleRotationMultiUser', '1.1.3', {
isMultiUser: true,
});
/**
* Conducts an AWS SecretsManager secret rotation for RDS PostgreSQL using the single user rotation scheme
*/
public static readonly POSTGRES_ROTATION_SINGLE_USER = new SecretRotationApplication('SecretsManagerRDSPostgreSQLRotationSingleUser', '1.1.3');
/**
* Conducts an AWS SecretsManager secret rotation for RDS PostgreSQL using the multi user rotation scheme
*/
public static readonly POSTGRES_ROTATION_MULTI_USER = new SecretRotationApplication('SecretsManagerRDSPostgreSQLRotationMultiUser', '1.1.3', {
isMultiUser: true,
});
/**
* Conducts an AWS SecretsManager secret rotation for RDS SQL Server using the single user rotation scheme
*/
public static readonly SQLSERVER_ROTATION_SINGLE_USER = new SecretRotationApplication('SecretsManagerRDSSQLServerRotationSingleUser', '1.1.3');
/**
* Conducts an AWS SecretsManager secret rotation for RDS SQL Server using the multi user rotation scheme
*/
public static readonly SQLSERVER_ROTATION_MULTI_USER = new SecretRotationApplication('SecretsManagerRDSSQLServerRotationMultiUser', '1.1.3', {
isMultiUser: true,
});
/**
* Conducts an AWS SecretsManager secret rotation for Amazon Redshift using the single user rotation scheme
*/
public static readonly REDSHIFT_ROTATION_SINGLE_USER = new SecretRotationApplication('SecretsManagerRedshiftRotationSingleUser', '1.1.3');
/**
* Conducts an AWS SecretsManager secret rotation for Amazon Redshift using the multi user rotation scheme
*/
public static readonly REDSHIFT_ROTATION_MULTI_USER = new SecretRotationApplication('SecretsManagerRedshiftRotationMultiUser', '1.1.3', {
isMultiUser: true,
});
/**
* Conducts an AWS SecretsManager secret rotation for MongoDB using the single user rotation scheme
*/
public static readonly MONGODB_ROTATION_SINGLE_USER = new SecretRotationApplication('SecretsManagerMongoDBRotationSingleUser', '1.1.3');
/**
* Conducts an AWS SecretsManager secret rotation for MongoDB using the multi user rotation scheme
*/
public static readonly MONGODB_ROTATION_MULTI_USER = new SecretRotationApplication('SecretsManagerMongoDBRotationMultiUser', '1.1.3', {
isMultiUser: true,
});
/**
* The application identifier of the rotation application
*/
public readonly applicationId: string;
/**
* The semantic version of the rotation application
*/
public readonly semanticVersion: string;
/**
* Whether the rotation application uses the mutli user scheme
*/
public readonly isMultiUser?: boolean;
constructor(applicationId: string, semanticVersion: string, options?: SecretRotationApplicationOptions) {
this.applicationId = `arn:aws:serverlessrepo:us-east-1:297356227824:applications/${applicationId}`;
this.semanticVersion = semanticVersion;
this.isMultiUser = options && options.isMultiUser;
}
}
/**
* Construction properties for a SecretRotation.
*/
export interface SecretRotationProps {
/**
* The secret to rotate. It must be a JSON string with the following format:
* ```
* {
* "engine": <required: database engine>,
* "host": <required: instance host name>,
* "username": <required: username>,
* "password": <required: password>,
* "dbname": <optional: database name>,
* "port": <optional: if not specified, default port will be used>,
* "masterarn": <required for multi user rotation: the arn of the master secret which will be used to create users/change passwords>
* }
* ```
*
* This is typically the case for a secret referenced from an
* AWS::SecretsManager::SecretTargetAttachment or an `ISecret` returned by the `attach()` method of `Secret`.
*
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-secretsmanager-secrettargetattachment.html
*/
readonly secret: ISecret;
/**
* The master secret for a multi user rotation scheme
*
* @default - single user rotation scheme
*/
readonly masterSecret?: ISecret;
/**
* Specifies the number of days after the previous rotation before
* Secrets Manager triggers the next automatic rotation.
*
* @default Duration.days(30)
*/
readonly automaticallyAfter?: Duration;
/**
* The serverless application for the rotation.
*/
readonly application: SecretRotationApplication;
/**
* The VPC where the Lambda rotation function will run.
*/
readonly vpc: ec2.IVpc;
/**
* The type of subnets in the VPC where the Lambda rotation function will run.
*
* @default - the Vpc default strategy if not specified.
*/
readonly vpcSubnets?: ec2.SubnetSelection;
/**
* The target service or database
*/
readonly target: ec2.IConnectable;
/**
* The security group for the Lambda rotation function
*
* @default - a new security group is created
*/
readonly securityGroup?: ec2.ISecurityGroup;
}
/**
* Secret rotation for a service or database
*/
export class SecretRotation extends Construct {
constructor(scope: Construct, id: string, props: SecretRotationProps) {
super(scope, id);
if (!props.target.connections.defaultPort) {
throw new Error('The `target` connections must have a default port range.');
}
if (props.application.isMultiUser && !props.masterSecret) {
throw new Error('The `masterSecret` must be specified for application using the multi user scheme.');
}
// Max length of 64 chars, get the last 64 chars
const uniqueId = this.node.uniqueId;
const rotationFunctionName = uniqueId.substring(Math.max(uniqueId.length - 64, 0), uniqueId.length);
const securityGroup = props.securityGroup || new ec2.SecurityGroup(this, 'SecurityGroup', {
vpc: props.vpc,
});
props.target.connections.allowDefaultPortFrom(securityGroup);
const parameters: { [key: string]: string } = {
endpoint: `https://secretsmanager.${Stack.of(this).region}.${Stack.of(this).urlSuffix}`,
functionName: rotationFunctionName,
vpcSubnetIds: props.vpc.selectSubnets(props.vpcSubnets).subnetIds.join(','),
vpcSecurityGroupIds: securityGroup.securityGroupId,
};
if (props.secret.encryptionKey) {
parameters.kmsKeyArn = props.secret.encryptionKey.keyArn;
}
if (props.masterSecret) {
parameters.masterSecretArn = props.masterSecret.secretArn;
if (props.masterSecret.encryptionKey) {
parameters.masterSecretKmsKeyArn = props.masterSecret.encryptionKey.keyArn;
}
}
const application = new serverless.CfnApplication(this, 'Resource', {
location: props.application,
parameters,
});
// This creates a CF a dependency between the rotation schedule and the
// serverless application. This is needed because it's the application
// that creates the Lambda permission to invoke the function.
// See https://docs.aws.amazon.com/secretsmanager/latest/userguide/integrating_cloudformation.html
const rotationLambda = lambda.Function.fromFunctionArn(this, 'RotationLambda', Token.asString(application.getAtt('Outputs.RotationLambdaARN')));
props.secret.addRotationSchedule('RotationSchedule', {
rotationLambda,
automaticallyAfter: props.automaticallyAfter,
});
// Prevent secrets deletions when rotation is in place
props.secret.denyAccountRootDelete();
if (props.masterSecret) {
props.masterSecret.denyAccountRootDelete();
}
}
}