Enabling HTTPS on Elastic Beanstalk without a load balancer
- Aug 6, 2018
The recommended way to enable HTTPS in Elastic Beanstalk is to use one of AWS’s load balancers such as the Application Load Balancer (ALB) which supports autoscaling, fault tolerance, and other things.
This blog is about hosting a web app prototype on a single EC2 instance, using HTTPS via Let’s Encrypt, without a load balancer.
Using an AWS ALB costs a minimum of about $18 per month, on top of any other charges you currently have, such as $5 for the t2.micro instance that you may be running your prototype on.
So, if you’ve only got one EC2 instance in Elastic Beanstalk for your prototype, and don’t currently want the benefits of an ALB (fault tolerance, auto-scaling, etc), but do want the benefits of HTTPS (protection from interception, man-in-the-middle (MITM) attacks, etc), read on.
This blog post is out of date
The Scenario
I’ve been working on an app prototype which is made up of a JS front-end, and a Python Flask backend, all running on Elastic Beanstalk, with Amazon Linux, and Apache server.
Elastic Beanstalk provides a URL which can be used to connect to the app, which looks something like like http://testapp456325.eyiee2b7ip.ap-southeast-2.elasticbeanstalk.com / .. this url is not HTTPS.
The configuration
Create an .ebextensions directory at the root of your project, and within the directory, create the following file called 00_apache_ssl.config
Within the file:
Replace EB_INSTANCE_DOMAIN_NAME with the domain name of your Elastic Beanstalk EC2 instance. i.e. testapp456325.eyiee2b7ip.ap-southeast-2.elasticbeanstalk.com
Replace YOUR_EMAIL_ADDRESS with your email address. i.e. [email protected]
Resources:
sslSecurityGroupIngress:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: {"Fn::GetAtt" : ["AWSEBSecurityGroup", "GroupId"]}
IpProtocol: tcp
ToPort: 443
FromPort: 443
CidrIp: 0.0.0.0/0
files:
/etc/httpd/conf.d/ssl.pre:
mode: "000644"
owner: root
group: root
content: |
LoadModule ssl_module modules/mod_ssl.so
Listen 443
<VirtualHost *:443>
<Directory /opt/python/current/app/build/static>
Order deny,allow
Allow from all
</Directory>
SSLEngine on
SSLCertificateFile "/etc/letsencrypt/live/EB_INSTANCE_DOMAIN_NAME/fullchain.pem"
SSLCertificateKeyFile "/etc/letsencrypt/live/EB_INSTANCE_DOMAIN_NAME/privkey.pem"
SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH
SSLProtocol All -SSLv2 -SSLv3
SSLHonorCipherOrder On
SSLSessionTickets Off
Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"
Header always set X-Frame-Options DENY
Header always set X-Content-Type-Options nosniff
ProxyPass / http://localhost:80/ retry=0
ProxyPassReverse / http://localhost:80/
ProxyPreserveHost on
RequestHeader set X-Forwarded-Proto "https" early
# If you have pages that may take awhile to
# respond, add a ProxyTimeout:
# ProxyTimeout seconds
</VirtualHost>
/tmp/renew_cert_cron:
mode: "000777"
owner: root
group: root
content: |
# renew Lets encrypt cert with certbot command
0 1,13 * * * /tmp/certbot-auto renew
packages:
yum:
epel-release: []
mod24_ssl : []
# Steps here
# 1. Install certbot
# 2. Get cert (stop apache before grabbing)
# 3. Link certs where Apache can grab
# 4. Get the Apache config in place
# 5. Move certbot-auto into tmp folder
container_commands:
10_installcertbot:
command: "wget https://dl.eff.org/certbot-auto;chmod a+x certbot-auto"
20_getcert:
command: "sudo ./certbot-auto certonly --debug --non-interactive --email YOUR_EMAIL_ADDRESS --agree-tos --debug --apache --domains EB_INSTANCE_DOMAIN_NAME --keep-until-expiring"
30_link:
command: "sudo ln -sf /etc/letsencrypt/live/EB_INSTANCE_DOMAIN_NAME /etc/letsencrypt/live/ebcert"
40_config:
command: "sudo mv /etc/httpd/conf.d/ssl.pre /etc/httpd/conf.d/ssl.conf"
50_mv_certbot_to_temp_for_cron_renew:
command: "sudo mv ./certbot-auto /tmp"
60_create_cert_crontab:
command: "sudo crontab /tmp/renew_cert_cron"
70_delete_cronjob_file:
command: "sudo rm /tmp/renew_cert_cron"
Deploy to Elastic Beanstalk
Now you should be all good to deploy. If the configuration change worked correctly, it should now be possible to connect to your Elastic Beanstalk via HTTPS.
Keep in mind that the changes made by container_commands in 00_apache_ssl.config will remain, even if you roll back your application code.
If you want to roll back any changes made by container_commands, you’ll need to rebuild the Elastic Beanstalk instance.
Troubleshooting
If you get errors in deployment, you’ll need to log into the Elastic Beanstalk instance and start troubleshooting to figure out what’s causing the error.
To start the process, log into the Elastic Beanstalk instance via eb ssh
Run each of the commands in container_commands individually, to find out if any of the container commands have triggered the error.
Finally, run apachectl configtest to see if it’s the Apache config that’s causing the problem.
Please let me know in the comments below if this blog post needs to be updated.
The certbot –debug flag is currently required on Amazon Linux
If you remove the --debug
flag, you might see this error in eb-commandprocessor.log or eb-activity.log:
Amazon Linux support is very experimental at present...
if you would like to work on improving it, please ensure you have backups
and then run this script again with the --debug flag!
Alternatively, you can install OS dependencies yourself and run this script
again with --no-bootstrap.
(ElasticBeanstalk::ExternalInvocationError)
At the time of writing, the --debug
flag is required.
YAML can be error-prone
YAML can be hard to edit, which means that it’s easy to get a parse error in 00_apache_ssl.config
Here’s the kind of error you’ll get if the indentation is wrong in 00_apache_ssl.config
The configuration file .ebextensions/00_apache_ssl.config in application version python-v4 contains invalid YAML or JSON. YAML exception: Invalid Yaml: while parsing a block mapping in "<reader>", line 53, column 5: yum: ^ expected <block end>, but found BlockMappingStart in "<reader>", line 55, column 9: mod24_ssl : [] ^ , JSON exception: Invalid JSON: Unexpected character (r) at position 0.. Update the configuration file.
Capitalisation of ‘Resources’ in .ebextensions is important
'.ebextensions/00_apache_ssl.config' - Contains invalid key: 'resources'. For information about valid keys, see http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/ebextensions.html
Make sure you use a capital ‘R’ in Resources
CN was longer than 64 bytes
There’s a limit to how long your domain name can be. For example, while running the certbot command, you may get an error such as:
Error: urn:ietf:params:acme:error:malformed :: The request message was malformed :: Error finalizing order :: issuing precertificate: CN was longer than 64 bytes
i.e your domain name is TestApp456324-env-2.eyiee2b7ip.ap-southeast-2.elasticbeanstalk.com
which is 66 characters.
Changing the domain so that it’s i.e. TestApp456324-env.eyiee2b7ip.ap-southeast-2.elasticbeanstalk.com
is 64 characters, and works as expected.
Order not allowed in ssl.conf
AH00526: Syntax error on line 4 of /etc/httpd/conf.d/ssl.conf:
order not allowed here
To fix this, I wrapped Order
and Allow
within a Directory
tag,
and the SSL config within a VirtualHost
tag.
fullchain.pem does not exist or is empty
SSLCertificateFile: file '/etc/letsencrypt/live/TestApp456325.eyiee2b7ip.ap-southeast-2.elasticbeanstalk.com/fullchain.pem' does not exist or is empty
Make sure you use a lowercase URL. i.e. TestApp456325.eyiee2b7ip.ap-southeast-2.elasticbeanstalk.com
must be testApp456325.eyiee2b7ip.ap-southeast-2.elasticbeanstalk.com
Credit to Spencer Jones
This blog post was based off of a 2016 post by Spencer Jones: Free, Automated SSL with a Single AWS EC2 Instance
I found his post while trying to get HTTPS set up on my single instance Elastic Beanstalk setup. I had to make a few tweaks to get it work.
Specifically:
- Fixed up the indentation. The file wasn’t quite valid YAML.
- Fixed an “Order not allowed in ssl.conf” error. See troubleshooting steps above.
- On the certbot command, changing the –standalone flag to –apache due to an error when using
--standalone
while Apache was running.
Thanks for reading!
I hope you found this post useful. Do you have any questions? Are there any follow-up blog posts you’d like me to write relating to this? Feel free to leave any feedback in the comments below.