1
0
Fork 0

Merge pull request #24 from byjg/redirect-ssl

Add `redirect-ssl` and different `ssl_modes`
This commit is contained in:
Joao M 2022-08-17 10:51:47 -05:00 committed by GitHub
commit 21a4a056cb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 491 additions and 81 deletions

View file

@ -13,6 +13,7 @@ RUN apk add --no-cache haproxy bash python3 py3-pip py-yaml supervisor docker ce
&& pip3 install --upgrade pip \ && pip3 install --upgrade pip \
&& pip install -r requirements.txt \ && pip install -r requirements.txt \
&& pytest -s tests/ \ && pytest -s tests/ \
&& openssl dhparam -out /etc/haproxy/dhparam 2048 && openssl dhparam -out /etc/haproxy/dhparam 2048 \
&& openssl dhparam -out /etc/haproxy/dhparam-1024 1024
CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisord.conf" ] CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisord.conf" ]

View file

@ -8,19 +8,26 @@
Service discovery for HAProxy. Service discovery for HAProxy.
This Docker image will create dynamically the `haproxy.cfg` based on the labels defined in docker containers or from This Docker image will dynamically create the `haproxy.cfg` based on the labels defined in docker containers or from
a simple Yaml. a simple Yaml.
## Features ## Features
- Enable or disable Stats on port 1936 with custom password EasyHAProxy will discover the services based on the Docker Tags of the running containers in a Docker host or Docker Swarm cluster and dynamically set up the `haproxy.cfg`. Below, EasyHAProxy main features::
- Discover and setup haproxy from Docker Tag - Use Letsencrypt with HAProxy.
- Discover and setup haproxy redirect from Docker Tag - Balance traffic between multiple replicas
- Setup HAProxy CFG from a Yaml file. - Set SSL with three different levels of validations and according to the most recent definitions.
- Include your SSL certificate.
- Setup HAProxy to listen to TCP.
- Add redirects.
- Enable/disable Stats on port 1936 with a custom password.
- Enable/disable custom errors.
Also, it is possible to set up HAProxy from a simple Yaml file instead of creating `haproxy.cfg` file.
## Basic Usage ## Basic Usage
The Easy HAProxy will create the `haproxy.cfg` automatically based on the containers or from a YAML provided. The Easy HAProxy will automatically create the `haproxy.cfg` file based on the containers or a YAML provided.
The basic command line to run is: The basic command line to run is:
@ -41,14 +48,15 @@ The environment variables will setup the HAProxy.
| Environment Variable | Description | | Environment Variable | Description |
|-------------------------------|-------------------------------------------------------------------------------| |-------------------------------|-------------------------------------------------------------------------------|
| EASYHAPROXY_DISCOVER | How `haproxy.cfg` will be created: `static`, `docker` or `swarm` | | EASYHAPROXY_DISCOVER | How `haproxy.cfg` will be created: `static`, `docker` or `swarm` |
| EASYHAPROXY_LABEL_PREFIX | (Optional) The key will search to match resources. Default: `easyhaproxy`. | | EASYHAPROXY_LABEL_PREFIX | (Optional) The key will search for matching resources. Default: `easyhaproxy`. |
| EASYHAPROXY_LETSENCRYPT_EMAIL | (Optional) The email will be used to request certificate to letsencrypt | | EASYHAPROXY_LETSENCRYPT_EMAIL | (Optional) The email will be used to request the certificate to Letsencrypt |
| EASYHAPROXY_SSL_MODE | (Optional) `STRICT` supports only the most recent TLS version; `DEFAULT` good SSL integration with recent browsers; `LOOSE` supports all old SSL protocols for old browsers (not recommended). |
| HAPROXY_USERNAME | (Optional) The HAProxy username to the statistics. Default: `admin` | | HAPROXY_USERNAME | (Optional) The HAProxy username to the statistics. Default: `admin` |
| HAPROXY_PASSWORD | The HAProxy password to the statistics. If not set disable stats. | | HAPROXY_PASSWORD | The HAProxy password to the statistics. If not set, it will disable stats. |
| HAPROXY_STATS_PORT | (Optional) The HAProxy port to the statistics. Default: `1936` | | HAPROXY_STATS_PORT | (Optional) The HAProxy port to the statistics. Default: `1936` |
| HAPROXY_CUSTOMERRORS | (Optional) If HAProxy will use custom HTML errors. true/false. Default: false | | HAPROXY_CUSTOMERRORS | (Optional) If HAProxy will use custom HTML errors. true/false. Default: false |
The environment variable `EASYHAPROXY_DISCOVER` will define where is located your containers (see below more details): The environment variable `EASYHAPROXY_DISCOVER` will define where is located your containers (see below for more details):
- docker - docker
- swarm - swarm
@ -56,15 +64,15 @@ The environment variable `EASYHAPROXY_DISCOVER` will define where is located you
## Automatic Discover Services ## Automatic Discover Services
Easy HAProxy can discover automatically the container services running in the same network of Docker or in a Docker Swarm cluster. Easy HAProxy can automatically discover the container services running in the same network of Docker or in a Docker Swarm cluster.
### EASYHAPROXY_DISCOVER: docker ### EASYHAPROXY_DISCOVER: docker
This method will use a regular docker installation to discover the containers and configure the HAProxy. This method will use a standard docker installation to discover the containers and configure the HAProxy.
The only requirement is that containers and easy-haproxy must be in the same docker network. The only requirement is that containers and easy-haproxy must be in the same docker network.
The discover will occur every minute. The discovery will occur every minute.
e.g.: e.g.:
@ -81,11 +89,11 @@ docker run --network easyhaproxy myimage
This method requires a functional Docker Swarm Cluster. The system will search for the labels in all containers on all This method requires a functional Docker Swarm Cluster. The system will search for the labels in all containers on all
swarm nodes. swarm nodes.
The discover will occur every minute. The discovery will occur every minute.
Important: easyhaproxy needs to be in the same network of the containers or otherwise will not access. Important: easyhaproxy needs to be in the same network of the containers or otherwise will not access.
### Tags to be attached in the Docker Container (Swarm or Docker) ### Docker Container (Swarm or Docker) tags:
| Tag | Description | Example | | Tag | Description | Example |
|---------------------------------------|---------------------------------------------------------------------------------------------------------|--------------| |---------------------------------------|---------------------------------------------------------------------------------------------------------|--------------|
@ -93,14 +101,16 @@ Important: easyhaproxy needs to be in the same network of the containers or othe
| easyhaproxy.[definition].mode | (Optional) Is this `http` or `tcp` mode in HAProxy. (Defaults to http) | http | | easyhaproxy.[definition].mode | (Optional) Is this `http` or `tcp` mode in HAProxy. (Defaults to http) | http |
| easyhaproxy.[definition].port | (Optional) Port HAProxy will listen for the host. (Defaults to 80) | 80 | | easyhaproxy.[definition].port | (Optional) Port HAProxy will listen for the host. (Defaults to 80) | 80 |
| easyhaproxy.[definition].localport | (Optional) Port container is listening. (Defaults to 80) | 8080 | | easyhaproxy.[definition].localport | (Optional) Port container is listening. (Defaults to 80) | 8080 |
| easyhaproxy.[definition].redirect | (Optional) JSON containing key/value pair from host/to url redirect. | {"foo.com":"https://bla.com", "bar.com":"https://bar.org"} | | easyhaproxy.[definition].redirect | (Optional) JSON containing key/value pair from host/to URL redirect. | {"foo.com":"https://bla.com", "bar.com":"https://bar.org"} |
| easyhaproxy.[definition].sslcert | (Optional) Cert PEM Base64 encoded. Do not use this if letsencrypt is enabled. | | | easyhaproxy.[definition].sslcert | (Optional) Cert PEM Base64 encoded. Do not use this if letsencrypt is enabled. | |
| easyhaproxy.[definition].health-check | (Optional) `ssl`, enable health check via SSL in `mode tcp` (Defaults to "empty") | | | easyhaproxy.[definition].ssl | (Optional) If `true` you need to provide certificate as a file. See below. Do not use with `sslcert`. | true |
| easyhaproxy.[definition].letsencrypt | (Optional) Generate certificate with letsencrypt. Do not use with sslcert | true OR yes OR false OR no | | easyhaproxy.[definition].health-check | (Optional) `ssl`, enable health check via SSL in `mode tcp` (Defaults to "empty") | ssl |
| easyhaproxy.[definition].letsencrypt | (Optional) Generate certificate with letsencrypt. Do not use with `sslcert`. | true OR yes OR false OR no |
| easyhaproxy.[definition].redirect-ssl | (Optional) Redirect all requests to https | true OR yes OR false OR no |
### Defining the labels in Docker Swarm ### Defining the labels in Docker Swarm
if you are deploying a stack in a Docker Swarm cluster set labels at the `deploy` level: if you are deploying a stack in a Docker Swarm cluster, set labels at the `deploy` level:
```yaml ```yaml
services: services:
@ -138,7 +148,7 @@ docker run \
### TLS passthrough ### TLS passthrough
Used to pass on SSL-termination to a backend. Alternatively, you can enable health-check via SSL on the backend with the optional `health-check` label: Used to pass on SSL termination to a backend. Alternatively, you can enable health-check via SSL on the backend with the optional `health-check` label:
```bash ```bash
docker run \ docker run \
@ -177,9 +187,11 @@ easymapping:
containers: containers:
- container:5000 - container:5000
letsencrypt: true letsencrypt: true
redirect-ssl: true
host2.com.br: host2.com.br:
containers: containers:
- other:3000 - other:3000
ssl: false
redirect: redirect:
www.host1.com.br: http://host1.com.br www.host1.com.br: http://host1.com.br
@ -189,6 +201,7 @@ easymapping:
host1.com.br: host1.com.br:
containers: containers:
- container:80 - container:80
redirect-ssl: false
- port: 8080 - port: 8080
hosts: hosts:
@ -231,15 +244,17 @@ Caveats:
- Your container **must** listen to the port 80. Besides no error, the certificate won't be issued if in a different port. - Your container **must** listen to the port 80. Besides no error, the certificate won't be issued if in a different port.
- The port 2080 is reserved for the certbot and should not be exposed. - The port 2080 is reserved for the certbot and should not be exposed.
- You cannot set the port 443 for the container with the Letsencrypt. EasyHAProxy will handle this automatically once the certificate is issued. - You cannot set the port 443 for the container with the Letsencrypt because EasyHAProxy will handle this automatically once the certificate is issued.
- If you don't run the EasyHAProxy with the parameter `EASYHAPROXY_LETSENCRYPT_EMAIL` no certificate will be issued. - If you don't run the EasyHAProxy with the parameter `EASYHAPROXY_LETSENCRYPT_EMAIL` no certificate will be issued.
- Be aware about the issue limits - https://letsencrypt.org/docs/rate-limits/ - Be aware of Letsencrypt issue limits - https://letsencrypt.org/docs/duplicate-certificate-limit/ and https://letsencrypt.org/docs/rate-limits/
## Exposing Ports ## Exposing Ports
- You need to expose at least the ports `80` and `443` when you run the `byjg/easy-haproxy` image. You must expose some ports on the EasyHAProxy container and in the firewall. However, you don't need to expose the other container ports because EasyHAProxy will handle that.
- If you enable the HAProxy statistics you must also expose the port defined in `HAPROXY_STATS_PORT` environment variable.
- Every port defined in `easyhaproxy.[definitions].port` also should be enabel. - The ports `80` and `443`.
- If you enable the HAProxy statistics, you must also expose the port defined in `HAPROXY_STATS_PORT` environment variable.
- Every port defined in `easyhaproxy.[definitions].port` also should be exposed.
e.g. e.g.
@ -252,11 +267,10 @@ docker run \
-d byjg/easy-haproxy -d byjg/easy-haproxy
``` ```
Also, you need to expose these ports in the firewall.
## Mapping custom .cfg files ## Mapping custom .cfg files
Map a folder containing valid HAProxy `.cfg` files to `/etc/haproxy/conf.d`. It will be concatenated to your HAProxy CFG. You can concatenate valid HAProxy `.cfg` files to the dynamically generated `haproxy.cfg` by mapping the folder `/etc/haproxy/conf.d`.
```bash ```bash
docker run \ docker run \
@ -265,11 +279,24 @@ docker run \
-d byjg/easy-haproxy -d byjg/easy-haproxy
``` ```
## Mapping SSL certificates volumes
EasyHAProxy stores the certificates inside the folder `/certs/haproxy` and `/certs/letsencrypt`.
- If you want to preserve the letsencrypt certificates between reloads, map the folder `/certs/letsencrypt` to your volume.
- If you want to provide your certificates as a file instead of a Base64 parameter, map the folder `/certs/haproxy` to your volume, and instead of use `easyhaproxy.[definition].sslcert`, use `easyhaproxy.[definition].ssl: true`
```bash
docker run \
/* other parameters */
-v /your/certs/letsencrypt:/certs/letsencrypt \
-d byjg/easy-haproxy
```
## Handling SSL ## Handling SSL
You can attach a valid SSL certificate to the request. You can attach a valid SSL certificate to the request.
1. First Create a single PEM file including CA. 1. First, Create a single PEM file including CA.
```bash ```bash
cat example.com.crt example.com.key > single.pem cat example.com.crt example.com.key > single.pem
@ -298,7 +325,7 @@ cat single.pem | base64 -w0
## Setting Custom Errors ## Setting Custom Errors
If enabled, map the volume : `/etc/haproxy/errors-custom/` to your container and put a file named `ERROR_NUMBER.http` If enabled, map the volume : `/etc/haproxy/errors-custom/` to your container and put a file named `ERROR_NUMBER.http`
where ERROR_NUMBER is the http error code (e.g. 503.http) where ERROR_NUMBER is the HTTP error code (e.g., `503.http`)
## Build ## Build
@ -307,5 +334,11 @@ where ERROR_NUMBER is the http error code (e.g. 503.http)
docker build -t byjg/easy-haproxy . docker build -t byjg/easy-haproxy .
``` ```
## Limitations
EasyHAProxy has some limitations when there is more than one easy-haproxy container running:
- Replicas can be out-of-sync for a few seconds because each replica will discover the pods separately.
- Each replica will request a Letsencrypt certificate and can fail because the letsencrypt challenge can be directed to the other replica.
---- ----
[Open source ByJG](http://opensource.byjg.com) [Open source ByJG](http://opensource.byjg.com)

View file

@ -15,12 +15,12 @@ REQUEST_CERTS=""
RENEW_CERTS="" RENEW_CERTS=""
for domain in $(cat /scripts/letsencrypt_hosts.txt); do for domain in $(cat /scripts/letsencrypt_hosts.txt); do
if [ ! -f "/etc/haproxy/certs/$domain.pem" ]; then if [ ! -f "/certs/letsencrypt/$domain.pem" ]; then
REQUEST_CERTS="$REQUES_CERTS -d $domain" REQUEST_CERTS="$REQUES_CERTS -d $domain"
continue continue
fi fi
if [[ $(find "/etc/haproxy/certs/$domain.pem" -mtime +30 -print) ]]; then if [[ $(find "/certs/letsencrypt/$domain.pem" -mtime +30 -print) ]]; then
RENEW_CERTS="$RENEW_CERTS -d $domain" RENEW_CERTS="$RENEW_CERTS -d $domain"
fi fi
done done

View file

@ -5,7 +5,7 @@ for CERTIFICATE in `find /etc/letsencrypt/live/* -type d`; do
CERTIFICATE=`basename $CERTIFICATE` CERTIFICATE=`basename $CERTIFICATE`
# Combine certificate and private key to single file # Combine certificate and private key to single file
cat /etc/letsencrypt/live/$CERTIFICATE/fullchain.pem /etc/letsencrypt/live/$CERTIFICATE/privkey.pem > /etc/haproxy/certs/$CERTIFICATE.pem cat /etc/letsencrypt/live/$CERTIFICATE/fullchain.pem /etc/letsencrypt/live/$CERTIFICATE/privkey.pem > /certs/letsencrypt/$CERTIFICATE.pem
done done
# It will be checked on haproxy-reload.sh # It will be checked on haproxy-reload.sh

View file

@ -16,7 +16,7 @@ else
touch ${CONTROL_FILE} touch ${CONTROL_FILE}
if [[ "$EASYHAPROXY_DISCOVER" == "docker" ]]; then if [[ "$EASYHAPROXY_DISCOVER" == "docker" ]]; then
CONTAINERS=$(docker ps -q) CONTAINERS=$(docker ps -q | sort | uniq)
LABEL_PATH=".Config.Labels" LABEL_PATH=".Config.Labels"
for container in ${CONTAINERS}; do for container in ${CONTAINERS}; do

View file

@ -5,7 +5,8 @@ with open("/tmp/.docker_data", 'r') as content_file:
line_list = content_file.readlines() line_list = content_file.readlines()
result = { result = {
"customerrors": True if os.getenv("HAPROXY_CUSTOMERRORS") == "true" else False "customerrors": True if os.getenv("HAPROXY_CUSTOMERRORS") == "true" else False,
"ssl_mode": os.getenv("EASYHAPROXY_SSL_MODE", "default")
} }
if os.getenv("HAPROXY_PASSWORD"): if os.getenv("HAPROXY_PASSWORD"):
@ -16,6 +17,11 @@ if os.getenv("HAPROXY_PASSWORD"):
} }
result["lookup_label"] = os.getenv("EASYHAPROXY_LABEL_PREFIX") if os.getenv("EASYHAPROXY_LABEL_PREFIX") else "easyhaproxy" result["lookup_label"] = os.getenv("EASYHAPROXY_LABEL_PREFIX") if os.getenv("EASYHAPROXY_LABEL_PREFIX") else "easyhaproxy"
if (os.getenv("EASYHAPROXY_LETSENCRYPT_EMAIL")):
result["letsencrypt"] = {
"email": os.getenv("EASYHAPROXY_LETSENCRYPT_EMAIL")
}
cfg = HaproxyConfigGenerator(result) cfg = HaproxyConfigGenerator(result)
print(cfg.generate(line_list)) print(cfg.generate(line_list))

