Skip to content

Commit

Permalink
SSL using Let's Encrypt
Browse files Browse the repository at this point in the history
Fixes #42. Dig is used to monitor for an A-Record. Once detected,
we use Certbot to obtain a certificate.
  • Loading branch information
Sjors committed Jun 3, 2018
1 parent be2dc41 commit 92ec07f
Show file tree
Hide file tree
Showing 12 changed files with 184 additions and 40 deletions.
82 changes: 76 additions & 6 deletions vendor/AWS/Matreon.Template
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ Metadata:
- Label:
default: Matreon
Parameters:
- HostName
- Prefix
- Domain
- FromEmail
- SmtpHost
- SmtpUser
Expand Down Expand Up @@ -47,15 +48,22 @@ Parameters:
AllowedValues:
- testnet
- bitcoin
HostName:
Default: http://example.com
Description: Hostname, no trailing slash
Prefix:
Type: String
Description: For https:// we'll automatically request a certificate. You agree to the ACME server's Subscriber Agreement.
Default: 'https'
AllowedValues:
- 'https'
- 'http'
Domain:
Default: example.com
Description: Domain without http(s), no trailing slash
Type: String
MinLength: '5'
MaxLength: '100'
FromEmail:
Default: you@example.com
Description: From email address
Description: From email address. Also submitted during HTTPS certificate registration.
Type: String
MinLength: '5'
MaxLength: '100'
Expand Down Expand Up @@ -130,9 +138,13 @@ Parameters:
Conditions:
NetworkBitcoin: !Equals [!Ref 'Network', 'bitcoin']
NetworkTestnet: !Equals [!Ref 'Network', 'testnet']
UseDomain: !Not [!Equals [!Ref 'Domain', '']]
SSL: !Equals [!Ref 'Prefix', 'https']

Resources:
WebServer:
Type: AWS::EC2::Instance
DependsOn: IPAddress
Metadata:
AWS::CloudFormation::Init:
configSets:
Expand All @@ -150,6 +162,7 @@ Resources:
- install_postgres
- install_rails
- install_nginx
- install_certbot
- prepare_cron_and_services
install_cfn:
files:
Expand Down Expand Up @@ -188,19 +201,27 @@ Resources:
command: groupadd -r charge && useradd -r -m -g charge charge
03_matreon:
command: groupadd -r matreon && useradd -r -m -g matreon matreon
04_certbot:
command: groupadd -r certbot && useradd -r -m -g certbot certbot
10_lightningrpc_group:
command: groupadd lightningrpc
&& usermod -a -G lightningrpc bitcoin
&& usermod -a -G lightningrpc charge

env_files:
files:
/root/.env:
content: !Sub |
DOMAIN=${Domain}
IP=${IPAddress}
EMAIL=${FromEmail}

/home/matreon/.env:
content: !Sub |
RAILS_ENV=production
NODE_ENV=production
DATABASE_URL=postgres://matreon@localhost:5432
HOSTNAME=${HostName}
HOSTNAME=${Prefix}://${Domain}
FROM_EMAIL=${FromEmail}
BUGS_TO=${BugsEmail}
SMTP_HOST=${SmtpHost}
Expand Down Expand Up @@ -382,12 +403,42 @@ Resources:
command: echo "DEVISE_SECRET_KEY=`hexdump -n 64 -e '16/4 \"%08x\" 1 \"\n\"' /dev/random`" >> /home/matreon/.env

install_nginx:
files:
/etc/nginx/conf.d/matreon/server_name:
content:
!If
- UseDomain
- !Sub |
server_name ${Domain};
- !Sub |
server_name _;

/etc/nginx/conf.d/redirect_domain.conf.disabled:
content: !Sub |
server {
server_name *.amazonaws.com;
listen 80;
return 301 http://${Domain}$request_uri;
}
commands:
01_install:
command: amazon-linux-extras install nginx1.12
02_copy_conf:
command: cp /usr/local/src/matreon/vendor/www/nginx.conf /etc/nginx/nginx.conf
&& cp /usr/local/src/matreon/vendor/www/matreon.conf /etc/nginx/conf.d
&& cp /usr/local/src/matreon/vendor/www/matreon/listen /etc/nginx/conf.d/matreon

install_certbot:
commands:
01_add_EPEL7:
command: yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm -y
02_install:
command: yum install -y python2-certbot-nginx
03_copy_https_upgrade_conf:
command: cp /usr/local/src/matreon/vendor/www/https_upgrade.conf /etc/nginx/conf.d/https_upgrade.conf.disabled
04_add_cron_daily_at_random_time:
command: echo "`shuf -i 00-59 -n 1` `shuf -i 00-23 -n 1` * * * /usr/bin/certbot renew --quiet" >> /usr/local/src/matreon/vendor/AWS/crontab-matreon


