From 1c7230af3484d9473d5623196c5c3cbde0220926 Mon Sep 17 00:00:00 2001 From: JrCs <90z7oey02@sneakemail.com> Date: Sat, 5 Dec 2015 13:21:03 +0100 Subject: [PATCH] Add letsencrypt service into nginx-proxy --- Dockerfile | 7 +++- LICENSE | 2 +- Procfile | 5 ++- README.md | 24 +++++++++++- install_simp_le.sh | 30 ++++++++++++++ letsencrypt_service | 73 +++++++++++++++++++++++++++++++++++ letsencrypt_service_data.tmpl | 10 +++++ nginx.tmpl | 20 ++++++++++ update_certs | 5 +++ update_nginx | 10 +++++ 10 files changed, 181 insertions(+), 5 deletions(-) create mode 100755 install_simp_le.sh create mode 100755 letsencrypt_service create mode 100644 letsencrypt_service_data.tmpl create mode 100755 update_certs create mode 100755 update_nginx diff --git a/Dockerfile b/Dockerfile index 4111003..2b384c3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,7 +23,12 @@ RUN wget https://github.com/jwilder/docker-gen/releases/download/$DOCKER_GEN_VER && tar -C /usr/local/bin -xvzf docker-gen-linux-amd64-$DOCKER_GEN_VERSION.tar.gz \ && rm /docker-gen-linux-amd64-$DOCKER_GEN_VERSION.tar.gz -COPY . /app/ +# Install simp_le program +COPY /install_simp_le.sh /app/install_simp_le.sh +RUN chmod +rx /app/install_simp_le.sh && sync && /app/install_simp_le.sh && rm -f /app/install_simp_le.sh + +COPY docker-entrypoint.sh Dockerfile letsencrypt_service letsencrypt_service_data.tmpl LICENSE \ + nginx.tmpl Procfile update_certs update_nginx /app/ WORKDIR /app/ ENV DOCKER_HOST unix:///tmp/docker.sock diff --git a/LICENSE b/LICENSE index fc926a8..2865b99 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014 Jason Wilder +Copyright (c) 2014-2015 Jason Wilder Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Procfile b/Procfile index 8547156..e76aad9 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,3 @@ -nginx: nginx -dockergen: docker-gen -watch -only-exposed -notify "nginx -s reload" /app/nginx.tmpl /etc/nginx/conf.d/default.conf +nginx: /usr/sbin/nginx +dockergen: /usr/local/bin/docker-gen -watch -only-exposed -notify "/app/update_nginx" /app/nginx.tmpl /etc/nginx/conf.d/default.conf +letsencrypt: /app/letsencrypt_service diff --git a/README.md b/README.md index 9d4bb40..2818a4a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![nginx 1.9.6](https://img.shields.io/badge/nginx-1.9.6-brightgreen.svg) ![License MIT](https://img.shields.io/badge/license-MIT-blue.svg) [![Build](https://circleci.com/gh/jwilder/nginx-proxy.svg?&style=shield&circle-token=2da3ee844076a47371bd45da81cf27409ca7306a)](https://circleci.com/gh/jwilder/nginx-proxy) +![nginx 1.9.6](https://img.shields.io/badge/nginx-1.9.6-brightgreen.svg) ![License MIT](https://img.shields.io/badge/license-MIT-blue.svg) [![Build](https://circleci.com/gh/jwilder/nginx-proxy.svg?&style=shield&circle-token=2da3ee844076a47371bd45da81cf27409ca7306a)](https://circleci.com/gh/jwilder/nginx-proxy) [![](https://badge.imagelayers.io/jwilder/nginx-proxy:latest.svg)](https://imagelayers.io/?images=jwilder/nginx-proxy:latest 'Get your own badge on imagelayers.io') nginx-proxy sets up a container running nginx and [docker-gen][1]. docker-gen generates reverse proxy configs for nginx and reloads nginx when containers are started and stopped. @@ -121,6 +121,28 @@ to establish a connection. A self-signed or generic cert named `default.crt` an will allow a client browser to make a SSL connection (likely w/ a warning) and subsequently receive a 503. +#### Let's Encrypt + +Use the Let's Encrypt service to automatically create a valid certificate for a virtual host. + +Set the following environment variables to enable Let's Encrypt support for a container being proxied. + +- `LETSENCRYPT_HOST` +- `LETSENCRYPT_EMAIL` + +The `LETSENCRYPT_HOST` variable most likely needs to be the same as the `VIRTUAL_HOST` variable and must be publicly reachable domains. Specify multiple hosts with a comma delimiter. + +For example + +``` +$ docker run -d -p 80:80 -p 443:443 \ + -e VIRTUAL_HOST="foo.bar.com,bar.com" \ + -e LETSENCRYPT_HOST="foo.bar.com,bar.com" \ + -e LETSENCRYPT_EMAIL="foo@bar.com" \ + -v /var/run/docker.sock:/tmp/docker.sock:ro \ + jwilder/nginx-proxy +``` + ### Basic Authentication Support In order to be able to secure your virtual host, you have to create a file named as its equivalent VIRTUAL_HOST variable on directory diff --git a/install_simp_le.sh b/install_simp_le.sh new file mode 100755 index 0000000..e2240cd --- /dev/null +++ b/install_simp_le.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# Copyright (c) 2015 Yves Blusseau + +set -e + +apt-get update + +# Install python packages needed by simp_le +apt-get install -y -q --no-install-recommends python python-requests + +# Install python packages needed to build simp_le +apt-get install -y -q --no-install-recommends git gcc libssl-dev libffi-dev python-dev python-pip + +# Get Let's Encrypt simp_le client source +git -C /opt clone https://github.com/kuba/simp_le.git + +cd /opt/simp_le +# Upgrade setuptools +pip install -U setuptools +# Install simp_le in /usr/local/bin +python ./setup.py install + +# Make house cleaning +rm -rf /opt/simp_le + +apt-get autoremove -y git gcc libssl-dev libffi-dev python-dev python-pip + +apt-get clean all +rm -r /var/lib/apt/lists/* diff --git a/letsencrypt_service b/letsencrypt_service new file mode 100755 index 0000000..bbb7f88 --- /dev/null +++ b/letsencrypt_service @@ -0,0 +1,73 @@ +#!/bin/bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +seconds_to_wait=3600 + +update_certs() { + [[ ! -f "$DIR"/letsencrypt_service_data ]] && return + + # Load relevant container settings + source "$DIR"/letsencrypt_service_data + + reload_nginx='false' + for cid in "${LETSENCRYPT_CONTAINERS[@]}"; do + # Derive host and email variable names + host_varname="LETSENCRYPT_${cid}_HOST" + # Array variable indirection hack: http://stackoverflow.com/a/25880676/350221 + hosts_array=$host_varname[@] + email_varname="LETSENCRYPT_${cid}_EMAIL" + + params_d_str="" + hosts_array_expanded=("${!hosts_array}") + # First domain will be our base domain + base_domain="${hosts_array_expanded[0]}" + + # Create directorty for the first domain + mkdir -p /etc/nginx/certs/$base_domain + cd /etc/nginx/certs/$base_domain + + for domain in "${!hosts_array}"; do + # Add all the domains to certificate + params_d_str+=" -d $domain" + done + echo "Creating/renewal $base_domain certificates... (${hosts_array_expanded[*]})" + /usr/local/bin/simp_le \ + -f account_key.json -f key.pem -f fullchain.pem \ + $params_d_str \ + --email "${!email_varname}" \ + --server=https://acme-v01.api.letsencrypt.org/directory \ + --default_root /usr/share/nginx/html/ + + simp_le_return=$? + + if [[ $simp_le_return -eq 0 ]]; then + for domain in "${!hosts_array}"; do + # Symlink all alternative names to base domain certificate + ln -sf ./$base_domain/fullchain.pem /etc/nginx/certs/$domain".crt" + ln -sf ./$base_domain/key.pem /etc/nginx/certs/$domain".key" + done + reload_nginx='true' + fi + done + unset LETSENCRYPT_CONTAINERS + if [[ "$reload_nginx" == 'true' ]]; then + /usr/local/bin/docker-gen -only-exposed /app/nginx.tmpl /etc/nginx/conf.d/default.conf + nginx -s reload + fi +} + +pid= +trap '[[ $pid ]] && kill $pid; exec $0' EXIT +trap 'trap - EXIT' INT TERM + +echo 'Waiting 10s before updating certs...' +sleep 10s + +update_certs + +# Wait some amount of time +echo "Sleep for ${seconds_to_wait}s" +sleep $seconds_to_wait & pid=$! +wait +pid= diff --git a/letsencrypt_service_data.tmpl b/letsencrypt_service_data.tmpl new file mode 100644 index 0000000..7c2933f --- /dev/null +++ b/letsencrypt_service_data.tmpl @@ -0,0 +1,10 @@ +LETSENCRYPT_CONTAINERS=({{ range $host, $containers := groupBy $ "Env.LETSENCRYPT_HOST" }}{{ range $container := $containers }} '{{ $container.ID }}' {{ end }}{{ end }}) + +{{ range $hosts, $containers := groupBy $ "Env.LETSENCRYPT_HOST" }} + +{{ range $container := $containers }} +LETSENCRYPT_{{ $container.ID }}_HOST=( {{ range $host := split $hosts "," }}'{{ $host }}' {{ end }}) +LETSENCRYPT_{{ $container.ID }}_EMAIL="{{ $container.Env.LETSENCRYPT_EMAIL }}" +{{ end }} + +{{ end }} diff --git a/nginx.tmpl b/nginx.tmpl index 255cc35..a08ed36 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -143,7 +143,17 @@ server { include /etc/nginx/vhost.d/default; {{ end }} + location /.well-known/ { + root /usr/share/nginx/html; + try_files $uri @proxy_pass; + } + + # Redirect hack. See: http://stackoverflow.com/a/20694873/350221 location / { + error_page 418 = @proxy_pass; return 418; + } + + location @proxy_pass { proxy_pass {{ trim $proto }}://{{ trim $host }}; {{ if (exists (printf "/etc/nginx/htpasswd/%s" $host)) }} auth_basic "Restricted {{ $host }}"; @@ -169,7 +179,17 @@ server { include /etc/nginx/vhost.d/default; {{ end }} + location /.well-known/ { + root /usr/share/nginx/html; + try_files $uri @proxy_pass; + } + + # Redirect hack. See: http://stackoverflow.com/a/20694873/350221 location / { + error_page 418 = @proxy_pass; return 418; + } + + location @proxy_pass { proxy_pass {{ trim $proto }}://{{ trim $host }}; {{ if (exists (printf "/etc/nginx/htpasswd/%s" $host)) }} auth_basic "Restricted {{ $host }}"; diff --git a/update_certs b/update_certs new file mode 100755 index 0000000..b4695a4 --- /dev/null +++ b/update_certs @@ -0,0 +1,5 @@ +#!/bin/bash + +# Copyright (c) 2015 Yves Blusseau + +pkill -f -SIGUSR1 /app/letsencrypt_service diff --git a/update_nginx b/update_nginx new file mode 100755 index 0000000..fcfa074 --- /dev/null +++ b/update_nginx @@ -0,0 +1,10 @@ +#!/bin/bash + +# Copyright (c) 2015 Yves Blusseau + +nginx -s reload + +docker-gen \ + -only-exposed \ + -notify '/app/update_certs' \ + /app/letsencrypt_service_data.tmpl /app/letsencrypt_service_data