e25e202da9519dbce9978555c3daf30d746f4d36
haproxy-letsencrypt-docker.md
| ... | ... | @@ -22,7 +22,7 @@ Docker and HAProxy and Let's Encrypt: pain in the arse. |
| 22 | 22 | There's a few things that make this a bit of a hassle: |
| 23 | 23 | * We want haproxy to be running on port 80/443, but those are the ports certbot needs to do validation.<br/>We'll do this in two stages for minimum pain. |
| 24 | 24 | * haproxy with the default config won't start up if it can't resolve the container IPs for the backends.<br/>This is a general problem with haproxy and containers. We'll do some config to make it work. |
| 25 | -* certbot needs to be run once in one way to request the certs, and then every couple of days/weeks in another way to check and renew certs.<br/>We'll need two different incantations for certbot. |
|
| 25 | +* certbot needs to be run one way to request the certs, and then every couple of days/weeks another way to check and renew certs.<br/>We'll need two different incantations for certbot. |
|
| 26 | 26 | * When the certs are renewed, we'll need to tell haproxy to pick them up<br/>Some docker-in-docker magic is required. |
| 27 | 27 | * certbot doesn't know how to make haproxy-complicit cert pem files<br/>We'll need to do a little scripting. Not much though. 3-lines, max. |
| 28 | 28 | |
| ... | ... | @@ -178,7 +178,7 @@ backend wiki |
| 178 | 178 | What's going on here then? |
| 179 | 179 | 1. The global section logs everything to stdout, because that's what you do with docker. [rule 6](/rules#thou-shalt-respect-the-sanctity-of-stdout) does not apply in dockerland. |
| 180 | 180 | 2. We're setting the Mozilla recommended ciphers and DH values. Check the [current recommendations](https://mozilla.github.io/server-side-tls/ssl-config-generator/) if you're foolish enough to go into production with this stuff. |
| 181 | -3. We're using 'resolvers' and 'default-server init-addr none' to get around the problem of containers not being up at haproxy startup time. Docker with user-defined networks always puts a resolver at 127.0.0.11:53, and haproxy can use that to resolve container names at runtime instead of startup time. |
|
| 181 | +3. We're using 'resolvers' and 'default-server init-addr none' to get around the problem of containers not being up at haproxy startup time. Docker with user-defined networks always puts a resolver at 127.0.0.11:53, and haproxy can use that to resolve container names at *runtime* instead of *startup *time. |
|
| 182 | 182 | 4. We're binding to port 8080 and 8443, and setting the cert to the Let's Encrypt cert we dumped out in the previous section. The ports will be mapped back to 80 and 443 by docker later on. |
| 183 | 183 | 5. Always redirect to https. |
| 184 | 184 | 6. All traffic that matches the certbot [ACME](https://ietf-wg-acme.github.io/acme/draft-ietf-acme-acme.html) challenge protocol is directed to our letsencrypt container (to be created later). |
| ... | ... | @@ -187,7 +187,7 @@ What's going on here then? |
| 187 | 187 | Let's wrap this up in docker-compose... |
| 188 | 188 | |
| 189 | 189 | ## docker-compose.yml |
| 190 | -In your current directory (so next to the 'haproxy' dir you created above), put this is `docker-compose.yml`: |
|
| 190 | +In your current directory (next to the 'haproxy' dir you created above), put this is `docker-compose.yml`: |
|
| 191 | 191 | |
| 192 | 192 | ```yaml |
| 193 | 193 | version: '3' |
| ... | ... | @@ -227,3 +227,61 @@ Run that bad boy with `docker-compose up`. You should see some startup messages |
| 227 | 227 | Test port 443 from the docker host with: |
| 228 | 228 | `openssl s_client -connect localhost:443 | openssl x509 -text` |
| 229 | 229 | and you should see your cert if all has gone well. |
| 230 | + |
|
| 231 | +# Stage 3 - automatic cert renewal |
|
| 232 | +So far we've got haproxy up, with certs, and everything is [tickety boo](https://en.wiktionary.org/wiki/tickety-boo). |
|
| 233 | +Those certs only last for 90 days though, and we're not in the habit of breaking [rule 7](/rules#thou-shalt-automate-everything). We'll need a container that can: |
|
| 234 | +* See the certificates we already have |
|
| 235 | +* Renew them |
|
| 236 | +* Tell haproxy something has changed |
|
| 237 | +* Keep doing the above |
|
| 238 | + |
|
| 239 | +## Dockerfile |
|
| 240 | +We've already built the image for this in stage 1, so we're good to go. |
|
| 241 | + |
|
| 242 | +## docker-compose.yml |
|
| 243 | +Add to the existing docker-compose.yml, so it looks like: |
|
| 244 | +```yaml |
|
| 245 | +version: '3' |
|
| 246 | + |
|
| 247 | +services: |
|
| 248 | + haproxy: |
|
| 249 | + container_name: haproxy |
|
| 250 | + image: haproxy:latest |
|
| 251 | + restart: always |
|
| 252 | + volumes: |
|
| 253 | + - ./haproxy/bind:/usr/local/etc/haproxy:ro,Z |
|
| 254 | + - letsencrypt_etc:/etc/letsencrypt |
|
| 255 | + networks: |
|
| 256 | + - haproxy |
|
| 257 | + ports: |
|
| 258 | + - 80:8080 |
|
| 259 | + - 443:8443 |
|
| 260 | + user: '1001' |
|
| 261 | + |
|
| 262 | +letsencrypt: |
|
| 263 | + build: ./letsencrypt |
|
| 264 | + image: letsencrypt |
|
| 265 | + container_name: letsencrypt |
|
| 266 | + restart: always |
|
| 267 | + volumes: |
|
| 268 | + - letsencrypt_etc:/etc/letsencrypt |
|
| 269 | + - /var/run/docker.sock:/var/run/docker.sock:rw,Z |
|
| 270 | + networks: |
|
| 271 | + - haproxy |
|
| 272 | + command: bash -c 'sleep 10; while true; do certbot renew --deploy-hook /deploy-hook; sleep 86400; done' |
|
| 273 | + privileged: true |
|
| 274 | + |
|
| 275 | +volumes: |
|
| 276 | + letsencrypt_etc: |
|
| 277 | + |
|
| 278 | +networks: |
|
| 279 | + haproxy: |
|
| 280 | +``` |
|
| 281 | +What doing? |
|
| 282 | +* We're mounting the letsencrypt volume back up at /etc/letsencrypt |
|
| 283 | +* The docker socket from the host is mounted at /var/run/docker.sock. This lets us do docker operations from inside the container |
|
| 284 | +* There's a small sleep to let haproxy start up, then we attempt a renew, and run the deploy-hook script (see stage 1) if anything changed. |
|
| 285 | +* * The deploy-hook script cats the cert chain and key into an haproxy style .pem file, then sends a SIGHUP to the haproxy container, causing it to re-read its config |
|
| 286 | +* The container is granted priviledged permissions to let the docker socket work |
|
| 287 | + |