prepare_cron_and_services:
commands:
Expand Down Expand Up @@ -421,6 +472,18 @@ Resources:
16_run_nginx_service:
command: systemctl enable nginx
&& systemctl start nginx
17_run_redirect_domain_service:
command:
!If
- UseDomain
- systemctl enable redirect-domain.service && systemctl start redirect-domain.service
- echo
17_run_first_certificate_service:
command:
!If
- SSL
- systemctl enable first-certificate.service && systemctl start first-certificate.service
- echo

Properties:
ImageId: ami-43eec3a8
Expand Down Expand Up @@ -469,6 +532,10 @@ Resources:
FromPort: '80'
ToPort: '80'
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: '443'
ToPort: '443'
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort:
!If
Expand All @@ -487,6 +554,9 @@ Resources:
CidrIp: 0.0.0.0/0

Outputs:
IP:
Value: !Sub '${WebServer.PublicIp}'
Description: IP address to use for DNS A-Record
WebsiteURL:
Value: !Sub 'http://${WebServer.PublicDnsName}/'
Description: URL for your Matreon
24 changes: 23 additions & 1 deletion vendor/AWS/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
## Parameters

### Domain

If you change the domain later, update it in `/etc/nginx/conf.d/matreon.conf`, `/home/matreon/.env` and `/root/.env`.

Then restart Nginx and Rails:

```
systemctl restart nginx
systemctl restart rails.service
```

Create new certificate if needed (TODO: explain steps).

## Deploy

