diff --git a/vendor/AWS/Matreon.Template b/vendor/AWS/Matreon.Template index 82213af..fc52a2f 100644 --- a/vendor/AWS/Matreon.Template +++ b/vendor/AWS/Matreon.Template @@ -16,7 +16,8 @@ Metadata: - Label: default: Matreon Parameters: - - HostName + - Prefix + - Domain - FromEmail - SmtpHost - SmtpUser @@ -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' @@ -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: @@ -150,6 +162,7 @@ Resources: - install_postgres - install_rails - install_nginx + - install_certbot - prepare_cron_and_services install_cfn: files: @@ -188,6 +201,8 @@ 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 @@ -195,12 +210,18 @@ Resources: 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} @@ -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: @@ -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 @@ -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 @@ -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 diff --git a/vendor/AWS/README.md b/vendor/AWS/README.md index cd2e00f..a40a5f5 100644 --- a/vendor/AWS/README.md +++ b/vendor/AWS/README.md @@ -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. @@ -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: @@ -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. diff --git a/vendor/AWS/redirect_domain.sh b/vendor/AWS/redirect_domain.sh new file mode 100755 index 0000000..6ada7bc --- /dev/null +++ b/vendor/AWS/redirect_domain.sh @@ -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 diff --git a/vendor/certbot/README.md b/vendor/certbot/README.md new file mode 100644 index 0000000..44a2952 --- /dev/null +++ b/vendor/certbot/README.md @@ -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. diff --git a/vendor/certbot/first-certificate.service b/vendor/certbot/first-certificate.service new file mode 100644 index 0000000..79ff27e --- /dev/null +++ b/vendor/certbot/first-certificate.service @@ -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 diff --git a/vendor/certbot/first_certificate.sh b/vendor/certbot/first_certificate.sh new file mode 100755 index 0000000..bfe6ee1 --- /dev/null +++ b/vendor/certbot/first_certificate.sh @@ -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 diff --git a/vendor/certbot/redirect-domain.service b/vendor/certbot/redirect-domain.service new file mode 100644 index 0000000..ffc4a94 --- /dev/null +++ b/vendor/certbot/redirect-domain.service @@ -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 diff --git a/vendor/www/https_upgrade.conf b/vendor/www/https_upgrade.conf new file mode 100644 index 0000000..1efd486 --- /dev/null +++ b/vendor/www/https_upgrade.conf @@ -0,0 +1,4 @@ +server { + listen 80; + return 301 https://$host$request_uri; +} diff --git a/vendor/www/matreon.conf b/vendor/www/matreon.conf index bda936a..975197e 100644 --- a/vendor/www/matreon.conf +++ b/vendor/www/matreon.conf @@ -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; } @@ -45,4 +47,6 @@ server { location /static/ { proxy_pass http://localhost:9112/static/; } + + # Certbot will add its thing here: } diff --git a/vendor/www/matreon/listen b/vendor/www/matreon/listen new file mode 100644 index 0000000..25b2388 --- /dev/null +++ b/vendor/www/matreon/listen @@ -0,0 +1,3 @@ +# Removed by first-certificate.service once HTTPS is enabled (at the same time +# the request upgrader is enabled) +listen 80; diff --git a/vendor/www/matreon/server_name b/vendor/www/matreon/server_name new file mode 100644 index 0000000..6b6c5ad --- /dev/null +++ b/vendor/www/matreon/server_name @@ -0,0 +1,2 @@ +# File is replaced during provisioning +server_name _; diff --git a/vendor/www/nginx.conf b/vendor/www/nginx.conf index a858280..e0e9e17 100644 --- a/vendor/www/nginx.conf +++ b/vendor/www/nginx.conf @@ -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; @@ -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 { -# } -# }