diff options
65 files changed, 1307 insertions, 151 deletions
| diff --git a/data/templates/dynamic-dns/ddclient.conf.j2 b/data/templates/dynamic-dns/ddclient.conf.j2 index c2c9b1dd6..e8ef5ac90 100644 --- a/data/templates/dynamic-dns/ddclient.conf.j2 +++ b/data/templates/dynamic-dns/ddclient.conf.j2 @@ -34,7 +34,9 @@ zone={{ config.zone }}  # DynDNS provider configuration for {{ service }}, {{ dns_record }}  protocol={{ config.protocol }},  max-interval=28d, +{%                     if config.login is vyos_defined %}  login={{ config.login }}, +{%                     endif %}  password='{{ config.password }}',  {%                     if config.server is vyos_defined %}  server={{ config.server }}, diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2 index 7bd9efdce..3e101820c 100644 --- a/data/templates/frr/bgpd.frr.j2 +++ b/data/templates/frr/bgpd.frr.j2 @@ -86,6 +86,9 @@  {% if config.solo is vyos_defined %}   neighbor {{ neighbor }} solo  {% endif %} +{% if config.enforce_first_as is vyos_defined %} + neighbor {{ neighbor }} enforce-first-as +{% endif %}  {% if config.strict_capability_match is vyos_defined %}   neighbor {{ neighbor }} strict-capability-match  {% endif %} diff --git a/data/templates/load-balancing/haproxy.cfg.j2 b/data/templates/load-balancing/haproxy.cfg.j2 new file mode 100644 index 000000000..f8e1587f8 --- /dev/null +++ b/data/templates/load-balancing/haproxy.cfg.j2 @@ -0,0 +1,164 @@ +# 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 '' %} +{%         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 %} +{%         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 }}{{ ' 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 }} +{%         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/data/templates/mdns-repeater/avahi-daemon.j2 b/data/templates/mdns-repeater/avahi-daemon.j2 index 65bb5a306..3aaa7fc82 100644 --- a/data/templates/mdns-repeater/avahi-daemon.j2 +++ b/data/templates/mdns-repeater/avahi-daemon.j2 @@ -2,6 +2,9 @@  use-ipv4=yes  use-ipv6=yes  allow-interfaces={{ interface | join(', ') }} +{% if browse_domain is vyos_defined and browse_domain | length %} +browse-domains={{ browse_domain | join(', ') }} +{% endif %}  disallow-other-stacks=no  [wide-area] @@ -16,3 +19,6 @@ publish-workstation=no  [reflector]  enable-reflector=yes +{% if allow_service is vyos_defined and allow_service | length %} +reflect-filters={{ allow_service | join(', ') }} +{% endif %} diff --git a/data/templates/ocserv/ocserv_config.j2 b/data/templates/ocserv/ocserv_config.j2 index aa1073bca..1401b8b26 100644 --- a/data/templates/ocserv/ocserv_config.j2 +++ b/data/templates/ocserv/ocserv_config.j2 @@ -16,6 +16,12 @@ acct = "radius [config=/run/ocserv/radiusclient.conf]"  {% if "radius" in authentication.mode %}  auth = "radius [config=/run/ocserv/radiusclient.conf{{ ',groupconfig=true' if authentication.radius.groupconfig is vyos_defined else '' }}]" +{%     if authentication.identity_based_config.disabled is not vyos_defined %} +{%         if "group" in authentication.identity_based_config.mode %} +config-per-group = {{ authentication.identity_based_config.directory }} +default-group-config = {{ authentication.identity_based_config.default_config }} +{%         endif %} +{%     endif %}  {% elif "local" in authentication.mode %}  {%     if authentication.mode.local == "password-otp" %}  auth = "plain[passwd=/run/ocserv/ocpasswd,otp=/run/ocserv/users.oath]" @@ -28,6 +34,13 @@ auth = "plain[/run/ocserv/ocpasswd]"  auth = "plain[/run/ocserv/ocpasswd]"  {% endif %} +{% if "identity_based_config" in authentication %} +{%     if "user" in authentication.identity_based_config.mode %} +config-per-user = {{ authentication.identity_based_config.directory }} +default-user-config = {{ authentication.identity_based_config.default_config }} +{%     endif %} +{% endif %} +  {% if ssl.certificate is vyos_defined %}  server-cert = /run/ocserv/cert.pem  server-key = /run/ocserv/cert.key diff --git a/data/templates/rsyslog/rsyslog.conf.j2 b/data/templates/rsyslog/rsyslog.conf.j2 index 0460ae5f0..5352fc367 100644 --- a/data/templates/rsyslog/rsyslog.conf.j2 +++ b/data/templates/rsyslog/rsyslog.conf.j2 @@ -49,7 +49,7 @@ $outchannel {{ file_name }},/var/log/user/{{ file_name }},{{ file_options.archiv  {%             set _ = tmp.append(facility.replace('all', '*') + '.' + facility_options.level) %}  {%         endfor %}  {%         if host_options.protocol is vyos_defined('tcp') %} -{%             if host_options.oct_count is vyos_defined %} +{%             if host_options.format.octet_counted is vyos_defined %}  {{ tmp | join(';') }} @@(o){{ host_name | bracketize_ipv6 }}:{{ host_options.port }};RSYSLOG_SyslogProtocol23Format  {%             else %}  {{ tmp | join(';') }} @@{{ host_name | bracketize_ipv6 }}:{{ host_options.port }} 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/container.xml.in b/interface-definitions/container.xml.in index 9b6d2369d..6651fc642 100644 --- a/interface-definitions/container.xml.in +++ b/interface-definitions/container.xml.in @@ -201,8 +201,7 @@                      <description>IPv6 address</description>                    </valueHelp>                    <constraint> -                    <validator name="ipv4-address"/> -                    <validator name="ipv6-address"/> +                    <validator name="ip-address"/>                    </constraint>                    <multi/>                  </properties> diff --git a/interface-definitions/dns-domain-name.xml.in b/interface-definitions/dns-domain-name.xml.in index e93c49ebd..ef34ecbf5 100644 --- a/interface-definitions/dns-domain-name.xml.in +++ b/interface-definitions/dns-domain-name.xml.in @@ -23,8 +23,7 @@            </valueHelp>            <multi/>            <constraint> -            <validator name="ipv4-address"/> -            <validator name="ipv6-address"/> +            <validator name="ip-address"/>              #include <include/constraint/interface-name.xml.i>            </constraint>          </properties> @@ -74,9 +73,9 @@              <properties>                <help>Host name for static address mapping</help>                <constraint> -                <regex>[A-Za-z0-9][-.A-Za-z0-9]*[A-Za-z0-9]</regex> +                #include <include/constraint/host-name.xml.i>                </constraint> -              <constraintErrorMessage>invalid hostname</constraintErrorMessage> +              <constraintErrorMessage>Host-name must be alphanumeric and can contain hyphens</constraintErrorMessage>              </properties>              <children>                <leafNode name="alias"> diff --git a/interface-definitions/dns-forwarding.xml.in b/interface-definitions/dns-forwarding.xml.in index de6991e06..ced1c9c31 100644 --- a/interface-definitions/dns-forwarding.xml.in +++ b/interface-definitions/dns-forwarding.xml.in @@ -670,8 +670,7 @@                    </valueHelp>                    <multi/>                    <constraint> -                    <validator name="ipv4-address"/> -                    <validator name="ipv6-address"/> +                    <validator name="ip-address"/>                    </constraint>                  </properties>                  <defaultValue>0.0.0.0 ::</defaultValue> diff --git a/interface-definitions/flow-accounting-conf.xml.in b/interface-definitions/flow-accounting-conf.xml.in index 878566b3f..40a9bb423 100644 --- a/interface-definitions/flow-accounting-conf.xml.in +++ b/interface-definitions/flow-accounting-conf.xml.in @@ -230,8 +230,7 @@                      <description>IPv6 server to export NetFlow</description>                    </valueHelp>                    <constraint> -                    <validator name="ipv4-address"/> -                    <validator name="ipv6-address"/> +                    <validator name="ip-address"/>                    </constraint>                  </properties>                  <children> @@ -408,8 +407,7 @@                      <description>IPv6 server to export sFlow</description>                    </valueHelp>                    <constraint> -                    <validator name="ipv4-address"/> -                    <validator name="ipv6-address"/> +                    <validator name="ip-address"/>                    </constraint>                  </properties>                  <children> diff --git a/interface-definitions/high-availability.xml.in b/interface-definitions/high-availability.xml.in index 94253def3..c5e46ed38 100644 --- a/interface-definitions/high-availability.xml.in +++ b/interface-definitions/high-availability.xml.in @@ -129,8 +129,7 @@                          <description>IPv6 ping target address</description>                        </valueHelp>                        <constraint> -                        <validator name="ipv4-address"/> -                        <validator name="ipv6-address"/> +                        <validator name="ip-address"/>                        </constraint>                      </properties>                    </leafNode> @@ -156,8 +155,7 @@                      <description>IPv6 hello source address</description>                    </valueHelp>                    <constraint> -                    <validator name="ipv4-address"/> -                    <validator name="ipv6-address"/> +                    <validator name="ip-address"/>                    </constraint>                  </properties>                </leafNode> @@ -173,8 +171,7 @@                      <description>IPv6 unicast peer address</description>                    </valueHelp>                    <constraint> -                    <validator name="ipv4-address"/> -                    <validator name="ipv6-address"/> +                    <validator name="ip-address"/>                    </constraint>                  </properties>                </leafNode> diff --git a/interface-definitions/https.xml.in b/interface-definitions/https.xml.in index cf30ab2be..5430193b5 100644 --- a/interface-definitions/https.xml.in +++ b/interface-definitions/https.xml.in @@ -36,8 +36,7 @@                      <description>any</description>                    </valueHelp>                    <constraint> -                    <validator name="ipv4-address"/> -                    <validator name="ipv6-address"/> +                    <validator name="ip-address"/>                      <regex>\*</regex>                    </constraint>                  </properties> diff --git a/interface-definitions/include/bgp/neighbor-update-source.xml.i b/interface-definitions/include/bgp/neighbor-update-source.xml.i index c6aa776c2..92e817166 100644 --- a/interface-definitions/include/bgp/neighbor-update-source.xml.i +++ b/interface-definitions/include/bgp/neighbor-update-source.xml.i @@ -20,8 +20,7 @@        <description>Interface as route source</description>      </valueHelp>      <constraint> -      <validator name="ipv4-address"/> -      <validator name="ipv6-address"/> +      <validator name="ip-address"/>        #include <include/constraint/interface-name.xml.i>      </constraint>    </properties> diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i index 527eaf991..d69fd7dab 100644 --- a/interface-definitions/include/bgp/protocol-common-config.xml.i +++ b/interface-definitions/include/bgp/protocol-common-config.xml.i @@ -940,8 +940,7 @@        <description>Interface name</description>      </valueHelp>      <constraint> -      <validator name="ipv4-address"/> -      <validator name="ipv6-address"/> +      <validator name="ip-address"/>        #include <include/constraint/interface-name.xml.i>      </constraint>    </properties> @@ -1017,6 +1016,12 @@          <valueless/>        </properties>      </leafNode> +    <leafNode name="enforce-first-as"> +      <properties> +        <help>Ensure the first AS in the AS path matches the peer AS</help> +        <valueless/> +      </properties> +    </leafNode>      <leafNode name="strict-capability-match">        <properties>          <help>Enable strict capability negotiation</help> diff --git a/interface-definitions/include/constraint/alpha-numeric-hyphen-underscore.xml.i b/interface-definitions/include/constraint/alpha-numeric-hyphen-underscore.xml.i index eb568d7d9..ba097c6b5 100644 --- a/interface-definitions/include/constraint/alpha-numeric-hyphen-underscore.xml.i +++ b/interface-definitions/include/constraint/alpha-numeric-hyphen-underscore.xml.i @@ -1,3 +1,3 @@ -<!-- include start from include/constraint/alpha-numeric-hyphen-underscore.xml.in --> +<!-- include start from include/constraint/alpha-numeric-hyphen-underscore.xml.i -->  <regex>[-_a-zA-Z0-9]+</regex>  <!-- include end --> diff --git a/interface-definitions/include/constraint/host-name.xml.i b/interface-definitions/include/constraint/host-name.xml.i index 202c200f4..cc9740c16 100644 --- a/interface-definitions/include/constraint/host-name.xml.i +++ b/interface-definitions/include/constraint/host-name.xml.i @@ -1,3 +1,3 @@ -<!-- include start from constraint/host-name.xml.in -->
 +<!-- include start from constraint/host-name.xml.i -->
  <regex>[A-Za-z0-9][-.A-Za-z0-9]*[A-Za-z0-9]</regex>
  <!-- include end -->
 diff --git a/interface-definitions/include/constraint/interface-name-with-wildcard.xml.i b/interface-definitions/include/constraint/interface-name-with-wildcard.xml.i index 09867b380..adff530b6 100644 --- a/interface-definitions/include/constraint/interface-name-with-wildcard.xml.i +++ b/interface-definitions/include/constraint/interface-name-with-wildcard.xml.i @@ -1,4 +1,4 @@ -<!-- include start from constraint/interface-name-with-wildcard.xml.in --> +<!-- include start from constraint/interface-name-with-wildcard.xml.i -->  <regex>(bond|br|dum|en|ersp|eth|gnv|ifb|lan|l2tp|l2tpeth|macsec|peth|ppp|pppoe|pptp|sstp|tun|veth|vti|vtun|vxlan|wg|wlan|wwan)([0-9]?)(\*?)(.+)?|lo</regex>  <validator name="file-path --lookup-path /sys/class/net --directory"/>  <!-- include end --> diff --git a/interface-definitions/include/constraint/interface-name.xml.i b/interface-definitions/include/constraint/interface-name.xml.i index e540e4418..1b14eabf5 100644 --- a/interface-definitions/include/constraint/interface-name.xml.i +++ b/interface-definitions/include/constraint/interface-name.xml.i @@ -1,4 +1,4 @@ -<!-- include start from constraint/interface-name.xml.in --> +<!-- include start from constraint/interface-name.xml.i -->  <regex>(bond|br|dum|en|ersp|eth|gnv|ifb|lan|l2tp|l2tpeth|macsec|peth|ppp|pppoe|pptp|sstp|tun|veth|vti|vtun|vxlan|wg|wlan|wwan)[0-9]+(.\d+)?|lo</regex>  <validator name="file-path --lookup-path /sys/class/net --directory"/>  <!-- include end --> 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-backend.xml.i b/interface-definitions/include/haproxy/rule-backend.xml.i new file mode 100644 index 000000000..a6832d693 --- /dev/null +++ b/interface-definitions/include/haproxy/rule-backend.xml.i @@ -0,0 +1,131 @@ +<!-- 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> +        <multi/> +      </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/rule-frontend.xml.i b/interface-definitions/include/haproxy/rule-frontend.xml.i new file mode 100644 index 000000000..001ae2d80 --- /dev/null +++ b/interface-definitions/include/haproxy/rule-frontend.xml.i @@ -0,0 +1,131 @@ +<!-- 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> +        <multi/> +      </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="backend"> +          <properties> +            <help>Backend 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/include/interface/dhcp-options.xml.i b/interface-definitions/include/interface/dhcp-options.xml.i index 2ed5fd403..8027769ff 100644 --- a/interface-definitions/include/interface/dhcp-options.xml.i +++ b/interface-definitions/include/interface/dhcp-options.xml.i @@ -12,6 +12,10 @@      <leafNode name="host-name">        <properties>          <help>Override system host-name sent to DHCP server</help> +        <constraint> +          #include <include/constraint/host-name.xml.i> +        </constraint> +        <constraintErrorMessage>Host-name must be alphanumeric and can contain hyphens</constraintErrorMessage>        </properties>      </leafNode>      <leafNode name="mtu"> diff --git a/interface-definitions/include/ipsec/local-address.xml.i b/interface-definitions/include/ipsec/local-address.xml.i index 9d267f3f7..71f514950 100644 --- a/interface-definitions/include/ipsec/local-address.xml.i +++ b/interface-definitions/include/ipsec/local-address.xml.i @@ -19,8 +19,7 @@        <description>Allow any IPv4 address present on the system to be used for VPN</description>      </valueHelp>      <constraint> -      <validator name="ipv4-address"/> -      <validator name="ipv6-address"/> +      <validator name="ip-address"/>        <regex>(any)</regex>      </constraint>    </properties> diff --git a/interface-definitions/include/ipsec/remote-address.xml.i b/interface-definitions/include/ipsec/remote-address.xml.i index ba96290d0..91decba3c 100644 --- a/interface-definitions/include/ipsec/remote-address.xml.i +++ b/interface-definitions/include/ipsec/remote-address.xml.i @@ -19,8 +19,7 @@        <description>Allow any IP address of the remote peer</description>      </valueHelp>      <constraint> -      <validator name="ipv4-address"/> -      <validator name="ipv6-address"/> +      <validator name="ip-address"/>        <validator name="fqdn"/>        <regex>(any)</regex>      </constraint> diff --git a/interface-definitions/include/listen-address-single.xml.i b/interface-definitions/include/listen-address-single.xml.i index 30293b338..6cc5aef0a 100644 --- a/interface-definitions/include/listen-address-single.xml.i +++ b/interface-definitions/include/listen-address-single.xml.i @@ -14,8 +14,7 @@        <description>IPv6 address to listen for incoming connections</description>      </valueHelp>      <constraint> -      <validator name="ipv4-address"/> -      <validator name="ipv6-address"/> +      <validator name="ip-address"/>        <validator name="ipv6-link-local"/>      </constraint>    </properties> diff --git a/interface-definitions/include/listen-address-vrf.xml.i b/interface-definitions/include/listen-address-vrf.xml.i index 8c2bdce70..23ecc2476 100644 --- a/interface-definitions/include/listen-address-vrf.xml.i +++ b/interface-definitions/include/listen-address-vrf.xml.i @@ -14,8 +14,7 @@        <description>IPv6 address to listen for incoming connections</description>      </valueHelp>      <constraint> -      <validator name="ipv4-address"/> -      <validator name="ipv6-address"/> +      <validator name="ip-address"/>      </constraint>    </properties>    <children> diff --git a/interface-definitions/include/listen-address.xml.i b/interface-definitions/include/listen-address.xml.i index 48003dbf2..2454f43ff 100644 --- a/interface-definitions/include/listen-address.xml.i +++ b/interface-definitions/include/listen-address.xml.i @@ -15,8 +15,7 @@      </valueHelp>      <multi/>      <constraint> -      <validator name="ipv4-address"/> -      <validator name="ipv6-address"/> +      <validator name="ip-address"/>        <validator name="ipv6-link-local"/>      </constraint>    </properties> diff --git a/interface-definitions/include/name-server-ipv4-ipv6-port.xml.i b/interface-definitions/include/name-server-ipv4-ipv6-port.xml.i index fb0a4f4ae..b326a6537 100644 --- a/interface-definitions/include/name-server-ipv4-ipv6-port.xml.i +++ b/interface-definitions/include/name-server-ipv4-ipv6-port.xml.i @@ -11,8 +11,7 @@        <description>Domain Name Server (DNS) IPv6 address</description>      </valueHelp>      <constraint> -      <validator name="ipv4-address"/> -      <validator name="ipv6-address"/> +      <validator name="ip-address"/>      </constraint>    </properties>    <children> diff --git a/interface-definitions/include/name-server-ipv4-ipv6.xml.i b/interface-definitions/include/name-server-ipv4-ipv6.xml.i index 14973234b..cf483e5d9 100644 --- a/interface-definitions/include/name-server-ipv4-ipv6.xml.i +++ b/interface-definitions/include/name-server-ipv4-ipv6.xml.i @@ -11,8 +11,7 @@        <description>Domain Name Server (DNS) IPv6 address</description>      </valueHelp>      <constraint> -      <validator name="ipv4-address"/> -      <validator name="ipv6-address"/> +      <validator name="ip-address"/>      </constraint>      <multi/>    </properties> diff --git a/interface-definitions/include/radius-server-ipv4-ipv6.xml.i b/interface-definitions/include/radius-server-ipv4-ipv6.xml.i index c593512b4..efd418bb2 100644 --- a/interface-definitions/include/radius-server-ipv4-ipv6.xml.i +++ b/interface-definitions/include/radius-server-ipv4-ipv6.xml.i @@ -16,8 +16,7 @@            <description>RADIUS server IPv6 address</description>          </valueHelp>          <constraint> -          <validator name="ipv4-address"/> -          <validator name="ipv6-address"/> +          <validator name="ip-address"/>          </constraint>        </properties>        <children> diff --git a/interface-definitions/include/static/static-route-bfd.xml.i b/interface-definitions/include/static/static-route-bfd.xml.i index a05a08d12..d588b369f 100644 --- a/interface-definitions/include/static/static-route-bfd.xml.i +++ b/interface-definitions/include/static/static-route-bfd.xml.i @@ -10,28 +10,27 @@          <help>Use BFD multi hop session</help>        </properties>        <children> -       <tagNode name="source"> -         <properties> -           <help>Use source for BFD session</help> -          <valueHelp> -             <format>ipv4</format> -             <description>IPv4 source address</description> -           </valueHelp> -           <valueHelp> -             <format>ipv6</format> -             <description>IPv6 source address</description> -           </valueHelp> -           <constraint> -             <validator name="ipv4-address"/> -             <validator name="ipv6-address"/> -           </constraint> -         </properties> -         <children> -           #include <include/bfd/profile.xml.i> -         </children> -       </tagNode> +        <tagNode name="source"> +          <properties> +            <help>Use source for BFD session</help> +            <valueHelp> +              <format>ipv4</format> +              <description>IPv4 source address</description> +            </valueHelp> +            <valueHelp> +              <format>ipv6</format> +              <description>IPv6 source address</description> +            </valueHelp> +            <constraint> +              <validator name="ip-address"/> +            </constraint> +          </properties> +          <children> +            #include <include/bfd/profile.xml.i> +          </children> +        </tagNode>        </children>      </node>    </children>  </node> -<!-- include end -->
