-
Notifications
You must be signed in to change notification settings - Fork 4k
/
Copy pathdns-validated-certificate.ts
121 lines (110 loc) · 4.74 KB
/
dns-validated-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
import * as path from 'path';
import * as iam from '@aws-cdk/aws-iam';
import * as lambda from '@aws-cdk/aws-lambda';
import * as route53 from '@aws-cdk/aws-route53';
import * as cdk from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CertificateProps, ICertificate } from './certificate';
/**
* Properties to create a DNS validated certificate managed by AWS Certificate Manager
*
* @experimental
*/
export interface DnsValidatedCertificateProps extends CertificateProps {
/**
* Route 53 Hosted Zone used to perform DNS validation of the request. The zone
* must be authoritative for the domain name specified in the Certificate Request.
*/
readonly hostedZone: route53.IHostedZone;
/**
* AWS region that will host the certificate. This is needed especially
* for certificates used for CloudFront distributions, which require the region
* to be us-east-1.
*
* @default the region the stack is deployed in.
*/
readonly region?: string;
/**
* An endpoint of Route53 service, which is not necessary as AWS SDK could figure
* out the right endpoints for most regions, but for some regions such as those in
* aws-cn partition, the default endpoint is not working now, hence the right endpoint
* need to be specified through this prop.
*
* Route53 is not been officially launched in China, it is only available for AWS
* internal accounts now. To make DnsValidatedCertificate work for internal accounts
* now, a special endpoint needs to be provided.
*
* @default - The AWS SDK will determine the Route53 endpoint to use based on region
*/
readonly route53Endpoint?: string;
/**
* Role to use for the custom resource that creates the validated certificate
*
* @default - A new role will be created
*/
readonly customResourceRole?: iam.IRole;
}
/**
* A certificate managed by AWS Certificate Manager. Will be automatically
* validated using DNS validation against the specified Route 53 hosted zone.
*
* @resource AWS::CertificateManager::Certificate
* @experimental
*/
export class DnsValidatedCertificate extends cdk.Resource implements ICertificate {
public readonly certificateArn: string;
private normalizedZoneName: string;
private hostedZoneId: string;
private domainName: string;
constructor(scope: Construct, id: string, props: DnsValidatedCertificateProps) {
super(scope, id);
this.domainName = props.domainName;
this.normalizedZoneName = props.hostedZone.zoneName;
// Remove trailing `.` from zone name
if (this.normalizedZoneName.endsWith('.')) {
this.normalizedZoneName = this.normalizedZoneName.substring(0, this.normalizedZoneName.length - 1);
}
// Remove any `/hostedzone/` prefix from the Hosted Zone ID
this.hostedZoneId = props.hostedZone.hostedZoneId.replace(/^\/hostedzone\//, '');
const requestorFunction = new lambda.Function(this, 'CertificateRequestorFunction', {
code: lambda.Code.fromAsset(path.resolve(__dirname, '..', 'lambda-packages', 'dns_validated_certificate_handler', 'lib')),
handler: 'index.certificateRequestHandler',
runtime: lambda.Runtime.NODEJS_14_X,
timeout: cdk.Duration.minutes(15),
role: props.customResourceRole,
});
requestorFunction.addToRolePolicy(new iam.PolicyStatement({
actions: ['acm:RequestCertificate', 'acm:DescribeCertificate', 'acm:DeleteCertificate'],
resources: ['*'],
}));
requestorFunction.addToRolePolicy(new iam.PolicyStatement({
actions: ['route53:GetChange'],
resources: ['*'],
}));
requestorFunction.addToRolePolicy(new iam.PolicyStatement({
actions: ['route53:changeResourceRecordSets'],
resources: [`arn:${cdk.Stack.of(requestorFunction).partition}:route53:::hostedzone/${this.hostedZoneId}`],
}));
const certificate = new cdk.CustomResource(this, 'CertificateRequestorResource', {
serviceToken: requestorFunction.functionArn,
properties: {
DomainName: props.domainName,
SubjectAlternativeNames: cdk.Lazy.list({ produce: () => props.subjectAlternativeNames }, { omitEmpty: true }),
HostedZoneId: this.hostedZoneId,
Region: props.region,
Route53Endpoint: props.route53Endpoint,
},
});
this.certificateArn = certificate.getAtt('Arn').toString();
}
protected validate(): string[] {
const errors: string[] = [];
// Ensure the zone name is a parent zone of the certificate domain name
if (!cdk.Token.isUnresolved(this.normalizedZoneName) &&
this.domainName !== this.normalizedZoneName &&
!this.domainName.endsWith('.' + this.normalizedZoneName)) {
errors.push(`DNS zone ${this.normalizedZoneName} is not authoritative for certificate domain name ${this.domainName}`);
}
return errors;
}
}