Skip to content

Commit af2680c

Browse files
authored
fix(core): Prevent volatile physical name generation (#2984)
The PhysicalName generation strategy for cross-account/region use-cases could generate names that are subject to collisions/sniping when the account and region were not specified; and those names would also have changed if a stack went from re-locatable to account/region specific, even if the target environment would not have changed. The generator will now throw errors in case the account ID or region is blank or a Token.
1 parent f954128 commit af2680c

File tree

4 files changed

+118
-15
lines changed

4 files changed

+118
-15
lines changed

packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -756,8 +756,9 @@ export = {
756756
const app = new App();
757757

758758
const buildAccount = '901234567890';
759+
const buildRegion = 'bermuda-triangle-1';
759760
const buildStack = new Stack(app, 'BuildStack', {
760-
env: { account: buildAccount },
761+
env: { account: buildAccount, region: buildRegion },
761762
});
762763
const rolePhysicalName = 'ProjectRolePhysicalName';
763764
const projectRole = new iam.Role(buildStack, 'ProjectRole', {
@@ -771,7 +772,7 @@ export = {
771772
});
772773

773774
const pipelineStack = new Stack(app, 'PipelineStack', {
774-
env: { account: '123456789012' },
775+
env: { account: '123456789012', region: 'bermuda-triangle-42' },
775776
});
776777
const sourceBucket = new s3.Bucket(pipelineStack, 'ArtifactBucket', {
777778
bucketName: 'source-bucket',
@@ -826,7 +827,7 @@ export = {
826827
{
827828
"Ref": "AWS::Partition",
828829
},
829-
`:iam::${buildAccount}:role/buildstackebuildactionrole166c75d145cdaa010350`,
830+
`:iam::${buildAccount}:role/buildstackebuildactionrole166c75d1d8be701b1ad8`,
830831
],
831832
],
832833
},
@@ -861,7 +862,7 @@ export = {
861862
{
862863
"Ref": "AWS::Partition",
863864
},
864-
`:s3:::pipelinestackeartifactsbucket5409dc8418216ab8debe`,
865+
':s3:::pipelinestackeartifactsbucket5409dc84ec8d21c5e28c',
865866
],
866867
],
867868
},
@@ -873,7 +874,7 @@ export = {
873874
{
874875
"Ref": "AWS::Partition",
875876
},
876-
`:s3:::pipelinestackeartifactsbucket5409dc8418216ab8debe/*`,
877+
':s3:::pipelinestackeartifactsbucket5409dc84ec8d21c5e28c/*',
877878
],
878879
],
879880
},