\ No newline at end of file +<!-- include end --> diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in index cf0ff497c..4e061c3e6 100644 --- a/interface-definitions/interfaces-openvpn.xml.in +++ b/interface-definitions/interfaces-openvpn.xml.in @@ -333,8 +333,7 @@                  <description>Remote end IPv6 address</description>                </valueHelp>                <constraint> -                <validator name="ipv4-address"/> -                <validator name="ipv6-address"/> +                <validator name="ip-address"/>                </constraint>                <multi/>              </properties> diff --git a/interface-definitions/interfaces-virtual-ethernet.xml.in b/interface-definitions/interfaces-virtual-ethernet.xml.in index a5702bfc0..864f658da 100644 --- a/interface-definitions/interfaces-virtual-ethernet.xml.in +++ b/interface-definitions/interfaces-virtual-ethernet.xml.in @@ -22,7 +22,6 @@            #include <include/interface/dhcpv6-options.xml.i>            #include <include/interface/disable.xml.i>            #include <include/interface/vrf.xml.i> -          #include <include/interface/netns.xml.i>            <leafNode name="peer-name">              <properties>                <help>Virtual ethernet peer interface name</help> diff --git a/interface-definitions/load-balancing-haproxy.xml.in b/interface-definitions/load-balancing-haproxy.xml.in new file mode 100644 index 000000000..f955a2fb7 --- /dev/null +++ b/interface-definitions/load-balancing-haproxy.xml.in @@ -0,0 +1,248 @@ +<?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> +                </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="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/interface-definitions/load-balancing-wan.xml.in b/interface-definitions/load-balancing-wan.xml.in index 3a2c111ac..c12cab22a 100644 --- a/interface-definitions/load-balancing-wan.xml.in +++ b/interface-definitions/load-balancing-wan.xml.in @@ -179,6 +179,7 @@                          <regex>(ping|ttl|user-defined)</regex>                        </constraint>                      </properties> +                    <defaultValue>ping</defaultValue>                    </leafNode>                  </children>                </tagNode> diff --git a/interface-definitions/ntp.xml.in b/interface-definitions/ntp.xml.in index 558204a06..2275dd61c 100644 --- a/interface-definitions/ntp.xml.in +++ b/interface-definitions/ntp.xml.in @@ -25,8 +25,7 @@                  <description>Fully qualified domain name of NTP server</description>                </valueHelp>                <constraint> -                <validator name="ipv4-address"/> -                <validator name="ipv6-address"/> +                <validator name="ip-address"/>                  <validator name="fqdn"/>                </constraint>              </properties> diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in index 02828c4f6..aa39950c2 100644 --- a/interface-definitions/policy.xml.in +++ b/interface-definitions/policy.xml.in @@ -966,8 +966,7 @@                          <description>Peer IPv6 address</description>                        </valueHelp>                        <constraint> -                        <validator name="ipv4-address"/> -                        <validator name="ipv6-address"/> +                        <validator name="ip-address"/>                        </constraint>                      </properties>                    </leafNode> @@ -1533,8 +1532,7 @@                          <description>IPv6 address</description>                        </valueHelp>                        <constraint> -                        <validator name="ipv4-address"/> -                        <validator name="ipv6-address"/> +                        <validator name="ip-address"/>                        </constraint>                      </properties>                    </leafNode> diff --git a/interface-definitions/protocols-bfd.xml.in b/interface-definitions/protocols-bfd.xml.in index edbac8d0e..9048cf5c2 100644 --- a/interface-definitions/protocols-bfd.xml.in +++ b/interface-definitions/protocols-bfd.xml.in @@ -21,8 +21,7 @@                  <description>BFD peer IPv6 address</description>                </valueHelp>                <constraint> -                <validator name="ipv4-address"/> -                <validator name="ipv6-address"/> +                <validator name="ip-address"/>                </constraint>              </properties>              <children> @@ -48,8 +47,7 @@                          <description>Local IPv6 address used to connect to the peer</description>                        </valueHelp>                        <constraint> -                        <validator name="ipv4-address"/> -                        <validator name="ipv6-address"/> +                        <validator name="ip-address"/>                        </constraint>                      </properties>                    </leafNode> diff --git a/interface-definitions/protocols-rpki.xml.in b/interface-definitions/protocols-rpki.xml.in index c41fa54f2..e9fd04b5f 100644 --- a/interface-definitions/protocols-rpki.xml.in +++ b/interface-definitions/protocols-rpki.xml.in @@ -23,8 +23,7 @@                  <description>Fully qualified domain name of RPKI server</description>                </valueHelp>                <constraint> -                <validator name="ipv4-address"/> -                <validator name="ipv6-address"/> +                <validator name="ip-address"/>                  <validator name="fqdn"/>                </constraint>              </properties> diff --git a/interface-definitions/service-mdns-repeater.xml.in b/interface-definitions/service-mdns-repeater.xml.in index 9a94f1488..4aab9a558 100644 --- a/interface-definitions/service-mdns-repeater.xml.in +++ b/interface-definitions/service-mdns-repeater.xml.in @@ -15,6 +15,32 @@              <children>                #include <include/generic-disable-node.xml.i>                #include <include/generic-interface-multi.xml.i> +              <leafNode name="browse-domain"> +                <properties> +                  <help>mDNS browsing domains in addition to the default one</help> +                  <valueHelp> +                    <format>txt</format> +                    <description>mDNS browsing domain</description> +                  </valueHelp> +                  <constraint> +                    <validator name="fqdn"/> +                  </constraint> +                  <multi/> +                </properties> +              </leafNode> +              <leafNode name="allow-service"> +                <properties> +                  <help>Allowed mDNS services to be repeated</help> +                  <valueHelp> +                    <format>txt</format> +                    <description>mDNS service</description> +                  </valueHelp> +                  <constraint> +                    <regex>[-_.a-zA-Z0-9]+</regex> +                  </constraint> +                  <multi/> +                </properties> +              </leafNode>                <leafNode name="vrrp-disable">                  <properties>                    <help>Disables mDNS repeater on VRRP interfaces not in MASTER state</help> diff --git a/interface-definitions/service-upnp.xml.in b/interface-definitions/service-upnp.xml.in index 1b2e00d91..20e01bfbd 100644 --- a/interface-definitions/service-upnp.xml.in +++ b/interface-definitions/service-upnp.xml.in @@ -120,9 +120,8 @@                <multi/>                <constraint>                  #include <include/constraint/interface-name.xml.i> -                <validator name="ipv4-address"/> +                <validator name="ip-address"/>                  <validator name="ipv4-prefix"/> -                <validator name="ipv6-address"/>                  <validator name="ipv6-prefix"/>                </constraint>              </properties> diff --git a/interface-definitions/snmp.xml.in b/interface-definitions/snmp.xml.in index 6527cabd6..0851b8389 100644 --- a/interface-definitions/snmp.xml.in +++ b/interface-definitions/snmp.xml.in @@ -94,8 +94,7 @@                  <description>IPv6 address to listen for incoming SNMP requests</description>                </valueHelp>                <constraint> -                <validator name="ipv4-address"/> -                <validator name="ipv6-address"/> +                <validator name="ip-address"/>                </constraint>              </properties>              <children> @@ -166,8 +165,7 @@                  <description>IPv6 address</description>                </valueHelp>                <constraint> -                <validator name="ipv4-address"/> -                <validator name="ipv6-address"/> +                <validator name="ip-address"/>                </constraint>              </properties>            </leafNode> @@ -183,8 +181,7 @@                  <description>IPv6 address</description>                </valueHelp>                <constraint> -                <validator name="ipv4-address"/> -                <validator name="ipv6-address"/> +                <validator name="ip-address"/>                </constraint>              </properties>              <children> diff --git a/interface-definitions/system-config-mgmt.xml.in b/interface-definitions/system-config-mgmt.xml.in index 716332d2a..de5a8cc16 100644 --- a/interface-definitions/system-config-mgmt.xml.in +++ b/interface-definitions/system-config-mgmt.xml.in @@ -30,8 +30,7 @@                  <properties>                    <help>Source address or interface for archive server connections</help>                    <constraint> -                    <validator name="ipv4-address"/> -                    <validator name="ipv6-address"/> +                    <validator name="ip-address"/>                      #include <include/constraint/interface-name.xml.i>                    </constraint>                  </properties> diff --git a/interface-definitions/system-sflow.xml.in b/interface-definitions/system-sflow.xml.in index 9c748c24a..c5152abe9 100644 --- a/interface-definitions/system-sflow.xml.in +++ b/interface-definitions/system-sflow.xml.in @@ -25,8 +25,7 @@                  <description>sFlow IPv6 agent address</description>                </valueHelp>                <constraint> -                <validator name="ipv4-address"/> -                <validator name="ipv6-address"/> +                <validator name="ip-address"/>                  <validator name="ipv6-link-local"/>                </constraint>              </properties> @@ -97,8 +96,7 @@                  <description>IPv6 server to export sFlow</description>                </valueHelp>                <constraint> -                <validator name="ipv4-address"/> -                <validator name="ipv6-address"/> +                <validator name="ip-address"/>                </constraint>              </properties>              <children> diff --git a/interface-definitions/vpn-l2tp.xml.in b/interface-definitions/vpn-l2tp.xml.in index ec186cd23..ee0edc3e3 100644 --- a/interface-definitions/vpn-l2tp.xml.in +++ b/interface-definitions/vpn-l2tp.xml.in @@ -38,8 +38,9 @@                      <properties>                        <help>Sent to the client (LAC) in the Host-Name attribute</help>                        <constraint> -                        <regex>[A-Za-z0-9][-.A-Za-z0-9]*[A-Za-z0-9]</regex> +                        #include <include/constraint/host-name.xml.i>                        </constraint> +                      <constraintErrorMessage>Host-name must be alphanumeric and can contain hyphens</constraintErrorMessage>                      </properties>                    </leafNode>                  </children> diff --git a/interface-definitions/vpn-openconnect.xml.in b/interface-definitions/vpn-openconnect.xml.in index a426f604d..75c64a99a 100644 --- a/interface-definitions/vpn-openconnect.xml.in +++ b/interface-definitions/vpn-openconnect.xml.in @@ -71,6 +71,58 @@                    </leafNode>                  </children>                </node> +              <node name="identity-based-config"> +                <properties> +                  <help>Include configuration file by username or RADIUS group attribute</help> +                </properties> +                <children> +                  #include <include/generic-disable-node.xml.i> +                  <leafNode name="mode"> +                    <properties> +                      <help>Select per user or per group configuration file - ignored if authentication group is configured</help> +                      <completionHelp> +                        <list>user group</list> +                      </completionHelp> +                      <valueHelp> +                        <format>user</format> +                        <description>Match configuration file on username</description> +                      </valueHelp> +                      <valueHelp> +                        <format>group</format> +                        <description>Match RADIUS response class attribute as file name</description> +                      </valueHelp> +                      <constraint> +                        <regex>(user|group)</regex> +                      </constraint> +                      <constraintErrorMessage>Invalid mode, must be either user or group</constraintErrorMessage> +                    </properties> +                  </leafNode> +                  <leafNode name="directory"> +                    <properties> +                      <help>Directory to containing configuration files</help> +                      <valueHelp> +                        <format>path</format> +                        <description>Path to configuration directory, must be under /config/auth</description> +                      </valueHelp> +                      <constraint> +                        <validator name="file-path" argument="--directory --parent-dir /config/auth --strict"/> +                      </constraint> +                    </properties> +                  </leafNode> +                  <leafNode name="default-config"> +                    <properties> +                      <help>Default configuration if discrete config could not be found</help> +                      <valueHelp> +                        <format>filename</format> +                        <description>Default configuration filename, must be under /config/auth</description> +                      </valueHelp> +                      <constraint> +                        <validator name="file-path" argument="--file --parent-dir /config/auth --strict"/> +                      </constraint> +                    </properties> +                  </leafNode> +                </children> +              </node>                <leafNode name="group">                  <properties>                    <help>Group that a client is allowed to select (from a list). Maps to RADIUS Class attribute.</help> diff --git a/op-mode-definitions/show-interfaces-wwan.xml.in b/op-mode-definitions/show-interfaces-wwan.xml.in index 45558115b..3682282a3 100644 --- a/op-mode-definitions/show-interfaces-wwan.xml.in +++ b/op-mode-definitions/show-interfaces-wwan.xml.in @@ -86,13 +86,13 @@              <properties>                <help>Show Wireless Modem (WWAN) interface information</help>              </properties> -            <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-type=wirelessmodem</command> +            <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-type=wwan</command>              <children>                <leafNode name="detail">                  <properties>                    <help>Show detailed Wireless Modem (WWAN( interface information</help>                  </properties> -                <command>${vyos_op_scripts_dir}/interfaces.py show --intf-type=wirelessmodem</command> +                <command>${vyos_op_scripts_dir}/interfaces.py show --intf-type=wwan</command>                </leafNode>              </children>            </node> diff --git a/python/vyos/base.py b/python/vyos/base.py index c1acfd060..9b93cb2f2 100644 --- a/python/vyos/base.py +++ b/python/vyos/base.py @@ -1,4 +1,4 @@ -# Copyright 2018-2023 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2018-2022 VyOS maintainers and contributors <maintainers@vyos.io>  #  # This library is free software; you can redistribute it and/or  # modify it under the terms of the GNU Lesser General Public @@ -41,6 +41,7 @@ class BaseWarning:                  isfirstmessage = False                  initial_indent = self.standardindent              print(f'{mes}') +        print('')  class Warning(): diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 6a49c022a..30bea3b86 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -374,10 +374,11 @@ class EthernetIf(Interface):          self.set_tso(dict_search('offload.tso', config) != None)          # Set physical interface speed and duplex -        if {'speed', 'duplex'} <= set(config): -            speed = config.get('speed') -            duplex = config.get('duplex') -            self.set_speed_duplex(speed, duplex) +        if 'speed_duplex_changed' in config: +            if {'speed', 'duplex'} <= set(config): +                speed = config.get('speed') +                duplex = config.get('duplex') +                self.set_speed_duplex(speed, duplex)          # Set interface ring buffer          if 'ring_buffer' in config: diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 2f1d5eb96..f62b9f7d2 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -532,7 +532,7 @@ class Interface(Control):              return None          # As a PoC we only allow 'dummy' interfaces -        if not ('dum' in self.ifname or 'veth' in self.ifname): +        if 'dum' not in self.ifname:              return None          # Check if interface realy exists in namespace diff --git a/python/vyos/util.py b/python/vyos/util.py index 0593184cc..d5330db13 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -1146,13 +1146,6 @@ def sysctl_write(name, value):          return True      return False -# approach follows a discussion in: -# https://stackoverflow.com/questions/1175208/elegant-python-function-to-convert-camelcase-to-snake-case -def camel_to_snake_case(name: str) -> str: -    pattern = r'\d+|[A-Z]?[a-z]+|\W|[A-Z]{2,}(?=[A-Z][a-z]|\d|\W|$)' -    words = re.findall(pattern, name) -    return '_'.join(map(str.lower, words)) -  def load_as_module(name: str, path: str):      import importlib.util diff --git a/python/vyos/utils/dict.py b/python/vyos/utils/dict.py index 4afc9f54e..7c93deef6 100644 --- a/python/vyos/utils/dict.py +++ b/python/vyos/utils/dict.py @@ -253,4 +253,4 @@ def check_mutually_exclusive_options(d, keys, required=False):          raise ValueError(f"Options {orig_keys} are mutually-exclusive but more than one of them is present: {orig_present_keys}")      if required and (len(present_keys) < 1): -        raise ValueError(f"At least one of the following options is required: {orig_present_keys}") +        raise ValueError(f"At least one of the following options is required: {orig_keys}") 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/smoketest/scripts/cli/test_service_dns_dynamic.py b/smoketest/scripts/cli/test_service_dns_dynamic.py index 57705e26f..a3aa41f94 100755 --- a/smoketest/scripts/cli/test_service_dns_dynamic.py +++ b/smoketest/scripts/cli/test_service_dns_dynamic.py @@ -45,22 +45,32 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase):          self.cli_commit()      def test_dyndns_service(self): +        from itertools import product          ddns = ['interface', interface, 'service'] +        users = [None, 'vyos_user']          services = ['cloudflare', 'afraid', 'dyndns', 'zoneedit'] -        for service in services: -            user = 'vyos_user' +        for user, service in product(users, services):              password = 'vyos_pass'              zone = 'vyos.io'              self.cli_delete(base_path)              self.cli_set(base_path + ddns + [service, 'host-name', hostname]) -            self.cli_set(base_path + ddns + [service, 'login', user]) +            if user is not None: +                self.cli_set(base_path + ddns + [service, 'login', user])              self.cli_set(base_path + ddns + [service, 'password', password])              self.cli_set(base_path + ddns + [service, 'zone', zone])              # commit changes              if service == 'cloudflare':                  self.cli_commit() +            elif user is None: +                # not set user is only allowed for cloudflare +                with self.assertRaises(ConfigSessionError): +                    # remove zone to test not set user +                    self.cli_delete(base_path + ddns + [service, 'zone', 'vyos.io']) +                    self.cli_commit() +                # this case is fininshed, user not set is not allowed when service isn't cloudflare +                continue              else:                  # zone option only works on cloudflare, an exception is raised                  # for all others @@ -72,7 +82,7 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase):              # we can only read the configuration file when we operate as 'root'              protocol = get_config_value('protocol') -            login = get_config_value('login') +            login = None if user is None else get_config_value('login')              pwd = get_config_value('password')              # some services need special treatment diff --git a/src/conf_mode/dynamic_dns.py b/src/conf_mode/dynamic_dns.py index 06a2f7e15..426e3d693 100755 --- a/src/conf_mode/dynamic_dns.py +++ b/src/conf_mode/dynamic_dns.py @@ -108,7 +108,8 @@ def verify(dyndns):                      raise ConfigError(f'"host-name" {error_msg}')                  if 'login' not in config: -                    raise ConfigError(f'"login" (username) {error_msg}') +                    if service != 'cloudflare' and ('protocol' not in config or config['protocol'] != 'cloudflare'): +                        raise ConfigError(f'"login" (username) {error_msg}, unless using CloudFlare')                  if 'password' not in config:                      raise ConfigError(f'"password" {error_msg}') diff --git a/src/conf_mode/high-availability.py b/src/conf_mode/high-availability.py index 7a63f5b4b..e18b426b1 100755 --- a/src/conf_mode/high-availability.py +++ b/src/conf_mode/high-availability.py @@ -21,6 +21,7 @@ from ipaddress import ip_interface  from ipaddress import IPv4Interface  from ipaddress import IPv6Interface +from vyos.base import Warning  from vyos.config import Config  from vyos.configdict import dict_merge  from vyos.ifconfig.vrrp import VRRP @@ -107,11 +108,16 @@ def verify(ha):                      raise ConfigError(f'Authentication requires both type and passwortd to be set in VRRP group "{group}"')              if 'health_check' in group_config: +                health_check_types = ["script", "ping"]                  from vyos.utils.dict import check_mutually_exclusive_options                  try: -                    check_mutually_exclusive_options(group_config["health_check"], ["script", "ping"], required=True) +                    check_mutually_exclusive_options(group_config["health_check"], health_check_types, required=True)                  except ValueError as e: -                    raise ConfigError(f'Health check config is incorrect in VRRP group "{group}": {e}') +                    Warning(f'Health check configuration for VRRP group "{group}" will remain unused ' \ +                            f'until it has one of the following options: {health_check_types}') +                    # XXX: health check has default options so we need to remove it +                    # to avoid generating useless config statements in keepalived.conf +                    del group_config["health_check"]              # Keepalived doesn't allow mixing IPv4 and IPv6 in one group, so we mirror that restriction              # We also need to make sure VRID is not used twice on the same interface with the diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index b49c945cd..31cfab368 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -22,6 +22,7 @@ from sys import exit  from vyos.base import Warning  from vyos.config import Config  from vyos.configdict import get_interface_dict +from vyos.configdict import is_node_changed  from vyos.configverify import verify_address  from vyos.configverify import verify_dhcpv6  from vyos.configverify import verify_eapol @@ -66,11 +67,17 @@ def get_config(config=None):                                 get_first_key=True, no_tag_node_value_mangle=True)      base = ['interfaces', 'ethernet'] -    _, ethernet = get_interface_dict(conf, base) +    ifname, ethernet = get_interface_dict(conf, base)      if 'deleted' not in ethernet:         if pki: ethernet['pki'] = pki +    tmp = is_node_changed(conf, base + [ifname, 'speed']) +    if tmp: ethernet.update({'speed_duplex_changed': {}}) + +    tmp = is_node_changed(conf, base + [ifname, 'duplex']) +    if tmp: ethernet.update({'speed_duplex_changed': {}}) +      return ethernet  def verify(ethernet): diff --git a/src/conf_mode/load-balancing-haproxy.py b/src/conf_mode/load-balancing-haproxy.py new file mode 100755 index 000000000..b29fdffc7 --- /dev/null +++ b/src/conf_mode/load-balancing-haproxy.py @@ -0,0 +1,181 @@ +#!/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 '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: +            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) diff --git a/src/conf_mode/system-syslog.py b/src/conf_mode/system-syslog.py index e646fb0ae..cf34bad2e 100755 --- a/src/conf_mode/system-syslog.py +++ b/src/conf_mode/system-syslog.py @@ -111,9 +111,9 @@ def verify(syslog):  def generate(syslog):      if not syslog:          if os.path.exists(rsyslog_conf): -            os.path.unlink(rsyslog_conf) +            os.unlink(rsyslog_conf)          if os.path.exists(logrotate_conf): -            os.path.unlink(logrotate_conf) +            os.unlink(logrotate_conf)          return None @@ -126,9 +126,10 @@ def generate(syslog):      return None  def apply(syslog): +    systemd_socket = 'syslog.socket'      systemd_service = 'syslog.service'      if not syslog: -        call(f'systemctl stop {systemd_service}') +        call(f'systemctl stop {systemd_service} {systemd_socket}')          return None      # we need to restart the service if e.g. the VRF name changed diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py index 68da70d7d..83021a3e6 100755 --- a/src/conf_mode/vpn_openconnect.py +++ b/src/conf_mode/vpn_openconnect.py @@ -17,6 +17,7 @@  import os  from sys import exit +from vyos.base import Warning  from vyos.config import Config  from vyos.configdict import dict_merge  from vyos.pki import wrap_certificate @@ -173,6 +174,19 @@ def verify(ocserv):                                  users_wo_pswd.append(user)                          if users_wo_pswd:                              raise ConfigError(f'password required for users:\n{users_wo_pswd}') + +            # Validate that if identity-based-config is configured all child config nodes are set +            if 'identity_based_config' in ocserv["authentication"]: +                if 'disabled' not in ocserv["authentication"]["identity_based_config"]: +                    Warning("Identity based configuration files is a 3rd party addition. Use at your own risk, this might break the ocserv daemon!") +                    if 'mode' not in ocserv["authentication"]["identity_based_config"]: +                        raise ConfigError('OpenConnect radius identity-based-config enabled but mode not selected') +                    elif 'group' in ocserv["authentication"]["identity_based_config"]["mode"] and "radius" not in ocserv["authentication"]["mode"]: +                        raise ConfigError('OpenConnect config-per-group must be used with radius authentication') +                    if 'directory' not in ocserv["authentication"]["identity_based_config"]: +                        raise ConfigError('OpenConnect identity-based-config enabled but directory not set') +                    if 'default_config' not in ocserv["authentication"]["identity_based_config"]: +                        raise ConfigError('OpenConnect identity-based-config enabled but default-config not set')          else:              raise ConfigError('openconnect authentication mode required')      else: diff --git a/src/op_mode/interfaces.py b/src/op_mode/interfaces.py index dd87b5901..f38b95a71 100755 --- a/src/op_mode/interfaces.py +++ b/src/op_mode/interfaces.py @@ -277,6 +277,10 @@ def _get_counter_data(ifname: typing.Optional[str],          res_intf['rx_bytes'] = _get_counter_val(cache['rx_bytes'], stats['rx_bytes'])          res_intf['tx_packets'] = _get_counter_val(cache['tx_packets'], stats['tx_packets'])          res_intf['tx_bytes'] = _get_counter_val(cache['tx_bytes'], stats['tx_bytes']) +        res_intf['rx_dropped'] = _get_counter_val(cache['rx_dropped'], stats['rx_dropped']) +        res_intf['tx_dropped'] = _get_counter_val(cache['tx_dropped'], stats['tx_dropped']) +        res_intf['rx_over_errors'] = _get_counter_val(cache['rx_over_errors'], stats['rx_over_errors']) +        res_intf['tx_carrier_errors'] = _get_counter_val(cache['tx_carrier_errors'], stats['tx_carrier_errors'])          ret.append(res_intf) @@ -368,19 +372,23 @@ def _format_show_summary(data):  @catch_broken_pipe  def _format_show_counters(data: list): -    formatting = '%-12s %10s %10s %10s %10s' -    print(formatting % ('Interface', 'Rx Packets', 'Rx Bytes', 'Tx Packets', 'Tx Bytes')) - -    for intf in data: -        print(formatting % ( -            intf['ifname'], -            intf['rx_packets'], -            intf['rx_bytes'], -            intf['tx_packets'], -            intf['tx_bytes'] -        )) - -    return 0 +    data_entries = [] +    for entry in data: +            interface = entry.get('ifname') +            rx_packets = entry.get('rx_packets') +            rx_bytes = entry.get('rx_bytes') +            tx_packets = entry.get('tx_packets') +            tx_bytes = entry.get('tx_bytes') +            rx_dropped = entry.get('rx_dropped') +            tx_dropped = entry.get('tx_dropped') +            rx_errors = entry.get('rx_over_errors') +            tx_errors = entry.get('tx_carrier_errors') +            data_entries.append([interface, rx_packets, rx_bytes, tx_packets, tx_bytes, rx_dropped, tx_dropped, rx_errors, tx_errors]) +     +    headers = ['Interface', 'Rx Packets', 'Rx Bytes', 'Tx Packets', 'Tx Bytes', 'Rx Dropped', 'Tx Dropped', 'Rx Errors', 'Tx Errors'] +    output = tabulate(data_entries, headers, numalign="left") +    print (output) +    return output  def show(raw: bool, intf_name: typing.Optional[str],                      intf_type: typing.Optional[str], diff --git a/src/tests/test_util.py b/src/tests/test_util.py index d8b2b7940..473052bef 100644 --- a/src/tests/test_util.py +++ b/src/tests/test_util.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2020-2022 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -26,17 +26,3 @@ class TestVyOSUtil(TestCase):      def test_sysctl_read(self):          self.assertEqual(sysctl_read('net.ipv4.conf.lo.forwarding'), '1') - -    def test_camel_to_snake_case(self): -        self.assertEqual(camel_to_snake_case('ConnectionTimeout'), -                                             'connection_timeout') -        self.assertEqual(camel_to_snake_case('connectionTimeout'), -                                             'connection_timeout') -        self.assertEqual(camel_to_snake_case('TCPConnectionTimeout'), -                                             'tcp_connection_timeout') -        self.assertEqual(camel_to_snake_case('TCPPort'), -                                             'tcp_port') -        self.assertEqual(camel_to_snake_case('UseHTTPProxy'), -                                             'use_http_proxy') -        self.assertEqual(camel_to_snake_case('CustomerID'), -                                             'customer_id') | 
