summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/config-mode-dependencies/vyos-1x.json1
-rw-r--r--data/templates/frr/bgpd.frr.j22
-rw-r--r--data/templates/frr/static_routes_macro.j26
-rw-r--r--interface-definitions/include/bgp/neighbor-path-attribute.xml.i1
-rw-r--r--interface-definitions/include/pki/openssh-key.xml.i14
-rw-r--r--interface-definitions/include/static/static-route-reject.xml.i1
-rw-r--r--interface-definitions/include/static/static-route-segments.xml.i14
-rw-r--r--interface-definitions/include/static/static-route6.xml.i5
-rw-r--r--interface-definitions/include/version/dhcpv6-server-version.xml.i2
-rw-r--r--interface-definitions/pki.xml.in39
-rw-r--r--interface-definitions/protocols_rpki.xml.in17
-rw-r--r--interface-definitions/service_dhcpv6-server.xml.in15
-rw-r--r--python/vyos/kea.py3
-rw-r--r--python/vyos/pki.py31
-rwxr-xr-xsmoketest/bin/vyos-configtest-pki41
-rw-r--r--smoketest/configs/rpki-only71
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py4
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_bgp.py13
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_rpki.py140
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_static.py62
-rwxr-xr-xsmoketest/scripts/cli/test_service_dhcpv6-server.py2
-rwxr-xr-xsrc/conf_mode/pki.py40
-rwxr-xr-xsrc/conf_mode/protocols_rpki.py47
-rwxr-xr-xsrc/init/vyos-router4
-rwxr-xr-xsrc/migration-scripts/dhcpv6-server/4-to-568
-rwxr-xr-xsrc/migration-scripts/ipsec/6-to-72
-rwxr-xr-xsrc/migration-scripts/rpki/1-to-222
-rwxr-xr-xsrc/validators/ipv6-srv6-segments13
28 files changed, 586 insertions, 94 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/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2
index 419d0bee1..e9422b257 100644
--- a/data/templates/frr/bgpd.frr.j2
+++ b/data/templates/frr/bgpd.frr.j2
@@ -76,7 +76,7 @@
neighbor {{ neighbor }} password {{ config.password }}
{% endif %}
{% if config.path_attribute.discard is vyos_defined %}
- neighbor {{ neighbor }} path-attribute discard {{ config.path_attribute.discard }}
+ neighbor {{ neighbor }} path-attribute discard {{ config.path_attribute.discard | join(' ') }}
{% endif %}
{% if config.path_attribute.treat_as_withdraw is vyos_defined %}
neighbor {{ neighbor }} path-attribute treat-as-withdraw {{ config.path_attribute.treat_as_withdraw }}
diff --git a/data/templates/frr/static_routes_macro.j2 b/data/templates/frr/static_routes_macro.j2
index 8afd4a68a..cf8046968 100644
--- a/data/templates/frr/static_routes_macro.j2
+++ b/data/templates/frr/static_routes_macro.j2
@@ -13,17 +13,17 @@
{% endif %}
{% if prefix_config.interface is vyos_defined %}
{% for interface, interface_config in prefix_config.interface.items() if interface_config.disable is not defined %}
-{{ ip_ipv6 }} route {{ prefix }} {{ interface }} {{ interface_config.distance if interface_config.distance is vyos_defined }} {{ 'nexthop-vrf ' ~ interface_config.vrf if interface_config.vrf is vyos_defined }} {{ 'table ' ~ table if table is vyos_defined }}
+{{ ip_ipv6 }} route {{ prefix }} {{ interface }} {{ interface_config.distance if interface_config.distance is vyos_defined }} {{ 'nexthop-vrf ' ~ interface_config.vrf if interface_config.vrf is vyos_defined }} {{ 'segments ' ~ interface_config.segments if interface_config.segments is vyos_defined }} {{ 'table ' ~ table if table is vyos_defined }}
{% endfor %}
{% endif %}
{% if prefix_config.next_hop is vyos_defined and prefix_config.next_hop is not none %}
{% for next_hop, next_hop_config in prefix_config.next_hop.items() if next_hop_config.disable is not defined %}
-{{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} {{ next_hop_config.interface if next_hop_config.interface is vyos_defined }} {{ next_hop_config.distance if next_hop_config.distance is vyos_defined }} {{ 'nexthop-vrf ' ~ next_hop_config.vrf if next_hop_config.vrf is vyos_defined }} {{ 'bfd profile ' ~ next_hop_config.bfd.profile if next_hop_config.bfd.profile is vyos_defined }} {{ 'table ' ~ table if table is vyos_defined }}
+{{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} {{ next_hop_config.interface if next_hop_config.interface is vyos_defined }} {{ next_hop_config.distance if next_hop_config.distance is vyos_defined }} {{ 'nexthop-vrf ' ~ next_hop_config.vrf if next_hop_config.vrf is vyos_defined }} {{ 'bfd profile ' ~ next_hop_config.bfd.profile if next_hop_config.bfd.profile is vyos_defined }} {{ 'segments ' ~ next_hop_config.segments if next_hop_config.segments is vyos_defined }} {{ 'table ' ~ table if table is vyos_defined }}
{% if next_hop_config.bfd.multi_hop.source is vyos_defined %}
{% for source, source_config in next_hop_config.bfd.multi_hop.source.items() %}
{{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} bfd multi-hop source {{ source }} profile {{ source_config.profile }}
{% endfor %}
-{% endif %}
+{% endif %}
{% endfor %}
{% endif %}
{% endmacro %}
diff --git a/interface-definitions/include/bgp/neighbor-path-attribute.xml.i b/interface-definitions/include/bgp/neighbor-path-attribute.xml.i
index 30568d8c6..399a6bcdd 100644
--- a/interface-definitions/include/bgp/neighbor-path-attribute.xml.i
+++ b/interface-definitions/include/bgp/neighbor-path-attribute.xml.i
@@ -14,6 +14,7 @@
<constraint>
<validator name="numeric" argument="--range 1-255"/>
</constraint>
+ <multi/>
</properties>
</leafNode>
<leafNode name="treat-as-withdraw">
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/static/static-route-reject.xml.i b/interface-definitions/include/static/static-route-reject.xml.i
index 81d4f9afd..ef713ac85 100644
--- a/interface-definitions/include/static/static-route-reject.xml.i
+++ b/interface-definitions/include/static/static-route-reject.xml.i
@@ -9,4 +9,3 @@
</children>
</node>
<!-- include end -->
-
diff --git a/interface-definitions/include/static/static-route-segments.xml.i b/interface-definitions/include/static/static-route-segments.xml.i
new file mode 100644
index 000000000..2068b1ab4
--- /dev/null
+++ b/interface-definitions/include/static/static-route-segments.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from static/static-route-segments.xml.i -->
+<leafNode name="segments">
+ <properties>
+ <help>SRv6 segments</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Segs (SIDs)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-srv6-segments"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <!-- include end -->
diff --git a/interface-definitions/include/static/static-route6.xml.i b/interface-definitions/include/static/static-route6.xml.i
index a83cc230b..4468c8025 100644
--- a/interface-definitions/include/static/static-route6.xml.i
+++ b/interface-definitions/include/static/static-route6.xml.i
@@ -31,6 +31,7 @@
<children>
#include <include/generic-disable-node.xml.i>
#include <include/static/static-route-distance.xml.i>
+ #include <include/static/static-route-segments.xml.i>
#include <include/static/static-route-vrf.xml.i>
</children>
</tagNode>
@@ -47,13 +48,13 @@
</properties>
<children>
#include <include/generic-disable-node.xml.i>
+ #include <include/static/static-route-bfd.xml.i>
#include <include/static/static-route-distance.xml.i>
#include <include/static/static-route-interface.xml.i>
+ #include <include/static/static-route-segments.xml.i>
#include <include/static/static-route-vrf.xml.i>
- #include <include/static/static-route-bfd.xml.i>
</children>
</tagNode>
</children>
</tagNode>
<!-- include end -->
-
diff --git a/interface-definitions/include/version/dhcpv6-server-version.xml.i b/interface-definitions/include/version/dhcpv6-server-version.xml.i
index bfef27b77..1f30368a3 100644
--- a/interface-definitions/include/version/dhcpv6-server-version.xml.i
+++ b/interface-definitions/include/version/dhcpv6-server-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/dhcpv6-server-version.xml.i -->
-<syntaxVersion component='dhcpv6-server' version='4'></syntaxVersion>
+<syntaxVersion component='dhcpv6-server' version='5'></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_dhcpv6-server.xml.in b/interface-definitions/service_dhcpv6-server.xml.in
index 28b97a64b..daca7b43f 100644
--- a/interface-definitions/service_dhcpv6-server.xml.in
+++ b/interface-definitions/service_dhcpv6-server.xml.in
@@ -97,6 +97,21 @@
</properties>
<children>
#include <include/dhcp/option-v6.xml.i>
+ <leafNode name="interface">
+ <properties>
+ <help>Optional interface for this subnet to accept requests from</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Interface name</description>
+ </valueHelp>
+ <constraint>
+ #include <include/constraint/interface-name.xml.i>
+ </constraint>
+ </properties>
+ </leafNode>
<tagNode name="range">
<properties>
<help>Parameters setting ranges for assigning IPv6 addresses</help>
diff --git a/python/vyos/kea.py b/python/vyos/kea.py
index 720bebec3..7365c1f02 100644
--- a/python/vyos/kea.py
+++ b/python/vyos/kea.py
@@ -212,6 +212,9 @@ def kea6_parse_subnet(subnet, config):
if 'option' in config:
out['option-data'] = kea6_parse_options(config['option'])
+ if 'interface' in config:
+ out['interface'] = config['interface']
+
if 'range' in config:
pools = []
for num, range_config in config['range'].items():
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/configs/rpki-only b/smoketest/configs/rpki-only
new file mode 100644
index 000000000..0f89b9a1b
--- /dev/null
+++ b/smoketest/configs/rpki-only
@@ -0,0 +1,71 @@
+interfaces {
+ ethernet eth0 {
+ duplex auto
+ speed auto
+ address 192.0.2.1/24
+ }
+ loopback lo {
+ }
+}
+protocols {
+ 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/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index a7dd11145..415f3436f 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -652,8 +652,8 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
nftables_search = [
['ct state { established, related }', 'accept'],
['ct state invalid', 'reject'],
- ['ct state new', 'ct status dnat', 'accept'],
- ['ct state { established, new }', 'ct status snat', 'accept'],
+ ['ct state new', 'ct status == dnat', 'accept'],
+ ['ct state { established, new }', 'ct status == snat', 'accept'],
['ct state related', 'ct helper { "ftp", "pptp" }', 'accept'],
['drop', f'comment "{name} default-action drop"'],
['jump VYOS_STATE_POLICY'],
diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py
index db49db98f..1d68ae08b 100755
--- a/smoketest/scripts/cli/test_protocols_bgp.py
+++ b/smoketest/scripts/cli/test_protocols_bgp.py
@@ -60,7 +60,7 @@ neighbor_config = {
'route_map_out' : route_map_out,
'no_send_comm_ext' : '',
'addpath_all' : '',
- 'p_attr_discard' : '123',
+ 'p_attr_discard' : ['10', '20', '30', '40', '50'],
},
'192.0.2.2' : {
'bfd_profile' : bfd_profile,
@@ -137,7 +137,7 @@ peer_group_config = {
'cap_over' : '',
'ttl_security' : '5',
'disable_conn_chk' : '',
- 'p_attr_discard' : '250',
+ 'p_attr_discard' : ['100', '150', '200'],
},
'bar' : {
'remote_as' : '111',
@@ -284,7 +284,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
if 'addpath_all' in peer_config:
self.assertIn(f' neighbor {peer} addpath-tx-all-paths', frrconfig)
if 'p_attr_discard' in peer_config:
- self.assertIn(f' neighbor {peer} path-attribute discard {peer_config["p_attr_discard"]}', frrconfig)
+ tmp = ' '.join(peer_config["p_attr_discard"])
+ self.assertIn(f' neighbor {peer} path-attribute discard {tmp}', frrconfig)
if 'p_attr_taw' in peer_config:
self.assertIn(f' neighbor {peer} path-attribute treat-as-withdraw {peer_config["p_attr_taw"]}', frrconfig)
if 'addpath_per_as' in peer_config:
@@ -460,7 +461,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
if 'update_src' in peer_config:
self.cli_set(base_path + ['neighbor', peer, 'update-source', peer_config["update_src"]])
if 'p_attr_discard' in peer_config:
- self.cli_set(base_path + ['neighbor', peer, 'path-attribute', 'discard', peer_config["p_attr_discard"]])
+ for attribute in peer_config['p_attr_discard']:
+ self.cli_set(base_path + ['neighbor', peer, 'path-attribute', 'discard', attribute])
if 'p_attr_taw' in peer_config:
self.cli_set(base_path + ['neighbor', peer, 'path-attribute', 'treat-as-withdraw', peer_config["p_attr_taw"]])
if 'route_map_in' in peer_config:
@@ -584,7 +586,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
if 'disable_conn_chk' in config:
self.cli_set(base_path + ['peer-group', peer_group, 'disable-connected-check'])
if 'p_attr_discard' in config:
- self.cli_set(base_path + ['peer-group', peer_group, 'path-attribute', 'discard', config["p_attr_discard"]])
+ for attribute in config['p_attr_discard']:
+ self.cli_set(base_path + ['peer-group', peer_group, 'path-attribute', 'discard', attribute])
if 'p_attr_taw' in config:
self.cli_set(base_path + ['peer-group', peer_group, 'path-attribute', 'treat-as-withdraw', config["p_attr_taw"]])
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_protocols_static.py b/smoketest/scripts/cli/test_protocols_static.py
index abf1080ab..c5cf2aab6 100755
--- a/smoketest/scripts/cli/test_protocols_static.py
+++ b/smoketest/scripts/cli/test_protocols_static.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021-2023 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
@@ -55,13 +55,13 @@ routes = {
'blackhole' : { 'distance' : '90' },
},
'100.64.0.0/16' : {
- 'blackhole' : { },
+ 'blackhole' : {},
},
'100.65.0.0/16' : {
'reject' : { 'distance' : '10', 'tag' : '200' },
},
'100.66.0.0/16' : {
- 'blackhole' : { },
+ 'blackhole' : {},
'reject' : { 'distance' : '10', 'tag' : '200' },
},
'2001:db8:100::/40' : {
@@ -88,8 +88,28 @@ routes = {
'2001:db8:300::/40' : {
'reject' : { 'distance' : '250', 'tag' : '500' },
},
+ '2001:db8:400::/40' : {
+ 'next_hop' : {
+ '2001:db8::400' : { 'segments' : '2001:db8:aaaa::400/2002::400/2003::400/2004::400' },
+ },
+ },
+ '2001:db8:500::/40' : {
+ 'next_hop' : {
+ '2001:db8::500' : { 'segments' : '2001:db8:aaaa::500/2002::500/2003::500/2004::500' },
+ },
+ },
+ '2001:db8:600::/40' : {
+ 'interface' : {
+ 'eth0' : { 'segments' : '2001:db8:aaaa::600/2002::600' },
+ },
+ },
+ '2001:db8:700::/40' : {
+ 'interface' : {
+ 'eth1' : { 'segments' : '2001:db8:aaaa::700' },
+ },
+ },
'2001:db8::/32' : {
- 'blackhole' : { 'distance' : '200', 'tag' : '600' },
+ 'blackhole' : { 'distance' : '200', 'tag' : '600' }
},
}
@@ -108,18 +128,14 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
super(TestProtocolsStatic, cls).tearDownClass()
def tearDown(self):
- for route, route_config in routes.items():
- route_type = 'route'
- if is_ipv6(route):
- route_type = 'route6'
- self.cli_delete(base_path + [route_type, route])
-
- for table in tables:
- self.cli_delete(base_path + ['table', table])
-
- tmp = self.getFRRconfig('', end='')
+ self.cli_delete(base_path)
self.cli_commit()
+ v4route = self.getFRRconfig('ip route', end='')
+ self.assertFalse(v4route)
+ v6route = self.getFRRconfig('ipv6 route', end='')
+ self.assertFalse(v6route)
+
def test_01_static(self):
bfd_profile = 'vyos-test'
for route, route_config in routes.items():
@@ -142,7 +158,8 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
self.cli_set(base + ['next-hop', next_hop, 'bfd', 'profile', bfd_profile ])
if 'bfd_source' in next_hop_config:
self.cli_set(base + ['next-hop', next_hop, 'bfd', 'multi-hop', 'source', next_hop_config['bfd_source'], 'profile', bfd_profile])
-
+ if 'segments' in next_hop_config:
+ self.cli_set(base + ['next-hop', next_hop, 'segments', next_hop_config['segments']])
if 'interface' in route_config:
for interface, interface_config in route_config['interface'].items():
@@ -153,6 +170,8 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
self.cli_set(base + ['interface', interface, 'distance', interface_config['distance']])
if 'vrf' in interface_config:
self.cli_set(base + ['interface', interface, 'vrf', interface_config['vrf']])
+ if 'segments' in interface_config:
+ self.cli_set(base + ['interface', interface, 'segments', interface_config['segments']])
if 'blackhole' in route_config:
self.cli_set(base + ['blackhole'])
@@ -200,6 +219,8 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
tmp += ' bfd profile ' + bfd_profile
if 'bfd_source' in next_hop_config:
tmp += ' bfd multi-hop source ' + next_hop_config['bfd_source'] + ' profile ' + bfd_profile
+ if 'segments' in next_hop_config:
+ tmp += ' segments ' + next_hop_config['segments']
if 'disable' in next_hop_config:
self.assertNotIn(tmp, frrconfig)
@@ -215,6 +236,8 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
tmp += ' ' + interface_config['distance']
if 'vrf' in interface_config:
tmp += ' nexthop-vrf ' + interface_config['vrf']
+ if 'segments' in interface_config:
+ tmp += ' segments ' + interface_config['segments']
if 'disable' in interface_config:
self.assertNotIn(tmp, frrconfig)
@@ -369,7 +392,8 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
self.cli_set(route_base_path + ['next-hop', next_hop, 'interface', next_hop_config['interface']])
if 'vrf' in next_hop_config:
self.cli_set(route_base_path + ['next-hop', next_hop, 'vrf', next_hop_config['vrf']])
-
+ if 'segments' in next_hop_config:
+ self.cli_set(route_base_path + ['next-hop', next_hop, 'segments', next_hop_config['segments']])
if 'interface' in route_config:
for interface, interface_config in route_config['interface'].items():
@@ -380,6 +404,8 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
self.cli_set(route_base_path + ['interface', interface, 'distance', interface_config['distance']])
if 'vrf' in interface_config:
self.cli_set(route_base_path + ['interface', interface, 'vrf', interface_config['vrf']])
+ if 'segments' in interface_config:
+ self.cli_set(route_base_path + ['interface', interface, 'segments', interface_config['segments']])
if 'blackhole' in route_config:
self.cli_set(route_base_path + ['blackhole'])
@@ -417,6 +443,8 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
tmp += ' ' + next_hop_config['distance']
if 'vrf' in next_hop_config:
tmp += ' nexthop-vrf ' + next_hop_config['vrf']
+ if 'segments' in next_hop_config:
+ tmp += ' segments ' + next_hop_config['segments']
if 'disable' in next_hop_config:
self.assertNotIn(tmp, frrconfig)
@@ -432,6 +460,8 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
tmp += ' ' + interface_config['distance']
if 'vrf' in interface_config:
tmp += ' nexthop-vrf ' + interface_config['vrf']
+ if 'segments' in interface_config:
+ tmp += ' segments ' + interface_config['segments']
if 'disable' in interface_config:
self.assertNotIn(tmp, frrconfig)
diff --git a/smoketest/scripts/cli/test_service_dhcpv6-server.py b/smoketest/scripts/cli/test_service_dhcpv6-server.py
index 5a831b8a0..6ecf6c1cf 100755
--- a/smoketest/scripts/cli/test_service_dhcpv6-server.py
+++ b/smoketest/scripts/cli/test_service_dhcpv6-server.py
@@ -102,6 +102,7 @@ class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase):
pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]
self.cli_set(base_path + ['preference', preference])
+ self.cli_set(pool + ['interface', interface])
self.cli_set(pool + ['subnet-id', '1'])
# we use the first subnet IP address as default gateway
self.cli_set(pool + ['lease-time', 'default', lease_time])
@@ -146,6 +147,7 @@ class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase):
self.verify_config_value(obj, ['Dhcp6', 'shared-networks'], 'name', shared_net_name)
self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'subnet', subnet)
+ self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'interface', interface)
self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'id', 1)
self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'valid-lifetime', int(lease_time))
self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'min-valid-lifetime', int(min_lease_time))
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_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/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/dhcpv6-server/4-to-5 b/src/migration-scripts/dhcpv6-server/4-to-5
new file mode 100755
index 000000000..e808edbe0
--- /dev/null
+++ b/src/migration-scripts/dhcpv6-server/4-to-5
@@ -0,0 +1,68 @@
+#!/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/>.
+
+# T5993: Check if subnet is locally accessible and assign interface to subnet
+
+import sys
+from ipaddress import ip_network
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['service', 'dhcpv6-server', 'shared-network-name']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+def find_subnet_interface(subnet):
+ subnet_net = ip_network(subnet)
+
+ for iftype in config.list_nodes(['interfaces']):
+ for ifname in config.list_nodes(['interfaces', iftype]):
+ if_base = ['interfaces', iftype, ifname]
+
+ if config.exists(if_base + ['address']):
+ for addr in config.return_values(if_base + ['address']):
+ if ip_network(addr, strict=False) == subnet_net:
+ return ifname
+
+ return False
+
+for network in config.list_nodes(base):
+ if not config.exists(base + [network, 'subnet']):
+ continue
+
+ for subnet in config.list_nodes(base + [network, 'subnet']):
+ subnet_interface = find_subnet_interface(subnet)
+
+ if subnet_interface:
+ config.set(base + [network, 'subnet', subnet, 'interface'], value=subnet_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/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/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/validators/ipv6-srv6-segments b/src/validators/ipv6-srv6-segments
new file mode 100755
index 000000000..e72a4f90f
--- /dev/null
+++ b/src/validators/ipv6-srv6-segments
@@ -0,0 +1,13 @@
+#!/bin/sh
+segments="$1"
+export IFS="/"
+
+for ipv6addr in $segments; do
+ ipaddrcheck --is-ipv6-single $ipv6addr
+ if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IPv6 address"
+ exit 1
+ fi
+done
+exit 0
+