diff --git a/easymapping/__init__.py b/easymapping/__init__.py index a26b99f..d570085 100644 --- a/easymapping/__init__.py +++ b/easymapping/__init__.py @@ -2,8 +2,7 @@ import base64 import hashlib from jinja2 import Environment, FileSystemLoader import json -import time - +import os class DockerLabelHandler: def __init__(self, label): @@ -34,9 +33,12 @@ class DockerLabelHandler: class HaproxyConfigGenerator: - def __init__(self, mapping): + def __init__(self, mapping, ssl_cert_folder="/etc/haproxy/certs"): self.mapping = mapping self.label = DockerLabelHandler("com.byjg.easyhaproxy") + self.ssl_cert_folder = ssl_cert_folder + self.ssl_cert_increment = 0 + os.makedirs(self.ssl_cert_folder, exist_ok=True) def generate(self, lineList = []): @@ -89,14 +91,13 @@ class HaproxyConfigGenerator: "80" ) + hash = "" if self.label.create(["sslcert", definition]) in d: hash = hashlib.md5( d[self.label.create(["sslcert", definition])].encode('utf-8') ).hexdigest() - else: - hash = "" - key = port+hash + key = port if not hash else port + "_" + hash if key not in easymapping: easymapping[key] = { @@ -123,8 +124,9 @@ class HaproxyConfigGenerator: # handle SSL ssl_label = self.label.create(["sslcert", definition]) if self.label.has_label(ssl_label): - filename = "/etc/haproxy/certs/{}.{}.pem".format( - d[ssl_label], str(time.time()) + self.ssl_cert_increment += 1 + filename = "{}/{}.{}.pem".format( + self.ssl_cert_folder, d[host_label], str(self.ssl_cert_increment) ) easymapping[key]["ssl_cert"] = filename with open(filename, 'wb') as file: diff --git a/tests/expected/no-services.txt b/tests/expected/no-services.txt new file mode 100644 index 0000000..f5ecac3 --- /dev/null +++ b/tests/expected/no-services.txt @@ -0,0 +1,12 @@ +global + log stdout format raw local0 info + maxconn 2000 + tune.ssl.default-dh-param 2048 + +defaults + log global + + timeout connect 3s + timeout client 10s + timeout server 10m + diff --git a/tests/expected/services-tcp.txt b/tests/expected/services-tcp.txt new file mode 100644 index 0000000..66cb16d --- /dev/null +++ b/tests/expected/services-tcp.txt @@ -0,0 +1,26 @@ +global + log stdout format raw local0 info + maxconn 2000 + tune.ssl.default-dh-param 2048 + +defaults + log global + + timeout connect 3s + timeout client 10s + timeout server 10m + + +frontend tcp_in_31339_1 + bind *:31339 + mode tcp + option tcplog + log global + default_backend srv_agent_quantum_local_31339_1 + +backend srv_agent_quantum_local_31339_1 + balance roundrobin + mode tcp + option tcp-check + tcp-check connect ssl + server srv test_agent:9001 check weight 1 verify none diff --git a/tests/expected/services.txt b/tests/expected/services.txt new file mode 100644 index 0000000..3e38ed6 --- /dev/null +++ b/tests/expected/services.txt @@ -0,0 +1,95 @@ +global + log stdout format raw local0 info + maxconn 2000 + tune.ssl.default-dh-param 2048 + +defaults + log global + + timeout connect 3s + timeout client 10s + timeout server 10m + + +frontend tcp_in_31339_1 + bind *:31339 + mode tcp + option tcplog + log global + default_backend srv_agent_quantum_example_org_31339_1 + +backend srv_agent_quantum_example_org_31339_1 + balance roundrobin + mode tcp + option tcp-check + tcp-check connect + server srv my-stack_agent:9001 check weight 1 + +frontend http_in_31337_2 + 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_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 + +backend srv_cadvisor_quantum_example_org_31337_2 + 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 my-stack_cadvisor:8080 check weight 1 +backend srv_node-exporter_quantum_example_org_31337_2 + 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 my-stack_node-exporter:9100 check weight 1 + +frontend http_in_80_3 + bind *:80 + 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_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 + +backend srv_www_somehost_com_br_80_3 + 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 some-service:80 check weight 1 + +frontend http_in_443_4 + bind *:443 ssl crt /tmp/www.somehost.com.br.1.pem + 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 some-service:80 check weight 1 diff --git a/tests/fixtures/services b/tests/fixtures/services index 2d832e7..c213a8d 100644 --- a/tests/fixtures/services +++ b/tests/fixtures/services @@ -3,3 +3,4 @@ my-stack_agent={"com.byjg.easyhaproxy.definitions":"agent","com.byjg.easyhaproxy my-stack_cadvisor={"com.byjg.easyhaproxy.definitions":"cadvisor","com.byjg.easyhaproxy.host.cadvisor":"cadvisor.quantum.example.org","com.byjg.easyhaproxy.localport.cadvisor":"8080","com.byjg.easyhaproxy.port.cadvisor":"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={"com.byjg.easyhaproxy.definitions":"exp","com.byjg.easyhaproxy.host.exp":"node-exporter.quantum.example.org","com.byjg.easyhaproxy.localport.exp":"9100","com.byjg.easyhaproxy.port.exp":"31337","com.docker.stack.image":"stefanprodan/swarmprom-node-exporter:v0.16.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"} 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={"com.byjg.easyhaproxy.definitions":"http,https","com.byjg.easyhaproxy.port.http":"80","com.byjg.easyhaproxy.host.http":"www.somehost.com.br","com.byjg.easyhaproxy.localport.http":"80","com.byjg.easyhaproxy.redirect.http":"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","com.byjg.easyhaproxy.port.https":"443","com.byjg.easyhaproxy.host.https":"www.somehost.com.br","com.byjg.easyhaproxy.localport.https":"80","com.byjg.easyhaproxy.redirect.https":"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","com.byjg.easyhaproxy.sslcert.https":"U29tZSBQRU0gQ2VydGlmaWNhdGU="} \ No newline at end of file diff --git a/tests/test_parser.py b/tests/test_parser.py index c9ff08e..4f076be 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -19,11 +19,13 @@ def test_parser_doesnt_crash(): "customerrors": False } - cfg = easymapping.HaproxyConfigGenerator(result) + cfg = easymapping.HaproxyConfigGenerator(result, "/tmp") haproxy_config = cfg.generate(lineList) + assert len(haproxy_config) > 0 - assert "frontend" not in haproxy_config - assert "backend" not in haproxy_config + 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 def test_parser_finds_services(): @@ -33,14 +35,20 @@ def test_parser_finds_services(): "customerrors": False } - cfg = easymapping.HaproxyConfigGenerator(result) - haproxy_config = cfg.generate(lineList) - assert len(haproxy_config) > 0 - assert "mode tcp" in haproxy_config - assert "mode http" in haproxy_config + cert_file = "/tmp/www.somehost.com.br.1.pem" + if os.path.exists(cert_file): + os.remove(cert_file) - assert "frontend tcp_in_31339_1" in haproxy_config - assert "frontend http_in_31337_2" in haproxy_config + cfg = easymapping.HaproxyConfigGenerator(result, "/tmp") + haproxy_config = cfg.generate(lineList) + + assert len(haproxy_config) > 0 + path = os.path.dirname(os.path.realpath(__file__)) + with open(path + "/expected/services.txt", 'r') as expected_file: + assert expected_file.read() == haproxy_config + + with open(cert_file, 'r') as expected_file: + assert expected_file.read() == "Some PEM Certificate" def test_parser_static(): @@ -48,13 +56,14 @@ 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) + cfg = easymapping.HaproxyConfigGenerator(parsed, "/tmp") 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 + def test_parser_tcp(): lineList = load_fixture("services-tcp") @@ -62,23 +71,11 @@ def test_parser_tcp(): "customerrors": False } - cfg = easymapping.HaproxyConfigGenerator(result) + cfg = easymapping.HaproxyConfigGenerator(result, "/tmp") haproxy_config = cfg.generate(lineList) # print(haproxy_config) - frontend_cfg = "frontend tcp_in_31339_1\n" - frontend_cfg += " bind *:31339\n" - frontend_cfg += " mode tcp\n" - frontend_cfg += " option tcplog\n" - frontend_cfg += " log global\n" - frontend_cfg += " default_backend srv_agent_quantum_local_31339_1\n\n" - assert frontend_cfg in haproxy_config - - backend_cfg = "backend srv_agent_quantum_local_31339_1\n" - backend_cfg += " balance roundrobin\n" - backend_cfg += " mode tcp\n" - backend_cfg += " option tcp-check\n" - backend_cfg += " tcp-check connect ssl\n" - backend_cfg += " server srv test_agent:9001 check weight 1 verify none" - - assert backend_cfg in haproxy_config + assert len(haproxy_config) > 0 + 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