Skip to content

Commit

Permalink
feat(rds): make rds secret name configurable (#13626)
Browse files Browse the repository at this point in the history
close #8984 

As suggested by @njlynch in the first issue, I added the ability to set the secret name that RDS generates.

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
hedrall authored Mar 19, 2021
1 parent aa45ede commit 62a91b7
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 3 deletions.
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-rds/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ It's also possible to create user credentials together with the instance/cluster
```ts
const myUserSecret = new rds.DatabaseSecret(this, 'MyUserSecret', {
username: 'myuser',
secretName: 'my-user-secret', // optional, defaults to a CloudFormation-generated name
masterSecret: instance.secret,
excludeCharacters: '{}[]()\'"/\\', // defaults to the set " %+~`#$&*()|[]{}:;<>?!'/@\"\\"
});
Expand Down
8 changes: 8 additions & 0 deletions packages/@aws-cdk/aws-rds/lib/database-secret.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ export interface DatabaseSecretProps {
*/
readonly username: string;

/**
* A name for the secret.
*
* @default - A name is generated by CloudFormation.
*/
readonly secretName?: string;

/**
* The KMS key to use to encrypt the secret.
*
Expand Down Expand Up @@ -60,6 +67,7 @@ export class DatabaseSecret extends secretsmanager.Secret {
super(scope, id, {
encryptionKey: props.encryptionKey,
description: `Generated by the CDK for stack: ${Aws.STACK_NAME}`,
secretName: props.secretName,
generateSecretString: {
passwordLength: 30, // Oracle password cannot have more than 30 characters
secretStringTemplate: JSON.stringify({
Expand Down
3 changes: 2 additions & 1 deletion packages/@aws-cdk/aws-rds/lib/private/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export function renderCredentials(scope: Construct, engine: IEngine, credentials
renderedCredentials = Credentials.fromSecret(
new DatabaseSecret(scope, 'Secret', {
username: renderedCredentials.username,
secretName: renderedCredentials.secretName,
encryptionKey: renderedCredentials.encryptionKey,
excludeCharacters: renderedCredentials.excludeCharacters,
// if username must be referenced as a string we can safely replace the
Expand Down Expand Up @@ -131,4 +132,4 @@ export function helperRemovalPolicy(basePolicy?: RemovalPolicy): RemovalPolicy {
*/
export function renderUnless<A>(value: A, suppressValue: A): A | undefined {
return value === suppressValue ? undefined : value;
}
}
15 changes: 15 additions & 0 deletions packages/@aws-cdk/aws-rds/lib/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,13 @@ export interface BackupProps {
* Base options for creating Credentials.
*/
export interface CredentialsBaseOptions {
/**
* The name of the secret.
*
* @default - A name is generated by CloudFormation.
*/
readonly secretName?: string;

/**
* KMS encryption key to encrypt the generated secret.
*
Expand Down Expand Up @@ -232,6 +239,14 @@ export abstract class Credentials {
*/
public abstract readonly username: string;

/**
* The name to use for the Secret if a new Secret is to be generated in
* SecretsManager for these Credentials.
*
* @default - A name is generated by CloudFormation.
*/
public abstract readonly secretName?: string;

/**
* Whether the username should be referenced as a string and not as a dynamic
* reference to the username in the secret.
Expand Down
48 changes: 47 additions & 1 deletion packages/@aws-cdk/aws-rds/test/cluster.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import * as cxapi from '@aws-cdk/cx-api';
import { testFutureBehavior } from 'cdk-build-tools/lib/feature-flag';
import {
AuroraEngineVersion, AuroraMysqlEngineVersion, AuroraPostgresEngineVersion, CfnDBCluster, Credentials, DatabaseCluster,
DatabaseClusterEngine, DatabaseClusterFromSnapshot, ParameterGroup, PerformanceInsightRetention, SubnetGroup,
DatabaseClusterEngine, DatabaseClusterFromSnapshot, ParameterGroup, PerformanceInsightRetention, SubnetGroup, DatabaseSecret,
} from '../lib';

describe('cluster', () => {
Expand Down Expand Up @@ -1763,6 +1763,52 @@ describe('cluster', () => {

});

test('can set custom name to database secret by fromSecret', () => {
// GIVEN
const stack = testStack();
const vpc = new ec2.Vpc(stack, 'VPC');
const secretName = 'custom-secret-name';
const secret = new DatabaseSecret(stack, 'Secret', {
username: 'admin',
secretName,
} );

// WHEN
new DatabaseCluster(stack, 'Database', {
engine: DatabaseClusterEngine.aurora({ version: AuroraEngineVersion.VER_1_22_2 }),
credentials: Credentials.fromSecret(secret),
instanceProps: {
vpc,
},
});

// THEN
expect(stack).toHaveResourceLike('AWS::SecretsManager::Secret', {
Name: secretName,
});
});

test('can set custom name to database secret by fromGeneratedSecret', () => {
// GIVEN
const stack = testStack();
const vpc = new ec2.Vpc(stack, 'VPC');
const secretName = 'custom-secret-name';

// WHEN
new DatabaseCluster(stack, 'Database', {
engine: DatabaseClusterEngine.aurora({ version: AuroraEngineVersion.VER_1_22_2 }),
credentials: Credentials.fromGeneratedSecret('admin', { secretName }),
instanceProps: {
vpc,
},
});

// THEN
expect(stack).toHaveResourceLike('AWS::SecretsManager::Secret', {
Name: secretName,
});
});

test('can set public accessibility for database cluster with instances in private subnet', () => {
// GIVEN
const stack = testStack();
Expand Down
38 changes: 37 additions & 1 deletion packages/@aws-cdk/aws-rds/test/instance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1204,8 +1204,44 @@ describe('instance', () => {
MasterUsername: 'postgres', // username is a string
MasterUserPassword: '{{resolve:ssm-secure:/dbPassword:1}}', // reference to SSM
});
});

test('can set custom name to database secret by fromSecret', () => {
// WHEN
const secretName = 'custom-secret-name';
const secret = new rds.DatabaseSecret(stack, 'Secret', {
username: 'admin',
secretName,
} );
new rds.DatabaseInstance(stack, 'Instance', {
engine: rds.DatabaseInstanceEngine.mysql({
version: rds.MysqlEngineVersion.VER_8_0_19,
}),
credentials: rds.Credentials.fromSecret(secret),
vpc,
});

// THEN
expect(stack).toHaveResourceLike('AWS::SecretsManager::Secret', {
Name: secretName,
});
});

test('can set custom name to database secret by fromGeneratedSecret', () => {
// WHEN
const secretName = 'custom-secret-name';
new rds.DatabaseInstance(stack, 'Instance', {
engine: rds.DatabaseInstanceEngine.mysql({
version: rds.MysqlEngineVersion.VER_8_0_19,
}),
credentials: rds.Credentials.fromGeneratedSecret('admin', { secretName }),
vpc,
});

// THEN
expect(stack).toHaveResourceLike('AWS::SecretsManager::Secret', {
Name: secretName,
});
});

test('can set publiclyAccessible to false with public subnets', () => {
Expand Down Expand Up @@ -1274,4 +1310,4 @@ test.each([
DeletionPolicy: subnetValue,
UpdateReplacePolicy: subnetValue,
}, ResourcePart.CompleteDefinition);
});
});

0 comments on commit 62a91b7

Please sign in to comment.