1
0
Fork 0

Merge pull request #45 from byjg/issue/38

Issue #38 Add HTTPS Support for SSL
This commit is contained in:
Joao M 2023-07-03 13:37:38 -05:00 committed by GitHub
commit 78ea9b6810
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 147 additions and 49 deletions

View file

@ -19,6 +19,14 @@ EasyHAProxy can detect and configure HAProxy automatically on the following plat
- Docker Swarm - Docker Swarm
- Kubernetes - Kubernetes
## Who is using?
EasyHAProxy is part of some projects:
- Dokku
- MicroK8s
See detailed instructions on how to install below.
## Features ## Features
EasyHAProxy will discover the services based on the Docker Tags of the containers running on a Docker host or Docker Swarm cluster and dynamically set up the `haproxy.cfg`. Below, EasyHAProxy main features: EasyHAProxy will discover the services based on the Docker Tags of the containers running on a Docker host or Docker Swarm cluster and dynamically set up the `haproxy.cfg`. Below, EasyHAProxy main features:
@ -34,7 +42,7 @@ EasyHAProxy will discover the services based on the Docker Tags of the container
Also, it is possible to set up HAProxy from a simple Yaml file instead of creating `haproxy.cfg` file. Also, it is possible to set up HAProxy from a simple Yaml file instead of creating `haproxy.cfg` file.
## How Does It Works? ## How Does It Work?
You don't need to change your current infrastructure and don't need to learn the HAProxy configuration. You don't need to change your current infrastructure and don't need to learn the HAProxy configuration.

View file

@ -1,20 +1,20 @@
# Docker environment variables # Docker environment variables
| Environment Variable | Description | Default | | Environment Variable | Description | Default |
|---------------------------------|-------------------------------------------------------------------------------------------------|------------------| |---------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|
| EASYHAPROXY_DISCOVER | How the services will be discovered to create `haproxy.cfg`: `static`, `docker`, `swarm` or `kubernetes` | **required** | | 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_LABEL_PREFIX | (Optional) The key will search for matching resources. | `easyhaproxy` |
| EASYHAPROXY_LETSENCRYPT_EMAIL | (Optional) The email will be used to request the certificate to Letsencrypt | *empty* | | EASYHAPROXY_LETSENCRYPT_EMAIL | (Optional) The email will be used to request the certificate to Letsencrypt | *empty* |
| EASYHAPROXY_LETSENCRYPT_SERVER | (Optional) Can be `staging` or 'schema://domain.tld'. If set, will try to connect to the Letsencrypt test server | *empty* | | EASYHAPROXY_LETSENCRYPT_SERVER | (Optional) Can be `staging` or 'schema://domain.tld'. If set, will try to connect to the Letsencrypt test server | *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_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 configuration every N seconds. | 10 | | EASYHAPROXY_REFRESH_CONF | (Optional) Check configuration every N seconds. | 10 |
| EASYHAPROXY_LOG_LEVEL | (Optional) The log level for EasyHAproxy messages. Available: TRACE,DEBUG,INFO,WARN,ERROR,FATAL | DEBUG | | 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 | | 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_LOG_LEVEL | (Optional) The log level for HAProxy messages. Available: TRACE,DEBUG,INFO,WARN,ERROR,FATAL | INFO |
| HAPROXY_USERNAME | (Optional) The HAProxy username to the statistics. | `admin` | | 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_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_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` | | HAPROXY_CUSTOMERRORS | (Optional) If HAProxy will use custom HTML errors. true/false. | `false` |

View file

@ -79,15 +79,17 @@ Notes:
## Kubernetes annotations ## Kubernetes annotations
| annotation | Description | Default | Example | | annotation | Description | Default | Example |
|-----------------------------|-----------------------------------------------------------------------------------------|--------------|--------------| |----------------------------------|-----------------------------------------------------------------------------------------|--------------|---------------------------------------|
| kubernetes.io/ingress.class | (required) Activate EasyHAProxy. | **required** | easyhaproxy-ingress | kubernetes.io/ingress.class | (required) Activate EasyHAProxy. | **required** | easyhaproxy-ingress |
| easyhaproxy.redirect_ssl | (optional) Boolean. Force redirect all endpoints to HTTPS. | false | true or false | easyhaproxy.redirect_ssl | (optional) Boolean. Force redirect all endpoints to HTTPS. | false | true or false |
| easyhaproxy.letsencrypt | (optional) Boolean. It will request letsencrypt certificates for the ingresses domains. | false | true or false | easyhaproxy.letsencrypt | (optional) Boolean. It will request letsencrypt certificates for the ingresses domains. | false | true or false |
| easyhaproxy.redirect | (optional) JSON. Key pair with a domain and its destination. | *empty* | {"domain":"redirect_url"} | easyhaproxy.redirect | (optional) JSON. Key pair with a domain and its destination. | *empty* | {"domain":"redirect_url"} |
| easyhaproxy.mode | (optional) Set the HTTP mode for that connection. | http | http or tcp | easyhaproxy.mode | (optional) Set the HTTP mode for that connection. | http | http or tcp |
| easyhaproxy.listen_port | (optional) Set the an additional port for that ingress | http | http or tcp | easyhaproxy.listen_port | (optional) Set the an additional port for that ingress | http | http or tcp |
| easyhaproxy.balance | (optional) Set the balance algorithm for that ingress. See [HAProxy documentation](https://cbonte.github.io/haproxy-dconv/1.8/configuration.html#4.2-balance) | roundrobin | roundrobin, leastconn, source, uri, url_param, hdr, rdp-cookie, static-rr, static-est, hdr(host), rdp-cookie, map-based, map-based(backend) | | easyhaproxt.logLevel.certbot | (optional) Certbot log level | DEBUG | TRACE,DEBUG,INFO,WARN,ERROR or FATAL |
| easyhaproxt.logLevel.eashhaproxy | (optional) EasyHAProxy log level | DEBUG | TRACE,DEBUG,INFO,WARN,ERROR or FATAL |
| easyhaproxt.logLevel.haproxy | (optional) HAProxy log level | INFO | TRACE,DEBUG,INFO,WARN,ERROR or FATAL |
**Important**: The annotations are per ingress and applied to all hosts in that ingress configuration. **Important**: The annotations are per ingress and applied to all hosts in that ingress configuration.

View file

@ -20,9 +20,11 @@ customerrors: true # Optional (default false)
ssl_mode: default ssl_mode: default
letsencrypt: { logLevel:
"email": "acme@example.org" haproxy: INFO
}
letsencrypt:
email: "acme@example.org"
easymapping: easymapping:
- port: 80 - port: 80
@ -83,10 +85,14 @@ customerrors: true # Optional (default false)
ssl_mode: default # Optional ssl_mode: default # Optional
letsencrypt: { # Optional. If you enable `letsencrypt` will need to setu0p this, logLevel:
# otherwise the certificate will be issued certbot: DEBUG # Optional (default: DEBUG). Can be: TRACE,DEBUG,INFO,WARN,ERROR,FATAL
"email": "acme@example.org" easyhaproxy: DEBUG # Optional (default: DEBUG). Can be: TRACE,DEBUG,INFO,WARN,ERROR,FATAL
} haproxy: INFO # Optional (default: INFO). Can be: TRACE,DEBUG,INFO,WARN,ERROR,FATAL
# Optional. If you enable `letsencrypt` will need to set up this, otherwise the certificate will be issued
letsencrypt:
email": "acme@example.org"
easymapping: easymapping:
- port: 80 # Listen port - port: 80 # Listen port

View file

@ -56,7 +56,7 @@ class HaproxyConfigGenerator:
self.serving_hosts = [] self.serving_hosts = []
self.certs = {} self.certs = {}
def generate(self, container_metadata = {}): def generate(self, container_metadata={}):
self.mapping.setdefault("easymapping", []) self.mapping.setdefault("easymapping", [])
if container_metadata != {}: if container_metadata != {}:

View file

@ -31,7 +31,13 @@ class ContainerEnv:
"email": os.getenv("EASYHAPROXY_LETSENCRYPT_EMAIL"), "email": os.getenv("EASYHAPROXY_LETSENCRYPT_EMAIL"),
"server": os.getenv("EASYHAPROXY_LETSENCRYPT_SERVER", "false").lower() in ["true", "1", "yes"] "server": os.getenv("EASYHAPROXY_LETSENCRYPT_SERVER", "false").lower() in ["true", "1", "yes"]
} }
env_vars["logLevel"] = {
"easyhaproxy": os.getenv("EASYHAPROXY_LOG_LEVEL") if os.getenv("EASYHAPROXY_LOG_LEVEL") else Functions.DEBUG,
"haproxy": os.getenv("HAPROXY_LOG_LEVEL") if os.getenv("HAPROXY_LOG_LEVEL") else Functions.INFO,
"certbot": os.getenv("CERTBOT_LOG_LEVEL") if os.getenv("CERTBOT_LOG_LEVEL") else Functions.DEBUG,
}
return env_vars return env_vars

View file

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

View file

@ -1,5 +1,18 @@
{% set log_definition = data["logLevel"] | default({}) %}
{% set log_level = log_definition["haproxy"] | default("INFO") | upper %}
{% if log_level == "TRACE" or log_level == "DEBUG" %}
{% set haproxy_log_level = "debug" %}
{% elif log_level == "INFO" %}
{% set haproxy_log_level = "info" %}
{% elif log_level == "WARN" %}
{% set haproxy_log_level = "warning" %}
{% elif log_level == "ERROR" %}
{% set haproxy_log_level = "err" %}
{% elif log_level == "FATAL" %}
{% set haproxy_log_level = "crit" %}
{% endif %}
global global
log stdout format raw local0 info log stdout format raw local0 {{ haproxy_log_level }}
maxconn 2000 maxconn 2000
{% if data["ssl_mode"] == "strict" %} {% if data["ssl_mode"] == "strict" %}
{% include "ssl_strict.j2" %} {% include "ssl_strict.j2" %}

View file

@ -36,7 +36,7 @@ backend srv_stats
server Local 127.0.0.1:1936 server Local 127.0.0.1:1936
frontend http_in_443 frontend http_in_443
bind *:443 ssl crt /certs/letsencrypt/ alpn http/1.1 crt /certs/haproxy/ alpn http/1.1 bind *:443 ssl crt /certs/letsencrypt/ alpn h2,http/1.1 crt /certs/haproxy/ alpn h2,http/1.1
mode http mode http
acl is_rule_hostssl_local_443_1 hdr(host) -i hostssl.local acl is_rule_hostssl_local_443_1 hdr(host) -i hostssl.local

View file

@ -74,7 +74,7 @@ backend srv_test2_example_org_80
server srv-0 83d57d592e26:8080 check weight 1 server srv-0 83d57d592e26:8080 check weight 1
frontend http_in_443 frontend http_in_443
bind *:443 ssl crt /certs/letsencrypt/ alpn http/1.1 crt /certs/haproxy/ alpn http/1.1 bind *:443 ssl crt /certs/letsencrypt/ alpn h2,http/1.1 crt /certs/haproxy/ alpn h2,http/1.1
mode http mode http
acl is_rule_test_example_org_443_1 hdr(host) -i test.example.org acl is_rule_test_example_org_443_1 hdr(host) -i test.example.org

View file

@ -49,7 +49,7 @@ backend srv_host1_local_80
server srv-0 5b69bc7fea1b:80 check weight 1 server srv-0 5b69bc7fea1b:80 check weight 1
frontend http_in_443 frontend http_in_443
bind *:443 ssl crt /certs/letsencrypt/ alpn http/1.1 crt /certs/haproxy/ alpn http/1.1 bind *:443 ssl crt /certs/letsencrypt/ alpn h2,http/1.1 crt /certs/haproxy/ alpn h2,http/1.1
mode http mode http
acl is_rule_host2_local_443_1 hdr(host) -i host2.local acl is_rule_host2_local_443_1 hdr(host) -i host2.local

View file

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

View file

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

View file

@ -1,12 +1,20 @@
import pytest import pytest
import os import os
from functions import Functions
from processor import ContainerEnv from processor import ContainerEnv
def test_container_env_empty(): def test_container_env_empty():
assert { assert {
"customerrors": False, "customerrors": False,
"ssl_mode": "default", "ssl_mode": "default",
"lookup_label": "easyhaproxy" "lookup_label": "easyhaproxy",
"logLevel": {
"easyhaproxy": Functions.DEBUG,
"haproxy": Functions.INFO,
"certbot": Functions.DEBUG,
},
} == ContainerEnv.read() } == ContainerEnv.read()
# os.environ['CERTBOT_LOG_LEVEL'] = 'warn' # os.environ['CERTBOT_LOG_LEVEL'] = 'warn'
@ -17,7 +25,12 @@ def test_container_env_customerrors():
assert { assert {
"customerrors": True, "customerrors": True,
"ssl_mode": "default", "ssl_mode": "default",
"lookup_label": "easyhaproxy" "lookup_label": "easyhaproxy",
"logLevel": {
"easyhaproxy": Functions.DEBUG,
"haproxy": Functions.INFO,
"certbot": Functions.DEBUG,
},
} == ContainerEnv.read() } == ContainerEnv.read()
finally: finally:
os.environ['HAPROXY_CUSTOMERRORS'] = '' os.environ['HAPROXY_CUSTOMERRORS'] = ''
@ -28,7 +41,12 @@ def test_container_env_sslmode():
assert { assert {
"customerrors": False, "customerrors": False,
"ssl_mode": "strict", "ssl_mode": "strict",
"lookup_label": "easyhaproxy" "lookup_label": "easyhaproxy",
"logLevel": {
"easyhaproxy": Functions.DEBUG,
"haproxy": Functions.INFO,
"certbot": Functions.DEBUG,
},
} == ContainerEnv.read() } == ContainerEnv.read()
finally: finally:
os.environ['EASYHAPROXY_SSL_MODE'] = '' os.environ['EASYHAPROXY_SSL_MODE'] = ''
@ -41,6 +59,11 @@ def test_container_env_stats():
"customerrors": False, "customerrors": False,
"ssl_mode": "default", "ssl_mode": "default",
"lookup_label": "easyhaproxy", "lookup_label": "easyhaproxy",
"logLevel": {
"easyhaproxy": Functions.DEBUG,
"haproxy": Functions.INFO,
"certbot": Functions.DEBUG,
},
} == ContainerEnv.read() } == ContainerEnv.read()
finally: finally:
os.environ['HAPROXY_USERNAME'] = '' os.environ['HAPROXY_USERNAME'] = ''
@ -58,7 +81,12 @@ def test_container_env_stats_password():
"password": "xyz", "password": "xyz",
"port": "1936" "port": "1936"
} },
"logLevel": {
"easyhaproxy": Functions.DEBUG,
"haproxy": Functions.INFO,
"certbot": Functions.DEBUG,
},
} == ContainerEnv.read() } == ContainerEnv.read()
finally: finally:
os.environ['HAPROXY_PASSWORD'] = '' os.environ['HAPROXY_PASSWORD'] = ''
@ -78,7 +106,12 @@ def test_container_env_stats_password():
"password": "xyz", "password": "xyz",
"port": "2101" "port": "2101"
} },
"logLevel": {
"easyhaproxy": Functions.DEBUG,
"haproxy": Functions.INFO,
"certbot": Functions.DEBUG,
},
} == ContainerEnv.read() } == ContainerEnv.read()
finally: finally:
os.environ['HAPROXY_USERNAME'] = '' os.environ['HAPROXY_USERNAME'] = ''
@ -96,7 +129,12 @@ def test_container_env_stats_password():
"letsencrypt": { "letsencrypt": {
"email": "acme@example.org", "email": "acme@example.org",
"server": False "server": False
} },
"logLevel": {
"easyhaproxy": Functions.DEBUG,
"haproxy": Functions.INFO,
"certbot": Functions.DEBUG,
},
} == ContainerEnv.read() } == ContainerEnv.read()
finally: finally:
os.environ['EASYHAPROXY_LETSENCRYPT_EMAIL'] = '' os.environ['EASYHAPROXY_LETSENCRYPT_EMAIL'] = ''
@ -112,7 +150,32 @@ def test_container_env_letsencrypt():
"letsencrypt": { "letsencrypt": {
"email": "acme@example.org", "email": "acme@example.org",
"server": True "server": True
} },
"logLevel": {
"easyhaproxy": Functions.DEBUG,
"haproxy": Functions.INFO,
"certbot": Functions.DEBUG,
},
} == ContainerEnv.read() } == ContainerEnv.read()
finally: finally:
os.environ['EASYHAPROXY_LETSENCRYPT_EMAIL'] = '' os.environ['EASYHAPROXY_LETSENCRYPT_EMAIL'] = ''
def test_container_log_level():
os.environ['CERTBOT_LOG_LEVEL'] = Functions.TRACE
os.environ['EASYHAPROXY_LOG_LEVEL'] = Functions.ERROR
os.environ['HAPROXY_LOG_LEVEL'] = Functions.FATAL
try:
assert {
"customerrors": False,
"ssl_mode": "default",
"lookup_label": "easyhaproxy",
"logLevel": {
"easyhaproxy": Functions.ERROR,
"haproxy": Functions.FATAL,
"certbot": Functions.TRACE,
},
} == ContainerEnv.read()
finally:
os.environ['CERTBOT_LOG_LEVEL'] = ''
os.environ['EASYHAPROXY_LOG_LEVEL'] = ''
os.environ['HAPROXY_LOG_LEVEL'] = ''