From a2de7c151fe4efd934757ee714b2f419ecd86b1f Mon Sep 17 00:00:00 2001 From: dezeroku Date: Fri, 18 Feb 2022 23:14:37 +0100 Subject: [PATCH] Add support for multiple certificates This change allows `customCertificate` entry to be an array of objects. Each object describes a single certificate entry, using the supported options. The change is fully backwards compatible (object type `customCertificate` works exactly as it did before). Also some functions are now `async`. Tested with serverless 3.1.1 TODO: think about the return values + there's one variable resolver that I can't test (requires older serverless probably?) --- Readme.md | 49 +++++++++++++++++++++++++++++++ index.js | 87 +++++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 127 insertions(+), 9 deletions(-) diff --git a/Readme.md b/Readme.md index 4a546b6..76dfd14 100644 --- a/Readme.md +++ b/Readme.md @@ -93,6 +93,8 @@ To remove the certificate and delete the CNAME recordsets from route53, run: serverless remove-cert +It's also possible to define multiple certificates to be used, in such case the `customCertificate` should be an array of objects described above. + # Combine with serverless-domain-manager If you combine this plugin with [serverless-domain-manager](https://github.com/amplify-education/serverless-domain-manager) you can automate the complete process of creating a custom domain with a certificate. @@ -130,6 +132,40 @@ Open serverless.yml and add the following: enabled: true // optional - default is true. For some stages you may not want to use certificates (and custom domains associated with it). rewriteRecords: false +You can also modify serverless.yml like so, to support multiple certificates (possibly defined in different regions): + + plugins: + - serverless-certificate-creator + - serverless-domain-manager + + ... + + custom: + customDomain: + domainName: abc.somedomain.io + certificateName: 'abc.somedomain.io' + basePath: '' + stage: ${self:provider.stage} + createRoute53Record: true + customCertificate: + - + certificateName: 'abc.somedomain.io' //required + idempotencyToken: 'abcsomedomainio' //optional + hostedZoneNames: 'somedomain.io.' //required if hostedZoneIds is not set + hostedZoneIds: 'XXXXXXXXX' //required if hostedZoneNames is not set + region: eu-west-1 // optional - default is us-east-1 which is required for custom api gateway domains of Type Edge (default) + enabled: true // optional - default is true. For some stages you may not want to use certificates (and custom domains associated with it). + rewriteRecords: false + - + certificateName: 'abc.someseconddomain.io' //required + idempotencyToken: 'abcsomeseconddomainio' //optional + hostedZoneNames: 'someseconddomain.io.' //required if hostedZoneIds is not set + hostedZoneIds: 'XXXXXXXXX' //required if hostedZoneNames is not set + region: us-east-1 // optional - default is us-east-1 which is required for custom api gateway domains of Type Edge (default) + enabled: true // optional - default is true. For some stages you may not want to use certificates (and custom domains associated with it). + rewriteRecords: false + + Now you can run: serverless create-cert @@ -155,6 +191,19 @@ The new supported syntax is: see the serverless [docs](https://serverless.com/framework/docs/providers/aws/guide/plugins#custom-variable-types) for more information +Similarly, if you've defined multiple certificates, you have to add the array index to the variable. +For example, to obtain the certificate details about first certificate in an array you'd use (note the single quotes around indexes as a second param): + +If you are on version >= 2.27.0 of serverless & have elected to use the variable resolver: `variablesResolutionMode: 20210219`. +You must use this supported syntax which is: + + ${certificate:${self:custom.customCertificate.0.certificateName, '0'}.CertificateArn} + +For the new variable resolver: `variablesResolutionMode: 20210326`: +The new supported syntax is: + + ${certificate(${self:custom.customCertificate.0.certificateName}, '0'):CertificateArn} + ### License Copyright (c) 2018 Bastian Töpfer, contributors. diff --git a/index.js b/index.js index eeb97c5..69267d8 100644 --- a/index.js +++ b/index.js @@ -20,13 +20,13 @@ class CreateCertificatePlugin { this.initialized = false; this.commands = { 'create-cert': { - usage: 'creates a certificate for an existing domain/hosted zone', + usage: 'creates a certificate(s) for an existing domain/hosted zone', lifecycleEvents: [ 'create' ] }, 'remove-cert': { - usage: 'removes the certificate previously created by create-cert command', + usage: 'removes the certificate(s) previously created by create-cert command', lifecycleEvents: [ 'remove' ] @@ -57,6 +57,23 @@ class CreateCertificatePlugin { }; } + /** + * Checks if the custom certificate entry in JSON is in fact an array. + * + * If no details have been set, `false` is returned. + * + * @return Boolean + */ + isCustomCertificateArray() { + const arr = (this.serverless.service.custom || {}).customCertificate || null; + if (Array.isArray(arr)) { + this.certificateArrayLength = arr.length; + return true; + } else { + return false; + } + } + /** * Gets the details for the custom certificate from the service settings. * @@ -65,7 +82,11 @@ class CreateCertificatePlugin { * @return {Object|null} */ getCustomCertificateDetails() { - return (this.serverless.service.custom || {}).customCertificate || null; + if (Number.isInteger(this.certificateIndex)) { + return (this.serverless.service.custom || {}).customCertificate[(this.certificateIndex)] || null; + } else { + return (this.serverless.service.custom || {}).customCertificate || null; + } } initializeVariables() { @@ -73,7 +94,6 @@ class CreateCertificatePlugin { this.enabled = this.evaluateEnabled(); if (this.enabled) { const customCertificate = this.getCustomCertificateDetails() || {}; - const credentials = this.serverless.providers.aws.getCredentials(); this.route53 = new this.serverless.providers.aws.sdk.Route53(credentials); this.region = customCertificate.region || 'us-east-1'; @@ -238,7 +258,21 @@ class CreateCertificatePlugin { /** * Creates a certificate for the given options set in serverless.yml under custom->customCertificate */ - createCertificate() { + async createCertificate() { + if (this.isCustomCertificateArray()) { + for (var i = 0; i < this.certificateArrayLength; i++) { + this.certificateIndex = i; + this.initialized = false; + await this._createCertificate(); + } + // TODO: should we care about the function returns? + } + else { + return this._createCertificate(); + } + } + + _createCertificate() { this.initializeVariables(); if (!this.enabled) { return this.reportDisabled(); @@ -301,7 +335,21 @@ class CreateCertificatePlugin { * Deletes the certificate for the given options set in serverless.yml under custom->customCertificate * (if it exists) */ - deleteCertificate() { + async deleteCertificate() { + if (this.isCustomCertificateArray()) { + for (var i = 0; i < this.certificateArrayLength; i++) { + this.certificateIndex = i; + this.initialized = false; + await this._deleteCertificate(); + // TODO: should we care about the function returns? + } + } + else { + return this._deleteCertificate(); + } + } + + _deleteCertificate() { this.initializeVariables(); if (!this.enabled) { return this.reportDisabled(); @@ -504,13 +552,28 @@ class CreateCertificatePlugin { /** * Prints out a summary of all certificate related info */ - certificateSummary() { + + async certificateSummary() { + this.serverless.cli.consoleLog(chalk.yellow.underline('Serverless Certificate Creator Summary')); + if (this.isCustomCertificateArray()) { + for (var i = 0; i < this.certificateArrayLength; i++) { + this.certificateIndex = i; + this.initialized = false; + await this._certificateSummary(); + } + // TODO: should we care about the function returns? + } + else { + return this._certificateSummary(); + } + } + + _certificateSummary() { this.initializeVariables(); if (!this.enabled) { return this.reportDisabled(); } return this.getExistingCertificate().then(existingCertificate => { - this.serverless.cli.consoleLog(chalk.yellow.underline('Serverless Certificate Creator Summary')); this.serverless.cli.consoleLog(chalk.yellow('Certificate')); this.serverless.cli.consoleLog(` ${existingCertificate.CertificateArn} => ${existingCertificate.DomainName}`); @@ -519,9 +582,13 @@ class CreateCertificatePlugin { } async getCertificateProperty({address, params}) { - const property = address const domainName = params[0] + if (params.length >= 2) { + this.certificateIndex = parseInt(params[1]); + this.initialized = false; + } + this.initializeVariables(); if (!this.enabled) { @@ -550,6 +617,8 @@ class CreateCertificatePlugin { } getCertificatePropertyOld(src) { + // TODO: seems that this is not used anymore? + // How can I test it? this.initializeVariables(); if (!this.enabled) { return Promise.resolve('');