View file

@ -46,15 +46,18 @@ class DockerLabelHandler:
class HaproxyConfigGenerator: class HaproxyConfigGenerator:
def __init__(self, mapping, ssl_cert_folder="/etc/haproxy/certs/discover"): def __init__(self, mapping, ssl_cert_folder="/certs"):
self.mapping = mapping self.mapping = mapping
self.mapping.setdefault("ssl_mode", 'default')
self.mapping.setdefault("letsencrypt", {"email": ""})
self.mapping["ssl_mode"] = self.mapping["ssl_mode"].lower()
self.label = DockerLabelHandler(mapping['lookup_label'] if 'lookup_label' in mapping else "easyhaproxy") self.label = DockerLabelHandler(mapping['lookup_label'] if 'lookup_label' in mapping else "easyhaproxy")
self.ssl_cert_folder = ssl_cert_folder self.ssl_cert_haproxy = ssl_cert_folder + "/haproxy"
self.ssl_cert_letsecncrypt = ssl_cert_folder + "/letsencrypt"
self.letsencrypt_hosts = [] self.letsencrypt_hosts = []
self.letsencrypt_email = os.getenv("EASYHAPROXY_LETSENCRYPT_EMAIL", "") os.makedirs(self.ssl_cert_haproxy, exist_ok=True)
os.makedirs(self.ssl_cert_folder, exist_ok=True) os.makedirs(self.ssl_cert_letsecncrypt, exist_ok=True)
def generate(self, line_list = []): def generate(self, line_list = []):
self.mapping.setdefault("easymapping", []) self.mapping.setdefault("easymapping", [])
@ -113,7 +116,7 @@ class HaproxyConfigGenerator:
letsencrypt = self.label.get_bool( letsencrypt = self.label.get_bool(
self.label.create([definition, "letsencrypt"]), self.label.create([definition, "letsencrypt"]),
False False
) and self.letsencrypt_email != "" ) and self.mapping["letsencrypt"]["email"] != ""
if port not in easymapping: if port not in easymapping:
easymapping[port] = { easymapping[port] = {
@ -142,6 +145,10 @@ class HaproxyConfigGenerator:
easymapping[port]["hosts"][hostname].setdefault("letsencrypt", False) easymapping[port]["hosts"][hostname].setdefault("letsencrypt", False)
easymapping[port]["hosts"][hostname]["containers"] += ["{}:{}".format(container, ct_port)] easymapping[port]["hosts"][hostname]["containers"] += ["{}:{}".format(container, ct_port)]
easymapping[port]["hosts"][hostname]["letsencrypt"] = letsencrypt easymapping[port]["hosts"][hostname]["letsencrypt"] = letsencrypt
easymapping[port]["hosts"][hostname]["redirect_ssl"] = self.label.get_bool(
self.label.create([definition, "redirect_ssl"])
)
easymapping[port]["redirect"] = self.label.get_json( easymapping[port]["redirect"] = self.label.get_json(
self.label.create([definition, "redirect"]) self.label.create([definition, "redirect"])
) )
@ -157,7 +164,8 @@ class HaproxyConfigGenerator:
} }
easymapping["443"]["hosts"][hostname] = dict(easymapping[port]["hosts"][hostname]) easymapping["443"]["hosts"][hostname] = dict(easymapping[port]["hosts"][hostname])
easymapping["443"]["hosts"][hostname]["letsencrypt"] = False easymapping["443"]["hosts"][hostname]["letsencrypt"] = False
easymapping["443"]["ssl_cert"] = "/etc/haproxy/certs" easymapping["443"]["hosts"][hostname]["redirect_ssl"] = False
easymapping["443"]["ssl_cert"] = self.ssl_cert_letsecncrypt
self.letsencrypt_hosts.append(hostname) if hostname not in self.letsencrypt_hosts else self.letsencrypt_hosts self.letsencrypt_hosts.append(hostname) if hostname not in self.letsencrypt_hosts else self.letsencrypt_hosts
@ -165,12 +173,14 @@ class HaproxyConfigGenerator:
ssl_label = self.label.create([definition, "sslcert"]) ssl_label = self.label.create([definition, "sslcert"])
if self.label.has_label(ssl_label): if self.label.has_label(ssl_label):
filename = "{}/{}.pem".format( filename = "{}/{}.pem".format(
self.ssl_cert_folder, d[host_label] self.ssl_cert_haproxy, d[host_label]
) )
easymapping[port]["ssl_cert"] = filename easymapping[port]["ssl_cert"] = filename
with open(filename, 'wb') as file: with open(filename, 'wb') as file:
file.write( file.write(
base64.b64decode(d[ssl_label]) base64.b64decode(d[ssl_label])
) )
if self.label.get_bool(self.label.create([definition, "ssl"])):
easymapping[port]["ssl_cert"] = self.ssl_cert_haproxy
return easymapping.values() return easymapping.values()

