Let's Encrypt with Amazon Linux 2
7 min read

Let's Encrypt with Amazon Linux 2

Let's Encrypt with Amazon Linux 2
In December 2020, Certbot-auto was officially deprecated. Since then, we have to migrate to Certbot. The problem is, if you were using an Amazon Linux AMI image on your Elastic beanstalk environment, you will be surprised to know that you can't install Cerbot because Epel is not compatible with your system anymore.

Basically, Certbot-auto is deprecated, Amazon Linux AMI has reached end-of-life and you need to migrate to Amazon Linux 2.

However, the good news is, Certbot have some nice new features:

  • Certbot automatically stays up-to-date
  • Automatic renewal comes preconfigured
  • DNS plugins and 3rd parties to write their own Certbot snap plugins 9 as well.
  • You can use the wildcard for your subdomains

There's no need to create a crontab to renew your certificate!

Requirements

My guide has been written for the people who had an AWS Elastic Beanstalk application using Docker and Nginx. But I guess it will work fine for the other configuration too.

Migrate to Amazon Linux 2

It sucks but yeah, you need to do that too. But Amazon devs are such nice guys, they even wrote an guide about it.

TL;DR the application bundle change and you need to move a few files in some specific folders. For example, before you put everything in .ebextensions. Now you need to move the nginx files to .platform/nginx/conf.d.

Check your EC2 ports

Ports 80 and 443 needs to be open.

Your domain has to be on Route53

The script will use the Route53 API in order to confirm the DNS challenge. The http challenge will still work, but if you want to use the wildcard for your subdomain, you need to use the dns challenge.

Environment variables

Email and Domain

Configuration

.platform/nginx/conf.d folder contains :

  • custom_redirect.conf : force https redirection
  • custom.conf : nginx ssl configuration for Docker

.ebextensions folder contains :

  • 01_files.config : the file certbot_route53.sh contains our script to solve our dns challenge
  • 02_certbot.config : container commands

.platform/nginx/conf.d/custom.conf

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name  localhost;

    ssl_certificate       /etc/letsencrypt/live/ebcert/fullchain.pem;
    ssl_certificate_key   /etc/letsencrypt/live/ebcert/privkey.pem;

    ssl_session_timeout  5m;
    ssl_protocols  TLSv1.1 TLSv1.2;
    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
    ssl_prefer_server_ciphers   on;

    location / {
        proxy_pass http://docker;
        proxy_http_version 1.1;

        proxy_set_header Connection "";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

.platform/nginx/conf.d/custom_redirect.conf

server {
    listen 8080;
    server_name localhost;

    location / {
        return 301 https://$host$request_uri;
    }
} 

.ebextensions/01_files.config

This will create a script called named certbot_route53.sh , which attempt the validation from manual-auth-hook.

The script receives two parameters ($1,$2) and additionally two environment variables from Certbot :

  • CERTBOT_DOMAIN : The domain being authenticated
  • CERTBOT_VALIDATION : The validation string

The script will use the Route53 API in order to create an TXT record on your hosted zone with a specific value CERTBOT_VALIDATION.

files:
    "/home/ec2-user/letsencrypt/certbot_route53.sh" :
        mode: "000755"
        owner: root
        group: root
        content: |
            aws route53 wait resource-record-sets-changed --id \
                $(aws route53 change-resource-record-sets --hosted-zone-id \
                    "$(aws route53 list-hosted-zones-by-name --dns-name $2 --query HostedZones[0].Id --output text)" \
                    --query ChangeInfo.Id --output text \
                    --change-batch "{
                        \"Changes\": [{
                        \"Action\": \"$1\",
                        \"ResourceRecordSet\": {
                            \"Name\": \"_acme-challenge.${CERTBOT_DOMAIN}\",
                            \"ResourceRecords\": [{\"Value\": \"\\\"${CERTBOT_VALIDATION}\\\"\"}],
                            \"Type\": \"TXT\",
                            \"TTL\": 30
                        }
                        }]
                    }"
                )

