From 22cac26555894a28b9764569d1f6b87d89342d09 Mon Sep 17 00:00:00 2001 From: Joao M Date: Mon, 15 Aug 2022 14:27:49 +0000 Subject: [PATCH] Some controls improvements for certbot. --- README.md | 51 ++++++++++++++++++++++----- assets/scripts/certbot.sh | 3 +- assets/scripts/certbot_to_haproxy.sh | 1 + assets/scripts/exit-event-listener.py | 2 +- easymapping/__init__.py | 3 +- tests/test_parser.py | 41 +++++++++++---------- 6 files changed, 70 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 266ea04..e051e25 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): @@ -202,6 +203,38 @@ 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 +- 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. + + ## 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/scripts/certbot.sh b/assets/scripts/certbot.sh index e8432ea..eee72c7 100755 --- a/assets/scripts/certbot.sh +++ b/assets/scripts/certbot.sh @@ -35,11 +35,12 @@ if [ -n "$REQUEST_CERTS" ]; then --non-interactive \ --max-log-backups=0 \ --post-hook "/scripts/certbot_to_haproxy.sh" \ - $REQUEST_CERTS --email info@xpto.us + $REQUEST_CERTS --email $EASYHAPROXY_LETSENCRYPT_EMAIL fi if [ -n "$RENEW_CERTS" ]; then certbot renew --post-hook "/scripts/certbot_to_haproxy.sh" fi +# Release semaphore rm /tmp/certbot-lock \ No newline at end of file diff --git a/assets/scripts/certbot_to_haproxy.sh b/assets/scripts/certbot_to_haproxy.sh index f3735da..f7833f6 100755 --- a/assets/scripts/certbot_to_haproxy.sh +++ b/assets/scripts/certbot_to_haproxy.sh @@ -8,4 +8,5 @@ for CERTIFICATE in `find /etc/letsencrypt/live/* -type d`; do 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/easymapping/__init__.py b/easymapping/__init__.py index e3d434a..f3bca0a 100644 --- a/easymapping/__init__.py +++ b/easymapping/__init__.py @@ -47,6 +47,7 @@ class HaproxyConfigGenerator: self.label = DockerLabelHandler(mapping['lookup_label'] if 'lookup_label' in mapping else "easyhaproxy") self.ssl_cert_folder = ssl_cert_folder self.letsencrypt_hosts = [] + self.letsencrypt_email = os.getenv("EASYHAPROXY_LETSENCRYPT_EMAIL", "") os.makedirs(self.ssl_cert_folder, exist_ok=True) @@ -108,7 +109,7 @@ class HaproxyConfigGenerator: letsencrypt = self.label.get_bool( self.label.create([definition, "letsencrypt"]), False - ) + ) and self.letsencrypt_email != "" self.letsencrypt_hosts.append(d[host_label]) if letsencrypt and d[host_label] not in self.letsencrypt_hosts else self.letsencrypt_hosts if port not in easymapping: diff --git a/tests/test_parser.py b/tests/test_parser.py index 52043e3..ccb9b05 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,7 +22,7 @@ def test_parser_doesnt_crash(): "customerrors": False } - cfg = easymapping.HaproxyConfigGenerator(result, "/tmp/certs") + cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) haproxy_config = cfg.generate(line_list) assert len(haproxy_config) > 0 @@ -35,11 +38,11 @@ def test_parser_finds_services(): "customerrors": False } - cert_file = "/tmp/certs/www.somehost.com.br.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/certs") + cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) + cfg.letsencrypt_email = LETSENCRYPT_EMAIL haproxy_config = cfg.generate(line_list) assert len(haproxy_config) > 0 @@ -47,7 +50,7 @@ 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 @@ -60,11 +63,11 @@ def test_parser_finds_services_changed_label(): "lookup_label": "haproxy" } - cert_file = "/tmp/certs/www.somehost.com.br.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/certs") + cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) + cfg.letsencrypt_email = LETSENCRYPT_EMAIL haproxy_config = cfg.generate(line_list) assert len(haproxy_config) > 0 @@ -72,7 +75,7 @@ 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 @@ -84,11 +87,11 @@ def test_parser_finds_services_raw(): "customerrors": False } - cert_file = "/tmp/certs/www.somehost.com.br.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/certs") + cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) + cfg.letsencrypt_email = LETSENCRYPT_EMAIL parsed_object = [ { @@ -154,7 +157,7 @@ def test_parser_finds_services_raw(): "byjg.ca":"https://www.somehost.com.br", "www.byjg.ca":"https://www.somehost.com.br" }, - "ssl_cert":"/tmp/certs/www.somehost.com.br.pem" + "ssl_cert":CERT_FILE }, { "mode":"http", @@ -190,7 +193,7 @@ 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/certs") + cfg = easymapping.HaproxyConfigGenerator(parsed, CERTS_FOLDER) haproxy_config = cfg.generate() assert len(haproxy_config) > 0 @@ -265,7 +268,7 @@ def test_parser_tcp(): "customerrors": False } - cfg = easymapping.HaproxyConfigGenerator(result, "/tmp/certs") + cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) haproxy_config = cfg.generate(line_list) # print(haproxy_config) @@ -282,7 +285,7 @@ def test_parser_multi_containers(): "customerrors": False } - cfg = easymapping.HaproxyConfigGenerator(result, "/tmp/certs") + cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) haproxy_config = cfg.generate(line_list) assert len(haproxy_config) > 0