diff --git a/Dockerfile b/Dockerfile index 70c9950..64c5ce2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ -# A minimal Nginx container including ContainerPilot and a simple virtualhost config -FROM nginx:latest +# A minimal Nginx container including ContainerPilot +FROM nginx:1.11 # Add some stuff via apt-get RUN apt-get update \ @@ -33,10 +33,10 @@ RUN export CONSUL_TEMPLATE_VERSION=0.14.0 \ && rm /tmp/consul-template.zip # Add Containerpilot and set its configuration -ENV CONTAINERPILOT_VER 2.4.3 +ENV CONTAINERPILOT_VER 2.4.4 ENV CONTAINERPILOT file:///etc/containerpilot.json -RUN export CONTAINERPILOT_CHECKSUM=2c469a0e79a7ac801f1c032c2515dd0278134790 \ +RUN export CONTAINERPILOT_CHECKSUM=6194ee482dae95844046266dcec2150655ef80e9 \ && curl -Lso /tmp/containerpilot.tar.gz \ "https://github.com/joyent/containerpilot/releases/download/${CONTAINERPILOT_VER}/containerpilot-${CONTAINERPILOT_VER}.tar.gz" \ && echo "${CONTAINERPILOT_CHECKSUM} /tmp/containerpilot.tar.gz" | sha1sum -c \ @@ -56,7 +56,10 @@ RUN export JQ_VERSION=1.5 \ && chmod a+x /usr/local/bin/jq # Add our configuration files and scripts -COPY etc /etc +RUN rm -f /etc/nginx/conf.d/default.conf +COPY etc/acme /etc/acme +COPY etc/containerpilot.json /etc/ +COPY etc/nginx /etc/nginx/templates COPY bin /usr/local/bin # Usable SSL certs written here diff --git a/bin/acme b/bin/acme index 66ffc50..d57fead 100755 --- a/bin/acme +++ b/bin/acme @@ -58,7 +58,7 @@ function renewConsulSession () { function createConsulSession () { printf "Creating Consul session... " - local SID=$(curl -sX PUT -d '{"LockDelay":"0s","Name":"acme-lock","Behavior":"release","TTL":"600s"}' ${CONSUL_ROOT}/session/create | awk -F '"' '{print $4}') + local SID=$(curl -sX PUT -d '{"LockDelay":"0s","Name":"acme-lock","Behavior":"release","TTL":"5400s"}' ${CONSUL_ROOT}/session/create | awk -F '"' '{print $4}') rc=$? if [[ $rc -ne 0 ]]; then echo "failed" @@ -106,10 +106,10 @@ function updateKeys () { local TEMP_PRIVKEY="${TEMP_CERT_DIR}/privkey.pem" local FULLCHAIN="${CERT_DIR}/fullchain.pem" local PRIVKEY="${CERT_DIR}/privkey.pem" - if [ -f ${TEMP_FULLCHAIN} -a -f ${TEMP_PRIVKEY} -a "$(cat ${TEMP_FULLCHAIN})" != "" -a "$(cat ${TEMP_PRIVKEY})" != "" ]; then + if [ -f ${TEMP_FULLCHAIN} -a -f ${TEMP_PRIVKEY} -a "$(cat ${TEMP_FULLCHAIN} 2> /dev/null)" != "" -a "$(cat ${TEMP_PRIVKEY} 2> /dev/null)" != "" ]; then cp -f $TEMP_FULLCHAIN $FULLCHAIN cp -f $TEMP_PRIVKEY $PRIVKEY - $SCRIPTPATH/reload.sh + $SCRIPTPATH/reload fi } @@ -126,9 +126,8 @@ case "$1" in acquire-leader) acquireLeader ;; - checkin) - renewConsulSession && - ( acquireLeader || exit 0 ) + watch) + /usr/local/bin/consul-template -config /etc/acme/watch.hcl -consul $CONSUL_HOST:8500 ;; init) if [ -f ${CERT_DIR}/fullchain.pem -a -f ${CERT_DIR}/privkey.pem ]; then @@ -142,13 +141,13 @@ case "$1" in renew-certs) shift renewConsulSession && - acquireLeader && + (acquireLeader || exit 0) && ${SCRIPTPATH}/dehydrated --cron --domain ${ACME_DOMAIN} --hook /etc/acme/dehydrated/hook.sh --config /etc/acme/dehydrated/config.${ACME_ENV} ;; clean-certs) shift renewConsulSession && - acquireLeader && + (acquireLeader || exit 0) && ${SCRIPTPATH}/dehydrated --cleanup --domain ${ACME_DOMAIN} --hook /etc/acme/dehydrated/hook.sh --config /etc/acme/dehydrated/config.${ACME_ENV} ;; generate-challenge-token) @@ -157,6 +156,12 @@ case "$1" in update-keys) updateKeys ;; + checkin) + renewConsulSession && + ( acquireLeader || exit 0 ) && + ${SCRIPTPATH}/dehydrated --cron --domain ${ACME_DOMAIN} --hook /etc/acme/dehydrated/hook.sh --config /etc/acme/dehydrated/config.${ACME_ENV} && + ${SCRIPTPATH}/dehydrated --cleanup --domain ${ACME_DOMAIN} --hook /etc/acme/dehydrated/hook.sh --config /etc/acme/dehydrated/config.${ACME_ENV} + ;; *) echo $"Usage: $0 [ {get,create,renew}-consul-session | acquire-leader | init | checkin | renew-certs | clean-certs | generate-challenge-token ]" exit 1 diff --git a/bin/generate-config b/bin/generate-config new file mode 100755 index 0000000..98f5978 --- /dev/null +++ b/bin/generate-config @@ -0,0 +1,31 @@ +#!/bin/bash +set -e + +SERVICE_NAME=${SERVICE_NAME:-nginx} +CONSUL=${CONSUL:-consul} +CERT_DIR="/var/www/ssl" + +# Determine whether ACME SSL certificates have been acquired +SSL_READY="false" +if [ -f ${CERT_DIR}/fullchain.pem -a -f ${CERT_DIR}/privkey.pem ]; then + export SSL_READY="true" +fi + +# Generate a conf.d config file for every corresponding cond.d Consul template +for f in $(ls -1 /etc/nginx/templates/conf.d/) +do + consul-template \ + -once \ + -dedup \ + -consul ${CONSUL}:8500 \ + -template "/etc/nginx/templates/conf.d/${f}:/etc/nginx/conf.d/${f}" +done + +# Render Nginx configuration template using values from Consul +consul-template \ + -once \ + -dedup \ + -consul ${CONSUL}:8500 \ + -template "/etc/nginx/templates/health.conf:/etc/nginx/health.conf" \ + -template "/etc/nginx/templates/ssl.conf:/etc/nginx/ssl.conf" \ + -template "/etc/nginx/templates/nginx.conf:/etc/nginx/nginx.conf" diff --git a/bin/health-check b/bin/health-check new file mode 100755 index 0000000..136ffad --- /dev/null +++ b/bin/health-check @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +LE_STAGING_CACERT=/etc/acme/ca/lets-encrypt-staging.crt +ACME_ENV=${ACME_ENV:-staging} + +SCHEME=$1 + +if [[ $SCHEME == "https" ]]; then + grep -q $ACME_DOMAIN /etc/hosts || printf "\n\n127.0.0.1 ${ACME_DOMAIN}\n" >> /etc/hosts + if [[ "$ACME_ENV" == "staging" ]]; then + curl --cacert $LE_STAGING_CACERT -fsSo /dev/null "https://${ACME_DOMAIN}/nginx-health" + else + curl -fsSo /dev/null "https://${ACME_DOMAIN}/nginx-health" + fi +else + curl -fsSo /dev/null "http://localhost/nginx-health" +fi diff --git a/bin/reload b/bin/reload new file mode 100755 index 0000000..bfc53e5 --- /dev/null +++ b/bin/reload @@ -0,0 +1,5 @@ +#!/bin/bash +set -e + +generate-config +nginx -s reload diff --git a/bin/reload.sh b/bin/reload.sh deleted file mode 100755 index f4e221b..0000000 --- a/bin/reload.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash - -SERVICE_NAME=${SERVICE_NAME:-nginx} -CONSUL=${CONSUL:-consul} -CERT_DIR="/var/www/ssl" - -# Render Nginx configuration template using values from Consul, -# but do not reload because Nginx has't started yet -preStart() { - consul-template \ - -once \ - -dedup \ - -consul ${CONSUL}:8500 \ - -template "/etc/nginx/nginx.conf.ctmpl:/etc/nginx/nginx.conf" -} - -# Render Nginx configuration template using values from Consul, -# then gracefully reload Nginx -onChange() { - local SSL_READY="false" - if [ -f ${CERT_DIR}/fullchain.pem -a -f ${CERT_DIR}/privkey.pem ]; then - SSL_READY="true" - fi - export SSL_READY - consul-template \ - -once \ - -dedup \ - -consul ${CONSUL}:8500 \ - -template "/etc/nginx/nginx.conf.ctmpl:/etc/nginx/nginx.conf:nginx -s reload" -} - -help() { - echo "Usage: ./reload.sh preStart => first-run configuration for Nginx" - echo " ./reload.sh onChange => [default] update Nginx config on upstream changes" -} - -until - cmd=$1 - if [ -z "$cmd" ]; then - onChange - fi - shift 1 - $cmd "$@" - [ "$?" -ne 127 ] -do - onChange - exit -done diff --git a/bin/sensor.sh b/bin/sensor similarity index 100% rename from bin/sensor.sh rename to bin/sensor diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index d9d553a..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,53 +0,0 @@ -# Autopilot Nginx demo -# -# For most use cases, it's better to build a custom image using this Nginx base image -# see https://github.com/autopilotpattern/wordpress for an example - -nginx: - image: autopilotpattern/nginx - restart: always - mem_limit: 512m - env_file: _env - environment: - - BACKEND=example - - CONSUL_AGENT=1 - - ACME_ENV=staging - ports: - - 80 - - 443 - - 9090 # so we can see telemetry - labels: - - triton.cns.services=nginx - -example: - image: autopilotpattern/nginx-example-app - mem_limit: 128m - env_file: _env - environment: - - CONSUL_AGENT=1 - expose: - - 4000 # not strictly necessary because we don't link - restart: always - labels: - - triton.cns.services=example - -# Start with a single host which will bootstrap the cluster. -# In production we'll want to use an HA cluster. -consul: - image: consul:v0.7.0 - restart: always - mem_limit: 128m - expose: - - 53 - - 8300 - - 8301 - - 8302 - - 8400 - - 8500 - ports: - - 8500 - dns: - - 127.0.0.1 - labels: - - triton.cns.services=consul - command: agent -server -client=0.0.0.0 -bootstrap -ui diff --git a/etc/acme/ca/lets-encrypt-staging.crt b/etc/acme/ca/lets-encrypt-staging.crt new file mode 100644 index 0000000..8c1c4b1 --- /dev/null +++ b/etc/acme/ca/lets-encrypt-staging.crt @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIFATCCAumgAwIBAgIRAKc9ZKBASymy5TLOEp57N98wDQYJKoZIhvcNAQELBQAw +GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDMyMzIyNTM0NloXDTM2 +MDMyMzIyNTM0NlowGjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMIICIjANBgkq +hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA+pYHvQw5iU3v2b3iNuYNKYgsWD6KU7aJ +diddtZQxSWYzUI3U0I1UsRPTxnhTifs/M9NW4ZlV13ZfB7APwC8oqKOIiwo7IwlP +xg0VKgyz+kT8RJfYr66PPIYP0fpTeu42LpMJ+CKo9sbpgVNDZN2z/qiXrRNX/VtG +TkPV7a44fZ5bHHVruAxvDnylpQxJobtCBWlJSsbIRGFHMc2z88eUz9NmIOWUKGGj +EmP76x8OfRHpIpuxRSCjn0+i9+hR2siIOpcMOGd+40uVJxbRRP5ZXnUFa2fF5FWd +O0u0RPI8HON0ovhrwPJY+4eWKkQzyC611oLPYGQ4EbifRsTsCxUZqyUuStGyp8oa +aoSKfF6X0+KzGgwwnrjRTUpIl19A92KR0Noo6h622OX+4sZiO/JQdkuX5w/HupK0 +A0M0WSMCvU6GOhjGotmh2VTEJwHHY4+TUk0iQYRtv1crONklyZoAQPD76hCrC8Cr +IbgsZLfTMC8TWUoMbyUDgvgYkHKMoPm0VGVVuwpRKJxv7+2wXO+pivrrUl2Q9fPe +Kk055nJLMV9yPUdig8othUKrRfSxli946AEV1eEOhxddfEwBE3Lt2xn0hhiIedbb +Ftf/5kEWFZkXyUmMJK8Ra76Kus2ABueUVEcZ48hrRr1Hf1N9n59VbTUaXgeiZA50 +qXf2bymE6F8CAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFMEmdKSKRKDm+iAo2FwjmkWIGHngMA0GCSqGSIb3DQEBCwUA +A4ICAQBCPw74M9X/Xx04K1VAES3ypgQYH5bf9FXVDrwhRFSVckria/7dMzoF5wln +uq9NGsjkkkDg17AohcQdr8alH4LvPdxpKr3BjpvEcmbqF8xH+MbbeUEnmbSfLI8H +sefuhXF9AF/9iYvpVNC8FmJ0OhiVv13VgMQw0CRKkbtjZBf8xaEhq/YqxWVsgOjm +dm5CAQ2X0aX7502x8wYRgMnZhA5goC1zVWBVAi8yhhmlhhoDUfg17cXkmaJC5pDd +oenZ9NVhW8eDb03MFCrWNvIh89DDeCGWuWfDltDq0n3owyL0IeSn7RfpSclpxVmV +/53jkYjwIgxIG7Gsv0LKMbsf6QdBcTjhvfZyMIpBRkTe3zuHd2feKzY9lEkbRvRQ +zbh4Ps5YBnG6CKJPTbe2hfi3nhnw/MyEmF3zb0hzvLWNrR9XW3ibb2oL3424XOwc +VjrTSCLzO9Rv6s5wi03qoWvKAQQAElqTYRHhynJ3w6wuvKYF5zcZF3MDnrVGLbh1 +Q9ePRFBCiXOQ6wPLoUhrrbZ8LpFUFYDXHMtYM7P9sc9IAWoONXREJaO08zgFtMp4 +8iyIYUyQAbsvx8oD2M8kRvrIRSrRJSl6L957b4AFiLIQ/GgV2curs0jje7Edx34c +idWw1VrejtwclobqNMVtG3EiPUIpJGpbMcJgbiLSmKkrvQtGng== +-----END CERTIFICATE----- diff --git a/etc/containerpilot.json b/etc/containerpilot.json index 248b3b3..e70b660 100644 --- a/etc/containerpilot.json +++ b/etc/containerpilot.json @@ -1,12 +1,11 @@ { "consul": "{{ if .CONSUL_AGENT }}localhost{{ else }}{{ if .CONSUL }}{{ .CONSUL }}{{ else }}consul{{ end }}{{ end }}:8500", - "preStart": "/usr/local/bin/reload.sh preStart", - "logging": {"level": "DEBUG"}, + "preStart": "generate-config", "services": [ { "name": "nginx", "port": 80, - "health": "/usr/bin/curl --fail --silent --show-error --output /dev/null http://localhost/nginx-health", + "health": "health-check http", "poll": 10, "ttl": 25, "interfaces": ["eth0"] @@ -14,7 +13,7 @@ { "name": "nginx-public", "port": 80, - "health": "/usr/bin/curl --fail --silent --show-error --output /dev/null http://localhost/nginx-health", + "health": "health-check http", "poll": 10, "ttl": 25, "interfaces": ["eth1", "eth0"] @@ -22,7 +21,15 @@ { "name": "nginx-ssl", "port": 443, - "health": "/usr/local/bin/acme init && /usr/bin/curl --insecure --fail --silent --show-error --output /dev/null --header \"HOST: {{ .ACME_DOMAIN }}\" https://localhost/nginx-health", + "health": "acme init && health-check https", + "poll": 10, + "ttl": 25, + "interfaces": ["eth0"] + }, + { + "name": "nginx-public-ssl", + "port": 443, + "health": "health-check https", "poll": 10, "ttl": 25, "interfaces": ["eth0"] @@ -36,16 +43,9 @@ "interfaces": ["eth1", "eth0"] }{{ end }} ], - "backends": [ - { - "name": "{{ .BACKEND }}", - "poll": 7, - "onChange": "/usr/local/bin/reload.sh" - } - ], "coprocesses": [{{ if .CONSUL_AGENT }} { - "command": ["/usr/local/bin/consul", "agent", + "command": ["consul", "agent", "-data-dir=/var/lib/consul", "-config-dir=/etc/consul", "-rejoin", @@ -57,9 +57,7 @@ {{ if and .CONSUL_AGENT .ACME_DOMAIN }},{{ end }} {{ if .ACME_DOMAIN }} { - "command": ["/usr/local/bin/consul-template", - "-config", "/etc/acme/watch.hcl", - "-consul", "{{ if .CONSUL_AGENT }}localhost{{ else }}{{ if .CONSUL }}{{ .CONSUL }}{{ else }}consul{{ end }}{{ end }}:8500"], + "command": ["acme", "watch"], "restarts": "unlimited" }{{ end }}], "telemetry": { @@ -70,35 +68,22 @@ "help": "Number of accepted connnections that were not handled", "type": "gauge", "poll": 5, - "check": ["/usr/local/bin/sensor.sh", "unhandled"] + "check": ["sensor", "unhandled"] }, { "name": "nginx_connections_load", "help": "Ratio of active connections (less waiting) to the maximum worker connections", "type": "gauge", "poll": 5, - "check": ["/usr/local/bin/sensor.sh", "connections_load"] + "check": ["sensor", "connections_load"] } ] }, "tasks": [{{ if .ACME_DOMAIN }} { "name": "acme-checkin", - "command": [ "/usr/local/bin/acme", "checkin" ], - "frequency": "5m", + "command": [ "acme", "checkin" ], + "frequency": "1h", "timeout": "10s" - }, - { - "name": "acme-renew-certs", - "command": [ "/usr/local/bin/acme", "renew-certs" ], - "frequency": "12h", - "timeout": "10m" - }, - { - "name": "clean-unused-certs", - "command": ["/usr/local/bin/acme", "clean-certs" ], - "frequency": "24h", - "timeout": "10m" - }{{ end }} - ] + }{{ end }}] } diff --git a/etc/nginx/conf.d/site.conf b/etc/nginx/conf.d/site.conf new file mode 100644 index 0000000..85324ad --- /dev/null +++ b/etc/nginx/conf.d/site.conf @@ -0,0 +1,32 @@ +{{ $acme_domain := env "ACME_DOMAIN" }} +{{ $ssl_ready := env "SSL_READY" }} + +# If we're listening on https, define an http listener that redirects everything to https +{{ if eq $ssl_ready "true" }} +server { + server_name _; + listen 80; + + include /etc/nginx/health.conf; + + location / { + return 301 https://$host$request_uri; + } +} +{{ end }} + +server { + server_name _; + # Listen on port 80 unless we have certificates installed, then listen on 443 + listen {{ if ne $ssl_ready "true" }}80{{ else }}443 ssl{{ end }}; + + include /etc/nginx/health.conf; + + location /.well-known/acme-challenge { + alias /var/www/acme/challenge; + } + + location / { + root /usr/share/nginx/html; + } +} diff --git a/etc/nginx/health.conf b/etc/nginx/health.conf new file mode 100644 index 0000000..48cb987 --- /dev/null +++ b/etc/nginx/health.conf @@ -0,0 +1,6 @@ +location /nginx-health { + stub_status; + allow 127.0.0.1; + deny all; + access_log /var/log/nginx/access.log main if=$isNot2xx; +} diff --git a/etc/nginx/nginx.conf b/etc/nginx/nginx.conf new file mode 100644 index 0000000..ae385d3 --- /dev/null +++ b/etc/nginx/nginx.conf @@ -0,0 +1,40 @@ +# This is an example Nginx configuration template file. +# Adjust the values below as required for your application. + +user nginx; +worker_processes 1; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + map $status $isNot2xx { + ~^2 0; + default 1; + } + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for" ' + '$upstream_addr'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + keepalive_timeout 65; + + {{ $ssl_ready := env "SSL_READY" }} + + {{ if eq $ssl_ready "true" }} + include ssl.conf; + {{ end }} + + include conf.d/*.conf; +} diff --git a/etc/nginx/nginx.conf.ctmpl b/etc/nginx/nginx.conf.ctmpl deleted file mode 100644 index 3eee8ef..0000000 --- a/etc/nginx/nginx.conf.ctmpl +++ /dev/null @@ -1,113 +0,0 @@ -# This is an example Nginx configuration template file. -# Adjust the values below as required for your application. - -user nginx; -worker_processes 1; - -error_log /var/log/nginx/error.log warn; -pid /var/run/nginx.pid; - -events { - worker_connections 1024; -} - - -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - map $status $isError { - ~^2 0; - default 1; - } - - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - access_log /var/log/nginx/access.log main; - - sendfile on; - #tcp_nopush on; - - keepalive_timeout 65; - - #gzip on; - - {{ $backend := env "BACKEND" }} - {{ $acme_domain := env "ACME_DOMAIN" }} - {{ $ssl_ready := env "SSL_READY" }} - {{ if service $backend }} - upstream {{ $backend }} { - # write the address:port pairs for each healthy backend instance - {{range service $backend }} - server {{.Address}}:{{.Port}}; - {{end}} - least_conn; - }{{ end }} - - # If we're listening on https, define an http listener that redirects everything to https - {{ if eq $ssl_ready "true" }} - server { - server_name _; - listen 80; - - location / { - return 301 https://$host$request_uri; - } - - # Respond to health requests defined in containerpilot.json - location /nginx-health { - stub_status; - allow 127.0.0.1; - deny all; - # Don't log these requests unless they fail - access_log /var/log/nginx/access.log main if=$isError; - } - } - {{ end }} - - {{ if eq $ssl_ready "true" }} - server { - server_name _; - # Listen on port 80 unless we have certificates installed, then listen on 443 - listen {{ if ne $ssl_ready "true" }}80{{ else }}443 ssl{{ end }}; - {{ if eq $ssl_ready "true" }} - ssl_certificate /var/www/ssl/fullchain.pem; - ssl_certificate_key /var/www/ssl/privkey.pem; - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_prefer_server_ciphers on; - ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA'; - ssl_session_timeout 1d; - ssl_session_cache shared:SSL:50m; - ssl_stapling on; - ssl_stapling_verify on; - add_header Strict-Transport-Security max-age=15768000; - {{ end }} - - location /nginx-health { - stub_status; - allow 127.0.0.1; - deny all; - access_log /var/log/nginx/access.log main if=$isError; - } - - location /.well-known/acme-challenge { - alias /var/www/acme/challenge; - } - - {{ if service $backend }} - location = /{{ $backend }} { - return 301 /{{ $backend }}/; - } - - location /{{ $backend }} { - proxy_pass http://{{ $backend }}; - proxy_redirect off; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - }{{ end }} - } - {{ end }} -} diff --git a/etc/nginx/ssl.conf b/etc/nginx/ssl.conf new file mode 100644 index 0000000..5e91aad --- /dev/null +++ b/etc/nginx/ssl.conf @@ -0,0 +1,13 @@ +# Recommended configurations from: https://wiki.mozilla.org/Security/Server_Side_TLS +# Should result in an A rating with: https://www.ssllabs.com/ + +ssl_certificate /var/www/ssl/fullchain.pem; +ssl_certificate_key /var/www/ssl/privkey.pem; +ssl_protocols TLSv1 TLSv1.1 TLSv1.2; +ssl_prefer_server_ciphers on; +ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA'; +ssl_session_timeout 1d; +ssl_session_cache shared:SSL:50m; +ssl_stapling on; +ssl_stapling_verify on; +add_header Strict-Transport-Security max-age=15768000; diff --git a/example-backend/Dockerfile b/example-backend/Dockerfile deleted file mode 100755 index 3d90db7..0000000 --- a/example-backend/Dockerfile +++ /dev/null @@ -1,42 +0,0 @@ -# a Node.js application container including ContainerPilot -FROM gliderlabs/alpine:3.3 - -# install curl -RUN apk update && apk add \ - nodejs \ - curl \ - && rm -rf /var/cache/apk/* - -# install the Express.js dependency -COPY package.json /opt/example/ -RUN cd /opt/example && npm install - -# Add Consul from https://releases.hashicorp.com/consul -RUN export CONSUL_VERSION=0.7.0 \ - && export CONSUL_CHECKSUM=b350591af10d7d23514ebaa0565638539900cdb3aaa048f077217c4c46653dd8 \ - && curl --retry 7 --fail -vo /tmp/consul.zip "https://releases.hashicorp.com/consul/${CONSUL_VERSION}/consul_${CONSUL_VERSION}_linux_amd64.zip" \ - && echo "${CONSUL_CHECKSUM} /tmp/consul.zip" | sha256sum -c \ - && unzip /tmp/consul -d /usr/local/bin \ - && rm /tmp/consul.zip \ - && mkdir /config - -# Add Containerpilot and set its configuration -ENV CONTAINERPILOT_VER 2.4.3 -ENV CONTAINERPILOT file:///etc/containerpilot.json - -RUN export CONTAINERPILOT_CHECKSUM=2c469a0e79a7ac801f1c032c2515dd0278134790 \ - && curl -Lso /tmp/containerpilot.tar.gz \ - "https://github.com/joyent/containerpilot/releases/download/${CONTAINERPILOT_VER}/containerpilot-${CONTAINERPILOT_VER}.tar.gz" \ - && echo "${CONTAINERPILOT_CHECKSUM} /tmp/containerpilot.tar.gz" | sha1sum -c \ - && tar zxf /tmp/containerpilot.tar.gz -C /usr/local/bin \ - && rm /tmp/containerpilot.tar.gz - -# add our application and configuration -COPY example.js /opt/example/ -COPY containerpilot.json /etc - -EXPOSE 4000 -CMD [ "/usr/local/bin/containerpilot", \ - "node", \ - "/opt/example/example.js" \ -] diff --git a/example-backend/containerpilot.json b/example-backend/containerpilot.json deleted file mode 100755 index 135aa9f..0000000 --- a/example-backend/containerpilot.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "consul": "{{ if .CONSUL_AGENT }}localhost{{ else }}{{ if .CONSUL }}{{ .CONSUL }}{{ else }}consul{{ end }}{{ end }}:8500", - "services": [ - { - "name": "example", - "port": 4000, - "health": "/usr/bin/curl -o /dev/null --fail -s http://localhost:4000/", - "poll": 3, - "ttl": 10 - } - ], - "coprocesses": [{{ if .CONSUL_AGENT }} - { - "command": ["/usr/local/bin/consul", "agent", - "-data-dir=/data", - "-config-dir=/config", - "-rejoin", - "-retry-join", "{{ .CONSUL }}", - "-retry-max", "10", - "-retry-interval", "10s"], - "restarts": "unlimited" - }{{ end }}] -} diff --git a/example-backend/example.js b/example-backend/example.js deleted file mode 100755 index 4c26f53..0000000 --- a/example-backend/example.js +++ /dev/null @@ -1,31 +0,0 @@ -var express = require('express'); - -var app = express(); - -// An array of quotes; source: http://www.journaldev.com/240/my-25-favorite-programming-quotes-that-are-funny-too -var quotes = [ - "The best thing about a boolean is even if you are wrong, you are only off by a bit. (Anonymous)", - "Without requirements or design, programming is the art of adding bugs to an empty text file. (Louis Srygley)", - "Before software can be reusable it first has to be usable. (Ralph Johnson)", - "There are two ways to write error-free programs; only the third one works. (Alan J. Perlis)", - "One [person's] crappy software is another [person's] full time job. (Jessica Gaston)", - "A good programmer is someone who always looks both ways before crossing a one-way street. (Doug Linder)", - "Always code as if the [person] who ends up maintaining your code will be a violent psychopath who knows where you live. (Martin Golding)", - "Deleted code is debugged code. (Jeff Sickel)", - "Walking on water and developing software from a specification are easy if both are frozen. (Edward V Berard)", - "If debugging is the process of removing software bugs, then programming must be the process of putting them in. (Edsger Dijkstra)", - "In order to understand recursion, one must first understand recursion. (Anonymous)", - "The cheapest, fastest, and most reliable components are those that aren’t there. (Gordon Bell)", - "The best performance improvement is the transition from the nonworking state to the working state. (J. Osterhout)" -]; - -// Return a random quote for all requests to the web root -app.get('*', function (req, res) { - res.setHeader('Content-Type', 'text/html'); - var quote = quotes[Math.floor(Math.random()*quotes.length)]; - res.send(quote); -}); - -app.listen(4000, function () { - console.log('Running Example app on port 4000'); -}); diff --git a/example-backend/package.json b/example-backend/package.json deleted file mode 100755 index e00b069..0000000 --- a/example-backend/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "example", - "version": "1.0.0", - "description": "", - "main": "example.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "Casey Bisson, Joyent", - "license": "Mozilla Public License 2.0", - "dependencies": { - "express": "^4.13.4" - } -} diff --git a/local-compose.yml b/local-compose.yml deleted file mode 100644 index 211450f..0000000 --- a/local-compose.yml +++ /dev/null @@ -1,36 +0,0 @@ -nginx: - extends: - file: docker-compose.yml - service: nginx - build: . - mem_limit: 128m - environment: - - CONSUL=consul - - CONSUL_AGENT=1 - - ACME_ENV=staging - links: - - consul:consul - ports: - - 80:80 - - 443:443 - - 9090:9090 # telemetry endpoint - -example: - extends: - file: docker-compose.yml - service: example - build: example-backend/ - environment: - - CONSUL=consul - - CONSUL_AGENT=1 - links: - - consul:consul - ports: - - 4000:4000 - -consul: - extends: - file: docker-compose.yml - service: consul - ports: - - 8500:8500 diff --git a/setup.sh b/setup.sh deleted file mode 100755 index cc81f5a..0000000 --- a/setup.sh +++ /dev/null @@ -1,129 +0,0 @@ -#!/bin/bash -set -e -o pipefail - -help() { - echo 'Usage ./setup.sh [-f docker-compose.yml] [-p project]' - echo - echo 'Checks that your Triton and Docker environment is sane and configures' - echo 'an environment file to use.' - echo - echo 'Optional flags:' - echo ' -f use this file as the docker-compose config file' - echo ' -p use this name as the project prefix for docker-compose' -} - - -# default values which can be overriden by -f or -p flags -export COMPOSE_PROJECT_NAME=nginx -export COMPOSE_FILE= - -# give the docker remote api more time before timeout -export COMPOSE_HTTP_TIMEOUT=300 - -# populated by `check` function whenever we're using Triton -TRITON_USER= -TRITON_DC= -TRITON_ACCOUNT= - -# --------------------------------------------------- -# Top-level commmands - - -# Check for correct configuration -check() { - - command -v docker >/dev/null 2>&1 || { - echo - tput rev # reverse - tput bold # bold - echo 'Docker is required, but does not appear to be installed.' - tput sgr0 # clear - echo 'See https://docs.joyent.com/public-cloud/api-access/docker' - exit 1 - } - command -v json >/dev/null 2>&1 || { - echo - tput rev # reverse - tput bold # bold - echo 'Error! JSON CLI tool is required, but does not appear to be installed.' - tput sgr0 # clear - echo 'See https://apidocs.joyent.com/cloudapi/#getting-started' - exit 1 - } - - # if we're not testing on Triton, don't bother checking Triton config - if [ ! -z "${COMPOSE_FILE}" ]; then - exit 0 - fi - - command -v triton >/dev/null 2>&1 || { - echo - tput rev # reverse - tput bold # bold - echo 'Error! Joyent Triton CLI is required, but does not appear to be installed.' - tput sgr0 # clear - echo 'See https://www.joyent.com/blog/introducing-the-triton-command-line-tool' - exit 1 - } - - # make sure Docker client is pointed to the same place as the Triton client - local docker_user=$(docker info 2>&1 | awk -F": " '/SDCAccount:/{print $2}') - local docker_dc=$(echo $DOCKER_HOST | awk -F"/" '{print $3}' | awk -F'.' '{print $1}') - TRITON_USER=$(triton profile get | awk -F": " '/account:/{print $2}') - TRITON_DC=$(triton profile get | awk -F"/" '/url:/{print $3}' | awk -F'.' '{print $1}') - TRITON_ACCOUNT=$(triton account get | awk -F": " '/id:/{print $2}') - if [ ! "$docker_user" = "$TRITON_USER" ] || [ ! "$docker_dc" = "$TRITON_DC" ]; then - echo - tput rev # reverse - tput bold # bold - echo 'Error! The Triton CLI configuration does not match the Docker CLI configuration.' - tput sgr0 # clear - echo - echo "Docker user: ${docker_user}" - echo "Triton user: ${TRITON_USER}" - echo "Docker data center: ${docker_dc}" - echo "Triton data center: ${TRITON_DC}" - exit 1 - fi - - local triton_cns_enabled=$(triton account get | awk -F": " '/cns/{print $2}') - if [ ! "true" == "$triton_cns_enabled" ]; then - echo - tput rev # reverse - tput bold # bold - echo 'Error! Triton CNS is required and not enabled.' - tput sgr0 # clear - echo - exit 1 - fi - - echo CONSUL=consul.svc.${TRITON_ACCOUNT}.${TRITON_DC}.cns.joyent.com > _env -} - -# --------------------------------------------------- -# parse arguments - -while getopts "f:p:h" optchar; do - case "${optchar}" in - f) export COMPOSE_FILE=${OPTARG} ;; - p) export COMPOSE_PROJECT_NAME=${OPTARG} ;; - esac -done -shift $(expr $OPTIND - 1 ) - -until - cmd=$1 - if [ ! -z "$cmd" ]; then - shift 1 - $cmd "$@" - if [ $? == 127 ]; then - help - fi - exit - fi -do - echo -done - -# default behavior -check