Skip to content

Instantly share code, notes, and snippets.

@rcknr
Last active July 19, 2018 12:36
Show Gist options
  • Star 45 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save rcknr/f7cb503f270e89c29dc70d1c8ef29461 to your computer and use it in GitHub Desktop.
Save rcknr/f7cb503f270e89c29dc70d1c8ef29461 to your computer and use it in GitHub Desktop.
Using Let's Encrypt certificates with Amazon API Gateway

##Using Let's Encrypt certificates with AWS API Gateway

Before starting off with API Gateway set up it's worth mentioning that certificate configuration for this particular service is so far isn't well integrated, therefore different from other AWS services. Despite it using CloudFrount to serve on custom domains it won't let you customize distributions it creates, however all the limitations of CloudFront naturally apply to API Gateway. The most important in this case is the size of the key, which is limited by 2048 bit. Many tutorials provide ready to use terminal commands that have the key size preset at 4096 bit for the sake of better security. This won't work with API Gateway and you'll get an error message about certificate's validity or incorrect chain which won't suggest you the real cause of the issue. Another consideration is that to add a custom domain to API Gateway you have to have a certificate already, because API Gateway simply doesn't work over HTTP. In case of Let's Encrypt it means you have to validate your domain with some other backend or hosting before attaching it to API Gateway. There're many articles about how to get a certificate from Let's Encrypt and instructions may differ depending on the OS you're using, but generally the command should look like this:

./letsencrypt-auto certonly -a manual --rsa-key-size 2048 -d api.example.com

After you get your certificate you can add it either using AWS Console or AWS CLI tool, which is more comfortable since you won't have to copy paste nothing sensitive. Below is the command to do that, conisder that the name under --certificate-name switch is free form and I recommend to put a reference in there regarding certificate expiration, which will be helpful when you need to renew it.

sudo aws apigateway create-domain-name --domain-name api.example.com --certificate-name example-domain --certificate-body file:///etc/letsencrypt/live/api.example.com/cert.pem --certificate-private-key file:///etc/letsencrypt/live/api.example.com/privkey.pem --certificate-chain file:///etc/letsencrypt/live/api.example.com/chain.pem

Speaking of renewing the certificate, in case of Let's Encrypt that needs to happen quite often, every three month. So we need to make sure we've got everything we need to do that without downtime for our API. So far the only supported method of domain validation in Let's Encrypt client is web challenge where you have to host a certain file in a certain directory under your domain. To do that with API Gateway so we don't have to re-deploy the API for every certificate renewal, we have to create a couple of resources and a method to serve a challange saved in a stage variable. If you're familiar with API import and Swagger you can go ahead and import the Swagger file below to add required resources and a method.

IMPORTANT: Be sure to switch import mode into merge before you proceed!

The structure in the file assumes you are mapping your API to the domain without a base path. If this is not the case and you have several APIs mapped to a single domain, you might want to have a separate API just for renewing the certificate. In this case you have to remove /.well-known under the paths section in the swagger file and the use .well-known as a base path in the API mapping. After importing that file and re-deploying your API you will just need to set a stage variable called acme to the complete ACME challenge provided by Let's Encrypt client.

---
swagger: "2.0"
info:
version: "2016-04-30T12:00:00Z"
title: "Let's Encrypt"
schemes:
- "https"
paths:
/.well-known/acme-challenge/{key}:
get:
consumes:
- "application/json"
produces:
- "text/plain"
parameters:
- name: "key"
in: "path"
required: true
type: "string"
responses:
200:
description: "200 response"
schema:
$ref: "#/definitions/Empty"
x-amazon-apigateway-integration:
responses:
default:
statusCode: "200"
responseTemplates:
text/plain: "#if($stageVariables.acme.startsWith($input.params('key')))$stageVariables.acme#end\n"
requestTemplates:
application/json: "{\"statusCode\": 200}"
type: "mock"
definitions:
Empty:
type: "object"
@adamchainz
Copy link

Try aws apigateway update-client-certificate help

@TylerMills
Copy link

TylerMills commented Sep 29, 2016

