Skip to content

Commit 5effc9f

Browse files
authored
fix: CloudFormation timeout if $AWS_ENDPOINT_URL_S3 is set during cdk deploy (#933)
The CLI asks the SDK for the URL to an S3 bucket, in order to pass it to CloudFormation. CloudFormation will then attempt to contact S3 on that URL in order to download the template. A feature of the the SDK is to respect the `$AWS_ENDPOINT_URL_S3` environment variable, which can be used to override the S3 endpoint that the SDK will hit; you might use this if you have private VPC endpoints for a number of AWS services. The problem arises that `$AWS_ENDPOINT_URL_S3` also affects the URL that the CDK CLI passes to CloudFormation. This will most likely be an endpoint that is not routable for CloudFormation like `https://vpce-xxx.s3.us-east-1.vpce.amazonaws.com`, and CloudFormation will time out waiting for the template download. To get around this, we will temporarily unset `$AWS_ENDPOINT_URL_S3` for the duration of calling the SDK to provide us with a URL. --- By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license
1 parent 96fda68 commit 5effc9f

File tree

2 files changed

+62
-10
lines changed

2 files changed

+62
-10
lines changed

packages/@aws-cdk/toolkit-lib/lib/api/cloudformation/template-body-parameter.ts

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,21 @@ export async function makeBodyParameter(
9999
*
100100
* Replaces environment placeholders (which this field may contain),
101101
* and reformats s3://.../... urls into S3 REST URLs (which CloudFormation
102-
* expects)
102+
* expects).
103+
*
104+
* We need to return the official region- and partition-specific URL for AWS S3
105+
* here, so we use the SDK's information about endpoints. At the same time, the
106+
* SDK allows overriding this URL by setting an environment variable
107+
* (specifically $AWS_ENDPOINT_URL_S3) but we want to *not* honor that, because
108+
* there's a 99.9% chance this URL will not be routable from AWS CloudFormation.
109+
*
110+
* To allow for the off chance that someone is running this tool against a
111+
* custom build of CloudFormation that does need a specific S3 endpoint passed
112+
* to it, we'll introduce a new environment variable that we'll respect instead:
113+
*
114+
* AWS_ENDPOINT_URL_S3_FOR_CLOUDFORMATION
103115
*/
104-
async function restUrlFromManifest(url: string, environment: Environment): Promise<string> {
116+
export async function restUrlFromManifest(url: string, environment: Environment): Promise<string> {
105117
const doNotUseMarker = '**DONOTUSE**';
106118
const region = environment.region;
107119
// This URL may contain placeholders, so still substitute those.
@@ -127,13 +139,26 @@ async function restUrlFromManifest(url: string, environment: Environment): Promi
127139
const bucketName = s3Url[1];
128140
const objectKey = s3Url[2];
129141

130-
// SDK v3 no longer allows for getting endpoints from only region.
131-
// A command and client config must now be provided.
132-
const s3 = new S3Client({ region });
133-
const endpoint = await getEndpointFromInstructions({}, HeadObjectCommand, {
134-
...s3.config,
135-
});
136-
endpoint.url.hostname;
142+
const originalOverrideS3Endpoint = process.env.AWS_ENDPOINT_URL_S3;
143+
setEnv('AWS_ENDPOINT_URL_S3', process.env.AWS_ENDPOINT_URL_S3_FOR_CLOUDFORMATION);
144+
try {
145+
// SDK v3 no longer allows for getting endpoints from only region.
146+
// A command and client config must now be provided.
147+
const s3 = new S3Client({ region });
148+
const endpoint = await getEndpointFromInstructions({}, HeadObjectCommand, {
149+
...s3.config,
150+
});
151+
152+
return `${endpoint.url.origin}/${bucketName}/${objectKey}`;
153+
} finally {
154+
setEnv('AWS_ENDPOINT_URL_S3', originalOverrideS3Endpoint);
155+
}
156+
}
137157

138-
return `${endpoint.url.origin}/${bucketName}/${objectKey}`;
158+
function setEnv(name: string, value: string | undefined) {
159+
if (value) {
160+
process.env[name] = value;
161+
} else {
162+
delete process.env[name];
163+
}
139164
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { restUrlFromManifest } from '../../../lib/api/cloudformation/template-body-parameter';
2+
3+
test('restUrlFromManifest ignores AWS_ENDPOINT_URL_S3', async () => {
4+
process.env.AWS_ENDPOINT_URL_S3 = 'https://boop.com/';
5+
try {
6+
await expect(restUrlFromManifest('s3://my-bucket/object', {
7+
account: '123456789012',
8+
region: 'us-east-1',
9+
name: 'env',
10+
})).resolves.toEqual('https://s3.us-east-1.amazonaws.com/my-bucket/object');
11+
} finally {
12+
delete process.env.AWS_ENDPOINT_URL_S3;
13+
}
14+
});
15+
16+
test('restUrlFromManifest respects AWS_ENDPOINT_URL_S3_FOR_CLOUDFORMATION', async () => {
17+
process.env.AWS_ENDPOINT_URL_S3_FOR_CLOUDFORMATION = 'https://boop.com/';
18+
try {
19+
await expect(restUrlFromManifest('s3://my-bucket/object', {
20+
account: '123456789012',
21+
region: 'us-east-1',
22+
name: 'env',
23+
})).resolves.toEqual('https://boop.com/my-bucket/object');
24+
} finally {
25+
delete process.env.AWS_ENDPOINT_URL_S3_FOR_CLOUDFORMATION;
26+
}
27+
});

0 commit comments

Comments
 (0)