See [README](/README.md#deploy-to-aws) for a UI based deploy process.
Expand Down Expand Up @@ -74,7 +89,7 @@ export SMTP_PASSWORD=...
export GIT_URL=https://github.com/Sjors/matreon.git
export GIT_BRANCH=`git rev-parse --abbrev-ref HEAD` # don't forget to push if you're working on a branch

aws cloudformation create-stack --template-body file:///$PWD/vendor/AWS/Matreon.Template --stack-name $STACK --parameters ParameterKey=Network,ParameterValue=$NETWORK ParameterKey=KeyName,ParameterValue=$KEY_NAME ParameterKey=HostName,ParameterValue=$HOSTNAME ParameterKey=FromEmail,ParameterValue=$FROM_EMAIL ParameterKey=BugsEmail,ParameterValue=$BUGS_TO ParameterKey=SmtpHost,ParameterValue=$SMTP_HOST ParameterKey=SmtpPort,ParameterValue=$SMTP_PORT ParameterKey=SmtpUser,ParameterValue=$SMTP_USERNAME ParameterKey=SmtpPassword,ParameterValue=$SMTP_PASSWORD ParameterKey=GitURL,ParameterValue=$GIT_URL ParameterKey=GitBranch,ParameterValue=$GIT_BRANCH
aws cloudformation create-stack --template-body file:///$PWD/vendor/AWS/Matreon.Template --stack-name $STACK --parameters ParameterKey=Network,ParameterValue=$NETWORK ParameterKey=KeyName,ParameterValue=$KEY_NAME ParameterKey=Prefix,ParameterValue=$PREFIX ParameterKey=Domain,ParameterValue=$DOMAIN ParameterKey=FromEmail,ParameterValue=$FROM_EMAIL ParameterKey=BugsEmail,ParameterValue=$BUGS_TO ParameterKey=SmtpHost,ParameterValue=$SMTP_HOST ParameterKey=SmtpPort,ParameterValue=$SMTP_PORT ParameterKey=SmtpUser,ParameterValue=$SMTP_USERNAME ParameterKey=SmtpPassword,ParameterValue=$SMTP_PASSWORD ParameterKey=GitURL,ParameterValue=$GIT_URL ParameterKey=GitBranch,ParameterValue=$GIT_BRANCH
```

You can follow the progress in the [management console](https://eu-central-1.console.aws.amazon.com/cloudformation/home?region=eu-central-1#/stacks) or:
Expand Down Expand Up @@ -103,6 +118,13 @@ To follow the provisioning process:
tail -f /var/log/cfn-init-cmd.log
```

To monitor systemd services:

```sh
journalctl -xe -f
journalctl -xe -f --unit first-certificate.service
```

Once provisioning is complete, the temporary IP is changed to a permanent one.
This may cause your SSH connection to freeze, and you'll need to update $HOSTNAME.

Expand Down
11 changes: 11 additions & 0 deletions vendor/AWS/redirect_domain.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
while sleep 60
do
if dig $DOMAIN | grep $IP; then
mv /etc/nginx/conf.d/redirect_domain.conf.disabled /etc/nginx/conf.d/redirect_domain.conf
systemctl restart nginx
exit 0
fi
echo "DNS entry (A record for $DOMAIN to $IP) not found yet..."
done
exit 1
6 changes: 6 additions & 0 deletions vendor/certbot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
## Permissions

Although the AWS template contains a user `certbot`, the actual Certbot, systemd
services and cron jobs using it all run as root. This is because they need to
modify nginx configuration files both to install the certificate and, more importantly,
to pass the domain control verification challenge.
15 changes: 15 additions & 0 deletions vendor/certbot/first-certificate.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[Unit]
Description=Harden, Diffie-Hellman Parameters, wait for A-Record, request and install first certificate.
After=network.target
ConditionPathExists=/etc/nginx/conf.d/https_upgrade.conf.disabled
ConditionPathExists=!/home/cert/.failed

[Service]
EnvironmentFile=/root/.env
ExecStart=/usr/local/src/matreon/vendor/certbot/first_certificate.sh

Type=simple
Restart=on-failure

[Install]
WantedBy=multi-user.target
22 changes: 22 additions & 0 deletions vendor/certbot/first_certificate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
if [ ! -f /etc/ssl/certs/dhparam.pem ]; then
openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048
fi

while sleep 60
do
if dig $DOMAIN | grep $IP; then
if /bin/certbot --nginx -m $EMAIL --agree-tos -n --domains $DOMAIN; then
mv /etc/nginx/conf.d/https_upgrade.conf.disabled /etc/nginx/conf.d/https_upgrade.conf
>/etc/nginx/conf.d/matreon/listen
systemctl restart nginx
exit 0
else
echo "Failed to register or install certificate, remove /home/certbot/.failed and restart the service to try again."
touch /home/cert/.failed
exit 1
fi
fi
echo "DNS entry (A record for $DOMAIN to $IP) not found yet..."
done
exit 1
14 changes: 14 additions & 0 deletions vendor/certbot/redirect-domain.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[Unit]
Description=Wait for A-record and enables redirect
After=network.target
ConditionPathExists=/etc/nginx/conf.d/redirect_domain.conf.disabled

[Service]
EnvironmentFile=/root/.env
ExecStart=/usr/local/src/matreon/vendor/AWS/redirect_domain.sh

Type=simple
Restart=on-failure

[Install]
WantedBy=multi-user.target
4 changes: 4 additions & 0 deletions vendor/www/https_upgrade.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
server {
listen 80;
return 301 https://$host$request_uri;
}
10 changes: 7 additions & 3 deletions vendor/www/matreon.conf
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
upstream matreon {
upstream matreon_rails {
server unix:///var/www/matreon/tmp/puma.sock;
}

server {
listen 80;
include /etc/nginx/conf.d/matreon/server_name;
include /etc/nginx/conf.d/matreon/listen;

root /var/www/matreon/public;

location / {
proxy_pass http://matreon;
proxy_pass http://matreon_rails;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
Expand Down Expand Up @@ -45,4 +47,6 @@ server {
location /static/ {
proxy_pass http://localhost:9112/static/;
}

# Certbot will add its thing here:
}
3 changes: 3 additions & 0 deletions vendor/www/matreon/listen
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Removed by first-certificate.service once HTTPS is enabled (at the same time
# the request upgrader is enabled)
listen 80;
2 changes: 2 additions & 0 deletions vendor/www/matreon/server_name
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# File is replaced during provisioning
server_name _;
31 changes: 1 addition & 30 deletions vendor/www/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ http {
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_names_hash_bucket_size 1000;

include /etc/nginx/mime.types;
default_type application/octet-stream;
Expand All @@ -31,33 +32,3 @@ http {
# for more information.
include /etc/nginx/conf.d/*.conf;
}

# Settings for a TLS enabled server.
#
# server {
# listen 443 ssl http2 default_server;
# listen [::]:443 ssl http2 default_server;
# server_name _;
# root /usr/share/nginx/html;
#
# ssl_certificate "/etc/pki/nginx/server.crt";
# ssl_certificate_key "/etc/pki/nginx/private/server.key";
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 10m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
#
# # Load configuration files for the default server block.
# include /etc/nginx/default.d/*.conf;
#
# location / {
# }
#
# error_page 404 /404.html;
# location = /40x.html {
# }
#
# error_page 500 502 503 504 /50x.html;
# location = /50x.html {
# }
# }

0 comments on commit 92ec07f

Please sign in to comment.