Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to access plugin output in serverless.yml of Serverless Framework?

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.

  1. request certificate from certbot
  2. receive DNS txt records for validation from certbot
  3. attach DNS txt records to route53 recordsets
  4. deploy the DNS records and validate the certificate
  5. download the certificate from certbot and upload it to ACM
  6. receive the certificate ARN from ACM
  7. reference to the certificate ARN from within the cloudfront distribution inside the cloudformation template
  8. redeploy with the certificate ARN attached
like image 695
Tom Avatar asked Nov 07 '22 14:11

Tom


1 Answers

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.

like image 159
Trent Bartlem Avatar answered Nov 14 '22 22:11

Trent Bartlem