Skip to content

Commit 963634b

Browse files
authored
fix(ec2): looking up a shared VPC has incorrect account ID in ARN (#24486)
This ensures that when a VPC is looked up using the context provider the resulting VPC has the correct ARN. Previously, the OwnerId field in the output was ignored; this is now passed as part of the environment of the resource (along with its region). Additionally, owner-id as added as a valid field to query on (as defined by the DescribeVpcs documentation). Further, previously the ARN did not include the region or account; this seems to be an error as Arn.format only considers the given component and the stack region/account, so those parameters are now passed as well. If OwnerId is undefined, this will fallback to the AWS::AccountId pseudoparameter (the parent stack's Account ID); it seems like OwnerId should always be defined though (even though the documentation technically says it is not required). Finally, an interface private to the tests had a typo in the name and that is corrected (to MockVpcContextResponse). This follows up on #23982 which was closed by automation after incorrectly detecting it as abandoned. Closes #23865. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 6e77287 commit 963634b

File tree

7 files changed

+129
-3
lines changed

7 files changed

+129
-3
lines changed

packages/@aws-cdk/cx-api/lib/context/vpc.ts

+7
Original file line numberDiff line numberDiff line change
@@ -168,4 +168,11 @@ export interface VpcContextResponse {
168168
* @default - Region of the parent stack
169169
*/
170170
readonly region?: string;
171+
172+
/**
173+
* The ID of the AWS account that owns the VPC.
174+
*
175+
* @default the account id of the parent stack
176+
*/
177+
readonly ownerAccountId?: string;
171178
}

packages/aws-cdk-lib/aws-ec2/lib/vpc-lookup.ts

+7
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,11 @@ export interface VpcLookupOptions {
6868
* @default true
6969
*/
7070
readonly returnVpnGateways?: boolean;
71+
72+
/**
73+
* The ID of the AWS account that owns the VPC
74+
*
75+
* @default the account id of the parent stack
76+
*/
77+
readonly ownerAccountId?: string;
7178
}

packages/aws-cdk-lib/aws-ec2/lib/vpc.ts

+4
Original file line numberDiff line numberDiff line change
@@ -1257,6 +1257,7 @@ export class Vpc extends VpcBase {
12571257
// We give special treatment to some tags
12581258
if (options.vpcId) { filter['vpc-id'] = options.vpcId; }
12591259
if (options.vpcName) { filter['tag:Name'] = options.vpcName; }
1260+
if (options.ownerAccountId) { filter['owner-id'] = options.ownerAccountId; }
12601261
if (options.isDefault !== undefined) {
12611262
filter.isDefault = options.isDefault ? 'true' : 'false';
12621263
}
@@ -2161,13 +2162,16 @@ class LookedUpVpc extends VpcBase {
21612162
constructor(scope: Construct, id: string, props: cxapi.VpcContextResponse, isIncomplete: boolean) {
21622163
super(scope, id, {
21632164
region: props.region,
2165+
account: props.ownerAccountId,
21642166
});
21652167

21662168
this.vpcId = props.vpcId;
21672169
this.vpcArn = Arn.format({
21682170
service: 'ec2',
21692171
resource: 'vpc',
21702172
resourceName: this.vpcId,
2173+
region: this.env.region,
2174+
account: this.env.account,
21712175
}, Stack.of(this));
21722176
this.cidr = props.vpcCidrBlock;
21732177
this._vpnGatewayId = props.vpnGatewayId;

packages/aws-cdk-lib/aws-ec2/test/vpc.from-lookup.test.ts

+96-2
Original file line numberDiff line numberDiff line change
@@ -287,17 +287,111 @@ describe('vpc from lookup', () => {
287287
expect(vpc.env.region).toEqual('region-1234');
288288
restoreContextProvider(previous);
289289
});
290+
291+
test('passes owner account id to LookedUpVpc correctly', () => {
292+
const previous = mockVpcContextProviderWith({
293+
vpcId: 'vpc-1234',
294+
subnetGroups: [],
295+
region: 'region-1234',
296+
ownerAccountId: '123456789012',
297+
});
298+
299+
const stack = new Stack();
300+
const vpc = Vpc.fromLookup(stack, 'Vpc', {
301+
vpcId: 'vpc-1234',
302+
});
303+
expect(vpc.env.account).toEqual('123456789012');
304+
restoreContextProvider(previous);
305+
});
306+
307+
test('passes owner account id to context query correctly', () => {
308+
const previous = mockVpcContextProviderWith({
309+
vpcId: 'vpc-1234',
310+
subnetGroups: [],
311+
region: 'region-1234',
312+
ownerAccountId: '123456789012',
313+
}, options => {
314+
expect(options.filter['owner-id']).toEqual('123456789012');
315+
});
316+
317+
const stack = new Stack();
318+
const vpc = Vpc.fromLookup(stack, 'Vpc', {
319+
vpcId: 'vpc-1234',
320+
ownerAccountId: '123456789012',
321+
});
322+
expect(vpc.env.account).toEqual('123456789012');
323+
restoreContextProvider(previous);
324+
});
325+
326+
test('a looked up VPC in a different region shared from an account has correct VPC', () => {
327+
const previous = mockVpcContextProviderWith({
328+
vpcId: 'vpc-1234',
329+
subnetGroups: [],
330+
region: 'region-1234',
331+
ownerAccountId: '123456789012',
332+
});
333+
const stack = new Stack();
334+
const vpc = Vpc.fromLookup(stack, 'Vpc', {
335+
vpcId: 'vpc-1234',
336+
});
337+
expect(stack.resolve(vpc.vpcArn)).toEqual({
338+
'Fn::Join': [
339+
'',
340+
[
341+
'arn:',
342+
{
343+
Ref: 'AWS::Partition',
344+
},
345+
':ec2:region-1234:123456789012:vpc/vpc-1234',
346+
],
347+
],
348+
});
349+
restoreContextProvider(previous);
350+
});
351+
352+
test('a looked up VPC falls back to the parent stack\'s account and region', () => {
353+
const previous = mockVpcContextProviderWith({
354+
vpcId: 'vpc-1234',
355+
subnetGroups: [],
356+
});
357+
const stack = new Stack();
358+
const vpc = Vpc.fromLookup(stack, 'Vpc', {
359+
vpcId: 'vpc-1234',
360+
});
361+
expect(stack.resolve(vpc.vpcArn)).toEqual({
362+
'Fn::Join': [
363+
'',
364+
[
365+
'arn:',
366+
{
367+
Ref: 'AWS::Partition',
368+
},
369+
':ec2:',
370+
{
371+
Ref: 'AWS::Region',
372+
},
373+
':',
374+
{
375+
Ref: 'AWS::AccountId',
376+
},
377+
':vpc/vpc-1234',
378+
],
379+
],
380+
});
381+
restoreContextProvider(previous);
382+
});
290383
});
291384
});
292385

293-
interface MockVcpContextResponse {
386+
interface MockVpcContextResponse {
294387
readonly vpcId: string;
295388
readonly subnetGroups: cxapi.VpcSubnetGroup[];
389+
readonly ownerAccountId?: string;
296390
readonly region?: string;
297391
}
298392

299393
function mockVpcContextProviderWith(
300-
response: MockVcpContextResponse,
394+
response: MockVpcContextResponse,
301395
paramValidator?: (options: cxschema.VpcContextQuery) => void) {
302396
const previous = ContextProvider.getValue;
303397
ContextProvider.getValue = (_scope: Construct, options: GetContextValueOptions) => {

packages/aws-cdk-lib/cx-api/lib/context/vpc.ts

+7
Original file line numberDiff line numberDiff line change
@@ -168,4 +168,11 @@ export interface VpcContextResponse {
168168
* @default - Region of the parent stack
169169
*/
170170
readonly region?: string;
171+
172+
/**
173+
* The ID of the AWS account that owns the VPC.
174+
*
175+
* @default the account id of the parent stack
176+
*/
177+
readonly ownerAccountId?: string;
171178
}

packages/aws-cdk/lib/context-providers/vpcs.ts

+1
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ export class VpcNetworkContextProviderPlugin implements ContextProviderPlugin {
138138
return {
139139
vpcId,
140140
vpcCidrBlock: vpc.CidrBlock!,
141+
ownerAccountId: vpc.OwnerId,
141142
availabilityZones: grouped.azs,
142143
isolatedSubnetIds: collapse(flatMap(findGroups(SubnetType.Isolated, grouped), group => group.subnets.map(s => s.subnetId))),
143144
isolatedSubnetNames: collapse(flatMap(findGroups(SubnetType.Isolated, grouped), group => group.name ? [group.name] : [])),

packages/aws-cdk/test/context-providers/vpcs.test.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ test('looks up the requested VPC', async () => {
7878
expect(result).toEqual({
7979
vpcId: 'vpc-1234567',
8080
vpcCidrBlock: '1.1.1.1/16',
81+
ownerAccountId: '123456789012',
8182
availabilityZones: ['bermuda-triangle-1337'],
8283
isolatedSubnetIds: undefined,
8384
isolatedSubnetNames: undefined,
@@ -234,6 +235,7 @@ test('does not throw when subnet with subnetGroupNameTag is found', async () =>
234235
expect(result).toEqual({
235236
vpcId: 'vpc-1234567',
236237
vpcCidrBlock: '1.1.1.1/16',
238+
ownerAccountId: '123456789012',
237239
availabilityZones: ['bermuda-triangle-1337'],
238240
isolatedSubnetIds: undefined,
239241
isolatedSubnetNames: undefined,
@@ -329,6 +331,7 @@ test('uses the VPC main route table when a subnet has no specific association',
329331
expect(result).toEqual({
330332
vpcId: 'vpc-1234567',
331333
vpcCidrBlock: '1.1.1.1/16',
334+
ownerAccountId: '123456789012',
332335
availabilityZones: ['bermuda-triangle-1337'],
333336
isolatedSubnetIds: undefined,
334337
isolatedSubnetNames: undefined,
@@ -392,6 +395,7 @@ test('Recognize public subnet by route table', async () => {
392395
expect(result).toEqual({
393396
vpcId: 'vpc-1234567',
394397
vpcCidrBlock: '1.1.1.1/16',
398+
ownerAccountId: '123456789012',
395399
availabilityZones: ['bermuda-triangle-1337'],
396400
isolatedSubnetIds: undefined,
397401
isolatedSubnetNames: undefined,
@@ -455,6 +459,7 @@ test('Recognize private subnet by route table', async () => {
455459
expect(result).toEqual({
456460
vpcId: 'vpc-1234567',
457461
vpcCidrBlock: '1.1.1.1/16',
462+
ownerAccountId: '123456789012',
458463
availabilityZones: ['bermuda-triangle-1337'],
459464
isolatedSubnetIds: undefined,
460465
isolatedSubnetNames: undefined,
@@ -506,6 +511,7 @@ test('Recognize isolated subnet by route table', async () => {
506511
expect(result).toEqual({
507512
vpcId: 'vpc-1234567',
508513
vpcCidrBlock: '1.1.1.1/16',
514+
ownerAccountId: '123456789012',
509515
availabilityZones: ['bermuda-triangle-1337'],
510516
isolatedSubnetIds: ['sub-123456'],
511517
isolatedSubnetNames: ['Isolated'],
@@ -532,7 +538,7 @@ function mockVpcLookup(options: VpcLookupOptions) {
532538

533539
AWS.mock('EC2', 'describeVpcs', (params: aws.EC2.DescribeVpcsRequest, cb: AwsCallback<aws.EC2.DescribeVpcsResult>) => {
534540
expect(params.Filters).toEqual([{ Name: 'foo', Values: ['bar'] }]);
535-
return cb(null, { Vpcs: [{ VpcId, CidrBlock: '1.1.1.1/16' }] });
541+
return cb(null, { Vpcs: [{ VpcId, CidrBlock: '1.1.1.1/16', OwnerId: '123456789012' }] });
536542
});
537543

538544
AWS.mock('EC2', 'describeSubnets', (params: aws.EC2.DescribeSubnetsRequest, cb: AwsCallback<aws.EC2.DescribeSubnetsResult>) => {

0 commit comments

Comments
 (0)