Skip to content

Commit 9af36af

Browse files
authored
feat(ssm): allow referencing "latest" version of SSM parameter (#1768)
There are many requests from people to integrate with SSM parameter store in same way, and in particular to get the latest version of a parameter. The mechanisms to get a specific version or the latest version at deployment time are very different, but both are now supported by and hidden in the ssm.ParameterStoreString class. Make the naming around properties that return a (potentially tokenized) value consistent. All properties of objects that return a string value are `stringValue`, all properties of objects that return a list value are `stringListValue`. Fixes #1587. BREAKING CHANGE: Rename `parameter.valueAsString` => `parameter.stringValue`, rename `parameter.valueAsList` => `parameter.stringListValue`, rename `ssmParameter.parameterValue` => `ssmParameter.stringValue` or `ssmParameter.stringListValue` depending on type, rename `secretString.value` => `secretString.stringValue`, rename `secret.toSecretString()` =>`secret.secretString`
1 parent 42876e7 commit 9af36af

23 files changed

+299
-85
lines changed

packages/@aws-cdk/assets-docker/lib/image-asset.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export class DockerImageAsset extends cdk.Construct {
6161
this.node.addMetadata(cxapi.ASSET_METADATA, asset);
6262

6363
// parse repository name and tag from the parameter (<REPO_NAME>:<TAG>)
64-
const components = cdk.Fn.split(':', imageNameParameter.valueAsString);
64+
const components = cdk.Fn.split(':', imageNameParameter.stringValue);
6565
const repositoryName = cdk.Fn.select(0, components).toString();
6666
const imageTag = cdk.Fn.select(1, components).toString();
6767

packages/@aws-cdk/assets/lib/asset.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,9 @@ export class Asset extends cdk.Construct {
109109
description: `S3 key for asset version "${this.node.path}"`
110110
});
111111

112-
this.s3BucketName = bucketParam.value.toString();
113-
this.s3Prefix = cdk.Fn.select(0, cdk.Fn.split(cxapi.ASSET_PREFIX_SEPARATOR, keyParam.valueAsString)).toString();
114-
const s3Filename = cdk.Fn.select(1, cdk.Fn.split(cxapi.ASSET_PREFIX_SEPARATOR, keyParam.valueAsString)).toString();
112+
this.s3BucketName = bucketParam.stringValue;
113+
this.s3Prefix = cdk.Fn.select(0, cdk.Fn.split(cxapi.ASSET_PREFIX_SEPARATOR, keyParam.stringValue)).toString();
114+
const s3Filename = cdk.Fn.select(1, cdk.Fn.split(cxapi.ASSET_PREFIX_SEPARATOR, keyParam.stringValue)).toString();
115115
this.s3ObjectKey = `${this.s3Prefix}${s3Filename}`;
116116

117117
this.bucket = s3.Bucket.import(this, 'AssetBucket', {

packages/@aws-cdk/aws-secretsmanager/README.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@ const secretsmanager = require('@aws-cdk/aws-secretsmanager');
55
```
66

77
### Create a new Secret in a Stack
8-
9-
In order to have SecretsManager generate a new secret value automatically, you can get started with the following:
8+
In order to have SecretsManager generate a new secret value automatically,
9+
you can get started with the following:
1010

1111
[example of creating a secret](test/integ.secret.lit.ts)
1212

13-
The `Secret` construct does not allow specifying the `SecretString` property of the `AWS::SecretsManager::Secret`
14-
resource as this will almost always lead to the secret being surfaced in plain text and possibly committed to your
15-
source control. If you need to use a pre-existing secret, the recommended way is to manually provision
16-
the secret in *AWS SecretsManager* and use the `Secret.import` method to make it available in your CDK Application:
13+
The `Secret` construct does not allow specifying the `SecretString` property
14+
of the `AWS::SecretsManager::Secret` resource (as this will almost always
15+
lead to the secret being surfaced in plain text and possibly committed to
16+
your source control).
17+
18+
If you need to use a pre-existing secret, the recommended way is to manually
19+
provision the secret in *AWS SecretsManager* and use the `Secret.import`
20+
method to make it available in your CDK Application:
1721

1822
```ts
1923
const secret = Secret.import(scope, 'ImportedSecret', {
@@ -22,3 +26,6 @@ const secret = Secret.import(scope, 'ImportedSecret', {
2226
encryptionKey,
2327
});
2428
```
29+
30+
SecretsManager secret values can only be used in select set of properties. For the
31+
list of properties, see [the CloudFormation Dynamic References documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.htm).

packages/@aws-cdk/aws-secretsmanager/lib/secret-string.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export class SecretString extends cdk.DynamicReference {
4444
/**
4545
* Return the full value of the secret
4646
*/
47-
public get value(): string {
47+
public get stringValue(): string {
4848
return this.resolveStringForJsonKey('');
4949
}
5050

packages/@aws-cdk/aws-secretsmanager/lib/secret.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,21 @@ export interface ISecret extends cdk.IConstruct {
2020
readonly secretArn: string;
2121

2222
/**
23-
* Returns a SecretString corresponding to this secret, so that the secret value can be referred to from other parts
24-
* of the application (such as an RDS instance's master user password property).
23+
* Returns a SecretString corresponding to this secret.
24+
*
25+
* SecretString represents the value of the Secret.
26+
*/
27+
readonly secretString: SecretString;
28+
29+
/**
30+
* Retrieve the value of the Secret, as a string.
2531
*/
26-
toSecretString(): SecretString;
32+
readonly stringValue: string;
33+
34+
/**
35+
* Interpret the secret as a JSON object and return a field's value from it
36+
*/
37+
jsonFieldValue(key: string): string;
2738

2839
/**
2940
* Exports this secret.
@@ -97,7 +108,7 @@ export abstract class SecretBase extends cdk.Construct implements ISecret {
97108
public abstract readonly encryptionKey?: kms.IEncryptionKey;
98109
public abstract readonly secretArn: string;
99110

100-
private secretString?: SecretString;
111+
private _secretString?: SecretString;
101112

102113
public abstract export(): SecretImportProps;
103114

@@ -127,9 +138,17 @@ export abstract class SecretBase extends cdk.Construct implements ISecret {
127138
}
128139
}
129140

130-
public toSecretString() {
131-
this.secretString = this.secretString || new SecretString(this, 'SecretString', { secretId: this.secretArn });
132-
return this.secretString;
141+
public get secretString() {
142+
this._secretString = this._secretString || new SecretString(this, 'SecretString', { secretId: this.secretArn });
143+
return this._secretString;
144+
}
145+
146+
public get stringValue() {
147+
return this.secretString.stringValue;
148+
}
149+
150+
public jsonFieldValue(key: string): string {
151+
return this.secretString.jsonFieldValue(key);
133152
}
134153
}
135154

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
1+
import iam = require('@aws-cdk/aws-iam');
12
import cdk = require('@aws-cdk/cdk');
23
import secretsmanager = require('../lib');
34

4-
const app = new cdk.App();
5-
const stack = new cdk.Stack(app, 'aws-cdk-rds-integ');
5+
class ExampleStack extends cdk.Stack {
6+
constructor(scope: cdk.App, id: string) {
7+
super(scope, id);
8+
9+
/// !show
10+
const loginSecret = new secretsmanager.SecretString(this, 'Secret', {
11+
secretId: 'SomeLogin'
12+
});
613

7-
/// !show
8-
const loginSecret = new secretsmanager.SecretString(stack, 'Secret', { secretId: 'SomeLogin', });
14+
new iam.User(this, 'User', {
15+
// Get the 'password' field from the secret that looks like
16+
// { "username": "XXXX", "password": "YYYY" }
17+
password: loginSecret.jsonFieldValue('password')
18+
});
19+
/// !hide
920

10-
// DO NOT ACTUALLY DO THIS, as this will expose your secret.
11-
// This code only exists to show how the secret would be used.
12-
new cdk.Output(stack, 'SecretUsername', { value: loginSecret.jsonFieldValue('username') });
13-
new cdk.Output(stack, 'SecretPassword', { value: loginSecret.jsonFieldValue('password') });
14-
/// !hide
21+
}
22+
}
1523

24+
const app = new cdk.App();
25+
new ExampleStack(app, 'aws-cdk-secret-integ');
1626
app.run();

packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.expected.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,25 @@
6060
"Properties": {
6161
"GenerateSecretString": {}
6262
}
63+
},
64+
"User00B015A1": {
65+
"Type": "AWS::IAM::User",
66+
"Properties": {
67+
"LoginProfile": {
68+
"Password": {
69+
"Fn::Join": [
70+
"",
71+
[
72+
"{{resolve:secretsmanager:",
73+
{
74+
"Ref": "SecretA720EF05"
75+
},
76+
":SecretString:::}}"
77+
]
78+
]
79+
}
80+
}
81+
}
6382
}
6483
}
6584
}

packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,23 @@ import iam = require('@aws-cdk/aws-iam');
22
import cdk = require('@aws-cdk/cdk');
33
import secretsManager = require('../lib');
44

5-
const app = new cdk.App();
6-
const stack = new cdk.Stack(app, 'Integ-SecretsManager-Secret');
7-
const role = new iam.Role(stack, 'TestRole', { assumedBy: new iam.AccountRootPrincipal() });
5+
class SecretsManagerStack extends cdk.Stack {
6+
constructor(scope: cdk.App, id: string) {
7+
super(scope, id);
8+
9+
const role = new iam.Role(this, 'TestRole', { assumedBy: new iam.AccountRootPrincipal() });
810

9-
/// !show
10-
const secret = new secretsManager.Secret(stack, 'Secret');
11-
secret.grantRead(role);
12-
/// !hide
11+
/// !show
12+
const secret = new secretsManager.Secret(this, 'Secret');
13+
secret.grantRead(role);
1314

15+
new iam.User(this, 'User', {
16+
password: secret.stringValue
17+
});
18+
/// !hide
19+
}
20+
}
21+
22+
const app = new cdk.App();
23+
new SecretsManagerStack(app, 'Integ-SecretsManager-Secret');
1424
app.run();

packages/@aws-cdk/aws-secretsmanager/test/test.secret-string.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export = {
1313
});
1414

1515
// THEN
16-
test.equal(ref.node.resolve(ref.value), '{{resolve:secretsmanager:SomeSecret:SecretString:::}}');
16+
test.equal(ref.node.resolve(ref.stringValue), '{{resolve:secretsmanager:SomeSecret:SecretString:::}}');
1717

1818
test.done();
1919
},

packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ export = {
224224
new cdk.Resource(stack, 'FakeResource', {
225225
type: 'CDK::Phony::Resource',
226226
properties: {
227-
value: secret.toSecretString().value
227+
value: secret.stringValue
228228
}
229229
});
230230

0 commit comments

Comments
 (0)