View file

@ -1,8 +1,19 @@
# To test: # To test:
# curl -k -H "Host: host1.local" https://127.0.0.1/ # curl -k -H "Host: host1.local" https://127.0.0.1/
# curl -k -H "Host: host2.local" https://127.0.0.1/
#
# curl -I -H Host:host1.local http://127.0.0.1
# HTTP/1.1 301 Moved Permanently
# content-length: 0
# location: https://host1.local/
# #
# or add to /etc/hosts # curl -I -H Host:host2.local http://127.0.0.1
# 127.0.0.1 host1.local # HTTP/1.1 200 OK
# accept-ranges: bytes
# content-length: 3276
# content-type: text/html; charset=utf-8
# last-modified: Tue, 16 Aug 2022 15:48:05 GMT
# date: Tue, 16 Aug 2022 16:36:35 GMT
# #
# Test SSL: # Test SSL:
# openssl s_client -showcerts -connect 127.0.0.1:443 --servername host1.local # openssl s_client -showcerts -connect 127.0.0.1:443 --servername host1.local
@ -16,6 +27,7 @@ services:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
environment: environment:
EASYHAPROXY_DISCOVER: docker EASYHAPROXY_DISCOVER: docker
EASYHAPROXY_SSL_MODE: "loose"
HAPROXY_CUSTOMERRORS: "true" HAPROXY_CUSTOMERRORS: "true"
HAPROXY_USERNAME: admin HAPROXY_USERNAME: admin
HAPROXY_PASSWORD: password HAPROXY_PASSWORD: password
@ -29,7 +41,7 @@ services:
container: container:
image: byjg/static-httpserver image: byjg/static-httpserver
labels: labels:
easyhaproxy.http.redirect: host1.local--https://host1.local easyhaproxy.http.redirect_ssl: true
easyhaproxy.http.host: host1.local easyhaproxy.http.host: host1.local
easyhaproxy.http.port: 80 easyhaproxy.http.port: 80
@ -41,8 +53,8 @@ services:
container2: container2:
image: byjg/static-httpserver image: byjg/static-httpserver
labels: labels:
easyhaproxy.http.redirect: host2.local--https://host2.local
easyhaproxy.http.host: host2.local easyhaproxy.http.host: host2.local
easyhaproxy.http.localport: 8080
easyhaproxy.http.port: 80 easyhaproxy.http.port: 80
easyhaproxy.https.port: 443 easyhaproxy.https.port: 443

