Skip to content

Commit c16e8dd

Browse files
authored
Merge pull request #18 from schwamster/next
PRs #15 #16 #17
2 parents cfc1c9c + b2cd6f9 commit c16e8dd

File tree

5 files changed

+98
-55
lines changed

5 files changed

+98
-55
lines changed

Readme.md

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ Check out their getting started guide for more information [here](https://server
4343
Make sure you have the following installed before starting:
4444
* [nodejs](https://nodejs.org/en/download/)
4545
* [npm](https://www.npmjs.com/get-npm?utm_source=house&utm_medium=homepage&utm_campaign=free%20orgs&utm_term=Install%20npm)
46-
* [serverless](https://serverless.com/framework/docs/providers/aws/guide/installation/)
46+
* [serverless](https://serverless.com/framework/docs/providers/aws/guide/installation/) >= v1.52.0
4747

4848
# Usage
4949

@@ -62,11 +62,11 @@ open serverless.yml and add the following:
6262
certificateName: 'abc.somedomain.io'
6363
//optional
6464
idempotencyToken: 'abcsomedomainio'
65-
//required if hostedZoneId is not set
66-
hostedZoneName: 'somedomain.io.'
67-
//required if hostedZoneName is not set
68-
hostedZoneId: 'XXXXXXXXX'
69-
// optional default is false. if you set it to true you will get a new file (after executing serverless create-cert), that contains certificate info that you can use in your deploy pipeline
65+
//required if hostedZoneIds is not set, alternativly as an array
66+
hostedZoneNames: 'somedomain.io.'
67+
//required if hostedZoneNames is not set
68+
hostedZoneIds: 'XXXXXXXXX'
69+
// optional default is false. if you set it to true you will get a new file (after executing serverless create-cert), that contains certificate info that you can use in your deploy pipeline, alternativly as an array
7070
writeCertInfoToFile: false
7171
// optional, only used when writeCertInfoToFile is set to true. It sets the name of the file containing the cert info
7272
certInfoFileName: 'cert-info.yml'
@@ -81,6 +81,8 @@ open serverless.yml and add the following:
8181
tags:
8282
Name: 'somedomain.com'
8383
Environment: 'prod'
84+
//optional default false. this is useful if you managed to delete your certificate but the dns validation records still exist
85+
rewriteRecords: false
8486

8587

8688
now you can run:
@@ -118,10 +120,11 @@ Open serverless.yml and add the following:
118120
customCertificate:
119121
certificateName: 'abc.somedomain.io' //required
120122
idempotencyToken: 'abcsomedomainio' //optional
121-
hostedZoneName: 'somedomain.io.' //required if hostedZoneId is not set
122-
hostedZoneId: 'XXXXXXXXX' //required if hostedZoneName is not set
123+
hostedZoneNames: 'somedomain.io.' //required if hostedZoneIds is not set
124+
hostedZoneIds: 'XXXXXXXXX' //required if hostedZoneNames is not set
123125
region: eu-west-1 // optional - default is us-east-1 which is required for custom api gateway domains of Type Edge (default)
124126
enabled: true // optional - default is true. For some stages you may not want to use certificates (and custom domains associated with it).
127+
rewriteRecords: false
125128

126129
Now you can run:
127130

@@ -130,6 +133,14 @@ Now you can run:
130133

131134
Please make sure to check out the complete sample project [here](https://github.com/schwamster/serverless-certificate-creator/tree/master/examples/certificate-creator-example).
132135

136+
### Reference Certificate Arn via variableResolvers
137+
138+
Since version 1.2.0 of this plugin you can use the following syntax to access the certificates Arn in other plugins
139+
140+
${certificate:${self:custom.customCertificate.certificateName}:CertificateArn}
141+
142+
see the serverless [docs](https://serverless.com/framework/docs/providers/aws/guide/plugins#custom-variable-types) for more information
143+
133144
### License
134145

135146
Copyright (c) 2018 Bastian Töpfer, contributors.

examples/certificate-creator-example/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"author": "",
1010
"license": "ISC",
1111
"devDependencies": {
12-
"serverless-certificate-creator": "^1.0.8",
12+
"serverless-certificate-creator": "^1.2.0",
1313
"serverless-domain-manager": "^3.2.6"
1414
}
1515
}

examples/certificate-creator-example/serverless.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,11 @@ custom:
4242
idempotencyToken: 'certcreatorsamplegreenelephantio'
4343
writeCertInfoToFile: true
4444
certInfoFileName: "certs/${self:provider.stage}/cert-info.yml"
45-
hostedZoneName: 'greenelephant.io.'
45+
hostedZoneNames: 'greenelephant.io.'
4646
subjectAlternativeNames :
4747
- 'certcreatorsample1.greenelephant.io'
4848
- 'certcreatorsample2.greenelephant.io'
4949
tags:
5050
Name: 'somedomain.com'
51-
Environment: 'prod'
51+
Environment: 'prod'
52+
rewriteRecords: false

index.js

Lines changed: 74 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const fs = require('fs');
55
const path = require('path');
66
const YAML = require('yamljs');
77
const mkdirp = require('mkdirp');
8+
var packageJson = require('./package.json');
89

910
const unsupportedRegionPrefixes = ['cn-'];
1011

@@ -13,7 +14,7 @@ class CreateCertificatePlugin {
1314
this.serverless = serverless;
1415
this.options = options;
1516
this.initialized = false;
16-
17+
this.serverless.cli.log(`serverless-certificate-creator version ${packageJson.version} called`);
1718
this.commands = {
1819
'create-cert': {
1920
usage: 'creates a certificate for an existing domain/hosted zone',
@@ -28,6 +29,14 @@ class CreateCertificatePlugin {
2829
'after:deploy:deploy': this.certificateSummary.bind(this),
2930
'after:info:info': this.certificateSummary.bind(this),
3031
};
32+
33+
this.variableResolvers = {
34+
certificate: {
35+
resolver: this.getCertificateProperty.bind(this),
36+
isDisabledAtPrepopulation: true,
37+
serviceName: 'serverless-certificate-creator depends on AWS credentials.'
38+
}
39+
};
3140
}
3241

3342
initializeVariables() {
@@ -38,12 +47,15 @@ class CreateCertificatePlugin {
3847
this.route53 = new this.serverless.providers.aws.sdk.Route53(credentials);
3948
this.region = this.serverless.service.custom.customCertificate.region || 'us-east-1';
4049
this.domain = this.serverless.service.custom.customCertificate.certificateName;
41-
this.hostedZoneId = this.serverless.service.custom.customCertificate.hostedZoneId;
42-
this.hostedZoneName = this.serverless.service.custom.customCertificate.hostedZoneName;
50+
//hostedZoneId is mapped for backwards compatibility
51+
this.hostedZoneIds = this.serverless.service.custom.customCertificate.hostedZoneIds ? this.serverless.service.custom.customCertificate.hostedZoneIds : (this.serverless.service.custom.customCertificate.hostedZoneId) ? [].concat(this.serverless.service.custom.customCertificate.hostedZoneId) : [];
52+
//hostedZoneName is mapped for backwards compatibility
53+
this.hostedZoneNames = this.serverless.service.custom.customCertificate.hostedZoneNames ? this.serverless.service.custom.customCertificate.hostedZoneNames : (this.serverless.service.custom.customCertificate.hostedZoneName) ? [].concat(this.serverless.service.custom.customCertificate.hostedZoneName) : [];
4354
const acmCredentials = Object.assign({}, credentials, { region: this.region });
4455
this.acm = new this.serverless.providers.aws.sdk.ACM(acmCredentials);
4556
this.idempotencyToken = this.serverless.service.custom.customCertificate.idempotencyToken;
4657
this.writeCertInfoToFile = this.serverless.service.custom.customCertificate.writeCertInfoToFile || false;
58+
this.rewriteRecords = this.serverless.service.custom.customCertificate.rewriteRecords || false;
4759
this.certInfoFileName = this.serverless.service.custom.customCertificate.certInfoFileName || 'cert-info.yml';
4860
this.subjectAlternativeNames = this.serverless.service.custom.customCertificate.subjectAlternativeNames || [];
4961
this.tags = this.serverless.service.custom.customCertificate.tags || {};
@@ -55,7 +67,6 @@ class CreateCertificatePlugin {
5567
}
5668
})
5769
}
58-
5970
this.initialized = true;
6071
}
6172
}
@@ -110,7 +121,7 @@ class CreateCertificatePlugin {
110121
CertificateArn: certificateArn,
111122
Tags: mappedTags
112123
}
113-
124+
114125
this.serverless.cli.log(`tagging certificate`);
115126
return this.acm.addTagsToCertificate(params).promise().catch(error => {
116127
this.serverless.cli.log('tagging certificate failed', error);
@@ -228,21 +239,19 @@ class CreateCertificatePlugin {
228239
});
229240
}
230241

231-
getHostedZoneId() {
242+
getHostedZoneIds() {
232243

233244
return this.route53.listHostedZones({}).promise().then(data => {
234245

235-
if (this.hostedZoneId) {
236-
return this.hostedZoneId;
237-
}
246+
let hostedZones = data.HostedZones.filter(x => this.hostedZoneIds.includes(x.Id.replace(/\/hostedzone\//g, '')) || this.hostedZoneNames.includes(x.Name));
238247

239-
let hostedZone = data.HostedZones.filter(x => x.Name == this.hostedZoneName);
240-
if (hostedZone.length == 0) {
248+
if (hostedZones.length == 0) {
241249
throw "no hosted zone for domain found"
242250
}
243251

244-
this.hostedZoneId = hostedZone[0].Id.replace(/\/hostedzone\//g, '');
245-
return this.hostedZoneId;
252+
return hostedZones.map(({ Id, Name }) => {
253+
return { hostedZoneId: Id.replace(/\/hostedzone\//g, ''), Name: Name.substr(0, Name.length - 1) };
254+
});
246255
}).catch(error => {
247256
this.serverless.cli.log('certificate validation failed', error);
248257
console.log('problem', error);
@@ -255,38 +264,40 @@ class CreateCertificatePlugin {
255264
* at least a short time after the cert has been created, thats why you should delay this call a bit after u created a new cert
256265
*/
257266
createRecordSetForDnsValidation(certificate) {
258-
return this.getHostedZoneId().then((hostedZoneId) => {
259-
260-
let changes = certificate.Certificate.DomainValidationOptions.map((x) => {
261-
return {
262-
Action: "CREATE",
263-
ResourceRecordSet: {
264-
Name: x.ResourceRecord.Name,
265-
ResourceRecords: [
266-
{
267-
Value: x.ResourceRecord.Value
268-
}
269-
],
270-
TTL: 60,
271-
Type: x.ResourceRecord.Type
267+
return this.getHostedZoneIds().then((hostedZoneIds) => {
268+
269+
return Promise.all(hostedZoneIds.map(({ hostedZoneId, Name }) => {
270+
let changes = certificate.Certificate.DomainValidationOptions.filter(({DomainName}) => DomainName.endsWith(Name)).map((x) => {
271+
return {
272+
Action: this.rewriteRecords ? "UPSERT" : "CREATE",
273+
ResourceRecordSet: {
274+
Name: x.ResourceRecord.Name,
275+
ResourceRecords: [
276+
{
277+
Value: x.ResourceRecord.Value
278+
}
279+
],
280+
TTL: 60,
281+
Type: x.ResourceRecord.Type
282+
}
272283
}
273-
}
274-
});
284+
});
275285

276-
var params = {
277-
ChangeBatch: {
278-
Changes: changes,
279-
Comment: `DNS Validation for certificate ${certificate.Certificate.DomainValidationOptions[0].DomainName}`
280-
},
281-
HostedZoneId: hostedZoneId
282-
};
283-
return this.route53.changeResourceRecordSets(params).promise().then(recordSetResult => {
284-
this.serverless.cli.log('dns validation record(s) created - certificate is ready for use after validation has gone through');
285-
}).catch(error => {
286-
this.serverless.cli.log('could not create record set for dns validation', error);
287-
console.log('problem', error);
288-
throw error;
289-
});
286+
var params = {
287+
ChangeBatch: {
288+
Changes: changes,
289+
Comment: `DNS Validation for certificate ${Name}`
290+
},
291+
HostedZoneId: hostedZoneId
292+
};
293+
return this.route53.changeResourceRecordSets(params).promise().then(recordSetResult => {
294+
this.serverless.cli.log('dns validation record(s) created - certificate is ready for use after validation has gone through');
295+
}).catch(error => {
296+
this.serverless.cli.log('could not create record set for dns validation', error);
297+
console.log('problem', error);
298+
throw error;
299+
});
300+
}));
290301
});
291302
}
292303

@@ -306,6 +317,26 @@ class CreateCertificatePlugin {
306317
return true;
307318
});
308319
}
320+
321+
getCertificateProperty(src) {
322+
this.initializeVariables();
323+
let [s, domainName, property] = src.split(':');
324+
return this.listCertificates()
325+
.then(({ CertificateSummaryList }) => {
326+
let cert = CertificateSummaryList.filter(({ DomainName }) => DomainName == domainName)[0];
327+
if (cert && cert[property]) {
328+
return cert[property];
329+
} else {
330+
this.serverless.cli.consoleLog(chalk.yellow('Warning, certificate or certificate property was not found. Returning an empty string instead!'));
331+
return '';
332+
}
333+
})
334+
.catch(error => {
335+
console.log(this.domain, this.region);
336+
this.serverless.cli.log('Could not find certificate property attempting to create...');
337+
throw error;
338+
});
339+
}
309340
}
310341

311342

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "serverless-certificate-creator",
3-
"version": "1.1.0",
3+
"version": "1.2.0",
44
"description": "creates a certificate that can be used for custom domains for your api gateway",
55
"main": "index.js",
66
"scripts": {

0 commit comments

Comments
 (0)