-
Notifications
You must be signed in to change notification settings - Fork 4k
/
Copy pathdomain-name.ts
299 lines (262 loc) · 9.87 KB
/
domain-name.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
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
import { Construct } from 'constructs';
import { CfnDomainName } from './apigateway.generated';
import { BasePathMapping, BasePathMappingOptions } from './base-path-mapping';
import { EndpointType, IRestApi } from './restapi';
import { IStage } from './stage';
import * as apigwv2 from '../../aws-apigatewayv2';
import * as acm from '../../aws-certificatemanager';
import { IBucket } from '../../aws-s3';
import { IResource, Names, Resource, Token } from '../../core';
/**
* Options for creating an api mapping
*/
export interface ApiMappingOptions {
/**
* The api path name that callers of the API must provide in the URL after
* the domain name (e.g. `example.com/base-path`). If you specify this
* property, it can't be an empty string.
*
* If this is undefined, a mapping will be added for the empty path. Any request
* that does not match a mapping will get sent to the API that has been mapped
* to the empty path.
*
* @default - map requests from the domain root (e.g. `example.com`).
*/
readonly basePath?: string;
}
/**
* The minimum version of the SSL protocol that you want API Gateway to use for HTTPS connections.
*/
export enum SecurityPolicy {
/** Cipher suite TLS 1.0 */
TLS_1_0 = 'TLS_1_0',
/** Cipher suite TLS 1.2 */
TLS_1_2 = 'TLS_1_2',
}
export interface DomainNameOptions {
/**
* The custom domain name for your API. Uppercase letters are not supported.
*/
readonly domainName: string;
/**
* The reference to an AWS-managed certificate for use by the edge-optimized
* endpoint for the domain name. For "EDGE" domain names, the certificate
* needs to be in the US East (N. Virginia) region.
*/
readonly certificate: acm.ICertificate;
/**
* The type of endpoint for this DomainName.
* @default REGIONAL
*/
readonly endpointType?: EndpointType;
/**
* The Transport Layer Security (TLS) version + cipher suite for this domain name.
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-domainname.html
* @default SecurityPolicy.TLS_1_2
*/
readonly securityPolicy?: SecurityPolicy;
/**
* The mutual TLS authentication configuration for a custom domain name.
* @default - mTLS is not configured.
*/
readonly mtls?: MTLSConfig;
/**
* The base path name that callers of the API must provide in the URL after
* the domain name (e.g. `example.com/base-path`). If you specify this
* property, it can't be an empty string.
*
* @default - map requests from the domain root (e.g. `example.com`).
*/
readonly basePath?: string;
}
export interface DomainNameProps extends DomainNameOptions {
/**
* If specified, all requests to this domain will be mapped to the production
* deployment of this API. If you wish to map this domain to multiple APIs
* with different base paths, use `addBasePathMapping` or `addApiMapping`.
*
* @default - you will have to call `addBasePathMapping` to map this domain to
* API endpoints.
*/
readonly mapping?: IRestApi;
}
export interface IDomainName extends IResource {
/**
* The domain name (e.g. `example.com`)
*
* @attribute DomainName
*/
readonly domainName: string;
/**
* The Route53 alias target to use in order to connect a record set to this domain through an alias.
*
* @attribute DistributionDomainName,RegionalDomainName
*/
readonly domainNameAliasDomainName: string;
/**
* The Route53 hosted zone ID to use in order to connect a record set to this domain through an alias.
*
* @attribute DistributionHostedZoneId,RegionalHostedZoneId
*/
readonly domainNameAliasHostedZoneId: string;
}
export class DomainName extends Resource implements IDomainName {
/**
* Imports an existing domain name.
*/
public static fromDomainNameAttributes(scope: Construct, id: string, attrs: DomainNameAttributes): IDomainName {
class Import extends Resource implements IDomainName {
public readonly domainName = attrs.domainName;
public readonly domainNameAliasDomainName = attrs.domainNameAliasTarget;
public readonly domainNameAliasHostedZoneId = attrs.domainNameAliasHostedZoneId;
}
return new Import(scope, id);
}
public readonly domainName: string;
public readonly domainNameAliasDomainName: string;
public readonly domainNameAliasHostedZoneId: string;
private readonly basePaths = new Set<string | undefined>();
private readonly securityPolicy?: SecurityPolicy;
private readonly endpointType: EndpointType;
constructor(scope: Construct, id: string, props: DomainNameProps) {
super(scope, id);
this.endpointType = props.endpointType || EndpointType.REGIONAL;
const edge = this.endpointType === EndpointType.EDGE;
this.securityPolicy = props.securityPolicy;
if (!Token.isUnresolved(props.domainName) && /[A-Z]/.test(props.domainName)) {
throw new Error(`Domain name does not support uppercase letters. Got: ${props.domainName}`);
}
const mtlsConfig = this.configureMTLS(props.mtls);
const resource = new CfnDomainName(this, 'Resource', {
domainName: props.domainName,
certificateArn: edge ? props.certificate.certificateArn : undefined,
regionalCertificateArn: edge ? undefined : props.certificate.certificateArn,
endpointConfiguration: { types: [this.endpointType] },
mutualTlsAuthentication: mtlsConfig,
securityPolicy: props.securityPolicy,
});
this.domainName = resource.ref;
this.domainNameAliasDomainName = edge
? resource.attrDistributionDomainName
: resource.attrRegionalDomainName;
this.domainNameAliasHostedZoneId = edge
? resource.attrDistributionHostedZoneId
: resource.attrRegionalHostedZoneId;
const multiLevel = this.validateBasePath(props.basePath);
if (props.mapping && !multiLevel) {
this.addBasePathMapping(props.mapping, {
basePath: props.basePath,
});
} else if (props.mapping && multiLevel) {
this.addApiMapping(props.mapping.deploymentStage, {
basePath: props.basePath,
});
}
}
private validateBasePath(path?: string): boolean {
if (this.isMultiLevel(path)) {
if (this.endpointType === EndpointType.EDGE) {
throw new Error('multi-level basePath is only supported when endpointType is EndpointType.REGIONAL');
}
if (this.securityPolicy && this.securityPolicy !== SecurityPolicy.TLS_1_2) {
throw new Error('securityPolicy must be set to TLS_1_2 if multi-level basePath is provided');
}
return true;
}
return false;
}
private isMultiLevel(path?: string): boolean {
return (path?.split('/').filter(x => !!x) ?? []).length >= 2;
}
/**
* Maps this domain to an API endpoint.
*
* This uses the BasePathMapping from ApiGateway v1 which does not support multi-level paths.
*
* If you need to create a mapping for a multi-level path use `addApiMapping` instead.
*
* @param targetApi That target API endpoint, requests will be mapped to the deployment stage.
* @param options Options for mapping to base path with or without a stage
*/
public addBasePathMapping(targetApi: IRestApi, options: BasePathMappingOptions = { }): BasePathMapping {
if (this.basePaths.has(options.basePath)) {
throw new Error(`DomainName ${this.node.id} already has a mapping for path ${options.basePath}`);
}
if (this.isMultiLevel(options.basePath)) {
throw new Error('BasePathMapping does not support multi-level paths. Use "addApiMapping instead.');
}
this.basePaths.add(options.basePath);
const basePath = options.basePath || '/';
const id = `Map:${basePath}=>${Names.nodeUniqueId(targetApi.node)}`;
return new BasePathMapping(this, id, {
domainName: this,
restApi: targetApi,
...options,
});
}
/**
* Maps this domain to an API endpoint.
*
* This uses the ApiMapping from ApiGatewayV2 which supports multi-level paths, but
* also only supports:
* - SecurityPolicy.TLS_1_2
* - EndpointType.REGIONAL
*
* @param targetStage the target API stage.
* @param options Options for mapping to a stage
*/
public addApiMapping(targetStage: IStage, options: ApiMappingOptions = {}): void {
if (this.basePaths.has(options.basePath)) {
throw new Error(`DomainName ${this.node.id} already has a mapping for path ${options.basePath}`);
}
this.validateBasePath(options.basePath);
this.basePaths.add(options.basePath);
const id = `Map:${options.basePath ?? 'none'}=>${Names.nodeUniqueId(targetStage.node)}`;
new apigwv2.CfnApiMapping(this, id, {
apiId: targetStage.restApi.restApiId,
stage: targetStage.stageName,
domainName: this.domainName,
apiMappingKey: options.basePath,
});
}
private configureMTLS(mtlsConfig?: MTLSConfig): CfnDomainName.MutualTlsAuthenticationProperty | undefined {
if (!mtlsConfig) return undefined;
return {
truststoreUri: mtlsConfig.bucket.s3UrlForObject(mtlsConfig.key),
truststoreVersion: mtlsConfig.version,
};
}
}
export interface DomainNameAttributes {
/**
* The domain name (e.g. `example.com`)
*/
readonly domainName: string;
/**
* The Route53 alias target to use in order to connect a record set to this domain through an alias.
*/
readonly domainNameAliasTarget: string;
/**
* The Route53 hosted zone ID to use in order to connect a record set to this domain through an alias.
*/
readonly domainNameAliasHostedZoneId: string;
}
/**
* The mTLS authentication configuration for a custom domain name.
*/
export interface MTLSConfig {
/**
* The bucket that the trust store is hosted in.
*/
readonly bucket: IBucket;
/**
* The key in S3 to look at for the trust store.
*/
readonly key: string;
/**
* The version of the S3 object that contains your truststore.
* To specify a version, you must have versioning enabled for the S3 bucket.
* @default - latest version
*/
readonly version?: string;
}