packages/@aws-cdk/core/lib/private/physical-name-generator.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ export function generatePhysicalName(resource: IResource): string {
88
const stackPart = new PrefixNamePart(stack.stackName, 25);
99
const idPart = new SuffixNamePart(resource.node.uniqueId, 24);
1010

11-
let region: string | undefined = stack.region;
12-
if (Token.isUnresolved(region)) {
13-
region = undefined;
11+
const region: string = stack.region;
12+
if (Token.isUnresolved(region) || !region) {
13+
throw new Error(`Cannot generate a physical name for ${resource.node.path}, because the region is un-resolved or missing`);
1414
}
1515

16-
let account: string | undefined = stack.account;
17-
if (Token.isUnresolved(account)) {
18-
account = undefined;
16+
const account: string = stack.account;
17+
if (Token.isUnresolved(account) || !account) {
18+
throw new Error(`Cannot generate a physical name for ${resource.node.path}, because the account is un-resolved or missing`);
1919
}
2020

2121
const parts = [stackPart, idPart]
@@ -25,8 +25,8 @@ export function generatePhysicalName(resource: IResource): string {
2525
const sha256 = crypto.createHash('sha256')
2626
.update(stackPart.bareStr)
2727
.update(idPart.bareStr)
28-
.update(region || '')
29-
.update(account || '');
28+
.update(region)
29+
.update(account);
3030
const hash = sha256.digest('hex').slice(0, hashLength);
3131

3232
const ret = [...parts, hash].join('');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import nodeunit = require('nodeunit');
2+
import { App, Aws, Resource, Stack } from '../../lib';
3+
import { generatePhysicalName } from '../../lib/private/physical-name-generator';
4+
5+
export = nodeunit.testCase({
6+
'generates correct physical names'(test: nodeunit.Test) {
7+
const app = new App();
8+
const stack = new Stack(app, 'TestStack', { env: { account: '012345678912', region: 'bermuda-triangle-1' } });
9+
10+
const testResourceA = new TestResource(stack, 'A');
11+
const testResourceB = new TestResource(testResourceA, 'B');
12+
13+
test.equal(generatePhysicalName(testResourceA), 'teststackteststackaa164c141d59b37c1b663');
14+
test.equal(generatePhysicalName(testResourceB), 'teststackteststackab27595cd34d8188283a1f');
15+
16+
test.done();
17+
},
18+
19+
'generates different names in different accounts'(test: nodeunit.Test) {
20+
const appA = new App();
21+
const stackA = new Stack(appA, 'TestStack', { env: { account: '012345678912', region: 'bermuda-triangle-1' } });
22+
const resourceA = new TestResource(stackA, 'Resource');
23+
24+
const appB = new App();
25+
const stackB = new Stack(appB, 'TestStack', { env: { account: '012345678913', region: 'bermuda-triangle-1' } });
26+
const resourceB = new TestResource(stackB, 'Resource');
27+
28+
test.notEqual(generatePhysicalName(resourceA), generatePhysicalName(resourceB));
29+
30+
test.done();
31+
},
32+
33+
'generates different names in different regions'(test: nodeunit.Test) {
34+
const appA = new App();
35+
const stackA = new Stack(appA, 'TestStack', { env: { account: '012345678912', region: 'bermuda-triangle-1' } });
36+
const resourceA = new TestResource(stackA, 'Resource');
37+
38+
const appB = new App();
39+
const stackB = new Stack(appB, 'TestStack', { env: { account: '012345678912', region: 'bermuda-triangle-2' } });
40+
const resourceB = new TestResource(stackB, 'Resource');
41+
42+
test.notEqual(generatePhysicalName(resourceA), generatePhysicalName(resourceB));
43+
44+
test.done();
45+
},
46+
47+
'fails when the region is an unresolved token'(test: nodeunit.Test) {
48+
const app = new App();
49+
const stack = new Stack(app, 'TestStack', { env: { account: '012345678912', region: Aws.REGION } });
50+
const testResource = new TestResource(stack, 'A');
51+
52+
test.throws(() => generatePhysicalName(testResource),
53+
/Cannot generate a physical name for TestStack\/A, because the region is un-resolved or missing/);
54+
55+
test.done();
56+
},
57+
58+
'fails when the region is not provided'(test: nodeunit.Test) {
59+
const app = new App();
60+
const stack = new Stack(app, 'TestStack', { env: { account: '012345678912' } });
61+
const testResource = new TestResource(stack, 'A');
62+
63+
test.throws(() => generatePhysicalName(testResource),
64+
/Cannot generate a physical name for TestStack\/A, because the region is un-resolved or missing/);
65+
66+
test.done();
67+
},
68+
69+
'fails when the account is an unresolved token'(test: nodeunit.Test) {
70+
const app = new App();
71+
const stack = new Stack(app, 'TestStack', { env: { account: Aws.ACCOUNT_ID, region: 'bermuda-triangle-1' } });
72+
const testResource = new TestResource(stack, 'A');
73+
74+
test.throws(() => generatePhysicalName(testResource),
75+
/Cannot generate a physical name for TestStack\/A, because the account is un-resolved or missing/);
76+
77+
test.done();
78+
},
79+
80+
'fails when the account is not provided'(test: nodeunit.Test) {
81+
const app = new App();
82+
const stack = new Stack(app, 'TestStack', { env: { region: 'bermuda-triangle-1' } });
83+
const testResource = new TestResource(stack, 'A');
84+
85+
test.throws(() => generatePhysicalName(testResource),
86+
/Cannot generate a physical name for TestStack\/A, because the account is un-resolved or missing/);
87+
88+
test.done();
89+
},
90+
});
91+
92+
class TestResource extends Resource {}

packages/@aws-cdk/core/test/test.cross-environment-token.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ export = {
1212
const stack1 = new Stack(app, 'Stack1', {
1313
env: {
1414
account: '123456789012',
15+
region: 'bermuda-triangle-1337',
1516
},
1617
});
1718
const myResource = new MyResource(stack1, 'MyResource', 'PhysicalName');
1819

1920
const stack2 = new Stack(app, 'Stack2', {
2021
env: {
2122
account: '234567890123',
23+
region: 'bermuda-triangle-42',
2224
},
2325
});
2426

@@ -56,11 +58,13 @@ export = {
5658
const stack1 = new Stack(app, 'Stack1', {
5759
env: {
5860
account: '123456789012',
61+
region: 'bermuda-triangle-1337',
5962
},
6063
});
6164
const stack2 = new Stack(app, 'Stack2', {
6265
env: {
6366
account: '234567890123',
67+
region: 'bermuda-triangle-42',
6468
},
6569
});
6670

@@ -88,13 +92,15 @@ export = {
8892
const stack1 = new Stack(app, 'Stack1', {
8993
env: {
9094
account: '123456789012',
95+
region: 'bermuda-triangle-1337',
9196
},
9297
});
9398
const myResource = new MyResource(stack1, 'MyResource', PhysicalName.GENERATE_IF_NEEDED);
9499

95100
const stack2 = new Stack(app, 'Stack2', {
96101
env: {
97102
account: '234567890123',
103+
region: 'bermuda-triangle-42',
98104
},
99105
});
100106

@@ -115,7 +121,7 @@ export = {
115121
{
116122
Ref: 'AWS::Partition',
117123
},
118-
':myservice:::my-resource/stack1stack1myresourcec54ced43dab875fcfa49',
124+
':myservice:::my-resource/stack1stack1myresourcec54ced43683ebf9a3c4c',
119125
],
120126
],
121127
},
@@ -132,11 +138,13 @@ export = {
132138
const stack1 = new Stack(app, 'Stack1', {
133139
env: {
134140
account: '123456789012',
141+
region: 'bermuda-triangle-1337',
135142
},
136143
});
137144
const stack2 = new Stack(app, 'Stack2', {
138145
env: {
139146
account: '234567890123',
147+
region: 'bermuda-triangle-42',
140148
},
141149
});
142150

@@ -150,7 +158,7 @@ export = {
150158
test.deepEqual(toCloudFormation(stack2), {
151159
Outputs: {
152160
Output: {
153-
Value: 'stack1stack1myresourcec54ced43dab875fcfa49',
161+
Value: 'stack1stack1myresourcec54ced43683ebf9a3c4c',
154162
},
155163
},
156164
});
@@ -165,11 +173,13 @@ export = {
165173
const stack1 = new Stack(app, 'Stack1', {
166174
env: {
167175
account: '123456789012',
176+
region: 'bermuda-triangle-1337',
168177
},
169178
});
170179
const stack2 = new Stack(app, 'Stack2', {
171180
env: {
172181
account: '234567890123',
182+
region: 'bermuda-triangle-42',
173183
},
174184
});
175185

0 commit comments

Comments
 (0)