diff options
| -rw-r--r-- | data/templates/load-balancing/haproxy.cfg.j2 | 154 | ||||
| -rw-r--r-- | data/templates/load-balancing/override_haproxy.conf.j2 | 14 | ||||
| -rw-r--r-- | debian/control | 1 | ||||
| -rw-r--r-- | interface-definitions/include/haproxy/mode.xml.i | 22 | ||||
| -rw-r--r-- | interface-definitions/include/haproxy/rule.xml.i | 130 | ||||
| -rw-r--r-- | interface-definitions/include/haproxy/timeout.xml.i | 45 | ||||
| -rw-r--r-- | interface-definitions/load-balancing-haproxy.xml.in | 245 | ||||
| -rwxr-xr-x | src/conf_mode/load-balancing-haproxy.py | 182 | 
8 files changed, 793 insertions, 0 deletions
| diff --git a/data/templates/load-balancing/haproxy.cfg.j2 b/data/templates/load-balancing/haproxy.cfg.j2 new file mode 100644 index 000000000..3d98d78b7 --- /dev/null +++ b/data/templates/load-balancing/haproxy.cfg.j2 @@ -0,0 +1,154 @@ +# Generated by ${vyos_conf_scripts_dir}/load-balancing-haproxy.py + +global +    log /dev/log local0 +    log /dev/log local1 notice +    chroot /var/lib/haproxy +    stats socket /run/haproxy/admin.sock mode 660 level admin +    stats timeout 30s +    user haproxy +    group haproxy +    daemon + +{% if global_parameters is vyos_defined %} +{%     if global_parameters.max_connections is vyos_defined %} +    maxconn {{ global_parameters.max_connections }} +{%     endif %} + +    # Default SSL material locations +    ca-base /etc/ssl/certs +    crt-base /etc/ssl/private + +{%     if global_parameters.tls.ssl_bind_ciphers is vyos_defined %} +    # https://ssl-config.mozilla.org/#server=haproxy&version=2.6.12-1&config=intermediate&openssl=3.0.8-1&guideline=5.6 +    ssl-default-bind-ciphers {{ global_parameters.tls.ssl_bind_ciphers | join(':') | upper }} +{%     endif %} +    ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 +{%     if global_parameters.tls.tls_version_min is vyos_defined('1.3') %} +    ssl-default-bind-options force-tlsv13 +{%     else %} +    ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets +{%     endif %} +{% endif %} + +defaults +    log     global +    mode    http +    option  dontlognull +    timeout connect 10s +    timeout client  50s +    timeout server  50s +    errorfile 400 /etc/haproxy/errors/400.http +    errorfile 403 /etc/haproxy/errors/403.http +    errorfile 408 /etc/haproxy/errors/408.http +    errorfile 500 /etc/haproxy/errors/500.http +    errorfile 502 /etc/haproxy/errors/502.http +    errorfile 503 /etc/haproxy/errors/503.http +    errorfile 504 /etc/haproxy/errors/504.http + +# Frontend +{% if server is vyos_defined %} +{%     for front, front_config in server.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.redirect_http_to_https is vyos_defined %} +    http-request redirect scheme https unless { ssl_fc } +{%         endif %} +{%         if front_config.mode is vyos_defined %} +    mode {{ front_config.mode }} +{%         endif %} +{%         if front_config.rule is vyos_defined %} +{%             for rule, rule_config in front_config.rule.items() %} +    # rule {{ rule }} +{%                 if rule_config.domain_name is vyos_defined and rule_config.set.server is vyos_defined %} +{%                     set rule_options = 'hdr(host)' %} +{%                     if rule_config.ssl is vyos_defined %} +{%                         set ssl_rule_translate = {'req-ssl-sni': 'req_ssl_sni', 'ssl-fc-sni': 'ssl_fc_sni', 'ssl-fc-sni-end': 'ssl_fc_sni_end'} %} +{%                         set rule_options = ssl_rule_translate[rule_config.ssl] %} +{%                     endif %} +    acl {{ rule }} {{ rule_options }} -i {{ rule_config.domain_name }} +    use_backend {{ rule_config.set.server }} if {{ rule }} +{%                 endif %} +{# path url #} +{%                 if rule_config.url_path is vyos_defined and rule_config.set.redirect_location is vyos_defined %} +{%                     set path_mod_translate = {'begin': '-i -m beg', 'end': '-i -m end', 'exact': ''} %} +{%                     for path, path_config in rule_config.url_path.items() %} +{%                         for url in path_config %} +    acl {{ rule }} path {{ path_mod_translate[path] }} {{ url }} +{%                         endfor %} +{%                     endfor %} +    http-request redirect location {{ rule_config.set.redirect_location }} code 301 if {{ rule }} +{%                 endif %} +{# endpath #} +{%             endfor %} +{%         endif %} +{%         if front_config.backend is vyos_defined %} +{%             for backend in front_config.backend %} +    default_backend {{ backend }} +{%             endfor %} +{%         endif %} + +{%     endfor %} +{% endif %} + +# Backend +{% if backend is vyos_defined %} +{%     for back, back_config in backend.items() %} +backend {{ back }} +{%         if back_config.balance is vyos_defined %} +{%             set balance_translate = {'least-connection': 'leastconn', 'round-robin': 'roundrobin', 'source-address': 'source'} %} +    balance {{ balance_translate[back_config.balance] }} +{%         endif %} +{# If mode is not TCP skip Forwarded #} +{%         if back_config.mode is not vyos_defined('tcp') %} +    option forwardfor +    http-request set-header X-Forwarded-Port %[dst_port] +    http-request add-header X-Forwarded-Proto https if { ssl_fc } +{%         endif %} +{%         if back_config.mode is vyos_defined %} +    mode {{ back_config.mode }} +{%         endif %} +{%         if back_config.rule is vyos_defined %} +{%             for rule, rule_config in back_config.rule.items() %} +{%                 if rule_config.domain_name is vyos_defined and rule_config.set.server is vyos_defined %} +{%                     set rule_options = 'hdr(host)' %} +{%                     if rule_config.ssl is vyos_defined %} +{%                         set ssl_rule_translate = {'req-ssl-sni': 'req_ssl_sni', 'ssl-fc-sni': 'ssl_fc_sni', 'ssl-fc-sni-end': 'ssl_fc_sni_end'} %} +{%                         set rule_options = ssl_rule_translate[rule_config.ssl] %} +{%                     endif %} +    acl {{ rule }} {{ rule_options }} -i {{ rule_config.domain_name }} +    use-server {{ rule_config.set.server }} if {{ rule }} +{%                 endif %} +{# path url #} +{%                 if rule_config.url_path is vyos_defined and rule_config.set.redirect_location is vyos_defined %} +{%                     set path_mod_translate = {'begin': '-i -m beg', 'end': '-i -m end', 'exact': ''} %} +{%                     for path, path_config in rule_config.url_path.items() %} +{%                         for url in path_config %} +    acl {{ rule }} path {{ path_mod_translate[path] }} {{ url }} +{%                         endfor %} +{%                     endfor %} +    http-request redirect location {{ rule_config.set.redirect_location }} code 301 if {{ rule }} +{%                 endif %} +{# endpath #} +{%             endfor %} +{%         endif %} +{%         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 }} +{%             endfor %} +{%         endif %} +{%         if back_config.timeout.check is vyos_defined %} +    timeout check {{ back_config.timeout.check }} +{%         endif %} +{%         if back_config.timeout.connect is vyos_defined %} +    timeout connect {{ back_config.timeout.connect }} +{%         endif %} +{%         if back_config.timeout.server is vyos_defined %} +    timeout server {{ back_config.timeout.server }} +{%         endif %} + +{%     endfor %} +{% endif %} + diff --git a/data/templates/load-balancing/override_haproxy.conf.j2 b/data/templates/load-balancing/override_haproxy.conf.j2 new file mode 100644 index 000000000..395b5d279 --- /dev/null +++ b/data/templates/load-balancing/override_haproxy.conf.j2 @@ -0,0 +1,14 @@ +{% set haproxy_command = 'ip vrf exec ' ~ vrf ~ ' ' if vrf is vyos_defined else '' %} +[Unit] +StartLimitIntervalSec=0 +After=vyos-router.service +ConditionPathExists=/run/haproxy/haproxy.cfg + +[Service] +EnvironmentFile= +Environment= +Environment="CONFIG=/run/haproxy/haproxy.cfg" "PIDFILE=/run/haproxy.pid" "EXTRAOPTS=-S /run/haproxy-master.sock" +ExecStart= +ExecStart={{ haproxy_command }}/usr/sbin/haproxy -Ws -f /run/haproxy/haproxy.cfg -p /run/haproxy.pid -S /run/haproxy-master.sock +Restart=always +RestartSec=10 diff --git a/debian/control b/debian/control index 4a2706fc3..ec08968c9 100644 --- a/debian/control +++ b/debian/control @@ -65,6 +65,7 @@ Depends:    fuse-overlayfs,    libpam-google-authenticator,    grc, +  haproxy,    hostapd,    hsflowd,    hvinfo, diff --git a/interface-definitions/include/haproxy/mode.xml.i b/interface-definitions/include/haproxy/mode.xml.i new file mode 100644 index 000000000..672ea65b4 --- /dev/null +++ b/interface-definitions/include/haproxy/mode.xml.i @@ -0,0 +1,22 @@ +<!-- include start from haproxy/mode.xml.i --> +<leafNode name="mode"> +  <properties> +    <help>Proxy mode</help> +    <completionHelp> +      <list>http tcp</list> +    </completionHelp> +    <constraintErrorMessage>invalid value</constraintErrorMessage> +    <valueHelp> +      <format>http</format> +      <description>HTTP proxy mode</description> +    </valueHelp> +    <valueHelp> +      <format>tcp</format> +      <description>TCP proxy mode</description> +    </valueHelp> +    <constraint> +      <regex>(http|tcp)</regex> +    </constraint> +  </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/haproxy/rule.xml.i b/interface-definitions/include/haproxy/rule.xml.i new file mode 100644 index 000000000..9d9f63c9c --- /dev/null +++ b/interface-definitions/include/haproxy/rule.xml.i @@ -0,0 +1,130 @@ +<!-- include start from haproxy/rule.xml.i --> +<tagNode name="rule"> +  <properties> +    <help>Proxy rule number</help> +    <valueHelp> +      <format>u32:1-10000</format> +      <description>Number for this proxy rule</description> +    </valueHelp> +    <constraint> +      <validator name="numeric" argument="--range 1-10000"/> +    </constraint> +    <constraintErrorMessage>Proxy rule number must be between 1 and 10000</constraintErrorMessage> +  </properties> +  <children> +    <leafNode name="domain-name"> +      <properties> +        <help>Domain name to match</help> +        <valueHelp> +          <format>txt</format> +          <description>Domain address to match</description> +        </valueHelp> +        <constraint> +          <validator name="fqdn"/> +        </constraint> +      </properties> +    </leafNode> +    <node name="set"> +      <properties> +        <help>Proxy modifications</help> +      </properties> +      <children> +        <leafNode name="redirect-location"> +          <properties> +            <help>Set URL location</help> +            <valueHelp> +              <format>url</format> +              <description>Set URL location</description> +            </valueHelp> +            <constraint> +              <regex>^\/[\w\-.\/]+$</regex> +            </constraint> +            <constraintErrorMessage>Incorrect URL format</constraintErrorMessage> +          </properties> +        </leafNode> +        <leafNode name="server"> +          <properties> +            <help>Server name</help> +            <constraint> +              <regex>[-_a-zA-Z0-9]+</regex> +            </constraint> +            <constraintErrorMessage>Server name must be alphanumeric and can contain hyphen and underscores</constraintErrorMessage> +          </properties> +        </leafNode> +      </children> +    </node> +    <leafNode name="ssl"> +      <properties> +        <help>SSL match options</help> +        <completionHelp> +          <list>req-ssl-sni ssl-fc-sni</list> +        </completionHelp> +        <valueHelp> +          <format>req-ssl-sni</format> +          <description>SSL Server Name Indication (SNI) request match</description> +        </valueHelp> +        <valueHelp> +          <format>ssl-fc-sni</format> +          <description>SSL frontend connection Server Name Indication match</description> +        </valueHelp> +        <valueHelp> +          <format>ssl-fc-sni-end</format> +          <description>SSL frontend match end of connection Server Name Indication</description> +        </valueHelp> +        <constraint> +          <regex>(req-ssl-sni|ssl-fc-sni|ssl-fc-sni-end)</regex> +        </constraint> +      </properties> +    </leafNode> +    <node name="url-path"> +      <properties> +        <help>URL path match</help> +      </properties> +      <children> +        <leafNode name="begin"> +          <properties> +            <help>Begin URL match</help> +            <valueHelp> +              <format>url</format> +              <description>Begin URL</description> +            </valueHelp> +            <constraint> +              <regex>^\/[\w\-.\/]+$</regex> +            </constraint> +            <constraintErrorMessage>Incorrect URL format</constraintErrorMessage> +            <multi/> +          </properties> +        </leafNode> +        <leafNode name="end"> +          <properties> +            <help>End URL match</help> +            <valueHelp> +              <format>url</format> +              <description>End URL</description> +            </valueHelp> +            <constraint> +              <regex>^\/[\w\-.\/]+$</regex> +            </constraint> +            <constraintErrorMessage>Incorrect URL format</constraintErrorMessage> +            <multi/> +          </properties> +        </leafNode> +        <leafNode name="exact"> +          <properties> +            <help>Exactly URL match</help> +            <valueHelp> +              <format>url</format> +              <description>Exactly URL</description> +            </valueHelp> +            <constraint> +              <regex>^\/[\w\-.\/]+$</regex> +            </constraint> +            <constraintErrorMessage>Incorrect URL format</constraintErrorMessage> +            <multi/> +          </properties> +        </leafNode> +      </children> +    </node> +  </children> +</tagNode> +<!-- include end --> diff --git a/interface-definitions/include/haproxy/timeout.xml.i b/interface-definitions/include/haproxy/timeout.xml.i new file mode 100644 index 000000000..250b35683 --- /dev/null +++ b/interface-definitions/include/haproxy/timeout.xml.i @@ -0,0 +1,45 @@ +<!-- include start from haproxy/timeout.xml.i --> +<node name="timeout"> +  <properties> +    <help>Tiemout options</help> +  </properties> +  <children> +    <leafNode name="check"> +      <properties> +        <help>Timeout in seconds for established connections</help> +        <valueHelp> +          <format>u32:1-3600</format> +          <description>Check timeout in seconds</description> +        </valueHelp> +        <constraint> +          <validator name="numeric" argument="--range 1-3600"/> +        </constraint> +      </properties> +    </leafNode> +    <leafNode name="connect"> +      <properties> +        <help>Set the maximum time to wait for a connection attempt to a server to succeed</help> +        <valueHelp> +          <format>u32:1-3600</format> +          <description>Connect timeout in seconds</description> +        </valueHelp> +        <constraint> +          <validator name="numeric" argument="--range 1-3600"/> +        </constraint> +      </properties> +    </leafNode> +    <leafNode name="server"> +      <properties> +        <help>Set the maximum inactivity time on the server side</help> +        <valueHelp> +          <format>u32:1-3600</format> +          <description>Server timeout in seconds</description> +        </valueHelp> +        <constraint> +          <validator name="numeric" argument="--range 1-3600"/> +        </constraint> +      </properties> +    </leafNode> +  </children> +</node> +<!-- include end --> diff --git a/interface-definitions/load-balancing-haproxy.xml.in b/interface-definitions/load-balancing-haproxy.xml.in new file mode 100644 index 000000000..268f4cd18 --- /dev/null +++ b/interface-definitions/load-balancing-haproxy.xml.in @@ -0,0 +1,245 @@ +<?xml version="1.0"?> +<interfaceDefinition> +  <node name="load-balancing"> +    <children> +      <node name="reverse-proxy" owner="${vyos_conf_scripts_dir}/load-balancing-haproxy.py"> +        <properties> +          <help>Configure reverse-proxy</help> +        </properties> +        <children> +          <tagNode name="server"> +            <properties> +              <help>Frontend name</help> +              <constraint> +                <regex>[-_a-zA-Z0-9]+</regex> +              </constraint> +              <constraintErrorMessage>Server name must be alphanumeric and can contain hyphen and underscores</constraintErrorMessage> +            </properties> +            <children> +              <leafNode name="backend"> +                <properties> +                  <help>Backend member</help> +                  <constraint> +                    <regex>[-_a-zA-Z0-9]+</regex> +                  </constraint> +                  <constraintErrorMessage>Backend name must be alphanumeric and can contain hyphen and underscores</constraintErrorMessage> +                  <valueHelp> +                    <format>txt</format> +                    <description>Backend name</description> +                  </valueHelp> +                  <completionHelp> +                    <path>load-balancing reverse-proxy backend</path> +                  </completionHelp> +                  <multi/> +                </properties> +              </leafNode> +              #include <include/generic-description.xml.i> +              #include <include/listen-address.xml.i> +              #include <include/haproxy/mode.xml.i> +              #include <include/port-number.xml.i> +              #include <include/haproxy/rule.xml.i> +              <leafNode name="redirect-http-to-https"> +                <properties> +                  <help>Redirect HTTP to HTTPS</help> +                  <valueless/> +                </properties> +              </leafNode> +              <node name="ssl"> +                <properties> +                  <help>SSL Certificate, SSL Key and CA</help> +                </properties> +                <children> +                  #include <include/pki/certificate.xml.i> +                </children> +              </node> +            </children> +          </tagNode> +          <tagNode name="backend"> +            <properties> +              <help>Backend name</help> +              <constraint> +                <regex>[-_a-zA-Z0-9]+</regex> +              </constraint> +              <constraintErrorMessage>Backend name must be alphanumeric and can contain hyphen and underscores</constraintErrorMessage> +            </properties> +            <children> +              <leafNode name="balance"> +                <properties> +                  <help>Load-balancing algorithm</help> +                  <completionHelp> +                    <list>source-address round-robin least-connection</list> +                  </completionHelp> +                  <valueHelp> +                    <format>source-address</format> +                    <description>Based on hash of source IP address</description> +                  </valueHelp> +                  <valueHelp> +                    <format>round-robin</format> +                    <description>Round robin</description> +                  </valueHelp> +                  <valueHelp> +                    <format>least-connection</format> +                    <description>Least connection</description> +                  </valueHelp> +                  <constraint> +                    <regex>(source-address|round-robin|least-connection)</regex> +                  </constraint> +                </properties> +                <defaultValue>round-robin</defaultValue> +              </leafNode> +              #include <include/generic-description.xml.i> +              #include <include/haproxy/mode.xml.i> +              <node name="parameters"> +                <properties> +                  <help>Backend parameters</help> +                </properties> +                <children> +                  <leafNode name="http-check"> +                    <properties> +                      <help>HTTP health check</help> +                      <valueless/> +                    </properties> +                  </leafNode> +                </children> +              </node> +              #include <include/haproxy/rule.xml.i> +              <tagNode name="server"> +                <properties> +                  <help>Backend server name</help> +                </properties> +                <children> +                  <leafNode name="address"> +                    <properties> +                      <help>Backend server address</help> +                      <valueHelp> +                        <format>ipv4</format> +                        <description>IPv4 unicast peer address</description> +                      </valueHelp> +                      <valueHelp> +                        <format>ipv6</format> +                        <description>IPv6 unicast peer address</description> +                      </valueHelp> +                      <constraint> +                        <validator name="ipv4-address"/> +                        <validator name="ipv6-address"/> +                      </constraint> +                    </properties> +                  </leafNode> +                  <leafNode name="check"> +                    <properties> +                      <help>Active health check backend server</help> +                      <valueless/> +                    </properties> +                  </leafNode> +                  #include <include/port-number.xml.i> +                </children> +              </tagNode> +              <node name="ssl"> +                <properties> +                  <help>SSL Certificate, SSL Key and CA</help> +                </properties> +                <children> +                  #include <include/pki/ca-certificate.xml.i> +                </children> +              </node> +              #include <include/haproxy/timeout.xml.i> +            </children> +          </tagNode> +          <node name="global-parameters"> +            <properties> +              <help>Global perfomance parameters and limits</help> +            </properties> +            <children> +              <leafNode name="max-connections"> +                <properties> +                  <help>Maximum allowed connections</help> +                  <valueHelp> +                    <format>u32:1-2000000</format> +                    <description>Maximum allowed connections</description> +                  </valueHelp> +                  <constraint> +                    <validator name="numeric" argument="--range 1-2000000"/> +                  </constraint> +                </properties> +              </leafNode> +              <node name="tls"> +                <properties> +                  <help>Transport Layer Security (TLS) options</help> +                </properties> +                <children> +                  <leafNode name="ssl-bind-ciphers"> +                    <properties> +                      <help>SSL default bind ciphers</help> +                      <completionHelp> +                        <list>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</list> +                      </completionHelp> +                      <valueHelp> +                        <format>ecdhe-ecdsa-aes128-gcm-sha256</format> +                        <description>ecdhe-ecdsa-aes128-gcm-sha256</description> +                      </valueHelp> +                      <valueHelp> +                        <format>ecdhe-rsa-aes128-gcm-sha256</format> +                        <description>ecdhe-rsa-aes128-gcm-sha256</description> +                      </valueHelp> +                      <valueHelp> +                        <format>ecdhe-ecdsa-aes256-gcm-sha384</format> +                        <description>ecdhe-ecdsa-aes256-gcm-sha384</description> +                      </valueHelp> +                      <valueHelp> +                        <format>ecdhe-rsa-aes256-gcm-sha384</format> +                        <description>ecdhe-rsa-aes256-gcm-sha384</description> +                      </valueHelp> +                      <valueHelp> +                        <format>ecdhe-ecdsa-chacha20-poly1305</format> +                        <description>ecdhe-ecdsa-chacha20-poly1305</description> +                      </valueHelp> +                      <valueHelp> +                        <format>ecdhe-rsa-chacha20-poly1305</format> +                        <description>ecdhe-rsa-chacha20-poly1305</description> +                      </valueHelp> +                      <valueHelp> +                        <format>dhe-rsa-aes128-gcm-sha256</format> +                        <description>dhe-rsa-aes128-gcm-sha256</description> +                      </valueHelp> +                      <valueHelp> +                        <format>dhe-rsa-aes256-gcm-sha384</format> +                        <description>dhe-rsa-aes256-gcm-sha384</description> +                      </valueHelp> +                      <constraint> +                        <regex>(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) +                        </regex> +                      </constraint> +                      <multi/> +                    </properties> +                    <defaultValue>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</defaultValue> +                  </leafNode> +                  <leafNode name="tls-version-min"> +                    <properties> +                      <help>Specify the minimum required TLS version</help> +                      <completionHelp> +                        <list>1.2 1.3</list> +                      </completionHelp> +                      <valueHelp> +                        <format>1.2</format> +                        <description>TLS v1.2</description> +                      </valueHelp> +                      <valueHelp> +                        <format>1.3</format> +                        <description>TLS v1.3</description> +                      </valueHelp> +                      <constraint> +                        <regex>(1.2|1.3)</regex> +                      </constraint> +                    </properties> +                    <defaultValue>1.3</defaultValue> +                  </leafNode> +                </children> +              </node> +            </children> +          </node> +          #include <include/interface/vrf.xml.i> +        </children> +      </node> +    </children> +  </node> +</interfaceDefinition> diff --git a/src/conf_mode/load-balancing-haproxy.py b/src/conf_mode/load-balancing-haproxy.py new file mode 100755 index 000000000..d2b895fbe --- /dev/null +++ b/src/conf_mode/load-balancing-haproxy.py @@ -0,0 +1,182 @@ +#!/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 os + +from sys import exit +from shutil import rmtree + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.util import call +from vyos.util import check_port_availability +from vyos.util import is_listen_port_bind_service +from vyos.pki import wrap_certificate +from vyos.pki import wrap_private_key +from vyos.template import render +from vyos.xml import defaults +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +load_balancing_dir = '/run/haproxy' +load_balancing_conf_file = f'{load_balancing_dir}/haproxy.cfg' +systemd_service = 'haproxy.service' +systemd_override = r'/run/systemd/system/haproxy.service.d/10-override.conf' + + +def get_config(config=None): +    if config: +        conf = config +    else: +        conf = Config() + +    base = ['load-balancing', 'reverse-proxy'] +    lb = conf.get_config_dict(base, +                              get_first_key=True, +                              key_mangling=('-', '_'), +                              no_tag_node_value_mangle=True) + +    if lb: +        lb['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), +                                    get_first_key=True, no_tag_node_value_mangle=True) + +    # We have gathered the dict representation of the CLI, but there are default +    # options which we need to update into the dictionary retrived. +    default_values = defaults(base) +    if 'backend' in default_values: +        del default_values['backend'] +    if lb: +        lb = dict_merge(default_values, lb) + +    if 'backend' in lb: +        for backend in lb['backend']: +            default_balues_backend = defaults(base + ['backend']) +            lb['backend'][backend] = dict_merge(default_balues_backend, lb['backend'][backend]) + +    return lb + + +def verify(lb): +    if not lb: +        return None + +    if 'backend' not in lb or 'server' not in lb: +        raise ConfigError(f'"server" and "backend" must be configured!') + +    for front, front_config in lb['server'].items(): +        if 'port' not in front_config: +            raise ConfigError(f'"{front} server port" must be configured!') +        # We can use redirect to HTTPS without backend section +        if 'backend' not in front_config and 'redirect_http_to_https' not in front_config: +           raise ConfigError(f'"{front} backend" must be configured!') + +        # Check if bind address:port are used by another service +        tmp_address = front_config.get('address', '0.0.0.0') +        tmp_port = front_config['port'] +        if check_port_availability(tmp_address, int(tmp_port), 'tcp') is not True and \ +                not is_listen_port_bind_service(int(tmp_port), 'haproxy'): +            raise ConfigError(f'"TCP" port "{tmp_port}" is used by another service') + +    for back, back_config in lb['backend'].items(): +        if 'server' not in back_config: +            raise ConfigError(f'"{back} server" must be configured!') +        for bk_server, bk_server_conf in back_config['server'].items(): +            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!') + + +def generate(lb): +    if not lb: +        # Delete /run/haproxy/haproxy.cfg +        config_files = [load_balancing_conf_file, systemd_override] +        for file in config_files: +            if os.path.isfile(file): +                os.unlink(file) +        # Delete old directories +        #if os.path.isdir(load_balancing_dir): +        #    rmtree(load_balancing_dir, ignore_errors=True) + +        return None + +    # Create load-balance dir +    if not os.path.isdir(load_balancing_dir): +        os.mkdir(load_balancing_dir) + +    # SSL Certificates for frontend +    for front, front_config in lb['server'].items(): +        if 'ssl' in front_config: +            cert_file_path = os.path.join(load_balancing_dir, 'cert.pem') +            cert_key_path = os.path.join(load_balancing_dir, 'cert.pem.key') +            ca_cert_file_path = os.path.join(load_balancing_dir, 'ca.pem') + +            if 'certificate' in front_config['ssl']: +                #cert_file_path = os.path.join(load_balancing_dir, 'cert.pem') +                #cert_key_path = os.path.join(load_balancing_dir, 'cert.key') +                cert_name = front_config['ssl']['certificate'] +                pki_cert = lb['pki']['certificate'][cert_name] + +                with open(cert_file_path, 'w') as f: +                    f.write(wrap_certificate(pki_cert['certificate'])) + +                if 'private' in pki_cert and 'key' in pki_cert['private']: +                    with open(cert_key_path, 'w') as f: +                        f.write(wrap_private_key(pki_cert['private']['key'])) + +            if 'ca_certificate' in front_config['ssl']: +                ca_name = front_config['ssl']['ca_certificate'] +                pki_ca_cert = lb['pki']['ca'][ca_name] + +                with open(ca_cert_file_path, 'w') as f: +                    f.write(wrap_certificate(pki_ca_cert['certificate'])) + +    # SSL Certificates for backend +    for back, back_config in lb['backend'].items(): +        if 'ssl' in back_config: +            ca_cert_file_path = os.path.join(load_balancing_dir, 'ca.pem') + +            if 'ca_certificate' in back_config['ssl']: +                ca_name = back_config['ssl']['ca_certificate'] +                pki_ca_cert = lb['pki']['ca'][ca_name] + +                with open(ca_cert_file_path, 'w') as f: +                    f.write(wrap_certificate(pki_ca_cert['certificate'])) + +    render(load_balancing_conf_file, 'load-balancing/haproxy.cfg.j2', lb) +    render(systemd_override, 'load-balancing/override_haproxy.conf.j2', lb) + +    return None + + +def apply(lb): +    call('systemctl daemon-reload') +    if not lb: +        call(f'systemctl stop {systemd_service}') +    else: +        call(f'systemctl reload-or-restart {systemd_service}') + +    return None + + +if __name__ == '__main__': +    try: +        c = get_config() +        verify(c) +        generate(c) +        apply(c) +    except ConfigError as e: +        print(e) +        exit(1) | 
