diff --git a/build/Dockerfile b/build/Dockerfile index 334f3cb..9c03c36 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -1,20 +1,24 @@ -FROM alpine:3.18 +FROM alpine:3.20 ARG RELEASE_VERSION_ARG ENV RELEASE_VERSION=$RELEASE_VERSION_ARG ENV TZ="Etc/UTC" +RUN apk add --no-cache haproxy bash python3 py3-pip py-yaml certbot openssl \ + && pip3 install --upgrade pip --break-system-packages + +RUN openssl dhparam -out /etc/haproxy/dhparam 2048 \ + && openssl dhparam -out /etc/haproxy/dhparam-1024 1024 + WORKDIR /scripts -COPY src/ /scripts/ COPY build/assets / -RUN apk add --no-cache haproxy bash python3 py3-pip py-yaml certbot openssl \ - && pip3 install --upgrade pip \ - && pip install -r requirements.txt \ - && pytest -s -vv tests/ \ - && openssl dhparam -out /etc/haproxy/dhparam 2048 \ - && openssl dhparam -out /etc/haproxy/dhparam-1024 1024 +COPY src/ /scripts/ + +RUN pip install -r requirements.txt --break-system-packages + +RUN pytest -s -vv tests/ CMD ["/usr/bin/python", "-u", "/scripts/main.py" ] diff --git a/docs/acme.md b/docs/acme.md index f695726..2de2e26 100644 --- a/docs/acme.md +++ b/docs/acme.md @@ -8,21 +8,25 @@ Most of the issuers offers Automatic Issuing free of cost. ## Environment Variables -To enable the ACME protocol we need to enable Certbot in EasyHAProxy by setting up to 4 environment variables: +To enable the ACME protocol we need to enable Certbot in EasyHAProxy by setting up to the following environment variables: -| Environment Variable | Required? | Description | -|----------------------------------|-----------|----------------------------------------------------------------------------------------------------------------------------------| -| EASYHAPROXY_CERTBOT_EMAIL | YES | Your email in the certificate authority. | -| EASYHAPROXY_CERTBOT_AUTOCONFIG | - | Will use pre-sets for your Certificate Authority (CA). See table below. | -| EASYHAPROXY_CERTBOT_SERVER | - | The ACME Endpoint of your certificate authority. If you use AUTOCONFIG, it is set automatically. See table below. | -| EASYHAPROXY_CERTBOT_EAB_KID | - | External Account Binding (EAB) Key Identifier (KID) provided by your certificate authority. Some CA require it. See table below. | -| EASYHAPROXY_CERTBOT_EAB_HMAC_KEY | - | External Account Binding (EAB) HMAC Key provided by your certificate authority. Some CA require it. See table below. | -| EASYHAPROXY_CERTBOT_RETRY_COUNT | - | Wait 'n' requests before retrying issue invalid requests. Default 60. | +| Environment Variable | Required? | Description | +|------------------------------------------|-----------|----------------------------------------------------------------------------------------------------------------------------------| +| EASYHAPROXY_CERTBOT_EMAIL | YES | Your email in the certificate authority. | +| EASYHAPROXY_CERTBOT_AUTOCONFIG | - | Will use pre-sets for your Certificate Authority (CA). See table below. | +| EASYHAPROXY_CERTBOT_SERVER | - | The ACME Endpoint of your certificate authority. If you use AUTOCONFIG, it is set automatically. See table below. | +| EASYHAPROXY_CERTBOT_EAB_KID | - | External Account Binding (EAB) Key Identifier (KID) provided by your certificate authority. Some CA require it. See table below. | +| EASYHAPROXY_CERTBOT_EAB_HMAC_KEY | - | External Account Binding (EAB) HMAC Key provided by your certificate authority. Some CA require it. See table below. | +| EASYHAPROXY_CERTBOT_RETRY_COUNT | - | Wait 'n' requests before retrying issue invalid requests. Default 60. | +| EASYHAPROXY_CERTBOT_PREFERRED_CHALLENGES | - | The preferred challenges for Certbot. Available: `http` | +| EASYHAPROXY_CERTBOT_MANUAL_AUTH_HOOK | - | The path to a script that will be executed (default: None) | ## Auto Config Certificate Authority (CA) Here are detailed instructions per Certificate Authority (CA). If anyone is missing, please let's know. +Possible values for: `EASYHAPROXY_CERTBOT_AUTOCONFIG` + | CA | Auto Config | Free? | Account Required? | EAB KID? | EAB HMAC Key? | More Info | |----------------------|------------------|-------|--------------------|----------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Let's Encrypt | - | Yes | No | No | No | - | diff --git a/docs/environment-variable.md b/docs/environment-variable.md index b00bacf..661438d 100644 --- a/docs/environment-variable.md +++ b/docs/environment-variable.md @@ -1,19 +1,19 @@ # Docker environment variables -| Environment Variable | Description | Default | -|----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------| -| EASYHAPROXY_DISCOVER | How the services will be discovered to create `haproxy.cfg`: `static`, `docker`, `swarm` or `kubernetes` | **required** | -| EASYHAPROXY_LABEL_PREFIX | (Optional) The key will search for matching resources. | `easyhaproxy` | -| EASYHAPROXY_CERTBOT_* | (Optional) Enable Let's Encrypt or any other ACME certificate. See more: [acme](acme.md) | *empty* | -| EASYHAPROXY_SSL_MODE | (Optional) `strict` supports only the most recent TLS version; `default` good SSL integration with recent browsers; `loose` supports all old SSL protocols for old browsers (not recommended). | `default` | -| EASYHAPROXY_REFRESH_CONF | (Optional) Check for new containers/services every N seconds. | 10 | -| EASYHAPROXY_LOG_LEVEL | (Optional) The log level for EasyHAproxy messages. Available: TRACE,DEBUG,INFO,WARN,ERROR,FATAL | DEBUG | -| CERTBOT_LOG_LEVEL | (Optional) The log level for Certbot messages. Available: TRACE,DEBUG,INFO,WARN,ERROR,FATAL | DEBUG | -| HAPROXY_LOG_LEVEL | (Optional) The log level for HAProxy messages. Available: TRACE,DEBUG,INFO,WARN,ERROR,FATAL | DEBUG | -| HAPROXY_USERNAME | (Optional) The HAProxy username to the statistics. | `admin` | -| HAPROXY_PASSWORD | (Optional) The HAProxy password to the statistics. If not set, statistics will be available with no password | *empty* | -| HAPROXY_STATS_PORT | (Optional) The HAProxy port to the statistics. If set to `false`, disable statistics | `1936` | -| HAPROXY_CUSTOMERRORS | (Optional) If HAProxy will use custom HTML errors. true/false. | `false` | +| Environment Variable | Description | Default | +|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------| +| EASYHAPROXY_DISCOVER | How the services will be discovered to create `haproxy.cfg`: `static`, `docker`, `swarm` or `kubernetes` | **required** | +| EASYHAPROXY_LABEL_PREFIX | (Optional) The key will search for matching resources. | `easyhaproxy` | +| EASYHAPROXY_CERTBOT_* | (Optional) Enable Let's Encrypt or any other ACME certificate. See more: [acme](acme.md) | *empty* | +| EASYHAPROXY_SSL_MODE | (Optional) `strict` supports only the most recent TLS version; `default` good SSL integration with recent browsers; `loose` supports all old SSL protocols for old browsers (not recommended). | `default` | +| EASYHAPROXY_REFRESH_CONF | (Optional) Check for new containers/services every N seconds. | 10 | +| EASYHAPROXY_LOG_LEVEL | (Optional) The log level for EasyHAproxy messages. Available: TRACE,DEBUG,INFO,WARN,ERROR,FATAL | INFO | +| CERTBOT_LOG_LEVEL | (Optional) The log level for Certbot messages. Available: TRACE,DEBUG,INFO,WARN,ERROR,FATAL | DEBUG | +| HAPROXY_LOG_LEVEL | (Optional) The log level for HAProxy messages. Available: TRACE,DEBUG,INFO,WARN,ERROR,FATAL | DEBUG | +| HAPROXY_USERNAME | (Optional) The HAProxy username to the statistics. | `admin` | +| HAPROXY_PASSWORD | (Optional) The HAProxy password to the statistics. If not set, statistics will be available with no password | *empty* | +| HAPROXY_STATS_PORT | (Optional) The HAProxy port to the statistics. If set to `false`, disable statistics | `1936` | +| HAPROXY_CUSTOMERRORS | (Optional) If HAProxy will use custom HTML errors. true/false. | `false` | ---- diff --git a/src/functions/__init__.py b/src/functions/__init__.py index 1b3b1c0..619173c 100644 --- a/src/functions/__init__.py +++ b/src/functions/__init__.py @@ -41,6 +41,8 @@ class ContainerEnv: "eab_kid": os.getenv("EASYHAPROXY_CERTBOT_EAB_KID", ""), "eab_hmac_key": os.getenv("EASYHAPROXY_CERTBOT_EAB_HMAC_KEY", ""), "retry_count": int(os.getenv("EASYHAPROXY_CERTBOT_RETRY_COUNT", 60)), + "preferred_challenges": os.getenv("EASYHAPROXY_CERTBOT_PREFERRED_CHALLENGES", "http"), + "manual_auth_hook": os.getenv("EASYHAPROXY_CERTBOT_MANUAL_AUTH_HOOK", False), } if env_vars["certbot"]["autoconfig"] != "" and not env_vars["certbot"]["server"] and env_vars["certbot"]["email"] != "": @@ -285,6 +287,8 @@ class Certbot: self.eab_hmac_key = self.set_eab_hmac_key(env["certbot"]["eab_hmac_key"]) self.freeze_issue = {} self.retry_count = env["certbot"]["retry_count"] + self.certbot_preferred_challenges = env["certbot"]["preferred_challenges"] + self.certbot_manual_auth_hook = env["certbot"]["manual_auth_hook"] @staticmethod def set_acme_server(acme_server): @@ -337,9 +341,7 @@ class Certbot: renew_certs.append(host_arg) certbot_certonly = ('/usr/bin/certbot certonly {acme_server}' - ' --standalone' - ' --preferred-challenges http' - ' --http-01-port 2080' + ' --preferred-challenges {challenge}' ' --agree-tos' ' --issuance-timeout 90' ' --no-eff-email' @@ -350,9 +352,18 @@ class Certbot: eab_hmac_key=self.eab_hmac_key, certs=' '.join(request_certs), email=self.email, + challenge=self.certbot_preferred_challenges, acme_server=self.acme_server) ) + if 'http' in self.certbot_preferred_challenges: + certbot_certonly += (' --http-01-port 2080' + ' --standalone' + ) + + if self.certbot_manual_auth_hook: + certbot_certonly += ' --manual --manual-auth-hook \'{hook}\''.format(hook=self.certbot_manual_auth_hook) + ret_reload = False return_code_issue = 0 return_code_renew = 0 diff --git a/src/main.py b/src/main.py index 24769d8..fb4e96a 100644 --- a/src/main.py +++ b/src/main.py @@ -63,8 +63,8 @@ def main(): Functions.log(Functions.INIT_LOG, Functions.INFO, " _ ") Functions.log(Functions.INIT_LOG, Functions.INFO, " ___ __ _ ____ _ ___| |_ __ _ _ __ _ _ _____ ___ _ ") - Functions.log(Functions.INIT_LOG, Functions.INFO, "/ -_) _` (_-< || |___| ' \/ _` | '_ \ '_/ _ \ \ / || |") - Functions.log(Functions.INIT_LOG, Functions.INFO, "\___\__,_/__/\_, | |_||_\__,_| .__/_| \___/_\_\\_, |") + Functions.log(Functions.INIT_LOG, Functions.INFO, "/ -_) _` (_-< || |___| ' \\/ _` | '_ \\ '_/ _ \\ \\ / || |") + Functions.log(Functions.INIT_LOG, Functions.INFO, "\\___\\__,_/__/\\_, | |_||_\\__,_| .__/_| \\___/_\\_\\_, |") Functions.log(Functions.INIT_LOG, Functions.INFO, " |__/ |_| |__/ ") Functions.log(Functions.INIT_LOG, Functions.INFO, "Release: %s" % (os.getenv("RELEASE_VERSION"))) diff --git a/src/tests/test_containerenv.py b/src/tests/test_containerenv.py index 30c2680..27d672e 100644 --- a/src/tests/test_containerenv.py +++ b/src/tests/test_containerenv.py @@ -18,7 +18,9 @@ def test_container_env_empty(): "eab_kid": "", "email": "", "server": False, - "retry_count": 60} + "retry_count": 60, + "preferred_challenges": "http", + "manual_auth_hook": False} } == ContainerEnv.read() # os.environ['CERTBOT_LOG_LEVEL'] = 'warn' @@ -41,7 +43,9 @@ def test_container_env_customerrors(): "eab_kid": "", "email": "", "server": False, - "retry_count": 60} + "retry_count": 60, + "preferred_challenges": "http", + "manual_auth_hook": False} } == ContainerEnv.read() finally: del os.environ['HAPROXY_CUSTOMERRORS'] @@ -64,7 +68,9 @@ def test_container_env_sslmode(): "eab_kid": "", "email": "", "server": False, - "retry_count": 60} + "retry_count": 60, + "preferred_challenges": "http", + "manual_auth_hook": False} } == ContainerEnv.read() finally: del os.environ['EASYHAPROXY_SSL_MODE'] @@ -88,7 +94,9 @@ def test_container_env_stats(): "eab_kid": "", "email": "", "server": False, - "retry_count": 60} + "retry_count": 60, + "preferred_challenges": "http", + "manual_auth_hook": False} } == ContainerEnv.read() finally: del os.environ['HAPROXY_USERNAME'] @@ -118,7 +126,9 @@ def test_container_env_stats_password(): "eab_kid": "", "email": "", "server": False, - "retry_count": 60} + "retry_count": 60, + "preferred_challenges": "http", + "manual_auth_hook": False} } == ContainerEnv.read() finally: del os.environ['HAPROXY_PASSWORD'] @@ -148,7 +158,9 @@ def test_container_env_stats_password_2(): "eab_kid": "", "email": "", "server": False, - "retry_count": 60} + "retry_count": 60, + "preferred_challenges": "http", + "manual_auth_hook": False} } == ContainerEnv.read() finally: del os.environ['HAPROXY_USERNAME'] @@ -174,7 +186,9 @@ def test_container_env_certbot_email(): 'eab_kid': "", "email": "acme@example.org", "server": False, - "retry_count": 60 + "retry_count": 60, + "preferred_challenges": "http", + "manual_auth_hook": False } } == ContainerEnv.read() finally: @@ -187,6 +201,8 @@ def test_container_env_certbot_full(): os.environ['EASYHAPROXY_CERTBOT_EAB_KID'] = 'eab_kid' os.environ['EASYHAPROXY_CERTBOT_EAB_HMAC_KEY'] = 'eab_hmac_key' os.environ['EASYHAPROXY_CERTBOT_RETRY_COUNT'] = "10" + os.environ['EASYHAPROXY_CERTBOT_PREFERRED_CHALLENGES'] = "dns" + os.environ['EASYHAPROXY_CERTBOT_MANUAL_AUTH_HOOK'] = "something_manual_auth_hook" try: assert { "customerrors": False, @@ -203,7 +219,9 @@ def test_container_env_certbot_full(): "server": "schema://url/a", 'eab_hmac_key': 'eab_hmac_key', 'eab_kid': 'eab_kid', - 'retry_count': 10 + 'retry_count': 10, + "preferred_challenges": "dns", + "manual_auth_hook": "something_manual_auth_hook" } } == ContainerEnv.read() finally: @@ -212,6 +230,8 @@ def test_container_env_certbot_full(): del os.environ['EASYHAPROXY_CERTBOT_EAB_KID'] del os.environ['EASYHAPROXY_CERTBOT_EAB_HMAC_KEY'] del os.environ['EASYHAPROXY_CERTBOT_RETRY_COUNT'] + del os.environ['EASYHAPROXY_CERTBOT_PREFERRED_CHALLENGES'] + del os.environ['EASYHAPROXY_CERTBOT_MANUAL_AUTH_HOOK'] def test_container_log_level(): @@ -234,7 +254,9 @@ def test_container_log_level(): 'eab_kid': "", "email": "", "server": False, - "retry_count": 60 + "retry_count": 60, + "preferred_challenges": "http", + "manual_auth_hook": False } } == ContainerEnv.read() finally: