diff options
| -rw-r--r-- | data/templates/load-balancing/haproxy.cfg.j2 | 10 | ||||
| -rw-r--r-- | interface-definitions/load-balancing-haproxy.xml.in | 12 | ||||
| -rwxr-xr-x | smoketest/scripts/cli/test_load_balancing_reverse_proxy.py | 112 | ||||
| -rwxr-xr-x | src/conf_mode/load-balancing-haproxy.py | 2 | 
4 files changed, 134 insertions, 2 deletions
| diff --git a/data/templates/load-balancing/haproxy.cfg.j2 b/data/templates/load-balancing/haproxy.cfg.j2 index 1a8ce13f8..f8e1587f8 100644 --- a/data/templates/load-balancing/haproxy.cfg.j2 +++ b/data/templates/load-balancing/haproxy.cfg.j2 @@ -51,7 +51,13 @@ defaults  {%     for front, front_config in service.items() %}  frontend {{ front }}  {%         set ssl_front =  'ssl crt /run/haproxy/' ~ front_config.ssl.certificate ~ '.pem' if front_config.ssl.certificate is vyos_defined else '' %} -    bind {{ front_config.listen_address if front_config.listen_address if vyos_defined else '*' }}:{{ front_config.port }} {{ ssl_front }} +{%         if front_config.listen_address is vyos_defined %} +{%             for address in front_config.listen_address %} +    bind {{ address | bracketize_ipv6 }}:{{ front_config.port }} {{ ssl_front }} +{%             endfor %} +{%         else %} +    bind :::{{ front_config.port }} v4v6 {{ ssl_front }} +{%         endif %}  {%         if front_config.redirect_http_to_https is vyos_defined %}      http-request redirect scheme https unless { ssl_fc }  {%         endif %} @@ -140,7 +146,7 @@ backend {{ back }}  {%         if back_config.server is vyos_defined %}  {%             set ssl_back =  'ssl ca-file /run/haproxy/' ~ back_config.ssl.ca_certificate ~ '.pem' if back_config.ssl.ca_certificate is vyos_defined else '' %}  {%             for server, server_config in back_config.server.items() %} -    server {{ server }} {{ server_config.address }}:{{ server_config.port }} {{ 'check' if server_config.check is vyos_defined }} {{ ssl_back }} +    server {{ server }} {{ server_config.address }}:{{ server_config.port }}{{ ' check' if server_config.check is vyos_defined }}{{ ' send-proxy' if server_config.send_proxy is vyos_defined }}{{ ' send-proxy-v2' if server_config.send_proxy_v2 is vyos_defined }} {{ ssl_back }}  {%             endfor %}  {%         endif %}  {%         if back_config.timeout.check is vyos_defined %} diff --git a/interface-definitions/load-balancing-haproxy.xml.in b/interface-definitions/load-balancing-haproxy.xml.in index e295dcb63..f955a2fb7 100644 --- a/interface-definitions/load-balancing-haproxy.xml.in +++ b/interface-definitions/load-balancing-haproxy.xml.in @@ -131,6 +131,18 @@                      </properties>                    </leafNode>                    #include <include/port-number.xml.i> +                  <leafNode name="send-proxy"> +                    <properties> +                      <help>Send a Proxy Protocol version 1 header (text format)</help> +                      <valueless/> +                    </properties> +                  </leafNode> +                  <leafNode name="send-proxy-v2"> +                    <properties> +                      <help>Send a Proxy Protocol version 2 header (binary format)</help> +                      <valueless/> +                    </properties> +                  </leafNode>                  </children>                </tagNode>                <node name="ssl"> diff --git a/smoketest/scripts/cli/test_load_balancing_reverse_proxy.py b/smoketest/scripts/cli/test_load_balancing_reverse_proxy.py new file mode 100755 index 000000000..23a681321 --- /dev/null +++ b/smoketest/scripts/cli/test_load_balancing_reverse_proxy.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.util import process_named_running +from vyos.util import read_file + +PROCESS_NAME = 'haproxy' +HAPROXY_CONF = '/run/haproxy/haproxy.cfg' +base_path = ['load-balancing', 'reverse-proxy'] +proxy_interface = 'eth1' + + +class TestLoadBalancingReverseProxy(VyOSUnitTestSHIM.TestCase): +    def tearDown(self): +        # Check for running process +        self.assertTrue(process_named_running(PROCESS_NAME)) + +        self.cli_delete(['interfaces', 'ethernet', proxy_interface, 'address']) +        self.cli_delete(base_path) +        self.cli_commit() + +        # Process must be terminated after deleting the config +        self.assertFalse(process_named_running(PROCESS_NAME)) + +    def test_01_lb_reverse_proxy_domain(self): +        domains_bk_first = ['n1.example.com', 'n2.example.com', 'n3.example.com'] +        domain_bk_second = 'n5.example.com' +        frontend = 'https_front' +        front_port = '4433' +        bk_server_first = '192.0.2.11' +        bk_server_second = '192.0.2.12' +        bk_first_name = 'bk-01' +        bk_second_name = 'bk-02' +        bk_server_port = '9090' +        mode = 'http' +        rule_ten = '10' +        rule_twenty = '20' +        send_proxy = 'send-proxy' +        max_connections = '1000' + +        back_base = base_path + ['backend'] + +        self.cli_set(base_path + ['service', frontend, 'mode', mode]) +        self.cli_set(base_path + ['service', frontend, 'port', front_port]) +        for domain in domains_bk_first: +            self.cli_set(base_path + ['service', frontend, 'rule', rule_ten, 'domain-name', domain]) +        self.cli_set(base_path + ['service', frontend, 'rule', rule_ten, 'set', 'backend', bk_first_name]) +        self.cli_set(base_path + ['service', frontend, 'rule', rule_twenty, 'domain-name', domain_bk_second]) +        self.cli_set(base_path + ['service', frontend, 'rule', rule_twenty, 'set', 'backend', bk_second_name]) + +        self.cli_set(back_base + [bk_first_name, 'mode', mode]) +        self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, 'address', bk_server_first]) +        self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, 'port', bk_server_port]) +        self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, send_proxy]) + +        self.cli_set(back_base + [bk_second_name, 'mode', mode]) +        self.cli_set(back_base + [bk_second_name, 'server', bk_second_name, 'address', bk_server_second]) +        self.cli_set(back_base + [bk_second_name, 'server', bk_second_name, 'port', bk_server_port]) + +        self.cli_set(base_path + ['global-parameters', 'max-connections', max_connections]) + +        # commit changes +        self.cli_commit() + +        config = read_file(HAPROXY_CONF) + +        # Global +        self.assertIn(f'maxconn {max_connections}', config) + +        # Frontend +        self.assertIn(f'frontend {frontend}', config) +        self.assertIn(f'bind :::{front_port} v4v6', config) +        self.assertIn(f'mode {mode}', config) +        for domain in domains_bk_first: +            self.assertIn(f'acl {rule_ten} hdr(host) -i {domain}', config) +        self.assertIn(f'use_backend {bk_first_name} if {rule_ten}', config) +        self.assertIn(f'acl {rule_twenty} hdr(host) -i {domain_bk_second}', config) +        self.assertIn(f'use_backend {bk_second_name} if {rule_twenty}', config) + +        # Backend +        self.assertIn(f'backend {bk_first_name}', config) +        self.assertIn(f'balance roundrobin', config) +        self.assertIn(f'option forwardfor', config) +        self.assertIn('http-request add-header X-Forwarded-Proto https if { ssl_fc }', config) +        self.assertIn(f'mode {mode}', config) +        self.assertIn(f'server {bk_first_name} {bk_server_first}:{bk_server_port} send-proxy', config) + +        self.assertIn(f'backend {bk_second_name}', config) +        self.assertIn(f'mode {mode}', config) +        self.assertIn(f'server {bk_second_name} {bk_server_second}:{bk_server_port}', config) + + +if __name__ == '__main__': +    unittest.main(verbosity=2) diff --git a/src/conf_mode/load-balancing-haproxy.py b/src/conf_mode/load-balancing-haproxy.py index 938af6cda..b29fdffc7 100755 --- a/src/conf_mode/load-balancing-haproxy.py +++ b/src/conf_mode/load-balancing-haproxy.py @@ -95,6 +95,8 @@ def verify(lb):              if 'address' not in bk_server_conf or 'port' not in bk_server_conf:                  raise ConfigError(f'"backend {back} server {bk_server} address and port" must be configured!') +            if {'send_proxy', 'send_proxy_v2'} <= set(bk_server_conf): +                raise ConfigError(f'Cannot use both "send-proxy" and "send-proxy-v2" for server "{bk_server}"')  def generate(lb):      if not lb: | 
