diff --git a/data/templates/rsyslog/rsyslog.conf.j2 b/data/templates/rsyslog/rsyslog.conf.j2 index 9b9153010f..714788df0d 100644 --- a/data/templates/rsyslog/rsyslog.conf.j2 +++ b/data/templates/rsyslog/rsyslog.conf.j2 @@ -120,9 +120,12 @@ if prifilt("{{ tmp | join(',') }}") then { StreamDriverMode="1" # Select the authentication mode StreamDriverAuthMode="{{ auth_mode if auth_mode == 'anon' else 'x509/' + auth_mode }}" -{% if tls.permitted_peers is vyos_defined and auth_mode in ('fingerprint', 'name') %} +{% if tls.permitted_peer is vyos_defined and auth_mode in ('fingerprint', 'name') %} +{% set permitted_peers = tls.permitted_peer | map('trim') | select | join(',') %} +{% if permitted_peers %} # Only include permitted peers (list of allowed fingerprints or names) - StreamDriverPermittedPeers="{{ tls.permitted_peers }}" + StreamDriverPermittedPeers="{{ permitted_peers }}" +{% endif %} {% endif %} {% if tls.ca_certificate_path is vyos_defined %} # Include the path to the CA certificate file diff --git a/interface-definitions/include/version/system-version.xml.i b/interface-definitions/include/version/system-version.xml.i index 5cdece74a9..a9987e8e44 100644 --- a/interface-definitions/include/version/system-version.xml.i +++ b/interface-definitions/include/version/system-version.xml.i @@ -1,3 +1,3 @@ - + diff --git a/interface-definitions/system_syslog.xml.in b/interface-definitions/system_syslog.xml.in index 4acbc729d4..221b4320fc 100644 --- a/interface-definitions/system_syslog.xml.in +++ b/interface-definitions/system_syslog.xml.in @@ -86,7 +86,7 @@ fingerprint - Authenticate peer by matching its certificate fingerprint to a configured, permitted list (`permitted-peers` option) + Authenticate peer by matching its certificate fingerprint to a configured, permitted list (`permitted-peer` option) certvalid @@ -94,7 +94,7 @@ name - Authenticate peer by verifying its certificate subject name against a configured value (`permitted-peers` option) + Authenticate peer by verifying its certificate subject name against a configured value (`permitted-peer` option) (anon|fingerprint|certvalid|name) @@ -102,13 +102,14 @@ anon - + - Comma-separated list of allowed peer certificate fingerprints or subject names + Allowed peer certificate fingerprint or subject name txt - Comma-separated fingerprints or peer names.\nFor example:\n - 'SHA1:DD:23:E3:E7:70:F5:B4:13:44:16:78:A5:5A:8C:39:48:53:A6:DD:25,SHA256:10:C4:26:1D:CB:3C:AB:12:DB:1A:F0:47:37:AE:6D:D2:DE:66:B5:71:B7:2E:5B:BB:AE:0C:7E:7F:5F:0D:E9:64'\n - 'logs.example.com' + Peer fingerprint - SHA1:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX or subject name - logs.example.com + diff --git a/smoketest/scripts/cli/test_system_syslog.py b/smoketest/scripts/cli/test_system_syslog.py index b04c3ea58b..79fbe27ecc 100755 --- a/smoketest/scripts/cli/test_system_syslog.py +++ b/smoketest/scripts/cli/test_system_syslog.py @@ -308,13 +308,13 @@ def test_remote_tls(self): '172.10.0.1': { 'facility': {'all': {'level': 'debug'}}, 'port': '6514', - 'protocol': 'udp', + 'protocol': 'tcp', 'tls': {}, }, '172.10.0.2': { 'facility': {'all': {'level': 'debug'}}, 'port': '6514', - 'protocol': 'udp', + 'protocol': 'tcp', 'tls': { 'auth-mode': 'anon', }, @@ -336,7 +336,10 @@ def test_remote_tls(self): 'ca-certificate': ca_cert_name, 'certificate': client_cert_name, 'auth-mode': 'fingerprint', - 'permitted-peers': 'SHA1:E1:DB:C4:FF:83:54:85:40:2D:56:E7:1A:C3:FF:70:22:0F:21:74:ED', + 'permitted-peer': [ + 'SHA1:E1:DB:C4:FF:83:54:85:40:2D:56:E7:1A:C3:FF:70:22:0F:21:74:ED', + ' SHA1:FF:70:22:0F:21:74:ED:54:85:40:2D:56:E7:1A:C3:E1:DB:C4:FF:83 ', + ], }, }, '172.10.0.5': { @@ -347,7 +350,10 @@ def test_remote_tls(self): 'ca-certificate': ca_cert_name, 'certificate': client_cert_name, 'auth-mode': 'name', - 'permitted-peers': 'logs.example.com', + 'permitted-peer': [ + 'logs.example.com', + ' ', + ], }, }, } @@ -372,7 +378,12 @@ def test_remote_tls(self): tls = remote_options['tls'] if tls: for key, value in tls.items(): - self.cli_set(remote_base + ['tls', key], value=value) + if type(value) is list: + values = value + for value in values: + self.cli_set(remote_base + ['tls', key], value=value) + else: + self.cli_set(remote_base + ['tls', key], value=value) else: self.cli_set(remote_base + ['tls']) @@ -412,13 +423,28 @@ def test_remote_tls(self): auth_mode = value if value == 'anon' else f'x509/{value}' self.assertIn(f'StreamDriverAuthMode="{auth_mode}"', config) - if 'permitted-peers' in tls: - value = tls['permitted-peers'] + if 'permitted-peer' in tls: + values = tls['permitted-peer'] + value = ','.join([v.strip() for v in values if v.strip()]) self.assertIn(f'StreamDriverPermittedPeers="{value}"', config) if not tls: self.assertIn(f'StreamDriverAuthMode="anon"', config) + def test_remote_tls_protocol_udp(self): + remote_base = base_path + ['remote', '172.11.0.1'] + self.cli_set(remote_base + ['port'], value='6514') + self.cli_set(remote_base + ['facility', 'all', 'level'], value='debug') + self.cli_set(remote_base + ['protocol'], value='udp') + self.cli_set(remote_base + ['tls']) + + err_msg = "TLS is enabled for remote \"172.11.0.1\", but protocol is set to UDP" + with self.assertRaisesRegex(ConfigSessionError, err_msg): + self.cli_commit() + + self.cli_set(base_path + ['remote', '172.11.0.1', 'protocol'], value='tcp') + self.cli_commit() + def test_vrf_source_address(self): rhosts = { '169.254.0.10': { }, diff --git a/src/conf_mode/system_syslog.py b/src/conf_mode/system_syslog.py index 76999f311a..90bb45c513 100755 --- a/src/conf_mode/system_syslog.py +++ b/src/conf_mode/system_syslog.py @@ -71,15 +71,15 @@ def _verify_tls_remote_options(remote, remote_options, syslog): if ca_certificate: verify_pki_ca_certificate(syslog, ca_certificate) - permitted_peers = dict_search('tls.permitted_peers', remote_options) + permitted_peers = dict_search('tls.permitted_peer', remote_options) if not permitted_peers: if auth_mode == "fingerprint": raise ConfigError( - f'Auth mode "fingerprint" for remote "{remote}" requires "permitted-peers" to be configured!' + f'Auth mode "fingerprint" for remote "{remote}" requires "permitted-peer" to be configured!' ) elif auth_mode == "name": raise ConfigError( - f'Auth mode "name" for remote "{remote}" requires "permitted-peers" to specify allowed subject names!' + f'Auth mode "name" for remote "{remote}" requires "permitted-peer" to specify allowed subject names!' ) @@ -181,7 +181,7 @@ def verify(syslog): _verify_tls_remote_options(remote, remote_options, syslog) if 'protocol' in remote_options and remote_options['protocol'] == 'udp': - Warning( + raise ConfigError( f'TLS is enabled for remote "{remote}", but protocol is set to UDP. TLS is only supported with protocol TCP!' ) diff --git a/src/migration-scripts/system/29-to-30 b/src/migration-scripts/system/29-to-30 new file mode 100644 index 0000000000..7babde3f32 --- /dev/null +++ b/src/migration-scripts/system/29-to-30 @@ -0,0 +1,52 @@ +# Copyright VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . + +# T4251: +# - drop "tls enable" node (make "tls" a standalone key) +# - split "tls permitted-peers" list by commas into multiple "tls permitted-peer" entries + +from vyos.configtree import ConfigTree + +base = ['system', 'syslog', 'remote'] + + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + # Iterate over all remote syslog server entries (like 172.18.0.5) + for remote_addr in config.list_nodes(base): + remote_base = base + [remote_addr] + tls_base = remote_base + ['tls'] + + # (1) Remove "tls enable" -> migrate to simple "tls" + enable_path = tls_base + ['enable'] + if config.exists(enable_path): + # Remove obsolete "enable" node + config.delete(enable_path) + + # (2) Split "tls permitted-peers" (comma-separated string) + permitted_peers_path = tls_base + ['permitted-peers'] + if config.exists(permitted_peers_path): + peers_str = config.return_value(permitted_peers_path) + # Split CSV values and normalize whitespace + peers = [p.strip() for p in peers_str.split(',') if p.strip()] + + # Create a new "permitted-peer" entry per item + for peer in peers: + config.set(tls_base + ['permitted-peer'], value=peer, replace=False) + + # Remove the old combined node + config.delete(permitted_peers_path)