Some time ago, I found a problem while requesting HTTPS certificate with AWS Certificate Manger (ACM). For me, it took a few days to validate and was rejected. After a few tries, I have decided to use another way. Let’s Encrypt is a well known free, automated, and open certificate authority performing the 90-days with SSL certificate renewal automatically using Apache plugin called Certbot.
Today, I will show you a very simple way to get free SSL certificate in a few minutes and configurate auto-renewal. Let’s go!
Using Certbot with auto-renewal on webroot
Cerbot is awesome plugin to pass through ACME challenge and get Let’sEncrypt certificate absolutely anywhere. First, let’s see how Certbot works on exemple of EC2 web server with Nginx installed. It might be any on-prem server as well. If you have access to web server webroot, all you need is:
1. Install package manager. I will use SNAP in classic mode:
$ sudo snap install --classic certbot
2. (Optional) link Certbot binary to user bin for further usage:
$ sudo ln -s /snap/bin/certbot /usr/bin/certbot
3. Modify /etc/nginx/sites-available/WEBROOT-http.conf to redirect all HTTP traffic to HTTS except of /.well-known/acme-challenge/. It needs us to pass ACME challenge:
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name DOMAIN;
root /var/www/WEBROOT;
location / {
return 301 https://$server_name$request_uri;
}
location /.well-known/acme-challenge/ {}
}
4. Request HTTPS certificate:
$ sudo certbot certonly --webroot -w /var/www/WEBROOT -d example.com -m admin@example.com --agree-tos
5. Certbot will generate three files:
/etc/letsencrypt/live/DOMAIN/fullchain.pem
/etc/letsencrypt/live/DOMAIN/privkey.pem
/etc/letsencrypt/live/DOMAIN/chain.pem
6. Set auto-renewal using /etc/letsencrypt/renewal-hooks and deploying simple bash script:
#!/bin/bash
/usr/bin/systemctl reload nginx.service
7. Set execution permission and check:
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
sudo certbot renew --dry-run
Let’s Encrypt SSL certificate renewal on AWS
First of all, I will show you how to generate SSL certificate and pass ACME challenge from local host machine using docker Certbot container and DNS registerer. After, we will export certificate to ACM and attach certificate to CloudFront distribution. CloudFront will expose static web site content right from S3 bucket.
Manual renewal with docker image
To generate SSL certificate manually all we need is docker image certbot/certbot:
$ docker run -it -v /LOCALPATH:/etc/letsencrypt certbot/certbot certonly --manual --preferred-challenges dns --email admin@example.com --server https://acme-v02.api.letsencrypt.org/directory --agree-tos -d example.com
Running in manual mode, you will be prompted to confirm ACME challenge. To do so, copy base64_str generated in console, go to DNS registerer and add new TXT record:
TXT name | TXT value |
_acme-challenge.DOMAIN | BASE64_STR |
Auto-renewal with Lambda Python script
Next, we have to importe *.pem certificates to ACM to make our CloudFrond distribution available through HTTPS. Now, we need auto renew the certificate when 90 days period will come to the end. To do so, I leverage my simple python script based on boto3 library hosted on AWS Lambda.
1. Download Certbot and install in tmp/ directory.
import certbot.main
import boto3
...
s3 = boto3.client('s3')
acm = boto3.client('acm')
cf = boto3.client('cloudfront')
2. Request new certificate:
def request_certs(emails, domains):
certbot_args = [
# Use /tmp folder
'--config-dir', '/tmp/certbot/config',
'--work-dir', '/tmp/certbot/work',
'--logs-dir', '/tmp/certbot/logs',
# Request cert
'certonly',
# Manual installation
'--manual',
# Domain
'--domains', domains,
# Run in non-interactive mode
'--non-interactive',
# Agree
'--manual-public-ip-logging-ok',
# Agree to the terms of service
'--agree-tos',
# Email of domain administrators
'--email', emails,
# Validation scripts
'--manual-auth-hook', 'python auth-hook.py',
'--manual-cleanup-hook', 'python cleanup-hook.py',
'--preferred-challenges', 'http',
]
certbot.main.main(certbot_args)
3. (Optional) Renew existed certificate if expiration date comes to the end.
3.1 Check if certificate is close to expiration data:
cert = acm.describe_certificate(
CertificateArn=cert_arn
)
expireAt = (cert['Certificate']['NotAfter'] - datetime.now(timezone.utc)).days
3.2 If yes, renew:
def renew_certs(domains):
certbot_args = [
# Override directory paths so script doesn't have to be run as root
'--config-dir', '/tmp/certbot/config',
'--work-dir', '/tmp/certbot/work',
'--logs-dir', '/tmp/certbot/logs',
'--force-renewal',
# Renew
'renew',
'--cert-name', domains,
]
certbot.main.main(certbot_args)
3.3 Import to ACM:
response = acm.import_certificate(
CertificateArn=DOMAIN
Certificate=get_file_contents('/tmp/certbot/config/live/{}/cert.pem'.format(domain)),
PrivateKey=get_file_contents('/tmp/certbot/config/live/{}/privkey.pem'.format(domain)),
CertificateChain=get_file_contents('/tmp/certbot/config/live/{}/chain.pem'.format(domain))
4. The last step is getting certificate ARN from ACM and update CloudFrond distribution with new certificate one:
for c in acm.list_certificates():
if (c['DomainName'] == DOMAIN):
arn = c['CertificateArn']
break
cf.update_distribution(
DistributionConfig=
...
'ViewerCertificate': {
'CloudFrontDefaultCertificate': True,
'ACMCertificateArn': ARN,
'MinimumProtocolVersion': 'TLSv1.2_2021',
'CertificateSource': 'acm'
}
...
)
Wait for a some time for CloudFront distribution to be updated.
I hope you like the post. Please subscribe me on Twitter for mode content or follow my newsletter below. To view more interesting content go to CyberTechTalk web page.
Be an ethical, save your privacy!