.ebextensions/02_certbot.config

  1. 01_download_epel: install epel from amazon-linux-extras
  2. 02_enable_epel: enable epel
  3. 03_update_yum: update packages
  4. 04_install_certbot: install certbot and extension for nginx
  5. 05_get_certificate: ask for a certificate using dns challenge. To do so, the script create a TXT record on Route53 to prove that you're the owner. Once Certbot verified the TXT record, we can delete the TXT record using the same script but with "delete" parameter. As you can see, we can use the wildcard which is very convenient.
  6. 06_link: symbolic link for nginx to retrieve certificates
# In a single instance environment, you must also modify the instance's security group to 
# allow traffic on port 443. The following configuration file retrieves the security 
# group's ID using an AWS CloudFormation function and adds a rule to it. 
# Link : https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/https-singleinstance-docker.html
Resources:
  sslSecurityGroupIngress: 
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: {"Fn::GetAtt" : ["AWSEBSecurityGroup", "GroupId"]}
      IpProtocol: tcp
      ToPort: 443
      FromPort: 443
      CidrIp: 0.0.0.0/0

# Environment variables :
# ${DOMAIN}     : example.org
# ${EMAIL}      : user@example.org
container_commands:
  01_download_epel:
    command: "sudo amazon-linux-extras install epel -y"
  02_enable_epel:
    command: "sudo yum-config-manager --enable epel"
  03_update_yum:
    command: "sudo yum update -y --skip-broken"
  04_install_certbot: 
    command: "sudo yum install -y certbot python2-certbot-nginx python2-pip"
  05_get_certificate:
    command: "sudo certbot certonly --manual \
      --preferred-challenges dns \
      --manual-auth-hook \"/home/ec2-user/letsencrypt/certbot_route53.sh UPSERT ${DOMAIN}\" \
      --manual-cleanup-hook \"/home/ec2-user/letsencrypt/certbot_route53.sh DELETE ${DOMAIN}\" \
      --debug \
      --non-interactive \
      --email ${EMAIL} \
      --agree-tos \
      -d ${DOMAIN} \
      -d *.${DOMAIN} \
      --expand \
      --renew-with-new-domains \
      --keep-until-expiring \
      --post-hook \"sudo service nginx start\" \
      --pre-hook \"sudo service nginx stop\""
  06_link:
    command: "sudo ln -sf /etc/letsencrypt/live/${DOMAIN} /etc/letsencrypt/live/ebcert"

What's next?

After that, run eb deploy :

