diff --git a/docs/DESIGN_GUIDELINES.md b/docs/DESIGN_GUIDELINES.md index 06de3ba407792..7f77bec08c10b 100644 --- a/docs/DESIGN_GUIDELINES.md +++ b/docs/DESIGN_GUIDELINES.md @@ -754,10 +754,10 @@ interface IFoo extends IConstruct { class Foo extends Construct implements IFoo { public bar() { } - /** @mutating */ + @config public goo() { } - public mutateMe() { } // ERROR! missing "@mutating" or missing on IFoo + public mutateMe() { } // ERROR! missing "@config" or missing on IFoo } ``` diff --git a/packages/@aws-cdk/aws-batch/README.md b/packages/@aws-cdk/aws-batch/README.md index 67d31ea468b41..272c66d723092 100644 --- a/packages/@aws-cdk/aws-batch/README.md +++ b/packages/@aws-cdk/aws-batch/README.md @@ -300,6 +300,23 @@ new batch.JobDefinition(this, 'job-def', { }); ``` +### Using the secret on secrets manager + +You can set the environment variables from secrets manager. + +```ts +const dbSecret = new secretsmanager.Secret(this, 'secret'); + +new batch.JobDefinition(this, 'batch-job-def-secrets', { + container: { + image: ecs.EcrImage.fromRegistry('docker/whalesay'), + secrets: { + PASSWORD: ecs.Secret.fromSecretsManager(dbSecret, 'password'), + }, + }, +}); +``` + ### Importing an existing Job Definition #### From ARN diff --git a/packages/@aws-cdk/aws-batch/lib/job-definition.ts b/packages/@aws-cdk/aws-batch/lib/job-definition.ts index 025dea4516252..99c28fa03d5c6 100644 --- a/packages/@aws-cdk/aws-batch/lib/job-definition.ts +++ b/packages/@aws-cdk/aws-batch/lib/job-definition.ts @@ -112,6 +112,13 @@ export interface JobDefinitionContainer { */ readonly environment?: { [key: string]: string }; + /** + * The environment variables from secrets manager or ssm parameter store + * + * @default none + */ + readonly secrets?: { [key: string]: ecs.Secret }; + /** * The image used to start a container. */ @@ -453,6 +460,14 @@ export class JobDefinition extends Resource implements IJobDefinition { platformCapabilities: props.platformCapabilities ?? [PlatformCapabilities.EC2], }); + // add read secrets permission to execution role + if ( props.container.secrets && props.container.executionRole ) { + const executionRole = props.container.executionRole; + Object.values(props.container.secrets).forEach((secret) => { + secret.grantRead(executionRole); + }); + } + this.jobDefinitionArn = this.getResourceArnAttribute(jobDef.ref, { service: 'batch', resource: 'job-definition', @@ -507,6 +522,14 @@ export class JobDefinition extends Resource implements IJobDefinition { return { command: container.command, environment: this.deserializeEnvVariables(container.environment), + secrets: container.secrets + ? Object.entries(container.secrets).map(([key, value]) => { + return { + name: key, + valueFrom: value.arn, + }; + }) + : undefined, image: this.imageConfig.imageName, instanceType: container.instanceType && container.instanceType.toString(), jobRoleArn: container.jobRole && container.jobRole.roleArn, diff --git a/packages/@aws-cdk/aws-batch/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-batch/rosetta/default.ts-fixture index 6fcfe8682a3ff..76bb82380fc06 100644 --- a/packages/@aws-cdk/aws-batch/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/aws-batch/rosetta/default.ts-fixture @@ -4,6 +4,7 @@ import { Stack } from '@aws-cdk/core'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as batch from '@aws-cdk/aws-batch'; import * as ecs from '@aws-cdk/aws-ecs'; +import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; class Fixture extends Stack { constructor(scope: Construct, id: string) { diff --git a/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/batch-stack.assets.json b/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/batch-stack.assets.json new file mode 100644 index 0000000000000..10c7b228276fe --- /dev/null +++ b/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/batch-stack.assets.json @@ -0,0 +1,19 @@ +{ + "version": "20.0.0", + "files": { + "d3685c79f9ec67f5dd6fda839a136b079f201b3d72695fe0ea3b3788c3471cc8": { + "source": { + "path": "batch-stack.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "d3685c79f9ec67f5dd6fda839a136b079f201b3d72695fe0ea3b3788c3471cc8.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/batch-stack.template.json b/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/batch-stack.template.json index c7a0b0b5c83aa..6ee6d41362455 100644 --- a/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/batch-stack.template.json +++ b/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/batch-stack.template.json @@ -1365,6 +1365,14 @@ "UpdateReplacePolicy": "Retain", "DeletionPolicy": "Retain" }, + "batchsecret7CD5E4C6": { + "Type": "AWS::SecretsManager::Secret", + "Properties": { + "GenerateSecretString": {} + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "batchjobdeffromecrE0E30DAD": { "Type": "AWS::Batch::JobDefinition", "Properties": { @@ -1486,6 +1494,32 @@ } } }, + "executionroleDefaultPolicy497F11A3": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "secretsmanager:DescribeSecret", + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": { + "Ref": "batchsecret7CD5E4C6" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "executionroleDefaultPolicy497F11A3", + "Roles": [ + { + "Ref": "executionroleD9A39BE6" + } + ] + } + }, "batchjobdeffargate7FE30059": { "Type": "AWS::Batch::JobDefinition", "Properties": { @@ -1509,6 +1543,14 @@ "Type": "MEMORY", "Value": "512" } + ], + "Secrets": [ + { + "Name": "SECRET", + "ValueFrom": { + "Ref": "batchsecret7CD5E4C6" + } + } ] }, "PlatformCapabilities": [ diff --git a/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/cdk.out index 90bef2e09ad39..588d7b269d34f 100644 --- a/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/cdk.out @@ -1 +1 @@ -{"version":"17.0.0"} \ No newline at end of file +{"version":"20.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/integ.json b/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/integ.json index 307a072859518..25186aa4394c3 100644 --- a/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/integ.json +++ b/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/integ.json @@ -1,7 +1,7 @@ { - "version": "18.0.0", + "version": "20.0.0", "testCases": { - "aws-batch/test/integ.batch": { + "integ.batch": { "stacks": [ "batch-stack" ], diff --git a/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/manifest.json index 6210ed0c39e78..4d08217715b57 100644 --- a/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "17.0.0", + "version": "20.0.0", "artifacts": { "Tree": { "type": "cdk:tree", @@ -285,6 +285,12 @@ "data": "batchjobrepo4C508C51" } ], + "/batch-stack/batch-secret/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "batchsecret7CD5E4C6" + } + ], "/batch-stack/batch-job-def-from-ecr/Resource": [ { "type": "aws:cdk:logicalId", @@ -303,6 +309,12 @@ "data": "executionroleD9A39BE6" } ], + "/batch-stack/execution-role/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "executionroleDefaultPolicy497F11A3" + } + ], "/batch-stack/batch-job-def-fargate/Resource": [ { "type": "aws:cdk:logicalId", diff --git a/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/tree.json b/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/tree.json index 75308ccef8ff4..5c7381ec38e8b 100644 --- a/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-batch/test/batch.integ.snapshot/tree.json @@ -8,8 +8,8 @@ "id": "Tree", "path": "Tree", "constructInfo": { - "fqn": "@aws-cdk/core.Construct", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.1.33" } }, "batch-stack": { @@ -1614,6 +1614,30 @@ "version": "0.0.0" } }, + "batch-secret": { + "id": "batch-secret", + "path": "batch-stack/batch-secret", + "children": { + "Resource": { + "id": "Resource", + "path": "batch-stack/batch-secret/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::SecretsManager::Secret", + "aws:cdk:cloudformation:props": { + "generateSecretString": {} + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-secretsmanager.CfnSecret", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-secretsmanager.Secret", + "version": "0.0.0" + } + }, "batch-job-def-from-ecr": { "id": "batch-job-def-from-ecr", "path": "batch-stack/batch-job-def-from-ecr", @@ -1814,6 +1838,50 @@ "fqn": "@aws-cdk/aws-iam.CfnRole", "version": "0.0.0" } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "batch-stack/execution-role/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "batch-stack/execution-role/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": [ + "secretsmanager:DescribeSecret", + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": { + "Ref": "batchsecret7CD5E4C6" + } + } + ], + "Version": "2012-10-17" + }, + "policyName": "executionroleDefaultPolicy497F11A3", + "roles": [ + { + "Ref": "executionroleD9A39BE6" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Policy", + "version": "0.0.0" + } } }, "constructInfo": { @@ -1849,6 +1917,14 @@ "aws:cdk:cloudformation:props": { "type": "container", "containerProperties": { + "secrets": [ + { + "name": "SECRET", + "valueFrom": { + "Ref": "batchsecret7CD5E4C6" + } + } + ], "image": "docker/whalesay", "executionRoleArn": { "Fn::GetAtt": [ diff --git a/packages/@aws-cdk/aws-batch/test/integ.batch.ts b/packages/@aws-cdk/aws-batch/test/integ.batch.ts index 4430cda4a7bf3..6de9a121b9b6d 100644 --- a/packages/@aws-cdk/aws-batch/test/integ.batch.ts +++ b/packages/@aws-cdk/aws-batch/test/integ.batch.ts @@ -2,6 +2,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecr from '@aws-cdk/aws-ecr'; import * as ecs from '@aws-cdk/aws-ecs'; import * as iam from '@aws-cdk/aws-iam'; +import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; import * as cdk from '@aws-cdk/core'; import * as batch from '../lib/'; @@ -93,6 +94,7 @@ new batch.JobQueue(stack, 'batch-job-fargate-queue', { }); const repo = new ecr.Repository(stack, 'batch-job-repo'); +const secret = new secretsmanager.Secret(stack, 'batch-secret'); new batch.JobDefinition(stack, 'batch-job-def-from-ecr', { container: { @@ -115,5 +117,8 @@ new batch.JobDefinition(stack, 'batch-job-def-fargate', { container: { image: ecs.ContainerImage.fromRegistry('docker/whalesay'), executionRole, + secrets: { + SECRET: ecs.Secret.fromSecretsManager(secret), + }, }, }); diff --git a/packages/@aws-cdk/aws-batch/test/job-definition.test.ts b/packages/@aws-cdk/aws-batch/test/job-definition.test.ts index 13926b6b80788..addaa5447f6ec 100644 --- a/packages/@aws-cdk/aws-batch/test/job-definition.test.ts +++ b/packages/@aws-cdk/aws-batch/test/job-definition.test.ts @@ -1,5 +1,5 @@ import { throws } from 'assert'; -import { Template } from '@aws-cdk/assertions'; +import { Match, Template } from '@aws-cdk/assertions'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecr from '@aws-cdk/aws-ecr'; import * as ecs from '@aws-cdk/aws-ecs'; @@ -31,6 +31,12 @@ describe('Batch Job Definition', () => { options: { 'awslogs-region': 'us-east-1' }, }; + const secret = new secretsmanager.Secret(stack, 'test-secret'); + const parameter = ssm.StringParameter.fromSecureStringParameterAttributes(stack, 'test-parameter', { + parameterName: '/name', + version: 1, + }); + jobDefProps = { jobDefinitionName: 'test-job', container: { @@ -38,6 +44,10 @@ describe('Batch Job Definition', () => { environment: { foo: 'bar', }, + secrets: { + SECRET: ecs.Secret.fromSecretsManager(secret), + PARAMETER: ecs.Secret.fromSsmParameter(parameter), + }, jobRole: role, gpuCount: 1, image: ecs.EcrImage.fromRegistry('docker/whalesay'), @@ -82,6 +92,37 @@ describe('Batch Job Definition', () => { Value: 'bar', }, ], + Secrets: [ + { + Name: 'SECRET', + ValueFrom: { + Ref: Match.stringLikeRegexp('^testsecret[0-9A-Z]{8}$'), + }, + }, + { + Name: 'PARAMETER', + ValueFrom: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ssm:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':parameter/name', + ], + ], + }, + }, + ], InstanceType: jobDefProps.container.instanceType ? jobDefProps.container.instanceType.toString() : '', LinuxParameters: {}, LogConfiguration: { @@ -144,6 +185,37 @@ describe('Batch Job Definition', () => { Value: 'bar', }, ], + Secrets: [ + { + Name: 'SECRET', + ValueFrom: { + Ref: Match.stringLikeRegexp('^testsecret[0-9A-Z]{8}$'), + }, + }, + { + Name: 'PARAMETER', + ValueFrom: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ssm:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':parameter/name', + ], + ], + }, + }, + ], ExecutionRoleArn: { 'Fn::GetAtt': [ 'executionroleD9A39BE6', diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/github/source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/github/source-action.ts index 33e3ced8df351..f1b703351e1c0 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/github/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/github/source-action.ts @@ -67,6 +67,9 @@ export interface GitHubSourceActionProps extends codepipeline.CommonActionProps * const oauth = cdk.SecretValue.secretsManager('my-github-token'); * new GitHubSource(this, 'GitHubAction', { oauthToken: oauth, ... }); * + * If you rotate the value in the Secret, you must also change at least one property + * of the CodePipeline to force CloudFormation to re-read the secret. + * * The GitHub Personal Access Token should have these scopes: * * * **repo** - to read the repository diff --git a/packages/@aws-cdk/aws-opensearchservice/README.md b/packages/@aws-cdk/aws-opensearchservice/README.md index db737d1b84741..65cf275025354 100644 --- a/packages/@aws-cdk/aws-opensearchservice/README.md +++ b/packages/@aws-cdk/aws-opensearchservice/README.md @@ -97,6 +97,8 @@ const slr = new iam.CfnServiceLinkedRole(this, 'Service Linked Role', { ## Importing existing domains +### Using a known domain endpoint + To import an existing domain into your CDK application, use the `Domain.fromDomainEndpoint` factory method. This method accepts a domain endpoint of an already existing domain: @@ -105,6 +107,20 @@ const domainEndpoint = 'https://my-domain-jcjotrt6f7otem4sqcwbch3c4u.us-east-1.e const domain = opensearch.Domain.fromDomainEndpoint(this, 'ImportedDomain', domainEndpoint); ``` +### Using the output of another CloudFormation stack + +To import an existing domain with the help of an exported value from another CloudFormation stack, +use the `Domain.fromDomainAttributes` factory method. This will accept tokens. + +```ts +const domainArn = Fn.importValue(`another-cf-stack-export-domain-arn`); +const domainEndpoint = Fn.importValue(`another-cf-stack-export-domain-endpoint`); +const domain = Domain.fromDomainAttributes(this, 'ImportedDomain', { + domainArn, + domainEndpoint, +}); +``` + ## Permissions ### IAM diff --git a/packages/@aws-cdk/core/README.md b/packages/@aws-cdk/core/README.md index 468620b150dc2..773c2802a839b 100644 --- a/packages/@aws-cdk/core/README.md +++ b/packages/@aws-cdk/core/README.md @@ -276,6 +276,13 @@ exposed where they shouldn't be. If you try to use a `SecretValue` in a different location, an error about unsafe secret usage will be thrown at synthesis time. +If you rotate the secret's value in Secrets Manager, you must also change at +least one property on the resource where you are using the secret, to force +CloudFormation to re-read the secret. + +`SecretValue.ssmSecure()` is only supported for a limited set of resources. +[Click here for a list of supported resources and properties](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html#template-parameters-dynamic-patterns-resources). + ## ARN manipulation Sometimes you will need to put together or pick apart Amazon Resource Names diff --git a/packages/@aws-cdk/core/lib/secret-value.ts b/packages/@aws-cdk/core/lib/secret-value.ts index 2075a134c2021..ebbf471896cba 100644 --- a/packages/@aws-cdk/core/lib/secret-value.ts +++ b/packages/@aws-cdk/core/lib/secret-value.ts @@ -76,6 +76,10 @@ export class SecretValue extends Intrinsic { /** * Creates a `SecretValue` with a value which is dynamically loaded from AWS Secrets Manager. + * + * If you rotate the value in the Secret, you must also change at least one property + * on the resource where you are using the secret, to force CloudFormation to re-read the secret. + * * @param secretId The ID or ARN of the secret * @param options Options */ @@ -107,6 +111,10 @@ export class SecretValue extends Intrinsic { /** * Use a secret value stored from a Systems Manager (SSM) parameter. * + * This secret source in only supported in a limited set of resources and + * properties. [Click here for the list of supported + * properties](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html#template-parameters-dynamic-patterns-resources). + * * @param parameterName The name of the parameter in the Systems Manager * Parameter Store. The parameter name is case-sensitive. * diff --git a/packages/@aws-cdk/pipelines/README.md b/packages/@aws-cdk/pipelines/README.md index e0490385b3aec..f01d2edc840dc 100644 --- a/packages/@aws-cdk/pipelines/README.md +++ b/packages/@aws-cdk/pipelines/README.md @@ -1379,6 +1379,31 @@ After turning on `privilegedMode: true`, you will need to do a one-time manual c pipeline to get it going again (as with a broken 'synth' the pipeline will not be able to self update to the right state). +### Not authorized to perform sts:AssumeRole on arn:aws:iam::\*:role/\*-lookup-role-\* + +You may get an error like the following in the **Synth** step: + +```text +Could not assume role in target account using current credentials (which are for account 111111111111). User: +arn:aws:sts::111111111111:assumed-role/PipelineStack-PipelineBuildSynthCdkBuildProje-..../AWSCodeBuild-.... +is not authorized to perform: sts:AssumeRole on resource: +arn:aws:iam::222222222222:role/cdk-hnb659fds-lookup-role-222222222222-us-east-1. +Please make sure that this role exists in the account. If it doesn't exist, (re)-bootstrap the environment with +the right '--trust', using the latest version of the CDK CLI. +``` + +This is a sign that the CLI is trying to do Context Lookups during the **Synth** step, which are failing +because it cannot assume the right role. We recommend you don't rely on Context Lookups in the pipeline at +all, and commit a file called `cdk.context.json` with the right lookup values in it to source control. + +If you do want to do lookups in the pipeline, the cause is one of the following: + +* The target environment has not been bootstrapped; OR +* The target environment has been bootstrapped without the right `--trust` relationship; OR +* The CodeBuild execution role does not have permissions to call `sts:AssumeRole`. + +See the section called **Context Lookups** for more information on using this feature. + ### IAM policies: Cannot exceed quota for PoliciesPerRole / Maximum policy size exceeded This happens as a result of having a lot of targets in the Pipeline: the IAM policies that diff --git a/packages/@aws-cdk/pipelines/lib/codepipeline/codepipeline-source.ts b/packages/@aws-cdk/pipelines/lib/codepipeline/codepipeline-source.ts index d92adc8225782..6b42855f05b4b 100644 --- a/packages/@aws-cdk/pipelines/lib/codepipeline/codepipeline-source.ts +++ b/packages/@aws-cdk/pipelines/lib/codepipeline/codepipeline-source.ts @@ -34,6 +34,9 @@ export abstract class CodePipelineSource extends Step implements ICodePipelineAc * Authentication will be done by a secret called `github-token` in AWS * Secrets Manager (unless specified otherwise). * + * If you rotate the value in the Secret, you must also change at least one property + * on the Pipeline, to force CloudFormation to re-read the secret. + * * The token should have these permissions: * * * **repo** - to read the repository diff --git a/packages/aws-cdk-lib/README.md b/packages/aws-cdk-lib/README.md index 6f082fbbe2413..a620a2aecc4ad 100644 --- a/packages/aws-cdk-lib/README.md +++ b/packages/aws-cdk-lib/README.md @@ -307,6 +307,13 @@ exposed where they shouldn't be. If you try to use a `SecretValue` in a different location, an error about unsafe secret usage will be thrown at synthesis time. +If you rotate the secret's value in Secrets Manager, you must also change at +least one property on the resource where you are using the secret, to force +CloudFormation to re-read the secret. + +`SecretValue.ssmSecure()` is only supported for a limited set of resources. +[Click here for a list of supported resources and properties](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html#template-parameters-dynamic-patterns-resources). + ## ARN manipulation Sometimes you will need to put together or pick apart Amazon Resource Names