Context: to achieve full "infrastructure as code", I want to codify the process of requesting a SSL certificate using certbot
, validating a domain using DNS TXT
records, uploading the certificate to Amazon Certificate Manager (ACM), and finally attaching the certificate ACM ARN to my Cloudfront distribution. This should all be done through the Serverless framework.
I saw 2 potential options to make this work.
Option 1: use of asynchronous javascript file variables
I.e. in serverless.yml
I would define entries like:
custom:
domains:
prod: tommedema.tk
ssl:
prod:
dnsTxtRoot: ${{file(scripts/request-cert.js):cert.dnsTxtRoot}}
dnsTxtWww: ${{file(scripts/request-cert.js):cert.dnsTxtWww}}
certArn: ${{file(scripts/request-cert.js):cert.certArn}}
Where resources would then use these variables like so:
- Type: TXT
Name: _acme-challenge.www.${{self:custom.domains.${{self:provider.stage}}, ''}}
TTL: '86400'
ResourceRecords:
- ${{self:custom.ssl.${{self:provider.stage}}.dnsTxtWww}}
Where scripts/request-cert.js
would look like:
module.exports.cert = () => {
console.log('running async logic')
// TODO: run certbot, get DNS records, upload to ACM
return Promise.resolve({
dnsTxtRoot: '"LnaKMkgqlIkXXXXXXXX-7PkKvqb_wqwVnC4q0"',
dnsTxtWww: '"c43VS-XXXXXXXXXWVBRPCXXcA"',
certArn: 'arn:aws:acm:us-east-1:XXXX95:certificate/XXXXXX'
})
}
The problem here is that it appears to be impossible to send parameters to request-cert.js
, or for this script to be aware of the serverless
or options
plugin parameters (as it is not a plugin, but a simple script without context). This means that the script cannot be aware of the stage and domain etc. that the deployment is for, and therefore it is missing necessary variables in order to request the certificate.
So, option 1 seems out of the question.
Option 2: create a plugin
Of course I can create a plugin, which will have all required variables because it can access the serverless
and options
objects. The problem now is that I would have to access the output of the plugin inside serverless.yml
, and so far I have not seen how this can be done. I.e. I would like to be able to do something like this:
custom:
domains:
prod: tommedema.tk
ssl:
prod:
dnsTxtRoot: ${{myPlugin:cert.dnsTxtRoot}}
dnsTxtWww: ${{myPlugin:cert.dnsTxtWww}}
certArn: ${{myPlugin:cert.certArn}}
But this does not seem possible. Is that right?
If this is also not possible, how can I achieve my purpose to programatically (i.e. following infrastructure as code principles) deploy my services with custom SSL certificates, without any manual steps? I.e.
You could do it at deploy time, but certificates expire so it's best to make this a recurring thing.
When I was faced with this problem, I created a Lambda to upsert the SSL certificate and install it. (it needs a long timeout, but that's fine - it doesn't need to run often). The key it needs can be given as secure environment variables.
Then I use serverless-warmup-plugin
to set up a daily trigger to check whether the certificate is due to be refreshed. The plugin is configurable to warm up relevant lambdas on deployment too, which allows me to check for expired or missing SSL certs on each deploy.
Maybe you could do something similar.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With