-
Notifications
You must be signed in to change notification settings - Fork 4.1k
/
Copy pathcertificate.ts
273 lines (243 loc) · 7.87 KB
/
certificate.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
import * as route53 from '@aws-cdk/aws-route53';
import { Construct, IResource, Resource, Token } from '@aws-cdk/core';
import { CfnCertificate } from './certificatemanager.generated';
import { apexDomain } from './util';
/**
* Represents a certificate in AWS Certificate Manager
*/
export interface ICertificate extends IResource {
/**
* The certificate's ARN
*
* @attribute
*/
readonly certificateArn: string;
}
/**
* Properties for your certificate
*/
export interface CertificateProps {
/**
* Fully-qualified domain name to request a certificate for.
*
* May contain wildcards, such as ``*.domain.com``.
*/
readonly domainName: string;
/**
* Alternative domain names on your certificate.
*
* Use this to register alternative domain names that represent the same site.
*
* @default - No additional FQDNs will be included as alternative domain names.
*/
readonly subjectAlternativeNames?: string[];
/**
* What validation domain to use for every requested domain.
*
* Has to be a superdomain of the requested domain.
*
* @default - Apex domain is used for every domain that's not overridden.
* @deprecated use `validation` instead.
*/
readonly validationDomains?: {[domainName: string]: string};
/**
* Validation method used to assert domain ownership
*
* @default ValidationMethod.EMAIL
* @deprecated use `validation` instead.
*/
readonly validationMethod?: ValidationMethod;
/**
* How to validate this certifcate
*
* @default CertificateValidation.fromEmail()
*/
readonly validation?: CertificateValidation;
}
/**
* Properties for certificate validation
*/
export interface CertificationValidationProps {
/**
* Validation method
*
* @default ValidationMethod.EMAIL
*/
readonly method?: ValidationMethod;
/**
* Hosted zone to use for DNS validation
*
* @default - use email validation
*/
readonly hostedZone?: route53.IHostedZone;
/**
* A map of hosted zones to use for DNS validation
*
* @default - use `hostedZone`
*/
readonly hostedZones?: { [domainName: string]: route53.IHostedZone };
/**
* Validation domains to use for email validation
*
* @default - Apex domain
*/
readonly validationDomains?: { [domainName: string]: string };
}
/**
* How to validate a certificate
*/
export class CertificateValidation {
/**
* Validate the certifcate with DNS
*
* IMPORTANT: If `hostedZone` is not specified, DNS records must be added
* manually and the stack will not complete creating until the records are
* added.
*
* @param hostedZone the hosted zone where DNS records must be created
*/
public static fromDns(hostedZone?: route53.IHostedZone) {
return new CertificateValidation({
method: ValidationMethod.DNS,
hostedZone,
});
}
/**
* Validate the certifcate with automatically created DNS records in multiple
* Amazon Route 53 hosted zones.
*
* @param hostedZones a map of hosted zones where DNS records must be created
* for the domains in the certificate
*/
public static fromDnsMultiZone(hostedZones: { [domainName: string]: route53.IHostedZone }) {
return new CertificateValidation({
method: ValidationMethod.DNS,
hostedZones,
});
}
/**
* Validate the certifcate with Email
*
* IMPORTANT: if you are creating a certificate as part of your stack, the stack
* will not complete creating until you read and follow the instructions in the
* email that you will receive.
*
* ACM will send validation emails to the following addresses:
*
* admin@domain.com
* administrator@domain.com
* hostmaster@domain.com
* postmaster@domain.com
* webmaster@domain.com
*
* For every domain that you register.
*
* @param validationDomains a map of validation domains to use for domains in the certificate
*/
public static fromEmail(validationDomains?: { [domainName: string]: string }) {
return new CertificateValidation({
method: ValidationMethod.EMAIL,
validationDomains,
});
}
/**
* The validation method
*/
public readonly method: ValidationMethod;
/** @param props Certification validation properties */
private constructor(public readonly props: CertificationValidationProps) {
this.method = props.method ?? ValidationMethod.EMAIL;
}
}
/**
* A certificate managed by AWS Certificate Manager
*/
export class Certificate extends Resource implements ICertificate {
/**
* Import a certificate
*/
public static fromCertificateArn(scope: Construct, id: string, certificateArn: string): ICertificate {
class Import extends Resource implements ICertificate {
public certificateArn = certificateArn;
}
return new Import(scope, id);
}
/**
* The certificate's ARN
*/
public readonly certificateArn: string;
constructor(scope: Construct, id: string, props: CertificateProps) {
super(scope, id);
let validation: CertificateValidation;
if (props.validation) {
validation = props.validation;
} else { // Deprecated props
if (props.validationMethod === ValidationMethod.DNS) {
validation = CertificateValidation.fromDns();
} else {
validation = CertificateValidation.fromEmail(props.validationDomains);
}
}
const allDomainNames = [props.domainName].concat(props.subjectAlternativeNames || []);
const cert = new CfnCertificate(this, 'Resource', {
domainName: props.domainName,
subjectAlternativeNames: props.subjectAlternativeNames,
domainValidationOptions: renderDomainValidation(validation, allDomainNames),
validationMethod: validation.method,
});
this.certificateArn = cert.ref;
}
}
/**
* Method used to assert ownership of the domain
*/
export enum ValidationMethod {
/**
* Send email to a number of email addresses associated with the domain
*
* @see https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-validate-email.html
*/
EMAIL = 'EMAIL',
/**
* Validate ownership by adding appropriate DNS records
*
* @see https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-validate-dns.html
*/
DNS = 'DNS',
}
// eslint-disable-next-line max-len
function renderDomainValidation(validation: CertificateValidation, domainNames: string[]): CfnCertificate.DomainValidationOptionProperty[] | undefined {
const domainValidation: CfnCertificate.DomainValidationOptionProperty[] = [];
switch (validation.method) {
case ValidationMethod.DNS:
for (const domainName of getUniqueDnsDomainNames(domainNames)) {
const hostedZone = validation.props.hostedZones?.[domainName] ?? validation.props.hostedZone;
if (hostedZone) {
domainValidation.push({ domainName, hostedZoneId: hostedZone.hostedZoneId });
}
}
break;
case ValidationMethod.EMAIL:
for (const domainName of domainNames) {
const validationDomain = validation.props.validationDomains?.[domainName];
if (!validationDomain && Token.isUnresolved(domainName)) {
throw new Error('When using Tokens for domain names, \'validationDomains\' needs to be supplied');
}
domainValidation.push({ domainName, validationDomain: validationDomain ?? apexDomain(domainName) });
}
break;
default:
throw new Error(`Unknown validation method ${validation.method}`);
}
return domainValidation.length !== 0 ? domainValidation : undefined;
}
/**
* Removes wildcard domains (*.example.com) where the base domain (example.com) is present.
* This is because the DNS validation treats them as the same thing, and the automated CloudFormation
* DNS validation errors out with the duplicate records.
*/
function getUniqueDnsDomainNames(domainNames: string[]) {
return domainNames.filter(domain => {
return Token.isUnresolved(domain) || !domain.startsWith('*.') || !domainNames.includes(domain.replace('*.', ''));
});
}