Tom
Tom

Reputation: 8127

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

Upvotes: 5

Views: 757

Answers (2)

Robo
Robo

Reputation: 1032

Using Step Functions would be the best choice for this particular use case. Since you have 3 distinct steps, they would be three seperate Lambda functions that Step Functions can pass inputs/outputs between as well as including wait times and retries.

Upvotes: 0

Trent Bartlem
Trent Bartlem

Reputation: 2253

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.

Upvotes: 1

Related Questions