@trautonen/cdk-dns-validated-certificate 0.0.52 → 0.1.0
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.
- package/.jsii +90 -73
- package/API.md +129 -65
- package/README.md +31 -4
- package/assets/certificate-requestor.lambda/index.js +116 -37
- package/assets/certificate-requestor.lambda/index.js.map +2 -2
- package/lib/certificate-requestor.lambda.d.ts +8 -4
- package/lib/certificate-requestor.lambda.js +75 -33
- package/lib/dns-validated-certificate.d.ts +73 -40
- package/lib/dns-validated-certificate.js +73 -37
- package/lib/utils.d.ts +4 -0
- package/lib/utils.js +33 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# AWS CDK DNS Validated Certificate
|
|
2
2
|
|
|
3
|
-
CDK does not have a built in construct to manage cross-region or cross-account DNS validated certificates. There's an
|
|
3
|
+
CDK does not have a built in construct to manage cross-region or cross-account DNS validated certificates. There's an
|
|
4
|
+
attempt to work around the issue with a cross region references option for stacks, but it has a lot of issues and still
|
|
5
|
+
does not solve the cross-account use case.
|
|
4
6
|
|
|
5
|
-
This construct solves these problems by managing the certificate as a custom resource and with direct API calls to ACM
|
|
6
|
-
|
|
7
|
-
Currently there's a limitation which does not allow using alternative names for the certificate as it would require mapping of different roles to different hosted zones. This API is currently being developed.
|
|
7
|
+
This construct solves these problems by managing the certificate as a custom resource and with direct API calls to ACM
|
|
8
|
+
and Route53. In the future it will be possible to support not only Route53, but other DNS services too.
|
|
8
9
|
|
|
9
10
|
## Usage for cross-region validation
|
|
10
11
|
|
|
@@ -38,3 +39,29 @@ const certificate = new DnsValidatedCertificate(this, 'CrossAccountCertificate',
|
|
|
38
39
|
validationExternalId: externalId,
|
|
39
40
|
})
|
|
40
41
|
```
|
|
42
|
+
|
|
43
|
+
## Usage for cross-account alternative names validation
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// example.com is validated on same account against managed hosted zone
|
|
47
|
+
// and secondary.com is validated against external hosted zone on other account
|
|
48
|
+
const hostedZoneForMain: route53.IHostedZone = ...
|
|
49
|
+
const hostedZoneForAlternative: route53.IHostedZone =
|
|
50
|
+
route53.HostedZone.fromHostedZoneAttributes(this, 'SecondaryHostedZone', {
|
|
51
|
+
hostedZoneId: 'Z532DGDEDFS123456789',
|
|
52
|
+
zoneName: 'secondary.com'
|
|
53
|
+
})
|
|
54
|
+
const certificate = new DnsValidatedCertificate(this, 'CrossAccountCertificate', {
|
|
55
|
+
domainName: 'example.com',
|
|
56
|
+
alternativeDomainNames: ['secondary.com'],
|
|
57
|
+
validationHostedZones: [{
|
|
58
|
+
hostedZone: hostedZoneForMain
|
|
59
|
+
},{
|
|
60
|
+
hostedZone: hostedZoneForAlternative,
|
|
61
|
+
validationRole: iam.Role.fromRoleArn(
|
|
62
|
+
this, 'SecondaryValidationRole', 'arn:aws:iam::123456789:role/ChangeDnsRecordsRole'
|
|
63
|
+
),
|
|
64
|
+
validationExternalId: 'domain-assume'
|
|
65
|
+
}]
|
|
66
|
+
})
|
|
67
|
+
```
|
|
@@ -51,6 +51,30 @@ var containsSame = (array1, array2) => {
|
|
|
51
51
|
return false;
|
|
52
52
|
return array1.every((v1) => array2.includes(v1));
|
|
53
53
|
};
|
|
54
|
+
var orderBySignificance = (domains) => {
|
|
55
|
+
const copy = [...domains];
|
|
56
|
+
copy.sort((a, b) => {
|
|
57
|
+
const ac = a.split(".").length;
|
|
58
|
+
const bc = b.split(".").length;
|
|
59
|
+
if (ac > bc) {
|
|
60
|
+
return -1;
|
|
61
|
+
}
|
|
62
|
+
if (ac < bc) {
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
65
|
+
return 0;
|
|
66
|
+
});
|
|
67
|
+
return copy;
|
|
68
|
+
};
|
|
69
|
+
var cleanDomainName = (domainName) => {
|
|
70
|
+
if (domainName.endsWith(".")) {
|
|
71
|
+
return domainName.slice(0, -1);
|
|
72
|
+
}
|
|
73
|
+
return domainName;
|
|
74
|
+
};
|
|
75
|
+
var cleanChangeId = (changeId) => {
|
|
76
|
+
return changeId.replace("/change/", "");
|
|
77
|
+
};
|
|
54
78
|
var tryFor = async (maxSeconds, timeoutError, fn) => {
|
|
55
79
|
const startTime = Date.now();
|
|
56
80
|
for (let i = 0; true; i++) {
|
|
@@ -99,21 +123,52 @@ var changeRecordSets = async (route53, action, records, hostedZoneId) => {
|
|
|
99
123
|
}))
|
|
100
124
|
}
|
|
101
125
|
};
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
126
|
+
try {
|
|
127
|
+
const { ChangeInfo } = await route53.send(new import_client_route_53.ChangeResourceRecordSetsCommand(changeRecordSetsInput));
|
|
128
|
+
const changeId = ChangeInfo?.Id;
|
|
129
|
+
const result = await (0, import_client_route_53.waitUntilResourceRecordSetsChanged)({ client: route53, maxWaitTime: 180 }, { Id: changeId });
|
|
130
|
+
if (result.state !== "SUCCESS") {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`Validation records never changed for hosted zone ${hostedZoneId}: [${result.state}] ${result.reason ?? ""}`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
const operation = action === "CREATE" || action === "UPSERT" ? "changed" : "deleted";
|
|
136
|
+
const change = cleanChangeId(changeId);
|
|
137
|
+
console.log(`Validation records succesfully ${operation} for hosted zone ${hostedZoneId} with change id ${change}`);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
if (action === "DELETE" && error instanceof import_client_route_53.InvalidChangeBatch && error.message.includes("not found")) {
|
|
140
|
+
console.log(`All validation records have already been removed by some other certificate`);
|
|
141
|
+
} else {
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
108
144
|
}
|
|
109
|
-
|
|
145
|
+
};
|
|
146
|
+
var getRecordsForZoneNames = (records, zoneNames, result) => {
|
|
147
|
+
const [zoneName, ...restZoneNames] = zoneNames;
|
|
148
|
+
if (!zoneName) {
|
|
149
|
+
return result ?? {};
|
|
150
|
+
}
|
|
151
|
+
const matchingRecords = [];
|
|
152
|
+
const unmatchingRecords = [];
|
|
153
|
+
for (const record of records) {
|
|
154
|
+
const normalizedRecordName = cleanDomainName(record.Name);
|
|
155
|
+
if (normalizedRecordName.endsWith(zoneName)) {
|
|
156
|
+
matchingRecords.push(record);
|
|
157
|
+
} else {
|
|
158
|
+
unmatchingRecords.push(record);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return getRecordsForZoneNames(unmatchingRecords, restZoneNames, {
|
|
162
|
+
...result ?? {},
|
|
163
|
+
[zoneName]: matchingRecords
|
|
164
|
+
});
|
|
110
165
|
};
|
|
111
166
|
var requestCertificate = async (acm, route53, requestId, properties) => {
|
|
112
|
-
const {
|
|
167
|
+
const { DomainName, AlternativeDomainNames, TransparencyLoggingEnabled } = properties;
|
|
113
168
|
console.log(`Requesting certificate for ${DomainName}`);
|
|
114
169
|
const requestCertificateInput = {
|
|
115
170
|
DomainName,
|
|
116
|
-
SubjectAlternativeNames,
|
|
171
|
+
SubjectAlternativeNames: AlternativeDomainNames,
|
|
117
172
|
IdempotencyToken: crypto.createHash("sha256").update(requestId).digest("hex").slice(0, 32),
|
|
118
173
|
ValidationMethod: "DNS",
|
|
119
174
|
Options: {
|
|
@@ -131,12 +186,29 @@ var requestCertificate = async (acm, route53, requestId, properties) => {
|
|
|
131
186
|
const { Certificate } = await acm.send(new import_client_acm.DescribeCertificateCommand(describeCertificateInput));
|
|
132
187
|
return parseDomainValidationRecords(Certificate);
|
|
133
188
|
});
|
|
134
|
-
|
|
189
|
+
const hostedZones = Object.values(properties.ValidationHostedZones);
|
|
190
|
+
const hostedZoneIds = hostedZones.map((zone) => zone.HostedZoneId);
|
|
191
|
+
console.log(
|
|
192
|
+
`Upserting ${validationRecords.length} validation record(s) into hosted zone(s) ${hostedZoneIds.join(", ")}:`
|
|
193
|
+
);
|
|
135
194
|
validationRecords.forEach(
|
|
136
195
|
(record) => console.log(`${record.Name} ${record.Type} ${record.ResourceRecords?.map((rr) => rr.Value).join(",")}`)
|
|
137
196
|
);
|
|
138
|
-
const
|
|
139
|
-
|
|
197
|
+
const recordsForZoneNames = getRecordsForZoneNames(
|
|
198
|
+
validationRecords,
|
|
199
|
+
orderBySignificance(Object.keys(properties.ValidationHostedZones))
|
|
200
|
+
);
|
|
201
|
+
for (const hostedZone of hostedZones) {
|
|
202
|
+
const records = recordsForZoneNames[hostedZone.DomainName];
|
|
203
|
+
if (records.length > 0) {
|
|
204
|
+
await changeRecordSets(
|
|
205
|
+
route53(hostedZone.ValidationRoleArn, hostedZone.ValidationExternalId),
|
|
206
|
+
"UPSERT",
|
|
207
|
+
records,
|
|
208
|
+
hostedZone.HostedZoneId
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
140
212
|
console.log(`Waiting for certificate ${CertificateArn} to validate`);
|
|
141
213
|
const result = await (0, import_client_acm.waitUntilCertificateValidated)({ client: acm, maxWaitTime: 300 }, { CertificateArn });
|
|
142
214
|
if (result.state !== "SUCCESS") {
|
|
@@ -145,7 +217,7 @@ var requestCertificate = async (acm, route53, requestId, properties) => {
|
|
|
145
217
|
console.log(`Certificate ${CertificateArn} successfully validated`);
|
|
146
218
|
return CertificateArn;
|
|
147
219
|
};
|
|
148
|
-
var deleteCertificate = async (acm, route53, certificateArn,
|
|
220
|
+
var deleteCertificate = async (acm, route53, certificateArn, properties) => {
|
|
149
221
|
console.log(`Waiting for certificate ${certificateArn} usage to drain before deletion`);
|
|
150
222
|
const waitUsageMaxSeconds = 600;
|
|
151
223
|
const waitUsageTimeoutError = `Certificate was still in use after ${waitUsageMaxSeconds} seconds`;
|
|
@@ -162,16 +234,25 @@ var deleteCertificate = async (acm, route53, certificateArn, hostedZoneId, clean
|
|
|
162
234
|
});
|
|
163
235
|
console.log("Certificate is unused and will be deleted");
|
|
164
236
|
const validationRecords = parseDomainValidationRecords(certificate);
|
|
165
|
-
if (validationRecords &&
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
237
|
+
if (validationRecords && stringToBoolean(properties.CleanupValidationRecords)) {
|
|
238
|
+
const hostedZones = Object.values(properties.ValidationHostedZones);
|
|
239
|
+
const hostedZoneIds = hostedZones.map((zone) => zone.HostedZoneId);
|
|
240
|
+
console.log(
|
|
241
|
+
`Deleting ${validationRecords.length} validation record(s) from hosted zone(s) ${hostedZoneIds.join(", ")}`
|
|
242
|
+
);
|
|
243
|
+
const recordsForZoneNames = getRecordsForZoneNames(
|
|
244
|
+
validationRecords,
|
|
245
|
+
orderBySignificance(Object.keys(properties.ValidationHostedZones))
|
|
246
|
+
);
|
|
247
|
+
for (const hostedZone of hostedZones) {
|
|
248
|
+
const records = recordsForZoneNames[hostedZone.DomainName];
|
|
249
|
+
if (records.length > 0) {
|
|
250
|
+
await changeRecordSets(
|
|
251
|
+
route53(hostedZone.ValidationRoleArn, hostedZone.ValidationExternalId),
|
|
252
|
+
"DELETE",
|
|
253
|
+
records,
|
|
254
|
+
hostedZone.HostedZoneId
|
|
255
|
+
);
|
|
175
256
|
}
|
|
176
257
|
}
|
|
177
258
|
}
|
|
@@ -193,13 +274,15 @@ var addTags = async (acm, certificateArn, tags) => {
|
|
|
193
274
|
console.log(`All tags successfully added to certificate ${certificateArn}`);
|
|
194
275
|
};
|
|
195
276
|
var shouldRequestNew = (oldProperties, newProperties) => {
|
|
196
|
-
|
|
277
|
+
const oldHostedZoneIds = Object.values(oldProperties.ValidationHostedZones ?? {}).map((zone) => zone.HostedZoneId);
|
|
278
|
+
const newHostedZoneIds = Object.values(newProperties.ValidationHostedZones ?? {}).map((zone) => zone.HostedZoneId);
|
|
279
|
+
if (!containsSame(oldHostedZoneIds, newHostedZoneIds))
|
|
197
280
|
return true;
|
|
198
281
|
if (oldProperties.DomainName !== newProperties.DomainName)
|
|
199
282
|
return true;
|
|
200
|
-
if (oldProperties.
|
|
283
|
+
if (!containsSame(oldProperties.AlternativeDomainNames ?? [], newProperties.AlternativeDomainNames ?? []))
|
|
201
284
|
return true;
|
|
202
|
-
if (
|
|
285
|
+
if (oldProperties.CertificateRegion !== newProperties.CertificateRegion)
|
|
203
286
|
return true;
|
|
204
287
|
if (oldProperties.CleanupValidationRecords !== newProperties.CleanupValidationRecords)
|
|
205
288
|
return true;
|
|
@@ -232,10 +315,12 @@ var assumeRole = (roleArn, externalId) => {
|
|
|
232
315
|
var handler = async (event) => {
|
|
233
316
|
const properties = parseProperties(event.ResourceProperties);
|
|
234
317
|
const acm = new import_client_acm.ACMClient({ region: properties.CertificateRegion, retryMode: "adaptive" });
|
|
235
|
-
const route53 =
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
318
|
+
const route53 = (roleArn, externalId) => {
|
|
319
|
+
return new import_client_route_53.Route53Client({
|
|
320
|
+
retryMode: "adaptive",
|
|
321
|
+
credentials: assumeRole(roleArn, externalId)
|
|
322
|
+
});
|
|
323
|
+
};
|
|
239
324
|
switch (event.RequestType) {
|
|
240
325
|
case "Create": {
|
|
241
326
|
console.log(`Requesting new certificate:
|
|
@@ -273,13 +358,7 @@ ${objectToString(properties)}`);
|
|
|
273
358
|
if (properties.RemovalPolicy === "destroy") {
|
|
274
359
|
console.log(`Deleting old certificate as per removal policy:
|
|
275
360
|
${objectToString(properties)}`);
|
|
276
|
-
await deleteCertificate(
|
|
277
|
-
acm,
|
|
278
|
-
route53,
|
|
279
|
-
certificateArn,
|
|
280
|
-
properties.HostedZoneId,
|
|
281
|
-
stringToBoolean(properties.CleanupValidationRecords)
|
|
282
|
-
);
|
|
361
|
+
await deleteCertificate(acm, route53, certificateArn, properties);
|
|
283
362
|
}
|
|
284
363
|
return {
|
|
285
364
|
PhysicalResourceId: certificateArn,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/certificate-requestor.lambda.ts", "../../src/utils.ts"],
|
|
4
|
-
"sourcesContent": ["import * as crypto from 'crypto'\nimport {\n ACMClient,\n AddTagsToCertificateCommand,\n AddTagsToCertificateCommandInput,\n CertificateDetail,\n DeleteCertificateCommand,\n DeleteCertificateCommandInput,\n DescribeCertificateCommand,\n DescribeCertificateCommandInput,\n RequestCertificateCommand,\n RequestCertificateCommandInput,\n waitUntilCertificateValidated,\n} from '@aws-sdk/client-acm'\nimport {\n ChangeAction,\n ChangeResourceRecordSetsCommand,\n ChangeResourceRecordSetsCommandInput,\n InvalidChangeBatch,\n ResourceRecordSet,\n Route53Client,\n waitUntilResourceRecordSetsChanged,\n} from '@aws-sdk/client-route-53'\nimport { AssumeRoleCommand, AssumeRoleCommandInput, STSClient } from '@aws-sdk/client-sts'\nimport type { AwsCredentialIdentity, Provider } from '@aws-sdk/types'\nimport type { CloudFormationCustomResourceEvent } from 'aws-lambda'\nimport { containsSame, objectToString, stringToBoolean, tryFor } from './utils'\n\nexport type Properties = {\n HostedZoneId: string\n DomainName: string\n SubjectAlternativeNames?: string[]\n CertificateRegion: string\n ValidationRoleArn?: string\n ValidationExternalId?: string\n CleanupValidationRecords: string\n TransparencyLoggingEnabled: string\n Tags?: Record<string, string>\n RemovalPolicy: string\n}\n\nconst parseProperties = (properties: Record<string, any>): Properties => {\n // maybe should actually parse and not just assume\n return properties as unknown as Properties\n}\n\nconst parseDomainValidationRecords = (certificate: CertificateDetail): ResourceRecordSet[] | null => {\n const options = certificate.DomainValidationOptions ?? []\n if (options.length > 0 && options.every((opt) => opt.ResourceRecord?.Name)) {\n const uniqueRecords = [...new Map(options.map((opt) => [opt.ResourceRecord?.Name!, opt.ResourceRecord!])).values()]\n return uniqueRecords.map((record) => {\n return {\n Name: record.Name,\n Type: record.Type,\n TTL: 30,\n ResourceRecords: [\n {\n Value: record.Value,\n },\n ],\n }\n })\n }\n return null\n}\n\nconst changeRecordSets = async (\n route53: Route53Client,\n action: ChangeAction,\n records: ResourceRecordSet[],\n hostedZoneId: string\n): Promise<string> => {\n const changeRecordSetsInput: ChangeResourceRecordSetsCommandInput = {\n HostedZoneId: hostedZoneId,\n ChangeBatch: {\n Changes: records.map((record) => ({\n Action: action,\n ResourceRecordSet: record,\n })),\n },\n }\n const { ChangeInfo } = await route53.send(new ChangeResourceRecordSetsCommand(changeRecordSetsInput))\n const result = await waitUntilResourceRecordSetsChanged({ client: route53, maxWaitTime: 180 }, { Id: ChangeInfo?.Id })\n if (result.state !== 'SUCCESS') {\n throw new Error(\n `Record sets never changed for hosted zone ${hostedZoneId}: [${result.state}] ${result.reason ?? ''}`\n )\n }\n return ChangeInfo?.Id!\n}\n\nconst requestCertificate = async (\n acm: ACMClient,\n route53: Route53Client,\n requestId: string,\n properties: Properties\n): Promise<string> => {\n const { HostedZoneId, DomainName, SubjectAlternativeNames, TransparencyLoggingEnabled } = properties\n\n console.log(`Requesting certificate for ${DomainName}`)\n\n const requestCertificateInput: RequestCertificateCommandInput = {\n DomainName,\n SubjectAlternativeNames: SubjectAlternativeNames,\n IdempotencyToken: crypto.createHash('sha256').update(requestId).digest('hex').slice(0, 32),\n ValidationMethod: 'DNS',\n Options: {\n CertificateTransparencyLoggingPreference: TransparencyLoggingEnabled ? 'ENABLED' : 'DISABLED',\n },\n }\n const { CertificateArn } = await acm.send(new RequestCertificateCommand(requestCertificateInput))\n\n console.log(`Certificate ${CertificateArn} requested`)\n\n const validationMaxSeconds = 180\n const validationTimeoutError = `Domain validation options were not found in ${validationMaxSeconds} seconds`\n const validationRecords = await tryFor(validationMaxSeconds, validationTimeoutError, async () => {\n const describeCertificateInput: DescribeCertificateCommandInput = {\n CertificateArn,\n }\n const { Certificate } = await acm.send(new DescribeCertificateCommand(describeCertificateInput))\n return parseDomainValidationRecords(Certificate!)\n })\n\n console.log(`Upserting ${validationRecords.length} validation record(s) into hosted zone ${HostedZoneId}:`)\n validationRecords.forEach((record) =>\n console.log(`${record.Name} ${record.Type} ${record.ResourceRecords?.map((rr) => rr.Value).join(',')}`)\n )\n const changeId = await changeRecordSets(route53, 'UPSERT', validationRecords, HostedZoneId)\n console.log(`All validation records changed succesfully for change id ${changeId.replace('/change/', '')}`)\n\n console.log(`Waiting for certificate ${CertificateArn} to validate`)\n const result = await waitUntilCertificateValidated({ client: acm, maxWaitTime: 300 }, { CertificateArn })\n if (result.state !== 'SUCCESS') {\n throw new Error(`Certificate failed ${CertificateArn} to validate: [${result.state}] ${result.reason ?? ''}`)\n }\n console.log(`Certificate ${CertificateArn} successfully validated`)\n return CertificateArn!\n}\n\nconst deleteCertificate = async (\n acm: ACMClient,\n route53: Route53Client,\n certificateArn: string,\n hostedZoneId: string,\n cleanupValidationRecords: boolean\n): Promise<void> => {\n console.log(`Waiting for certificate ${certificateArn} usage to drain before deletion`)\n\n const waitUsageMaxSeconds = 600\n const waitUsageTimeoutError = `Certificate was still in use after ${waitUsageMaxSeconds} seconds`\n const certificate = await tryFor(waitUsageMaxSeconds, waitUsageTimeoutError, async () => {\n const describeCertificateInput: DescribeCertificateCommandInput = {\n CertificateArn: certificateArn,\n }\n const { Certificate } = await acm.send(new DescribeCertificateCommand(describeCertificateInput))\n const inUseBy = Certificate?.InUseBy ?? []\n if (inUseBy.length > 0) {\n return null\n }\n return Certificate!\n })\n console.log('Certificate is unused and will be deleted')\n\n const validationRecords = parseDomainValidationRecords(certificate)\n if (validationRecords && cleanupValidationRecords) {\n console.log(`Deleting ${validationRecords.length} validation record(s) from hosted zone ${hostedZoneId}`)\n try {\n const changeId = await changeRecordSets(route53, 'DELETE', validationRecords, hostedZoneId)\n console.log(`All validation records removed successfully for change id ${changeId.replace('/change/', '')}`)\n } catch (error) {\n if (error instanceof InvalidChangeBatch && error.message.includes('not found')) {\n // there's a deletion race condition where some other certificate has already deleted the records\n console.log(`All validation records have already been removed by some other certificate`)\n } else {\n throw error\n }\n }\n }\n\n console.log(`Deleting certificate ${certificateArn} from ACM`)\n const deleteCertificateInput: DeleteCertificateCommandInput = {\n CertificateArn: certificateArn,\n }\n await acm.send(new DeleteCertificateCommand(deleteCertificateInput))\n console.log(`Certificate ${certificateArn} successfully deleted`)\n}\n\nconst addTags = async (acm: ACMClient, certificateArn: string, tags: Record<string, string>) => {\n const tagList = Array.from(Object.entries(tags).map(([Key, Value]) => ({ Key, Value })))\n const addTagsInput: AddTagsToCertificateCommandInput = {\n CertificateArn: certificateArn,\n Tags: tagList,\n }\n\n console.log(`Adding ${tagList.length} tags to certificate ${certificateArn}`)\n await acm.send(new AddTagsToCertificateCommand(addTagsInput))\n console.log(`All tags successfully added to certificate ${certificateArn}`)\n}\n\nconst shouldRequestNew = (oldProperties: Properties, newProperties: Properties): boolean => {\n if (oldProperties.HostedZoneId !== newProperties.HostedZoneId) return true\n if (oldProperties.DomainName !== newProperties.DomainName) return true\n if (oldProperties.CertificateRegion !== newProperties.CertificateRegion) return true\n if (!containsSame(oldProperties.SubjectAlternativeNames ?? [], newProperties.SubjectAlternativeNames ?? []))\n return true\n if (oldProperties.CleanupValidationRecords !== newProperties.CleanupValidationRecords) return true\n if (oldProperties.TransparencyLoggingEnabled !== newProperties.TransparencyLoggingEnabled) return true\n if (oldProperties.RemovalPolicy !== newProperties.RemovalPolicy) return true\n return false\n}\n\nconst assumeRole = (\n roleArn: string | undefined,\n externalId: string | undefined\n): Provider<AwsCredentialIdentity> | undefined => {\n if (!roleArn) {\n return undefined\n }\n return async () => {\n const sts = new STSClient({ retryMode: 'adaptive' })\n const assumeRoleInput: AssumeRoleCommandInput = {\n RoleArn: roleArn,\n RoleSessionName: 'CertificateRequestor',\n ExternalId: externalId,\n }\n const { Credentials } = await sts.send(new AssumeRoleCommand(assumeRoleInput))\n return {\n accessKeyId: Credentials?.AccessKeyId!,\n secretAccessKey: Credentials?.SecretAccessKey!,\n sessionToken: Credentials?.SessionToken!,\n expiration: Credentials?.Expiration,\n }\n }\n}\n\nexport const handler = async (event: CloudFormationCustomResourceEvent) => {\n const properties = parseProperties(event.ResourceProperties)\n\n const acm = new ACMClient({ region: properties.CertificateRegion, retryMode: 'adaptive' })\n const route53 = new Route53Client({\n retryMode: 'adaptive',\n credentials: assumeRole(properties.ValidationRoleArn, properties.ValidationExternalId),\n })\n\n switch (event.RequestType) {\n case 'Create': {\n console.log(`Requesting new certificate:\\n${objectToString(properties)}`)\n const certificateArn = await requestCertificate(acm, route53, event.RequestId, properties)\n if (properties.Tags && Object.entries(properties.Tags).length > 0) {\n await addTags(acm, certificateArn, properties.Tags)\n }\n return {\n PhysicalResourceId: certificateArn,\n Data: {\n Arn: certificateArn,\n },\n }\n }\n case 'Update': {\n let certificateArn = event.PhysicalResourceId\n if (shouldRequestNew(parseProperties(event.OldResourceProperties), properties)) {\n console.log(`Requesting new certificate due to change of properties:\\n${objectToString(properties)}`)\n certificateArn = await requestCertificate(acm, route53, event.RequestId, properties)\n }\n if (properties.Tags && Object.entries(properties.Tags).length > 0) {\n await addTags(acm, certificateArn, properties.Tags)\n }\n return {\n PhysicalResourceId: certificateArn,\n Data: {\n Arn: certificateArn,\n },\n }\n }\n case 'Delete': {\n const certificateArn = event.PhysicalResourceId\n if (properties.RemovalPolicy === 'destroy') {\n console.log(`Deleting old certificate as per removal policy:\\n${objectToString(properties)}`)\n await deleteCertificate(\n acm,\n route53,\n certificateArn,\n properties.HostedZoneId,\n stringToBoolean(properties.CleanupValidationRecords)\n )\n }\n return {\n PhysicalResourceId: certificateArn,\n Data: {\n Arn: certificateArn,\n },\n }\n }\n }\n throw new Error(`Invalid request type`)\n}\n", "export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))\n\nexport const booleanToString = (value: boolean): string => {\n return value ? 'true' : 'false'\n}\n\nexport const stringToBoolean = (value: string): boolean => {\n return value === 'true' ? true : false\n}\n\nexport const objectToString = (value: object): string => {\n return JSON.stringify(value, undefined, 2)\n}\n\nexport const containsSame = <T>(array1: T[], array2: T[]): boolean => {\n if (array1.length !== array2.length) return false\n return array1.every((v1) => array2.includes(v1))\n}\n\nexport const tryFor = async <T>(maxSeconds: number, timeoutError: string, fn: () => Promise<T | null>): Promise<T> => {\n const startTime = Date.now()\n // eslint-disable-next-line no-constant-condition\n for (let i = 0; true; i++) {\n if (Date.now() > startTime + maxSeconds * 1000) {\n throw new Error(timeoutError)\n }\n const result = await fn()\n if (result !== null) {\n return result\n }\n const base = Math.pow(2, i)\n await sleep(Math.random() * base * 50 + base * 150)\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,aAAwB;AACxB,wBAYO;AACP,6BAQO;AACP,wBAAqE;;;ACvB9D,IAAM,QAAQ,CAAC,OAAe,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAM9E,IAAM,kBAAkB,CAAC,UAA2B;AACzD,SAAO,UAAU,SAAS,OAAO;AACnC;AAEO,IAAM,iBAAiB,CAAC,UAA0B;AACvD,SAAO,KAAK,UAAU,OAAO,QAAW,CAAC;AAC3C;AAEO,IAAM,eAAe,CAAI,QAAa,WAAyB;AACpE,MAAI,OAAO,WAAW,OAAO;AAAQ,WAAO;AAC5C,SAAO,OAAO,MAAM,CAAC,OAAO,OAAO,SAAS,EAAE,CAAC;AACjD;AAEO,IAAM,SAAS,OAAU,YAAoB,cAAsB,OAA4C;AACpH,QAAM,YAAY,KAAK,IAAI;AAE3B,WAAS,IAAI,GAAG,MAAM,KAAK;AACzB,QAAI,KAAK,IAAI,IAAI,YAAY,aAAa,KAAM;AAC9C,YAAM,IAAI,MAAM,YAAY;AAAA,IAC9B;AACA,UAAM,SAAS,MAAM,GAAG;AACxB,QAAI,WAAW,MAAM;AACnB,aAAO;AAAA,IACT;AACA,UAAM,OAAO,KAAK,IAAI,GAAG,CAAC;AAC1B,UAAM,MAAM,KAAK,OAAO,IAAI,OAAO,KAAK,OAAO,GAAG;AAAA,EACpD;AACF;;;
|
|
4
|
+
"sourcesContent": ["import * as crypto from 'crypto'\nimport {\n ACMClient,\n AddTagsToCertificateCommand,\n AddTagsToCertificateCommandInput,\n CertificateDetail,\n DeleteCertificateCommand,\n DeleteCertificateCommandInput,\n DescribeCertificateCommand,\n DescribeCertificateCommandInput,\n RequestCertificateCommand,\n RequestCertificateCommandInput,\n waitUntilCertificateValidated,\n} from '@aws-sdk/client-acm'\nimport {\n ChangeAction,\n ChangeResourceRecordSetsCommand,\n ChangeResourceRecordSetsCommandInput,\n InvalidChangeBatch,\n ResourceRecordSet,\n Route53Client,\n waitUntilResourceRecordSetsChanged,\n} from '@aws-sdk/client-route-53'\nimport { AssumeRoleCommand, AssumeRoleCommandInput, STSClient } from '@aws-sdk/client-sts'\nimport type { AwsCredentialIdentity, Provider } from '@aws-sdk/types'\nimport type { CloudFormationCustomResourceEvent } from 'aws-lambda'\nimport {\n cleanChangeId,\n cleanDomainName,\n containsSame,\n objectToString,\n orderBySignificance,\n stringToBoolean,\n tryFor,\n} from './utils'\n\nexport type ValidationHostedZoneProperties = {\n DomainName: string\n HostedZoneId: string\n ValidationRoleArn?: string\n ValidationExternalId?: string\n}\n\nexport type Properties = {\n DomainName: string\n AlternativeDomainNames?: string[]\n ValidationHostedZones: Record<string, ValidationHostedZoneProperties>\n CertificateRegion: string\n CleanupValidationRecords: string\n TransparencyLoggingEnabled: string\n Tags?: Record<string, string>\n RemovalPolicy: string\n}\n\ntype Route53Factory = (roleArn: string | undefined, externalId: string | undefined) => Route53Client\n\nconst parseProperties = (properties: Record<string, any>): Properties => {\n // maybe should actually parse and not just assume\n return properties as unknown as Properties\n}\n\nconst parseDomainValidationRecords = (certificate: CertificateDetail): ResourceRecordSet[] | null => {\n const options = certificate.DomainValidationOptions ?? []\n if (options.length > 0 && options.every((opt) => opt.ResourceRecord?.Name)) {\n const uniqueRecords = [...new Map(options.map((opt) => [opt.ResourceRecord?.Name!, opt.ResourceRecord!])).values()]\n return uniqueRecords.map((record) => {\n return {\n Name: record.Name,\n Type: record.Type,\n TTL: 30,\n ResourceRecords: [\n {\n Value: record.Value,\n },\n ],\n }\n })\n }\n return null\n}\n\nconst changeRecordSets = async (\n route53: Route53Client,\n action: ChangeAction,\n records: ResourceRecordSet[],\n hostedZoneId: string\n): Promise<void> => {\n const changeRecordSetsInput: ChangeResourceRecordSetsCommandInput = {\n HostedZoneId: hostedZoneId,\n ChangeBatch: {\n Changes: records.map((record) => ({\n Action: action,\n ResourceRecordSet: record,\n })),\n },\n }\n try {\n const { ChangeInfo } = await route53.send(new ChangeResourceRecordSetsCommand(changeRecordSetsInput))\n const changeId = ChangeInfo?.Id!\n const result = await waitUntilResourceRecordSetsChanged({ client: route53, maxWaitTime: 180 }, { Id: changeId })\n if (result.state !== 'SUCCESS') {\n throw new Error(\n `Validation records never changed for hosted zone ${hostedZoneId}: [${result.state}] ${result.reason ?? ''}`\n )\n }\n const operation = action === 'CREATE' || action === 'UPSERT' ? 'changed' : 'deleted'\n const change = cleanChangeId(changeId)\n console.log(`Validation records succesfully ${operation} for hosted zone ${hostedZoneId} with change id ${change}`)\n } catch (error: unknown) {\n if (action === 'DELETE' && error instanceof InvalidChangeBatch && error.message.includes('not found')) {\n // there's a deletion race condition where some other certificate has already deleted the records\n console.log(`All validation records have already been removed by some other certificate`)\n } else {\n throw error\n }\n }\n}\n\nconst getRecordsForZoneNames = (\n records: ResourceRecordSet[],\n zoneNames: string[],\n result?: Record<string, ResourceRecordSet[]>\n): Record<string, ResourceRecordSet[]> => {\n const [zoneName, ...restZoneNames] = zoneNames\n if (!zoneName) {\n return result ?? {}\n }\n const matchingRecords: ResourceRecordSet[] = []\n const unmatchingRecords: ResourceRecordSet[] = []\n for (const record of records) {\n const normalizedRecordName = cleanDomainName(record.Name!)\n if (normalizedRecordName.endsWith(zoneName)) {\n matchingRecords.push(record)\n } else {\n unmatchingRecords.push(record)\n }\n }\n return getRecordsForZoneNames(unmatchingRecords, restZoneNames, {\n ...(result ?? {}),\n [zoneName]: matchingRecords,\n })\n}\n\nconst requestCertificate = async (\n acm: ACMClient,\n route53: Route53Factory,\n requestId: string,\n properties: Properties\n): Promise<string> => {\n const { DomainName, AlternativeDomainNames, TransparencyLoggingEnabled } = properties\n\n console.log(`Requesting certificate for ${DomainName}`)\n\n const requestCertificateInput: RequestCertificateCommandInput = {\n DomainName,\n SubjectAlternativeNames: AlternativeDomainNames,\n IdempotencyToken: crypto.createHash('sha256').update(requestId).digest('hex').slice(0, 32),\n ValidationMethod: 'DNS',\n Options: {\n CertificateTransparencyLoggingPreference: TransparencyLoggingEnabled ? 'ENABLED' : 'DISABLED',\n },\n }\n const { CertificateArn } = await acm.send(new RequestCertificateCommand(requestCertificateInput))\n\n console.log(`Certificate ${CertificateArn} requested`)\n\n const validationMaxSeconds = 180\n const validationTimeoutError = `Domain validation options were not found in ${validationMaxSeconds} seconds`\n const validationRecords = await tryFor(validationMaxSeconds, validationTimeoutError, async () => {\n const describeCertificateInput: DescribeCertificateCommandInput = {\n CertificateArn,\n }\n const { Certificate } = await acm.send(new DescribeCertificateCommand(describeCertificateInput))\n return parseDomainValidationRecords(Certificate!)\n })\n\n const hostedZones = Object.values(properties.ValidationHostedZones)\n const hostedZoneIds = hostedZones.map((zone) => zone.HostedZoneId)\n\n console.log(\n `Upserting ${validationRecords.length} validation record(s) into hosted zone(s) ${hostedZoneIds.join(', ')}:`\n )\n validationRecords.forEach((record) =>\n console.log(`${record.Name} ${record.Type} ${record.ResourceRecords?.map((rr) => rr.Value).join(',')}`)\n )\n\n const recordsForZoneNames = getRecordsForZoneNames(\n validationRecords,\n orderBySignificance(Object.keys(properties.ValidationHostedZones))\n )\n for (const hostedZone of hostedZones) {\n const records = recordsForZoneNames[hostedZone.DomainName]\n if (records.length > 0) {\n await changeRecordSets(\n route53(hostedZone.ValidationRoleArn, hostedZone.ValidationExternalId),\n 'UPSERT',\n records,\n hostedZone.HostedZoneId\n )\n }\n }\n\n console.log(`Waiting for certificate ${CertificateArn} to validate`)\n const result = await waitUntilCertificateValidated({ client: acm, maxWaitTime: 300 }, { CertificateArn })\n if (result.state !== 'SUCCESS') {\n throw new Error(`Certificate failed ${CertificateArn} to validate: [${result.state}] ${result.reason ?? ''}`)\n }\n console.log(`Certificate ${CertificateArn} successfully validated`)\n return CertificateArn!\n}\n\nconst deleteCertificate = async (\n acm: ACMClient,\n route53: Route53Factory,\n certificateArn: string,\n properties: Properties\n): Promise<void> => {\n console.log(`Waiting for certificate ${certificateArn} usage to drain before deletion`)\n\n const waitUsageMaxSeconds = 600\n const waitUsageTimeoutError = `Certificate was still in use after ${waitUsageMaxSeconds} seconds`\n const certificate = await tryFor(waitUsageMaxSeconds, waitUsageTimeoutError, async () => {\n const describeCertificateInput: DescribeCertificateCommandInput = {\n CertificateArn: certificateArn,\n }\n const { Certificate } = await acm.send(new DescribeCertificateCommand(describeCertificateInput))\n const inUseBy = Certificate?.InUseBy ?? []\n if (inUseBy.length > 0) {\n return null\n }\n return Certificate!\n })\n console.log('Certificate is unused and will be deleted')\n\n const validationRecords = parseDomainValidationRecords(certificate)\n if (validationRecords && stringToBoolean(properties.CleanupValidationRecords)) {\n const hostedZones = Object.values(properties.ValidationHostedZones)\n const hostedZoneIds = hostedZones.map((zone) => zone.HostedZoneId)\n\n console.log(\n `Deleting ${validationRecords.length} validation record(s) from hosted zone(s) ${hostedZoneIds.join(', ')}`\n )\n\n const recordsForZoneNames = getRecordsForZoneNames(\n validationRecords,\n orderBySignificance(Object.keys(properties.ValidationHostedZones))\n )\n for (const hostedZone of hostedZones) {\n const records = recordsForZoneNames[hostedZone.DomainName]\n if (records.length > 0) {\n await changeRecordSets(\n route53(hostedZone.ValidationRoleArn, hostedZone.ValidationExternalId),\n 'DELETE',\n records,\n hostedZone.HostedZoneId\n )\n }\n }\n }\n\n console.log(`Deleting certificate ${certificateArn} from ACM`)\n const deleteCertificateInput: DeleteCertificateCommandInput = {\n CertificateArn: certificateArn,\n }\n await acm.send(new DeleteCertificateCommand(deleteCertificateInput))\n console.log(`Certificate ${certificateArn} successfully deleted`)\n}\n\nconst addTags = async (acm: ACMClient, certificateArn: string, tags: Record<string, string>) => {\n const tagList = Array.from(Object.entries(tags).map(([Key, Value]) => ({ Key, Value })))\n const addTagsInput: AddTagsToCertificateCommandInput = {\n CertificateArn: certificateArn,\n Tags: tagList,\n }\n\n console.log(`Adding ${tagList.length} tags to certificate ${certificateArn}`)\n await acm.send(new AddTagsToCertificateCommand(addTagsInput))\n console.log(`All tags successfully added to certificate ${certificateArn}`)\n}\n\nconst shouldRequestNew = (oldProperties: Properties, newProperties: Properties): boolean => {\n const oldHostedZoneIds = Object.values(oldProperties.ValidationHostedZones ?? {}).map((zone) => zone.HostedZoneId)\n const newHostedZoneIds = Object.values(newProperties.ValidationHostedZones ?? {}).map((zone) => zone.HostedZoneId)\n if (!containsSame(oldHostedZoneIds, newHostedZoneIds)) return true\n if (oldProperties.DomainName !== newProperties.DomainName) return true\n if (!containsSame(oldProperties.AlternativeDomainNames ?? [], newProperties.AlternativeDomainNames ?? [])) return true\n if (oldProperties.CertificateRegion !== newProperties.CertificateRegion) return true\n if (oldProperties.CleanupValidationRecords !== newProperties.CleanupValidationRecords) return true\n if (oldProperties.TransparencyLoggingEnabled !== newProperties.TransparencyLoggingEnabled) return true\n if (oldProperties.RemovalPolicy !== newProperties.RemovalPolicy) return true\n return false\n}\n\nconst assumeRole = (\n roleArn: string | undefined,\n externalId: string | undefined\n): Provider<AwsCredentialIdentity> | undefined => {\n if (!roleArn) {\n return undefined\n }\n return async () => {\n const sts = new STSClient({ retryMode: 'adaptive' })\n const assumeRoleInput: AssumeRoleCommandInput = {\n RoleArn: roleArn,\n RoleSessionName: 'CertificateRequestor',\n ExternalId: externalId,\n }\n const { Credentials } = await sts.send(new AssumeRoleCommand(assumeRoleInput))\n return {\n accessKeyId: Credentials?.AccessKeyId!,\n secretAccessKey: Credentials?.SecretAccessKey!,\n sessionToken: Credentials?.SessionToken!,\n expiration: Credentials?.Expiration,\n }\n }\n}\n\nexport const handler = async (event: CloudFormationCustomResourceEvent) => {\n const properties = parseProperties(event.ResourceProperties)\n\n const acm = new ACMClient({ region: properties.CertificateRegion, retryMode: 'adaptive' })\n const route53 = (roleArn: string | undefined, externalId: string | undefined): Route53Client => {\n return new Route53Client({\n retryMode: 'adaptive',\n credentials: assumeRole(roleArn, externalId),\n })\n }\n\n switch (event.RequestType) {\n case 'Create': {\n console.log(`Requesting new certificate:\\n${objectToString(properties)}`)\n const certificateArn = await requestCertificate(acm, route53, event.RequestId, properties)\n if (properties.Tags && Object.entries(properties.Tags).length > 0) {\n await addTags(acm, certificateArn, properties.Tags)\n }\n return {\n PhysicalResourceId: certificateArn,\n Data: {\n Arn: certificateArn,\n },\n }\n }\n case 'Update': {\n let certificateArn = event.PhysicalResourceId\n if (shouldRequestNew(parseProperties(event.OldResourceProperties), properties)) {\n console.log(`Requesting new certificate due to change of properties:\\n${objectToString(properties)}`)\n certificateArn = await requestCertificate(acm, route53, event.RequestId, properties)\n }\n if (properties.Tags && Object.entries(properties.Tags).length > 0) {\n await addTags(acm, certificateArn, properties.Tags)\n }\n return {\n PhysicalResourceId: certificateArn,\n Data: {\n Arn: certificateArn,\n },\n }\n }\n case 'Delete': {\n const certificateArn = event.PhysicalResourceId\n if (properties.RemovalPolicy === 'destroy') {\n console.log(`Deleting old certificate as per removal policy:\\n${objectToString(properties)}`)\n await deleteCertificate(acm, route53, certificateArn, properties)\n }\n return {\n PhysicalResourceId: certificateArn,\n Data: {\n Arn: certificateArn,\n },\n }\n }\n }\n throw new Error(`Invalid request type`)\n}\n", "export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))\n\nexport const booleanToString = (value: boolean): string => {\n return value ? 'true' : 'false'\n}\n\nexport const stringToBoolean = (value: string): boolean => {\n return value === 'true' ? true : false\n}\n\nexport const objectToString = (value: object): string => {\n return JSON.stringify(value, undefined, 2)\n}\n\nexport const containsSame = <T>(array1: T[], array2: T[]): boolean => {\n if (array1.length !== array2.length) return false\n return array1.every((v1) => array2.includes(v1))\n}\n\nexport const orderBySignificance = (domains: string[]): string[] => {\n const copy = [...domains]\n copy.sort((a, b) => {\n const ac = a.split('.').length\n const bc = b.split('.').length\n if (ac > bc) {\n return -1\n }\n if (ac < bc) {\n return 1\n }\n return 0\n })\n return copy\n}\n\nexport const cleanDomainName = (domainName: string): string => {\n if (domainName.endsWith('.')) {\n return domainName.slice(0, -1)\n }\n return domainName\n}\n\nexport const cleanHostedZoneId = (hostedZoneId: string): string => {\n return hostedZoneId.replace(/^\\/hostedzone\\//, '')\n}\n\nexport const cleanChangeId = (changeId: string): string => {\n return changeId.replace('/change/', '')\n}\n\nexport const tryFor = async <T>(maxSeconds: number, timeoutError: string, fn: () => Promise<T | null>): Promise<T> => {\n const startTime = Date.now()\n // eslint-disable-next-line no-constant-condition\n for (let i = 0; true; i++) {\n if (Date.now() > startTime + maxSeconds * 1000) {\n throw new Error(timeoutError)\n }\n const result = await fn()\n if (result !== null) {\n return result\n }\n const base = Math.pow(2, i)\n await sleep(Math.random() * base * 50 + base * 150)\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import type { CloudFormationCustomResourceEvent } from 'aws-lambda';
|
|
2
|
-
export type
|
|
3
|
-
HostedZoneId: string;
|
|
2
|
+
export type ValidationHostedZoneProperties = {
|
|
4
3
|
DomainName: string;
|
|
5
|
-
|
|
6
|
-
CertificateRegion: string;
|
|
4
|
+
HostedZoneId: string;
|
|
7
5
|
ValidationRoleArn?: string;
|
|
8
6
|
ValidationExternalId?: string;
|
|
7
|
+
};
|
|
8
|
+
export type Properties = {
|
|
9
|
+
DomainName: string;
|
|
10
|
+
AlternativeDomainNames?: string[];
|
|
11
|
+
ValidationHostedZones: Record<string, ValidationHostedZoneProperties>;
|
|
12
|
+
CertificateRegion: string;
|
|
9
13
|
CleanupValidationRecords: string;
|
|
10
14
|
TransparencyLoggingEnabled: string;
|
|
11
15
|
Tags?: Record<string, string>;
|