Skip to content

Instantly share code, notes, and snippets.

@jyates
Forked from rcknr/README.md
Created February 21, 2017 20:14
Show Gist options
  • Save jyates/107fd5dc90a3787439b775903b1fd531 to your computer and use it in GitHub Desktop.
Save jyates/107fd5dc90a3787439b775903b1fd531 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"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment