sst 2.36.4 → 2.36.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,549 +0,0 @@
1
- "use strict";
2
-
3
- const aws = require("aws-sdk");
4
-
5
- const defaultSleep = function (ms) {
6
- return new Promise((resolve) => setTimeout(resolve, ms));
7
- };
8
-
9
- // These are used for test purposes only
10
- let defaultResponseURL;
11
- let waiter;
12
- let sleep = defaultSleep;
13
- let random = Math.random;
14
- let maxAttempts = 10;
15
-
16
- /**
17
- * Upload a CloudFormation response object to S3.
18
- *
19
- * @param {object} event the Lambda event payload received by the handler function
20
- * @param {object} context the Lambda context received by the handler function
21
- * @param {string} responseStatus the response status, either 'SUCCESS' or 'FAILED'
22
- * @param {string} physicalResourceId CloudFormation physical resource ID
23
- * @param {object} [responseData] arbitrary response data object
24
- * @param {string} [reason] reason for failure, if any, to convey to the user
25
- * @returns {Promise} Promise that is resolved on success, or rejected on connection error or HTTP error response
26
- */
27
- let report = function (
28
- event,
29
- context,
30
- responseStatus,
31
- physicalResourceId,
32
- responseData,
33
- reason
34
- ) {
35
- return new Promise((resolve, reject) => {
36
- const https = require("https");
37
- const { URL } = require("url");
38
-
39
- var responseBody = JSON.stringify({
40
- Status: responseStatus,
41
- Reason: reason,
42
- PhysicalResourceId: physicalResourceId || context.logStreamName,
43
- StackId: event.StackId,
44
- RequestId: event.RequestId,
45
- LogicalResourceId: event.LogicalResourceId,
46
- Data: responseData,
47
- });
48
-
49
- const parsedUrl = new URL(event.ResponseURL || defaultResponseURL);
50
- const options = {
51
- hostname: parsedUrl.hostname,
52
- port: 443,
53
- path: parsedUrl.pathname + parsedUrl.search,
54
- method: "PUT",
55
- headers: {
56
- "Content-Type": "",
57
- "Content-Length": responseBody.length,
58
- },
59
- };
60
-
61
- https
62
- .request(options)
63
- .on("error", reject)
64
- .on("response", (res) => {
65
- res.resume();
66
- if (res.statusCode >= 400) {
67
- reject(
68
- new Error(
69
- `Server returned error ${res.statusCode}: ${res.statusMessage}`
70
- )
71
- );
72
- } else {
73
- resolve();
74
- }
75
- })
76
- .end(responseBody, "utf8");
77
- });
78
- };
79
-
80
- /**
81
- * Adds tags to an existing certificate
82
- *
83
- * @param {string} certificateArn the ARN of the certificate to add tags to
84
- * @param {string} region the region the certificate exists in
85
- * @param {map} tags Tags to add to the requested certificate
86
- */
87
- const addTags = async function (certificateArn, region, tags) {
88
- const result = Array.from(Object.entries(tags)).map(([Key, Value]) => ({
89
- Key,
90
- Value,
91
- }));
92
- const acm = new aws.ACM({ region });
93
-
94
- await acm
95
- .addTagsToCertificate({
96
- CertificateArn: certificateArn,
97
- Tags: result,
98
- })
99
- .promise();
100
- };
101
-
102
- /**
103
- * Requests a public certificate from AWS Certificate Manager, using DNS validation.
104
- * The hosted zone ID must refer to a **public** Route53-managed DNS zone that is authoritative
105
- * for the suffix of the certificate's Common Name (CN). For example, if the CN is
106
- * `*.example.com`, the hosted zone ID must point to a Route 53 zone authoritative
107
- * for `example.com`.
108
- *
109
- * @param {string} requestId the CloudFormation request ID
110
- * @param {string} domainName the Common Name (CN) field for the requested certificate
111
- * @param {string} hostedZoneId the Route53 Hosted Zone ID
112
- * @returns {string} Validated certificate ARN
113
- */
114
- const requestCertificate = async function (
115
- requestId,
116
- domainName,
117
- subjectAlternativeNames,
118
- certificateTransparencyLoggingPreference,
119
- hostedZoneId,
120
- region,
121
- route53Endpoint
122
- ) {
123
- const crypto = require("crypto");
124
- const acm = new aws.ACM({ region });
125
- const route53 = route53Endpoint
126
- ? new aws.Route53({ endpoint: route53Endpoint })
127
- : new aws.Route53();
128
- if (waiter) {
129
- // Used by the test suite, since waiters aren't mockable yet
130
- route53.waitFor = acm.waitFor = waiter;
131
- }
132
-
133
- console.log(`Requesting certificate for ${domainName}`);
134
-
135
- const reqCertResponse = await acm
136
- .requestCertificate({
137
- DomainName: domainName,
138
- SubjectAlternativeNames: subjectAlternativeNames,
139
- Options: {
140
- CertificateTransparencyLoggingPreference:
141
- certificateTransparencyLoggingPreference,
142
- },
143
- IdempotencyToken: crypto
144
- .createHash("sha256")
145
- .update(requestId)
146
- .digest("hex")
147
- .slice(0, 32),
148
- ValidationMethod: "DNS",
149
- })
150
- .promise();
151
-
152
- console.log(`Certificate ARN: ${reqCertResponse.CertificateArn}`);
153
-
154
- console.log("Waiting for ACM to provide DNS records for validation...");
155
-
156
- let records = [];
157
- for (let attempt = 0; attempt < maxAttempts && !records.length; attempt++) {
158
- const { Certificate } = await acm
159
- .describeCertificate({
160
- CertificateArn: reqCertResponse.CertificateArn,
161
- })
162
- .promise();
163
-
164
- records = getDomainValidationRecords(Certificate);
165
- if (!records.length) {
166
- // Exponential backoff with jitter based on 200ms base
167
- // component of backoff fixed to ensure minimum total wait time on
168
- // slow targets.
169
- const base = Math.pow(2, attempt);
170
- await sleep(random() * base * 50 + base * 150);
171
- }
172
- }
173
- if (!records.length) {
174
- throw new Error(
175
- `Response from describeCertificate did not contain DomainValidationOptions after ${maxAttempts} attempts.`
176
- );
177
- }
178
-
179
- console.log(
180
- `Upserting ${records.length} DNS records into zone ${hostedZoneId}:`
181
- );
182
-
183
- await commitRoute53Records(route53, records, hostedZoneId);
184
-
185
- console.log("Waiting for validation...");
186
- await acm
187
- .waitFor("certificateValidated", {
188
- // Wait up to 9 minutes and 30 seconds
189
- $waiter: {
190
- delay: 30,
191
- maxAttempts: 19,
192
- },
193
- CertificateArn: reqCertResponse.CertificateArn,
194
- })
195
- .promise();
196
-
197
- return reqCertResponse.CertificateArn;
198
- };
199
-
200
- /**
201
- * Deletes a certificate from AWS Certificate Manager (ACM) by its ARN.
202
- * If the certificate does not exist, the function will return normally.
203
- *
204
- * @param {string} arn The certificate ARN
205
- */
206
- const deleteCertificate = async function (
207
- arn,
208
- region,
209
- hostedZoneId,
210
- route53Endpoint,
211
- cleanupRecords
212
- ) {
213
- const acm = new aws.ACM({ region });
214
- const route53 = route53Endpoint
215
- ? new aws.Route53({ endpoint: route53Endpoint })
216
- : new aws.Route53();
217
- if (waiter) {
218
- // Used by the test suite, since waiters aren't mockable yet
219
- route53.waitFor = acm.waitFor = waiter;
220
- }
221
-
222
- try {
223
- console.log(`Waiting for certificate ${arn} to become unused`);
224
-
225
- let inUseByResources;
226
- let records = [];
227
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
228
- const { Certificate } = await acm
229
- .describeCertificate({
230
- CertificateArn: arn,
231
- })
232
- .promise();
233
-
234
- if (cleanupRecords) {
235
- records = getDomainValidationRecords(Certificate);
236
- }
237
- inUseByResources = Certificate.InUseBy || [];
238
-
239
- if (inUseByResources.length || !records.length) {
240
- // Exponential backoff with jitter based on 200ms base
241
- // component of backoff fixed to ensure minimum total wait time on
242
- // slow targets.
243
- const base = Math.pow(2, attempt);
244
- await sleep(random() * base * 50 + base * 150);
245
- } else {
246
- break;
247
- }
248
- }
249
-
250
- if (inUseByResources.length) {
251
- throw new Error(
252
- `Response from describeCertificate did not contain an empty InUseBy list after ${maxAttempts} attempts.`
253
- );
254
- }
255
- if (cleanupRecords && !records.length) {
256
- throw new Error(
257
- `Response from describeCertificate did not contain DomainValidationOptions after ${maxAttempts} attempts.`
258
- );
259
- }
260
-
261
- console.log(`Deleting certificate ${arn}`);
262
-
263
- await acm
264
- .deleteCertificate({
265
- CertificateArn: arn,
266
- })
267
- .promise();
268
-
269
- if (cleanupRecords) {
270
- console.log(
271
- `Deleting ${records.length} DNS records from zone ${hostedZoneId}:`
272
- );
273
-
274
- await commitRoute53Records(route53, records, hostedZoneId, "DELETE");
275
- }
276
- } catch (err) {
277
- if (err.name !== "ResourceNotFoundException") {
278
- throw err;
279
- }
280
- }
281
- };
282
-
283
- /**
284
- * Retrieve the unique domain validation options as records to be upserted (or deleted) from Route53.
285
- *
286
- * Returns an empty array ([]) if the domain validation options is empty or the records are not yet ready.
287
- */
288
- function getDomainValidationRecords(certificate) {
289
- const options = certificate.DomainValidationOptions || [];
290
- // Ensure all records are ready; there is (at least a theory there's) a chance of a partial response here in rare cases.
291
- if (
292
- options.length > 0 &&
293
- options.every((opt) => opt && !!opt.ResourceRecord)
294
- ) {
295
- // some alternative names will produce the same validation record
296
- // as the main domain (eg. example.com + *.example.com)
297
- // filtering duplicates to avoid errors with adding the same record
298
- // to the route53 zone twice
299
- const unique = options
300
- .map((val) => val.ResourceRecord)
301
- .reduce((acc, cur) => {
302
- acc[cur.Name] = cur;
303
- return acc;
304
- }, {});
305
- return Object.keys(unique)
306
- .sort()
307
- .map((key) => unique[key]);
308
- }
309
- return [];
310
- }
311
-
312
- /**
313
- * Execute Route53 ChangeResourceRecordSets for a set of records within a Hosted Zone,
314
- * and wait for the records to commit. Defaults to an 'UPSERT' action.
315
- */
316
- async function commitRoute53Records(
317
- route53,
318
- records,
319
- hostedZoneId,
320
- action = "UPSERT"
321
- ) {
322
- const changeBatch = await route53
323
- .changeResourceRecordSets({
324
- ChangeBatch: {
325
- Changes: records.map((record) => {
326
- console.log(`${record.Name} ${record.Type} ${record.Value}`);
327
- return {
328
- Action: action,
329
- ResourceRecordSet: {
330
- Name: record.Name,
331
- Type: record.Type,
332
- TTL: 60,
333
- ResourceRecords: [
334
- {
335
- Value: record.Value,
336
- },
337
- ],
338
- },
339
- };
340
- }),
341
- },
342
- HostedZoneId: hostedZoneId,
343
- })
344
- .promise();
345
-
346
- console.log("Waiting for DNS records to commit...");
347
- await route53
348
- .waitFor("resourceRecordSetsChanged", {
349
- // Wait up to 5 minutes
350
- $waiter: {
351
- delay: 30,
352
- maxAttempts: 10,
353
- },
354
- Id: changeBatch.ChangeInfo.Id,
355
- })
356
- .promise();
357
- }
358
-
359
- /**
360
- * Determines whether an update request should request a new certificate
361
- *
362
- * @param {map} oldParams the previously process request parameters
363
- * @param {map} newParams the current process request parameters
364
- * @param {string} physicalResourceId the physicalResourceId
365
- * @returns {boolean} whether or not to request a new certificate
366
- */
367
- function shouldUpdate(oldParams, newParams, physicalResourceId) {
368
- if (!oldParams) return true;
369
- if (oldParams.DomainName !== newParams.DomainName) return true;
370
- if (oldParams.SubjectAlternativeNames !== newParams.SubjectAlternativeNames)
371
- return true;
372
- if (
373
- oldParams.CertificateTransparencyLoggingPreference !==
374
- newParams.CertificateTransparencyLoggingPreference
375
- )
376
- return true;
377
- if (oldParams.HostedZoneId !== newParams.HostedZoneId) return true;
378
- if (oldParams.Region !== newParams.Region) return true;
379
- if (!physicalResourceId || !physicalResourceId.startsWith("arn:"))
380
- return true;
381
- return false;
382
- }
383
-
384
- /**
385
- * Main handler, invoked by Lambda
386
- */
387
- exports.certificateRequestHandler = async function (event, context) {
388
- var responseData = {};
389
- var physicalResourceId;
390
- var certificateArn;
391
- async function processRequest() {
392
- certificateArn = await requestCertificate(
393
- event.RequestId,
394
- event.ResourceProperties.DomainName,
395
- event.ResourceProperties.SubjectAlternativeNames,
396
- event.ResourceProperties.CertificateTransparencyLoggingPreference,
397
- event.ResourceProperties.HostedZoneId,
398
- event.ResourceProperties.Region,
399
- event.ResourceProperties.Route53Endpoint
400
- );
401
- responseData.Arn = physicalResourceId = certificateArn;
402
- }
403
-
404
- try {
405
- switch (event.RequestType) {
406
- case "Create":
407
- await processRequest();
408
- if (
409
- event.ResourceProperties.Tags &&
410
- physicalResourceId.startsWith("arn:")
411
- ) {
412
- await addTags(
413
- physicalResourceId,
414
- event.ResourceProperties.Region,
415
- event.ResourceProperties.Tags
416
- );
417
- }
418
- break;
419
- case "Update":
420
- if (
421
- shouldUpdate(
422
- event.OldResourceProperties,
423
- event.ResourceProperties,
424
- event.PhysicalResourceId
425
- )
426
- ) {
427
- await processRequest();
428
- } else {
429
- responseData.Arn = physicalResourceId = event.PhysicalResourceId;
430
- }
431
- if (
432
- event.ResourceProperties.Tags &&
433
- physicalResourceId.startsWith("arn:")
434
- ) {
435
- await addTags(
436
- physicalResourceId,
437
- event.ResourceProperties.Region,
438
- event.ResourceProperties.Tags
439
- );
440
- }
441
- break;
442
- case "Delete":
443
- physicalResourceId = event.PhysicalResourceId;
444
- const removalPolicy =
445
- event.ResourceProperties.RemovalPolicy ?? "destroy";
446
- // If the resource didn't create correctly, the physical resource ID won't be the
447
- // certificate ARN, so don't try to delete it in that case.
448
- if (
449
- physicalResourceId.startsWith("arn:") &&
450
- removalPolicy === "destroy"
451
- ) {
452
- await deleteCertificate(
453
- physicalResourceId,
454
- event.ResourceProperties.Region,
455
- event.ResourceProperties.HostedZoneId,
456
- event.ResourceProperties.Route53Endpoint,
457
- event.ResourceProperties.CleanupRecords === "true"
458
- );
459
- }
460
- break;
461
- default:
462
- throw new Error(`Unsupported request type ${event.RequestType}`);
463
- }
464
-
465
- console.log(`Uploading SUCCESS response to S3...`);
466
- await report(event, context, "SUCCESS", physicalResourceId, responseData);
467
- console.log("Done.");
468
- } catch (err) {
469
- console.log(`Caught error ${err}. Uploading FAILED message to S3.`);
470
- await report(
471
- event,
472
- context,
473
- "FAILED",
474
- physicalResourceId,
475
- null,
476
- err.message
477
- );
478
- }
479
- };
480
-
481
- /**
482
- * @private
483
- */
484
- exports.withReporter = function (reporter) {
485
- report = reporter;
486
- };
487
-
488
- /**
489
- * @private
490
- */
491
- exports.withDefaultResponseURL = function (url) {
492
- defaultResponseURL = url;
493
- };
494
-
495
- /**
496
- * @private
497
- */
498
- exports.withWaiter = function (w) {
499
- waiter = w;
500
- };
501
-
502
- /**
503
- * @private
504
- */
505
- exports.resetWaiter = function () {
506
- waiter = undefined;
507
- };
508
-
509
- /**
510
- * @private
511
- */
512
- exports.withSleep = function (s) {
513
- sleep = s;
514
- };
515
-
516
- /**
517
- * @private
518
- */
519
- exports.resetSleep = function () {
520
- sleep = defaultSleep;
521
- };
522
-
523
- /**
524
- * @private
525
- */
526
- exports.withRandom = function (r) {
527
- random = r;
528
- };
529
-
530
- /**
531
- * @private
532
- */
533
- exports.resetRandom = function () {
534
- random = Math.random;
535
- };
536
-
537
- /**
538
- * @private
539
- */
540
- exports.withMaxAttempts = function (ma) {
541
- maxAttempts = ma;
542
- };
543
-
544
- /**
545
- * @private
546
- */
547
- exports.resetMaxAttempts = function () {
548
- maxAttempts = 10;
549
- };