From a4afd62596b2aaec9169678bcde8593b4db60d65 Mon Sep 17 00:00:00 2001 From: Kyle Laker Date: Sat, 25 Dec 2021 19:44:10 -0500 Subject: [PATCH 1/6] feat(iam): add L2 AccessKey resource This adds an L2 resource for creating IAM access keys. Instructions for creating access keys are added to the README near the information on creating users. Tests are added (including an integration test) and locations elsewhere in the CDK where `CfnAccessKey` was used have been updated to leverage the new L2 construct (which required changes in the `secretsmanager` and `apigatewayv2-authorizers` packages). Excludes were added for two `awslint` rules. Access Keys don't support specifying physical names, so having such a property is impossible. Additionally, since the primary value of an `AWS::IAM::AccessKey` is to gain access to the `SecretAccessKey` value, a `fromXXX` static method doesn't seem to make a lot of sense (because ideally you'd just pull that from a Secret anyway if it was required in the app). --- .../test/http/integ.iam.expected.json | 6 +- .../test/http/integ.iam.ts | 8 +- packages/@aws-cdk/aws-iam/README.md | 21 +++++ packages/@aws-cdk/aws-iam/lib/access-key.ts | 91 +++++++++++++++++++ packages/@aws-cdk/aws-iam/lib/index.ts | 1 + packages/@aws-cdk/aws-iam/package.json | 2 + .../@aws-cdk/aws-iam/test/access-key.test.ts | 79 ++++++++++++++++ .../test/integ.access-key.expected.json | 22 +++++ .../@aws-cdk/aws-iam/test/integ.access-key.ts | 12 +++ .../@aws-cdk/aws-secretsmanager/lib/secret.ts | 6 +- .../test/integ.secret.lit.expected.json | 4 +- .../test/integ.secret.lit.ts | 4 +- .../aws-secretsmanager/test/secret.test.ts | 16 ++-- 13 files changed, 250 insertions(+), 22 deletions(-) create mode 100644 packages/@aws-cdk/aws-iam/lib/access-key.ts create mode 100644 packages/@aws-cdk/aws-iam/test/access-key.test.ts create mode 100644 packages/@aws-cdk/aws-iam/test/integ.access-key.expected.json create mode 100644 packages/@aws-cdk/aws-iam/test/integ.access-key.ts diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.iam.expected.json b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.iam.expected.json index ab33666949d9b..be1c18a7e49f7 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.iam.expected.json +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.iam.expected.json @@ -67,7 +67,7 @@ ] } }, - "UserAccess": { + "UserAccessEC42ADF7": { "Type": "AWS::IAM::AccessKey", "Properties": { "UserName": { @@ -184,13 +184,13 @@ }, "TESTACCESSKEYID": { "Value": { - "Ref": "UserAccess" + "Ref": "UserAccessEC42ADF7" } }, "TESTSECRETACCESSKEY": { "Value": { "Fn::GetAtt": [ - "UserAccess", + "UserAccessEC42ADF7", "SecretAccessKey" ] } diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.iam.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.iam.ts index a010e6c0b990e..87d9af75702f2 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.iam.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.iam.ts @@ -17,8 +17,8 @@ class ExampleComIntegration extends apigatewayv2.HttpRouteIntegration { const app = new cdk.App(); const stack = new cdk.Stack(app, 'IntegApiGatewayV2Iam'); const user = new iam.User(stack, 'User'); -const userAccessKey = new iam.CfnAccessKey(stack, 'UserAccess', { - userName: user.userName, +const userAccessKey = new iam.AccessKey(stack, 'UserAccess', { + user, }); const httpApi = new apigatewayv2.HttpApi(stack, 'HttpApi', { @@ -44,11 +44,11 @@ new cdk.CfnOutput(stack, 'API', { }); new cdk.CfnOutput(stack, 'TESTACCESSKEYID', { - value: userAccessKey.ref, + value: userAccessKey.accessKeyId, }); new cdk.CfnOutput(stack, 'TESTSECRETACCESSKEY', { - value: userAccessKey.attrSecretAccessKey, + value: userAccessKey.secretAccessKey, }); new cdk.CfnOutput(stack, 'TESTREGION', { diff --git a/packages/@aws-cdk/aws-iam/README.md b/packages/@aws-cdk/aws-iam/README.md index 438b54693b5b5..e03de10050294 100644 --- a/packages/@aws-cdk/aws-iam/README.md +++ b/packages/@aws-cdk/aws-iam/README.md @@ -457,6 +457,27 @@ const user = iam.User.fromUserAttributes(this, 'MyImportedUserByAttributes', { }); ``` +### Access Keys + +The ability for a user to make API calls via the CLI or an SDK is enabled by the user having an +access key pair. To create an access key: + +```ts +const user = new iam.User(this, 'MyUser'); +const accessKey = new iam.AccessKey(this, 'MyAccessKey', { user: user }); +``` + +You can force CloudFormation to rotate the access key by providing a monotonically increasing `serial` +property. Simply provide a higher serial value than any number used previously: + +```ts +const user = new iam.User(this, 'MyUser'); +const accessKey = new iam.AccessKey(this, 'MyAccessKey', { user: user, serial: 1 }); +``` + +An access key may only be associated with a single user and cannot be "moved" between users. Changing +the user associated with an access key replaces the access key (and its ID and secret value). + ## Groups An IAM user group is a collection of IAM users. User groups let you specify permissions for multiple users. diff --git a/packages/@aws-cdk/aws-iam/lib/access-key.ts b/packages/@aws-cdk/aws-iam/lib/access-key.ts new file mode 100644 index 0000000000000..362f49a073243 --- /dev/null +++ b/packages/@aws-cdk/aws-iam/lib/access-key.ts @@ -0,0 +1,91 @@ +import { IResource, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { CfnAccessKey } from './iam.generated'; +import { IUser } from './user'; + +/** + * Valid statuses for an IAM Access Key. + */ +export enum AccessKeyStatus { + /** + * An active access key. An active key can be used to make API calls. + */ + ACTIVE = 'Active', + + /** + * An inactive access key. An inactive key cannot be used to make API calls. + */ + INACTIVE = 'Inactive' +} + +/** + * Represents an IAM Access Key. + * + * @see https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html + */ +export interface IAccessKey extends IResource { + /** + * The Access Key ID. + * + * @attribute + */ + readonly accessKeyId: string; + + /** + * The Secret Access Key. + * + * @attribute + */ + readonly secretAccessKey: string; +} + +/** + * Properties for defining an IAM access key. + */ +export interface AccessKeyProps { + /** + * A CloudFormation-specific value that signifies the access key should be + * replaced/rotated. This value can only be incremented. Incrementing this + * value will cause CloudFormation to replace the Access Key resource. + * + * @default - No serial value + */ + readonly serial?: number; + + /** + * The status of the access key. An Active access key is allowed to be used + * to make API calls; An Inactive key cannot. + * + * @default - The access key is active + */ + readonly status?: AccessKeyStatus; + + /** + * The IAM user this key will belong to. + * + * Changing this value will result in the access key being deleted and a new + * access key (with a different ID and secret value) being assigned to the new + * user. + */ + readonly user: IUser; +} + +/** + * Define a new IAM Access Key. + */ +export class AccessKey extends Resource implements IAccessKey { + public readonly accessKeyId: string; + public readonly secretAccessKey: string; + + constructor(scope: Construct, id: string, props: AccessKeyProps) { + super(scope, id); + const accessKey = new CfnAccessKey(this, 'Resource', { + userName: props.user.userName, + serial: props.serial, + status: props.status, + }); + + this.accessKeyId = accessKey.ref; + this.secretAccessKey = accessKey.attrSecretAccessKey; + } +} diff --git a/packages/@aws-cdk/aws-iam/lib/index.ts b/packages/@aws-cdk/aws-iam/lib/index.ts index 06c2a9bb6cdcd..7b13245b49f39 100644 --- a/packages/@aws-cdk/aws-iam/lib/index.ts +++ b/packages/@aws-cdk/aws-iam/lib/index.ts @@ -13,6 +13,7 @@ export * from './unknown-principal'; export * from './oidc-provider'; export * from './permissions-boundary'; export * from './saml-provider'; +export * from './access-key'; // AWS::IAM CloudFormation Resources: export * from './iam.generated'; diff --git a/packages/@aws-cdk/aws-iam/package.json b/packages/@aws-cdk/aws-iam/package.json index c725e84b14edb..6e1d990eb5392 100644 --- a/packages/@aws-cdk/aws-iam/package.json +++ b/packages/@aws-cdk/aws-iam/package.json @@ -108,9 +108,11 @@ "awslint": { "exclude": [ "from-signature:@aws-cdk/aws-iam.Role.fromRoleArn", + "from-method:@aws-cdk/aws-iam.AccessKey", "construct-interface-extends-iconstruct:@aws-cdk/aws-iam.IManagedPolicy", "props-physical-name:@aws-cdk/aws-iam.OpenIdConnectProviderProps", "props-physical-name:@aws-cdk/aws-iam.SamlProviderProps", + "props-physical-name:@aws-cdk/aws-iam.AccessKeyProps", "resource-interface-extends-resource:@aws-cdk/aws-iam.IManagedPolicy", "docs-public-apis:@aws-cdk/aws-iam.IUser" ] diff --git a/packages/@aws-cdk/aws-iam/test/access-key.test.ts b/packages/@aws-cdk/aws-iam/test/access-key.test.ts new file mode 100644 index 0000000000000..fe54ffef2b159 --- /dev/null +++ b/packages/@aws-cdk/aws-iam/test/access-key.test.ts @@ -0,0 +1,79 @@ +import '@aws-cdk/assert-internal/jest'; +import { App, Stack } from '@aws-cdk/core'; +import { AccessKey, AccessKeyStatus, User } from '../lib'; + +describe('IAM Access keys', () => { + test('user name is identifed via reference', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'MyStack'); + const user = new User(stack, 'MyUser'); + + // WHEN + new AccessKey(stack, 'MyAccessKey', { user }); + + // THEN + expect(stack).toMatchTemplate({ + Resources: { + MyUserDC45028B: { + Type: 'AWS::IAM::User', + }, + MyAccessKeyF0FFBE2E: { + Type: 'AWS::IAM::AccessKey', + Properties: { + UserName: { Ref: 'MyUserDC45028B' }, + }, + }, + }, + }); + }); + + test('active status is specified with correct capitalization', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'MyStack'); + const user = new User(stack, 'MyUser'); + + // WHEN + new AccessKey(stack, 'MyAccessKey', { user, status: AccessKeyStatus.ACTIVE }); + + // THEN + expect(stack).toHaveResourceLike('AWS::IAM::AccessKey', { Status: 'Active' }); + }); + + test('inactive status is specified with correct capitalization', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'MyStack'); + const user = new User(stack, 'MyUser'); + + // WHEN + new AccessKey(stack, 'MyAccessKey', { + user, + status: AccessKeyStatus.INACTIVE, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::IAM::AccessKey', { + Status: 'Inactive', + }); + }); + + test('access key secret ', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'MyStack'); + const user = new User(stack, 'MyUser'); + + // WHEN + const accessKey = new AccessKey(stack, 'MyAccessKey', { + user, + }); + + // THEN + expect(stack.resolve(accessKey.secretAccessKey)).toStrictEqual({ + 'Fn::GetAtt': ['MyAccessKeyF0FFBE2E', 'SecretAccessKey'], + }); + }); + +}); diff --git a/packages/@aws-cdk/aws-iam/test/integ.access-key.expected.json b/packages/@aws-cdk/aws-iam/test/integ.access-key.expected.json new file mode 100644 index 0000000000000..d0d33a2ebefb3 --- /dev/null +++ b/packages/@aws-cdk/aws-iam/test/integ.access-key.expected.json @@ -0,0 +1,22 @@ +{ + "Resources": { + "TestUser6A619381": { + "Type": "AWS::IAM::User" + }, + "TestAccessKey4BFC5CF5": { + "Type": "AWS::IAM::AccessKey", + "Properties": { + "UserName": { + "Ref": "TestUser6A619381" + } + } + } + }, + "Outputs": { + "AccessKeyId": { + "Value": { + "Ref": "TestAccessKey4BFC5CF5" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/test/integ.access-key.ts b/packages/@aws-cdk/aws-iam/test/integ.access-key.ts new file mode 100644 index 0000000000000..65d229ed2b500 --- /dev/null +++ b/packages/@aws-cdk/aws-iam/test/integ.access-key.ts @@ -0,0 +1,12 @@ +import { App, CfnOutput, Stack } from '@aws-cdk/core'; +import { AccessKey, User } from '../lib'; + +const app = new App(); +const stack = new Stack(app, 'integ-iam-access-key-1'); + +const user = new User(stack, 'TestUser'); +const accessKey = new AccessKey(stack, 'TestAccessKey', { user }); + +new CfnOutput(stack, 'AccessKeyId', { value: accessKey.accessKeyId }); + +app.synth(); diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts index 5485a812f9120..b5587a770587a 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts @@ -205,8 +205,8 @@ export class SecretStringValueBeta1 { * ```ts * // Creates a new IAM user, access and secret keys, and stores the secret access key in a Secret. * const user = new iam.User(this, 'User'); - * const accessKey = new iam.CfnAccessKey(this, 'AccessKey', { userName: user.userName }); - * const secretValue = secretsmanager.SecretStringValueBeta1.fromToken(accessKey.attrSecretAccessKey); + * const accessKey = new iam.AccessKey(this, 'AccessKey', { user }); + * const secretValue = secretsmanager.SecretStringValueBeta1.fromToken(accessKey.secretAccessKey); * new secretsmanager.Secret(this, 'Secret', { * secretStringBeta1: secretValue, * }); @@ -216,7 +216,7 @@ export class SecretStringValueBeta1 { * const secretValue = secretsmanager.SecretStringValueBeta1.fromToken(JSON.stringify({ * username: user.userName, * database: 'foo', - * password: accessKey.attrSecretAccessKey + * password: accessKey.secretAccessKey * })); * * Note that the value being a Token does *not* guarantee safety. For example, a Lazy-evaluated string 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 1ae60ce0e2437..e72f363ac687f 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 @@ -127,7 +127,7 @@ } } }, - "AccessKey": { + "AccessKeyE6B25659": { "Type": "AWS::IAM::AccessKey", "Properties": { "UserName": { @@ -140,7 +140,7 @@ "Properties": { "SecretString": { "Fn::GetAtt": [ - "AccessKey", + "AccessKeyE6B25659", "SecretAccessKey" ] } 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 f86acbabc12e2..b45e9e2d05c98 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.ts +++ b/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.ts @@ -31,9 +31,9 @@ class SecretsManagerStack extends cdk.Stack { }); // Secret with predefined value - const accessKey = new iam.CfnAccessKey(this, 'AccessKey', { userName: user.userName }); + const accessKey = new iam.AccessKey(this, 'AccessKey', { user }); new secretsmanager.Secret(this, 'PredefinedSecret', { - secretStringBeta1: secretsmanager.SecretStringValueBeta1.fromToken(accessKey.attrSecretAccessKey), + secretStringBeta1: secretsmanager.SecretStringValueBeta1.fromToken(accessKey.secretAccessKey), }); /// !hide } diff --git a/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts b/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts index 365374e84bdd4..01ee219c9b0fd 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts +++ b/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts @@ -180,11 +180,11 @@ test('templated secret string', () => { describe('secretStringBeta1', () => { let user: iam.User; - let accessKey: iam.CfnAccessKey; + let accessKey: iam.AccessKey; beforeEach(() => { user = new iam.User(stack, 'User'); - accessKey = new iam.CfnAccessKey(stack, 'MyKey', { userName: user.userName }); + accessKey = new iam.AccessKey(stack, 'MyKey', { user }); }); test('fromUnsafePlaintext allows specifying a plaintext string', () => { @@ -206,18 +206,18 @@ describe('secretStringBeta1', () => { test('toToken allows referencing a construct attribute', () => { new secretsmanager.Secret(stack, 'Secret', { - secretStringBeta1: secretsmanager.SecretStringValueBeta1.fromToken(accessKey.attrSecretAccessKey), + secretStringBeta1: secretsmanager.SecretStringValueBeta1.fromToken(accessKey.secretAccessKey), }); expect(stack).toHaveResource('AWS::SecretsManager::Secret', { GenerateSecretString: ABSENT, - SecretString: { 'Fn::GetAtt': ['MyKey', 'SecretAccessKey'] }, + SecretString: { 'Fn::GetAtt': ['MyKey6AB29FA6', 'SecretAccessKey'] }, }); }); test('toToken allows referencing a construct attribute in nested JSON', () => { const secretString = secretsmanager.SecretStringValueBeta1.fromToken(JSON.stringify({ - key: accessKey.attrSecretAccessKey, + key: accessKey.secretAccessKey, username: 'myUser', })); new secretsmanager.Secret(stack, 'Secret', { @@ -233,7 +233,7 @@ describe('secretStringBeta1', () => { '{"key":"', { 'Fn::GetAtt': [ - 'MyKey', + 'MyKey6AB29FA6', 'SecretAccessKey', ], }, @@ -248,7 +248,7 @@ describe('secretStringBeta1', () => { // NOTE - This is actually not desired behavior, but the simple `!Token.isUnresolved` // check is the simplest and most consistent to implement. Covering this edge case of // a resolved Token representing a Ref/Fn::GetAtt is out of scope for this initial pass. - const secretKey = stack.resolve(accessKey.attrSecretAccessKey); + const secretKey = stack.resolve(accessKey.secretAccessKey); expect(() => new secretsmanager.Secret(stack, 'Secret', { secretStringBeta1: secretsmanager.SecretStringValueBeta1.fromToken(secretKey), })).toThrow(/appears to be plaintext/); @@ -260,7 +260,7 @@ describe('secretStringBeta1', () => { generateStringKey: 'username', secretStringTemplate: JSON.stringify({ username: 'username' }), }, - secretStringBeta1: secretsmanager.SecretStringValueBeta1.fromToken(accessKey.attrSecretAccessKey), + secretStringBeta1: secretsmanager.SecretStringValueBeta1.fromToken(accessKey.secretAccessKey), })).toThrow(/Cannot specify both `generateSecretString` and `secretStringBeta1`./); }); From a6e7b9febd7703f17a148c638c9f337b85fc8540 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 5 Jan 2022 15:27:56 +0100 Subject: [PATCH 2/6] Type `secretAccessKey` as `SecretValue` This doesn't achieve a lot by itself just yet, but it will if we change `SecretValue` a little. --- packages/@aws-cdk/aws-iam/lib/access-key.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-iam/lib/access-key.ts b/packages/@aws-cdk/aws-iam/lib/access-key.ts index 362f49a073243..259e46c67a572 100644 --- a/packages/@aws-cdk/aws-iam/lib/access-key.ts +++ b/packages/@aws-cdk/aws-iam/lib/access-key.ts @@ -1,4 +1,4 @@ -import { IResource, Resource } from '@aws-cdk/core'; +import { IResource, Resource, SecretValue } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnAccessKey } from './iam.generated'; import { IUser } from './user'; @@ -36,7 +36,7 @@ export interface IAccessKey extends IResource { * * @attribute */ - readonly secretAccessKey: string; + readonly secretAccessKey: SecretValue; } /** @@ -75,7 +75,7 @@ export interface AccessKeyProps { */ export class AccessKey extends Resource implements IAccessKey { public readonly accessKeyId: string; - public readonly secretAccessKey: string; + public readonly secretAccessKey: SecretValue; constructor(scope: Construct, id: string, props: AccessKeyProps) { super(scope, id); @@ -86,6 +86,8 @@ export class AccessKey extends Resource implements IAccessKey { }); this.accessKeyId = accessKey.ref; - this.secretAccessKey = accessKey.attrSecretAccessKey; + + // Not actually 'plainText', but until we have a more apt constructor + this.secretAccessKey = SecretValue.plainText(accessKey.attrSecretAccessKey); } } From 37e0357bce5fdaae2a3e18ac7521dbab8ecdc628 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 10 Jan 2022 16:29:22 +0100 Subject: [PATCH 3/6] Update examples --- packages/@aws-cdk/aws-secretsmanager/lib/secret.ts | 2 +- packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts index b5587a770587a..4bf6059a1f634 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts @@ -206,7 +206,7 @@ export class SecretStringValueBeta1 { * // Creates a new IAM user, access and secret keys, and stores the secret access key in a Secret. * const user = new iam.User(this, 'User'); * const accessKey = new iam.AccessKey(this, 'AccessKey', { user }); - * const secretValue = secretsmanager.SecretStringValueBeta1.fromToken(accessKey.secretAccessKey); + * const secretValue = secretsmanager.SecretStringValueBeta1.fromToken(accessKey.secretAccessKey.toString()); * new secretsmanager.Secret(this, 'Secret', { * secretStringBeta1: secretValue, * }); 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 b45e9e2d05c98..7d63f61dbb34a 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.ts +++ b/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.ts @@ -33,7 +33,7 @@ class SecretsManagerStack extends cdk.Stack { // Secret with predefined value const accessKey = new iam.AccessKey(this, 'AccessKey', { user }); new secretsmanager.Secret(this, 'PredefinedSecret', { - secretStringBeta1: secretsmanager.SecretStringValueBeta1.fromToken(accessKey.secretAccessKey), + secretStringBeta1: secretsmanager.SecretStringValueBeta1.fromToken(accessKey.secretAccessKey.toString()), }); /// !hide } From 580cc9ea2799058247911b1e29f26f50107c057c Mon Sep 17 00:00:00 2001 From: Kyle Laker Date: Mon, 10 Jan 2022 11:27:42 -0500 Subject: [PATCH 4/6] Update `fromToken` calls in tests `toString()` needs to be called on the `SecretValue` because it requires a string argument. And the same needs to be done in with JSON, otherwise the `toJSON` in `Intrinsic` seems to be used and that does not result in a token being returned. --- packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts b/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts index 01ee219c9b0fd..66ec71c497474 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts +++ b/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts @@ -206,7 +206,7 @@ describe('secretStringBeta1', () => { test('toToken allows referencing a construct attribute', () => { new secretsmanager.Secret(stack, 'Secret', { - secretStringBeta1: secretsmanager.SecretStringValueBeta1.fromToken(accessKey.secretAccessKey), + secretStringBeta1: secretsmanager.SecretStringValueBeta1.fromToken(accessKey.secretAccessKey.toString()), }); expect(stack).toHaveResource('AWS::SecretsManager::Secret', { @@ -217,7 +217,7 @@ describe('secretStringBeta1', () => { test('toToken allows referencing a construct attribute in nested JSON', () => { const secretString = secretsmanager.SecretStringValueBeta1.fromToken(JSON.stringify({ - key: accessKey.secretAccessKey, + key: accessKey.secretAccessKey.toString(), username: 'myUser', })); new secretsmanager.Secret(stack, 'Secret', { @@ -260,7 +260,7 @@ describe('secretStringBeta1', () => { generateStringKey: 'username', secretStringTemplate: JSON.stringify({ username: 'username' }), }, - secretStringBeta1: secretsmanager.SecretStringValueBeta1.fromToken(accessKey.secretAccessKey), + secretStringBeta1: secretsmanager.SecretStringValueBeta1.fromToken(accessKey.secretAccessKey.toString()), })).toThrow(/Cannot specify both `generateSecretString` and `secretStringBeta1`./); }); From b8bd8155221b77e688716a7c01a5acdd37cca553 Mon Sep 17 00:00:00 2001 From: Kyle Laker Date: Mon, 10 Jan 2022 13:05:29 -0500 Subject: [PATCH 5/6] Update example It seems `.toString()` needs to be called on `SecretValue`s being `JSON.stringify`-ed. --- 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 4bf6059a1f634..cc51f4b009d26 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts @@ -216,7 +216,7 @@ export class SecretStringValueBeta1 { * const secretValue = secretsmanager.SecretStringValueBeta1.fromToken(JSON.stringify({ * username: user.userName, * database: 'foo', - * password: accessKey.secretAccessKey + * password: accessKey.secretAccessKey.toString(), * })); * * Note that the value being a Token does *not* guarantee safety. For example, a Lazy-evaluated string From fedd493c95ddcfc7ed1e3182f59eef7dbbb30157 Mon Sep 17 00:00:00 2001 From: Kyle Laker Date: Mon, 10 Jan 2022 13:06:25 -0500 Subject: [PATCH 6/6] Update API Gateway authorizer tests This adds the new required call to `.toString()` to retrieve the value. --- .../aws-apigatewayv2-authorizers/test/http/integ.iam.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.iam.ts b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.iam.ts index 87d9af75702f2..6ae3c42bc8421 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.iam.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/integ.iam.ts @@ -48,7 +48,7 @@ new cdk.CfnOutput(stack, 'TESTACCESSKEYID', { }); new cdk.CfnOutput(stack, 'TESTSECRETACCESSKEY', { - value: userAccessKey.secretAccessKey, + value: userAccessKey.secretAccessKey.toString(), }); new cdk.CfnOutput(stack, 'TESTREGION', {