diff --git a/assets/scripts/swarm.py b/assets/scripts/swarm.py index fa00dc9..52c1a73 100644 --- a/assets/scripts/swarm.py +++ b/assets/scripts/swarm.py @@ -17,6 +17,11 @@ if os.getenv("HAPROXY_PASSWORD"): } result["lookup_label"] = os.getenv("EASYHAPROXY_LABEL_PREFIX") if os.getenv("EASYHAPROXY_LABEL_PREFIX") else "easyhaproxy" +if (os.getenv("EASYHAPROXY_LETSENCRYPT_EMAIL")): + result["letsencrypt"] = { + "email": os.getenv("EASYHAPROXY_LETSENCRYPT_EMAIL") + } + cfg = HaproxyConfigGenerator(result) print(cfg.generate(line_list)) diff --git a/easymapping/__init__.py b/easymapping/__init__.py index 44e3e64..1b29675 100644 --- a/easymapping/__init__.py +++ b/easymapping/__init__.py @@ -49,12 +49,12 @@ class HaproxyConfigGenerator: def __init__(self, mapping, ssl_cert_folder="/certs"): self.mapping = mapping self.mapping.setdefault("ssl_mode", 'default') + self.mapping.setdefault("letsencrypt", {"email": ""}) self.mapping["ssl_mode"] = self.mapping["ssl_mode"].lower() self.label = DockerLabelHandler(mapping['lookup_label'] if 'lookup_label' in mapping else "easyhaproxy") self.ssl_cert_haproxy = ssl_cert_folder + "/haproxy" self.ssl_cert_letsecncrypt = ssl_cert_folder + "/letsencrypt" self.letsencrypt_hosts = [] - self.letsencrypt_email = os.getenv("EASYHAPROXY_LETSENCRYPT_EMAIL", "") os.makedirs(self.ssl_cert_haproxy, exist_ok=True) os.makedirs(self.ssl_cert_letsecncrypt, exist_ok=True) @@ -116,7 +116,7 @@ class HaproxyConfigGenerator: letsencrypt = self.label.get_bool( self.label.create([definition, "letsencrypt"]), False - ) and self.letsencrypt_email != "" + ) and self.mapping["letsencrypt"]["email"] != "" if port not in easymapping: easymapping[port] = { @@ -164,6 +164,7 @@ class HaproxyConfigGenerator: } easymapping["443"]["hosts"][hostname] = dict(easymapping[port]["hosts"][hostname]) easymapping["443"]["hosts"][hostname]["letsencrypt"] = False + easymapping["443"]["hosts"][hostname]["redirect_ssl"] = False easymapping["443"]["ssl_cert"] = self.ssl_cert_letsecncrypt self.letsencrypt_hosts.append(hostname) if hostname not in self.letsencrypt_hosts else self.letsencrypt_hosts diff --git a/tests/expected/services-letsencrypt.txt b/tests/expected/services-letsencrypt.txt new file mode 100644 index 0000000..7dc6abc --- /dev/null +++ b/tests/expected/services-letsencrypt.txt @@ -0,0 +1,97 @@ +global + log stdout format raw local0 info + maxconn 2000 + tune.ssl.default-dh-param 2048 + + # intermediate configuration + ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 + ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 + ssl-default-bind-options prefer-client-ciphers no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets + + ssl-default-server-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 + ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 + ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets + + ssl-dh-param-file /etc/haproxy/dhparam + +defaults + log global + + timeout connect 3s + timeout client 10s + timeout server 10m + errorfile 400 /etc/haproxy/errors-custom/400.http + errorfile 403 /etc/haproxy/errors-custom/403.http + errorfile 408 /etc/haproxy/errors-custom/408.http + errorfile 500 /etc/haproxy/errors-custom/500.http + errorfile 502 /etc/haproxy/errors-custom/502.http + errorfile 503 /etc/haproxy/errors-custom/503.http + errorfile 504 /etc/haproxy/errors-custom/504.http + +frontend stats + bind *:1936 + mode http + stats enable + stats hide-version + stats realm Haproxy\ Statistics + stats uri / + stats auth admin:password +# acl is_proxystats hdr(host) -i some.host.com +# default_backend srv_stats +# use_backend srv_stats if is_proxystats + default_backend srv_stats + +backend srv_stats + mode http + server Local 127.0.0.1:1936 + +frontend http_in_80 + bind *:80 + mode http + + acl is_rule_test_example_org_80_1 hdr(host) -i test.example.org + acl is_rule_test_example_org_80_2 hdr(host) -i test.example.org:80 + acl is_letsencrypt_test_example_org_80 path_beg /.well-known/acme-challenge/ + use_backend letsencrypt_backend if is_letsencrypt_test_example_org_80 is_rule_test_example_org_80_1 OR is_letsencrypt_test_example_org_80 is_rule_test_example_org_80_2 + http-request redirect scheme https code 301 if !is_letsencrypt_test_example_org_80 is_rule_test_example_org_80_1 OR !is_letsencrypt_test_example_org_80 is_rule_test_example_org_80_2 + + acl is_rule_test2_example_org_80_1 hdr(host) -i test2.example.org + acl is_rule_test2_example_org_80_2 hdr(host) -i test2.example.org:80 + use_backend srv_test2_example_org_80 if is_rule_test2_example_org_80_1 OR is_rule_test2_example_org_80_2 + +backend srv_test_example_org_80 + balance roundrobin + mode http + option forwardfor + http-request set-header X-Forwarded-Port %[dst_port] + http-request add-header X-Forwarded-Proto https if { ssl_fc } + server srv-0 f5c645a0dfc6:80 check weight 1 + server srv-1 b63438410b6a:80 check weight 1 +backend srv_test2_example_org_80 + balance roundrobin + mode http + option forwardfor + http-request set-header X-Forwarded-Port %[dst_port] + http-request add-header X-Forwarded-Proto https if { ssl_fc } + server srv-0 83d57d592e26:8080 check weight 1 + +frontend http_in_443 + bind *:443 ssl crt /certs/letsencrypt/ alpn http/1.1 crt /certs/haproxy/ alpn http/1.1 + mode http + + acl is_rule_test_example_org_443_1 hdr(host) -i test.example.org + acl is_rule_test_example_org_443_2 hdr(host) -i test.example.org:443 + use_backend srv_test_example_org_443 if is_rule_test_example_org_443_1 OR is_rule_test_example_org_443_2 + +backend srv_test_example_org_443 + balance roundrobin + mode http + option forwardfor + http-request set-header X-Forwarded-Port %[dst_port] + http-request add-header X-Forwarded-Proto https if { ssl_fc } + server srv-0 f5c645a0dfc6:80 check weight 1 verify none + server srv-1 b63438410b6a:80 check weight 1 verify none + +backend letsencrypt_backend + mode http + server certbot 127.0.0.1:2080 \ No newline at end of file diff --git a/tests/fixtures/services-letsencrypt b/tests/fixtures/services-letsencrypt new file mode 100644 index 0000000..2d616ff --- /dev/null +++ b/tests/fixtures/services-letsencrypt @@ -0,0 +1,4 @@ +f5c645a0dfc6={"com.docker.compose.config-hash":"b95ebc27d0e61caa418cdfa632e05a656da9bbc3ea0d4603651971015f10a1f0","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:bea3509d6fdc8d7f9ec95563a5a226dc977ee74fb3e980e0de70e892c2d38dde","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"nginx","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"test.example.org","easyhaproxy.http.letsencrypt":"true","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"80","easyhaproxy.http.redirect":"{\"google.helloworld.com\": \"www.google.com\"}","easyhaproxy.http.redirect_ssl":"true"} +bbd4d1854155={"com.docker.compose.config-hash":"3dc790bf2bea944359c75a40c45655bd868f1d85beb599d1ca797e8ea2c95ee4","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:0fd95b1512c207048ab3fcc74032354f38143fbb8235ac2a47da903c98a58205","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"haproxy","com.docker.compose.version":"2.8.0"} +b63438410b6a={"com.docker.compose.config-hash":"b95ebc27d0e61caa418cdfa632e05a656da9bbc3ea0d4603651971015f10a1f0","com.docker.compose.container-number":"2","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:bea3509d6fdc8d7f9ec95563a5a226dc977ee74fb3e980e0de70e892c2d38dde","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"nginx","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"test.example.org","easyhaproxy.http.letsencrypt":"true","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"80","easyhaproxy.http.redirect":"{\"google.helloworld.com\": \"www.google.com\"}","easyhaproxy.http.redirect_ssl":"true"} +83d57d592e26={"com.docker.compose.config-hash":"8c5871144f1e8a3aeca037207c02f011ab2c6e6c311a3773602b63541762dab5","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:c4232396c715f3d568816c666e6d9b4a68ef6c36f6243b4007c4ee1d8335fd65","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"static","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"test2.example.org","easyhaproxy.http.localport":"8080","easyhaproxy.http.port":"80","io.buildah.version":"1.21.0"} diff --git a/tests/test_parser.py b/tests/test_parser.py index a36516f..cd16de6 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -35,14 +35,16 @@ def test_parser_finds_services(): line_list = load_fixture("services") result = { - "customerrors": False + "customerrors": False, + "letsencrypt": { + "email": LETSENCRYPT_EMAIL + } } if os.path.exists(CERT_FILE): os.remove(CERT_FILE) cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) - cfg.letsencrypt_email = LETSENCRYPT_EMAIL haproxy_config = cfg.generate(line_list) assert len(haproxy_config) > 0 @@ -60,14 +62,16 @@ def test_parser_finds_services_changed_label(): result = { "customerrors": False, - "lookup_label": "haproxy" + "lookup_label": "haproxy", + "letsencrypt": { + "email": LETSENCRYPT_EMAIL + } } if os.path.exists(CERT_FILE): os.remove(CERT_FILE) cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) - cfg.letsencrypt_email = LETSENCRYPT_EMAIL haproxy_config = cfg.generate(line_list) assert len(haproxy_config) > 0 @@ -84,14 +88,16 @@ def test_parser_finds_services_raw(): line_list = load_fixture("services") result = { - "customerrors": False + "customerrors": False, + "letsencrypt": { + "email": LETSENCRYPT_EMAIL + } } if os.path.exists(CERT_FILE): os.remove(CERT_FILE) cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) - cfg.letsencrypt_email = LETSENCRYPT_EMAIL parsed_object = [ { @@ -374,7 +380,30 @@ def test_parser_ssl_loose(): assert expected_file.read() == haproxy_config assert [] == cfg.letsencrypt_hosts +def test_parser_ssl_letsencrypt(): + line_list = load_fixture("services-letsencrypt") + + result = { + "customerrors": True, + "stats": { + "username": "admin", + "password": "password" + }, + "letsencrypt": { + "email": LETSENCRYPT_EMAIL + } + } + + cfg = easymapping.HaproxyConfigGenerator(result, CERTS_FOLDER) + haproxy_config = cfg.generate(line_list) + + assert len(haproxy_config) > 0 + path = os.path.dirname(os.path.realpath(__file__)) + with open(path + "/expected/services-letsencrypt.txt", 'r') as expected_file: + assert expected_file.read() == haproxy_config + assert ["test.example.org"] == cfg.letsencrypt_hosts #test_parser_finds_services_raw() #test_parser_tcp() #test_parser_multiple_hosts() +#test_parser_ssl_letsencrypt() \ No newline at end of file