diff options
53 files changed, 1224 insertions, 431 deletions
diff --git a/data/config-mode-dependencies/vyos-1x.json b/data/config-mode-dependencies/vyos-1x.json index b62603e34..b0586e0bb 100644 --- a/data/config-mode-dependencies/vyos-1x.json +++ b/data/config-mode-dependencies/vyos-1x.json @@ -27,6 +27,7 @@ "https": ["service_https"], "ipsec": ["vpn_ipsec"], "openconnect": ["vpn_openconnect"], + "rpki": ["protocols_rpki"], "sstp": ["vpn_sstp"] }, "vpn_l2tp": { diff --git a/data/templates/accel-ppp/config_extended_scripts.j2 b/data/templates/accel-ppp/config_extended_scripts.j2 new file mode 100644 index 000000000..ded0a0a39 --- /dev/null +++ b/data/templates/accel-ppp/config_extended_scripts.j2 @@ -0,0 +1,9 @@ +{% if extended_scripts is vyos_defined %} +[pppd-compat] +verbose=1 +radattr-prefix=/run/accel-pppd/radattr +{% set script_name = {'on_up': 'ip-up', 'on_down': 'ip-down', 'on_change':'ip-change', 'on_pre_up':'ip-pre-up'} %} +{% for script in extended_scripts %} +{{ script_name[script] }}={{ extended_scripts[script] }} +{% endfor %} +{% endif %}
\ No newline at end of file diff --git a/data/templates/accel-ppp/config_limits.j2 b/data/templates/accel-ppp/config_limits.j2 new file mode 100644 index 000000000..f10dfccd7 --- /dev/null +++ b/data/templates/accel-ppp/config_limits.j2 @@ -0,0 +1,12 @@ +{% if limits is vyos_defined %} +[connlimit] +{% if limits.connection_limit is vyos_defined %} +limit={{ limits.connection_limit }} +{% endif %} +{% if limits.burst is vyos_defined %} +burst={{ limits.burst }} +{% endif %} +{% if limits.timeout is vyos_defined %} +timeout={{ limits.timeout }} +{% endif %} +{% endif %}
\ No newline at end of file diff --git a/data/templates/accel-ppp/config_snmp.j2 b/data/templates/accel-ppp/config_snmp.j2 new file mode 100644 index 000000000..11526dd81 --- /dev/null +++ b/data/templates/accel-ppp/config_snmp.j2 @@ -0,0 +1,4 @@ +{% if snmp.master_agent is vyos_defined %} +[snmp] +master=1 +{% endif %} diff --git a/data/templates/accel-ppp/config_wins_server.j2 b/data/templates/accel-ppp/config_wins_server.j2 new file mode 100644 index 000000000..23312f92e --- /dev/null +++ b/data/templates/accel-ppp/config_wins_server.j2 @@ -0,0 +1,6 @@ +{% if wins_server is vyos_defined %} +[wins] +{% for server in wins_server %} +wins{{ loop.index }}={{ server }} +{% endfor %} +{% endif %} diff --git a/data/templates/accel-ppp/ipoe.config.j2 b/data/templates/accel-ppp/ipoe.config.j2 index 8b022eaa5..c89812985 100644 --- a/data/templates/accel-ppp/ipoe.config.j2 +++ b/data/templates/accel-ppp/ipoe.config.j2 @@ -6,10 +6,16 @@ ipoe shaper {# Common authentication backend definitions #} {% include 'accel-ppp/config_modules_auth_mode.j2' %} +ippool ipv6pool ipv6_nd ipv6_dhcp -ippool +{% if snmp is vyos_defined %} +net-snmp +{% endif %} +{% if limits is vyos_defined %} +connlimit +{% endif %} [core] thread-count={{ thread_count }} @@ -19,6 +25,7 @@ thread-count={{ thread_count }} max-starting={{ max_concurrent_sessions }} {% endif %} + [log] syslog=accel-ipoe,daemon copy=1 @@ -84,5 +91,14 @@ proxy-arp=1 {# Common RADIUS shaper configuration #} {% include 'accel-ppp/config_shaper_radius.j2' %} +{# Common Extended scripts configuration #} +{% include 'accel-ppp/config_extended_scripts.j2' %} + +{# Common Limits configuration #} +{% include 'accel-ppp/config_limits.j2' %} + +{# Common SNMP definitions #} +{% include 'accel-ppp/config_snmp.j2' %} + [cli] tcp=127.0.0.1:2002 diff --git a/data/templates/accel-ppp/l2tp.config.j2 b/data/templates/accel-ppp/l2tp.config.j2 index 203a9772e..4ce9042c2 100644 --- a/data/templates/accel-ppp/l2tp.config.j2 +++ b/data/templates/accel-ppp/l2tp.config.j2 @@ -10,6 +10,12 @@ ippool {% include 'accel-ppp/config_modules_ipv6.j2' %} {# Common authentication protocols (pap, chap ...) #} {% include 'accel-ppp/config_modules_auth_protocols.j2' %} +{% if snmp is vyos_defined %} +net-snmp +{% endif %} +{% if limits is vyos_defined %} +connlimit +{% endif %} [core] thread-count={{ thread_count }} @@ -24,15 +30,8 @@ syslog=accel-l2tp,daemon copy=1 level=5 -{# Common DNS name-server definition #} -{% include 'accel-ppp/config_name_server.j2' %} - -{% if wins_server is vyos_defined %} -[wins] -{% for server in wins_server %} -wins{{ loop.index }}={{ server }} -{% endfor %} -{% endif %} +[client-ip-range] +0.0.0.0/0 [l2tp] verbose=1 @@ -56,24 +55,36 @@ ipv6-pool={{ default_ipv6_pool }} ipv6-pool-delegate={{ default_ipv6_pool }} {% endif %} -[client-ip-range] -0.0.0.0/0 - {# Common IP pool definitions #} {% include 'accel-ppp/config_ip_pool.j2' %} +{# Common IPv6 pool definitions #} +{% include 'accel-ppp/config_ipv6_pool.j2' %} + +{# Common DNS name-server definition #} +{% include 'accel-ppp/config_name_server.j2' %} + +{# Common wins-server definition #} +{% include 'accel-ppp/config_wins_server.j2' %} + {# Common chap-secrets and RADIUS server/option definitions #} {% include 'accel-ppp/config_chap_secrets_radius.j2' %} {# Common ppp-options definitions #} {% include 'accel-ppp/ppp-options.j2' %} -{# Common IPv6 pool definitions #} -{% include 'accel-ppp/config_ipv6_pool.j2' %} - {# Common RADIUS shaper configuration #} {% include 'accel-ppp/config_shaper_radius.j2' %} +{# Common Extended scripts configuration #} +{% include 'accel-ppp/config_extended_scripts.j2' %} + +{# Common Limits configuration #} +{% include 'accel-ppp/config_limits.j2' %} + +{# Common SNMP definitions #} +{% include 'accel-ppp/config_snmp.j2' %} + [cli] tcp=127.0.0.1:2004 diff --git a/data/templates/accel-ppp/pppoe.config.j2 b/data/templates/accel-ppp/pppoe.config.j2 index bf7b2eb72..6b01958e5 100644 --- a/data/templates/accel-ppp/pppoe.config.j2 +++ b/data/templates/accel-ppp/pppoe.config.j2 @@ -10,7 +10,6 @@ ippool {% include 'accel-ppp/config_modules_ipv6.j2' %} {# Common authentication protocols (pap, chap ...) #} {% include 'accel-ppp/config_modules_auth_protocols.j2' %} - {% if snmp is vyos_defined %} net-snmp {% endif %} @@ -35,32 +34,8 @@ level=5 noauth=1 {% endif %} -{% if snmp.master_agent is vyos_defined %} -[snmp] -master=1 -{% endif %} - [client-ip-range] -disable - -{# Common IP pool definitions #} -{% include 'accel-ppp/config_ip_pool.j2' %} - -{# Common IPv6 pool definitions #} -{% include 'accel-ppp/config_ipv6_pool.j2' %} - -{# Common DNS name-server definition #} -{% include 'accel-ppp/config_name_server.j2' %} - -{% if wins_server is vyos_defined %} -[wins] -{% for server in wins_server %} -wins{{ loop.index }}={{ server }} -{% endfor %} -{% endif %} - -{# Common chap-secrets and RADIUS server/option definitions #} -{% include 'accel-ppp/config_chap_secrets_radius.j2' %} +0.0.0.0/0 [common] {% if session_control is vyos_defined and session_control is not vyos_defined('disable') %} @@ -70,9 +45,6 @@ single-session={{ session_control }} max-starting={{ max_concurrent_sessions }} {% endif %} -{# Common ppp-options definitions #} -{% include 'accel-ppp/ppp-options.j2' %} - [pppoe] verbose=1 ac-name={{ access_concentrator }} @@ -116,31 +88,35 @@ ipv6-pool={{ default_ipv6_pool }} ipv6-pool-delegate={{ default_ipv6_pool }} {% endif %} -{% if limits is vyos_defined %} -[connlimit] -{% if limits.connection_limit is vyos_defined %} -limit={{ limits.connection_limit }} -{% endif %} -{% if limits.burst is vyos_defined %} -burst={{ limits.burst }} -{% endif %} -{% if limits.timeout is vyos_defined %} -timeout={{ limits.timeout }} -{% endif %} -{% endif %} +{# Common IP pool definitions #} +{% include 'accel-ppp/config_ip_pool.j2' %} + +{# Common IPv6 pool definitions #} +{% include 'accel-ppp/config_ipv6_pool.j2' %} + +{# Common DNS name-server definition #} +{% include 'accel-ppp/config_name_server.j2' %} + +{# Common wins-server definition #} +{% include 'accel-ppp/config_wins_server.j2' %} + +{# Common chap-secrets and RADIUS server/option definitions #} +{% include 'accel-ppp/config_chap_secrets_radius.j2' %} + +{# Common ppp-options definitions #} +{% include 'accel-ppp/ppp-options.j2' %} {# Common RADIUS shaper configuration #} {% include 'accel-ppp/config_shaper_radius.j2' %} -{% if extended_scripts is vyos_defined %} -[pppd-compat] -verbose=1 -radattr-prefix=/run/accel-pppd/radattr -{% set script_name = {'on_up': 'ip-up', 'on_down': 'ip-down', 'on_change':'ip-change', 'on_pre_up':'ip-pre-up'} %} -{% for script in extended_scripts %} -{{ script_name[script] }}={{ extended_scripts[script] }} -{% endfor %} -{% endif %} +{# Common Extended scripts configuration #} +{% include 'accel-ppp/config_extended_scripts.j2' %} + +{# Common Limits configuration #} +{% include 'accel-ppp/config_limits.j2' %} + +{# Common SNMP definitions #} +{% include 'accel-ppp/config_snmp.j2' %} [cli] tcp=127.0.0.1:2001 diff --git a/data/templates/accel-ppp/pptp.config.j2 b/data/templates/accel-ppp/pptp.config.j2 index 290e6235d..a04bd40c0 100644 --- a/data/templates/accel-ppp/pptp.config.j2 +++ b/data/templates/accel-ppp/pptp.config.j2 @@ -10,6 +10,12 @@ ippool {% include 'accel-ppp/config_modules_ipv6.j2' %} {# Common authentication protocols (pap, chap ...) #} {% include 'accel-ppp/config_modules_auth_protocols.j2' %} +{% if snmp is vyos_defined %} +net-snmp +{% endif %} +{% if limits is vyos_defined %} +connlimit +{% endif %} [core] thread-count={{ thread_count }} @@ -24,15 +30,8 @@ syslog=accel-pptp,daemon copy=1 level=5 -{# Common DNS name-server definition #} -{% include 'accel-ppp/config_name_server.j2' %} - -{% if wins_server is vyos_defined %} -[wins] -{% for server in wins_server %} -wins{{ loop.index }}={{ server }} -{% endfor %} -{% endif %} +[client-ip-range] +0.0.0.0/0 [pptp] ifname=pptp%d @@ -52,24 +51,36 @@ ipv6-pool={{ default_ipv6_pool }} ipv6-pool-delegate={{ default_ipv6_pool }} {% endif %} -[client-ip-range] -0.0.0.0/0 - {# Common IP pool definitions #} {% include 'accel-ppp/config_ip_pool.j2' %} {# Common IPv6 pool definitions #} {% include 'accel-ppp/config_ipv6_pool.j2' %} -{# Common ppp-options definitions #} -{% include 'accel-ppp/ppp-options.j2' %} +{# Common DNS name-server definition #} +{% include 'accel-ppp/config_name_server.j2' %} + +{# Common wins-server definition #} +{% include 'accel-ppp/config_wins_server.j2' %} {# Common chap-secrets and RADIUS server/option definitions #} {% include 'accel-ppp/config_chap_secrets_radius.j2' %} +{# Common ppp-options definitions #} +{% include 'accel-ppp/ppp-options.j2' %} + {# Common RADIUS shaper configuration #} {% include 'accel-ppp/config_shaper_radius.j2' %} +{# Common Extended scripts configuration #} +{% include 'accel-ppp/config_extended_scripts.j2' %} + +{# Common Limits configuration #} +{% include 'accel-ppp/config_limits.j2' %} + +{# Common SNMP definitions #} +{% include 'accel-ppp/config_snmp.j2' %} + [cli] tcp=127.0.0.1:2003 diff --git a/data/templates/accel-ppp/sstp.config.j2 b/data/templates/accel-ppp/sstp.config.j2 index c0bc62d9f..b624f83a3 100644 --- a/data/templates/accel-ppp/sstp.config.j2 +++ b/data/templates/accel-ppp/sstp.config.j2 @@ -10,6 +10,12 @@ ippool {% include 'accel-ppp/config_modules_ipv6.j2' %} {# Common authentication protocols (pap, chap ...) #} {% include 'accel-ppp/config_modules_auth_protocols.j2' %} +{% if snmp is vyos_defined %} +net-snmp +{% endif %} +{% if limits is vyos_defined %} +connlimit +{% endif %} [core] thread-count={{ thread_count }} @@ -26,7 +32,7 @@ copy=1 level=5 [client-ip-range] -disable +0.0.0.0/0 [sstp] verbose=1 @@ -53,6 +59,9 @@ ipv6-pool-delegate={{ default_ipv6_pool }} {# Common DNS name-server definition #} {% include 'accel-ppp/config_name_server.j2' %} +{# Common wins-server definition #} +{% include 'accel-ppp/config_wins_server.j2' %} + {# Common chap-secrets and RADIUS server/option definitions #} {% include 'accel-ppp/config_chap_secrets_radius.j2' %} @@ -62,5 +71,14 @@ ipv6-pool-delegate={{ default_ipv6_pool }} {# Common RADIUS shaper configuration #} {% include 'accel-ppp/config_shaper_radius.j2' %} +{# Common Extended scripts configuration #} +{% include 'accel-ppp/config_extended_scripts.j2' %} + +{# Common Limits configuration #} +{% include 'accel-ppp/config_limits.j2' %} + +{# Common SNMP definitions #} +{% include 'accel-ppp/config_snmp.j2' %} + [cli] tcp=127.0.0.1:2005 diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2 index ed55db3f2..e9422b257 100644 --- a/data/templates/frr/bgpd.frr.j2 +++ b/data/templates/frr/bgpd.frr.j2 @@ -390,7 +390,7 @@ router bgp {{ system_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }} {% endif %} {% if afi_config.ead_es_route_target.export is vyos_defined %} {% for route_target in afi_config.ead_es_route_target.export %} - ead-es-route-target export {{ route_target }} + ead-es-route-target export {{ route_target }} {% endfor %} {% endif %} {% if afi_config.rt_auto_derive is vyos_defined %} @@ -402,6 +402,9 @@ router bgp {{ system_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }} {% if afi_config.flooding.head_end_replication is vyos_defined %} flooding head-end-replication {% endif %} +{% if afi_config.mac_vrf.soo is vyos_defined %} + mac-vrf soo {{ afi_config.mac_vrf.soo }} +{% endif %} {% if afi_config.nexthop.vpn.export is vyos_defined %} nexthop vpn export {{ afi_config.nexthop.vpn.export }} {% endif %} diff --git a/data/templates/frr/eigrpd.frr.j2 b/data/templates/frr/eigrpd.frr.j2 index 3038a0b1d..d16963a51 100644 --- a/data/templates/frr/eigrpd.frr.j2 +++ b/data/templates/frr/eigrpd.frr.j2 @@ -1,21 +1,31 @@ ! -router eigrp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }} +router eigrp {{ system_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }} {% if maximum_paths is vyos_defined %} -maximum-paths {{ maximum_paths }} + maximum-paths {{ maximum_paths }} {% endif %} {% if metric.weights is vyos_defined %} -metric weights {{ metric.weights }} + metric weights {{ metric.weights }} {% endif %} {% if network is vyos_defined %} {% for net in network %} -network {{ net }} + network {{ net }} +{% endfor %} +{% endif %} +{% if passive_interface is vyos_defined %} +{% for interface in passive_interface %} + passive-interface {{ interface }} {% endfor %} {% endif %} {% if redistribute is vyos_defined %} {% for protocol in redistribute %} -redistribute {{ protocol }} + redistribute {{ protocol }} {% endfor %} {% endif %} +{% if router_id is vyos_defined %} + eigrp router-id {{ router_id }} +{% endif %} {% if variance is vyos_defined %} -variance {{ variance }} -{% endif %}
\ No newline at end of file + variance {{ variance }} +{% endif %} +exit +! diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in index 662ba24ab..3219471b1 100644 --- a/interface-definitions/firewall.xml.in +++ b/interface-definitions/firewall.xml.in @@ -2,7 +2,7 @@ <interfaceDefinition> <node name="firewall" owner="${vyos_conf_scripts_dir}/firewall.py"> <properties> - <priority>199</priority> + <priority>319</priority> <help>Firewall</help> </properties> <children> @@ -94,7 +94,7 @@ <properties> <help>Firewall domain-group</help> <constraint> - <regex>[a-zA-Z_][a-zA-Z0-9][\w\-\.]*</regex> + <regex>[a-zA-Z_][a-zA-Z0-9]?[\w\-\.]*</regex> </constraint> <constraintErrorMessage>Name of domain-group can only contain alpha-numeric letters, hyphen, underscores and not start with numeric</constraintErrorMessage> </properties> diff --git a/interface-definitions/include/accel-ppp/extended-scripts.xml.i b/interface-definitions/include/accel-ppp/extended-scripts.xml.i new file mode 100644 index 000000000..4bba76e32 --- /dev/null +++ b/interface-definitions/include/accel-ppp/extended-scripts.xml.i @@ -0,0 +1,41 @@ +<!-- include start from accel-ppp/extended-scripts.xml.i --> +<node name="extended-scripts"> + <properties> + <help>Extended script execution</help> + </properties> + <children> + <leafNode name="on-pre-up"> + <properties> + <help>Script to run before PPPoE session interface comes up</help> + <constraint> + <validator name="script"/> + </constraint> + </properties> + </leafNode> + <leafNode name="on-up"> + <properties> + <help>Script to run when PPPoE session interface is completely configured and started</help> + <constraint> + <validator name="script"/> + </constraint> + </properties> + </leafNode> + <leafNode name="on-down"> + <properties> + <help>Script to run when PPPoE session interface going to terminate</help> + <constraint> + <validator name="script"/> + </constraint> + </properties> + </leafNode> + <leafNode name="on-change"> + <properties> + <help>Script to run when PPPoE session interface changed by RADIUS CoA handling</help> + <constraint> + <validator name="script"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/limits.xml.i b/interface-definitions/include/accel-ppp/limits.xml.i new file mode 100644 index 000000000..df72b79d4 --- /dev/null +++ b/interface-definitions/include/accel-ppp/limits.xml.i @@ -0,0 +1,28 @@ +<!-- include start from accel-ppp/limits.xml.i --> +<node name="limits"> + <properties> + <help>Limits the connection rate from a single source</help> + </properties> + <children> + <leafNode name="connection-limit"> + <properties> + <help>Acceptable rate of connections (e.g. 1/min, 60/sec)</help> + <constraint> + <regex>[0-9]+\/(min|sec)</regex> + </constraint> + <constraintErrorMessage>illegal value</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="burst"> + <properties> + <help>Burst count</help> + </properties> + </leafNode> + <leafNode name="timeout"> + <properties> + <help>Timeout in seconds</help> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/accel-ppp/snmp.xml.i b/interface-definitions/include/accel-ppp/snmp.xml.i new file mode 100644 index 000000000..373ced16f --- /dev/null +++ b/interface-definitions/include/accel-ppp/snmp.xml.i @@ -0,0 +1,15 @@ +<!-- include start from accel-ppp/snmp.xml.i --> +<node name="snmp"> + <properties> + <help>Enable SNMP</help> + </properties> + <children> + <leafNode name="master-agent"> + <properties> + <help>Enable SNMP master agent mode</help> + <valueless /> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i index ea6e75bbd..ca67eaf3c 100644 --- a/interface-definitions/include/bgp/protocol-common-config.xml.i +++ b/interface-definitions/include/bgp/protocol-common-config.xml.i @@ -894,6 +894,30 @@ </leafNode> </children> </node> + <node name="mac-vrf"> + <properties> + <help>EVPN MAC-VRF</help> + </properties> + <children> + <leafNode name="soo"> + <properties> + <help>Site-of-Origin extended community</help> + <valueHelp> + <format>ASN:NN</format> + <description>based on autonomous system number in format <0-65535:0-4294967295></description> + </valueHelp> + <valueHelp> + <format>IP:NN</format> + <description>Based on a router-id IP address in format <IP:0-65535></description> + </valueHelp> + <constraint> + <validator name="bgp-extended-community"/> + </constraint> + <constraintErrorMessage>Should be in form: ASN:NN or IPADDR:NN where ASN is autonomous system number</constraintErrorMessage> + </properties> + </leafNode> + </children> + </node> <tagNode name="vni"> <properties> <help>VXLAN Network Identifier</help> diff --git a/interface-definitions/include/eigrp/protocol-common-config.xml.i b/interface-definitions/include/eigrp/protocol-common-config.xml.i index a21d18424..a8290f772 100644 --- a/interface-definitions/include/eigrp/protocol-common-config.xml.i +++ b/interface-definitions/include/eigrp/protocol-common-config.xml.i @@ -1,5 +1,5 @@ <!-- include start from eigrp/protocol-common-config.xml.i --> -<leafNode name="local-as"> +<leafNode name="system-as"> <properties> <help>Autonomous System Number (ASN)</help> <valueHelp> @@ -61,6 +61,7 @@ <completionHelp> <script>${vyos_completion_dir}/list_interfaces</script> </completionHelp> + <multi/> </properties> </leafNode> <leafNode name="redistribute"> @@ -108,7 +109,7 @@ </properties> </leafNode> #include <include/router-id.xml.i> -<!-- FRR timers not implemented yet --> +<!-- FRR error: active time not implemented yet --> <leafNode name="variance"> <properties> <help>Control load balancing variance</help> diff --git a/interface-definitions/include/pki/openssh-key.xml.i b/interface-definitions/include/pki/openssh-key.xml.i new file mode 100644 index 000000000..8f005d077 --- /dev/null +++ b/interface-definitions/include/pki/openssh-key.xml.i @@ -0,0 +1,14 @@ +<!-- include start from pki/openssh-key.xml.i --> +<leafNode name="key"> + <properties> + <help>OpenSSH key in PKI configuration</help> + <completionHelp> + <path>pki openssh</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Name of OpenSSH key in PKI configuration</description> + </valueHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/version/l2tp-version.xml.i b/interface-definitions/include/version/l2tp-version.xml.i index 01004c5a0..5397407fb 100644 --- a/interface-definitions/include/version/l2tp-version.xml.i +++ b/interface-definitions/include/version/l2tp-version.xml.i @@ -1,3 +1,3 @@ <!-- include start from include/version/l2tp-version.xml.i --> -<syntaxVersion component='l2tp' version='8'></syntaxVersion> +<syntaxVersion component='l2tp' version='9'></syntaxVersion> <!-- include end --> diff --git a/interface-definitions/pki.xml.in b/interface-definitions/pki.xml.in index 617bdd584..7a0b073b4 100644 --- a/interface-definitions/pki.xml.in +++ b/interface-definitions/pki.xml.in @@ -168,6 +168,45 @@ </properties> <children> #include <include/pki/cli-public-key-base64.xml.i> + <leafNode name="type"> + <properties> + <help>SSH public key type</help> + <completionHelp> + <list>ssh-rsa</list> + </completionHelp> + <valueHelp> + <format>ssh-rsa</format> + <description>Key pair based on RSA algorithm</description> + </valueHelp> + <constraint> + <regex>(ssh-rsa)</regex> + </constraint> + </properties> + </leafNode> + </children> + </node> + <node name="private"> + <properties> + <help>Private key</help> + </properties> + <children> + #include <include/pki/cli-private-key-base64.xml.i> + #include <include/pki/password-protected.xml.i> + </children> + </node> + </children> + </tagNode> + <tagNode name="openssh"> + <properties> + <help>OpenSSH public and private keys</help> + </properties> + <children> + <node name="public"> + <properties> + <help>Public key</help> + </properties> + <children> + #include <include/pki/cli-public-key-base64.xml.i> </children> </node> <node name="private"> diff --git a/interface-definitions/protocols_rpki.xml.in b/interface-definitions/protocols_rpki.xml.in index 6c71f69f3..54d69eadb 100644 --- a/interface-definitions/protocols_rpki.xml.in +++ b/interface-definitions/protocols_rpki.xml.in @@ -47,22 +47,7 @@ <help>RPKI SSH connection settings</help> </properties> <children> - <leafNode name="private-key-file"> - <properties> - <help>RPKI SSH private key file</help> - <constraint> - <validator name="file-path"/> - </constraint> - </properties> - </leafNode> - <leafNode name="public-key-file"> - <properties> - <help>RPKI SSH public key file path</help> - <constraint> - <validator name="file-path"/> - </constraint> - </properties> - </leafNode> + #include <include/pki/openssh-key.xml.i> #include <include/generic-username.xml.i> </children> </node> diff --git a/interface-definitions/service_ipoe-server.xml.in b/interface-definitions/service_ipoe-server.xml.in index 23d6e54d1..414c9a731 100644 --- a/interface-definitions/service_ipoe-server.xml.in +++ b/interface-definitions/service_ipoe-server.xml.in @@ -177,13 +177,18 @@ #include <include/accel-ppp/vlan.xml.i> </children> </tagNode> - #include <include/accel-ppp/max-concurrent-sessions.xml.i> - #include <include/name-server-ipv4-ipv6.xml.i> #include <include/accel-ppp/client-ip-pool.xml.i> - #include <include/accel-ppp/gateway-address-multi.xml.i> #include <include/accel-ppp/client-ipv6-pool.xml.i> #include <include/accel-ppp/default-pool.xml.i> #include <include/accel-ppp/default-ipv6-pool.xml.i> + #include <include/accel-ppp/extended-scripts.xml.i> + #include <include/accel-ppp/gateway-address-multi.xml.i> + #include <include/accel-ppp/limits.xml.i> + #include <include/accel-ppp/max-concurrent-sessions.xml.i> + #include <include/accel-ppp/shaper.xml.i> + #include <include/accel-ppp/snmp.xml.i> + #include <include/generic-description.xml.i> + #include <include/name-server-ipv4-ipv6.xml.i> </children> </node> </children> diff --git a/interface-definitions/service_pppoe-server.xml.in b/interface-definitions/service_pppoe-server.xml.in index 477ed115f..9b5e4d3fb 100644 --- a/interface-definitions/service_pppoe-server.xml.in +++ b/interface-definitions/service_pppoe-server.xml.in @@ -49,9 +49,6 @@ </node> </children> </node> - #include <include/accel-ppp/client-ip-pool.xml.i> - #include <include/accel-ppp/client-ipv6-pool.xml.i> - #include <include/name-server-ipv4-ipv6.xml.i> <tagNode name="interface"> <properties> <help>interface(s) to listen on</help> @@ -63,35 +60,6 @@ #include <include/accel-ppp/vlan.xml.i> </children> </tagNode> - #include <include/accel-ppp/gateway-address.xml.i> - #include <include/accel-ppp/max-concurrent-sessions.xml.i> - #include <include/accel-ppp/mtu-128-16384.xml.i> - <node name="limits"> - <properties> - <help>Limits the connection rate from a single source</help> - </properties> - <children> - <leafNode name="connection-limit"> - <properties> - <help>Acceptable rate of connections (e.g. 1/min, 60/sec)</help> - <constraint> - <regex>[0-9]+\/(min|sec)</regex> - </constraint> - <constraintErrorMessage>illegal value</constraintErrorMessage> - </properties> - </leafNode> - <leafNode name="burst"> - <properties> - <help>Burst count</help> - </properties> - </leafNode> - <leafNode name="timeout"> - <properties> - <help>Timeout in seconds</help> - </properties> - </leafNode> - </children> - </node> <leafNode name="service-name"> <properties> <help>Service name</help> @@ -102,15 +70,6 @@ <multi/> </properties> </leafNode> - #include <include/accel-ppp/wins-server.xml.i> - #include <include/accel-ppp/ppp-options.xml.i> - <node name="ppp-options"> - <children> - <leafNode name="min-mtu"> - <defaultValue>1280</defaultValue> - </leafNode> - </children> - </node> <tagNode name="pado-delay"> <properties> <help>PADO delays</help> @@ -164,61 +123,28 @@ </properties> <defaultValue>replace</defaultValue> </leafNode> - #include <include/accel-ppp/shaper.xml.i> - <node name="snmp"> - <properties> - <help>Enable SNMP</help> - </properties> - <children> - <leafNode name="master-agent"> - <properties> - <help>enable SNMP master agent mode</help> - <valueless /> - </properties> - </leafNode> - </children> - </node> - <node name="extended-scripts"> - <properties> - <help>Extended script execution</help> - </properties> + #include <include/accel-ppp/client-ip-pool.xml.i> + #include <include/accel-ppp/client-ipv6-pool.xml.i> + #include <include/accel-ppp/default-pool.xml.i> + #include <include/accel-ppp/default-ipv6-pool.xml.i> + #include <include/accel-ppp/extended-scripts.xml.i> + #include <include/accel-ppp/gateway-address.xml.i> + #include <include/accel-ppp/limits.xml.i> + #include <include/accel-ppp/max-concurrent-sessions.xml.i> + #include <include/accel-ppp/mtu-128-16384.xml.i> + #include <include/accel-ppp/ppp-options.xml.i> + <node name="ppp-options"> <children> - <leafNode name="on-pre-up"> - <properties> - <help>Script to run before PPPoE session interface comes up</help> - <constraint> - <validator name="script"/> - </constraint> - </properties> - </leafNode> - <leafNode name="on-up"> - <properties> - <help>Script to run when PPPoE session interface is completely configured and started</help> - <constraint> - <validator name="script"/> - </constraint> - </properties> - </leafNode> - <leafNode name="on-down"> - <properties> - <help>Script to run when PPPoE session interface going to terminate</help> - <constraint> - <validator name="script"/> - </constraint> - </properties> - </leafNode> - <leafNode name="on-change"> - <properties> - <help>Script to run when PPPoE session interface changed by RADIUS CoA handling</help> - <constraint> - <validator name="script"/> - </constraint> - </properties> + <leafNode name="min-mtu"> + <defaultValue>1280</defaultValue> </leafNode> </children> </node> - #include <include/accel-ppp/default-pool.xml.i> - #include <include/accel-ppp/default-ipv6-pool.xml.i> + #include <include/accel-ppp/shaper.xml.i> + #include <include/accel-ppp/snmp.xml.i> + #include <include/accel-ppp/wins-server.xml.i> + #include <include/generic-description.xml.i> + #include <include/name-server-ipv4-ipv6.xml.i> </children> </node> </children> diff --git a/interface-definitions/vpn_l2tp.xml.in b/interface-definitions/vpn_l2tp.xml.in index 6148e3269..85a375db4 100644 --- a/interface-definitions/vpn_l2tp.xml.in +++ b/interface-definitions/vpn_l2tp.xml.in @@ -30,42 +30,6 @@ </node> </children> </node> - #include <include/accel-ppp/max-concurrent-sessions.xml.i> - #include <include/accel-ppp/mtu-128-16384.xml.i> - <leafNode name="mtu"> - <defaultValue>1436</defaultValue> - </leafNode> - <leafNode name="outside-address"> - <properties> - <help>External IP address to which VPN clients will connect</help> - <constraint> - <validator name="ipv4-address"/> - </constraint> - </properties> - </leafNode> - #include <include/accel-ppp/gateway-address.xml.i> - #include <include/name-server-ipv4-ipv6.xml.i> - <node name="lns"> - <properties> - <help>L2TP Network Server (LNS)</help> - </properties> - <children> - <leafNode name="shared-secret"> - <properties> - <help>Tunnel password used to authenticate the client (LAC)</help> - </properties> - </leafNode> - <leafNode name="host-name"> - <properties> - <help>Sent to the client (LAC) in the Host-Name attribute</help> - <constraint> - #include <include/constraint/host-name.xml.i> - </constraint> - <constraintErrorMessage>Host-name must be alphanumeric and can contain hyphens</constraintErrorMessage> - </properties> - </leafNode> - </children> - </node> <node name="ipsec-settings"> <properties> <help>Internet Protocol Security (IPsec) for remote access L2TP VPN</help> @@ -129,14 +93,53 @@ #include <include/ipsec/ike-group.xml.i> </children> </node> - #include <include/accel-ppp/wins-server.xml.i> + <node name="lns"> + <properties> + <help>L2TP Network Server (LNS)</help> + </properties> + <children> + <leafNode name="shared-secret"> + <properties> + <help>Tunnel password used to authenticate the client (LAC)</help> + </properties> + </leafNode> + <leafNode name="host-name"> + <properties> + <help>Sent to the client (LAC) in the Host-Name attribute</help> + <constraint> + #include <include/constraint/host-name.xml.i> + </constraint> + <constraintErrorMessage>Host-name must be alphanumeric and can contain hyphens</constraintErrorMessage> + </properties> + </leafNode> + </children> + </node> + <leafNode name="outside-address"> + <properties> + <help>External IP address to which VPN clients will connect</help> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> #include <include/accel-ppp/client-ip-pool.xml.i> #include <include/accel-ppp/client-ipv6-pool.xml.i> - #include <include/generic-description.xml.i> - #include <include/dhcp-interface.xml.i> - #include <include/accel-ppp/ppp-options.xml.i> #include <include/accel-ppp/default-pool.xml.i> #include <include/accel-ppp/default-ipv6-pool.xml.i> + #include <include/accel-ppp/extended-scripts.xml.i> + #include <include/accel-ppp/gateway-address.xml.i> + #include <include/accel-ppp/limits.xml.i> + #include <include/accel-ppp/max-concurrent-sessions.xml.i> + #include <include/accel-ppp/mtu-128-16384.xml.i> + <leafNode name="mtu"> + <defaultValue>1436</defaultValue> + </leafNode> + #include <include/accel-ppp/ppp-options.xml.i> + #include <include/accel-ppp/shaper.xml.i> + #include <include/accel-ppp/snmp.xml.i> + #include <include/accel-ppp/wins-server.xml.i> + #include <include/generic-description.xml.i> + #include <include/name-server-ipv4-ipv6.xml.i> </children> </node> </children> diff --git a/interface-definitions/vpn_pptp.xml.in b/interface-definitions/vpn_pptp.xml.in index 2e2a3bec4..a63633f57 100644 --- a/interface-definitions/vpn_pptp.xml.in +++ b/interface-definitions/vpn_pptp.xml.in @@ -30,11 +30,6 @@ </node> </children> </node> - #include <include/accel-ppp/max-concurrent-sessions.xml.i> - #include <include/accel-ppp/mtu-128-16384.xml.i> - <leafNode name="mtu"> - <defaultValue>1436</defaultValue> - </leafNode> <leafNode name="outside-address"> <properties> <help>External IP address to which VPN clients will connect</help> @@ -43,14 +38,24 @@ </constraint> </properties> </leafNode> - #include <include/accel-ppp/gateway-address.xml.i> - #include <include/name-server-ipv4-ipv6.xml.i> - #include <include/accel-ppp/wins-server.xml.i> #include <include/accel-ppp/client-ip-pool.xml.i> - #include <include/accel-ppp/default-pool.xml.i> #include <include/accel-ppp/client-ipv6-pool.xml.i> + #include <include/accel-ppp/default-pool.xml.i> #include <include/accel-ppp/default-ipv6-pool.xml.i> + #include <include/accel-ppp/extended-scripts.xml.i> + #include <include/accel-ppp/gateway-address.xml.i> + #include <include/accel-ppp/limits.xml.i> + #include <include/accel-ppp/max-concurrent-sessions.xml.i> + #include <include/accel-ppp/mtu-128-16384.xml.i> + <leafNode name="mtu"> + <defaultValue>1436</defaultValue> + </leafNode> #include <include/accel-ppp/ppp-options.xml.i> + #include <include/accel-ppp/shaper.xml.i> + #include <include/accel-ppp/snmp.xml.i> + #include <include/accel-ppp/wins-server.xml.i> + #include <include/generic-description.xml.i> + #include <include/name-server-ipv4-ipv6.xml.i> </children> </node> </children> diff --git a/interface-definitions/vpn_sstp.xml.in b/interface-definitions/vpn_sstp.xml.in index 0d5d53301..d23a001d5 100644 --- a/interface-definitions/vpn_sstp.xml.in +++ b/interface-definitions/vpn_sstp.xml.in @@ -25,19 +25,6 @@ </node> </children> </node> - #include <include/accel-ppp/max-concurrent-sessions.xml.i> - #include <include/interface/mtu-68-1500.xml.i> - #include <include/accel-ppp/gateway-address.xml.i> - #include <include/name-server-ipv4-ipv6.xml.i> - #include <include/accel-ppp/client-ip-pool.xml.i> - #include <include/accel-ppp/client-ipv6-pool.xml.i> - #include <include/port-number.xml.i> - <leafNode name="port"> - <defaultValue>443</defaultValue> - </leafNode> - #include <include/accel-ppp/default-pool.xml.i> - #include <include/accel-ppp/default-ipv6-pool.xml.i> - #include <include/accel-ppp/ppp-options.xml.i> <node name="ssl"> <properties> <help>SSL Certificate, SSL Key and CA</help> @@ -47,6 +34,25 @@ #include <include/pki/certificate.xml.i> </children> </node> + #include <include/accel-ppp/client-ip-pool.xml.i> + #include <include/accel-ppp/client-ipv6-pool.xml.i> + #include <include/accel-ppp/default-pool.xml.i> + #include <include/accel-ppp/default-ipv6-pool.xml.i> + #include <include/accel-ppp/extended-scripts.xml.i> + #include <include/accel-ppp/gateway-address.xml.i> + #include <include/accel-ppp/limits.xml.i> + #include <include/accel-ppp/max-concurrent-sessions.xml.i> + #include <include/interface/mtu-68-1500.xml.i> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>443</defaultValue> + </leafNode> + #include <include/accel-ppp/ppp-options.xml.i> + #include <include/accel-ppp/shaper.xml.i> + #include <include/accel-ppp/snmp.xml.i> + #include <include/accel-ppp/wins-server.xml.i> + #include <include/generic-description.xml.i> + #include <include/name-server-ipv4-ipv6.xml.i> </children> </node> </children> diff --git a/op-mode-definitions/dhcp.xml.in b/op-mode-definitions/dhcp.xml.in index ceb321f3e..0db7471e5 100644 --- a/op-mode-definitions/dhcp.xml.in +++ b/op-mode-definitions/dhcp.xml.in @@ -80,6 +80,32 @@ </tagNode> </children> </node> + <node name="static-mappings"> + <properties> + <help>Show DHCP server static mappings</help> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet</command> + <children> + <tagNode name="pool"> + <properties> + <help>Show DHCP server static mappings for a specific pool</help> + <completionHelp> + <path>service dhcp-server shared-network-name</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet --pool $6</command> + </tagNode> + <tagNode name="sort"> + <properties> + <help>Show DHCP server static mappings sorted by the specified key</help> + <completionHelp> + <list>ip mac duid pool</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet --sort $6</command> + </tagNode> + </children> + </node> <node name="statistics"> <properties> <help>Show DHCP server statistics</help> @@ -130,7 +156,7 @@ <properties> <help>Show DHCPv6 server leases sorted by the specified key</help> <completionHelp> - <list>end iaid_duid ip last_communication pool remaining state type</list> + <list>end duid ip last_communication pool remaining state type</list> </completionHelp> </properties> <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet6 --sort $6</command> @@ -146,6 +172,32 @@ </tagNode> </children> </node> + <node name="static-mappings"> + <properties> + <help>Show DHCPv6 server static mappings</help> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet6</command> + <children> + <tagNode name="pool"> + <properties> + <help>Show DHCPv6 server static mappings for a specific pool</help> + <completionHelp> + <path>service dhcp-server shared-network-name</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet6 --pool $6</command> + </tagNode> + <tagNode name="sort"> + <properties> + <help>Show DHCPv6 server static mappings sorted by the specified key</help> + <completionHelp> + <list>ip mac duid pool</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet6 --sort $6</command> + </tagNode> + </children> + </node> </children> </node> </children> diff --git a/python/vyos/accel_ppp_util.py b/python/vyos/accel_ppp_util.py index bd0c46a19..845b2f5f0 100644 --- a/python/vyos/accel_ppp_util.py +++ b/python/vyos/accel_ppp_util.py @@ -106,7 +106,26 @@ def get_pools_in_order(data: dict) -> list: return pools -def verify_accel_ppp_base_service(config, local_users=True): +def verify_accel_ppp_name_servers(config): + if "name_server_ipv4" in config: + if len(config["name_server_ipv4"]) > 2: + raise ConfigError( + "Not more then two IPv4 DNS name-servers " "can be configured" + ) + if "name_server_ipv6" in config: + if len(config["name_server_ipv6"]) > 3: + raise ConfigError( + "Not more then three IPv6 DNS name-servers " "can be configured" + ) + + +def verify_accel_ppp_wins_servers(config): + if 'wins_server' in config and len(config['wins_server']) > 2: + raise ConfigError( + 'Not more then two WINS name-servers can be configured') + + +def verify_accel_ppp_authentication(config, local_users=True): """ Common helper function which must be used by all Accel-PPP services based on get_config_dict() @@ -148,17 +167,6 @@ def verify_accel_ppp_base_service(config, local_users=True): if not dict_search('authentication.radius.dynamic_author.key', config): raise ConfigError('DAE/CoA server key required!') - if "name_server_ipv4" in config: - if len(config["name_server_ipv4"]) > 2: - raise ConfigError( - "Not more then two IPv4 DNS name-servers " "can be configured" - ) - - if "name_server_ipv6" in config: - if len(config["name_server_ipv6"]) > 3: - raise ConfigError( - "Not more then three IPv6 DNS name-servers " "can be configured" - ) diff --git a/python/vyos/kea.py b/python/vyos/kea.py index 7365c1f02..894ac9e9a 100644 --- a/python/vyos/kea.py +++ b/python/vyos/kea.py @@ -17,8 +17,6 @@ import json import os import socket -from datetime import datetime - from vyos.template import is_ipv6 from vyos.template import isc_static_route from vyos.template import netmask_from_cidr @@ -293,29 +291,6 @@ def kea6_parse_subnet(subnet, config): return out -def kea_parse_leases(lease_path): - contents = read_file(lease_path) - lines = contents.split("\n") - output = [] - - if len(lines) < 2: - return output - - headers = lines[0].split(",") - - for line in lines[1:]: - line_out = dict(zip(headers, line.split(","))) - - lifetime = int(line_out['valid_lifetime']) - expiry = int(line_out['expire']) - - line_out['start_timestamp'] = datetime.utcfromtimestamp(expiry - lifetime) - line_out['expire_timestamp'] = datetime.utcfromtimestamp(expiry) if expiry else None - - output.append(line_out) - - return output - def _ctrl_socket_command(path, command, args=None): if not os.path.exists(path): return None @@ -340,6 +315,16 @@ def _ctrl_socket_command(path, command, args=None): return json.loads(result.decode('utf-8')) +def kea_get_leases(inet): + ctrl_socket = f'/run/kea/dhcp{inet}-ctrl-socket' + + leases = _ctrl_socket_command(ctrl_socket, f'lease{inet}-get-all') + + if not leases or 'result' not in leases or leases['result'] != 0: + return [] + + return leases['arguments']['leases'] + def kea_get_active_config(inet): ctrl_socket = f'/run/kea/dhcp{inet}-ctrl-socket' diff --git a/python/vyos/pki.py b/python/vyos/pki.py index 792e24b76..02dece471 100644 --- a/python/vyos/pki.py +++ b/python/vyos/pki.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2023 VyOS maintainers and contributors +# Copyright (C) 2023-2024 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 @@ -20,7 +20,9 @@ import ipaddress from cryptography import x509 from cryptography.exceptions import InvalidSignature from cryptography.x509.extensions import ExtensionNotFound -from cryptography.x509.oid import NameOID, ExtendedKeyUsageOID, ExtensionOID +from cryptography.x509.oid import NameOID +from cryptography.x509.oid import ExtendedKeyUsageOID +from cryptography.x509.oid import ExtensionOID from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import dh @@ -45,6 +47,8 @@ DH_BEGIN='-----BEGIN DH PARAMETERS-----\n' DH_END='\n-----END DH PARAMETERS-----' OVPN_BEGIN = '-----BEGIN OpenVPN Static key V{0}-----\n' OVPN_END = '\n-----END OpenVPN Static key V{0}-----' +OPENSSH_KEY_BEGIN='-----BEGIN OPENSSH PRIVATE KEY-----\n' +OPENSSH_KEY_END='\n-----END OPENSSH PRIVATE KEY-----' # Print functions @@ -229,6 +233,12 @@ def wrap_public_key(raw_data): def wrap_private_key(raw_data, passphrase=None): return (KEY_ENC_BEGIN if passphrase else KEY_BEGIN) + raw_data + (KEY_ENC_END if passphrase else KEY_END) +def wrap_openssh_public_key(raw_data, type): + return f'{type} {raw_data}' + +def wrap_openssh_private_key(raw_data): + return OPENSSH_KEY_BEGIN + raw_data + OPENSSH_KEY_END + def wrap_certificate_request(raw_data): return CSR_BEGIN + raw_data + CSR_END @@ -245,7 +255,6 @@ def wrap_openvpn_key(raw_data, version='1'): return OVPN_BEGIN.format(version) + raw_data + OVPN_END.format(version) # Load functions - def load_public_key(raw_data, wrap_tags=True): if wrap_tags: raw_data = wrap_public_key(raw_data) @@ -267,6 +276,21 @@ def load_private_key(raw_data, passphrase=None, wrap_tags=True): except ValueError: return False +def load_openssh_public_key(raw_data, type): + try: + return serialization.load_ssh_public_key(bytes(f'{type} {raw_data}', 'utf-8')) + except ValueError: + return False + +def load_openssh_private_key(raw_data, passphrase=None, wrap_tags=True): + if wrap_tags: + raw_data = wrap_openssh_private_key(raw_data) + + try: + return serialization.load_ssh_private_key(bytes(raw_data, 'utf-8'), password=passphrase) + except ValueError: + return False + def load_certificate_request(raw_data, wrap_tags=True): if wrap_tags: raw_data = wrap_certificate_request(raw_data) @@ -429,4 +453,3 @@ def sort_ca_chain(ca_names, pki_node): from functools import cmp_to_key return sorted(ca_names, key=cmp_to_key(lambda cert1, cert2: ca_cmp(cert1, cert2, pki_node))) - diff --git a/smoketest/bin/vyos-configtest-pki b/smoketest/bin/vyos-configtest-pki index 2f8af0e61..e753193e9 100755 --- a/smoketest/bin/vyos-configtest-pki +++ b/smoketest/bin/vyos-configtest-pki @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022, VyOS maintainers and contributors +# Copyright (C) 2022-2024, 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 @@ -23,6 +23,7 @@ from vyos.pki import create_dh_parameters from vyos.pki import encode_certificate from vyos.pki import encode_dh_parameters from vyos.pki import encode_private_key +from vyos.utils.file import write_file subject = {'country': 'DE', 'state': 'BY', 'locality': 'Cloud', 'organization': 'VyOS', 'common_name': 'vyos'} ca_subject = {'country': 'DE', 'state': 'BY', 'locality': 'Cloud', 'organization': 'VyOS', 'common_name': 'vyos CA'} @@ -41,6 +42,40 @@ dh_pem = '/config/auth/ovpn_test_dh.pem' s2s_key = '/config/auth/ovpn_test_site2site.key' auth_key = '/config/auth/ovpn_test_tls_auth.key' +rpki_ssh_priv_key = """ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAweDyflDFR4qyEwETbJkZ2ZZc+sJNiDTvYpwGsWIkju49lJSxHe1x +Kf8FhwfyMu40Snt1yDlRmmmz4CsbLgbuZGMPvXG11e34+C0pSVUvpF6aqRTeLl1pDRK7Rn +jgm3su+I8SRLQR4qbLG6VXWOFuVpwiqbExLaU0hFYTPNP+dArNpsWEEKsohk6pTXdhg3Vz +Wp3vCMjl2JTshDa3lD7p2xISSAReEY0fnfEAmQzH4Z6DIwwGdFuMWoQIg+oFBM9ARrO2/F +IjRsz6AecR/WeU72JEw4aJic1/cAJQA6PiQBHwkuo3Wll1tbpxeRZoB2NQG22ETyJLvhfT +aooNLT9HpQAAA8joU5dM6FOXTAAAAAdzc2gtcnNhAAABAQDB4PJ+UMVHirITARNsmRnZll +z6wk2INO9inAaxYiSO7j2UlLEd7XEp/wWHB/Iy7jRKe3XIOVGaabPgKxsuBu5kYw+9cbXV +7fj4LSlJVS+kXpqpFN4uXWkNErtGeOCbey74jxJEtBHipssbpVdY4W5WnCKpsTEtpTSEVh +M80/50Cs2mxYQQqyiGTqlNd2GDdXNane8IyOXYlOyENreUPunbEhJIBF4RjR+d8QCZDMfh +noMjDAZ0W4xahAiD6gUEz0BGs7b8UiNGzPoB5xH9Z5TvYkTDhomJzX9wAlADo+JAEfCS6j +daWXW1unF5FmgHY1AbbYRPIku+F9Nqig0tP0elAAAAAwEAAQAAAQACkDlUjzfUhtJs6uY5 +WNrdJB5NmHUS+HQzzxFNlhkapK6+wKqI1UNaRUtq6iF7J+gcFf7MK2nXS098BsXguWm8fQ +zPuemoDvHsQhiaJhyvpSqRUrvPTB/f8t/0AhQiKiJIWgfpTaIw53inAGwjujNNxNm2eafH +TThhCYxOkRT7rsT6bnSio6yeqPy5QHg7IKFztp5FXDUyiOS3aX3SvzQcDUkMXALdvzX50t +1XIk+X48Rgkq72dL4VpV2oMNDu3hM6FqBUplf9Mv3s51FNSma/cibCQoVufrIfoqYjkNTj +IpYFUcq4zZ0/KvgXgzSsy9VN/4TtbalrOuu7X/SHJbvhAAAAgGPFsXgONYQvXxCnK1dIue +ozgaZg1I/n522E2ZCOXBW4dYJVyNpppwRreDzuFzTDEe061MpNHfScjVBJCCulivFYWscL +6oaGsryDbFxO3QmB4I98UBqrds2yan9/JGc6EYe299yvaHy7Y64+NC0+fN8H2RAZ61T4w1 +0JrCaJRyvzAAAAgQDvBfuV1U7o9k/fbU+U7W2UYnWblpOZAMfi1XQP6IJJeyWs90PdTdXh ++l0eIQrCawIiRJytNfxMmbD4huwTf77fWiyCcPznmALQ7ex/yJ+W5Z0V4dPGF3h7o1uiS2 +36JhQ7mfcliCkhp/1PIklBIMPcCp0zl+s9wMv2hX7w1Pah9QAAAIEAz6YgU9Xute+J+dBw +oWxEQ+igR6KE55Um7O9AvSrqnCm9r7lSFsXC2ErYOxoDSJ3yIBEV0b4XAGn6tbbVIs3jS8 +BnLHxclAHQecOx1PGn7PKbnPW0oJRq/X9QCIEelKYvlykpayn7uZooTXqcDaPZxfPpmPdy +e8chVJvdygi7kPEAAAAMY3BvQExSMS53dWUzAQIDBAUGBw== +-----END OPENSSH PRIVATE KEY----- +""" + +rpki_ssh_pub_key = """ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDB4PJ+UMVHirITARNsmRnZllz6wk2INO9inAaxYiSO7j2UlLEd7XEp/wWHB/Iy7jRKe3XIOVGaabPgKxsuBu5kYw+9cbXV7fj4LSlJVS+kXpqpFN4uXWkNErtGeOCbey74jxJEtBHipssbpVdY4W5WnCKpsTEtpTSEVhM80/50Cs2mxYQQqyiGTqlNd2GDdXNane8IyOXYlOyENreUPunbEhJIBF4RjR+d8QCZDMfhnoMjDAZ0W4xahAiD6gUEz0BGs7b8UiNGzPoB5xH9Z5TvYkTDhomJzX9wAlADo+JAEfCS6jdaWXW1unF5FmgHY1AbbYRPIku+F9Nqig0tP0el vyos@vyos +""" + def create_cert(subject, cert_path, key_path, sign_by=None, sign_by_key=None, ca=False, sub_ca=False): priv_key = create_private_key('rsa', 2048) cert_req = create_certificate_request(subject, priv_key) @@ -98,3 +133,7 @@ if __name__ == '__main__': # OpenVPN Auth Key system(f'openvpn --genkey secret {auth_key}') + + write_file('/config/id_rsa', rpki_ssh_priv_key.strip()) + write_file('/config/id_rsa.pub', rpki_ssh_pub_key.strip()) + write_file('/config/known-hosts-file', '') diff --git a/smoketest/config-tests/rpki-only b/smoketest/config-tests/rpki-only new file mode 100644 index 000000000..569463b12 --- /dev/null +++ b/smoketest/config-tests/rpki-only @@ -0,0 +1,30 @@ +set interfaces ethernet eth0 address '192.0.2.1/24' +set interfaces ethernet eth0 address '2001:db8::1/64' +set interfaces loopback lo +set pki openssh rpki-5.6.7.8 private key 'b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcnNhAAAAAwEAAQAAAQEAweDyflDFR4qyEwETbJkZ2ZZc+sJNiDTvYpwGsWIkju49lJSxHe1xKf8FhwfyMu40Snt1yDlRmmmz4CsbLgbuZGMPvXG11e34+C0pSVUvpF6aqRTeLl1pDRK7Rnjgm3su+I8SRLQR4qbLG6VXWOFuVpwiqbExLaU0hFYTPNP+dArNpsWEEKsohk6pTXdhg3VzWp3vCMjl2JTshDa3lD7p2xISSAReEY0fnfEAmQzH4Z6DIwwGdFuMWoQIg+oFBM9ARrO2/FIjRsz6AecR/WeU72JEw4aJic1/cAJQA6PiQBHwkuo3Wll1tbpxeRZoB2NQG22ETyJLvhfTaooNLT9HpQAAA8joU5dM6FOXTAAAAAdzc2gtcnNhAAABAQDB4PJ+UMVHirITARNsmRnZllz6wk2INO9inAaxYiSO7j2UlLEd7XEp/wWHB/Iy7jRKe3XIOVGaabPgKxsuBu5kYw+9cbXV7fj4LSlJVS+kXpqpFN4uXWkNErtGeOCbey74jxJEtBHipssbpVdY4W5WnCKpsTEtpTSEVhM80/50Cs2mxYQQqyiGTqlNd2GDdXNane8IyOXYlOyENreUPunbEhJIBF4RjR+d8QCZDMfhnoMjDAZ0W4xahAiD6gUEz0BGs7b8UiNGzPoB5xH9Z5TvYkTDhomJzX9wAlADo+JAEfCS6jdaWXW1unF5FmgHY1AbbYRPIku+F9Nqig0tP0elAAAAAwEAAQAAAQACkDlUjzfUhtJs6uY5WNrdJB5NmHUS+HQzzxFNlhkapK6+wKqI1UNaRUtq6iF7J+gcFf7MK2nXS098BsXguWm8fQzPuemoDvHsQhiaJhyvpSqRUrvPTB/f8t/0AhQiKiJIWgfpTaIw53inAGwjujNNxNm2eafHTThhCYxOkRT7rsT6bnSio6yeqPy5QHg7IKFztp5FXDUyiOS3aX3SvzQcDUkMXALdvzX50t1XIk+X48Rgkq72dL4VpV2oMNDu3hM6FqBUplf9Mv3s51FNSma/cibCQoVufrIfoqYjkNTjIpYFUcq4zZ0/KvgXgzSsy9VN/4TtbalrOuu7X/SHJbvhAAAAgGPFsXgONYQvXxCnK1dIueozgaZg1I/n522E2ZCOXBW4dYJVyNpppwRreDzuFzTDEe061MpNHfScjVBJCCulivFYWscL6oaGsryDbFxO3QmB4I98UBqrds2yan9/JGc6EYe299yvaHy7Y64+NC0+fN8H2RAZ61T4w10JrCaJRyvzAAAAgQDvBfuV1U7o9k/fbU+U7W2UYnWblpOZAMfi1XQP6IJJeyWs90PdTdXh+l0eIQrCawIiRJytNfxMmbD4huwTf77fWiyCcPznmALQ7ex/yJ+W5Z0V4dPGF3h7o1uiS236JhQ7mfcliCkhp/1PIklBIMPcCp0zl+s9wMv2hX7w1Pah9QAAAIEAz6YgU9Xute+J+dBwoWxEQ+igR6KE55Um7O9AvSrqnCm9r7lSFsXC2ErYOxoDSJ3yIBEV0b4XAGn6tbbVIs3jS8BnLHxclAHQecOx1PGn7PKbnPW0oJRq/X9QCIEelKYvlykpayn7uZooTXqcDaPZxfPpmPdye8chVJvdygi7kPEAAAAMY3BvQExSMS53dWUzAQIDBAUGBw==' +set pki openssh rpki-5.6.7.8 public key 'AAAAB3NzaC1yc2EAAAADAQABAAABAQDB4PJ+UMVHirITARNsmRnZllz6wk2INO9inAaxYiSO7j2UlLEd7XEp/wWHB/Iy7jRKe3XIOVGaabPgKxsuBu5kYw+9cbXV7fj4LSlJVS+kXpqpFN4uXWkNErtGeOCbey74jxJEtBHipssbpVdY4W5WnCKpsTEtpTSEVhM80/50Cs2mxYQQqyiGTqlNd2GDdXNane8IyOXYlOyENreUPunbEhJIBF4RjR+d8QCZDMfhnoMjDAZ0W4xahAiD6gUEz0BGs7b8UiNGzPoB5xH9Z5TvYkTDhomJzX9wAlADo+JAEfCS6jdaWXW1unF5FmgHY1AbbYRPIku+F9Nqig0tP0el' +set pki openssh rpki-5.6.7.8 public type 'ssh-rsa' +set policy route-map ROUTES-IN rule 10 action 'permit' +set policy route-map ROUTES-IN rule 10 match rpki 'valid' +set policy route-map ROUTES-IN rule 10 set local-preference '300' +set policy route-map ROUTES-IN rule 20 action 'permit' +set policy route-map ROUTES-IN rule 20 match rpki 'notfound' +set policy route-map ROUTES-IN rule 20 set local-preference '125' +set policy route-map ROUTES-IN rule 30 action 'deny' +set policy route-map ROUTES-IN rule 30 match rpki 'invalid' +set protocols bgp neighbor 192.0.2.200 address-family ipv4-unicast route-map import 'ROUTES-IN' +set protocols bgp neighbor 192.0.2.200 remote-as '200' +set protocols bgp neighbor 2001:db8::200 address-family ipv6-unicast route-map import 'ROUTES-IN' +set protocols bgp neighbor 2001:db8::200 remote-as '200' +set protocols bgp system-as '100' +set protocols rpki cache 1.2.3.4 port '3323' +set protocols rpki cache 1.2.3.4 preference '10' +set protocols rpki cache 5.6.7.8 port '2222' +set protocols rpki cache 5.6.7.8 preference '20' +set protocols rpki cache 5.6.7.8 ssh key 'rpki-5.6.7.8' +set protocols rpki cache 5.6.7.8 ssh username 'vyos' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$r/Yw/07NXNY$/ZB.Rjf9jxEV.BYoDyLdH.kH14rU52pOBtrX.4S34qlPt77chflCHvpTCq9a6huLzwaMR50rEICzA5GoIRZlM0' +set system login user vyos authentication plaintext-password '' +set system syslog global facility all level 'debug' +set system console device ttyS0 speed '115200' diff --git a/smoketest/configs/rpki-only b/smoketest/configs/rpki-only new file mode 100644 index 000000000..98e9892ad --- /dev/null +++ b/smoketest/configs/rpki-only @@ -0,0 +1,122 @@ +interfaces { + ethernet eth0 { + duplex auto + speed auto + address 192.0.2.1/24 + address 2001:db8::1/64 + } + loopback lo { + } +} +policy { + route-map ROUTES-IN { + rule 10 { + action permit + match { + rpki valid + } + set { + local-preference 300 + } + } + rule 20 { + action permit + match { + rpki notfound + } + set { + local-preference 125 + } + } + rule 30 { + action deny + match { + rpki invalid + } + } + } +} +protocols { + bgp 100 { + neighbor 192.0.2.200 { + address-family { + ipv4-unicast { + route-map { + import ROUTES-IN + } + } + } + remote-as 200 + } + neighbor 2001:db8::200 { + address-family { + ipv6-unicast { + route-map { + import ROUTES-IN + } + } + } + remote-as 200 + } + } + rpki { + cache 1.2.3.4 { + port 3323 + preference 10 + } + cache 5.6.7.8 { + port 2222 + preference 20 + ssh { + known-hosts-file "/config/known-hosts-file" + private-key-file "/config/id_rsa" + public-key-file "/config/id_rsa.pub" + username vyos + } + } + } +} +system { + config-management { + commit-revisions 200 + } + console { + device ttyS0 { + speed 115200 + } + } + conntrack { + modules { + ftp + h323 + nfs + pptp + sip + sqlnet + tftp + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$r/Yw/07NXNY$/ZB.Rjf9jxEV.BYoDyLdH.kH14rU52pOBtrX.4S34qlPt77chflCHvpTCq9a6huLzwaMR50rEICzA5GoIRZlM0 + plaintext-password "" + } + } + } + syslog { + global { + facility all { + level debug + } + facility protocols { + level debug + } + } + } +} + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:container@1:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.3.5 diff --git a/smoketest/scripts/cli/base_accel_ppp_test.py b/smoketest/scripts/cli/base_accel_ppp_test.py index 0e6e522b9..ac4bbcfe5 100644 --- a/smoketest/scripts/cli/base_accel_ppp_test.py +++ b/smoketest/scripts/cli/base_accel_ppp_test.py @@ -558,4 +558,53 @@ delegate={delegate_2_prefix},{delegate_mask},name={pool_name}""" self.assertTrue(conf['ppp'].getboolean('ipv6-accept-peer-intf-id')) self.assertEqual(conf['ppp']['lcp-echo-failure'], lcp_failure) self.assertEqual(conf['ppp']['lcp-echo-interval'], lcp_interval) - self.assertEqual(conf['ppp']['lcp-echo-timeout'], lcp_timeout)
\ No newline at end of file + self.assertEqual(conf['ppp']['lcp-echo-timeout'], lcp_timeout) + + + def test_accel_wins_server(self): + self.basic_config() + winsservers = ["192.0.2.1", "192.0.2.2"] + for wins in winsservers: + self.set(["wins-server", wins]) + self.cli_commit() + conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False) + conf.read(self._config_file) + for ws in winsservers: + self.assertIn(ws, [conf["wins"]["wins1"], conf["wins"]["wins2"]]) + + def test_accel_snmp(self): + self.basic_config() + self.set(['snmp', 'master-agent']) + self.cli_commit() + conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False) + conf.read(self._config_file) + self.assertEqual(conf['modules']['net-snmp'], None) + self.assertEqual(conf['snmp']['master'],'1') + + def test_accel_shaper(self): + self.basic_config() + fwmark = '2' + self.set(['shaper', 'fwmark', fwmark]) + self.cli_commit() + conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False) + conf.read(self._config_file) + self.assertEqual(conf['modules']['shaper'], None) + self.assertEqual(conf['shaper']['verbose'], '1') + self.assertEqual(conf['shaper']['down-limiter'], 'tbf') + self.assertEqual(conf['shaper']['fwmark'], fwmark) + + def test_accel_limits(self): + self.basic_config() + burst = '100' + timeout = '20' + limits = '1/min' + self.set(['limits', 'connection-limit', limits]) + self.set(['limits', 'timeout', timeout]) + self.set(['limits', 'burst', burst]) + self.cli_commit() + conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False) + conf.read(self._config_file) + self.assertEqual(conf['modules']['connlimit'], None) + self.assertEqual(conf['connlimit']['limit'], limits) + self.assertEqual(conf['connlimit']['burst'], burst) + self.assertEqual(conf['connlimit']['timeout'], timeout) diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index 415f3436f..bc2848492 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -813,7 +813,8 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.verify_nftables(nftables_search_v6, 'ip6 vyos_filter') def test_flow_offload(self): - self.cli_set(['firewall', 'flowtable', 'smoketest', 'interface', 'eth0']) + self.cli_set(['interfaces', 'ethernet', 'eth0', 'vif', '10']) + self.cli_set(['firewall', 'flowtable', 'smoketest', 'interface', 'eth0.10']) self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'hardware']) # QEMU virtual NIC does not support hw-tc-offload @@ -839,7 +840,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): nftables_search = [ ['flowtable VYOS_FLOWTABLE_smoketest'], ['hook ingress priority filter'], - ['devices = { eth0 }'], + ['devices = { eth0.10 }'], ['ct state { established, related }', 'meta l4proto { tcp, udp }', 'flow add @VYOS_FLOWTABLE_smoketest'], ] diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index eadd8a004..1d68ae08b 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -751,7 +751,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): def test_bgp_07_l2vpn_evpn(self): vnis = ['10010', '10020', '10030'] - neighbors = ['192.0.2.10', '192.0.2.20', '192.0.2.30'] + soo = '1.2.3.4:10000' evi_limit = '1000' route_targets = ['1.1.1.1:100', '1.1.1.1:200', '1.1.1.1:300'] @@ -763,6 +763,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['address-family', 'l2vpn-evpn', 'default-originate', 'ipv6']) self.cli_set(base_path + ['address-family', 'l2vpn-evpn', 'disable-ead-evi-rx']) self.cli_set(base_path + ['address-family', 'l2vpn-evpn', 'disable-ead-evi-tx']) + self.cli_set(base_path + ['address-family', 'l2vpn-evpn', 'mac-vrf', 'soo', soo]) for vni in vnis: self.cli_set(base_path + ['address-family', 'l2vpn-evpn', 'vni', vni, 'advertise-default-gw']) self.cli_set(base_path + ['address-family', 'l2vpn-evpn', 'vni', vni, 'advertise-svi-ip']) @@ -786,6 +787,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' disable-ead-evi-rx', frrconfig) self.assertIn(f' disable-ead-evi-tx', frrconfig) self.assertIn(f' flooding disable', frrconfig) + self.assertIn(f' mac-vrf soo {soo}', frrconfig) for vni in vnis: vniconfig = self.getFRRconfig(f' vni {vni}') self.assertIn(f'vni {vni}', vniconfig) diff --git a/smoketest/scripts/cli/test_protocols_rpki.py b/smoketest/scripts/cli/test_protocols_rpki.py index c52c0dd76..29f03a26a 100755 --- a/smoketest/scripts/cli/test_protocols_rpki.py +++ b/smoketest/scripts/cli/test_protocols_rpki.py @@ -14,20 +14,93 @@ # 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 import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError -from vyos.utils.process import cmd +from vyos.utils.file import read_file from vyos.utils.process import process_named_running base_path = ['protocols', 'rpki'] PROCESS_NAME = 'bgpd' -rpki_ssh_key = '/config/auth/id_rsa_rpki' -rpki_ssh_pub = f'{rpki_ssh_key}.pub' +rpki_key_name = 'rpki-smoketest' +rpki_key_type = 'ssh-rsa' + +rpki_ssh_key = """ +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAweDyflDFR4qyEwETbJkZ2ZZc+sJNiDTvYpwGsWIkju49lJSxHe1x +Kf8FhwfyMu40Snt1yDlRmmmz4CsbLgbuZGMPvXG11e34+C0pSVUvpF6aqRTeLl1pDRK7Rn +jgm3su+I8SRLQR4qbLG6VXWOFuVpwiqbExLaU0hFYTPNP+dArNpsWEEKsohk6pTXdhg3Vz +Wp3vCMjl2JTshDa3lD7p2xISSAReEY0fnfEAmQzH4Z6DIwwGdFuMWoQIg+oFBM9ARrO2/F +IjRsz6AecR/WeU72JEw4aJic1/cAJQA6PiQBHwkuo3Wll1tbpxeRZoB2NQG22ETyJLvhfT +aooNLT9HpQAAA8joU5dM6FOXTAAAAAdzc2gtcnNhAAABAQDB4PJ+UMVHirITARNsmRnZll +z6wk2INO9inAaxYiSO7j2UlLEd7XEp/wWHB/Iy7jRKe3XIOVGaabPgKxsuBu5kYw+9cbXV +7fj4LSlJVS+kXpqpFN4uXWkNErtGeOCbey74jxJEtBHipssbpVdY4W5WnCKpsTEtpTSEVh +M80/50Cs2mxYQQqyiGTqlNd2GDdXNane8IyOXYlOyENreUPunbEhJIBF4RjR+d8QCZDMfh +noMjDAZ0W4xahAiD6gUEz0BGs7b8UiNGzPoB5xH9Z5TvYkTDhomJzX9wAlADo+JAEfCS6j +daWXW1unF5FmgHY1AbbYRPIku+F9Nqig0tP0elAAAAAwEAAQAAAQACkDlUjzfUhtJs6uY5 +WNrdJB5NmHUS+HQzzxFNlhkapK6+wKqI1UNaRUtq6iF7J+gcFf7MK2nXS098BsXguWm8fQ +zPuemoDvHsQhiaJhyvpSqRUrvPTB/f8t/0AhQiKiJIWgfpTaIw53inAGwjujNNxNm2eafH +TThhCYxOkRT7rsT6bnSio6yeqPy5QHg7IKFztp5FXDUyiOS3aX3SvzQcDUkMXALdvzX50t +1XIk+X48Rgkq72dL4VpV2oMNDu3hM6FqBUplf9Mv3s51FNSma/cibCQoVufrIfoqYjkNTj +IpYFUcq4zZ0/KvgXgzSsy9VN/4TtbalrOuu7X/SHJbvhAAAAgGPFsXgONYQvXxCnK1dIue +ozgaZg1I/n522E2ZCOXBW4dYJVyNpppwRreDzuFzTDEe061MpNHfScjVBJCCulivFYWscL +6oaGsryDbFxO3QmB4I98UBqrds2yan9/JGc6EYe299yvaHy7Y64+NC0+fN8H2RAZ61T4w1 +0JrCaJRyvzAAAAgQDvBfuV1U7o9k/fbU+U7W2UYnWblpOZAMfi1XQP6IJJeyWs90PdTdXh ++l0eIQrCawIiRJytNfxMmbD4huwTf77fWiyCcPznmALQ7ex/yJ+W5Z0V4dPGF3h7o1uiS2 +36JhQ7mfcliCkhp/1PIklBIMPcCp0zl+s9wMv2hX7w1Pah9QAAAIEAz6YgU9Xute+J+dBw +oWxEQ+igR6KE55Um7O9AvSrqnCm9r7lSFsXC2ErYOxoDSJ3yIBEV0b4XAGn6tbbVIs3jS8 +BnLHxclAHQecOx1PGn7PKbnPW0oJRq/X9QCIEelKYvlykpayn7uZooTXqcDaPZxfPpmPdy +e8chVJvdygi7kPEAAAAMY3BvQExSMS53dWUzAQIDBAUGBw== +""" + +rpki_ssh_pub = """ +AAAAB3NzaC1yc2EAAAADAQABAAABAQDB4PJ+UMVHirITARNsmRnZllz6wk2INO9inAaxYi +SO7j2UlLEd7XEp/wWHB/Iy7jRKe3XIOVGaabPgKxsuBu5kYw+9cbXV7fj4LSlJVS+kXpqp +FN4uXWkNErtGeOCbey74jxJEtBHipssbpVdY4W5WnCKpsTEtpTSEVhM80/50Cs2mxYQQqy +iGTqlNd2GDdXNane8IyOXYlOyENreUPunbEhJIBF4RjR+d8QCZDMfhnoMjDAZ0W4xahAiD +6gUEz0BGs7b8UiNGzPoB5xH9Z5TvYkTDhomJzX9wAlADo+JAEfCS6jdaWXW1unF5FmgHY1 +AbbYRPIku+F9Nqig0tP0el +""" + +rpki_ssh_key_replacement = """ +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAtLPMwiGR3o6puPDbus9Yqoah9/7rv7i6ykykPmcEZ6ERnA0N6bl7 +LkQxnCuX270ukTTZOhROvQnvQYIZohCMz27Q16z7r+I755QXL0x8x4Gqhg/hQUY7UtX6ts +db8+pO7G1PL4r9zT6/KJAF/wv86DezJ3I6TMaA7MCikXfQWJisBvhgAXF1+7V9CWaroGgV +/hHzQJu1yd4cfsYoHyeDaZ+lwFw4egNItIy63fIGDxrnXaonJ1ODGQh7zWlpl/cwQR/KyJ +P8vvOZ9olQ6syZV+DAcAo4Fe59wW2Zj4bl8bdGcdiDn0grkafxwTcg9ynr9kwQ8b66oXY4 +hwB4vlPFPwAAA8jkGyX45Bsl+AAAAAdzc2gtcnNhAAABAQC0s8zCIZHejqm48Nu6z1iqhq +H3/uu/uLrKTKQ+ZwRnoRGcDQ3puXsuRDGcK5fbvS6RNNk6FE69Ce9BghmiEIzPbtDXrPuv +4jvnlBcvTHzHgaqGD+FBRjtS1fq2x1vz6k7sbU8viv3NPr8okAX/C/zoN7MncjpMxoDswK +KRd9BYmKwG+GABcXX7tX0JZqugaBX+EfNAm7XJ3hx+xigfJ4Npn6XAXDh6A0i0jLrd8gYP +GuddqicnU4MZCHvNaWmX9zBBH8rIk/y+85n2iVDqzJlX4MBwCjgV7n3BbZmPhuXxt0Zx2I +OfSCuRp/HBNyD3Kev2TBDxvrqhdjiHAHi+U8U/AAAAAwEAAQAAAQA99gkX5/rknXaE+9Hc +VIzKrC+NodOkgetKwszuuNRB1HD9WVyT8A3U5307V5dSuaPmFoEF8UCugWGQzNONRq+B0T +W7Po1u2dxAo/7vMQL4RfX60icjAroExWqakfFtycIWP8UPQFGWtxVFC12C/tFRrwe3Vuu2 +t7otdEBKMRM3zU0Hj88/5FIk/MDhththDCKTMe4+iwNKo30dyqSCckpTd2k5de9JYz8Aom +87jtQcyDdynaELSo9CsA8KRPlozZ4VSWTVLH+Cv2TZWPL7hy79YvvIfuF/Sd6PGkNwG1Vj +TAbq2Wx4uq+HmpNiz7W0LnbZtQJ7dzLA3FZlvQMC8fVBAAAAgQDWvImVZCyVWpoG+LnKY3 +joegjKRYKdgKRPCqGoIHiYsqCRxqSRW3jsuQCCvk4YO3/ZmqORiGktK+5r8R1QEtwg5qbi +N7GZD34m7USNuqG2G/4puEly8syMmR6VRRvEURFQrpv2wniXNSefvsDc+WDqTfXGUxr+FT +478wkzjwc/fAAAAIEA9uP0Ym3OC3cZ5FOvmu51lxo5lqPlUeE78axg2I4u/9Il8nOvSVuq +B9X5wAUyGAGcUjT3EZmRAtL2sQxc5T0Vw3bnxCjzukEbFM+DRtYy1hXSOoGTTwKoMWBpho +R3X5uRLUQL/22C4rd7tSJpjqnZXIH0B5z2fFh4vzu8/SrgCrUAAACBALtep4BcGJfjfhfF +ODzQe7Rk7tsaX8pfNv6bQu0sR5C9pDURFRf0fRC0oqgeTuzq/vHPyNLsUUgTCpKWiLFmvU +G9pelLT3XPPgzA+g0gycM0unuX8kkP3T5VQAM/7u0+h1CaJ8A6cCkzvDJxYdfio3WR60OP +ulHg7HCcyomFLaSjAAAADGNwb0BMUjEud3VlMwECAwQFBg== +""" + +rpki_ssh_pub_replacement = """ +AAAAB3NzaC1yc2EAAAADAQABAAABAQC0s8zCIZHejqm48Nu6z1iqhqH3/uu/uLrKTKQ+Zw +RnoRGcDQ3puXsuRDGcK5fbvS6RNNk6FE69Ce9BghmiEIzPbtDXrPuv4jvnlBcvTHzHgaqG +D+FBRjtS1fq2x1vz6k7sbU8viv3NPr8okAX/C/zoN7MncjpMxoDswKKRd9BYmKwG+GABcX +X7tX0JZqugaBX+EfNAm7XJ3hx+xigfJ4Npn6XAXDh6A0i0jLrd8gYPGuddqicnU4MZCHvN +aWmX9zBBH8rIk/y+85n2iVDqzJlX4MBwCjgV7n3BbZmPhuXxt0Zx2IOfSCuRp/HBNyD3Ke +v2TBDxvrqhdjiHAHi+U8U/ +""" class TestProtocolsRPKI(VyOSUnitTestSHIM.TestCase): @classmethod @@ -44,10 +117,6 @@ class TestProtocolsRPKI(VyOSUnitTestSHIM.TestCase): self.cli_delete(base_path) self.cli_commit() - # Nothing RPKI specific should be left over in the config - # frrconfig = self.getFRRconfig('rpki') - # self.assertNotIn('rpki', frrconfig) - # check process health and continuity self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) @@ -107,28 +176,52 @@ class TestProtocolsRPKI(VyOSUnitTestSHIM.TestCase): }, } - self.cli_set(base_path + ['polling-period', polling]) + self.cli_set(['pki', 'openssh', rpki_key_name, 'private', 'key', rpki_ssh_key.replace('\n','')]) + self.cli_set(['pki', 'openssh', rpki_key_name, 'public', 'key', rpki_ssh_pub.replace('\n','')]) + self.cli_set(['pki', 'openssh', rpki_key_name, 'public', 'type', rpki_key_type]) - for peer, peer_config in cache.items(): - self.cli_set(base_path + ['cache', peer, 'port', peer_config['port']]) - self.cli_set(base_path + ['cache', peer, 'preference', peer_config['preference']]) - self.cli_set(base_path + ['cache', peer, 'ssh', 'username', peer_config['username']]) - self.cli_set(base_path + ['cache', peer, 'ssh', 'public-key-file', rpki_ssh_pub]) - self.cli_set(base_path + ['cache', peer, 'ssh', 'private-key-file', rpki_ssh_key]) + for cache_name, cache_config in cache.items(): + self.cli_set(base_path + ['cache', cache_name, 'port', cache_config['port']]) + self.cli_set(base_path + ['cache', cache_name, 'preference', cache_config['preference']]) + self.cli_set(base_path + ['cache', cache_name, 'ssh', 'username', cache_config['username']]) + self.cli_set(base_path + ['cache', cache_name, 'ssh', 'key', rpki_key_name]) # commit changes self.cli_commit() # Verify FRR configuration frrconfig = self.getFRRconfig('rpki') - self.assertIn(f'rpki polling_period {polling}', frrconfig) + for cache_name, cache_config in cache.items(): + port = cache_config['port'] + preference = cache_config['preference'] + username = cache_config['username'] + self.assertIn(f'rpki cache {cache_name} {port} {username} /run/frr/id_rpki_{cache_name} /run/frr/id_rpki_{cache_name}.pub preference {preference}', frrconfig) + + # Verify content of SSH keys + tmp = read_file(f'/run/frr/id_rpki_{cache_name}') + self.assertIn(rpki_ssh_key.replace('\n',''), tmp) + tmp = read_file(f'/run/frr/id_rpki_{cache_name}.pub') + self.assertIn(rpki_ssh_pub.replace('\n',''), tmp) + + # Change OpenSSH key and verify it was properly written to filesystem + self.cli_set(['pki', 'openssh', rpki_key_name, 'private', 'key', rpki_ssh_key_replacement.replace('\n','')]) + self.cli_set(['pki', 'openssh', rpki_key_name, 'public', 'key', rpki_ssh_pub_replacement.replace('\n','')]) + # commit changes + self.cli_commit() - for peer, peer_config in cache.items(): - port = peer_config['port'] - preference = peer_config['preference'] - username = peer_config['username'] - self.assertIn(f'rpki cache {peer} {port} {username} {rpki_ssh_key} {rpki_ssh_pub} preference {preference}', frrconfig) + for cache_name, cache_config in cache.items(): + port = cache_config['port'] + preference = cache_config['preference'] + username = cache_config['username'] + self.assertIn(f'rpki cache {cache_name} {port} {username} /run/frr/id_rpki_{cache_name} /run/frr/id_rpki_{cache_name}.pub preference {preference}', frrconfig) + # Verify content of SSH keys + tmp = read_file(f'/run/frr/id_rpki_{cache_name}') + self.assertIn(rpki_ssh_key_replacement.replace('\n',''), tmp) + tmp = read_file(f'/run/frr/id_rpki_{cache_name}.pub') + self.assertIn(rpki_ssh_pub_replacement.replace('\n',''), tmp) + + self.cli_delete(['pki', 'openssh']) def test_rpki_verify_preference(self): cache = { @@ -150,10 +243,5 @@ class TestProtocolsRPKI(VyOSUnitTestSHIM.TestCase): with self.assertRaises(ConfigSessionError): self.cli_commit() - if __name__ == '__main__': - # Create OpenSSH keypair used in RPKI tests - if not os.path.isfile(rpki_ssh_key): - cmd(f'ssh-keygen -t rsa -f {rpki_ssh_key} -N ""') - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_ipoe-server.py b/smoketest/scripts/cli/test_service_ipoe-server.py index 20a168b58..5f1cf9ad1 100755 --- a/smoketest/scripts/cli/test_service_ipoe-server.py +++ b/smoketest/scripts/cli/test_service_ipoe-server.py @@ -232,5 +232,9 @@ delegate={delegate_2_prefix},{delegate_mask},name={pool_name}""" def test_accel_ppp_options(self): pass + @unittest.skip("WINS server is not used in IPoE") + def test_accel_wins_server(self): + pass + if __name__ == "__main__": unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_conntrack.py b/smoketest/scripts/cli/test_system_conntrack.py index cea34138e..ce237a6e7 100755 --- a/smoketest/scripts/cli/test_system_conntrack.py +++ b/smoketest/scripts/cli/test_system_conntrack.py @@ -328,10 +328,10 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase): nftables_search = [ ['ct timeout ct-timeout-1 {'], ['protocol tcp'], - ['policy = { syn_sent : 77, established : 99, close : 88 }'], + ['policy = { syn_sent : 1m17s, established : 1m39s, close : 1m28s }'], ['ct timeout ct-timeout-2 {'], ['protocol udp'], - ['policy = { unreplied : 55 }'], + ['policy = { unreplied : 55s }'], ['chain VYOS_CT_TIMEOUT {'], ['ip saddr 192.0.2.1', 'ip daddr 192.0.2.2', 'tcp dport 22', 'ct timeout set "ct-timeout-1"'], ['iifname "eth1"', 'meta l4proto udp', 'ip saddr 198.51.100.1', 'ct timeout set "ct-timeout-2"'] @@ -340,7 +340,7 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase): nftables6_search = [ ['ct timeout ct-timeout-1 {'], ['protocol tcp'], - ['policy = { last_ack : 33, time_wait : 22 }'], + ['policy = { last_ack : 33s, time_wait : 22s }'], ['chain VYOS_CT_TIMEOUT {'], ['iifname "eth2"', 'meta l4proto tcp', 'ip6 saddr 2001:db8::1', 'ct timeout set "ct-timeout-1"'] ] diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py index 4be40e99e..3ab6ac5c3 100755 --- a/src/conf_mode/pki.py +++ b/src/conf_mode/pki.py @@ -24,11 +24,12 @@ from vyos.config import config_dict_merge from vyos.configdep import set_dependents from vyos.configdep import call_dependents from vyos.configdict import node_changed -from vyos.configdiff import Diff from vyos.defaults import directories from vyos.pki import is_ca_certificate from vyos.pki import load_certificate from vyos.pki import load_public_key +from vyos.pki import load_openssh_public_key +from vyos.pki import load_openssh_private_key from vyos.pki import load_private_key from vyos.pki import load_crl from vyos.pki import load_dh_parameters @@ -64,6 +65,10 @@ sync_search = [ 'path': ['interfaces', 'sstpc'], }, { + 'keys': ['key'], + 'path': ['protocols', 'rpki', 'cache'], + }, + { 'keys': ['certificate', 'ca_certificate', 'local_key', 'remote_key'], 'path': ['vpn', 'ipsec'], }, @@ -86,7 +91,8 @@ sync_translate = { 'remote_key': 'key_pair', 'shared_secret_key': 'openvpn', 'auth_key': 'openvpn', - 'crypt_key': 'openvpn' + 'crypt_key': 'openvpn', + 'key': 'openssh', } def certbot_delete(certificate): @@ -150,6 +156,11 @@ def get_config(config=None): if 'changed' not in pki: pki.update({'changed':{}}) pki['changed'].update({'key_pair' : tmp}) + tmp = node_changed(conf, base + ['openssh'], recursive=True) + if tmp: + if 'changed' not in pki: pki.update({'changed':{}}) + pki['changed'].update({'openssh' : tmp}) + tmp = node_changed(conf, base + ['openvpn', 'shared-secret'], recursive=True) if tmp: if 'changed' not in pki: pki.update({'changed':{}}) @@ -241,6 +252,17 @@ def is_valid_private_key(raw_data, protected=False): return True return load_private_key(raw_data, passphrase=None, wrap_tags=True) +def is_valid_openssh_public_key(raw_data, type): + # If it loads correctly we're good, or return False + return load_openssh_public_key(raw_data, type) + +def is_valid_openssh_private_key(raw_data, protected=False): + # If it loads correctly we're good, or return False + # With encrypted private keys, we always return true as we cannot ask for password to verify + if protected: + return True + return load_openssh_private_key(raw_data, passphrase=None, wrap_tags=True) + def is_valid_crl(raw_data): # If it loads correctly we're good, or return False return load_crl(raw_data, wrap_tags=True) @@ -322,6 +344,20 @@ def verify(pki): if not is_valid_private_key(private['key'], protected): raise ConfigError(f'Invalid private key on key-pair "{name}"') + if 'openssh' in pki: + for name, key_conf in pki['openssh'].items(): + if 'public' in key_conf and 'key' in key_conf['public']: + if 'type' not in key_conf['public']: + raise ConfigError(f'Must define OpenSSH public key type for "{name}"') + if not is_valid_openssh_public_key(key_conf['public']['key'], key_conf['public']['type']): + raise ConfigError(f'Invalid OpenSSH public key "{name}"') + + if 'private' in key_conf and 'key' in key_conf['private']: + private = key_conf['private'] + protected = 'password_protected' in private + if not is_valid_openssh_private_key(private['key'], protected): + raise ConfigError(f'Invalid OpenSSH private key "{name}"') + if 'x509' in pki: if 'default' in pki['x509']: default_values = pki['x509']['default'] diff --git a/src/conf_mode/protocols_eigrp.py b/src/conf_mode/protocols_eigrp.py index 609b39065..c13e52a3d 100755 --- a/src/conf_mode/protocols_eigrp.py +++ b/src/conf_mode/protocols_eigrp.py @@ -19,6 +19,7 @@ from sys import argv from vyos.config import Config from vyos.configdict import dict_merge +from vyos.configverify import verify_vrf from vyos.template import render_to_string from vyos import ConfigError from vyos import frr @@ -72,7 +73,14 @@ def get_config(config=None): return eigrp def verify(eigrp): - pass + if not eigrp or 'deleted' in eigrp: + return + + if 'system_as' not in eigrp: + raise ConfigError('EIGRP system-as must be defined!') + + if 'vrf' in eigrp: + verify_vrf(eigrp) def generate(eigrp): if not eigrp or 'deleted' in eigrp: diff --git a/src/conf_mode/protocols_rpki.py b/src/conf_mode/protocols_rpki.py index 0fc14e868..a59ecf3e4 100755 --- a/src/conf_mode/protocols_rpki.py +++ b/src/conf_mode/protocols_rpki.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2024 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 @@ -16,16 +16,22 @@ import os +from glob import glob from sys import exit from vyos.config import Config +from vyos.pki import wrap_openssh_public_key +from vyos.pki import wrap_openssh_private_key from vyos.template import render_to_string -from vyos.utils.dict import dict_search +from vyos.utils.dict import dict_search_args +from vyos.utils.file import write_file from vyos import ConfigError from vyos import frr from vyos import airbag airbag.enable() +rpki_ssh_key_base = '/run/frr/id_rpki' + def get_config(config=None): if config: conf = config @@ -33,7 +39,8 @@ def get_config(config=None): conf = Config() base = ['protocols', 'rpki'] - rpki = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + rpki = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, with_pki=True) # Bail out early if configuration tree does not exist if not conf.exists(base): rpki.update({'deleted' : ''}) @@ -63,22 +70,40 @@ def verify(rpki): preferences.append(preference) if 'ssh' in peer_config: - files = ['private_key_file', 'public_key_file'] - for file in files: - if file not in peer_config['ssh']: - raise ConfigError('RPKI+SSH requires username and public/private ' \ - 'key file to be defined!') + if 'username' not in peer_config['ssh']: + raise ConfigError('RPKI+SSH requires username to be defined!') + + if 'key' not in peer_config['ssh'] or 'openssh' not in rpki['pki']: + raise ConfigError('RPKI+SSH requires key to be defined!') - filename = peer_config['ssh'][file] - if not os.path.exists(filename): - raise ConfigError(f'RPKI SSH {file.replace("-","-")} "{filename}" does not exist!') + if peer_config['ssh']['key'] not in rpki['pki']['openssh']: + raise ConfigError('RPKI+SSH key not found on PKI subsystem!') return None def generate(rpki): + for key in glob(f'{rpki_ssh_key_base}*'): + os.unlink(key) + if not rpki: return + + if 'cache' in rpki: + for cache, cache_config in rpki['cache'].items(): + if 'ssh' in cache_config: + key_name = cache_config['ssh']['key'] + public_key_data = dict_search_args(rpki['pki'], 'openssh', key_name, 'public', 'key') + public_key_type = dict_search_args(rpki['pki'], 'openssh', key_name, 'public', 'type') + private_key_data = dict_search_args(rpki['pki'], 'openssh', key_name, 'private', 'key') + + cache_config['ssh']['public_key_file'] = f'{rpki_ssh_key_base}_{cache}.pub' + cache_config['ssh']['private_key_file'] = f'{rpki_ssh_key_base}_{cache}' + + write_file(cache_config['ssh']['public_key_file'], wrap_openssh_public_key(public_key_data, public_key_type)) + write_file(cache_config['ssh']['private_key_file'], wrap_openssh_private_key(private_key_data)) + rpki['new_frr_config'] = render_to_string('frr/rpki.frr.j2', rpki) + return None def apply(rpki): diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py index 5f72b983c..852b714eb 100755 --- a/src/conf_mode/service_ipoe-server.py +++ b/src/conf_mode/service_ipoe-server.py @@ -25,8 +25,10 @@ from vyos.template import render from vyos.utils.process import call from vyos.utils.dict import dict_search from vyos.accel_ppp_util import get_pools_in_order +from vyos.accel_ppp_util import verify_accel_ppp_name_servers +from vyos.accel_ppp_util import verify_accel_ppp_wins_servers from vyos.accel_ppp_util import verify_accel_ppp_ip_pool -from vyos.accel_ppp_util import verify_accel_ppp_base_service +from vyos.accel_ppp_util import verify_accel_ppp_authentication from vyos import ConfigError from vyos import airbag airbag.enable() @@ -69,8 +71,10 @@ def verify(ipoe): raise ConfigError('Option "client-subnet" incompatible with "vlan"!' 'Use "ipoe client-ip-pool" instead.') - verify_accel_ppp_base_service(ipoe, local_users=False) + verify_accel_ppp_authentication(ipoe, local_users=False) verify_accel_ppp_ip_pool(ipoe) + verify_accel_ppp_name_servers(ipoe) + verify_accel_ppp_wins_servers(ipoe) return None diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py index c2dfbdb44..c9d1e805f 100755 --- a/src/conf_mode/service_pppoe-server.py +++ b/src/conf_mode/service_pppoe-server.py @@ -25,7 +25,9 @@ from vyos.configverify import verify_interface_exists from vyos.template import render from vyos.utils.process import call from vyos.utils.dict import dict_search -from vyos.accel_ppp_util import verify_accel_ppp_base_service +from vyos.accel_ppp_util import verify_accel_ppp_name_servers +from vyos.accel_ppp_util import verify_accel_ppp_wins_servers +from vyos.accel_ppp_util import verify_accel_ppp_authentication from vyos.accel_ppp_util import verify_accel_ppp_ip_pool from vyos.accel_ppp_util import get_pools_in_order from vyos import ConfigError @@ -67,11 +69,11 @@ def verify(pppoe): if not pppoe: return None - verify_accel_ppp_base_service(pppoe) + verify_accel_ppp_authentication(pppoe) verify_accel_ppp_ip_pool(pppoe) + verify_accel_ppp_name_servers(pppoe) + verify_accel_ppp_wins_servers(pppoe) - if 'wins_server' in pppoe and len(pppoe['wins_server']) > 2: - raise ConfigError('Not more then two WINS name-servers can be configured') if 'interface' not in pppoe: raise ConfigError('At least one listen interface must be defined!') diff --git a/src/conf_mode/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py index 266381754..04ccbcec3 100755 --- a/src/conf_mode/vpn_l2tp.py +++ b/src/conf_mode/vpn_l2tp.py @@ -24,7 +24,9 @@ from vyos.configdict import get_accel_dict from vyos.template import render from vyos.utils.process import call from vyos.utils.dict import dict_search -from vyos.accel_ppp_util import verify_accel_ppp_base_service +from vyos.accel_ppp_util import verify_accel_ppp_name_servers +from vyos.accel_ppp_util import verify_accel_ppp_wins_servers +from vyos.accel_ppp_util import verify_accel_ppp_authentication from vyos.accel_ppp_util import verify_accel_ppp_ip_pool from vyos.accel_ppp_util import get_pools_in_order from vyos import ConfigError @@ -62,12 +64,10 @@ def verify(l2tp): if not l2tp: return None - verify_accel_ppp_base_service(l2tp) + verify_accel_ppp_authentication(l2tp) verify_accel_ppp_ip_pool(l2tp) - - if 'wins_server' in l2tp and len(l2tp['wins_server']) > 2: - raise ConfigError( - 'Not more then two WINS name-servers can be configured') + verify_accel_ppp_name_servers(l2tp) + verify_accel_ppp_wins_servers(l2tp) return None diff --git a/src/conf_mode/vpn_pptp.py b/src/conf_mode/vpn_pptp.py index b1d5067d5..c0d8330bd 100755 --- a/src/conf_mode/vpn_pptp.py +++ b/src/conf_mode/vpn_pptp.py @@ -22,7 +22,9 @@ from vyos.config import Config from vyos.template import render from vyos.utils.process import call from vyos.utils.dict import dict_search -from vyos.accel_ppp_util import verify_accel_ppp_base_service +from vyos.accel_ppp_util import verify_accel_ppp_name_servers +from vyos.accel_ppp_util import verify_accel_ppp_wins_servers +from vyos.accel_ppp_util import verify_accel_ppp_authentication from vyos.accel_ppp_util import verify_accel_ppp_ip_pool from vyos.accel_ppp_util import get_pools_in_order from vyos import ConfigError @@ -60,12 +62,10 @@ def verify(pptp): if not pptp: return None - verify_accel_ppp_base_service(pptp) + verify_accel_ppp_authentication(pptp) verify_accel_ppp_ip_pool(pptp) - - if 'wins_server' in pptp and len(pptp['wins_server']) > 2: - raise ConfigError( - 'Not more then two WINS name-servers can be configured') + verify_accel_ppp_name_servers(pptp) + verify_accel_ppp_wins_servers(pptp) def generate(pptp): diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py index 5c229fe62..8661a8aff 100755 --- a/src/conf_mode/vpn_sstp.py +++ b/src/conf_mode/vpn_sstp.py @@ -26,7 +26,9 @@ from vyos.template import render from vyos.utils.process import call from vyos.utils.network import check_port_availability from vyos.utils.dict import dict_search -from vyos.accel_ppp_util import verify_accel_ppp_base_service +from vyos.accel_ppp_util import verify_accel_ppp_name_servers +from vyos.accel_ppp_util import verify_accel_ppp_wins_servers +from vyos.accel_ppp_util import verify_accel_ppp_authentication from vyos.accel_ppp_util import verify_accel_ppp_ip_pool from vyos.accel_ppp_util import get_pools_in_order from vyos.utils.network import is_listen_port_bind_service @@ -43,48 +45,18 @@ cert_file_path = os.path.join(cfg_dir, 'sstp-cert.pem') cert_key_path = os.path.join(cfg_dir, 'sstp-cert.key') ca_cert_file_path = os.path.join(cfg_dir, 'sstp-ca.pem') -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - base = ['vpn', 'sstp'] - if not conf.exists(base): - return None - - # retrieve common dictionary keys - sstp = get_accel_dict(conf, base, sstp_chap_secrets, with_pki=True) - if dict_search('client_ip_pool', sstp): - # Multiple named pools require ordered values T5099 - sstp['ordered_named_pools'] = get_pools_in_order(dict_search('client_ip_pool', sstp)) - - sstp['server_type'] = 'sstp' - return sstp - - -def verify(sstp): - if not sstp: - return None - - port = sstp.get('port') - proto = 'tcp' - if check_port_availability('0.0.0.0', int(port), proto) is not True and \ - not is_listen_port_bind_service(int(port), 'accel-pppd'): - raise ConfigError(f'"{proto}" port "{port}" is used by another service') - - verify_accel_ppp_base_service(sstp) - verify_accel_ppp_ip_pool(sstp) +def verify_certificate(config): # # SSL certificate checks # - if not sstp['pki']: + if not config['pki']: raise ConfigError('PKI is not configured') - if 'ssl' not in sstp: + if 'ssl' not in config: raise ConfigError('SSL missing on SSTP config') - ssl = sstp['ssl'] + ssl = config['ssl'] # CA if 'ca_certificate' not in ssl: @@ -92,10 +64,10 @@ def verify(sstp): ca_name = ssl['ca_certificate'] - if ca_name not in sstp['pki']['ca']: + if ca_name not in config['pki']['ca']: raise ConfigError('Invalid CA certificate on SSTP config') - if 'certificate' not in sstp['pki']['ca'][ca_name]: + if 'certificate' not in config['pki']['ca'][ca_name]: raise ConfigError('Missing certificate data for CA certificate on SSTP config') # Certificate @@ -104,10 +76,10 @@ def verify(sstp): cert_name = ssl['certificate'] - if cert_name not in sstp['pki']['certificate']: + if cert_name not in config['pki']['certificate']: raise ConfigError('Invalid certificate on SSTP config') - pki_cert = sstp['pki']['certificate'][cert_name] + pki_cert = config['pki']['certificate'][cert_name] if 'certificate' not in pki_cert: raise ConfigError('Missing certificate data for certificate on SSTP config') @@ -118,6 +90,43 @@ def verify(sstp): if 'password_protected' in pki_cert['private']: raise ConfigError('Encrypted private key is not supported on SSTP config') + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['vpn', 'sstp'] + if not conf.exists(base): + return None + + # retrieve common dictionary keys + sstp = get_accel_dict(conf, base, sstp_chap_secrets, with_pki=True) + if dict_search('client_ip_pool', sstp): + # Multiple named pools require ordered values T5099 + sstp['ordered_named_pools'] = get_pools_in_order(dict_search('client_ip_pool', sstp)) + + sstp['server_type'] = 'sstp' + return sstp + + +def verify(sstp): + if not sstp: + return None + + port = sstp.get('port') + proto = 'tcp' + if check_port_availability('0.0.0.0', int(port), proto) is not True and \ + not is_listen_port_bind_service(int(port), 'accel-pppd'): + raise ConfigError(f'"{proto}" port "{port}" is used by another service') + + verify_accel_ppp_authentication(sstp) + verify_accel_ppp_ip_pool(sstp) + verify_accel_ppp_name_servers(sstp) + verify_accel_ppp_wins_servers(sstp) + verify_certificate(sstp) + + def generate(sstp): if not sstp: return None @@ -143,6 +152,7 @@ def generate(sstp): return sstp + def apply(sstp): if not sstp: call('systemctl stop accel-ppp@sstp.service') diff --git a/src/init/vyos-router b/src/init/vyos-router index 2b4fac5ef..eac3e7e47 100755 --- a/src/init/vyos-router +++ b/src/init/vyos-router @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2024 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 @@ -449,7 +449,7 @@ start () run_postconfig_scripts tmp=$(${vyos_libexec_dir}/read-saved-value.py --path "protocols rpki cache") - if [ ! -z $tmp ]; then + if [[ ! -z "$tmp" ]]; then vtysh -c "rpki start" fi } diff --git a/src/migration-scripts/ipsec/6-to-7 b/src/migration-scripts/ipsec/6-to-7 index 71fbbe8a1..f8b6de560 100755 --- a/src/migration-scripts/ipsec/6-to-7 +++ b/src/migration-scripts/ipsec/6-to-7 @@ -63,7 +63,7 @@ if config.exists(ipsec_site_base): changes_made = True peer_x509_base = ipsec_site_base + [peer, 'authentication', 'x509'] - pki_name = 'peer_' + peer.replace(".", "-") + pki_name = 'peer_' + peer.replace(".", "-").replace("@", "") if config.exists(peer_x509_base + ['cert-file']): cert_file = config.return_value(peer_x509_base + ['cert-file']) diff --git a/src/migration-scripts/l2tp/8-to-9 b/src/migration-scripts/l2tp/8-to-9 new file mode 100755 index 000000000..e85a3892b --- /dev/null +++ b/src/migration-scripts/l2tp/8-to-9 @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 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/>. + +# Deleted 'dhcp-interface' from l2tp + +import os + +from sys import argv +from sys import exit +from vyos.configtree import ConfigTree + + +if len(argv) < 2: + print("Must specify file name!") + exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +config = ConfigTree(config_file) +base = ['vpn', 'l2tp', 'remote-access'] +if not config.exists(base): + exit(0) + +#deleting unused dhcp-interface +if config.exists(base + ['dhcp-interface']): + config.delete(base + ['dhcp-interface']) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print("Failed to save the modified config: {}".format(e)) + exit(1) diff --git a/src/migration-scripts/rpki/1-to-2 b/src/migration-scripts/rpki/1-to-2 index 559440bba..50d4a3dfc 100755 --- a/src/migration-scripts/rpki/1-to-2 +++ b/src/migration-scripts/rpki/1-to-2 @@ -19,7 +19,11 @@ from sys import exit from sys import argv + from vyos.configtree import ConfigTree +from vyos.pki import OPENSSH_KEY_BEGIN +from vyos.pki import OPENSSH_KEY_END +from vyos.utils.file import read_file if len(argv) < 2: print("Must specify file name!") @@ -43,6 +47,24 @@ if config.exists(base + ['cache']): if config.exists(ssh_node + ['known-hosts-file']): config.delete(ssh_node + ['known-hosts-file']) + if config.exists(base + ['cache', cache, 'ssh']): + private_key_node = base + ['cache', cache, 'ssh', 'private-key-file'] + private_key_file = config.return_value(private_key_node) + private_key = read_file(private_key_file).replace(OPENSSH_KEY_BEGIN, '').replace(OPENSSH_KEY_END, '').replace('\n','') + + public_key_node = base + ['cache', cache, 'ssh', 'public-key-file'] + public_key_file = config.return_value(public_key_node) + public_key = read_file(public_key_file).split() + + config.set(['pki', 'openssh', f'rpki-{cache}', 'private', 'key'], value=private_key) + config.set(['pki', 'openssh', f'rpki-{cache}', 'public', 'key'], value=public_key[1]) + config.set(['pki', 'openssh', f'rpki-{cache}', 'public', 'type'], value=public_key[0]) + config.set_tag(['pki', 'openssh']) + config.set(ssh_node + ['key'], value=f'rpki-{cache}') + + config.delete(private_key_node) + config.delete(public_key_node) + try: with open(file_name, 'w') as f: f.write(config.to_string()) diff --git a/src/op_mode/dhcp.py b/src/op_mode/dhcp.py index a64acec31..1d9ad0e76 100755 --- a/src/op_mode/dhcp.py +++ b/src/op_mode/dhcp.py @@ -29,8 +29,8 @@ from vyos.base import Warning from vyos.configquery import ConfigTreeQuery from vyos.kea import kea_get_active_config +from vyos.kea import kea_get_leases from vyos.kea import kea_get_pool_from_subnet_id -from vyos.kea import kea_parse_leases from vyos.utils.process import is_systemd_service_running time_string = "%a %b %d %H:%M:%S %Z %Y" @@ -38,7 +38,8 @@ time_string = "%a %b %d %H:%M:%S %Z %Y" config = ConfigTreeQuery() lease_valid_states = ['all', 'active', 'free', 'expired', 'released', 'abandoned', 'reset', 'backup'] sort_valid_inet = ['end', 'mac', 'hostname', 'ip', 'pool', 'remaining', 'start', 'state'] -sort_valid_inet6 = ['end', 'iaid_duid', 'ip', 'last_communication', 'pool', 'remaining', 'state', 'type'] +sort_valid_inet6 = ['end', 'duid', 'ip', 'last_communication', 'pool', 'remaining', 'state', 'type'] +mapping_sort_valid = ['mac', 'ip', 'pool', 'duid'] ArgFamily = typing.Literal['inet', 'inet6'] ArgState = typing.Literal['all', 'active', 'free', 'expired', 'released', 'abandoned', 'reset', 'backup'] @@ -77,8 +78,7 @@ def _get_raw_server_leases(family='inet', pool=None, sorted=None, state=[], orig :return list """ inet_suffix = '6' if family == 'inet6' else '4' - lease_file = f'/config/dhcp/dhcp{inet_suffix}-leases.csv' - leases = kea_parse_leases(lease_file) + leases = kea_get_leases(inet_suffix) if pool is None: pool = _get_dhcp_pools(family=family) @@ -89,28 +89,37 @@ def _get_raw_server_leases(family='inet', pool=None, sorted=None, state=[], orig data = [] for lease in leases: + lifetime = lease['valid-lft'] + expiry = (lease['cltt'] + lifetime) + + lease['start_timestamp'] = datetime.utcfromtimestamp(expiry - lifetime) + lease['expire_timestamp'] = datetime.utcfromtimestamp(expiry) if expiry else None + data_lease = {} - data_lease['ip'] = lease['address'] - lease_state_long = {'0': 'active', '1': 'rejected', '2': 'expired'} + data_lease['ip'] = lease['ip-address'] + lease_state_long = {0: 'active', 1: 'rejected', 2: 'expired'} data_lease['state'] = lease_state_long[lease['state']] - data_lease['pool'] = kea_get_pool_from_subnet_id(active_config, inet_suffix, lease['subnet_id']) if active_config else '-' + data_lease['pool'] = kea_get_pool_from_subnet_id(active_config, inet_suffix, lease['subnet-id']) if active_config else '-' data_lease['end'] = lease['expire_timestamp'].timestamp() if lease['expire_timestamp'] else None data_lease['origin'] = 'local' # TODO: Determine remote in HA if family == 'inet': - data_lease['mac'] = lease['hwaddr'] + data_lease['mac'] = lease['hw-address'] data_lease['start'] = lease['start_timestamp'].timestamp() data_lease['hostname'] = lease['hostname'] if family == 'inet6': data_lease['last_communication'] = lease['start_timestamp'].timestamp() - data_lease['iaid_duid'] = _format_hex_string(lease['duid']) - lease_types_long = {'0': 'non-temporary', '1': 'temporary', '2': 'prefix delegation'} - data_lease['type'] = lease_types_long[lease['lease_type']] + data_lease['duid'] = _format_hex_string(lease['duid']) + data_lease['type'] = lease['type'] + + if lease['type'] == 'IA_PD': + prefix_len = lease['prefix-len'] + data_lease['ip'] += f'/{prefix_len}' data_lease['remaining'] = '-' - if lease['expire']: + if lease['valid-lft'] > 0: data_lease['remaining'] = lease['expire_timestamp'] - datetime.utcnow() if data_lease['remaining'].days >= 0: @@ -172,11 +181,11 @@ def _get_formatted_server_leases(raw_data, family='inet'): remain = lease.get('remaining') lease_type = lease.get('type') pool = lease.get('pool') - host_identifier = lease.get('iaid_duid') + host_identifier = lease.get('duid') data_entries.append([ipaddr, state, start, end, remain, lease_type, pool, host_identifier]) headers = ['IPv6 address', 'State', 'Last communication', 'Lease expiration', 'Remaining', 'Type', 'Pool', - 'IAID_DUID'] + 'DUID'] output = tabulate(data_entries, headers, numalign='left') return output @@ -241,6 +250,47 @@ def _get_formatted_pool_statistics(pool_data, family='inet'): output = tabulate(data_entries, headers, numalign='left') return output +def _get_raw_server_static_mappings(family='inet', pool=None, sorted=None): + if pool is None: + pool = _get_dhcp_pools(family=family) + else: + pool = [pool] + + v = 'v6' if family == 'inet6' else '' + mappings = [] + for p in pool: + pool_config = config.get_config_dict(['service', f'dhcp{v}-server', 'shared-network-name', p], + get_first_key=True) + if 'subnet' in pool_config: + for subnet, subnet_config in pool_config['subnet'].items(): + if 'static-mapping' in subnet_config: + for name, mapping_config in subnet_config['static-mapping'].items(): + mapping = {'pool': p, 'subnet': subnet, 'name': name} + mapping.update(mapping_config) + mappings.append(mapping) + + if sorted: + if sorted == 'ip': + data.sort(key = lambda x:ip_address(x['ip-address'])) + else: + data.sort(key = lambda x:x[sorted]) + return mappings + +def _get_formatted_server_static_mappings(raw_data, family='inet'): + data_entries = [] + for entry in raw_data: + pool = entry.get('pool') + subnet = entry.get('subnet') + name = entry.get('name') + ip_addr = entry.get('ip-address', 'N/A') + mac_addr = entry.get('mac', 'N/A') + duid = entry.get('duid', 'N/A') + description = entry.get('description', 'N/A') + data_entries.append([pool, subnet, name, ip_addr, mac_addr, duid, description]) + + headers = ['Pool', 'Subnet', 'Name', 'IP Address', 'MAC Address', 'DUID', 'Description'] + output = tabulate(data_entries, headers, numalign='left') + return output def _verify(func): """Decorator checks if DHCP(v6) config exists""" @@ -294,6 +344,21 @@ def show_server_leases(raw: bool, family: ArgFamily, pool: typing.Optional[str], else: return _get_formatted_server_leases(lease_data, family=family) +@_verify +def show_server_static_mappings(raw: bool, family: ArgFamily, pool: typing.Optional[str], + sorted: typing.Optional[str]): + v = 'v6' if family == 'inet6' else '' + if pool and pool not in _get_dhcp_pools(family=family): + raise vyos.opmode.IncorrectValue(f'DHCP{v} pool "{pool}" does not exist!') + + if sorted and sorted not in mapping_sort_valid: + raise vyos.opmode.IncorrectValue(f'DHCP{v} sort "{sorted}" is invalid!') + + static_mappings = _get_raw_server_static_mappings(family=family, pool=pool, sorted=sorted) + if raw: + return static_mappings + else: + return _get_formatted_server_static_mappings(static_mappings, family=family) def _get_raw_client_leases(family='inet', interface=None): from time import mktime |