Skip to content
Commits on Source (40)
certs/*
haproxy/certs/*
haproxy/haproxy.cfg
acme-challenge/*
# haproxy
HAproxy for IndieHosters
# Warning: still experimental, use at your own risk
# HAProxy
HAProxy for IndieHosters
## What is it?
This repository contains one of the most advanced and libre HAproxy for docker setup that we are aware of.
It ships with:
- container discovery though docker API (like [smartstack](http://nerds.airbnb.com/smartstack-service-discovery-cloud/)) based on Env var `HOST` (Based on [docker-gen](https://github.com/jwilder/docker-gen))
- container discovery though docker API (like [smartstack](http://nerds.airbnb.com/smartstack-service-discovery-cloud/)) based on Env var `VIRTUAL_HOST` (Based on [docker-gen](https://github.com/jwilder/docker-gen))
- [ocsp](https://en.wikipedia.org/wiki/Online_Certificate_Status_Protocol)
- [letsencrypt](https://letsencrypt.org/)
Once you managed to make this running, each time you add a container with the environment variable `HOST` it will:
Each time you add a container with the environment variable `VIRTUAL_HOST` it will:
- generate a valid certificate (if the dns is pointing correctly)
- serves the OCSP response
- serves your web container with https
......@@ -21,25 +19,31 @@ Once you managed to make this running, each time you add a container with the en
- docker
- docker-compose
## Get started
## Get started ( for libre.sh )
```
git clone https://github.com/indiehosters/haproxy.git
# docker network create lb_web (already done when you install libre.sh )
cd /system/
git clone https://lab.libreho.st/libre.sh/compose/haproxy
cd haproxy
docker-compose up -d
libre enable
libre start
```
The first time, you might need to run letsencrypt-watch in an interactive way to accept TOS and register your account.
Use the following to do so:
In these commands, we just created one network (lb_web) to isolate HAProxy with the web servers, and then, we started the containers of HAProxy and companions.
```
docker-compose run letsencrypt-watch
docker run --network=lb_web -e VIRTUAL_HOST=example.org nginx
```
And profit!
Here, we just started the most simple web server, and added to the lb_web network.
The only thing that HAProxy needs to see and connect to this container are the following:
- expose a port 80
- have a VIRTUAL_HOST variable setup with the domain name
- be in the lb_web network
And finally for HAProxy to be able to provision the Let's encrypt certificate, you need to configure your DNS for example.org to point to the IP of HAProxy.
```
docker run -e HOST=example.org nginx
```
## Contributing
......@@ -59,4 +63,3 @@ You can help us by:
- renewal :)
- docker-swarm compatibility
- other backend (etcd/consul...)
#!/bin/bash -eux
grep -wvFf <(ls /data/domains/) <(ls certs/*/*.csr | grep -vf <(echo -e "node-exporter\ncadvisor") | cut -d"/" -f2) | xargs -i% rm -rf /system/haproxy/certs/%
discovery:
image: jwilder/docker-gen
volumes:
version: '2'
networks:
lb_web:
external: true
services:
discovery:
image: jwilder/docker-gen
volumes:
- "/var/run/docker.sock:/tmp/docker.sock:ro"
- "./templates:/etc/docker-gen/templates"
- "./haproxy:/etc/haproxy"
command: "-watch /etc/docker-gen/templates/haproxy.cfg.tmpl /etc/haproxy/haproxy.cfg"
haproxy:
image: indiehosters/haproxy
volumes:
command: "-watch /etc/docker-gen/templates/haproxy.cfg.tmpl /etc/haproxy/haproxy.cfg"
haproxy:
image: indiehosters/haproxy
volumes:
- "./haproxy:/etc/haproxy"
- "/dev/log:/dev/log"
links:
links:
- letsencrypt
ports:
ports:
- "80:80"
- "443:443"
letsencrypt:
image: nginx
volumes:
networks:
- lb_web
letsencrypt-web:
image: nginx
volumes:
- "./acme-challenge:/usr/share/nginx/html/.well-known/acme-challenge:ro"
letsencrypt-watch:
image: indiehosters/letsencrypt
volumes:
networks:
- lb_web
letsencrypt:
image: libresh/letsencrypt
volumes:
- "./haproxy:/etc/haproxy"
- "./acme-challenge:/html-root/.well-known/acme-challenge/"
- "./letsencrypt:/etc/letsencrypt"
ocsp:
image: indiehosters/ocsp
volumes:
- "./certs:/var/certs"
ocsp:
image: indiehosters/ocsp
volumes:
- "./haproxy:/etc/haproxy"
-----BEGIN CERTIFICATE-----
MIIEEzCCAvugAwIBAgIJAPJb5aIb+XTnMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYD
VQQGEwJGUjETMBEGA1UECAwKU29tZS1TdGF0ZTEOMAwGA1UEBwwFZmxlcnMxITAf
BgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDERMA8GA1UECwwIVGltb3Ro
ZWUxETAPBgNVBAMMCFRpbW90aGVlMSIwIAYJKoZIhvcNAQkBFhN0aW1vdGhlZUB1
bnRlZW0ub3JnMB4XDTE2MDcyMDE1MTMxNVoXDTE3MDcyMDE1MTMxNVowgZ8xCzAJ
BgNVBAYTAkZSMRMwEQYDVQQIDApTb21lLVN0YXRlMQ4wDAYDVQQHDAVmbGVyczEh
MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMREwDwYDVQQLDAhUaW1v
dGhlZTERMA8GA1UEAwwIVGltb3RoZWUxIjAgBgkqhkiG9w0BCQEWE3RpbW90aGVl
QHVudGVlbS5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvG3Ng
Wf/TJ+9gr+WIDZ1MiiR64Hf09oRt+9/Jb7dtGQu5kZsB9XuFv5tUiQwlAobAifwl
CJuOHsB8eTermb8DymhYn7evK3vWuiYarCmi17M9UqFv7ngKW01tZ2mSV0Syb/yT
V1dJ29icRISUCqfxXpySNQu0pLxTbmmoGFYF4LbYXb+hJnx6pP5KXj2u5VTi5g4q
et8vTkwg23uudYus1e4FqzCappEGZ81HXHus4OzPmv1Wrnz+UzqD6V/kpJLu8w6a
AgrDpTpiRlcqaiAN3UzdBNGrbzJ1DBpI2B0SyEt81YbVxW+0982leC8iWge81J2t
WJOFt3bNt1cYHMnzAgMBAAGjUDBOMB0GA1UdDgQWBBSWslWj8fsJuG8eqZ0jmyU6
FSwo+zAfBgNVHSMEGDAWgBSWslWj8fsJuG8eqZ0jmyU6FSwo+zAMBgNVHRMEBTAD
AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAI82Bg4bUd9QQRngj6p1RLHJRRbR32OvJS
/C/L6dGSoAR4wwGjIzM4XzUcFWk/kM7KmbWeF5uwkoLXaKmSylZ9tIJhdYClERq6
RPun2y9L9E1VnR7m8wemqyTzVEOajbOZuuOnTBnrdDyn3G4yIiFV0U6Wovh8w0VK
u99W1dfvLCaRtvDq5lWCGK0mRvvhZigDAwrjn7JymwxehT8XVLlziTws0Kj3Lm8H
a4m7EMlOit9mA8Qmwo3okdsykKLT7AfLDilOat5iF2KBrJzMQ4ssU/CkZpmQu6TV
8vxuv+J2F+XQIQfq1EtZ4pJdbNipLR5Dwp8vDr94cwf57joGe6gI
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCvG3NgWf/TJ+9g
r+WIDZ1MiiR64Hf09oRt+9/Jb7dtGQu5kZsB9XuFv5tUiQwlAobAifwlCJuOHsB8
eTermb8DymhYn7evK3vWuiYarCmi17M9UqFv7ngKW01tZ2mSV0Syb/yTV1dJ29ic
RISUCqfxXpySNQu0pLxTbmmoGFYF4LbYXb+hJnx6pP5KXj2u5VTi5g4qet8vTkwg
23uudYus1e4FqzCappEGZ81HXHus4OzPmv1Wrnz+UzqD6V/kpJLu8w6aAgrDpTpi
RlcqaiAN3UzdBNGrbzJ1DBpI2B0SyEt81YbVxW+0982leC8iWge81J2tWJOFt3bN
t1cYHMnzAgMBAAECggEADK2XH+3EOPyWN1Dk9IHP1r/VvlPMO58xnWj0HfiJtktu
7uOc7npglCF4VJmLmI5jRUN4TxIdeR++3gRExJbgHp0kCu6ddQBX+gcGRT4b0Z9+
ab021QzWAiH0LCyllyPcZHL5PY3AUyeouj+3vaQ73pz0Bx3yGAUpYX0IB1Sjvc8V
pUOpG45tENA270/rJJ6QeFgU/cinLJZZNfMiUuNaa2SP0kZZ5TmLLcQAwbHbu/B4
1e5OToF8VNhS7ux0yjB7yvqlpn4vOXtv3S6cfoc1ZAuPltpm62xWvg1xoJM3MTCd
cbQ9wrn13fNhHUnQrU/hUFBO4dnVa4oNY52oWeiFEQKBgQDT+T4asgShrVqCXvyO
do2eWKEw4YK6E3kl1RuFVPW0EuQJNOiagJ7B2Bu0QoqAKXK3dukftJyY9XSWRZDJ
i0CZFemldv2n++1A+xv9pFRiWIbvXnULTQ8ftSCrViO0+gAroaE4jRrP910h5CQy
5y/dIDpruHT7Nyq7JDp/CICN6QKBgQDTegB+gkiBHx7MAlGi497Nh0WaA8TJDfBa
IJHtSNiFm9WOxYdkb2l+ZhLF5VVU0GiAbN1yHitrKlaWePhoPV/8COwG7/zm3xkR
fGWNT0lE5a430FpEaKqEdVk99YTHrVIZJijzbmTHWIooglEFEoJX66lmHoBBbLyz
J9bx34vjewKBgQCvRqZKzq6zvbNcoAx4BKG/sIvA7of5M0b2uIukKYdt2j0DO+Om
dfpQUVE/MdJyfVNlTwcfd2oRtM0twOIjNWk57HuhnY3LTK53D+yqRCqek5Mn9VQU
/yglUNFtu7GU4TAo4+J673qfQmyZLJueF7plP1PvZLweShSkYCWaiz45mQKBgQDL
LwIAir6MyNC3JFc5UQTFpf16j7pnjevrPDXYrM/T9zjFdwWxutW56uqIluJYnfB5
y58sQM5opYISCuJojOeG6S+VLslnLN0SDtonXhFAkAjGeXnxFjsXDwSSYi2UB/PE
VASudtd/LJN5flPHstPnE3Zrtkx2D7pxy4paU/U8twKBgFd8po4vi7mTLmfb/un8
jmyILpAd3F5BUcSzRB7l+SFv0nVepOFY8xJXVVBwKdAYg+NIley7Ma1GoFfneubf
7sFSlrH4ioeJeYX6bRZIdigep8aV0p7KftwDdksVx2/w6fKujfuT5H7y3pa2th1t
HrbV/GxbHbML7+8CtaFL4MR0
-----END PRIVATE KEY-----
global
log /dev/log local0 info
log /dev/log local0 notice
ca-base /etc/ssl/certs
maxconn 4096
tune.ssl.default-dh-param 2048
ssl-default-bind-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
......@@ -10,12 +11,15 @@ defaults
mode http
option forwardfor
option httpclose
option http-server-close
option httplog
option dontlognull
retries 3
compression algo gzip
compression type text/html text/plain text/css application/javascript
timeout connect 5000
timeout client 50000
timeout server 50000
timeout server 500000
frontend http-in
bind *:80
......@@ -26,22 +30,62 @@ frontend http-in
frontend https-in
mode http
bind *:443 ssl no-sslv3 crt /etc/haproxy/certs
reqadd X-Forwarded-Proto:\ https
bind *:443 ssl no-sslv3 crt /etc/haproxy/certs
reqadd X-Forwarded-Proto:\ https
rspadd Strict-Transport-Security:\ max-age=15768000
rspidel X-Powered-By
use_backend letsencrypt if { path_beg /.well-known/acme }
{{ range $host, $container := groupBy $ "Env.HOST" }}
use_backend {{ $host}} if { hdr(host) -i {{ $host }} www.{{ $host }} }
acl acme path_beg /.well-known/acme
{{ range $host, $containers := groupByMulti $ "Env.VIRTUAL_HOST" "," }}
{{ $reverseProxyFor := (first (groupByKeys $containers "Env.REVERSE_PROXY_FOR")) }}
{{ if $reverseProxyFor }}
use_backend {{ $host }}-acme if acme { hdr(host) -i {{ $host }} }
{{end}}
{{end}}
use_backend letsencrypt-web if acme
{{ range $host, $containers := groupBy $ "Env.LIBRESH_WEBHOOK_HOST" }}
use_backend webhook if { path_beg /XxosJDdRpo7Rww87VkJGzv1QLegnhh-uniq-libresh }
{{end}}
{{ range $host, $containers := groupByMulti $ "Env.VIRTUAL_HOST" "," }}
use_backend {{ $host}} if { hdr(host) -i {{ $host }} }
use_backend {{ $host}} if { hdr(host) -i {{ $host }}:443 }
{{end}}
{{ $containers := whereExist $ "Env.HOST" }}
{{ range $container := $containers }}
backend {{ $container.Env.HOST }}
{{ range $host, $containers := groupByMulti $ "Env.VIRTUAL_HOST" "," }}
{{ $reverseProxyFor := (first (groupByKeys $containers "Env.REVERSE_PROXY_FOR")) }}
{{ if $reverseProxyFor }}
backend {{ $host }}-acme
server Server {{ $reverseProxyFor }}:80
{{end}}
backend {{ $host }}
option http-server-close
cookie SERVERID insert nocache indirect
server Server {{ $container.IP }}:80 cookie Server
{{ range $container := $containers }}
{{ $networkLen := len $container.Networks }}
{{ if $reverseProxyFor }}
http-request set-header Host {{ $host }}
server Server {{ $reverseProxyFor }}:443 ssl sni str({{ $host }}) ca-file ca-certificates.crt
{{ else }}
{{ if eq $networkLen 1 }}
{{ $network := index $container.Networks 0 }}
server Server {{ $network.IP }}:80 cookie Server
{{ else }}
{{ range $network := $container.Networks }}
{{ if eq $network.Name "lb_web" }}
server Server {{ $network.IP }}:80 cookie Server
{{end}}
{{end}}
{{end}}
{{end}}
{{end}}
{{end}}
backend letsencrypt
backend letsencrypt-web
cookie SERVERID insert nocache indirect
server Server letsencrypt:80 cookie Server
server Server letsencrypt-web:80 cookie Server
{{ range $host, $containers := groupBy $ "Env.LIBRESH_WEBHOOK_HOST" }}
backend webhook
cookie SERVERID insert nocache indirect
server Server webhook:80 cookie Server
{{end}}