2021-02-05 20:34:17,025 P28471 [INFO] Command 05_getcert
2021-02-05 20:35:34,968 P28471 [INFO] -----------------------Command Output-----------------------
2021-02-05 20:35:34,969 P28471 [INFO] 	Saving debug log to /var/log/letsencrypt/letsencrypt.log
2021-02-05 20:35:34,969 P28471 [INFO] 	Plugins selected: Authenticator manual, Installer None
2021-02-05 20:35:34,969 P28471 [INFO] 	Running pre-hook command: sudo service nginx stop
2021-02-05 20:35:34,970 P28471 [INFO] 	Error output from pre-hook command sudo:
2021-02-05 20:35:34,970 P28471 [INFO] 	Redirecting to /bin/systemctl stop nginx.service
2021-02-05 20:35:34,970 P28471 [INFO] 	
2021-02-05 20:35:34,970 P28471 [INFO] 	Renewing an existing certificate for example.com and *.example.com
2021-02-05 20:35:34,970 P28471 [INFO] 	Performing the following challenges:
2021-02-05 20:35:34,970 P28471 [INFO] 	dns-01 challenge for example.com
2021-02-05 20:35:34,970 P28471 [INFO] 	Running manual-auth-hook command: /home/ec2-user/letsencrypt/authenticator.sh UPSERT example.com
2021-02-05 20:35:34,971 P28471 [INFO] 	
2021-02-05 20:35:34,971 P28471 [INFO] 	Waiting for verification...
2021-02-05 20:35:34,971 P28471 [INFO] 	Cleaning up challenges
2021-02-05 20:35:34,971 P28471 [INFO] 	Running manual-cleanup-hook command: /home/ec2-user/letsencrypt/authenticator.sh DELETE example.com
2021-02-05 20:35:34,971 P28471 [INFO] 	
2021-02-05 20:35:34,971 P28471 [INFO] 	Running post-hook command: sudo service nginx start
2021-02-05 20:35:34,971 P28471 [INFO] 	Error output from post-hook command sudo:
2021-02-05 20:35:34,972 P28471 [INFO] 	Redirecting to /bin/systemctl start nginx.service
2021-02-05 20:35:34,972 P28471 [INFO] 	
2021-02-05 20:35:34,972 P28471 [INFO] 	IMPORTANT NOTES:
2021-02-05 20:35:34,972 P28471 [INFO] 	 - Congratulations! Your certificate and chain have been saved at:
2021-02-05 20:35:34,972 P28471 [INFO] 	   /etc/letsencrypt/live/example.com/fullchain.pem
2021-02-05 20:35:34,972 P28471 [INFO] 	   Your key file has been saved at:
2021-02-05 20:35:34,972 P28471 [INFO] 	   /etc/letsencrypt/live/example.com/privkey.pem
2021-02-05 20:35:34,972 P28471 [INFO] 	   Your certificate will expire on 2021-05-06. To obtain a new or
2021-02-05 20:35:34,972 P28471 [INFO] 	   tweaked version of this certificate in the future, simply run
2021-02-05 20:35:34,972 P28471 [INFO] 	   certbot again. To non-interactively renew *all* of your
2021-02-05 20:35:34,972 P28471 [INFO] 	   certificates, run "certbot renew"
2021-02-05 20:35:34,972 P28471 [INFO] 	 - If you like Certbot, please consider supporting our work by:
2021-02-05 20:35:34,972 P28471 [INFO] 	
2021-02-05 20:35:34,972 P28471 [INFO] 	   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
2021-02-05 20:35:34,972 P28471 [INFO] 	   Donating to EFF:                    https://eff.org/donate-le
2021-02-05 20:35:34,972 P28471 [INFO] 	

Congratulations !

That's it ✌️ I hope this guide helped you to migrate your web application! Don't forget this is based on my web application, which is an EB Amazon Linux 2 using Nginx and Docker. Write in the comment below if it helps you or if you have some questions! Cheers


Problems?

If you got some errors, check your logs. Use eb log --all --zipor use your browser and extract the logs from it. Check the file cfn-init-cmd and eb-engine.

  • Error 1
ReadTimeout: HTTPSConnectionPool(host='acme-v02.api.letsencrypt.org', port=443): Read timed out. (read timeout=45)

To fix this, check that your 443 port is open on your EC2 instance. Or maybe you need to change the MTU, because the original value is 9001 (jumbo frame) and need to be lowered to 1500.

  • Error 2
2021-02-05 11:42:04,124 P3608 [INFO] 	Please see the logfiles in /var/log/letsencrypt for more details.
2021-02-05 11:42:04,124 P3608 [INFO] 	IMPORTANT NOTES:
2021-02-05 11:42:04,124 P3608 [INFO] 	 - The following errors were reported by the server:
2021-02-05 11:42:04,124 P3608 [INFO] 	
2021-02-05 11:42:04,124 P3608 [INFO] 	   Domain: example.com
2021-02-05 11:42:04,124 P3608 [INFO] 	   Type:   unauthorized
2021-02-05 11:42:04,124 P3608 [INFO] 	   Detail: No TXT record found at
2021-02-05 11:42:04,125 P3608 [INFO] 	   _acme-challenge.example.com
2021-02-05 11:42:04,125 P3608 [INFO] 	
2021-02-05 11:42:04,125 P3608 [INFO] 	   To fix these errors, please make sure that your domain name was
2021-02-05 11:42:04,125 P3608 [INFO] 	   entered correctly and the DNS A/AAAA record(s) for that domain
2021-02-05 11:42:04,125 P3608 [INFO] 	   contain(s) the right IP address.

Check that your domain is correctly configured in Route53. You need to migrate your domain to Amazon Route53.


Photo by CHUTTERSNAP on Unsplash