Page MenuHomeVyOS Platform

No OneTemporary

Size
27 KB
Referenced Files
None
Subscribers
None
diff --git a/data/templates/load-balancing/haproxy.cfg.j2 b/data/templates/load-balancing/haproxy.cfg.j2
index a75ee9904..defb76fba 100644
--- a/data/templates/load-balancing/haproxy.cfg.j2
+++ b/data/templates/load-balancing/haproxy.cfg.j2
@@ -1,164 +1,169 @@
# 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.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.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_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 service is vyos_defined %}
{% 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 '' %}
+{% set ssl_front = [] %}
+{% if front_config.ssl.certificate is vyos_defined and front_config.ssl.certificate is iterable %}
+{% for cert in front_config.ssl.certificate %}
+{% set _ = ssl_front.append('crt /run/haproxy/' ~ cert ~ '.pem') %}
+{% endfor %}
+{% endif %}
+{% set ssl_directive = 'ssl' if ssl_front else '' %}
{% if front_config.listen_address is vyos_defined %}
{% for address in front_config.listen_address %}
- bind {{ address | bracketize_ipv6 }}:{{ front_config.port }} {{ ssl_front }}
+ bind {{ address | bracketize_ipv6 }}:{{ front_config.port }} {{ ssl_directive }} {{ ssl_front | join(' ') }}
{% endfor %}
{% else %}
- bind :::{{ front_config.port }} v4v6 {{ ssl_front }}
+ bind :::{{ front_config.port }} v4v6 {{ ssl_directive }} {{ ssl_front | join(' ') }}
{% endif %}
{% 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.backend 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 %}
{% for domain in rule_config.domain_name %}
acl {{ rule }} {{ rule_options }} -i {{ domain }}
{% endfor %}
use_backend {{ rule_config.set.backend }} 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 %}
{% for domain in rule_config.domain_name %}
acl {{ rule }} {{ rule_options }} -i {{ domain }}
{% endfor %}
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 }}{{ ' backup' if server_config.backup 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 %}
timeout check {{ back_config.timeout.check }}s
{% endif %}
{% if back_config.timeout.connect is vyos_defined %}
timeout connect {{ back_config.timeout.connect }}s
{% endif %}
{% if back_config.timeout.server is vyos_defined %}
timeout server {{ back_config.timeout.server }}s
{% endif %}
{% endfor %}
{% endif %}
-
diff --git a/interface-definitions/include/pki/certificate-multi.xml.i b/interface-definitions/include/pki/certificate-multi.xml.i
new file mode 100644
index 000000000..c49c5d9b2
--- /dev/null
+++ b/interface-definitions/include/pki/certificate-multi.xml.i
@@ -0,0 +1,15 @@
+<!-- include start from pki/certificate-multi.xml.i -->
+<leafNode name="certificate">
+ <properties>
+ <help>Certificate in PKI configuration</help>
+ <completionHelp>
+ <path>pki certificate</path>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Name of certificate in PKI configuration</description>
+ </valueHelp>
+ <multi/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/load-balancing-haproxy.xml.in b/interface-definitions/load-balancing-haproxy.xml.in
index 564c335ec..8f6bd3a99 100644
--- a/interface-definitions/load-balancing-haproxy.xml.in
+++ b/interface-definitions/load-balancing-haproxy.xml.in
@@ -1,254 +1,254 @@
<?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="service">
<properties>
<help>Frontend service name</help>
<constraint>
#include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
</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>
#include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
</constraint>
<constraintErrorMessage>Backend name must be alphanumeric and can contain hyphen and underscores</constraintErrorMessage>
<valueHelp>
<format>txt</format>
<description>Name of reverse-proxy backend system</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-frontend.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>
+ #include <include/pki/certificate-multi.xml.i>
</children>
</node>
</children>
</tagNode>
<tagNode name="backend">
<properties>
<help>Backend server name</help>
<constraint>
#include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
</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-backend.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="ip-address"/>
</constraint>
</properties>
</leafNode>
<leafNode name="backup">
<properties>
<help>Use backup server if other servers are not available</help>
<valueless/>
</properties>
</leafNode>
<leafNode name="check">
<properties>
<help>Active health check backend server</help>
<valueless/>
</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">
<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>
<leafNode name="ssl-bind-ciphers">
<properties>
<help>Cipher algorithms ("cipher suite") used during SSL/TLS handshake for all frontend servers</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>
#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
index ec4311bb5..333ebc66c 100755
--- a/src/conf_mode/load-balancing-haproxy.py
+++ b/src/conf_mode/load-balancing-haproxy.py
@@ -1,167 +1,169 @@
#!/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.utils.process import call
from vyos.utils.network import check_port_availability
from vyos.utils.network 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 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)
if lb:
lb = conf.merge_defaults(lb, recursive=True)
return lb
def verify(lb):
if not lb:
return None
if 'backend' not in lb or 'service' not in lb:
raise ConfigError(f'"service" and "backend" must be configured!')
for front, front_config in lb['service'].items():
if 'port' not in front_config:
raise ConfigError(f'"{front} service port" 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!')
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:
# 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['service'].items():
if 'ssl' in front_config:
if 'certificate' in front_config['ssl']:
- cert_name = front_config['ssl']['certificate']
- pki_cert = lb['pki']['certificate'][cert_name]
- cert_file_path = os.path.join(load_balancing_dir, f'{cert_name}.pem')
- cert_key_path = os.path.join(load_balancing_dir, f'{cert_name}.pem.key')
+ cert_names = front_config['ssl']['certificate']
- with open(cert_file_path, 'w') as f:
- f.write(wrap_certificate(pki_cert['certificate']))
+ for cert_name in cert_names:
+ pki_cert = lb['pki']['certificate'][cert_name]
+ cert_file_path = os.path.join(load_balancing_dir, f'{cert_name}.pem')
+ cert_key_path = os.path.join(load_balancing_dir, f'{cert_name}.pem.key')
- 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']))
+ 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]
ca_cert_file_path = os.path.join(load_balancing_dir, f'{ca_name}.pem')
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:
if 'ca_certificate' in back_config['ssl']:
ca_name = back_config['ssl']['ca_certificate']
pki_ca_cert = lb['pki']['ca'][ca_name]
ca_cert_file_path = os.path.join(load_balancing_dir, f'{ca_name}.pem')
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)

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 6, 8:04 AM (1 d, 21 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4623726
Default Alt Text
(27 KB)

Event Timeline