From 35d9600ce60dbdf4e720b89a7efc1112ef99d53e Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 13 Mar 2019 23:12:21 +0100 Subject: [PATCH 1/4] fix(secretsmanager): allow templated string creation The interface `TemplatedSecretStringGenerator` was exported but not used in the `generateSecretString` prop. --- .../@aws-cdk/aws-secretsmanager/lib/secret.ts | 2 +- .../test/integ.secret.lit.expected.json | 40 +++++++++++++++++++ .../test/integ.secret.lit.ts | 14 +++++++ .../aws-secretsmanager/test/test.secret.ts | 23 +++++++++++ 4 files changed, 78 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts index 142592b8a6a29..9ae0e7dedf572 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts @@ -75,7 +75,7 @@ export interface SecretProps { * @default 32 characters with upper-case letters, lower-case letters, punctuation and numbers (at least one from each * category), per the default values of ``SecretStringGenerator``. */ - generateSecretString?: SecretStringGenerator; + generateSecretString?: SecretStringGenerator | TemplatedSecretStringGenerator; /** * A name for the secret. Note that deleting secrets from SecretsManager does not happen immediately, but after a 7 to diff --git a/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.expected.json b/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.expected.json index 2eb92c11cc1b1..b09235155139e 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.expected.json +++ b/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.expected.json @@ -79,6 +79,46 @@ } } } + }, + "TemplatedSecret3D98B577": { + "Type": "AWS::SecretsManager::Secret", + "Properties": { + "GenerateSecretString": { + "GenerateStringKey": "password", + "SecretStringTemplate": "{\"username\":\"user\"}" + } + } + }, + "OtherUser6093621C": { + "Type": "AWS::IAM::User", + "Properties": { + "LoginProfile": { + "Password": { + "Fn::Join": [ + "", + [ + "{{resolve:secretsmanager:", + { + "Ref": "TemplatedSecret3D98B577" + }, + ":SecretString:password::}}" + ] + ] + } + }, + "UserName": { + "Fn::Join": [ + "", + [ + "{{resolve:secretsmanager:", + { + "Ref": "TemplatedSecret3D98B577" + }, + ":SecretString:username::}}" + ] + ] + } + } } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.ts b/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.ts index 4a90c8f6ef424..171426e6ba45c 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.ts +++ b/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.ts @@ -9,12 +9,26 @@ class SecretsManagerStack extends cdk.Stack { const role = new iam.Role(this, 'TestRole', { assumedBy: new iam.AccountRootPrincipal() }); /// !show + // Default secret const secret = new secretsManager.Secret(this, 'Secret'); secret.grantRead(role); new iam.User(this, 'User', { password: secret.stringValue }); + + // Templated secret + const templatedSecret = new secretsManager.Secret(this, 'TemplatedSecret', { + generateSecretString: { + secretStringTemplate: JSON.stringify({ username: 'user' }), + generateStringKey: 'password' + } + }); + + new iam.User(this, 'OtherUser', { + userName: templatedSecret.jsonFieldValue('username'), + password: templatedSecret.jsonFieldValue('password') + }); /// !hide } } diff --git a/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts b/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts index 47b724900612d..bded10762e20d 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts @@ -21,6 +21,29 @@ export = { test.done(); }, + 'templated secret string'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new secretsmanager.Secret(stack, 'Secret', { + generateSecretString: { + secretStringTemplate: JSON.stringify({ username: 'username' }), + generateStringKey: 'password' + } + }); + + // THEN + expect(stack).to(haveResource('AWS::SecretsManager::Secret', { + GenerateSecretString: { + SecretStringTemplate: '{"username":"username"}', + GenerateStringKey: 'password' + } + })); + + test.done(); + }, + 'grantRead'(test: Test) { // GIVEN const stack = new cdk.Stack(); From ac12ecfa362d918533ef2389cd35331dfb00dbb6 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Fri, 22 Mar 2019 15:56:59 +0100 Subject: [PATCH 2/4] Fold the 2 structures together --- .../@aws-cdk/aws-rds/lib/database-secret.ts | 4 +- .../@aws-cdk/aws-secretsmanager/lib/secret.ts | 29 ++++++----- .../aws-secretsmanager/test/test.secret.ts | 51 +++++++++++++++++++ 3 files changed, 69 insertions(+), 15 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/lib/database-secret.ts b/packages/@aws-cdk/aws-rds/lib/database-secret.ts index c28750e1b56cc..fb2783f4f6edd 100644 --- a/packages/@aws-cdk/aws-rds/lib/database-secret.ts +++ b/packages/@aws-cdk/aws-rds/lib/database-secret.ts @@ -26,12 +26,12 @@ export class DatabaseSecret extends secretsmanager.Secret { constructor(scope: cdk.Construct, id: string, props: DatabaseSecretProps) { super(scope, id, { encryptionKey: props.encryptionKey, - generateSecretString: ({ + generateSecretString: { passwordLength: 30, // Oracle password cannot have more than 30 characters secretStringTemplate: JSON.stringify({ username: props.username }), generateStringKey: 'password', excludeCharacters: '"@/\\' - }) as secretsmanager.TemplatedSecretStringGenerator + } }); } } diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts index bb8491181da52..c4744476dac18 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts @@ -81,7 +81,7 @@ export interface SecretProps { * @default 32 characters with upper-case letters, lower-case letters, punctuation and numbers (at least one from each * category), per the default values of ``SecretStringGenerator``. */ - generateSecretString?: SecretStringGenerator | TemplatedSecretStringGenerator; + generateSecretString?: SecretStringGenerator /** * A name for the secret. Note that deleting secrets from SecretsManager does not happen immediately, but after a 7 to @@ -186,6 +186,12 @@ export class Secret extends SecretBase { constructor(scope: cdk.Construct, id: string, props: SecretProps = {}) { super(scope, id); + if (props.generateSecretString && + (props.generateSecretString.secretStringTemplate || props.generateSecretString.generateStringKey) && + !(props.generateSecretString.secretStringTemplate && props.generateSecretString.generateStringKey)) { + throw new Error('Both `secretStringTemplate` and `generateStringKey` must be specified.'); + } + const resource = new secretsmanager.CfnSecret(this, 'Resource', { description: props.description, kmsKeyId: props.encryptionKey && props.encryptionKey.keyArn, @@ -367,24 +373,21 @@ export interface SecretStringGenerator { * @default false */ excludeNumbers?: boolean; -} -/** - * Configuration to generate secrets such as passwords automatically, and include them in a JSON object template. - */ -export interface TemplatedSecretStringGenerator extends SecretStringGenerator { /** - * The JSON key name that's used to add the generated password to the JSON structure specified by the - * ``secretStringTemplate`` parameter. + * A properly structured JSON string that the generated password can be added to. The ``generateStringKey`` is + * combined with the generated random string and inserted into the JSON structure that's specified by this parameter. + * The merged JSON string is returned as the completed SecretString of the secret. If you specify ``secretStringTemplate`` + * then ``generateStringKey`` must be also be specified. */ - generateStringKey: string; + secretStringTemplate?: string; /** - * A properly structured JSON string that the generated password can be added to. The ``generateStringKey`` is - * combined with the generated random string and inserted into the JSON structure that's specified by this parameter. - * The merged JSON string is returned as the completed SecretString of the secret. + * The JSON key name that's used to add the generated password to the JSON structure specified by the + * ``secretStringTemplate`` parameter. If you specify ``generateStringKey`` then ``secretStringTemplate`` + * must be also be specified. */ - secretStringTemplate: string; + generateStringKey?: string; } class ImportedSecret extends SecretBase { diff --git a/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts b/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts index d0110dabed1c8..d8d6435ef7d59 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts @@ -22,6 +22,29 @@ export = { test.done(); }, + 'secret with generate secret string options'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new secretsmanager.Secret(stack, 'Secret', { + generateSecretString: { + excludeUppercase: true, + passwordLength: 20 + } + }); + + // THEN + expect(stack).to(haveResource('AWS::SecretsManager::Secret', { + GenerateSecretString: { + ExcludeUppercase: true, + PasswordLength: 20 + } + })); + + test.done(); + }, + 'templated secret string'(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -337,6 +360,34 @@ export = { } })); + test.done(); + }, + + 'throws when specifying secretStringTemplate but not generateStringKey'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // THEN + test.throws(() => new secretsmanager.Secret(stack, 'Secret', { + generateSecretString: { + secretStringTemplate: JSON.stringify({ username: 'username' }) + } + }), /`secretStringTemplate`.+`generateStringKey`/); + + test.done(); + }, + + 'throws when specifying generateStringKey but not secretStringTemplate'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // THEN + test.throws(() => new secretsmanager.Secret(stack, 'Secret', { + generateSecretString: { + generateStringKey: 'password' + } + }), /`secretStringTemplate`.+`generateStringKey`/); + test.done(); } }; From 92b87ffbb97d64fe1c22f04ab01d7ed51a2aa278 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Tue, 26 Mar 2019 14:22:15 +0100 Subject: [PATCH 3/4] Update secret.ts --- packages/@aws-cdk/aws-secretsmanager/lib/secret.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts index c4744476dac18..3e5061eb8afb0 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts @@ -189,7 +189,7 @@ export class Secret extends SecretBase { if (props.generateSecretString && (props.generateSecretString.secretStringTemplate || props.generateSecretString.generateStringKey) && !(props.generateSecretString.secretStringTemplate && props.generateSecretString.generateStringKey)) { - throw new Error('Both `secretStringTemplate` and `generateStringKey` must be specified.'); + throw new Error('`secretStringTemplate` and `generateStringKey` must be specified together.'); } const resource = new secretsmanager.CfnSecret(this, 'Resource', { From 41f15a0fd2b21c8796ea4ad321cb2f70dada69d5 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Tue, 26 Mar 2019 14:22:31 +0100 Subject: [PATCH 4/4] Update secret.ts --- packages/@aws-cdk/aws-secretsmanager/lib/secret.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts index 3e5061eb8afb0..bd79a0b7eadad 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts @@ -81,7 +81,7 @@ export interface SecretProps { * @default 32 characters with upper-case letters, lower-case letters, punctuation and numbers (at least one from each * category), per the default values of ``SecretStringGenerator``. */ - generateSecretString?: SecretStringGenerator + generateSecretString?: SecretStringGenerator; /** * A name for the secret. Note that deleting secrets from SecretsManager does not happen immediately, but after a 7 to