View file

@ -12,7 +12,7 @@ easymapping:
www.host1.local: https://host1.local www.host1.local: https://host1.local
- port: 443 - port: 443
ssl_cert: /etc/certs/host1.local.pem ssl_cert: /certs/haproxy/host1.local.pem
hosts: hosts:
host1.local: host1.local:
containers: containers:

View file

@ -8,7 +8,7 @@ services:
image: byjg/easy-haproxy image: byjg/easy-haproxy
volumes: volumes:
- ./config.yml:/etc/haproxy/easyconfig.yml - ./config.yml:/etc/haproxy/easyconfig.yml
- ./host1.local.pem:/etc/certs/host1.local.pem - ./certs:/certs
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
environment: environment:
EASYHAPROXY_DISCOVER: static EASYHAPROXY_DISCOVER: static

View file

@ -1,5 +1,5 @@
{% if "ssl_cert" in o %} {% if "ssl_cert" in o %}
bind *:{{ o["port"] }} ssl crt /etc/haproxy/certs/discover/ alpn http/1.1 crt /etc/haproxy/certs/ alpn http/1.1 bind *:{{ o["port"] }} ssl crt /certs/letsencrypt/ alpn http/1.1 crt /certs/haproxy/ alpn http/1.1
{% elif "h2" in o and o["h2"] %} {% elif "h2" in o and o["h2"] %}
bind *:{{ o["port"] }} proto h2 bind *:{{ o["port"] }} proto h2
option http-use-htx option http-use-htx

View file

@ -13,5 +13,9 @@
acl is_letsencrypt_{{ host }} path_beg /.well-known/acme-challenge/ acl is_letsencrypt_{{ host }} path_beg /.well-known/acme-challenge/
use_backend letsencrypt_backend if is_letsencrypt_{{ host }} is_rule_{{ host }}_1 OR is_letsencrypt_{{ host }} is_rule_{{ host }}_2 use_backend letsencrypt_backend if is_letsencrypt_{{ host }} is_rule_{{ host }}_1 OR is_letsencrypt_{{ host }} is_rule_{{ host }}_2
{% endif %} {% endif %}
{% if o["hosts"][k]["redirect_ssl"] %}
http-request redirect scheme https code 301 if !is_letsencrypt_{{ host }} is_rule_{{ host }}_1 OR !is_letsencrypt_{{ host }} is_rule_{{ host }}_2
{% else %}
use_backend srv_{{ host }} if is_rule_{{ host }}_1 OR is_rule_{{ host }}_2 use_backend srv_{{ host }} if is_rule_{{ host }}_1 OR is_rule_{{ host }}_2
{% endif %}
{% endfor %} {% endfor %}

View file

@ -1,18 +1,14 @@
global global
log stdout format raw local0 info log stdout format raw local0 info
maxconn 2000 maxconn 2000
tune.ssl.default-dh-param 2048 {% if data["ssl_mode"] == "strict" %}
{% include "ssl_strict.j2" %}
{% elif data["ssl_mode"] == "loose" %}
{% include "ssl_loose.j2" %}
{% else %}
{% include "ssl_default.j2" %}
{% endif %}
# intermediate configuration
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options prefer-client-ciphers no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
ssl-default-server-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
ssl-dh-param-file /etc/haproxy/dhparam
defaults defaults
log global log global

12
templates/ssl_default.j2 Normal file
View file

@ -0,0 +1,12 @@
tune.ssl.default-dh-param 2048
# intermediate configuration
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options prefer-client-ciphers no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
ssl-default-server-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
ssl-dh-param-file /etc/haproxy/dhparam

10
templates/ssl_loose.j2 Normal file
View file

@ -0,0 +1,10 @@
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options no-sslv3 no-tls-tickets
ssl-default-server-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA
ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-server-options no-sslv3 no-tls-tickets
ssl-dh-param-file /etc/haproxy/dhparam-1024

7
templates/ssl_strict.j2 Normal file
View file

@ -0,0 +1,7 @@
# modern configuration
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options prefer-client-ciphers no-sslv3 no-tlsv10 no-tlsv11 no-tlsv12 no-tls-tickets
ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tlsv12 no-tls-tickets

View file

@ -0,0 +1,97 @@
global
log stdout format raw local0 info
maxconn 2000
tune.ssl.default-dh-param 2048
# intermediate configuration
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options prefer-client-ciphers no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
ssl-default-server-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
ssl-dh-param-file /etc/haproxy/dhparam
defaults
log global
timeout connect 3s
timeout client 10s
timeout server 10m
errorfile 400 /etc/haproxy/errors-custom/400.http
errorfile 403 /etc/haproxy/errors-custom/403.http
errorfile 408 /etc/haproxy/errors-custom/408.http
errorfile 500 /etc/haproxy/errors-custom/500.http
errorfile 502 /etc/haproxy/errors-custom/502.http
errorfile 503 /etc/haproxy/errors-custom/503.http
errorfile 504 /etc/haproxy/errors-custom/504.http
frontend stats
bind *:1936
mode http
stats enable
stats hide-version
stats realm Haproxy\ Statistics
stats uri /
stats auth admin:password
# acl is_proxystats hdr(host) -i some.host.com
# default_backend srv_stats
# use_backend srv_stats if is_proxystats
default_backend srv_stats
backend srv_stats
mode http
server Local 127.0.0.1:1936
frontend http_in_80
bind *:80
mode http
acl is_rule_test_example_org_80_1 hdr(host) -i test.example.org
acl is_rule_test_example_org_80_2 hdr(host) -i test.example.org:80
acl is_letsencrypt_test_example_org_80 path_beg /.well-known/acme-challenge/
use_backend letsencrypt_backend if is_letsencrypt_test_example_org_80 is_rule_test_example_org_80_1 OR is_letsencrypt_test_example_org_80 is_rule_test_example_org_80_2
http-request redirect scheme https code 301 if !is_letsencrypt_test_example_org_80 is_rule_test_example_org_80_1 OR !is_letsencrypt_test_example_org_80 is_rule_test_example_org_80_2
acl is_rule_test2_example_org_80_1 hdr(host) -i test2.example.org
acl is_rule_test2_example_org_80_2 hdr(host) -i test2.example.org:80
use_backend srv_test2_example_org_80 if is_rule_test2_example_org_80_1 OR is_rule_test2_example_org_80_2
backend srv_test_example_org_80
balance roundrobin
mode http
option forwardfor
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
server srv-0 f5c645a0dfc6:80 check weight 1
server srv-1 b63438410b6a:80 check weight 1
backend srv_test2_example_org_80
balance roundrobin
mode http
option forwardfor
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
server srv-0 83d57d592e26:8080 check weight 1
frontend http_in_443
bind *:443 ssl crt /certs/letsencrypt/ alpn http/1.1 crt /certs/haproxy/ alpn http/1.1
mode http
acl is_rule_test_example_org_443_1 hdr(host) -i test.example.org
acl is_rule_test_example_org_443_2 hdr(host) -i test.example.org:443
use_backend srv_test_example_org_443 if is_rule_test_example_org_443_1 OR is_rule_test_example_org_443_2
backend srv_test_example_org_443
balance roundrobin
mode http
option forwardfor
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
server srv-0 f5c645a0dfc6:80 check weight 1 verify none
server srv-1 b63438410b6a:80 check weight 1 verify none
backend letsencrypt_backend
mode http
server certbot 127.0.0.1:2080

View file

@ -0,0 +1,81 @@
global
log stdout format raw local0 info
maxconn 2000
tune.ssl.default-dh-param 2048
# intermediate configuration
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options prefer-client-ciphers no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
ssl-default-server-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
ssl-dh-param-file /etc/haproxy/dhparam
defaults
log global
timeout connect 3s
timeout client 10s
timeout server 10m
frontend http_in_80
bind *:80
mode http
acl is_rule_host2_local_80_1 hdr(host) -i host2.local
acl is_rule_host2_local_80_2 hdr(host) -i host2.local:80
use_backend srv_host2_local_80 if is_rule_host2_local_80_1 OR is_rule_host2_local_80_2
acl is_rule_host1_local_80_1 hdr(host) -i host1.local
acl is_rule_host1_local_80_2 hdr(host) -i host1.local:80
http-request redirect scheme https code 301 if !is_letsencrypt_host1_local_80 is_rule_host1_local_80_1 OR !is_letsencrypt_host1_local_80 is_rule_host1_local_80_2
backend srv_host2_local_80
balance roundrobin
mode http
option forwardfor
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
server srv-0 b18a88da403b:8080 check weight 1
backend srv_host1_local_80
balance roundrobin
mode http
option forwardfor
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
server srv-0 78a90f5c2d8a:80 check weight 1
frontend http_in_443
bind *:443 ssl crt /certs/letsencrypt/ alpn http/1.1 crt /certs/haproxy/ alpn http/1.1
mode http
acl is_rule_host2_local_443_1 hdr(host) -i host2.local
acl is_rule_host2_local_443_2 hdr(host) -i host2.local:443
use_backend srv_host2_local_443 if is_rule_host2_local_443_1 OR is_rule_host2_local_443_2
acl is_rule_host1_local_443_1 hdr(host) -i host1.local
acl is_rule_host1_local_443_2 hdr(host) -i host1.local:443
use_backend srv_host1_local_443 if is_rule_host1_local_443_1 OR is_rule_host1_local_443_2
backend srv_host2_local_443
balance roundrobin
mode http
option forwardfor
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
server srv-0 b18a88da403b:8080 check weight 1
backend srv_host1_local_443
balance roundrobin
mode http
option forwardfor
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
server srv-0 78a90f5c2d8a:8080 check weight 1
backend letsencrypt_backend
mode http
server certbot 127.0.0.1:2080

View file

@ -66,7 +66,7 @@ backend srv_node-exporter_quantum_example_org_31337
server srv-0 my-stack_node-exporter:9100 check weight 1 server srv-0 my-stack_node-exporter:9100 check weight 1
frontend http_in_443 frontend http_in_443
bind *:443 ssl crt /etc/haproxy/certs/discover/ alpn http/1.1 crt /etc/haproxy/certs/ alpn http/1.1 bind *:443 ssl crt /certs/letsencrypt/ alpn http/1.1 crt /certs/haproxy/ alpn http/1.1
mode http mode http
redirect prefix https://www.somehost.com.br code 301 if { hdr(host) -i somehost.com.br } redirect prefix https://www.somehost.com.br code 301 if { hdr(host) -i somehost.com.br }
redirect prefix https://www.somehost.com.br code 301 if { hdr(host) -i somehost.com } redirect prefix https://www.somehost.com.br code 301 if { hdr(host) -i somehost.com }

View file

@ -0,0 +1,25 @@
global
log stdout format raw local0 info
maxconn 2000
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options no-sslv3 no-tls-tickets
ssl-default-server-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA
ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-server-options no-sslv3 no-tls-tickets
ssl-dh-param-file /etc/haproxy/dhparam-1024
defaults
log global
timeout connect 3s
timeout client 10s
timeout server 10m
backend letsencrypt_backend
mode http
server certbot 127.0.0.1:2080

View file

@ -0,0 +1,22 @@
global
log stdout format raw local0 info
maxconn 2000
# modern configuration
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options prefer-client-ciphers no-sslv3 no-tlsv10 no-tlsv11 no-tlsv12 no-tls-tickets
ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tlsv12 no-tls-tickets
defaults
log global
timeout connect 3s
timeout client 10s
timeout server 10m
backend letsencrypt_backend
mode http
server certbot 127.0.0.1:2080

View file

@ -76,7 +76,7 @@ backend srv_host2_com_br_80
server srv-0 other:3000 check weight 1 server srv-0 other:3000 check weight 1
frontend http_in_443 frontend http_in_443
bind *:443 ssl crt /etc/haproxy/certs/discover/ alpn http/1.1 crt /etc/haproxy/certs/ alpn http/1.1 bind *:443 ssl crt /certs/letsencrypt/ alpn http/1.1 crt /certs/haproxy/ alpn http/1.1
mode http mode http
acl is_rule_host1_com_br_443_1 hdr(host) -i host1.com.br acl is_rule_host1_com_br_443_1 hdr(host) -i host1.com.br

4
tests/fixtures/services-letsencrypt vendored Normal file
View file

@ -0,0 +1,4 @@
f5c645a0dfc6={"com.docker.compose.config-hash":"b95ebc27d0e61caa418cdfa632e05a656da9bbc3ea0d4603651971015f10a1f0","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:bea3509d6fdc8d7f9ec95563a5a226dc977ee74fb3e980e0de70e892c2d38dde","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"nginx","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"test.example.org","easyhaproxy.http.letsencrypt":"true","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"80","easyhaproxy.http.redirect":"{\"google.helloworld.com\": \"www.google.com\"}","easyhaproxy.http.redirect_ssl":"true"}
bbd4d1854155={"com.docker.compose.config-hash":"3dc790bf2bea944359c75a40c45655bd868f1d85beb599d1ca797e8ea2c95ee4","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:0fd95b1512c207048ab3fcc74032354f38143fbb8235ac2a47da903c98a58205","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"haproxy","com.docker.compose.version":"2.8.0"}
b63438410b6a={"com.docker.compose.config-hash":"b95ebc27d0e61caa418cdfa632e05a656da9bbc3ea0d4603651971015f10a1f0","com.docker.compose.container-number":"2","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:bea3509d6fdc8d7f9ec95563a5a226dc977ee74fb3e980e0de70e892c2d38dde","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"nginx","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"test.example.org","easyhaproxy.http.letsencrypt":"true","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"80","easyhaproxy.http.redirect":"{\"google.helloworld.com\": \"www.google.com\"}","easyhaproxy.http.redirect_ssl":"true"}
83d57d592e26={"com.docker.compose.config-hash":"8c5871144f1e8a3aeca037207c02f011ab2c6e6c311a3773602b63541762dab5","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:c4232396c715f3d568816c666e6d9b4a68ef6c36f6243b4007c4ee1d8335fd65","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"static","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"test2.example.org","easyhaproxy.http.localport":"8080","easyhaproxy.http.port":"80","io.buildah.version":"1.21.0"}

3
tests/fixtures/services-redirect-ssl vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -19,7 +19,7 @@ easymapping:
www.host1.com.br: http://host1.com.br www.host1.com.br: http://host1.com.br
- port: 443 - port: 443
ssl_cert: /etc/haproxy/certs/mycert.pem ssl_cert: /certs/haproxy/mycert.pem
hosts: hosts:
host1.com.br: host1.com.br:
containers: containers:

View file

@ -4,7 +4,7 @@ import os
import yaml import yaml
CERTS_FOLDER="/tmp/certs" CERTS_FOLDER="/tmp/certs"
CERT_FILE="/tmp/certs/www.somehost.com.br.pem" CERT_FILE="/tmp/certs/haproxy/www.somehost.com.br.pem"
LETSENCRYPT_EMAIL="some@email.com" LETSENCRYPT_EMAIL="some@email.com"
def load_fixture(file): def load_fixture(file):
@ -35,14 +35,16 @@ def test_parser_finds_services():
line_list = load_fixture("services") line_list = load_fixture("services")
result = { result = {
"customerrors": False "customerrors": False,
"letsencrypt": {
"email": LETSENCRYPT_EMAIL
}
} }
if os.path.exists(CERT_FILE): if os.path.exists(CERT_FILE):
os.remove(CERT_FILE) os.remove(CERT_FILE)
cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER)
cfg.letsencrypt_email = LETSENCRYPT_EMAIL
haproxy_config = cfg.generate(line_list) haproxy_config = cfg.generate(line_list)
assert len(haproxy_config) > 0 assert len(haproxy_config) > 0
@ -60,14 +62,16 @@ def test_parser_finds_services_changed_label():
result = { result = {
"customerrors": False, "customerrors": False,
"lookup_label": "haproxy" "lookup_label": "haproxy",
"letsencrypt": {
"email": LETSENCRYPT_EMAIL
}
} }
if os.path.exists(CERT_FILE): if os.path.exists(CERT_FILE):
os.remove(CERT_FILE) os.remove(CERT_FILE)
cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER)
cfg.letsencrypt_email = LETSENCRYPT_EMAIL
haproxy_config = cfg.generate(line_list) haproxy_config = cfg.generate(line_list)
assert len(haproxy_config) > 0 assert len(haproxy_config) > 0
@ -84,14 +88,16 @@ def test_parser_finds_services_raw():
line_list = load_fixture("services") line_list = load_fixture("services")
result = { result = {
"customerrors": False "customerrors": False,
"letsencrypt": {
"email": LETSENCRYPT_EMAIL
}
} }
if os.path.exists(CERT_FILE): if os.path.exists(CERT_FILE):
os.remove(CERT_FILE) os.remove(CERT_FILE)
cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER)
cfg.letsencrypt_email = LETSENCRYPT_EMAIL
parsed_object = [ parsed_object = [
{ {
@ -103,7 +109,8 @@ def test_parser_finds_services_raw():
"containers": [ "containers": [
"my-stack_agent:9001" "my-stack_agent:9001"
], ],
"letsencrypt": False "letsencrypt": False,
"redirect_ssl": False
} }
}, },
"redirect":{ "redirect":{
@ -119,18 +126,20 @@ def test_parser_finds_services_raw():
"containers": [ "containers": [
"my-stack_cadvisor:8080" "my-stack_cadvisor:8080"
], ],
"letsencrypt": False "letsencrypt": False,
"redirect_ssl": False
}, },
"node-exporter.quantum.example.org":{ "node-exporter.quantum.example.org":{
"containers": [ "containers": [
"my-stack_node-exporter:9100" "my-stack_node-exporter:9100"
], ],
"letsencrypt": True "letsencrypt": True,
"redirect_ssl": False
} }
}, },
"redirect":{ "redirect":{
} },
}, },
{ {
"mode":"http", "mode":"http",
@ -141,13 +150,15 @@ def test_parser_finds_services_raw():
"containers": [ "containers": [
"my-stack_node-exporter:9100" "my-stack_node-exporter:9100"
], ],
"letsencrypt": False "letsencrypt": False,
"redirect_ssl": False
}, },
"www.somehost.com.br":{ "www.somehost.com.br":{
"containers": [ "containers": [
"some-service:80" "some-service:80"
], ],
"letsencrypt": False "letsencrypt": False,
"redirect_ssl": False
} }
}, },
"redirect":{ "redirect":{
@ -168,7 +179,8 @@ def test_parser_finds_services_raw():
"containers": [ "containers": [
"some-service:80" "some-service:80"
], ],
"letsencrypt": False "letsencrypt": False,
"redirect_ssl": False
} }
}, },
"redirect":{ "redirect":{
@ -177,7 +189,7 @@ def test_parser_finds_services_raw():
"www.somehost.com":"https://www.somehost.com.br", "www.somehost.com":"https://www.somehost.com.br",
"byjg.ca":"https://www.somehost.com.br", "byjg.ca":"https://www.somehost.com.br",
"www.byjg.ca":"https://www.somehost.com.br" "www.byjg.ca":"https://www.somehost.com.br"
} },
} }
] ]
@ -235,7 +247,7 @@ def test_parser_static_raw():
}, },
{ {
"port": 443, "port": 443,
"ssl_cert": "/etc/haproxy/certs/mycert.pem", "ssl_cert": "/certs/haproxy/mycert.pem",
"hosts": { "hosts": {
"host1.com.br": { "host1.com.br": {
"containers": [ "containers": [
@ -317,6 +329,81 @@ def test_parser_multiple_hosts():
assert [] == cfg.letsencrypt_hosts assert [] == cfg.letsencrypt_hosts
def test_parser_redirect_ssl():
line_list = load_fixture("services-redirect-ssl")
result = {
"customerrors": False
}
cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER)
haproxy_config = cfg.generate(line_list)
assert len(haproxy_config) > 0
path = os.path.dirname(os.path.realpath(__file__))
with open(path + "/expected/services-redirect-ssl.txt", 'r') as expected_file:
assert expected_file.read() == haproxy_config
assert [] == cfg.letsencrypt_hosts
def test_parser_ssl_strict():
line_list = load_fixture("no-services")
result = {
"customerrors": False,
"ssl_mode": "strict"
}
cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER)
haproxy_config = cfg.generate(line_list)
assert len(haproxy_config) > 0
path = os.path.dirname(os.path.realpath(__file__))
with open(path + "/expected/ssl-strict.txt", 'r') as expected_file:
assert expected_file.read() == haproxy_config
assert [] == cfg.letsencrypt_hosts
def test_parser_ssl_loose():
line_list = load_fixture("no-services")
result = {
"customerrors": False,
"ssl_mode": "loose"
}
cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER)
haproxy_config = cfg.generate(line_list)
assert len(haproxy_config) > 0
path = os.path.dirname(os.path.realpath(__file__))
with open(path + "/expected/ssl-loose.txt", 'r') as expected_file:
assert expected_file.read() == haproxy_config
assert [] == cfg.letsencrypt_hosts
def test_parser_ssl_letsencrypt():
line_list = load_fixture("services-letsencrypt")
result = {
"customerrors": True,
"stats": {
"username": "admin",
"password": "password"
},
"letsencrypt": {
"email": LETSENCRYPT_EMAIL
}
}
cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER)
haproxy_config = cfg.generate(line_list)
assert len(haproxy_config) > 0
path = os.path.dirname(os.path.realpath(__file__))
with open(path + "/expected/services-letsencrypt.txt", 'r') as expected_file:
assert expected_file.read() == haproxy_config
assert ["test.example.org"] == cfg.letsencrypt_hosts
#test_parser_finds_services_raw() #test_parser_finds_services_raw()
#test_parser_tcp() #test_parser_tcp()
#test_parser_multiple_hosts() #test_parser_multiple_hosts()
#test_parser_ssl_letsencrypt()