From 94623c658089119e4c76d8465577661088edf189 Mon Sep 17 00:00:00 2001 From: Kendra Neil <53584728+TheRealAmazonKendra@users.noreply.github.com> Date: Tue, 14 Jun 2022 17:16:30 -0700 Subject: [PATCH] fix(codepipeline): cannot deploy pipeline stack with crossAccountKeys twice (under feature flag) When multiple copies of the same pipeline are deployed in separate stacks, the alias name for the KMS key is the same, causing the deployment to fail. This hcange fixes that using the stack name instead of the stack ID to create a stack safe uniqueId for the alias name. This fix is behind the following feature flag: @aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeUniqueId Fixes issue #18828. --- ...-codepipeline-cloudformation.template.json | 2 +- .../PipelineStack.template.json | 2 +- .../aws-cdk-codepipeline-lambda.template.json | 2 +- ...dk-codepipeline-alexa-deploy.template.json | 2 +- ...-codepipeline-cloudformation.template.json | 2 +- ...ipeline-codecommit-codebuild.template.json | 2 +- ...-cdk-codepipeline-codecommit.template.json | 2 +- ...ws-cdk-pipeline-event-target.template.json | 2 +- ...k-codepipeline-stepfunctions.template.json | 2 +- .../@aws-cdk/aws-codepipeline/lib/pipeline.ts | 19 +- .../@aws-cdk/aws-codepipeline/package.json | 3 +- .../aws-codepipeline/test/pipeline.test.ts | 334 ++++++++++++++++++ .../pipeline-events.template.json | 2 +- packages/@aws-cdk/core/lib/names.ts | 58 ++- .../core/lib/private/unique-resource-name.ts | 122 +++++++ .../test/private/unique-resource-name.test.ts | 101 ++++++ packages/@aws-cdk/cx-api/lib/features.ts | 11 + .../PipelineSecurityStack.template.json | 2 +- .../PipelineStack.template.json | 2 +- .../PipelineStack.template.json | 2 +- .../PipelineStack.template.json | 2 +- 21 files changed, 656 insertions(+), 20 deletions(-) create mode 100644 packages/@aws-cdk/core/lib/private/unique-resource-name.ts create mode 100644 packages/@aws-cdk/core/test/private/unique-resource-name.test.ts diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/cfn-template-from-repo.lit.integ.snapshot/aws-cdk-codepipeline-cloudformation.template.json b/packages/@aws-cdk/aws-codepipeline-actions/test/cfn-template-from-repo.lit.integ.snapshot/aws-cdk-codepipeline-cloudformation.template.json index cb837eab62aa1..83869967f93dc 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/cfn-template-from-repo.lit.integ.snapshot/aws-cdk-codepipeline-cloudformation.template.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/cfn-template-from-repo.lit.integ.snapshot/aws-cdk-codepipeline-cloudformation.template.json @@ -44,7 +44,7 @@ "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-awscdkcodepipelinecloudformationpipeline7dbde619", + "AliasName": "alias/codepipeline-aws-cdk-codepipeline-cloudformation-pipeline-7dbde619", "TargetKeyId": { "Fn::GetAtt": [ "PipelineArtifactsBucketEncryptionKey01D58D69", diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/lambda-deployed-through-codepipeline.lit.integ.snapshot/PipelineStack.template.json b/packages/@aws-cdk/aws-codepipeline-actions/test/lambda-deployed-through-codepipeline.lit.integ.snapshot/PipelineStack.template.json index e3da2128d9482..edce01a382b91 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/lambda-deployed-through-codepipeline.lit.integ.snapshot/PipelineStack.template.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/lambda-deployed-through-codepipeline.lit.integ.snapshot/PipelineStack.template.json @@ -38,7 +38,7 @@ "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-pipelinestackpipeline9db740af", + "AliasName": "alias/codepipeline-pipelinestack-pipeline-9db740af", "TargetKeyId": { "Fn::GetAtt": [ "PipelineArtifactsBucketEncryptionKey01D58D69", diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/lambda-pipeline.integ.snapshot/aws-cdk-codepipeline-lambda.template.json b/packages/@aws-cdk/aws-codepipeline-actions/test/lambda-pipeline.integ.snapshot/aws-cdk-codepipeline-lambda.template.json index f19a0fc010bae..39ca1662ed0ec 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/lambda-pipeline.integ.snapshot/aws-cdk-codepipeline-lambda.template.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/lambda-pipeline.integ.snapshot/aws-cdk-codepipeline-lambda.template.json @@ -38,7 +38,7 @@ "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-awscdkcodepipelinelambdapipeline87a4b3d3", + "AliasName": "alias/codepipeline-aws-cdk-codepipeline-lambda-pipeline-87a4b3d3", "TargetKeyId": { "Fn::GetAtt": [ "PipelineArtifactsBucketEncryptionKey01D58D69", diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-alexa-deploy.integ.snapshot/aws-cdk-codepipeline-alexa-deploy.template.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-alexa-deploy.integ.snapshot/aws-cdk-codepipeline-alexa-deploy.template.json index b1eb53d6c59cc..10688fe9721a6 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-alexa-deploy.integ.snapshot/aws-cdk-codepipeline-alexa-deploy.template.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-alexa-deploy.integ.snapshot/aws-cdk-codepipeline-alexa-deploy.template.json @@ -48,7 +48,7 @@ "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-awscdkcodepipelinealexadeploypipeline961107f5", + "AliasName": "alias/codepipeline-aws-cdk-codepipeline-alexa-deploy-pipeline-961107f5", "TargetKeyId": { "Fn::GetAtt": [ "PipelineArtifactsBucketEncryptionKey01D58D69", diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-cfn.integ.snapshot/aws-cdk-codepipeline-cloudformation.template.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-cfn.integ.snapshot/aws-cdk-codepipeline-cloudformation.template.json index b671f21665464..fd3fafc21a418 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-cfn.integ.snapshot/aws-cdk-codepipeline-cloudformation.template.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-cfn.integ.snapshot/aws-cdk-codepipeline-cloudformation.template.json @@ -38,7 +38,7 @@ "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-awscdkcodepipelinecloudformationpipeline7dbde619", + "AliasName": "alias/codepipeline-aws-cdk-codepipeline-cloudformation-pipeline-7dbde619", "TargetKeyId": { "Fn::GetAtt": [ "PipelineArtifactsBucketEncryptionKey01D58D69", diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-code-commit-build.integ.snapshot/aws-cdk-codepipeline-codecommit-codebuild.template.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-code-commit-build.integ.snapshot/aws-cdk-codepipeline-codecommit-codebuild.template.json index 7a9855fe36470..1c1241a8d23d6 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-code-commit-build.integ.snapshot/aws-cdk-codepipeline-codecommit-codebuild.template.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-code-commit-build.integ.snapshot/aws-cdk-codepipeline-codecommit-codebuild.template.json @@ -244,7 +244,7 @@ "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-awscdkcodepipelinecodecommitcodebuildpipeline9540e1f5", + "AliasName": "alias/codepipeline-aws-cdk-codepipeline-codecommit-codebuild-pipeline-9540e1f5", "TargetKeyId": { "Fn::GetAtt": [ "PipelineArtifactsBucketEncryptionKey01D58D69", diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-code-commit.integ.snapshot/aws-cdk-codepipeline-codecommit.template.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-code-commit.integ.snapshot/aws-cdk-codepipeline-codecommit.template.json index cca5e3c5d5725..9ad15802f8bd6 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-code-commit.integ.snapshot/aws-cdk-codepipeline-codecommit.template.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-code-commit.integ.snapshot/aws-cdk-codepipeline-codecommit.template.json @@ -109,7 +109,7 @@ "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-awscdkcodepipelinecodecommitpipelinef780ca18", + "AliasName": "alias/codepipeline-aws-cdk-codepipeline-codecommit-pipeline-f780ca18", "TargetKeyId": { "Fn::GetAtt": [ "PipelineArtifactsBucketEncryptionKey01D58D69", diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-events.integ.snapshot/aws-cdk-pipeline-event-target.template.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-events.integ.snapshot/aws-cdk-pipeline-event-target.template.json index 02a0628ba357a..38dec014dc488 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-events.integ.snapshot/aws-cdk-pipeline-event-target.template.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-events.integ.snapshot/aws-cdk-pipeline-event-target.template.json @@ -38,7 +38,7 @@ "MyPipelineArtifactsBucketEncryptionKeyAlias9D4F8C59": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-awscdkpipelineeventtargetmypipeline4ae5d407", + "AliasName": "alias/codepipeline-aws-cdk-pipeline-event-target-mypipeline-4ae5d407", "TargetKeyId": { "Fn::GetAtt": [ "MyPipelineArtifactsBucketEncryptionKey8BF0A7F3", diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-stepfunctions.integ.snapshot/aws-cdk-codepipeline-stepfunctions.template.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-stepfunctions.integ.snapshot/aws-cdk-codepipeline-stepfunctions.template.json index f407e31285705..f8f998e639f18 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-stepfunctions.integ.snapshot/aws-cdk-codepipeline-stepfunctions.template.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-stepfunctions.integ.snapshot/aws-cdk-codepipeline-stepfunctions.template.json @@ -78,7 +78,7 @@ "MyPipelineArtifactsBucketEncryptionKeyAlias9D4F8C59": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-awscdkcodepipelinestepfunctionsmypipelinece88aa28", + "AliasName": "alias/codepipeline-aws-cdk-codepipeline-stepfunctions-mypipeline-ce88aa28", "TargetKeyId": { "Fn::GetAtt": [ "MyPipelineArtifactsBucketEncryptionKey8BF0A7F3", diff --git a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts index c4f1027069f59..f34c77a8cb607 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts @@ -7,6 +7,7 @@ import { ArnFormat, BootstraplessSynthesizer, DefaultStackSynthesizer, + FeatureFlags, IStackSynthesizer, Lazy, Names, @@ -17,6 +18,7 @@ import { Stage as CdkStage, Token, } from '@aws-cdk/core'; +import * as cxapi from '@aws-cdk/cx-api'; import { Construct } from 'constructs'; import { ActionCategory, IAction, IPipeline, IStage, PipelineNotificationEvents, PipelineNotifyOnOptions } from './action'; import { CfnPipeline } from './codepipeline.generated'; @@ -695,10 +697,19 @@ export class Pipeline extends PipelineBase { private generateNameForDefaultBucketKeyAlias(): string { const prefix = 'alias/codepipeline-'; const maxAliasLength = 256; - const uniqueId = Names.uniqueId(this); - // take the last 256 - (prefix length) characters of uniqueId - const startIndex = Math.max(0, uniqueId.length - (maxAliasLength - prefix.length)); - return prefix + uniqueId.substring(startIndex).toLowerCase(); + const maxResourceNameLength = maxAliasLength - prefix.length; + // Names.uniqueId() may have naming collisions when the IDs of resources are similar + // and/or when they are too long and sliced. We do not want to update this and + // automatically change the name of every KMS key already generated so we are putting + // this under a feature flag. + const uniqueId = FeatureFlags.of(this).isEnabled(cxapi.CODEPIPELINE_CROSS_ACCOUNT_KEY_ALIAS_STACK_SAFE_UNIQUE_ID) ? + Names.uniqueResourceName(this, { + separator: '-', + maxLength: maxResourceNameLength, + allowedSpecialCharacters: '/_-', + }) : + Names.uniqueId(this).slice(-maxResourceNameLength); + return prefix + uniqueId.toLowerCase(); } /** diff --git a/packages/@aws-cdk/aws-codepipeline/package.json b/packages/@aws-cdk/aws-codepipeline/package.json index 3e0453cce710a..89f0519235440 100644 --- a/packages/@aws-cdk/aws-codepipeline/package.json +++ b/packages/@aws-cdk/aws-codepipeline/package.json @@ -89,7 +89,6 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/integ-runner": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", - "@aws-cdk/cx-api": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.5.2", "jest": "^27.5.1" @@ -101,6 +100,7 @@ "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/core": "0.0.0", + "@aws-cdk/cx-api": "0.0.0", "constructs": "^10.0.0" }, "homepage": "https://github.com/aws/aws-cdk", @@ -111,6 +111,7 @@ "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/core": "0.0.0", + "@aws-cdk/cx-api": "0.0.0", "constructs": "^10.0.0" }, "engines": { diff --git a/packages/@aws-cdk/aws-codepipeline/test/pipeline.test.ts b/packages/@aws-cdk/aws-codepipeline/test/pipeline.test.ts index 27db5eaf16629..463d91cf67e98 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/pipeline.test.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/pipeline.test.ts @@ -482,6 +482,284 @@ describe('', () => { }); }); }); + + describe('cross account key alias name tests', () => { + const kmsAliasResource = 'AWS::KMS::Alias'; + + test('cross account key alias is named with stack name instead of ID when feature flag is enabled', () => { + const stack = createPipelineStack({ + withFeatureFlag: true, + suffix: 'Name', + stackId: 'PipelineStack', + }); + + Template.fromStack(stack).hasResourceProperties(kmsAliasResource, { + AliasName: 'alias/codepipeline-actual-stack-name-pipeline-0a412eb5', + }); + }); + + test('cross account key alias is named with stack ID when feature flag is not enabled', () => { + const stack = createPipelineStack({ + suffix: 'Name', + stackId: 'PipelineStack', + }); + + Template.fromStack(stack).hasResourceProperties(kmsAliasResource, { + AliasName: 'alias/codepipeline-pipelinestackpipeline9db740af', + }); + }); + + test('cross account key alias is named with generated stack name when stack name is undefined and feature flag is enabled', () => { + const stack = createPipelineStack({ + withFeatureFlag: true, + suffix: 'Name', + stackId: 'PipelineStack', + undefinedStackName: true, + }); + + Template.fromStack(stack).hasResourceProperties(kmsAliasResource, { + AliasName: 'alias/codepipeline-pipelinestack-pipeline-9db740af', + }); + }); + + test('cross account key alias is named with stack ID when stack name is not present and feature flag is not enabled', () => { + const stack = createPipelineStack({ + suffix: 'Name', + stackId: 'PipelineStack', + undefinedStackName: true, + }); + + Template.fromStack(stack).hasResourceProperties(kmsAliasResource, { + AliasName: 'alias/codepipeline-pipelinestackpipeline9db740af', + }); + }); + + test('cross account key alias is named with stack name and nested stack ID when feature flag is enabled', () => { + const stack = createPipelineStack({ + withFeatureFlag: true, + suffix: 'Name', + stackId: 'TopLevelStack', + nestedStackId: 'NestedPipelineStack', + pipelineId: 'ActualPipeline', + }); + + Template.fromStack(stack.nestedStack!).hasResourceProperties(kmsAliasResource, { + AliasName: 'alias/codepipeline-actual-stack-name-nestedpipelinestack-actualpipeline-23a98110', + }); + }); + + test('cross account key alias is named with stack ID and nested stack ID when stack name is present and feature flag is not enabled', () => { + const stack = createPipelineStack({ + suffix: 'Name', + stackId: 'TopLevelStack', + nestedStackId: 'NestedPipelineStack', + pipelineId: 'ActualPipeline', + }); + + Template.fromStack(stack.nestedStack!).hasResourceProperties(kmsAliasResource, { + AliasName: 'alias/codepipeline-toplevelstacknestedpipelinestackactualpipeline3161a537', + }); + }); + + test('cross account key alias is named with generated stack name and nested stack ID when stack name is undefined and feature flag is enabled', () => { + const stack = createPipelineStack({ + withFeatureFlag: true, + suffix: 'Name', + stackId: 'TopLevelStack', + nestedStackId: 'NestedPipelineStack', + pipelineId: 'ActualPipeline', + undefinedStackName: true, + }); + + Template.fromStack(stack.nestedStack!).hasResourceProperties(kmsAliasResource, { + AliasName: 'alias/codepipeline-toplevelstack-nestedpipelinestack-actualpipeline-3161a537', + }); + }); + + test('cross account key alias is named with stack ID and nested stack ID when stack name is not present and feature flag is not enabled', () => { + const stack = createPipelineStack({ + suffix: 'Name', + stackId: 'TopLevelStack', + nestedStackId: 'NestedPipelineStack', + pipelineId: 'ActualPipeline', + undefinedStackName: true, + }); + + Template.fromStack(stack.nestedStack!).hasResourceProperties(kmsAliasResource, { + AliasName: 'alias/codepipeline-toplevelstacknestedpipelinestackactualpipeline3161a537', + }); + }); + + test('cross account key alias is properly shortened to 256 characters when stack name is too long and feature flag is enabled', () => { + const stack = createPipelineStack({ + withFeatureFlag: true, + suffix: 'NeedsToBeShortenedDueToTheLengthOfThisAbsurdNameThatNoOneShouldUseButItStillMightHappenSoWeMustTestForTheTestCase', + stackId: 'too-long', + pipelineId: 'ActualPipelineWithExtraSuperLongNameThatWillNeedToBeShortenedDueToTheAlsoVerySuperExtraLongNameOfTheStack-AlsoWithSomeDifferentCharactersAddedToTheEnd', + }); + + Template.fromStack(stack).hasResourceProperties(kmsAliasResource, { + AliasName: 'alias/codepipeline-actual-stack-needstobeshortenedduetothelengthofthisabsurdnamethatnooneshouldusebutitstillmighthappensowemusttestfohatwillneedtobeshortenedduetothealsoverysuperextralongnameofthestack-alsowithsomedifferentcharactersaddedtotheend-384b9343', + }); + }); + + test('cross account key alias is properly shortened to 256 characters when stack name is too long and feature flag is not enabled', () => { + const stack = createPipelineStack({ + suffix: 'too-long', + stackId: 'NeedsToBeShortenedDueToTheLengthOfThisAbsurdNameThatNoOneShouldUseButItStillMightHappenSoWeMustTestForTheTestCase', + pipelineId: 'ActualPipelineWithExtraSuperLongNameThatWillNeedToBeShortenedDueToTheAlsoVerySuperExtraLongNameOfTheStack-AlsoWithSomeDifferentCharactersAddedToTheEnd', + }); + + Template.fromStack(stack).hasResourceProperties(kmsAliasResource, { + AliasName: 'alias/codepipeline-ortenedduetothelengthofthisabsurdnamethatnooneshouldusebutitstillmighthappensowemusttestforthetestcaseactualpipelinewithextrasuperlongnamethatwillneedtobeshortenedduetothealsoverysuperextralongnameofthestackalsowithsomedifferentc498e0672', + }); + }); + + test('cross account key alias names do not conflict when the stack ID is the same and pipeline ID is the same and feature flag is enabled', () => { + const stack1 = createPipelineStack({ + withFeatureFlag: true, + suffix: '1', + stackId: 'STACK-ID', + }); + + const stack2 = createPipelineStack({ + withFeatureFlag: true, + suffix: '2', + stackId: 'STACK-ID', + }); + + expect(Template.fromStack(stack1).findResources(kmsAliasResource)).not.toEqual(Template.fromStack(stack2).findResources(kmsAliasResource)); + + Template.fromStack(stack1).hasResourceProperties(kmsAliasResource, { + AliasName: 'alias/codepipeline-actual-stack-1-pipeline-b09fefee', + }); + + Template.fromStack(stack2).hasResourceProperties(kmsAliasResource, { + AliasName: 'alias/codepipeline-actual-stack-2-pipeline-f46258fe', + }); + }); + + test('cross account key alias names do conflict when the stack ID is the same and pipeline ID is the same when feature flag is not enabled', () => { + const stack1 = createPipelineStack({ + suffix: '1', + stackId: 'STACK-ID', + }); + + const stack2 = createPipelineStack({ + suffix: '2', + stackId: 'STACK-ID', + }); + + expect(Template.fromStack(stack1).findResources(kmsAliasResource)).toEqual(Template.fromStack(stack2).findResources(kmsAliasResource)); + + Template.fromStack(stack1).hasResourceProperties(kmsAliasResource, { + AliasName: 'alias/codepipeline-stackidpipeline32fb88b3', + }); + + Template.fromStack(stack2).hasResourceProperties(kmsAliasResource, { + AliasName: 'alias/codepipeline-stackidpipeline32fb88b3', + }); + }); + + test('cross account key alias names do not conflict for nested stacks when pipeline ID is the same and nested stacks have the same ID when feature flag is enabled', () => { + const stack1 = createPipelineStack({ + withFeatureFlag: true, + suffix: 'Name-1', + stackId: 'STACK-ID', + nestedStackId: 'Nested', + pipelineId: 'PIPELINE-ID', + }); + const stack2 = createPipelineStack({ + withFeatureFlag: true, + suffix: 'Name-2', + stackId: 'STACK-ID', + nestedStackId: 'Nested', + pipelineId: 'PIPELINE-ID', + }); + + expect(Template.fromStack(stack1.nestedStack!).findResources(kmsAliasResource)) + .not.toEqual(Template.fromStack(stack2.nestedStack!).findResources(kmsAliasResource)); + + Template.fromStack(stack1.nestedStack!).hasResourceProperties(kmsAliasResource, { + AliasName: 'alias/codepipeline-actual-stack-name-1-nested-pipeline-id-c8c9f252', + }); + + Template.fromStack(stack2.nestedStack!).hasResourceProperties(kmsAliasResource, { + AliasName: 'alias/codepipeline-actual-stack-name-2-nested-pipeline-id-aff6dd63', + }); + }); + + test('cross account key alias names do conflict for nested stacks when pipeline ID is the same and nested stacks have the same ID when feature flag is not enabled', () => { + const stack1 = createPipelineStack({ + suffix: '1', + stackId: 'STACK-ID', + nestedStackId: 'Nested', + pipelineId: 'PIPELINE-ID', + }); + const stack2 = createPipelineStack({ + suffix: '2', + stackId: 'STACK-ID', + nestedStackId: 'Nested', + pipelineId: 'PIPELINE-ID', + }); + + expect(Template.fromStack(stack1.nestedStack!).findResources(kmsAliasResource)) + .toEqual(Template.fromStack(stack2.nestedStack!).findResources(kmsAliasResource)); + + Template.fromStack(stack1.nestedStack!).hasResourceProperties(kmsAliasResource, { + AliasName: 'alias/codepipeline-stackidnestedpipelineid3e91360a', + }); + + Template.fromStack(stack2.nestedStack!).hasResourceProperties(kmsAliasResource, { + AliasName: 'alias/codepipeline-stackidnestedpipelineid3e91360a', + }); + }); + + test('cross account key alias names do not conflict for nested stacks when in the same stack but nested stacks have different IDs when feature flag is enabled', () => { + const stack = createPipelineStack({ + withFeatureFlag: true, + suffix: 'Name-1', + stackId: 'STACK-ID', + nestedStackId: 'First', + pipelineId: 'PIPELINE-ID', + }); + const nestedStack2 = new cdk.NestedStack(stack, 'Second'); + createPipelineWithSourceAndBuildStages(nestedStack2, 'Actual-Pipeline-Name-2', 'PIPELINE-ID'); + + expect(Template.fromStack(stack.nestedStack!).findResources(kmsAliasResource)) + .not.toEqual(Template.fromStack(nestedStack2).findResources(kmsAliasResource)); + + Template.fromStack(stack.nestedStack!).hasResourceProperties(kmsAliasResource, { + AliasName: 'alias/codepipeline-actual-stack-name-1-first-pipeline-id-3c59cb88', + }); + + Template.fromStack(nestedStack2).hasResourceProperties(kmsAliasResource, { + AliasName: 'alias/codepipeline-actual-stack-name-1-second-pipeline-id-16143d12', + }); + }); + + test('cross account key alias names do not conflict for nested stacks when in the same stack but nested stacks have different IDs when feature flag is not enabled', () => { + const stack = createPipelineStack({ + suffix: 'Name-1', + stackId: 'STACK-ID', + nestedStackId: 'First', + pipelineId: 'PIPELINE-ID', + }); + const nestedStack2 = new cdk.NestedStack(stack, 'Second'); + createPipelineWithSourceAndBuildStages(nestedStack2, 'Actual-Pipeline-Name-2', 'PIPELINE-ID'); + + expect(Template.fromStack(stack.nestedStack!).findResources(kmsAliasResource)) + .not.toEqual(Template.fromStack(nestedStack2).findResources(kmsAliasResource)); + + Template.fromStack(stack.nestedStack!).hasResourceProperties(kmsAliasResource, { + AliasName: 'alias/codepipeline-stackidfirstpipelineid5abca693', + }); + + Template.fromStack(nestedStack2).hasResourceProperties(kmsAliasResource, { + AliasName: 'alias/codepipeline-stackidsecondpipelineid288ce778', + }); + }); + }); }); describe('test with shared setup', () => { @@ -563,3 +841,59 @@ class ReusePipelineStack extends cdk.Stack { }); } } + +interface PipelineStackProps extends cdk.StackProps { + readonly nestedStackId?: string; + readonly pipelineName: string; + readonly pipelineId?: string; +} + +class PipelineStack extends cdk.Stack { + nestedStack?: cdk.NestedStack; + pipeline: codepipeline.Pipeline; + + constructor(scope?: Construct, id?: string, props?: PipelineStackProps) { + super (scope, id, props); + + props?.nestedStackId ? this.nestedStack = new cdk.NestedStack(this, props!.nestedStackId!) : undefined; + this.pipeline = createPipelineWithSourceAndBuildStages(this.nestedStack || this, props?.pipelineName, props?.pipelineId); + } +} + +function createPipelineWithSourceAndBuildStages(scope: Construct, pipelineName?: string, pipelineId: string = 'Pipeline') { + const artifact = new codepipeline.Artifact(); + return new codepipeline.Pipeline(scope, pipelineId, { + pipelineName, + crossAccountKeys: true, + reuseCrossRegionSupportStacks: false, + stages: [ + { + stageName: 'Source', + actions: [new FakeSourceAction({ actionName: 'Source', output: artifact })], + }, + { + stageName: 'Build', + actions: [new FakeBuildAction({ actionName: 'Build', input: artifact })], + }, + ], + }); +}; + +interface CreatePipelineStackOptions { + readonly withFeatureFlag?: boolean, + readonly suffix: string, + readonly stackId?: string, + readonly pipelineId?: string, + readonly undefinedStackName?: boolean, + readonly nestedStackId?: string, +} + +function createPipelineStack(options: CreatePipelineStackOptions): PipelineStack { + const context = options.withFeatureFlag ? { context: { [cxapi.CODEPIPELINE_CROSS_ACCOUNT_KEY_ALIAS_STACK_SAFE_UNIQUE_ID]: true } } : undefined; + return new PipelineStack(new cdk.App(context), options.stackId, { + stackName: options.undefinedStackName ? undefined : `Actual-Stack-${options.suffix}`, + nestedStackId: options.nestedStackId, + pipelineName: `Actual-Pipeline-${options.suffix}`.substring(0, 100), + pipelineId: options.pipelineId, + }); +}; diff --git a/packages/@aws-cdk/aws-events-targets/test/codepipeline/pipeline-event-target.integ.snapshot/pipeline-events.template.json b/packages/@aws-cdk/aws-events-targets/test/codepipeline/pipeline-event-target.integ.snapshot/pipeline-events.template.json index a11fb7f25fb7b..2a37807987742 100644 --- a/packages/@aws-cdk/aws-events-targets/test/codepipeline/pipeline-event-target.integ.snapshot/pipeline-events.template.json +++ b/packages/@aws-cdk/aws-events-targets/test/codepipeline/pipeline-event-target.integ.snapshot/pipeline-events.template.json @@ -44,7 +44,7 @@ "pipelinePipeline22F2A91DArtifactsBucketEncryptionKeyAlias9530209A": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-pipelineeventspipelinepipeline22f2a91dfbb66895", + "AliasName": "alias/codepipeline-pipeline-events-pipelinepipeline22f2a91d-fbb66895", "TargetKeyId": { "Fn::GetAtt": [ "pipelinePipeline22F2A91DArtifactsBucketEncryptionKey87C796D2", diff --git a/packages/@aws-cdk/core/lib/names.ts b/packages/@aws-cdk/core/lib/names.ts index ea75927acb5ed..057ec37d3201c 100644 --- a/packages/@aws-cdk/core/lib/names.ts +++ b/packages/@aws-cdk/core/lib/names.ts @@ -1,5 +1,36 @@ import { IConstruct, Node } from 'constructs'; +import { unresolved } from './private/encoding'; +import { makeUniqueResourceName } from './private/unique-resource-name'; import { makeUniqueId } from './private/uniqueid'; +import { Stack } from './stack'; + + +/** + * Options for creating a unique resource name. +*/ +export interface UniqueResourceNameOptions { + + /** + * The maximum length of the unique resource name. + * + * @default - 256 + */ + readonly maxLength?: number; + + /** + * The separator used between the path components. + * + * @default - none + */ + readonly separator?: string; + + /** + * Non-alphanumeric characters allowed in the unique resource name. + * + * @default - none + */ + readonly allowedSpecialCharacters?: string; +} /** * Functions for devising unique names for constructs. For example, those can be @@ -9,7 +40,8 @@ export class Names { /** * Returns a CloudFormation-compatible unique identifier for a construct based * on its path. The identifier includes a human readable portion rendered - * from the path components and a hash suffix. + * from the path components and a hash suffix. uniqueId is not unique if multiple + * copies of the stack are deployed. Prefer using uniqueResourceName(). * * @param construct The construct * @returns a unique id based on the construct path @@ -35,5 +67,29 @@ export class Names { return components.length > 0 ? makeUniqueId(components) : ''; } + /** + * Returns a CloudFormation-compatible unique identifier for a construct based + * on its path. This function finds the stackName of the parent stack (non-nested) + * to the construct, and the ids of the components in the construct path. + * + * The user can define allowed special characters, a separator between the elements, + * and the maximum length of the resource name. The name includes a human readable portion rendered + * from the path components, with or without user defined separators, and a hash suffix. + * If the resource name is longer than the maximum length, it is trimmed in the middle. + * + * @param construct The construct + * @param options Options for defining the unique resource name + * @returns a unique resource name based on the construct path + */ + public static uniqueResourceName(construct: IConstruct, options: UniqueResourceNameOptions) { + const node = Node.of(construct); + + const componentsPath = node.scopes.slice(node.scopes.indexOf(node.scopes.reverse() + .find(component => (Stack.isStack(component) && !unresolved(component.stackName)))!, + )).map(component => Stack.isStack(component) && !unresolved(component.stackName) ? component.stackName : Node.of(component).id); + + return makeUniqueResourceName(componentsPath, options); + } + private constructor() {} } diff --git a/packages/@aws-cdk/core/lib/private/unique-resource-name.ts b/packages/@aws-cdk/core/lib/private/unique-resource-name.ts new file mode 100644 index 0000000000000..cf816dc9a5758 --- /dev/null +++ b/packages/@aws-cdk/core/lib/private/unique-resource-name.ts @@ -0,0 +1,122 @@ +import { createHash } from 'crypto'; +// import { unresolved } from './encoding'; + +/** + * Options for creating a unique resource name. +*/ +interface MakeUniqueResourceNameOptions { + + /** + * The maximum length of the unique resource name. + * + * @default - 256 + */ + readonly maxLength?: number; + + /** + * The separator used between the path components. + * + * @default - none + */ + readonly separator?: string; + + /** + * Non-alphanumeric characters allowed in the unique resource name. + * + * @default - none + */ + readonly allowedSpecialCharacters?: string; +} + +/** + * Resources with this ID are hidden from humans + * + * They do not appear in the human-readable part of the logical ID, + * but they are included in the hash calculation. + */ +const HIDDEN_FROM_HUMAN_ID = 'Resource'; + +/** +* Resources with this ID are complete hidden from the logical ID calculation. +*/ +const HIDDEN_ID = 'Default'; + +const PATH_SEP = '/'; + +const MAX_LEN = 256; + +const HASH_LEN = 8; + +export function makeUniqueResourceName(components: string[], options: MakeUniqueResourceNameOptions) { + const maxLength = options.maxLength ?? 256; + const separator = options.separator ?? ''; + components = components.filter(x => x !== HIDDEN_ID); + + if (components.length === 0) { + throw new Error('Unable to calculate a unique resource name for an empty set of components'); + } + + // top-level resources will simply use the `name` as-is if the name is also short enough + // in order to support transparent migration of cloudformation templates to the CDK without the + // need to rename all resources. + if (components.length === 1) { + const topLevelResource = removeNonAllowedSpecialCharacters(components[0], separator, options.allowedSpecialCharacters); + + if (topLevelResource.length <= maxLength) { + return topLevelResource; + } + } + + // Calculate the hash from the full path, included unresolved tokens so the hash value is always unique + const hash = pathHash(components); + const human = removeDupes(components) + .filter(pathElement => pathElement !== HIDDEN_FROM_HUMAN_ID) + .map(pathElement => removeNonAllowedSpecialCharacters(pathElement, separator, options.allowedSpecialCharacters)) + .filter(pathElement => pathElement) + .join(separator) + .concat(separator); + + const maxhumanLength = maxLength - HASH_LEN; + return human.length > maxhumanLength ? `${splitInMiddle(human, maxhumanLength)}${hash}`: `${human}${hash}`; +} + +/** + * Take a hash of the given path. + * + * The hash is limited in size. + */ +function pathHash(path: string[]): string { + const md5 = createHash('md5').update(path.join(PATH_SEP)).digest('hex'); + return md5.slice(0, HASH_LEN).toUpperCase(); +} + +/** + * Removes all non-allowed special characters in a string. + */ +function removeNonAllowedSpecialCharacters(s: string, _separator: string, allowedSpecialCharacters?: string) { + const pattern = allowedSpecialCharacters ? `[^A-Za-z0-9${allowedSpecialCharacters}]` : '[^A-Za-z0-9]'; + const regex = new RegExp(pattern, 'g'); + return s.replace(regex, ''); +} + +/** + * Remove duplicate "terms" from the path list + * + * If the previous path component name ends with this component name, skip the + * current component. + */ +function removeDupes(path: string[]): string[] { + const ret = new Array(); + + for (const component of path) { + if (ret.length === 0 || !ret[ret.length - 1].endsWith(component)) { + ret.push(component); + } + } + return ret; +} + +function splitInMiddle(s: string, maxLength: number = MAX_LEN - HASH_LEN) { + const half = maxLength / 2; + return s.slice(0, half) + s.slice(-half); +} \ No newline at end of file diff --git a/packages/@aws-cdk/core/test/private/unique-resource-name.test.ts b/packages/@aws-cdk/core/test/private/unique-resource-name.test.ts new file mode 100644 index 0000000000000..c2dcdeff445bc --- /dev/null +++ b/packages/@aws-cdk/core/test/private/unique-resource-name.test.ts @@ -0,0 +1,101 @@ +import { createHash } from 'crypto'; +import { makeUniqueResourceName } from '../../lib/private/unique-resource-name'; + +const pathHash = (path: string[]): string => { + return createHash('md5').update(path.join('/')).digest('hex').slice(0, 8).toUpperCase(); +}; + +describe('makeUniqueResourceName tests', () => { + test('unique resource name is just resource name when the resource is top level, short enough, has no nonalphanumeric characters', () => { + const uniqueResourceName = makeUniqueResourceName(['toplevelresource'], {}); + expect(uniqueResourceName).toEqual('toplevelresource'); + }); + + test('unique resource name is shortened with a hash added when resource is top level and resource name is too long', () => { + const tooLongName = ['anamethatisslightlylongerthan256charactersthatisalsoatoplevelresourcesothereisonlyonevalueinthisarrayandapparentlybrevityisnotthestrongpointofthepersonwhonamedthisresourcewhichIbettheywillcometoregretlaterbutitiswhatitisanywhodlethisfunctionshouldshortenthis']; + const uniqueResourceName = makeUniqueResourceName(tooLongName, {}); + + const expectedName = `anamethatisslightlylongerthan256charactersthatisalsoatoplevelresourcesothereisonlyonevalueinthisarrayandapparentlybrevityisngpointofthepersonwhonamedthisresourcewhichIbettheywillcometoregretlaterbutitiswhatitisanywhodlethisfunctionshouldshortenthis${pathHash(tooLongName)}`; + expect(uniqueResourceName).toEqual(expectedName); + expect(uniqueResourceName.length).toEqual(256); + }); + + test('unique resource name removes special characters when resource is top level', () => { + const componentsPath = ['I-love-special-characters-¯\\\_(ツ)_/¯-for-real-though']; + const expectedName = 'Ilovespecialcharactersforrealthough'; + + expect(makeUniqueResourceName(componentsPath, {})).toEqual(expectedName); + }); + + test('unique resource name shortens from the middle and adds a hash when maxLength is defined, resource is top level, and resource name is longer than max', () => { + const componentsPath = ['ThisIsStillLongerThanTheAllowedLength']; + const expectedName = `ThisIsLength${pathHash(componentsPath)}`; + + expect(makeUniqueResourceName(componentsPath, { maxLength: 20 })).toEqual(expectedName); + }); + + test('unique resource name shortens from the middle and adds a hash when maxLength is defined, resource is top level, resource name is longer than max, and separator is provided', () => { + const componentsPath = ['ThisIsStillLongerThanTheAllowedLength']; + const expectedName = `ThisIsength-${pathHash(componentsPath)}`; + + expect(makeUniqueResourceName(componentsPath, { maxLength: 20, separator: '-' })).toEqual(expectedName); + }); + + test('unique resource name removes special characters and makes no other changes when resouce is top level and too long with special characters but proper length without', () => { + const tooLongName = ['a-name-that-is-slightly-longer-than-256-characters-that-is-also-a-top-level-resource-so-there-is-only-one-value-in-this-array-and-apparently-brevity-is-not-the-strong-point-of-the-person-who-named-this-resource-which-I-bet-they-will-come-to-regret-later-but-it-is-what-it-is']; + const expectedName = 'anamethatisslightlylongerthan256charactersthatisalsoatoplevelresourcesothereisonlyonevalueinthisarrayandapparentlybrevityisnotthestrongpointofthepersonwhonamedthisresourcewhichIbettheywillcometoregretlaterbutitiswhatitis'; + + expect(makeUniqueResourceName(tooLongName, {})).toEqual(expectedName); + }); + + test('unique resource name leaves in allowed special characters and adds no hash when resource is top level and resouce name is short enougn', () => { + const componentsPath = ['¯\\\_(ツ)_/¯-shruggie-gets-to-stay-¯\\\_(ツ)_/¯']; + const expectedName = '¯\_(ツ)_/¯shruggiegetstostay¯\_(ツ)_/¯'; + + expect(makeUniqueResourceName(componentsPath, { allowedSpecialCharacters: '¯\\\_(ツ)/', maxLength: 200 })).toEqual(expectedName); + }); + + test('unique resource name leaves in allowed special characters and adds no hash or separators when resource is top level and resouce name is short enougn', () => { + const componentsPath = ['¯\\\_(ツ)_/¯-shruggie-gets-to-stay-¯\\\_(ツ)_/¯']; + const expectedName = '¯\_(ツ)_/¯shruggiegetstostay¯\_(ツ)_/¯'; + + expect(makeUniqueResourceName(componentsPath, { allowedSpecialCharacters: '¯\\\_(ツ)/', maxLength: 200, separator: '-' })).toEqual(expectedName); + }); + + test('unique resource name is shortened with a hash and separator added when resource is top level, resource name is too long, and separator is provided', () => { + const tooLongName = ['anamethatisslightlylongerthan256charactersthatisalsoatoplevelresourcesothereisonlyonevalueinthisarrayandapparentlybrevityisnotthestrongpointofthepersonwhonamedthisresourcewhichIbettheywillcometoregretlaterbutitiswhatitisanywhodlethisfunctionshouldshortenthis']; + const uniqueResourceName = makeUniqueResourceName(tooLongName, { separator: '~' }); + + const expectedName = `anamethatisslightlylongerthan256charactersthatisalsoatoplevelresourcesothereisonlyonevalueinthisarrayandapparentlybrevityisnpointofthepersonwhonamedthisresourcewhichIbettheywillcometoregretlaterbutitiswhatitisanywhodlethisfunctionshouldshortenthis~${pathHash(tooLongName)}`; + expect(uniqueResourceName).toEqual(expectedName); + expect(uniqueResourceName.length).toEqual(256); + }); + + test('unique resource name removes special characters when they are included in the components names', () => { + const componentsPath = ['I', 'love', 'special', 'characters', '¯\\\_(ツ)_/¯', 'for', 'real', 'though']; + const expectedName = `Ilovespecialcharactersforrealthough${pathHash(componentsPath)}`; + + expect(makeUniqueResourceName(componentsPath, {})).toEqual(expectedName); + }); + + test('unique resource name removes special characters that are not allow listed and leaves the allowed ones', () => { + const componentsPath = ['I-love-special-characters-', '¯\\\_(ツ)_/¯', '-for-real-though-']; + const expectedName = `I-love-special-characters--for-real-though-${pathHash(componentsPath)}`; + + expect(makeUniqueResourceName(componentsPath, { allowedSpecialCharacters: '-' })).toEqual(expectedName); + }); + + test('unique resource name adds in separator and adds hash when separator is provided and name is not too long', () => { + const componentsPath = ['This', 'unique', 'resource', 'name', 'needs', 'a', 'separator']; + const expectedName = `This.*.unique.*.resource.*.name.*.needs.*.a.*.separator.*.${pathHash(componentsPath)}`; + + expect(makeUniqueResourceName(componentsPath, { separator: '.*.' })).toEqual(expectedName); + }); + + test('unique resource name adds in separator, adds hash, and shortens name when separator is provided and name too long', () => { + const componentsPath = ['This', 'unique', 'resource', 'name', 'is', 'longer', 'than', 'allowed']; + const expectedName = `This/unique/resourcelonger/than/allowed/${pathHash(componentsPath)}`; + + expect(makeUniqueResourceName(componentsPath, { maxLength: 48, separator: '/' })).toEqual(expectedName); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/lib/features.ts b/packages/@aws-cdk/cx-api/lib/features.ts index 1bae09a0759c7..08daf2c22d25d 100644 --- a/packages/@aws-cdk/cx-api/lib/features.ts +++ b/packages/@aws-cdk/cx-api/lib/features.ts @@ -255,6 +255,16 @@ export const IAM_MINIMIZE_POLICIES = '@aws-cdk/aws-iam:minimizePolicies'; */ export const VALIDATE_SNAPSHOT_REMOVAL_POLICY = '@aws-cdk/core:validateSnapshotRemovalPolicy'; +/** + * Enable this feature flag to have CodePipeline generate a unique cross account key alias name using the stack name. + * + * Previously, when creating multiple pipelines with similar naming conventions and when crossAccountKeys is true, + * the KMS key alias name created for these pipelines may be the same due to how the uniqueId is generated. + * + * This new implementation creates a stack safe uniqueId for the alias name using the stack name instead of the stack ID. + */ +export const CODEPIPELINE_CROSS_ACCOUNT_KEY_ALIAS_STACK_SAFE_UNIQUE_ID = '@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeUniqueId'; + /** * Flag values that should apply for new projects * @@ -284,6 +294,7 @@ export const FUTURE_FLAGS: { [key: string]: boolean } = { [CHECK_SECRET_USAGE]: true, [IAM_MINIMIZE_POLICIES]: true, [VALIDATE_SNAPSHOT_REMOVAL_POLICY]: true, + [CODEPIPELINE_CROSS_ACCOUNT_KEY_ALIAS_STACK_SAFE_UNIQUE_ID]: true, }; /** diff --git a/packages/@aws-cdk/pipelines/test/pipeline-security.integ.snapshot/PipelineSecurityStack.template.json b/packages/@aws-cdk/pipelines/test/pipeline-security.integ.snapshot/PipelineSecurityStack.template.json index 575b93e6806be..4a198bda45898 100644 --- a/packages/@aws-cdk/pipelines/test/pipeline-security.integ.snapshot/PipelineSecurityStack.template.json +++ b/packages/@aws-cdk/pipelines/test/pipeline-security.integ.snapshot/PipelineSecurityStack.template.json @@ -212,7 +212,7 @@ "TestPipelineArtifactsBucketEncryptionKeyAliasE8D86DD3": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-pipelinesecuritystacktestpipelinef7060861", + "AliasName": "alias/codepipeline-pipelinesecuritystack-testpipeline-f7060861", "TargetKeyId": { "Fn::GetAtt": [ "TestPipelineArtifactsBucketEncryptionKey13258842", diff --git a/packages/@aws-cdk/pipelines/test/pipeline-with-assets-single-upload.integ.snapshot/PipelineStack.template.json b/packages/@aws-cdk/pipelines/test/pipeline-with-assets-single-upload.integ.snapshot/PipelineStack.template.json index 4bc78e4f6e723..00b5ffe04a975 100644 --- a/packages/@aws-cdk/pipelines/test/pipeline-with-assets-single-upload.integ.snapshot/PipelineStack.template.json +++ b/packages/@aws-cdk/pipelines/test/pipeline-with-assets-single-upload.integ.snapshot/PipelineStack.template.json @@ -212,7 +212,7 @@ "PipelineArtifactsBucketEncryptionKeyAlias94A07392": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-pipelinestackpipelinee95eedaa", + "AliasName": "alias/codepipeline-pipelinestack-pipeline-e95eedaa", "TargetKeyId": { "Fn::GetAtt": [ "PipelineArtifactsBucketEncryptionKeyF5BF0670", diff --git a/packages/@aws-cdk/pipelines/test/pipeline-with-assets.integ.snapshot/PipelineStack.template.json b/packages/@aws-cdk/pipelines/test/pipeline-with-assets.integ.snapshot/PipelineStack.template.json index 3b4a804b2bf66..e0d29148d1a94 100644 --- a/packages/@aws-cdk/pipelines/test/pipeline-with-assets.integ.snapshot/PipelineStack.template.json +++ b/packages/@aws-cdk/pipelines/test/pipeline-with-assets.integ.snapshot/PipelineStack.template.json @@ -212,7 +212,7 @@ "PipelineArtifactsBucketEncryptionKeyAlias94A07392": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-pipelinestackpipelinee95eedaa", + "AliasName": "alias/codepipeline-pipelinestack-pipeline-e95eedaa", "TargetKeyId": { "Fn::GetAtt": [ "PipelineArtifactsBucketEncryptionKeyF5BF0670", diff --git a/packages/@aws-cdk/pipelines/test/pipeline.integ.snapshot/PipelineStack.template.json b/packages/@aws-cdk/pipelines/test/pipeline.integ.snapshot/PipelineStack.template.json index 5765c36030f37..973c97e05586a 100644 --- a/packages/@aws-cdk/pipelines/test/pipeline.integ.snapshot/PipelineStack.template.json +++ b/packages/@aws-cdk/pipelines/test/pipeline.integ.snapshot/PipelineStack.template.json @@ -212,7 +212,7 @@ "PipelineArtifactsBucketEncryptionKeyAlias94A07392": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-pipelinestackpipelinee95eedaa", + "AliasName": "alias/codepipeline-pipelinestack-pipeline-e95eedaa", "TargetKeyId": { "Fn::GetAtt": [ "PipelineArtifactsBucketEncryptionKeyF5BF0670",