diff --git a/Dockerfile b/Dockerfile index 77c80c5..87f765f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,20 +2,17 @@ FROM alpine:3.16 WORKDIR /scripts -RUN apk add --no-cache haproxy bash python3 py3-pip py-yaml supervisor docker \ - && ln -s /usr/bin/python3 /usr/bin/python - COPY requirements.txt /scripts - -RUN pip3 install --upgrade pip \ - && pip install -r requirements.txt - COPY templates /scripts/templates/ COPY easymapping /scripts/easymapping/ COPY tests/ /scripts/tests/ - COPY assets / -RUN pytest -s tests/ +RUN apk add --no-cache haproxy bash python3 py3-pip py-yaml supervisor docker certbot openssl \ + && ln -s /usr/bin/python3 /usr/bin/python \ + && pip3 install --upgrade pip \ + && pip install -r requirements.txt \ + && pytest -s tests/ \ + && openssl dhparam -out /etc/haproxy/dhparam 2048 CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisord.conf" ] diff --git a/README.md b/README.md index 668acd2..c1156b8 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Service discovery for HAProxy. This Docker image will create dynamically the `haproxy.cfg` based on the labels defined in docker containers or from -a simple Yaml instead docker +a simple Yaml. ## Features @@ -38,14 +38,15 @@ The mapping to `/var/run/docker.sock` is necessary to discover the docker contai The environment variables will setup the HAProxy. -| Environment Variable | Description | -|--------------------------|-------------------------------------------------------------------------------| -| 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`. | -| 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_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 | +| Environment Variable | Description | +|-------------------------------|-------------------------------------------------------------------------------| +| 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_LETSENCRYPT_EMAIL | (Optional) The email will be used to request certificate to letsencrypt | +| 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_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 | The environment variable `EASYHAPROXY_DISCOVER` will define where is located your containers (see below more details): @@ -88,13 +89,14 @@ Important: easyhaproxy needs to be in the same network of the containers or othe | Tag | Description | Example | |---------------------------------------|---------------------------------------------------------------------------------------------------------|--------------| +| easyhaproxy.[definition].host | Host(s) HAProxy is listening. More than one host use comma as delimiter | somehost.com OR host1.com,host2.com | | easyhaproxy.[definition].mode | (Optional) Is this `http` or `tcp` mode in HAProxy. (Defaults to http) | http | -| easyhaproxy.[definition].port | (Optional) What is the port that the HAProxy will listen to. (Defaults to 80) | 80 | -| easyhaproxy.[definition].localport. | (Optional) What is the port that the container is listening. (Defaults to 80) | 8080 | -| easyhaproxy.[definition].host | What is the host that the HAProxy will listen to. | somehost.com | -| easyhaproxy.[definition].redirect | (Optional) Host redirects from connections in the port defined above. | foo.com--https://bla.com,bar.com--https://bar.org | -| easyhaproxy.[definition].sslcert | (Optional) Cert PEM Base64 encoded. | | +| 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].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].health-check | (Optional) `ssl`, enable health check via SSL in `mode tcp` (Defaults to "empty") | | +| easyhaproxy.[definition].letsencrypt | (Optional) Generate certificate with letsencrypt. Do not use with sslcert | true OR yes OR false OR no | ### Defining the labels in Docker Swarm @@ -151,7 +153,7 @@ docker run \ ```bash docker run \ - -l easyhaproxy.[definition].redirect=www.byjg.com.br--http://byjg.com.br,byjg.com--http://byjg.com.br + -l easyhaproxy.[definition].redirect='{"www.byjg.com.br":"http://byjg.com.br","byjg.com":"http://byjg.com.br"}' ``` ## EASYHAPROXY_DISCOVER: static @@ -171,19 +173,28 @@ customerrors: true # Optional (default false) easymapping: - port: 80 hosts: - host1.com.br: container:5000 - host2.com.br: other:3000 + host1.com.br: + containers: + - container:5000 + letsencrypt: true + host2.com.br: + containers: + - other:3000 redirect: www.host1.com.br: http://host1.com.br - port: 443 ssl_cert: /path/to/ssl/certificate hosts: - host1.com.br: container:80 + host1.com.br: + containers: + - container:80 - port: 8080 hosts: - host3.com.br: domain:8181 + host3.com.br: + containers: + - domain:8181 ``` Running: @@ -192,6 +203,57 @@ Running: docker run -v /my/config.yml:/etc/haproxy/easyconfig.yml .... byjg/easyhaproxy ``` +## Letsencrypt + +This HAProxy can issue a letsencrypt certificate. The command is as below: + +Run the EasyHAProxy: + +```bash +docker run \ + -e EASYHAPROXY_LETSENCRYPT_EMAIL=john@doe.com + .... \ + byjg/easy-haproxy +``` + +Run your container: +```bash +docker run \ + -l easyhaproxy.express.port=80 \ + -l easyhaproxy.express.localport=3000 \ + -l easyhaproxy.express.host=example.org \ + -l easyhaproxy.express.letsencrypt=true \ + .... \ + some/myimage +``` + +Caveats: + +- 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. +- You cannot set the port 443 for the container with the Letsencrypt. 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. +- Be aware about the issue limits - https://letsencrypt.org/docs/rate-limits/ + +## Exposing Ports + +- You need to expose at least the ports `80` and `443` when you run the `byjg/easy-haproxy` image. +- 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. + +e.g. + +```bash +docker run \ + /* other parameters */ + -p 80:80 \ + -p 443:443 \ + -p 1936:1936 \ + -d byjg/easy-haproxy +``` + +Also, you need to expose these ports in the firewall. + ## 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. diff --git a/assets/etc/crontabs/root b/assets/etc/crontabs/root index cabb0aa..fb82802 100644 --- a/assets/etc/crontabs/root +++ b/assets/etc/crontabs/root @@ -5,5 +5,5 @@ 0 2 * * * run-parts /etc/periodic/daily 0 3 * * 6 run-parts /etc/periodic/weekly 0 5 1 * * run-parts /etc/periodic/monthly -* * * * * /scripts/haproxy-reload.sh +* * * * * /scripts/haproxy-reload.sh > /proc/$(cat /var/run/supervisord.pid)/fd/1 2>&1 diff --git a/assets/etc/haproxy/certs/.place_holder_cert.pem b/assets/etc/haproxy/certs/.place_holder_cert.pem new file mode 100644 index 0000000..49558f4 --- /dev/null +++ b/assets/etc/haproxy/certs/.place_holder_cert.pem @@ -0,0 +1,51 @@ +-----BEGIN CERTIFICATE----- +MIIDyTCCArGgAwIBAgIUVpIokbXupa29drpQBmRlKTViM+UwDQYJKoZIhvcNAQEL +BQAwdDELMAkGA1UEBhMCQVUxFTATBgNVBAgMDFBsYWNlIEhvbGRlcjEVMBMGA1UE +BwwMUGxhY2UgSG9sZGVyMSEwHwYDVQQKDBhQbGFjZSBIb2xkZXIgQ2VydGlmaWNh +dGUxFDASBgNVBAMMC2V4YW1wbGUub3JnMB4XDTIyMDgxNTA0NTEwMFoXDTMyMDgx +MjA0NTEwMFowdDELMAkGA1UEBhMCQVUxFTATBgNVBAgMDFBsYWNlIEhvbGRlcjEV +MBMGA1UEBwwMUGxhY2UgSG9sZGVyMSEwHwYDVQQKDBhQbGFjZSBIb2xkZXIgQ2Vy +dGlmaWNhdGUxFDASBgNVBAMMC2V4YW1wbGUub3JnMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAqufw4FdqYcJep7mHHcYGUN79GNBLpvAIdg+1NbKx+cB/ +PtaDuozqVkkT8CmM0Mruay4vCbYkMytCeKgHj2+hLMy7oUQvx2pK/V0i0foPAC0m +gAvgmaWZbQTENHX4A0Rwvim0yixgeBVhz4hTMOIunilSXbKRkFBUidCnYQe1Nzy1 +dbH/fh8++fzLCglDE2kydrE3Zq/54G2xFOxPt1DZRnQ3RBYaMIR/uPPjpVxRWl+p +w4ucklAIZcu2htlOpGl7/3baMtnhpTo9LrkWzSNS7CJQvj6BvDULbcN3+hNPOoVE +MM9MCaM9V+FS25kf+DfyaVUVDVIv0thpwa+f3tCXSwIDAQABo1MwUTAdBgNVHQ4E +FgQUV831A1qfpV+Sy/J+lN6LKLgopsQwHwYDVR0jBBgwFoAUV831A1qfpV+Sy/J+ +lN6LKLgopsQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAhnf7 +aW/jpr8JWC0l2lo0z1HpOaSNehhcfNyM6Y43lR3Lr1avibbIXkAOzCuSWanFNnex +dOq8PbG9bmO1ncM6qzXRBzk4pLVJmzzDZs/fPVghSZumY2bzzJFtdCQ5FiLaaE/c +cKtBPvoUjfvrBU9OwFSb9UaoQdxtateb/Kk6JfzWi6YZqxSXFNa0ZTWJaoRFQVj5 +bZZe+wgpGRz46p+2YMwsNolXxa+7yY9x6kOMqZP6++5LZGXm5iWxjWbN4WtqmNnN +cK35fAdLlg4d3wn5tVuTkpKH0FcaRlgpBSjVKejgnFTCxKcOwOCqOfu+nuSSWpa7 +aRqdAbMTeVFQhyLXhQ== +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCq5/DgV2phwl6n +uYcdxgZQ3v0Y0Eum8Ah2D7U1srH5wH8+1oO6jOpWSRPwKYzQyu5rLi8JtiQzK0J4 +qAePb6EszLuhRC/Hakr9XSLR+g8ALSaAC+CZpZltBMQ0dfgDRHC+KbTKLGB4FWHP +iFMw4i6eKVJdspGQUFSJ0KdhB7U3PLV1sf9+Hz75/MsKCUMTaTJ2sTdmr/ngbbEU +7E+3UNlGdDdEFhowhH+48+OlXFFaX6nDi5ySUAhly7aG2U6kaXv/dtoy2eGlOj0u +uRbNI1LsIlC+PoG8NQttw3f6E086hUQwz0wJoz1X4VLbmR/4N/JpVRUNUi/S2GnB +r5/e0JdLAgMBAAECggEAI0nY9rmWAbF8ke1A9OjajQA+Ck2YEVQmqxn7NKc9EHCq +1XK9qFtIV6CnOUObC9GbAQ58L+kn+FjKVNd9GCTYhsOPSnEl3GsaKM5+ThTv2/12 +oaHSMmd7EoOVb6+cEjCjhuBdsBERqjngBFYFt2Y8cfPeSfKBE+dCTWKD7QkGZe0Q +tEII2NeE1AoQwG34TANxyn+HbZG809i+k21Pm0tPNyAFwhPvotpjSftMNgco8jcx +39DCt7rG1etpc4VhIUUl8GqsNAHYg8/SfscE18PYeX7RJpKGS128E1mKCqWqn+Ie +VCiMmWoQ4ZBlUFp3tS4+FWcQtDr8Z82QTZQn3ODwIQKBgQDV1NR+SxjBBnzpHDZC +cTxCK0PU37VmPjyr6BAXlftCPLGLgKEVIDRuna6EoiZ0/K0RMDZRoaJgDQPoAXDV +UwJOWYtlJBzUxwQbBJc3S7C3kEC+qeRT9lRwIrn7P6gT1DMcSRsV4ULwGRWAMa2Q +6apbK/K+56Ds7LiStu3OcJSxDwKBgQDMnAvLm0uMo9ZceKG515ruqzQj2YPz2+Zc +f38pD1hESDRj1bzgT09FAsejPnlN7KPp8TFgRUB9Rqb6DZCkx49zdtcDkuZIKfQH +Ga7ITBXnTwe8M+nwq2Q2LJYPdB/p8mBqh4ujA2XIS7ZHCKqOFGsseP4H1uxZeORI +pIPQp4C+BQKBgQDKHw9tAZc4feV8g4pWa6rF8ReBFKTnLFU1OXpckQybo7s/Xirl +STfGh437GTq4wk7lPGlb6CkQGb1jhFkfjANWBBZbWDNYfXZIA6LcRdOY7+YDU5vc +Ma/G/0xFTfqWI7LcPc44dGFNiqhkMJEbtYOuAnDGOzRGP8yIAhnvVUN3yQKBgQC1 +eaYglYGBoQMMi1Xt7iQVkbWyIkedr6l22wJe2aRRE7Wb4sQeM1m8fMWyrUOL8NpF +MU6481NKibKp0AQ9kl5Sa9Iy8kTbNpKhBY93SbyXpwnWTDku4+UDA7KozDdOGVKY +ydX45JeO+lAWWsJjOAsCq+Gr9F020jmvkHL1SsuuPQKBgQDHHCSOlUycLnzxEAks +uiu5MseFzkmdWN1ShrjPkcJ4HLmSD5zRgCySpBB92WPU+yBANlY7WN6fJHVJk/d7 +sNAg78GuAxcWrNAQwu6DRjhnb8zTVICJX8HmDLmsoOHwLsShdckWMks5XEk3NzoV +4ym6aG0tDX+rkBkP/VIjSXA+Cg== +-----END PRIVATE KEY----- diff --git a/assets/etc/haproxy/certs/README.md b/assets/etc/haproxy/certs/README.md deleted file mode 100644 index 828a1d8..0000000 --- a/assets/etc/haproxy/certs/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Certs Folder - -Docker Easy HAProxy will save the SSL Certs here. diff --git a/assets/etc/supervisord.conf b/assets/etc/supervisord.conf index 29e789b..dd1f43b 100644 --- a/assets/etc/supervisord.conf +++ b/assets/etc/supervisord.conf @@ -2,15 +2,15 @@ file=/dev/shm/supervisor.sock ; (the path to the socket file) [supervisord] -logfile=/tmp/supervisord.log ; (main log file;default $CWD/supervisord.log) -logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB) -logfile_backups=10 ; (num of main logfile rotation backups;default 10) -loglevel=info ; (log level;default info; others: debug,warn,trace) -pidfile=/tmp/supervisord.pid ; (supervisord pidfile;default supervisord.pid) -nodaemon=false ; (start in foreground if true;default false) -minfds=1024 ; (min. avail startup file descriptors;default 1024) -minprocs=200 ; (min. avail process descriptors;default 200) -user=root ; +logfile=/var/log/supervisord.log ; (main log file;default $CWD/supervisord.log) +logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB) +logfile_backups=10 ; (num of main logfile rotation backups;default 10) +loglevel=info ; (log level;default info; others: debug,warn,trace) +pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid) +nodaemon=false ; (start in foreground if true;default false) +minfds=1024 ; (min. avail startup file descriptors;default 1024) +minprocs=200 ; (min. avail process descriptors;default 200) +user=root ; ; the below section must remain in the config file for RPC ; (supervisorctl/web interface) to work, additional interfaces may be diff --git a/assets/scripts/certbot.sh b/assets/scripts/certbot.sh new file mode 100755 index 0000000..acf9e76 --- /dev/null +++ b/assets/scripts/certbot.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +# Semaphore +if [ -f /tmp/certbot-lock ]; then + echo "[CERTBOT_JOB] Another process is running" + exit 0 +fi + +touch /tmp/certbot-lock + +mkdir -p /var/log/letsencrypt +ln -sf /dev/stdout /var/log/letsencrypt/letsencrypt.log + +REQUEST_CERTS="" +RENEW_CERTS="" + +for domain in $(cat /scripts/letsencrypt_hosts.txt); do + if [ ! -f "/etc/haproxy/certs/$domain.pem" ]; then + REQUEST_CERTS="$REQUES_CERTS -d $domain" + continue + fi + + if [[ $(find "/etc/haproxy/certs/$domain.pem" -mtime +30 -print) ]]; then + RENEW_CERTS="$RENEW_CERTS -d $domain" + fi +done + +if [ -n "$REQUEST_CERTS" ]; then + echo "[CERTBOT_JOB] Requesting certificates for $REQUEST_CERTS" + certbot certonly \ + --standalone \ + --preferred-challenges http \ + --http-01-port 2080 \ + --agree-tos \ + --issuance-timeout 90 \ + --no-eff-email \ + --non-interactive \ + --max-log-backups=0 \ + --post-hook "/scripts/certbot_to_haproxy.sh" \ + $REQUEST_CERTS --email $EASYHAPROXY_LETSENCRYPT_EMAIL +fi + +if [ -n "$RENEW_CERTS" ]; then + echo "[CERTBOT_JOB] Resquesting renew certificated fort $RENEW_CERTS" + certbot renew --post-hook "/scripts/certbot_to_haproxy.sh" +fi + +# Release semaphore +rm /tmp/certbot-lock diff --git a/assets/scripts/certbot_to_haproxy.sh b/assets/scripts/certbot_to_haproxy.sh new file mode 100755 index 0000000..f7833f6 --- /dev/null +++ b/assets/scripts/certbot_to_haproxy.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Loop through all Let's Encrypt certificates +for CERTIFICATE in `find /etc/letsencrypt/live/* -type d`; do + CERTIFICATE=`basename $CERTIFICATE` + + # 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 +done + +# It will be checked on haproxy-reload.sh +touch /tmp/force-reload \ No newline at end of file diff --git a/assets/scripts/exit-event-listener.py b/assets/scripts/exit-event-listener.py index 4f64778..2a7cda7 100755 --- a/assets/scripts/exit-event-listener.py +++ b/assets/scripts/exit-event-listener.py @@ -11,7 +11,7 @@ def main(): headers, payload = childutils.listener.wait() childutils.listener.ok() events = ['PROCESS_STATE_FATAL', 'PROCESS_STATE_EXITED', 'PROCESS_STATE_STOPPED'] - if not (headers['eventname'] in events): + if headers['eventname'] not in events: continue print(headers) diff --git a/assets/scripts/haproxy-reload.sh b/assets/scripts/haproxy-reload.sh index 55e7e27..ee29cd0 100755 --- a/assets/scripts/haproxy-reload.sh +++ b/assets/scripts/haproxy-reload.sh @@ -35,6 +35,7 @@ else RELOAD="false" else python3 swarm.py > /etc/haproxy/haproxy.cfg + echo "[CONF_CHECK] New configuration found" fi fi @@ -43,11 +44,21 @@ if cmp -s ${CONTROL_FILE} ${CONTROL_FILE}.old ; then fi if [[ ! -z "$1" ]]; then - echo "Initial configuration" + echo "[CONF_CHECK] Initial configuration" RELOAD="false" +else + /scripts/certbot.sh +fi + + +# If Certbot reloads successfully will create the file /tmp/force-reload +if [ -f /tmp/force-reload ]; then + echo "[CONF_CHECK] New certificates found..." + RELOAD="true" + rm /tmp/force-reload fi if [[ "$RELOAD" == "true" ]]; then - echo "Reloading..." + echo "[CONF_CHECK] Reloading..." /usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -x /var/run/haproxy.sock -sf $(cat /run/haproxy.pid) & fi diff --git a/assets/scripts/static.py b/assets/scripts/static.py index 07054ef..2e36cc6 100644 --- a/assets/scripts/static.py +++ b/assets/scripts/static.py @@ -1,5 +1,7 @@ import yaml import sys +import os + from easymapping import HaproxyConfigGenerator if len(sys.argv) != 2: @@ -13,4 +15,8 @@ with open(sys.argv[1], 'r') as content_file: cfg = HaproxyConfigGenerator(parsed) print(cfg.generate()) +path = os.path.dirname(os.path.realpath(__file__)) +with open(path + "/letsencrypt_hosts.txt", 'w') as fp: + fp.write('\n'.join(cfg.letsencrypt_hosts)) + exit(0) diff --git a/assets/scripts/swarm.py b/assets/scripts/swarm.py index 425535a..52fd829 100644 --- a/assets/scripts/swarm.py +++ b/assets/scripts/swarm.py @@ -1,9 +1,8 @@ import os from easymapping import HaproxyConfigGenerator -# path = os.path.dirname(os.path.realpath(__file__)) with open("/tmp/.docker_data", 'r') as content_file: - lineList = content_file.readlines() + line_list = content_file.readlines() result = { "customerrors": True if os.getenv("HAPROXY_CUSTOMERRORS") == "true" else False @@ -19,8 +18,11 @@ if os.getenv("HAPROXY_PASSWORD"): result["lookup_label"] = os.getenv("EASYHAPROXY_LABEL_PREFIX") if os.getenv("EASYHAPROXY_LABEL_PREFIX") else "easyhaproxy" cfg = HaproxyConfigGenerator(result) -print(cfg.generate(lineList)) +print(cfg.generate(line_list)) +path = os.path.dirname(os.path.realpath(__file__)) +with open(path + "/letsencrypt_hosts.txt", 'w') as fp: + fp.write('\n'.join(cfg.letsencrypt_hosts)) # print(jsonStr) diff --git a/easymapping/__init__.py b/easymapping/__init__.py index c414bb2..2c5777c 100644 --- a/easymapping/__init__.py +++ b/easymapping/__init__.py @@ -25,6 +25,16 @@ class DockerLabelHandler: return default_value + def get_bool(self, label, default_value = False): + if self.has_label(label): + return self.__data[label].lower() in ["True", "true", "1", "yes"] + return default_value + + def get_json(self, label, default_value = {}): + if self.has_label(label): + return json.loads(self.__data[label]) + return default_value + def set_data(self, data): self.__data = data @@ -36,26 +46,21 @@ class DockerLabelHandler: class HaproxyConfigGenerator: - def __init__(self, mapping, ssl_cert_folder="/etc/haproxy/certs"): + def __init__(self, mapping, ssl_cert_folder="/etc/haproxy/certs/discover"): self.mapping = mapping self.label = DockerLabelHandler(mapping['lookup_label'] if 'lookup_label' in mapping else "easyhaproxy") self.ssl_cert_folder = ssl_cert_folder - self.ssl_cert_increment = 0 + self.letsencrypt_hosts = [] + self.letsencrypt_email = os.getenv("EASYHAPROXY_LETSENCRYPT_EMAIL", "") os.makedirs(self.ssl_cert_folder, exist_ok=True) def generate(self, line_list = []): + self.mapping.setdefault("easymapping", []) + # static? if len(line_list) > 0: self.mapping["easymapping"] = self.parse(line_list) - else: - for d in self.mapping["easymapping"]: - for name, hosts in d.get('hosts', {}).items(): - if type(hosts) != list: - d['hosts'][name] = [hosts] - # still 'None' -> default to [] for jinja2 - if self.mapping["easymapping"] is None: - self.mapping["easymapping"] = [] file_loader = FileSystemLoader('templates') env = Environment(loader=file_loader) @@ -105,16 +110,13 @@ class HaproxyConfigGenerator: "80" ) - hash = "" - if self.label.create([definition, "sslcert"]) in d: - hash = hashlib.md5( - d[self.label.create([definition, "sslcert"])].encode('utf-8') - ).hexdigest() + letsencrypt = self.label.get_bool( + self.label.create([definition, "letsencrypt"]), + False + ) and self.letsencrypt_email != "" - key = port if not hash else port + "_" + hash - - if key not in easymapping: - easymapping[key] = { + if port not in easymapping: + easymapping[port] = { "mode": mode, "health-check": "", "port": port, @@ -128,34 +130,47 @@ class HaproxyConfigGenerator: "80" ) - easymapping[key]["health-check"] = self.label.get( + easymapping[port]["health-check"] = self.label.get( self.label.create([definition, "health-check"]), "" ) - easymapping[key]["hosts"].setdefault(d[host_label], []) - easymapping[key]["hosts"][d[host_label]] += ["{}:{}".format(container, ct_port)] - - # handle SSL - ssl_label = self.label.create([definition, "sslcert"]) - if self.label.has_label(ssl_label): - self.ssl_cert_increment += 1 - filename = "{}/{}.{}.pem".format( - self.ssl_cert_folder, d[host_label], str(self.ssl_cert_increment) + for hostname in d[host_label].split(","): + hostname = hostname.strip() + easymapping[port]["hosts"].setdefault(hostname, {}) + easymapping[port]["hosts"][hostname].setdefault("containers", []) + easymapping[port]["hosts"][hostname].setdefault("letsencrypt", False) + easymapping[port]["hosts"][hostname]["containers"] += ["{}:{}".format(container, ct_port)] + easymapping[port]["hosts"][hostname]["letsencrypt"] = letsencrypt + easymapping[port]["redirect"] = self.label.get_json( + self.label.create([definition, "redirect"]) ) - easymapping[key]["ssl_cert"] = filename - with open(filename, 'wb') as file: - file.write( - base64.b64decode(d[ssl_label]) - ) - # handle redirects - redirect = self.label.get( - self.label.create([definition, "redirect"]) - ) - if len(redirect) > 0: - for r in redirect.split(","): - r_parts = r.split("--") - easymapping[key]["redirect"][r_parts[0]] = r_parts[1] + if letsencrypt: + if "443" not in easymapping: + easymapping["443"] = { + "mode": "http", + "health-check": "ssl", + "port": "443", + "hosts": dict(), + "redirect": dict(), + } + easymapping["443"]["hosts"][hostname] = dict(easymapping[port]["hosts"][hostname]) + easymapping["443"]["hosts"][hostname]["letsencrypt"] = False + easymapping["443"]["ssl_cert"] = "/etc/haproxy/certs" + self.letsencrypt_hosts.append(hostname) if hostname not in self.letsencrypt_hosts else self.letsencrypt_hosts + + + # handle SSL + ssl_label = self.label.create([definition, "sslcert"]) + if self.label.has_label(ssl_label): + filename = "{}/{}.pem".format( + self.ssl_cert_folder, d[host_label] + ) + easymapping[port]["ssl_cert"] = filename + with open(filename, 'wb') as file: + file.write( + base64.b64decode(d[ssl_label]) + ) return easymapping.values() diff --git a/example.yml b/example.yml deleted file mode 100644 index 4d77acb..0000000 --- a/example.yml +++ /dev/null @@ -1,23 +0,0 @@ -stats: - username: admin - password: password - port: 1936 - -customerrors: true - -easymapping: - - port: 80 - hosts: - host1.com.br: container:5000 - host2.com.br: other:3000 - redirect: - www.host1.com.br: http://host1.com.br - - - port: 443 - ssl_cert: /etc/easyconfig/mycert.pem - hosts: - host1.com.br: container:80 - - - port: 8080 - hosts: - host3.com.br: domain:8181 \ No newline at end of file diff --git a/examples/docker/docker-compose-multi-containers.yml b/examples/docker/docker-compose-multi-containers.yml index b2d7467..98e44cf 100644 --- a/examples/docker/docker-compose-multi-containers.yml +++ b/examples/docker/docker-compose-multi-containers.yml @@ -30,7 +30,7 @@ services: deploy: replicas: 2 labels: - easyhaproxy.http.redirect: google.helloworld.com--www.google.com + easyhaproxy.http.redirect: '{"google.helloworld.com": "www.google.com"}' easyhaproxy.http.host: www.helloworld.com easyhaproxy.http.port: 19901 easyhaproxy.http.localport: 80 diff --git a/examples/docker/docker-compose.yml b/examples/docker/docker-compose.yml index 9b1d9e6..c57920f 100644 --- a/examples/docker/docker-compose.yml +++ b/examples/docker/docker-compose.yml @@ -3,6 +3,9 @@ # # or add to /etc/hosts # 127.0.0.1 host1.local +# +# Test SSL: +# openssl s_client -showcerts -connect 127.0.0.1:443 --servername host1.local version: "3" @@ -33,4 +36,17 @@ services: easyhaproxy.https.port: 443 easyhaproxy.https.localport: 8080 easyhaproxy.https.host: host1.local - easyhaproxy.https.sslcert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZEVENDQXZXZ0F3SUJBZ0lVUmkrdzFaVmdlZWRUbE5JQXdxUUJNSnY2ZFhzd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0ZqRVVNQklHQTFVRUF3d0xhRzl6ZERFdWJHOWpZV3d3SGhjTk1qRXdPREV3TVRnME9UQTJXaGNOTXpFdwpPREE0TVRnME9UQTJXakFXTVJRd0VnWURWUVFEREF0b2IzTjBNUzVzYjJOaGJEQ0NBaUl3RFFZSktvWklodmNOCkFRRUJCUUFEZ2dJUEFEQ0NBZ29DZ2dJQkFNQkRBaExBeWdKdWFXNnc2ZmZpZ3pUQUFHWHBtRXowdEl4bjFrNFoKeDV3TjVycHYvcXUwUU1ZeitBdjJ1MWVPS0VLWmVhRlJWcFQwcjkzZFg3SXZiRVpIdDI1R1BpQnZsTEdxaGpLUgpQblNrLzdVOFhtc250dFVBVjdyVkVLMVVyZEZ3OC9Jd3JpUUMrZGhyMG1uWWZTRE1rdkJvTUZwZGhWTlRyYkFaCjFUQjZyUWpFN0FyME10OG15OTZYSm13cmNqSzJUaitFMnJnUElVejFlNWNla0ZZSURTQmF0bXcrMyt2citUNXgKRk5Ga0oybzMwVzVvOFpmbENKSnpyVmFpaHFRaWNzNlpLRGdwZjdpcVhNRml3V0lsaGRRcEd2eDVHZi9LRlRLOQpVYU9uUlp6L1grMkNlYkFGYVRIUjNrL1BZcHBXVGdCQkJ1UnZscEN3K3dkbmttdGVDMFNRUkY5MVFXVnI3ZWpvCjdLYU9sR0k1VnR2TVVzV3ZUZUFabXBheW1JYUFURVR1T0phWTBKVTExT21MZUQ5RE9qNUUyU1E3cUlYL3BGY3AKeHB6RzVqNGMrTWxndnhQMlZBa05UZUFYQ2FZaVBCUUg1WlpnMEhFMlduQjFLaExSRmxIZDRpSFFEMkdKNXlOLwo2ZkNGQmZaZktTZUs4SmF1d3hnV2tyYTUzT2NEcS9tS2QrREEvZEsrL3J1Rzd0cXdWZ0lhMDRIT3Bsek03TFlSCkdCMElyczkrbHI1L1BKYlFabVUwNzNNZG42Y1hBZzNwKzZ3dndGbERrUzV2MTNnQkRZTkh0RjYyYmM1NTFlZEYKWjZrR3pKN3dtR1JvODRhQlA3TXVSWmVSZUxPclNTNjdhMXdMZHpac01uUDFUSjd4OUxmcjlNS2wydURuUWRuWQpleDhEQWdNQkFBR2pVekJSTUIwR0ExVWREZ1FXQkJTUS9tdFpkNmg4ZW45WVFWSDZITzFQbFdXaXF6QWZCZ05WCkhTTUVHREFXZ0JTUS9tdFpkNmg4ZW45WVFWSDZITzFQbFdXaXF6QVBCZ05WSFJNQkFmOEVCVEFEQVFIL01BMEcKQ1NxR1NJYjNEUUVCQ3dVQUE0SUNBUUNoUVlOdWFoMyttVHBJQkRZeEdyalRKTnVPVElNYVd6TXlpMXRrZitMMApzRUd3cGJtQU8ybVdXUVlGN1dWTHNpOThQVUxoM2FkanQyaml1ZDlWbGFhQzZnbnduNVpvMStQaWxvOXNOTExXCjZpajArck40a3dJbS9wTnFpK2pEdXUyY3ZBdUhJd1pXZWg4YkVlLzVVQ3hvNGlobVdGUU44ZUo2VFVLQ3BoUkMKNkVvci9TU1paQlFIZ1BsMEJjaHpIT2t3dTdSM0xDbmRScXhqaEFvVmI5eVFPVitac21UZUpYdWx3TnpKMXVMdApUOE9JZ0lpRHBtQm83SFNOMkgwazNjaHgwMEFzalV5SjltbUFXUGVqRmUvS1hMUlBjVlpSMTdqaHpnZklCRXpzCk01V3RXRm0xYUhEalZ2Nk02aXRlVm02MUU5VCtrL00xMXJ1MWUyWXdzeFREdmI2eDA0bWNyTnU5c29xZGRCYnIKVmZwbHV1b1EvaEVBYlh0Rk5Qb1R5U3B6MGN3T3djSENvd1ZPTG1kS2d2SW1zelppTXlISEc4VkdHbVBoODhuNwp3VnhiMGdWMFA0Uk1yY01MZGVUZG41NVlRcjFDcUJyMzRlQjZvbDZBc2JUbTNWekJIUlZtRk5rc2wxbzVKQjV0CnRYTGdGL0c4L3J6Si80bTFQYVZ1eHJCN0R4VW1JazhFUGJTSVZrdlp2ZDdMQnpLd1E2SWZWYXVjZXdIZkVhalEKVklpZXhTTWlGYzdsdzNLbnhqT0haamY2Rk05VllnM05vKytHZEM5OXM3TGtJdUp3QU1MTnFUUTdIdmhuN1l2UAo0RmxTSWdjNnhqMFlrR1pFUWxiNW8vNW5hdUVxUVUwQUJndzZqdEk0TnhyTkxUNmNwN0NPNE0weElERWcvM1lECmFBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQotLS0tLUJFR0lOIFBSSVZBVEUgS0VZLS0tLS0KTUlJSlF3SUJBREFOQmdrcWhraUc5dzBCQVFFRkFBU0NDUzB3Z2drcEFnRUFBb0lDQVFEQVF3SVN3TW9DYm1sdQpzT24zNG9NMHdBQmw2WmhNOUxTTVo5Wk9HY2VjRGVhNmIvNnJ0RURHTS9nTDlydFhqaWhDbVhtaFVWYVU5Sy9kCjNWK3lMMnhHUjdkdVJqNGdiNVN4cW9ZeWtUNTBwUCsxUEY1cko3YlZBRmU2MVJDdFZLM1JjUFB5TUs0a0F2blkKYTlKcDJIMGd6Skx3YURCYVhZVlRVNjJ3R2RVd2VxMEl4T3dLOURMZkpzdmVseVpzSzNJeXRrNC9oTnE0RHlGTQo5WHVYSHBCV0NBMGdXclpzUHQvcjYvaytjUlRSWkNkcU45RnVhUEdYNVFpU2M2MVdvb2FrSW5MT21TZzRLWCs0CnFsekJZc0ZpSllYVUtScjhlUm4veWhVeXZWR2pwMFdjLzEvdGdubXdCV2t4MGQ1UHoyS2FWazRBUVFia2I1YVEKc1BzSFo1SnJYZ3RFa0VSZmRVRmxhKzNvNk95bWpwUmlPVmJiekZMRnIwM2dHWnFXc3BpR2dFeEU3amlXbU5DVgpOZFRwaTNnL1F6bytSTmtrTzZpRi82UlhLY2FjeHVZK0hQakpZTDhUOWxRSkRVM2dGd21tSWp3VUIrV1dZTkJ4Ck5scHdkU29TMFJaUjNlSWgwQTloaWVjamYrbndoUVgyWHlrbml2Q1dyc01ZRnBLMnVkem5BNnY1aW5mZ3dQM1MKdnY2N2h1N2FzRllDR3RPQnpxWmN6T3kyRVJnZENLN1BmcGErZnp5VzBHWmxOTzl6SForbkZ3SU42ZnVzTDhCWgpRNUV1YjlkNEFRMkRSN1JldG0zT2VkWG5SV2VwQnN5ZThKaGthUE9HZ1QrekxrV1hrWGl6cTBrdXUydGNDM2MyCmJESno5VXllOGZTMzYvVENwZHJnNTBIWjJIc2ZBd0lEQVFBQkFvSUNBUUMveFpiWjBjY3RxYWdzcXZhVk5URWUKZXExcStoZmFHdlBFWVFhWUhJcklFKzJpNVhjbkdjTEtjS2ZvZHhEakFuOFIvemdkT3A2Y01YMENWbi9Qb2hIawpBRUR0RTgrQVZ3d0FNMUZzT3dnTEhWR2FHejhxcnhCbFlkUWdIY3BtdWVJdTJQWGJDOGVIVUJpYVVPSXVoYXc1Ci9SUk1EQUMvQWkyc3NmaTdnT2p2VkU0b1F4UVcwUUcxS0dPT0FVSm4vdVlIdzJSRlkyVXUxcGlteE8ya0RPNTMKZ2N4bUMxV09ueUNIbUhhaVcvVWg3ejZKYW1mU000ZFh0VEpac2x5aDM3ZGhIS05iZzlWa1A3Q1FLQTRoTHpvcApoYmY1cVk2cmFyZ09OaW55MUhnTVB4cm13S3VVb3VKeU90TjB5QnR4akRDVU5hWFVCd2l5N3NOR1MrSDR2c3lCCjVQOUhoSUhTdHUrRlp0M0hHN0VJcUNuZGlhU0tEUzRqV2FWUUFiYm80bloyWnMyQkQreERlUFJDUlVxWDdyTTQKNFh6UElSV1dYbW1XZi83SWcyOUhicnA0YTlMY09tUTJsZUNKdGJhVEZTTjk2T0xVSjVFK2hRMHVsQ1pnQlZtUQpSQ1VZa0pQNGxPemJhS2R6anhnSE1ySHptNDVlVUZmOExpck94aTJ1eXhYSFFtRE51NGIzWDE4a3QzUGdVbVVtCjNkWHBsM2ZxU3lKYTdTQ1Y4Wk5CcnNyRHExRSt0aFl0dTkxUWJWU0d4SGQ5SHJOVmUzWGRMYk9DZFU5Q3VDNjkKTmdsem5hYTdzWkxxbXlLZWpUZkdzWTd4cldkTmNNUGw0cDRmY0lEL080RXBBU1pmb3JwVGVLTlQwWklmWlpldwpiMG1BUWVZWnFRTThpL3FNWU4vdUFRS0NBUUVBNXFnMXNSTk1jNlZkTS90UmdsYXNHWW94amdSQzJPcUFEWmdzCm1BWE1VSjNrRXJweXh0K2VDaW15OGlidVlwelJUSVE4ZkJUV1JrQ3RSWlhKNytLY0xWdGs5UVpJb0xiaHlOd2QKNEl4RVFaRnVVbGpEYnZTalRMU3ljc0h2bzY1aWJXSWZUTDdiZ1dsTEdnR3EvVU96ZkdzZ0g2Uzl3THA1RzMwRwo4RUx5akk1ZVRJWUlDcmZUbVZMK2M0NU1ScEVNS28rY3Z6OFB5c2lhT0ZUbjNjeXN3UFZkWWFlRUVxTVFqVTh3CklHTnNHWkx5dFk3QkFCQmNZMGxkcnRiYS9PK0Z2LytSSDd1VXR6UDd4cENJd0ZDeDgwWnpOK1dSeTlOdkk2M1UKenEzeUlCb1c5R3lBcEQyK1BMYVBOeGY3UUxUVUNoWTFaei9kWVJsdEtPeHYyQWE1Z1FLQ0FRRUExV0xXTnFwMApmaEIvWnRmU0VTaHhGTU04OWNqTjZBYXoxV0tMN3VUQm91OW9TSm54amtoa2FWNzZhY25UL2lxWHR4TU5nSGkxCmZJbURwVTNQdk0wWTRVZDJUNDdvSGM2UDFCclpQTi9HbVh5L3M2QkFFZFB3TGU3Sis0blRJU0hBZEdtcmgrYS8KNXBrdHUzMmc5bFdxZnR4ZWNGSVZTTFBXa3hUMFhLaU14cDFmZmtMK09hdnBNZ01GWks0MWlLczNkTlNoS1BvZwpMOEdTUGNQOXgveW43OFAyZUszTitQR2psQTZwUHpyQU55V1U3TjAvYm1IY0I5VEtQK3VkWVdjalZocnU3TVlOCndOckU0a0tkQzh2OGk3eDd0RGJ2Yjc5VCtGbzZQSWg1M3AwT3NuWnpBOFVSMFFOUit2RFF1ZlF1eWFqOFJFQysKWkc4WXlDS3N2azh5Z3dLQ0FRQS9mc1N4QjBmL2VlRXJZeDZ3QzUzNnRlRW9ZQ0hxeHJzVGd2V2JyOVRyeUZzMQprSi95QVRMblIwMWNmYjBYNW1WemM5K1dwTUhMdXhnMzFLRXZhU2xuRHdhK3NNa2pmTlN3ejI5bUZoYmdHZUhOCngyT2RVcmoxYjdURUJJRXNoTi9SanJaaEVSVXFEY3MvMEgrNmtuMkJYWmdOUGZPQ2I1TFJMMXpPblE5YUJBTVAKZThJUStVUEZyR1FoZVdXajgxL3ZBM081N2VreUFJRDd5dHU5WWcrWVdyTW5JODhtdGo3ak40NWZEQitBOXNQYgptUDJtUDlxKzlqNVUyQTZXbkhVc1FuVTMwQktEVUVzYUFVV3o4MExaWG1adlY4SUg0eDl3S2ZVd0pCQklLQVp6CnFMN005N1k3em1Ha1gvU3BmbDMwbk9KOGxzY2hhTGQxRVlsRVphMkJBb0lCQVFDeWUyNVQ0VFY1TUpGdjB6dVoKTUd1TmcxU2MvTzRGa24yZkVVT2NlV2pod1VCSDRjUGpUL2YxRHdXRHNOYUo5TlJieENyNTkzMU9BclBEYzVjOApBNDA0K1k0ak01UkJRa0tabGk5NHRIQW9kK2pjOVVCQjZUVXZKbGw1OVNsTXdDOTY3OXdTMjFaT0tuZlBLR0NYClNzWkdRRXNaeGY2Wmhoc0hnWEozZ2wvbHpVSlBtUGVPQTVZVlIrT2Q5LzA5S0lGRlRvalNmb3luaFZDdUt4NDkKeGI0dVZZbjJIT0o0eEowZlBUZ2hkQ0hNdnJtWGVlUVJqdmI4OGVhTm1xVlVFSEhGRnRnYjRma2xBNWZFN1JUeApCaGxpUkRCd1o3YlVrSU5LNnlWazluNkJUbnM1bU12UkxtZ2RuSnBZdkU3S0MwMkxUYlpiM0krajhDMFpVYStOCnF5N0RBb0lCQUFpZXJpYlM3V1VjbDJhQmxrbTUrVzdxTm0vSU5tNXp2bm9TUG82VjN3YTVoczZmOStDL2tiZEYKODdqUVBBL1lGZTN1UjJzQUo3c2xYNWV1Wks4V21mcEZtZ3psdTBzRXo4MU1MUS9XeXBadFp5dHlWdFd6QjJQdQpYQ1cxdGRTSDllSTJCbWhYZ29rSE5UTTQ4TmsveE9FTnJQL3NlWHJJeDVMSzBobkRIWm90dS96NitZU2tCOWhGCmNtMmZaeWdEMWRNTFg2bGlSaW14eUZZK2RJQ0pOQjk1SmlmVExXWW5XZUdkZGt3UHRYVWVHWEUxb2x6dk5rTEQKek16RTA5dWhreC9sUkpudGVPQkVaYWY4ME9CLzA5T2k5YjkvcnhZNTlkd3NINkdheExvVGZFS3VQbnZCVk1OUgpZa1UxNFd6UUtsZUZraUJKSTlsVnZuZmdHbk9sZ2cwPQotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t \ No newline at end of file + easyhaproxy.https.sslcert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZEVENDQXZXZ0F3SUJBZ0lVUmkrdzFaVmdlZWRUbE5JQXdxUUJNSnY2ZFhzd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0ZqRVVNQklHQTFVRUF3d0xhRzl6ZERFdWJHOWpZV3d3SGhjTk1qRXdPREV3TVRnME9UQTJXaGNOTXpFdwpPREE0TVRnME9UQTJXakFXTVJRd0VnWURWUVFEREF0b2IzTjBNUzVzYjJOaGJEQ0NBaUl3RFFZSktvWklodmNOCkFRRUJCUUFEZ2dJUEFEQ0NBZ29DZ2dJQkFNQkRBaExBeWdKdWFXNnc2ZmZpZ3pUQUFHWHBtRXowdEl4bjFrNFoKeDV3TjVycHYvcXUwUU1ZeitBdjJ1MWVPS0VLWmVhRlJWcFQwcjkzZFg3SXZiRVpIdDI1R1BpQnZsTEdxaGpLUgpQblNrLzdVOFhtc250dFVBVjdyVkVLMVVyZEZ3OC9Jd3JpUUMrZGhyMG1uWWZTRE1rdkJvTUZwZGhWTlRyYkFaCjFUQjZyUWpFN0FyME10OG15OTZYSm13cmNqSzJUaitFMnJnUElVejFlNWNla0ZZSURTQmF0bXcrMyt2citUNXgKRk5Ga0oybzMwVzVvOFpmbENKSnpyVmFpaHFRaWNzNlpLRGdwZjdpcVhNRml3V0lsaGRRcEd2eDVHZi9LRlRLOQpVYU9uUlp6L1grMkNlYkFGYVRIUjNrL1BZcHBXVGdCQkJ1UnZscEN3K3dkbmttdGVDMFNRUkY5MVFXVnI3ZWpvCjdLYU9sR0k1VnR2TVVzV3ZUZUFabXBheW1JYUFURVR1T0phWTBKVTExT21MZUQ5RE9qNUUyU1E3cUlYL3BGY3AKeHB6RzVqNGMrTWxndnhQMlZBa05UZUFYQ2FZaVBCUUg1WlpnMEhFMlduQjFLaExSRmxIZDRpSFFEMkdKNXlOLwo2ZkNGQmZaZktTZUs4SmF1d3hnV2tyYTUzT2NEcS9tS2QrREEvZEsrL3J1Rzd0cXdWZ0lhMDRIT3Bsek03TFlSCkdCMElyczkrbHI1L1BKYlFabVUwNzNNZG42Y1hBZzNwKzZ3dndGbERrUzV2MTNnQkRZTkh0RjYyYmM1NTFlZEYKWjZrR3pKN3dtR1JvODRhQlA3TXVSWmVSZUxPclNTNjdhMXdMZHpac01uUDFUSjd4OUxmcjlNS2wydURuUWRuWQpleDhEQWdNQkFBR2pVekJSTUIwR0ExVWREZ1FXQkJTUS9tdFpkNmg4ZW45WVFWSDZITzFQbFdXaXF6QWZCZ05WCkhTTUVHREFXZ0JTUS9tdFpkNmg4ZW45WVFWSDZITzFQbFdXaXF6QVBCZ05WSFJNQkFmOEVCVEFEQVFIL01BMEcKQ1NxR1NJYjNEUUVCQ3dVQUE0SUNBUUNoUVlOdWFoMyttVHBJQkRZeEdyalRKTnVPVElNYVd6TXlpMXRrZitMMApzRUd3cGJtQU8ybVdXUVlGN1dWTHNpOThQVUxoM2FkanQyaml1ZDlWbGFhQzZnbnduNVpvMStQaWxvOXNOTExXCjZpajArck40a3dJbS9wTnFpK2pEdXUyY3ZBdUhJd1pXZWg4YkVlLzVVQ3hvNGlobVdGUU44ZUo2VFVLQ3BoUkMKNkVvci9TU1paQlFIZ1BsMEJjaHpIT2t3dTdSM0xDbmRScXhqaEFvVmI5eVFPVitac21UZUpYdWx3TnpKMXVMdApUOE9JZ0lpRHBtQm83SFNOMkgwazNjaHgwMEFzalV5SjltbUFXUGVqRmUvS1hMUlBjVlpSMTdqaHpnZklCRXpzCk01V3RXRm0xYUhEalZ2Nk02aXRlVm02MUU5VCtrL00xMXJ1MWUyWXdzeFREdmI2eDA0bWNyTnU5c29xZGRCYnIKVmZwbHV1b1EvaEVBYlh0Rk5Qb1R5U3B6MGN3T3djSENvd1ZPTG1kS2d2SW1zelppTXlISEc4VkdHbVBoODhuNwp3VnhiMGdWMFA0Uk1yY01MZGVUZG41NVlRcjFDcUJyMzRlQjZvbDZBc2JUbTNWekJIUlZtRk5rc2wxbzVKQjV0CnRYTGdGL0c4L3J6Si80bTFQYVZ1eHJCN0R4VW1JazhFUGJTSVZrdlp2ZDdMQnpLd1E2SWZWYXVjZXdIZkVhalEKVklpZXhTTWlGYzdsdzNLbnhqT0haamY2Rk05VllnM05vKytHZEM5OXM3TGtJdUp3QU1MTnFUUTdIdmhuN1l2UAo0RmxTSWdjNnhqMFlrR1pFUWxiNW8vNW5hdUVxUVUwQUJndzZqdEk0TnhyTkxUNmNwN0NPNE0weElERWcvM1lECmFBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQotLS0tLUJFR0lOIFBSSVZBVEUgS0VZLS0tLS0KTUlJSlF3SUJBREFOQmdrcWhraUc5dzBCQVFFRkFBU0NDUzB3Z2drcEFnRUFBb0lDQVFEQVF3SVN3TW9DYm1sdQpzT24zNG9NMHdBQmw2WmhNOUxTTVo5Wk9HY2VjRGVhNmIvNnJ0RURHTS9nTDlydFhqaWhDbVhtaFVWYVU5Sy9kCjNWK3lMMnhHUjdkdVJqNGdiNVN4cW9ZeWtUNTBwUCsxUEY1cko3YlZBRmU2MVJDdFZLM1JjUFB5TUs0a0F2blkKYTlKcDJIMGd6Skx3YURCYVhZVlRVNjJ3R2RVd2VxMEl4T3dLOURMZkpzdmVseVpzSzNJeXRrNC9oTnE0RHlGTQo5WHVYSHBCV0NBMGdXclpzUHQvcjYvaytjUlRSWkNkcU45RnVhUEdYNVFpU2M2MVdvb2FrSW5MT21TZzRLWCs0CnFsekJZc0ZpSllYVUtScjhlUm4veWhVeXZWR2pwMFdjLzEvdGdubXdCV2t4MGQ1UHoyS2FWazRBUVFia2I1YVEKc1BzSFo1SnJYZ3RFa0VSZmRVRmxhKzNvNk95bWpwUmlPVmJiekZMRnIwM2dHWnFXc3BpR2dFeEU3amlXbU5DVgpOZFRwaTNnL1F6bytSTmtrTzZpRi82UlhLY2FjeHVZK0hQakpZTDhUOWxRSkRVM2dGd21tSWp3VUIrV1dZTkJ4Ck5scHdkU29TMFJaUjNlSWgwQTloaWVjamYrbndoUVgyWHlrbml2Q1dyc01ZRnBLMnVkem5BNnY1aW5mZ3dQM1MKdnY2N2h1N2FzRllDR3RPQnpxWmN6T3kyRVJnZENLN1BmcGErZnp5VzBHWmxOTzl6SForbkZ3SU42ZnVzTDhCWgpRNUV1YjlkNEFRMkRSN1JldG0zT2VkWG5SV2VwQnN5ZThKaGthUE9HZ1QrekxrV1hrWGl6cTBrdXUydGNDM2MyCmJESno5VXllOGZTMzYvVENwZHJnNTBIWjJIc2ZBd0lEQVFBQkFvSUNBUUMveFpiWjBjY3RxYWdzcXZhVk5URWUKZXExcStoZmFHdlBFWVFhWUhJcklFKzJpNVhjbkdjTEtjS2ZvZHhEakFuOFIvemdkT3A2Y01YMENWbi9Qb2hIawpBRUR0RTgrQVZ3d0FNMUZzT3dnTEhWR2FHejhxcnhCbFlkUWdIY3BtdWVJdTJQWGJDOGVIVUJpYVVPSXVoYXc1Ci9SUk1EQUMvQWkyc3NmaTdnT2p2VkU0b1F4UVcwUUcxS0dPT0FVSm4vdVlIdzJSRlkyVXUxcGlteE8ya0RPNTMKZ2N4bUMxV09ueUNIbUhhaVcvVWg3ejZKYW1mU000ZFh0VEpac2x5aDM3ZGhIS05iZzlWa1A3Q1FLQTRoTHpvcApoYmY1cVk2cmFyZ09OaW55MUhnTVB4cm13S3VVb3VKeU90TjB5QnR4akRDVU5hWFVCd2l5N3NOR1MrSDR2c3lCCjVQOUhoSUhTdHUrRlp0M0hHN0VJcUNuZGlhU0tEUzRqV2FWUUFiYm80bloyWnMyQkQreERlUFJDUlVxWDdyTTQKNFh6UElSV1dYbW1XZi83SWcyOUhicnA0YTlMY09tUTJsZUNKdGJhVEZTTjk2T0xVSjVFK2hRMHVsQ1pnQlZtUQpSQ1VZa0pQNGxPemJhS2R6anhnSE1ySHptNDVlVUZmOExpck94aTJ1eXhYSFFtRE51NGIzWDE4a3QzUGdVbVVtCjNkWHBsM2ZxU3lKYTdTQ1Y4Wk5CcnNyRHExRSt0aFl0dTkxUWJWU0d4SGQ5SHJOVmUzWGRMYk9DZFU5Q3VDNjkKTmdsem5hYTdzWkxxbXlLZWpUZkdzWTd4cldkTmNNUGw0cDRmY0lEL080RXBBU1pmb3JwVGVLTlQwWklmWlpldwpiMG1BUWVZWnFRTThpL3FNWU4vdUFRS0NBUUVBNXFnMXNSTk1jNlZkTS90UmdsYXNHWW94amdSQzJPcUFEWmdzCm1BWE1VSjNrRXJweXh0K2VDaW15OGlidVlwelJUSVE4ZkJUV1JrQ3RSWlhKNytLY0xWdGs5UVpJb0xiaHlOd2QKNEl4RVFaRnVVbGpEYnZTalRMU3ljc0h2bzY1aWJXSWZUTDdiZ1dsTEdnR3EvVU96ZkdzZ0g2Uzl3THA1RzMwRwo4RUx5akk1ZVRJWUlDcmZUbVZMK2M0NU1ScEVNS28rY3Z6OFB5c2lhT0ZUbjNjeXN3UFZkWWFlRUVxTVFqVTh3CklHTnNHWkx5dFk3QkFCQmNZMGxkcnRiYS9PK0Z2LytSSDd1VXR6UDd4cENJd0ZDeDgwWnpOK1dSeTlOdkk2M1UKenEzeUlCb1c5R3lBcEQyK1BMYVBOeGY3UUxUVUNoWTFaei9kWVJsdEtPeHYyQWE1Z1FLQ0FRRUExV0xXTnFwMApmaEIvWnRmU0VTaHhGTU04OWNqTjZBYXoxV0tMN3VUQm91OW9TSm54amtoa2FWNzZhY25UL2lxWHR4TU5nSGkxCmZJbURwVTNQdk0wWTRVZDJUNDdvSGM2UDFCclpQTi9HbVh5L3M2QkFFZFB3TGU3Sis0blRJU0hBZEdtcmgrYS8KNXBrdHUzMmc5bFdxZnR4ZWNGSVZTTFBXa3hUMFhLaU14cDFmZmtMK09hdnBNZ01GWks0MWlLczNkTlNoS1BvZwpMOEdTUGNQOXgveW43OFAyZUszTitQR2psQTZwUHpyQU55V1U3TjAvYm1IY0I5VEtQK3VkWVdjalZocnU3TVlOCndOckU0a0tkQzh2OGk3eDd0RGJ2Yjc5VCtGbzZQSWg1M3AwT3NuWnpBOFVSMFFOUit2RFF1ZlF1eWFqOFJFQysKWkc4WXlDS3N2azh5Z3dLQ0FRQS9mc1N4QjBmL2VlRXJZeDZ3QzUzNnRlRW9ZQ0hxeHJzVGd2V2JyOVRyeUZzMQprSi95QVRMblIwMWNmYjBYNW1WemM5K1dwTUhMdXhnMzFLRXZhU2xuRHdhK3NNa2pmTlN3ejI5bUZoYmdHZUhOCngyT2RVcmoxYjdURUJJRXNoTi9SanJaaEVSVXFEY3MvMEgrNmtuMkJYWmdOUGZPQ2I1TFJMMXpPblE5YUJBTVAKZThJUStVUEZyR1FoZVdXajgxL3ZBM081N2VreUFJRDd5dHU5WWcrWVdyTW5JODhtdGo3ak40NWZEQitBOXNQYgptUDJtUDlxKzlqNVUyQTZXbkhVc1FuVTMwQktEVUVzYUFVV3o4MExaWG1adlY4SUg0eDl3S2ZVd0pCQklLQVp6CnFMN005N1k3em1Ha1gvU3BmbDMwbk9KOGxzY2hhTGQxRVlsRVphMkJBb0lCQVFDeWUyNVQ0VFY1TUpGdjB6dVoKTUd1TmcxU2MvTzRGa24yZkVVT2NlV2pod1VCSDRjUGpUL2YxRHdXRHNOYUo5TlJieENyNTkzMU9BclBEYzVjOApBNDA0K1k0ak01UkJRa0tabGk5NHRIQW9kK2pjOVVCQjZUVXZKbGw1OVNsTXdDOTY3OXdTMjFaT0tuZlBLR0NYClNzWkdRRXNaeGY2Wmhoc0hnWEozZ2wvbHpVSlBtUGVPQTVZVlIrT2Q5LzA5S0lGRlRvalNmb3luaFZDdUt4NDkKeGI0dVZZbjJIT0o0eEowZlBUZ2hkQ0hNdnJtWGVlUVJqdmI4OGVhTm1xVlVFSEhGRnRnYjRma2xBNWZFN1JUeApCaGxpUkRCd1o3YlVrSU5LNnlWazluNkJUbnM1bU12UkxtZ2RuSnBZdkU3S0MwMkxUYlpiM0krajhDMFpVYStOCnF5N0RBb0lCQUFpZXJpYlM3V1VjbDJhQmxrbTUrVzdxTm0vSU5tNXp2bm9TUG82VjN3YTVoczZmOStDL2tiZEYKODdqUVBBL1lGZTN1UjJzQUo3c2xYNWV1Wks4V21mcEZtZ3psdTBzRXo4MU1MUS9XeXBadFp5dHlWdFd6QjJQdQpYQ1cxdGRTSDllSTJCbWhYZ29rSE5UTTQ4TmsveE9FTnJQL3NlWHJJeDVMSzBobkRIWm90dS96NitZU2tCOWhGCmNtMmZaeWdEMWRNTFg2bGlSaW14eUZZK2RJQ0pOQjk1SmlmVExXWW5XZUdkZGt3UHRYVWVHWEUxb2x6dk5rTEQKek16RTA5dWhreC9sUkpudGVPQkVaYWY4ME9CLzA5T2k5YjkvcnhZNTlkd3NINkdheExvVGZFS3VQbnZCVk1OUgpZa1UxNFd6UUtsZUZraUJKSTlsVnZuZmdHbk9sZ2cwPQotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t + + container2: + image: byjg/static-httpserver + labels: + easyhaproxy.http.redirect: host2.local--https://host2.local + easyhaproxy.http.host: host2.local + easyhaproxy.http.port: 80 + + easyhaproxy.https.port: 443 + easyhaproxy.https.localport: 8080 + easyhaproxy.https.host: host2.local + easyhaproxy.https.sslcert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxVENDQXBHZ0F3SUJBZ0lVSWQ1Yjl0OXVxSDc4ZzAyRXpiV0Y2RktWdzNnd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1pERUxNQWtHQTFVRUJoTUNRbEl4RnpBVkJnTlZCQWdNRGxKcGJ5QmtaU0JLWVc1bGFYSnZNUmN3RlFZRApWUVFIREE1U2FXOGdaR1VnU21GdVpXbHliekVOTUFzR0ExVUVDZ3dFUVVOTlJURVVNQklHQTFVRUF3d0xhRzl6CmRESXViRzlqWVd3d0hoY05Nakl3T0RFMU1EUXlOekExV2hjTk1qTXdPREUxTURReU56QTFXakJrTVFzd0NRWUQKVlFRR0V3SkNVakVYTUJVR0ExVUVDQXdPVW1sdklHUmxJRXBoYm1WcGNtOHhGekFWQmdOVkJBY01EbEpwYnlCawpaU0JLWVc1bGFYSnZNUTB3Q3dZRFZRUUtEQVJCUTAxRk1SUXdFZ1lEVlFRRERBdG9iM04wTWk1c2IyTmhiRENDCkFTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTVNLdnJPYWhhdkNYbnZTRjUxMzFocG8KNms2NUM1N2pnUlE4NEZhRGo1TWJKT1ZsWVFWRnRNRzBYT2s3YStoaDV2MWZlNHdIMFI3STZGRG8wVjlzUytzcwprbzVic0VsYzF4WWxnNUhidUtxODl2UlNLZzZFRGx6dHgzQktiaTkxMlBtdDV2RkdOSjE2emN3NzdEVXJRSVhvCjRJL2I0YTNwbUJpV2o0M05vVElybVNXSHRzR3d3T2ozaUR2U3dlcWRZWEpJcjNocEhINXU2cG9oakRvUXZxRHoKSzZNdThwNm1oQ1VLTnM3S0ZKbk5Jbk5HMjVvUVQ2TzBuNE9HdG1nUmpMV29wZEVuT2hNa0tzZklvSTFYdGxYQgpMQkR2N2h1SUNrM3Q1eXd0ZkNReU8wOWtYN2xGSWdkNXJuNytNandINVdOZXFiUUp4dWFxam9YUW5OWlVnVXNDCkF3RUFBYU5UTUZFd0hRWURWUjBPQkJZRUZOaE1CRzhxNmEraUsybkVDd1ZUbjZCOUVYWk9NQjhHQTFVZEl3UVkKTUJhQUZOaE1CRzhxNmEraUsybkVDd1ZUbjZCOUVYWk9NQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdEUVlKS29aSQpodmNOQVFFTEJRQURnZ0VCQUptdWR2eDgrcDVpSVVzVDhmbS9mYlZNMERBNnFXQUxEWVVKblRuM2o2THE0dnBmClBGQytxMUxtdVdmQlFNeXFLckhyUDNlNDkzRWN0WG9pU0taTzZpTjVkVkpJdXIwMk9qR3VpQUVjc1l1WTFuTG4KczlwaWlJK1VFd3hINnV4MU5hSFVuenNXYXVvQnZSaHpqWHZPNlNBVlNaSllhOWRZNW1pelhrbER5RE51RzVVMApsWHY5ZWdNR0JzeTBkRzZlRlhrVTVDUGR4V1U1NDB5STJzQ3RTQWo3eitXUlVENWs3Z0o3dFZvWTMvL2pIUVpHCjVTVFRtbTV0OWtwSVpUV2twdHlKb3M5b1pKRllNSVhxVzJGYzZ0eUxacFJwMzFSNzh0RHM2RVRJa1RvRGMwUlIKano2NnRoNkhJK1psZ0lCUWh3MDkraFlBaEJEZTkrRG1kL1N6UVpjPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLQpNSUlFdndJQkFEQU5CZ2txaGtpRzl3MEJBUUVGQUFTQ0JLa3dnZ1NsQWdFQUFvSUJBUURFaXI2em1vV3J3bDU3CjBoZWRkOVlhYU9wT3VRdWU0NEVVUE9CV2c0K1RHeVRsWldFRlJiVEJ0RnpwTzJ2b1llYjlYM3VNQjlFZXlPaFEKNk5GZmJFdnJMSktPVzdCSlhOY1dKWU9SMjdpcXZQYjBVaW9PaEE1YzdjZHdTbTR2ZGRqNXJlYnhSalNkZXMzTQpPK3cxSzBDRjZPQ1AyK0d0NlpnWWxvK056YUV5SzVrbGg3YkJzTURvOTRnNzBzSHFuV0Z5U0s5NGFSeCtidXFhCklZdzZFTDZnOHl1akx2S2Vwb1FsQ2piT3loU1p6U0p6UnR1YUVFK2p0SitEaHJab0VZeTFxS1hSSnpvVEpDckgKeUtDTlY3WlZ3U3dRNys0YmlBcE43ZWNzTFh3a01qdFBaRis1UlNJSGVhNSsvakk4QitWalhxbTBDY2JtcW82RgowSnpXVklGTEFnTUJBQUVDZ2dFQkFLY2VpdFZST1FRNWUvbXhSUjlDZksxc05IL0gzTmUzLzFQa0I2WElyRmFiCnFCM2V2RWF0Wk91b243QTZOS0VlVGpsMzdTZStwZFNWWk9VWGNxQy9CemJyYVpyZTMrRWhya3BJajcyQXBWK1kKMml3WmlXVmFhSlFnSTR1WjNtTkF3OFJhV0pzajVTMWE5SThMRE9pUTVJWjQ1Q212QUJEUEplTVNjdkpTdlJSWQplNU4wTDZzdHFTN1orSW95R1ZLVWZwMWlOTzBZeXl3T1VpU2tJUlhnc2N1UlhaR1lwaUdQb21zSitKczFlanpXCmp5U3RsWkpFcjRMMTI4NXJHUHJtSHFqVHdGZCtoRzgwV2M0MTc5eEwrV1JFNkhCRVVaU2l5OTVmZTZrY1BIWFgKQmdpVll0Y0ZLbWlCaTJkVGJ4bDRlOTR1dDIzOWkwSHRsSjFaSnRMaCtCRUNnWUVBOGEyOThLMnpYa0hvc3hoTgp0UnJIN1hmTVBUa0hERGQzcnhNMjFMVCtmSVhxaW5HVXA5TFlhRGNianVUUHM4ZTMzdUtNZDdSNlE0MHg4OXlXCklYTmthL1ZMMFBYVWVWNjdhQ1ZMTHFnWERMR3VkbHVKaW5IMFh2bUkwQ21CZWNGU01xSUZtUWxncUVSb0dHczMKVU1hYzBwODc2VDRYa0dRUUpkZjYyYkZwRTRVQ2dZRUEwREJIMFBEbE9wd1hjY0RnWHJNZmF5cjhIQWhJK0c1Ugp5V1EvLzlpaXJ0VTgzY2h3SVd3a2g1M2VMTE16TGdkcUpuUGlXeWFVVzVCcXptWXVEMjNuaHhjUTdQTmRJcU9PCkgxc0U2enFMTnNodjQ2dDVRS2xoMVE0cWpkN1VxdGdyU3JZNjNSWEpDTVdUd25OTWVETHRqOGdhS2Jqa3JHM1IKQk0yaWxkdDZVbzhDZ1lCWDdORFVsaTFTbG9IMVhsc3ZEMDQ3UzhGSGFNN3lsOTk0RjNKMFVtRGZwc3pjajFQNAo5cEY2NE1tcTQvM1l0MGxpMG1NdVRiL0pnYjN4cllnRkpYa2NlY0thaEVWSDNyb3B1cCt1bXNMQUFJaXJVTVFxClZTa0Z3SjBRdG5qL2RlRFV3UE51YU9YOGNkNjVPNUNGVjZ6SVI5eEJFREQ4ZkJzUDJaTE96bWVmRFFLQmdRREYKbTI0dlZ0aGQvMWNKZENnRCswVnhOWVhESGVJVlhMRm8xUzBpTFlDTkxuM3RqWmxSUUJLVVh6WkplM2F5MC9yZgpzTk5EN2FTWUhNWWtUenlkREpiYzFQb056eG15RFVpVFhwT1dxeVVFeE0vZmJCMVZVUEU1aDQ3QXhxZFoyb0dOCkV0ZGdqcE1aTG1DSUMyU2tHc0wrM05Kb2s4VUtIZHB1RXJtbVFJTWs1UUtCZ1FDZ0VXY1l0TFhDM1lEWU1GZEkKVWdjVGViRnFTczNtTFlndWIxeGVrVzNJWFIyeW9tNFY1ZlFUTGlGN1lmbjJkcERXNEljTVUwVUpGWVZzVWxoSwphR3RldDRWbTVObjgrTWdob3Q1eUFqcU85eUFVYXViN3dnaWZLSWU5OXRRS2Q4dVp5Q3ZKMGhodm1ERFNmeDRtCkIvVEVpRkFPOTl5RjQ5aVN4RVZTQVM2cHFRPT0KLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo= + diff --git a/examples/static/config.yml b/examples/static/config.yml index 2e75011..3ededd6 100644 --- a/examples/static/config.yml +++ b/examples/static/config.yml @@ -14,4 +14,6 @@ easymapping: - port: 443 ssl_cert: /etc/certs/host1.local.pem hosts: - host1.local: container:8080 + host1.local: + containers: + - container:8080 diff --git a/templates/bind.j2 b/templates/bind.j2 index 6cad5ff..334f72c 100644 --- a/templates/bind.j2 +++ b/templates/bind.j2 @@ -1,5 +1,5 @@ {% if "ssl_cert" in o %} - bind *:{{ o["port"] }} ssl crt {{ o["ssl_cert"] }} + bind *:{{ o["port"] }} ssl crt /etc/haproxy/certs/discover/ alpn http/1.1 crt /etc/haproxy/certs/ alpn http/1.1 {% elif "h2" in o and o["h2"] %} bind *:{{ o["port"] }} proto h2 option http-use-htx diff --git a/templates/frontend-mode-http.j2 b/templates/frontend-mode-http.j2 index 5d903e6..738761a 100644 --- a/templates/frontend-mode-http.j2 +++ b/templates/frontend-mode-http.j2 @@ -2,10 +2,16 @@ {% for k in o["redirect"] %} redirect prefix {{ o["redirect"][k] }} code 301 if { hdr(host) -i {{ k }} } {% endfor %} - {% for k in o["hosts"] %} - {% set host = k.replace(".", "_") + "_{0}_{1}".format(o["port"], salt) %} + + {%- for k in o["hosts"] %} + {% set host = k.replace(".", "_") + "_{0}".format(o["port"]) %} + {% set letsencrypt = o["hosts"][k]["letsencrypt"] %} acl is_rule_{{ host }}_1 hdr(host) -i {{ k }} acl is_rule_{{ host }}_2 hdr(host) -i {{ k }}:{{ o["port"] }} + {% if letsencrypt %} + 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 + {% endif %} use_backend srv_{{ host }} if is_rule_{{ host }}_1 OR is_rule_{{ host }}_2 {% endfor %} diff --git a/templates/frontend-mode-tcp.j2 b/templates/frontend-mode-tcp.j2 index 012d865..f08a403 100644 --- a/templates/frontend-mode-tcp.j2 +++ b/templates/frontend-mode-tcp.j2 @@ -2,5 +2,5 @@ option tcplog log global {% set backend = (o["hosts"]|first) %} - default_backend srv_{{ backend.replace(".", "_") + "_{0}_{1}".format(o["port"], salt) }} + default_backend srv_{{ backend.replace(".", "_") + "_{0}".format(o["port"]) }} diff --git a/templates/haproxy.cfg.j2 b/templates/haproxy.cfg.j2 index 2d70d04..97c9892 100644 --- a/templates/haproxy.cfg.j2 +++ b/templates/haproxy.cfg.j2 @@ -3,6 +3,17 @@ global 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 @@ -39,9 +50,8 @@ backend srv_stats {% endif %} {% for o in data["easymapping"] -%} {% set mode = o["mode"] or "http" %} - {% set salt = loop.index %} -frontend {{ mode }}_in_{{ o["port"] }}_{{ salt }} +frontend {{ mode }}_in_{{ o["port"] }} {% include "bind.j2" %} {% if mode == "http" %} {% include "frontend-mode-http.j2" %} @@ -50,7 +60,7 @@ frontend {{ mode }}_in_{{ o["port"] }}_{{ salt }} {% endif %} {% for k in o["hosts"] -%} - {% set host = k.replace(".", "_") + "_{0}_{1}".format(o["port"], salt) %} + {% set host = k.replace(".", "_") + "_{0}".format(o["port"]) %} backend srv_{{ host }} balance roundrobin mode {{ mode }} @@ -62,8 +72,12 @@ backend srv_{{ host }} option tcp-check tcp-check connect{{ " ssl" if o["health-check"] == "ssl" }} {% endif %} - {% for c in o["hosts"][k] %} + {% for c in o["hosts"][k]["containers"] %} server srv-{{ loop.index0 }} {{ c }} check weight 1{{ " verify none" if o["health-check"] == "ssl" }} {% endfor %} {% endfor %} {% endfor %} + +backend letsencrypt_backend + mode http + server certbot 127.0.0.1:2080 \ No newline at end of file diff --git a/tests/expected/no-services.txt b/tests/expected/no-services.txt index f5ecac3..3a3d948 100644 --- a/tests/expected/no-services.txt +++ b/tests/expected/no-services.txt @@ -3,6 +3,17 @@ global 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 @@ -10,3 +21,7 @@ defaults timeout client 10s timeout server 10m + +backend letsencrypt_backend + mode http + server certbot 127.0.0.1:2080 \ No newline at end of file diff --git a/tests/expected/services-multi-containers.txt b/tests/expected/services-multi-containers.txt index 3afa9ef..d47459e 100644 --- a/tests/expected/services-multi-containers.txt +++ b/tests/expected/services-multi-containers.txt @@ -3,6 +3,17 @@ global 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 @@ -11,15 +22,15 @@ defaults timeout server 10m -frontend http_in_19901_1 +frontend http_in_19901 bind *:19901 mode http - acl is_rule_www_helloworld_com_19901_1_1 hdr(host) -i www.helloworld.com - acl is_rule_www_helloworld_com_19901_1_2 hdr(host) -i www.helloworld.com:19901 - use_backend srv_www_helloworld_com_19901_1 if is_rule_www_helloworld_com_19901_1_1 OR is_rule_www_helloworld_com_19901_1_2 + acl is_rule_www_helloworld_com_19901_1 hdr(host) -i www.helloworld.com + acl is_rule_www_helloworld_com_19901_2 hdr(host) -i www.helloworld.com:19901 + use_backend srv_www_helloworld_com_19901 if is_rule_www_helloworld_com_19901_1 OR is_rule_www_helloworld_com_19901_2 -backend srv_www_helloworld_com_19901_1 +backend srv_www_helloworld_com_19901 balance roundrobin mode http option forwardfor @@ -27,3 +38,7 @@ backend srv_www_helloworld_com_19901_1 http-request add-header X-Forwarded-Proto https if { ssl_fc } server srv-0 test_nginx.2.t5r94mjlced7m3t5orfjbowmm:80 check weight 1 server srv-1 test_nginx.1.p552hqxkdx88narjrp5kouwb2:80 check weight 1 + +backend letsencrypt_backend + mode http + server certbot 127.0.0.1:2080 \ No newline at end of file diff --git a/tests/expected/services-multiple-hosts.txt b/tests/expected/services-multiple-hosts.txt new file mode 100644 index 0000000..4459421 --- /dev/null +++ b/tests/expected/services-multiple-hosts.txt @@ -0,0 +1,80 @@ +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 *:1937 + mode http + stats enable + stats hide-version + stats realm Haproxy\ Statistics + stats uri / + stats auth joe:s3cr3t +# 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:1937 + +frontend http_in_19901 + bind *:19901 + mode http + redirect prefix www.google.com code 301 if { hdr(host) -i google.helloworld.com } + + acl is_rule_hello_com_19901_1 hdr(host) -i hello.com + acl is_rule_hello_com_19901_2 hdr(host) -i hello.com:19901 + use_backend srv_hello_com_19901 if is_rule_hello_com_19901_1 OR is_rule_hello_com_19901_2 + + acl is_rule_www_helloworld_com_19901_1 hdr(host) -i www.helloworld.com + acl is_rule_www_helloworld_com_19901_2 hdr(host) -i www.helloworld.com:19901 + use_backend srv_www_helloworld_com_19901 if is_rule_www_helloworld_com_19901_1 OR is_rule_www_helloworld_com_19901_2 + +backend srv_hello_com_19901 + 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 3e63154954b0:80 check weight 1 + server srv-1 eb294c110eb1:80 check weight 1 +backend srv_www_helloworld_com_19901 + 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 3e63154954b0:80 check weight 1 + server srv-1 eb294c110eb1:80 check weight 1 + +backend letsencrypt_backend + mode http + server certbot 127.0.0.1:2080 \ No newline at end of file diff --git a/tests/expected/services-tcp.txt b/tests/expected/services-tcp.txt index bc1fcc2..5804dac 100644 --- a/tests/expected/services-tcp.txt +++ b/tests/expected/services-tcp.txt @@ -3,6 +3,17 @@ global 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 @@ -11,16 +22,20 @@ defaults timeout server 10m -frontend tcp_in_31339_1 +frontend tcp_in_31339 bind *:31339 mode tcp option tcplog log global - default_backend srv_agent_quantum_local_31339_1 + default_backend srv_agent_quantum_local_31339 -backend srv_agent_quantum_local_31339_1 +backend srv_agent_quantum_local_31339 balance roundrobin mode tcp option tcp-check tcp-check connect ssl server srv-0 test_agent:9001 check weight 1 verify none + +backend letsencrypt_backend + mode http + server certbot 127.0.0.1:2080 \ No newline at end of file diff --git a/tests/expected/services.txt b/tests/expected/services.txt index 9ae2b3e..bea8444 100644 --- a/tests/expected/services.txt +++ b/tests/expected/services.txt @@ -3,6 +3,17 @@ global 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 @@ -11,40 +22,42 @@ defaults timeout server 10m -frontend tcp_in_31339_1 +frontend tcp_in_31339 bind *:31339 mode tcp option tcplog log global - default_backend srv_agent_quantum_example_org_31339_1 + default_backend srv_agent_quantum_example_org_31339 -backend srv_agent_quantum_example_org_31339_1 +backend srv_agent_quantum_example_org_31339 balance roundrobin mode tcp option tcp-check tcp-check connect server srv-0 my-stack_agent:9001 check weight 1 -frontend http_in_31337_2 +frontend http_in_31337 bind *:31337 mode http - acl is_rule_cadvisor_quantum_example_org_31337_2_1 hdr(host) -i cadvisor.quantum.example.org - acl is_rule_cadvisor_quantum_example_org_31337_2_2 hdr(host) -i cadvisor.quantum.example.org:31337 - use_backend srv_cadvisor_quantum_example_org_31337_2 if is_rule_cadvisor_quantum_example_org_31337_2_1 OR is_rule_cadvisor_quantum_example_org_31337_2_2 + acl is_rule_cadvisor_quantum_example_org_31337_1 hdr(host) -i cadvisor.quantum.example.org + acl is_rule_cadvisor_quantum_example_org_31337_2 hdr(host) -i cadvisor.quantum.example.org:31337 + use_backend srv_cadvisor_quantum_example_org_31337 if is_rule_cadvisor_quantum_example_org_31337_1 OR is_rule_cadvisor_quantum_example_org_31337_2 - acl is_rule_node-exporter_quantum_example_org_31337_2_1 hdr(host) -i node-exporter.quantum.example.org - acl is_rule_node-exporter_quantum_example_org_31337_2_2 hdr(host) -i node-exporter.quantum.example.org:31337 - use_backend srv_node-exporter_quantum_example_org_31337_2 if is_rule_node-exporter_quantum_example_org_31337_2_1 OR is_rule_node-exporter_quantum_example_org_31337_2_2 + acl is_rule_node-exporter_quantum_example_org_31337_1 hdr(host) -i node-exporter.quantum.example.org + acl is_rule_node-exporter_quantum_example_org_31337_2 hdr(host) -i node-exporter.quantum.example.org:31337 + acl is_letsencrypt_node-exporter_quantum_example_org_31337 path_beg /.well-known/acme-challenge/ + use_backend letsencrypt_backend if is_letsencrypt_node-exporter_quantum_example_org_31337 is_rule_node-exporter_quantum_example_org_31337_1 OR is_letsencrypt_node-exporter_quantum_example_org_31337 is_rule_node-exporter_quantum_example_org_31337_2 + use_backend srv_node-exporter_quantum_example_org_31337 if is_rule_node-exporter_quantum_example_org_31337_1 OR is_rule_node-exporter_quantum_example_org_31337_2 -backend srv_cadvisor_quantum_example_org_31337_2 +backend srv_cadvisor_quantum_example_org_31337 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 my-stack_cadvisor:8080 check weight 1 -backend srv_node-exporter_quantum_example_org_31337_2 +backend srv_node-exporter_quantum_example_org_31337 balance roundrobin mode http option forwardfor @@ -52,7 +65,39 @@ backend srv_node-exporter_quantum_example_org_31337_2 http-request add-header X-Forwarded-Proto https if { ssl_fc } server srv-0 my-stack_node-exporter:9100 check weight 1 -frontend http_in_80_3 +frontend http_in_443 + bind *:443 ssl crt /etc/haproxy/certs/discover/ alpn http/1.1 crt /etc/haproxy/certs/ alpn http/1.1 + 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 } + redirect prefix https://www.somehost.com.br code 301 if { hdr(host) -i www.somehost.com } + redirect prefix https://www.somehost.com.br code 301 if { hdr(host) -i byjg.ca } + redirect prefix https://www.somehost.com.br code 301 if { hdr(host) -i www.byjg.ca } + + acl is_rule_node-exporter_quantum_example_org_443_1 hdr(host) -i node-exporter.quantum.example.org + acl is_rule_node-exporter_quantum_example_org_443_2 hdr(host) -i node-exporter.quantum.example.org:443 + use_backend srv_node-exporter_quantum_example_org_443 if is_rule_node-exporter_quantum_example_org_443_1 OR is_rule_node-exporter_quantum_example_org_443_2 + + acl is_rule_www_somehost_com_br_443_1 hdr(host) -i www.somehost.com.br + acl is_rule_www_somehost_com_br_443_2 hdr(host) -i www.somehost.com.br:443 + use_backend srv_www_somehost_com_br_443 if is_rule_www_somehost_com_br_443_1 OR is_rule_www_somehost_com_br_443_2 + +backend srv_node-exporter_quantum_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 my-stack_node-exporter:9100 check weight 1 +backend srv_www_somehost_com_br_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 some-service:80 check weight 1 + +frontend http_in_80 bind *:80 mode http redirect prefix https://www.somehost.com.br code 301 if { hdr(host) -i somehost.com.br } @@ -61,11 +106,11 @@ frontend http_in_80_3 redirect prefix https://www.somehost.com.br code 301 if { hdr(host) -i byjg.ca } redirect prefix https://www.somehost.com.br code 301 if { hdr(host) -i www.byjg.ca } - acl is_rule_www_somehost_com_br_80_3_1 hdr(host) -i www.somehost.com.br - acl is_rule_www_somehost_com_br_80_3_2 hdr(host) -i www.somehost.com.br:80 - use_backend srv_www_somehost_com_br_80_3 if is_rule_www_somehost_com_br_80_3_1 OR is_rule_www_somehost_com_br_80_3_2 + acl is_rule_www_somehost_com_br_80_1 hdr(host) -i www.somehost.com.br + acl is_rule_www_somehost_com_br_80_2 hdr(host) -i www.somehost.com.br:80 + use_backend srv_www_somehost_com_br_80 if is_rule_www_somehost_com_br_80_1 OR is_rule_www_somehost_com_br_80_2 -backend srv_www_somehost_com_br_80_3 +backend srv_www_somehost_com_br_80 balance roundrobin mode http option forwardfor @@ -73,23 +118,6 @@ backend srv_www_somehost_com_br_80_3 http-request add-header X-Forwarded-Proto https if { ssl_fc } server srv-0 some-service:80 check weight 1 -frontend http_in_443_4 - bind *:443 ssl crt /tmp/www.somehost.com.br.1.pem +backend letsencrypt_backend 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 } - redirect prefix https://www.somehost.com.br code 301 if { hdr(host) -i www.somehost.com } - redirect prefix https://www.somehost.com.br code 301 if { hdr(host) -i byjg.ca } - redirect prefix https://www.somehost.com.br code 301 if { hdr(host) -i www.byjg.ca } - - acl is_rule_www_somehost_com_br_443_4_1 hdr(host) -i www.somehost.com.br - acl is_rule_www_somehost_com_br_443_4_2 hdr(host) -i www.somehost.com.br:443 - use_backend srv_www_somehost_com_br_443_4 if is_rule_www_somehost_com_br_443_4_1 OR is_rule_www_somehost_com_br_443_4_2 - -backend srv_www_somehost_com_br_443_4 - 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 some-service:80 check weight 1 + server certbot 127.0.0.1:2080 \ No newline at end of file diff --git a/tests/expected/static.txt b/tests/expected/static.txt index e1feb38..9ea729a 100644 --- a/tests/expected/static.txt +++ b/tests/expected/static.txt @@ -3,6 +3,17 @@ global 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 @@ -34,27 +45,29 @@ backend srv_stats mode http server Local 127.0.0.1:1936 -frontend http_in_80_1 +frontend http_in_80 bind *:80 mode http redirect prefix http://host1.com.br code 301 if { hdr(host) -i www.host1.com.br } - acl is_rule_host1_com_br_80_1_1 hdr(host) -i host1.com.br - acl is_rule_host1_com_br_80_1_2 hdr(host) -i host1.com.br:80 - use_backend srv_host1_com_br_80_1 if is_rule_host1_com_br_80_1_1 OR is_rule_host1_com_br_80_1_2 + acl is_rule_host1_com_br_80_1 hdr(host) -i host1.com.br + acl is_rule_host1_com_br_80_2 hdr(host) -i host1.com.br:80 + acl is_letsencrypt_host1_com_br_80 path_beg /.well-known/acme-challenge/ + use_backend letsencrypt_backend if is_letsencrypt_host1_com_br_80 is_rule_host1_com_br_80_1 OR is_letsencrypt_host1_com_br_80 is_rule_host1_com_br_80_2 + use_backend srv_host1_com_br_80 if is_rule_host1_com_br_80_1 OR is_rule_host1_com_br_80_2 - acl is_rule_host2_com_br_80_1_1 hdr(host) -i host2.com.br - acl is_rule_host2_com_br_80_1_2 hdr(host) -i host2.com.br:80 - use_backend srv_host2_com_br_80_1 if is_rule_host2_com_br_80_1_1 OR is_rule_host2_com_br_80_1_2 + acl is_rule_host2_com_br_80_1 hdr(host) -i host2.com.br + acl is_rule_host2_com_br_80_2 hdr(host) -i host2.com.br:80 + use_backend srv_host2_com_br_80 if is_rule_host2_com_br_80_1 OR is_rule_host2_com_br_80_2 -backend srv_host1_com_br_80_1 +backend srv_host1_com_br_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 container:5000 check weight 1 -backend srv_host2_com_br_80_1 +backend srv_host2_com_br_80 balance roundrobin mode http option forwardfor @@ -62,15 +75,15 @@ backend srv_host2_com_br_80_1 http-request add-header X-Forwarded-Proto https if { ssl_fc } server srv-0 other:3000 check weight 1 -frontend http_in_443_2 - bind *:443 ssl crt /etc/haproxy/certs/mycert.pem +frontend http_in_443 + bind *:443 ssl crt /etc/haproxy/certs/discover/ alpn http/1.1 crt /etc/haproxy/certs/ alpn http/1.1 mode http - acl is_rule_host1_com_br_443_2_1 hdr(host) -i host1.com.br - acl is_rule_host1_com_br_443_2_2 hdr(host) -i host1.com.br:443 - use_backend srv_host1_com_br_443_2 if is_rule_host1_com_br_443_2_1 OR is_rule_host1_com_br_443_2_2 + acl is_rule_host1_com_br_443_1 hdr(host) -i host1.com.br + acl is_rule_host1_com_br_443_2 hdr(host) -i host1.com.br:443 + use_backend srv_host1_com_br_443 if is_rule_host1_com_br_443_1 OR is_rule_host1_com_br_443_2 -backend srv_host1_com_br_443_2 +backend srv_host1_com_br_443 balance roundrobin mode http option forwardfor @@ -78,18 +91,22 @@ backend srv_host1_com_br_443_2 http-request add-header X-Forwarded-Proto https if { ssl_fc } server srv-0 container:80 check weight 1 -frontend http_in_8080_3 +frontend http_in_8080 bind *:8080 mode http - acl is_rule_host3_com_br_8080_3_1 hdr(host) -i host3.com.br - acl is_rule_host3_com_br_8080_3_2 hdr(host) -i host3.com.br:8080 - use_backend srv_host3_com_br_8080_3 if is_rule_host3_com_br_8080_3_1 OR is_rule_host3_com_br_8080_3_2 + acl is_rule_host3_com_br_8080_1 hdr(host) -i host3.com.br + acl is_rule_host3_com_br_8080_2 hdr(host) -i host3.com.br:8080 + use_backend srv_host3_com_br_8080 if is_rule_host3_com_br_8080_1 OR is_rule_host3_com_br_8080_2 -backend srv_host3_com_br_8080_3 +backend srv_host3_com_br_8080 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 domain:8181 check weight 1 + +backend letsencrypt_backend + mode http + server certbot 127.0.0.1:2080 \ No newline at end of file diff --git a/tests/fixtures/services b/tests/fixtures/services index 47ed2b0..c77af21 100644 --- a/tests/fixtures/services +++ b/tests/fixtures/services @@ -1,6 +1,6 @@ portainer-agent_agent={"com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"portainer-agent"} my-stack_agent={"easyhaproxy.agent.host":"agent.quantum.example.org","easyhaproxy.agent.localport":"9001","easyhaproxy.agent.mode":"tcp","easyhaproxy.agent.port":"31339","com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"} my-stack_cadvisor={"easyhaproxy.cadvisor.host":"cadvisor.quantum.example.org","easyhaproxy.cadvisor.localport":"8080","easyhaproxy.cadvisor.port":"31337","com.docker.stack.image":"gcr.io/google-containers/cadvisor:v0.34.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"} -my-stack_node-exporter={"easyhaproxy.exp.host":"node-exporter.quantum.example.org","easyhaproxy.exp.localport":"9100","easyhaproxy.exp.port":"31337","com.docker.stack.image":"stefanprodan/swarmprom-node-exporter:v0.16.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"} +my-stack_node-exporter={"easyhaproxy.exp.host":"node-exporter.quantum.example.org","easyhaproxy.exp.localport":"9100","easyhaproxy.exp.port":"31337","com.docker.stack.image":"stefanprodan/swarmprom-node-exporter:v0.16.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring","easyhaproxy.exp.letsencrypt":"true"} my-stack_reverse-proxy={"com.docker.stack.image":"quay.io/pngmbh/easy-haproxy:tcp-mode","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"} -some-service={"easyhaproxy.http.port":"80","easyhaproxy.http.host":"www.somehost.com.br","easyhaproxy.http.localport":"80","easyhaproxy.http.redirect":"somehost.com.br--https://www.somehost.com.br,somehost.com--https://www.somehost.com.br,www.somehost.com--https://www.somehost.com.br,byjg.ca--https://www.somehost.com.br,www.byjg.ca--https://www.somehost.com.br","easyhaproxy.https.port":"443","easyhaproxy.https.host":"www.somehost.com.br","easyhaproxy.https.localport":"80","easyhaproxy.https.redirect":"somehost.com.br--https://www.somehost.com.br,somehost.com--https://www.somehost.com.br,www.somehost.com--https://www.somehost.com.br,byjg.ca--https://www.somehost.com.br,www.byjg.ca--https://www.somehost.com.br","easyhaproxy.https.sslcert":"U29tZSBQRU0gQ2VydGlmaWNhdGU="} \ No newline at end of file +some-service={"easyhaproxy.http.port":"80","easyhaproxy.http.host":"www.somehost.com.br","easyhaproxy.http.localport":"80","easyhaproxy.http.redirect":"{\"somehost.com.br\":\"https://www.somehost.com.br\",\"somehost.com\":\"https://www.somehost.com.br\",\"www.somehost.com\":\"https://www.somehost.com.br\",\"byjg.ca\":\"https://www.somehost.com.br\",\"www.byjg.ca\":\"https://www.somehost.com.br\"}","easyhaproxy.https.port":"443","easyhaproxy.https.host":"www.somehost.com.br","easyhaproxy.https.localport":"80","easyhaproxy.https.redirect":"{\"somehost.com.br\":\"https://www.somehost.com.br\",\"somehost.com\":\"https://www.somehost.com.br\",\"www.somehost.com\":\"https://www.somehost.com.br\",\"byjg.ca\":\"https://www.somehost.com.br\",\"www.byjg.ca\":\"https://www.somehost.com.br\"}","easyhaproxy.https.sslcert":"U29tZSBQRU0gQ2VydGlmaWNhdGU="} \ No newline at end of file diff --git a/tests/fixtures/services-changed-label b/tests/fixtures/services-changed-label index a6d5cc7..f4b0b14 100644 --- a/tests/fixtures/services-changed-label +++ b/tests/fixtures/services-changed-label @@ -1,6 +1,6 @@ portainer-agent_agent={"com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"portainer-agent"} my-stack_agent={"haproxy.agent.host":"agent.quantum.example.org","haproxy.agent.localport":"9001","haproxy.agent.mode":"tcp","haproxy.agent.port":"31339","com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"} my-stack_cadvisor={"haproxy.cadvisor.host":"cadvisor.quantum.example.org","haproxy.cadvisor.localport":"8080","haproxy.cadvisor.port":"31337","com.docker.stack.image":"gcr.io/google-containers/cadvisor:v0.34.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"} -my-stack_node-exporter={"haproxy.exp.host":"node-exporter.quantum.example.org","haproxy.exp.localport":"9100","haproxy.exp.port":"31337","com.docker.stack.image":"stefanprodan/swarmprom-node-exporter:v0.16.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"} +my-stack_node-exporter={"haproxy.exp.host":"node-exporter.quantum.example.org","haproxy.exp.localport":"9100","haproxy.exp.port":"31337","com.docker.stack.image":"stefanprodan/swarmprom-node-exporter:v0.16.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring","haproxy.exp.letsencrypt":"yes"} my-stack_reverse-proxy={"com.docker.stack.image":"quay.io/pngmbh/easy-haproxy:tcp-mode","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"} -some-service={"haproxy.http.port":"80","haproxy.http.host":"www.somehost.com.br","haproxy.http.localport":"80","haproxy.http.redirect":"somehost.com.br--https://www.somehost.com.br,somehost.com--https://www.somehost.com.br,www.somehost.com--https://www.somehost.com.br,byjg.ca--https://www.somehost.com.br,www.byjg.ca--https://www.somehost.com.br","haproxy.https.port":"443","haproxy.https.host":"www.somehost.com.br","haproxy.https.localport":"80","haproxy.https.redirect":"somehost.com.br--https://www.somehost.com.br,somehost.com--https://www.somehost.com.br,www.somehost.com--https://www.somehost.com.br,byjg.ca--https://www.somehost.com.br,www.byjg.ca--https://www.somehost.com.br","haproxy.https.sslcert":"U29tZSBQRU0gQ2VydGlmaWNhdGU="} \ No newline at end of file +some-service={"haproxy.http.port":"80","haproxy.http.host":"www.somehost.com.br","haproxy.http.localport":"80","haproxy.http.redirect":"{\"somehost.com.br\":\"https://www.somehost.com.br\",\"somehost.com\":\"https://www.somehost.com.br\",\"www.somehost.com\":\"https://www.somehost.com.br\",\"byjg.ca\":\"https://www.somehost.com.br\",\"www.byjg.ca\":\"https://www.somehost.com.br\"}","haproxy.https.port":"443","haproxy.https.host":"www.somehost.com.br","haproxy.https.localport":"80","haproxy.https.redirect":"{\"somehost.com.br\":\"https://www.somehost.com.br\",\"somehost.com\":\"https://www.somehost.com.br\",\"www.somehost.com\":\"https://www.somehost.com.br\",\"byjg.ca\":\"https://www.somehost.com.br\",\"www.byjg.ca\":\"https://www.somehost.com.br\"}","haproxy.https.sslcert":"U29tZSBQRU0gQ2VydGlmaWNhdGU="} \ No newline at end of file diff --git a/tests/fixtures/services-multiple-hosts b/tests/fixtures/services-multiple-hosts new file mode 100644 index 0000000..44bfc19 --- /dev/null +++ b/tests/fixtures/services-multiple-hosts @@ -0,0 +1,3 @@ +db79d3a910f4={"com.docker.compose.config-hash":"5bde40f52451521ad201e70de1291397376a0498a7c955624a609da3b60e7e8e","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:ea39067705590557dd0cd951664a10970ceefcb725a3c1f43690d6d6d4ed5fce","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-multi-containers.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"} +3e63154954b0={"com.docker.compose.config-hash":"4e0cbdd8372c6779863799e5021ed8178f74b55bd8e070abcdffaf87eb7baa36","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-multi-containers.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":"hello.com, www.helloworld.com","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"19901","easyhaproxy.http.redirect":"{\"google.helloworld.com\": \"www.google.com\"}"} +eb294c110eb1={"com.docker.compose.config-hash":"4e0cbdd8372c6779863799e5021ed8178f74b55bd8e070abcdffaf87eb7baa36","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-multi-containers.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":"hello.com, www.helloworld.com","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"19901","easyhaproxy.http.redirect":"{\"google.helloworld.com\": \"www.google.com\"}"} \ No newline at end of file diff --git a/tests/fixtures/static.yml b/tests/fixtures/static.yml index 82ea2e2..51498d5 100644 --- a/tests/fixtures/static.yml +++ b/tests/fixtures/static.yml @@ -8,16 +8,24 @@ customerrors: true # Optional (default false) easymapping: - port: 80 hosts: - host1.com.br: container:5000 - host2.com.br: other:3000 + host1.com.br: + containers: + - container:5000 + letsencrypt: true + host2.com.br: + containers: + - other:3000 redirect: www.host1.com.br: http://host1.com.br - port: 443 ssl_cert: /etc/haproxy/certs/mycert.pem hosts: - host1.com.br: container:80 + host1.com.br: + containers: + - container:80 - port: 8080 hosts: - host3.com.br: domain:8181 + host3.com.br: + containers: [ "domain:8181" ] diff --git a/tests/test_parser.py b/tests/test_parser.py index 556d75b..efbb359 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -3,6 +3,9 @@ import pytest import os import yaml +CERTS_FOLDER="/tmp/certs" +CERT_FILE="/tmp/certs/www.somehost.com.br.pem" +LETSENCRYPT_EMAIL="some@email.com" def load_fixture(file): path = os.path.dirname(os.path.realpath(__file__)) @@ -19,14 +22,14 @@ def test_parser_doesnt_crash(): "customerrors": False } - cfg = easymapping.HaproxyConfigGenerator(result, "/tmp") + 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/no-services.txt", 'r') as expected_file: assert expected_file.read() == haproxy_config - + assert [] == cfg.letsencrypt_hosts def test_parser_finds_services(): line_list = load_fixture("services") @@ -35,11 +38,11 @@ def test_parser_finds_services(): "customerrors": False } - cert_file = "/tmp/www.somehost.com.br.1.pem" - if os.path.exists(cert_file): - os.remove(cert_file) + if os.path.exists(CERT_FILE): + os.remove(CERT_FILE) - cfg = easymapping.HaproxyConfigGenerator(result, "/tmp") + cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) + cfg.letsencrypt_email = LETSENCRYPT_EMAIL haproxy_config = cfg.generate(line_list) assert len(haproxy_config) > 0 @@ -47,9 +50,11 @@ def test_parser_finds_services(): with open(path + "/expected/services.txt", 'r') as expected_file: assert expected_file.read() == haproxy_config - with open(cert_file, 'r') as expected_file: + with open(CERT_FILE, 'r') as expected_file: assert expected_file.read() == "Some PEM Certificate" + assert ['node-exporter.quantum.example.org'] == cfg.letsencrypt_hosts + def test_parser_finds_services_changed_label(): line_list = load_fixture("services-changed-label") @@ -58,11 +63,11 @@ def test_parser_finds_services_changed_label(): "lookup_label": "haproxy" } - cert_file = "/tmp/www.somehost.com.br.1.pem" - if os.path.exists(cert_file): - os.remove(cert_file) + if os.path.exists(CERT_FILE): + os.remove(CERT_FILE) - cfg = easymapping.HaproxyConfigGenerator(result, "/tmp") + cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) + cfg.letsencrypt_email = LETSENCRYPT_EMAIL haproxy_config = cfg.generate(line_list) assert len(haproxy_config) > 0 @@ -70,9 +75,11 @@ def test_parser_finds_services_changed_label(): with open(path + "/expected/services.txt", 'r') as expected_file: assert expected_file.read() == haproxy_config - with open(cert_file, 'r') as expected_file: + with open(CERT_FILE, 'r') as expected_file: assert expected_file.read() == "Some PEM Certificate" + assert ['node-exporter.quantum.example.org'] == cfg.letsencrypt_hosts + def test_parser_finds_services_raw(): line_list = load_fixture("services") @@ -80,11 +87,11 @@ def test_parser_finds_services_raw(): "customerrors": False } - cert_file = "/tmp/www.somehost.com.br.1.pem" - if os.path.exists(cert_file): - os.remove(cert_file) + if os.path.exists(CERT_FILE): + os.remove(CERT_FILE) - cfg = easymapping.HaproxyConfigGenerator(result, "/tmp") + cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) + cfg.letsencrypt_email = LETSENCRYPT_EMAIL parsed_object = [ { @@ -92,9 +99,12 @@ def test_parser_finds_services_raw(): "health-check":"", "port":"31339", "hosts":{ - "agent.quantum.example.org":[ - "my-stack_agent:9001" - ] + "agent.quantum.example.org": { + "containers": [ + "my-stack_agent:9001" + ], + "letsencrypt": False + } }, "redirect":{ @@ -105,25 +115,61 @@ def test_parser_finds_services_raw(): "health-check":"", "port":"31337", "hosts":{ - "cadvisor.quantum.example.org":[ - "my-stack_cadvisor:8080" - ], - "node-exporter.quantum.example.org":[ - "my-stack_node-exporter:9100" - ] + "cadvisor.quantum.example.org":{ + "containers": [ + "my-stack_cadvisor:8080" + ], + "letsencrypt": False + }, + "node-exporter.quantum.example.org":{ + "containers": [ + "my-stack_node-exporter:9100" + ], + "letsencrypt": True + } }, "redirect":{ } }, + { + "mode":"http", + "health-check":"", + "port":"443", + "hosts":{ + "node-exporter.quantum.example.org": { + "containers": [ + "my-stack_node-exporter:9100" + ], + "letsencrypt": False + }, + "www.somehost.com.br":{ + "containers": [ + "some-service:80" + ], + "letsencrypt": False + } + }, + "redirect":{ + "somehost.com.br":"https://www.somehost.com.br", + "somehost.com":"https://www.somehost.com.br", + "www.somehost.com":"https://www.somehost.com.br", + "byjg.ca":"https://www.somehost.com.br", + "www.byjg.ca":"https://www.somehost.com.br" + }, + "ssl_cert":CERT_FILE + }, { "mode":"http", "health-check":"", "port":"80", "hosts":{ - "www.somehost.com.br":[ - "some-service:80" - ] + "www.somehost.com.br":{ + "containers": [ + "some-service:80" + ], + "letsencrypt": False + } }, "redirect":{ "somehost.com.br":"https://www.somehost.com.br", @@ -132,30 +178,13 @@ def test_parser_finds_services_raw(): "byjg.ca":"https://www.somehost.com.br", "www.byjg.ca":"https://www.somehost.com.br" } - }, - { - "mode":"http", - "health-check":"", - "port":"443", - "hosts":{ - "www.somehost.com.br":[ - "some-service:80" - ] - }, - "redirect":{ - "somehost.com.br":"https://www.somehost.com.br", - "somehost.com":"https://www.somehost.com.br", - "www.somehost.com":"https://www.somehost.com.br", - "byjg.ca":"https://www.somehost.com.br", - "www.byjg.ca":"https://www.somehost.com.br" - }, - "ssl_cert":"/tmp/www.somehost.com.br.1.pem" } ] processed = list(cfg.parse(line_list)) assert parsed_object == processed + assert ['node-exporter.quantum.example.org'] == cfg.letsencrypt_hosts @@ -164,12 +193,72 @@ def test_parser_static(): with open(path + "/fixtures/static.yml", 'r') as content_file: parsed = yaml.load(content_file.read(), Loader=yaml.FullLoader) - cfg = easymapping.HaproxyConfigGenerator(parsed, "/tmp") + cfg = easymapping.HaproxyConfigGenerator(parsed, CERTS_FOLDER) haproxy_config = cfg.generate() assert len(haproxy_config) > 0 with open(path + "/expected/static.txt", 'r') as expected_file: assert expected_file.read() == haproxy_config + assert [] == cfg.letsencrypt_hosts + +def test_parser_static_raw(): + path = os.path.dirname(os.path.realpath(__file__)) + with open(path + "/fixtures/static.yml", 'r') as content_file: + parsed = yaml.load(content_file.read(), Loader=yaml.FullLoader) + + expected = { + "stats": { + "username": "admin", + "password": "test123", + "port": 1936 + }, + "customerrors": True, + "easymapping": [ + { + "port": 80, + "hosts": { + "host1.com.br": { + "containers": [ + "container:5000" + ], + "letsencrypt": True + }, + "host2.com.br": { + "containers": [ + "other:3000" + ] + } + }, + "redirect": { + "www.host1.com.br": "http://host1.com.br" + } + }, + { + "port": 443, + "ssl_cert": "/etc/haproxy/certs/mycert.pem", + "hosts": { + "host1.com.br": { + "containers": [ + "container:80" + ] + } + } + }, + { + "port": 8080, + "hosts": { + "host3.com.br": { + "containers": [ + "domain:8181" + ] + } + } + } + ] + } + + assert expected == parsed + def test_parser_tcp(): @@ -179,7 +268,7 @@ def test_parser_tcp(): "customerrors": False } - cfg = easymapping.HaproxyConfigGenerator(result, "/tmp") + cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) haproxy_config = cfg.generate(line_list) # print(haproxy_config) @@ -187,6 +276,7 @@ def test_parser_tcp(): path = os.path.dirname(os.path.realpath(__file__)) with open(path + "/expected/services-tcp.txt", 'r') as expected_file: assert expected_file.read() == haproxy_config + assert [] == cfg.letsencrypt_hosts def test_parser_multi_containers(): line_list = load_fixture("services-multi-containers") @@ -195,14 +285,38 @@ def test_parser_multi_containers(): "customerrors": False } - cfg = easymapping.HaproxyConfigGenerator(result, "/tmp") + 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-multi-containers.txt", 'r') as expected_file: assert expected_file.read() == haproxy_config + assert [] == cfg.letsencrypt_hosts + + +def test_parser_multiple_hosts(): + line_list = load_fixture("services-multiple-hosts") + + result = { + "customerrors": True, + "stats": { + "username": "joe", + "password": "s3cr3t", + "port": "1937" + } + } + + 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-multiple-hosts.txt", 'r') as expected_file: + assert expected_file.read() == haproxy_config + assert [] == cfg.letsencrypt_hosts #test_parser_finds_services_raw() #test_parser_tcp() +#test_parser_multiple_hosts()