Help me understand the workflow here, because I feel like there is something missing in steps 4 to 5.

  1. Create Let's Encrypt Certificate: ./certbot-auto certonly -a manual --rsa-key-size 2048 -d api.example.com
  2. Create Domain and SSL Cert in AWS sudo aws apigateway create-domain-name --domain-name api.example.com --certificate-name acme-domain --certificate-body 'cert.pem' --certificate-private-key 'privkey.pem' --certificate-chain 'chain.pem'
  3. Create MOCK Endpoint in API Gateway at /.well-known
  4. ??? Renew Certificate What do we do here with MOCK endpoint?
    certbot renew -a manual --rsa-key-size 2048 --post-hook './updateAWS.sh'
  5. ??? Update AWSaws apigateway update-client-certificate --client-certificate-id a1b2c3 --patch-operations op='replace', path='/home/cert.pem',value='2016-10-01 Refresh' --region us-east-1

Copy link

ghost commented Oct 5, 2016

@adamchainz That is for client certificates I'm looking for the certificate used to secure the custom domain. Client certificates are completely different.
@TylerMills the steps 4 and 5 you are talking about especially 5 you can't use apigateway update client certificate to update the custom domain certificate those two are completely separate. I agree though there is a missing option something like aws apigateway update-domain-name --certificate-body 'cert.pem' --auto-rotate. Since you don't want it to happen immediately in case it's invalid or you have a downtime of 40mins. Just like you would when you upload the new one and takes 40 minutes to populate then you rotate it when it's ready.

@rcknr
Copy link
Author

rcknr commented Oct 9, 2016

AFAIK, there's no way to update certificate using REST API. You could however replicate steps to switch certificates using AWS Console.

@kpx-dev
Copy link

kpx-dev commented Oct 26, 2016

There's also this awesome online service to generate the keys too:
https://zerossl.com/

Make sure to generate the key in 2048 bit for API Gateway.

@andrewwakeling
Copy link

A potential alternative: http://blog.talawah.net/2016/11/simple-cross-platform-way-to-manaully.html

Pros:

  • doesn't require an existing certificate
  • validation occurs via TXT DNS record
    • renewals do not interrupt normal operation of API
    • easy to renew/validate

@antstanley
Copy link

antstanley commented Jan 2, 2017

When using this method I would recommend using DNS validation to avoid any nonsense with MOCK Endpoints on API Gateway. Something like this will force DNS validation over the HTTP/S validation it uses by default..

Another thing to note is that API Gateway requires Server Name Indication (SNI) with it's SSL certs, which certbot (formerly the letsencrypt client) can create, but it does it implicitly (ie no flag or switch to force it to do it). To create an SSL cert with SNI on a sub-domain, you need to specify the primary domain (ie example.com) first, and the sub-domain (api.example.com) second in the command line. The client will give you a DNS challenge code for each domain (primary and sub-domain). You need to generate each certificate individually, so if you have 8 sub-domains, don't put all 8 in the command line, you must do it one at a time.

certbot certonly --manual --rsa-key-size 2048 -d example.com -d api.example.com --preferred-challenges dns-01

There is some bad documentation on it here https://certbot.eff.org/docs/using.html#plugins

@brianz
Copy link

brianz commented Feb 17, 2017

There's lot's of good info in here. I think some of it may be out of date since some of the limitations mentioned don't seem to be limitations any longer (i.e., key size, SNI). I was able to get a perfectly valid SSL cert working in API Gateway with:

certbot certonly --manual -d api.example.com --preferred-challenge dns

I recently published a blog post about my experiences creating certs with certbot for API Gateway. Previously I was using the HTTP method and spinning up a little EC2 instance to serve the challenge requests. Thanks to this gist and comments I learned about the --preferred-challenge dns option which is way easier!

Hopefully future readers find this helpful: http://blog.brianz.bz/post/custom-https-domains-with-serverless/

@jed
Copy link

jed commented Mar 5, 2017

hey all, i automated this if you're still in the market.

@lorismaz
Copy link

you apparently have now to use AWS Certificate Manager for certs in API Gateway . You can generate them for free (see https://aws.amazon.com/certificate-manager ) or you can upload your cerbot generated one using cli:

aws acm import-certificate --certificate file://Certificate.pem
                             --certificate-chain file://CertificateChain.pem
                             --private-key file://PrivateKey.pem

@AnalogJ
Copy link

AnalogJ commented Mar 29, 2017

I have a python script that will do all of this for you (and store your Letsencrypt certificate with AWS Certificate Manager). Its in aws-api-gateway-letsencrypt

I talk about it in my blog post as well: http://blog.thesparktree.com/post/152904762374/custom-domains-for-aws-lambdaapi-gateway-using

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment