Skip to content

Commit 31f8bd8

Browse files
authored
Merge pull request #4734 from alexandr-san4ez/T4251-current
syslog: T4251: Add TLS support to syslog
2 parents ebf27d5 + 68f2cec commit 31f8bd8

File tree

5 files changed

+348
-3
lines changed

5 files changed

+348
-3
lines changed

data/templates/rsyslog/rsyslog.conf.j2

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ if prifilt("{{ tmp | join(',') }}") then {
9292
{% set _ = tmp.append(facility.replace('all', '*') ~ "." ~ facility_options.level.replace('all', 'debug')) %}
9393
{% endfor %}
9494
{% set _ = tmp.sort() %}
95+
{% set tls = remote_options.tls %}
9596
# Remote syslog to {{ remote_name }}
9697
if prifilt("{{ tmp | join(',') }}") then {
9798
action(
@@ -100,7 +101,7 @@ if prifilt("{{ tmp | join(',') }}") then {
100101
target="{{ remote_name }}"
101102
# Port on the remote syslog server
102103
port="{{ remote_options.port }}"
103-
protocol="{{ remote_options.protocol }}"
104+
protocol="{{ 'tcp' if tls.enable is vyos_defined else remote_options.protocol }}"
104105
{% if remote_options.format.include_timezone is vyos_defined %}
105106
template="RSYSLOG_SyslogProtocol23Format"
106107
{% endif %}
@@ -110,6 +111,31 @@ if prifilt("{{ tmp | join(',') }}") then {
110111
{% endif %}
111112
{% if remote_options.vrf is vyos_defined %}
112113
Device="{{ remote_options.vrf }}"
114+
{% endif %}
115+
{% if tls.enable is vyos_defined %}
116+
{% set auth_mode = tls.auth_mode %}
117+
# Specify the use of the OpenSSL TLS driver for this action
118+
StreamDriver="ossl"
119+
# Set mode to TLS-only connections (do not accept plain TCP)
120+
StreamDriverMode="1"
121+
# Select the authentication mode
122+
StreamDriverAuthMode="{{ auth_mode if auth_mode == 'anon' else 'x509/' + auth_mode }}"
123+
{% if tls.permitted_peers is vyos_defined and auth_mode in ('fingerprint', 'name') %}
124+
# Only include permitted peers (list of allowed fingerprints or names)
125+
StreamDriverPermittedPeers="{{ tls.permitted_peers }}"
126+
{% endif %}
127+
{% if tls.ca_certificate_path is vyos_defined %}
128+
# Include the path to the CA certificate file
129+
StreamDriver.CAFile="{{ tls.ca_certificate_path }}"
130+
{% endif %}
131+
{% if tls.certificate_path is vyos_defined %}
132+
# Include the path to the client's certificate
133+
StreamDriver.CertFile="{{ tls.certificate_path }}"
134+
{% endif %}
135+
{% if tls.certificate_key_path is vyos_defined %}
136+
# Include the path to the client's private key
137+
StreamDriver.KeyFile="{{ tls.certificate_key_path }}"
138+
{% endif %}
113139
{% endif %}
114140
)
115141
}

debian/control

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ Depends:
326326
# End "vpn openconnect"
327327
# For "system syslog"
328328
rsyslog,
329+
rsyslog-openssl,
329330
# End "system syslog"
330331
# For "system option keyboard-layout"
331332
kbd,

interface-definitions/system_syslog.xml.in

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,60 @@
6565
#include <include/protocol-tcp-udp.xml.i>
6666
#include <include/source-address-ipv4-ipv6.xml.i>
6767
#include <include/interface/vrf.xml.i>
68+
<node name="tls">
69+
<properties>
70+
<help>Transport Layer Security (TLS) options for secure syslog</help>
71+
</properties>
72+
<children>
73+
<leafNode name="enable">
74+
<properties>
75+
<help>Enable TLS encryption for log transmission to this remote syslog server</help>
76+
<valueless/>
77+
</properties>
78+
</leafNode>
79+
<!-- CA cert help should describe trust anchor for server/client validation -->
80+
#include <include/pki/ca-certificate.xml.i>
81+
<!-- Certificate help should specify identity for mutual authentication -->
82+
#include <include/pki/certificate.xml.i>
83+
<leafNode name="auth-mode">
84+
<properties>
85+
<help>Specify the authentication and verification method for the remote peer's certificate during the TLS handshake</help>
86+
<completionHelp>
87+
<list>anon fingerprint certvalid name</list>
88+
</completionHelp>
89+
<valueHelp>
90+
<format>anon</format>
91+
<description>Allow encrypted connection without verifying the peer's identity (anonymous TLS)</description>
92+
</valueHelp>
93+
<valueHelp>
94+
<format>fingerprint</format>
95+
<description>Authenticate peer by matching its certificate fingerprint to a configured, permitted list (`permitted-peers` option)</description>
96+
</valueHelp>
97+
<valueHelp>
98+
<format>certvalid</format>
99+
<description>Authenticate peer if it presents a certificate signed by a trusted CA</description>
100+
</valueHelp>
101+
<valueHelp>
102+
<format>name</format>
103+
<description>Authenticate peer by verifying its certificate subject name against a configured value (`permitted-peers` option)</description>
104+
</valueHelp>
105+
<constraint>
106+
<regex>(anon|fingerprint|certvalid|name)</regex>
107+
</constraint>
108+
</properties>
109+
<defaultValue>anon</defaultValue>
110+
</leafNode>
111+
<leafNode name="permitted-peers">
112+
<properties>
113+
<help>Comma-separated list of allowed peer certificate fingerprints or subject names</help>
114+
<valueHelp>
115+
<format>txt</format>
116+
<description>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'</description>
117+
</valueHelp>
118+
</properties>
119+
</leafNode>
120+
</children>
121+
</node>
68122
</children>
69123
</tagNode>
70124
<node name="local">

smoketest/scripts/cli/test_system_syslog.py

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,43 @@
2727

2828
PROCESS_NAME = 'rsyslogd'
2929
RSYSLOG_CONF = '/run/rsyslog/rsyslog.conf'
30+
CERT_DIR = '/etc/rsyslog.d/certs'
3031

3132
base_path = ['system', 'syslog']
33+
pki_base = ['pki']
3234

3335
dummy_interface = 'dum372874'
3436

37+
ca_cert_name = "syslog_ca_certificate"
38+
ca_cert = """
39+
MIIBrTCCAV+gAwIBAgIUdTEOleLyGTteZC+yEi252lRUq8EwBQYDK2VwMEsxCzAJ
40+
BgNVBAYTAlVTMQ4wDAYDVQQIDAVTdGF0ZTENMAsGA1UEBwwEQ2l0eTEMMAoGA1UE
41+
CgwDT3JnMQ8wDQYDVQQDDAZSb290Q0EwIBcNMjUwOTE1MTQxNDI4WhgPMjEyNTA4
42+
MjIxNDE0MjhaMEsxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVTdGF0ZTENMAsGA1UE
43+
BwwEQ2l0eTEMMAoGA1UECgwDT3JnMQ8wDQYDVQQDDAZSb290Q0EwKjAFBgMrZXAD
44+
IQCtTlgU+aqU/i6k6b318vebALk0zs9RvE96vw7taIt2iqNTMFEwHQYDVR0OBBYE
45+
FHl8GywRMCWSotNGmyjuvRbPqCq8MB8GA1UdIwQYMBaAFHl8GywRMCWSotNGmyju
46+
vRbPqCq8MA8GA1UdEwEB/wQFMAMBAf8wBQYDK2VwA0EAouZ4s+/ZeZxZxOZ7yFG0
47+
RQ9BfPWySrX4kgavyJJeg8LNCYUIRIP6iC41MTyHUVsWwar91xBT0DKBkpwrOQ0n
48+
Dg==
49+
"""
50+
51+
client_cert_name = "syslog_client_certificate"
52+
client_cert = """
53+
MIIBVjCCAQgCFArrkIM+zg8luHbXwsS8cUB5xrh/MAUGAytlcDBLMQswCQYDVQQG
54+
EwJVUzEOMAwGA1UECAwFU3RhdGUxDTALBgNVBAcMBENpdHkxDDAKBgNVBAoMA09y
55+
ZzEPMA0GA1UEAwwGUm9vdENBMB4XDTI1MDkxNTE0MTUwN1oXDTM1MDkxMzE0MTUw
56+
N1owUDELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5
57+
MQwwCgYDVQQKDANPcmcxFDASBgNVBAMMC2V4YW1wbGUuY29tMCowBQYDK2VwAyEA
58+
eZZRz7yVQ+exm6vyh/GdGZrTSEmtbvfafG0digqpfnUwBQYDK2VwA0EAU8/kw1i0
59+
s4j2fPQmU1q6Qql3xaxUlDyzhRPSIeH7ZhOlNg8R7gR1QnA7Rel6oU4EqJJHvz9l
60+
83HQAy7ZcNIoBw==
61+
"""
62+
63+
client_cert_key = """
64+
MC4CAQAwBQYDK2VwBCIEIG59XPVZoMCxBVD/eJVqJSmV+Uc0bUHjHS4bkfkjM6Jj
65+
"""
66+
3567
def get_config(string=''):
3668
"""
3769
Retrieve current "running configuration" from FRR
@@ -51,11 +83,15 @@ def setUpClass(cls):
5183
# out the current configuration :)
5284
cls.cli_delete(cls, base_path)
5385
cls.cli_delete(cls, ['vrf'])
86+
cls.cli_delete(cls, pki_base)
5487

5588
def tearDown(self):
5689
# Check for running process
5790
self.assertTrue(process_named_running(PROCESS_NAME))
5891

92+
# delete test certificates for syslog
93+
self.cli_delete(pki_base)
94+
5995
# delete testing SYSLOG config
6096
self.cli_delete(base_path)
6197
self.cli_commit()
@@ -68,6 +104,30 @@ def tearDown(self):
68104
# Check for running process
69105
self.assertFalse(process_named_running(PROCESS_NAME))
70106

107+
def _set_tls_certificates(self):
108+
self.cli_set(
109+
pki_base + ['ca', ca_cert_name, 'certificate', ca_cert.replace('\n', '')]
110+
)
111+
self.cli_set(
112+
pki_base
113+
+ [
114+
'certificate',
115+
client_cert_name,
116+
'certificate',
117+
client_cert.replace('\n', ''),
118+
]
119+
)
120+
self.cli_set(
121+
pki_base
122+
+ [
123+
'certificate',
124+
client_cert_name,
125+
'private',
126+
'key',
127+
client_cert_key.replace('\n', ''),
128+
]
129+
)
130+
71131
def test_console(self):
72132
level = 'warning'
73133
self.cli_set(base_path + ['console', 'facility', 'all', 'level'], value=level)
@@ -239,6 +299,119 @@ def test_remote(self):
239299
# cleanup dummy interface
240300
self.cli_delete(dummy_if_path)
241301

302+
def test_remote_tls(self):
303+
self._set_tls_certificates()
304+
305+
rhosts = {
306+
'172.10.0.2': {
307+
'facility': {'all': {'level': 'debug'}},
308+
'port': '6514',
309+
'protocol': 'udp',
310+
'tls': {
311+
'enable': True,
312+
'auth-mode': 'anon',
313+
},
314+
},
315+
'172.10.0.3': {
316+
'facility': {'all': {'level': 'debug'}},
317+
'port': '6514',
318+
'protocol': 'tcp',
319+
'tls': {
320+
'enable': True,
321+
'ca-certificate': ca_cert_name,
322+
'auth-mode': 'certvalid',
323+
},
324+
},
325+
'172.10.0.4': {
326+
'facility': {'all': {'level': 'debug'}},
327+
'port': '6514',
328+
'protocol': 'tcp',
329+
'tls': {
330+
'enable': True,
331+
'ca-certificate': ca_cert_name,
332+
'certificate': client_cert_name,
333+
'auth-mode': 'fingerprint',
334+
'permitted-peers': 'SHA1:E1:DB:C4:FF:83:54:85:40:2D:56:E7:1A:C3:FF:70:22:0F:21:74:ED',
335+
},
336+
},
337+
'172.10.0.5': {
338+
'facility': {'all': {'level': 'debug'}},
339+
'port': '6514',
340+
'protocol': 'tcp',
341+
'tls': {
342+
'enable': True,
343+
'ca-certificate': ca_cert_name,
344+
'certificate': client_cert_name,
345+
'auth-mode': 'name',
346+
'permitted-peers': 'logs.example.com',
347+
},
348+
},
349+
}
350+
351+
for remote, remote_options in rhosts.items():
352+
remote_base = base_path + ['remote', remote]
353+
354+
if 'port' in remote_options:
355+
self.cli_set(remote_base + ['port'], value=remote_options['port'])
356+
357+
if 'facility' in remote_options:
358+
for facility, facility_options in remote_options['facility'].items():
359+
level = facility_options['level']
360+
self.cli_set(
361+
remote_base + ['facility', facility, 'level'], value=level
362+
)
363+
364+
if 'protocol' in remote_options:
365+
protocol = remote_options['protocol']
366+
self.cli_set(remote_base + ['protocol'], value=protocol)
367+
368+
tls = remote_options['tls']
369+
for key, value in tls.items():
370+
if key == 'enable':
371+
self.cli_set(remote_base + ['tls', 'enable'])
372+
else:
373+
self.cli_set(remote_base + ['tls', key], value=value)
374+
375+
self.cli_commit()
376+
377+
read_file(RSYSLOG_CONF)
378+
for remote, remote_options in rhosts.items():
379+
with self.subTest(remote=remote):
380+
config = get_config(f'# Remote syslog to {remote}')
381+
382+
if 'port' in remote_options:
383+
port = remote_options['port']
384+
self.assertIn(f'port="{port}"', config)
385+
386+
self.assertIn('protocol="tcp"', config)
387+
self.assertIn('StreamDriver="ossl"', config)
388+
self.assertIn('StreamDriverMode="1"', config)
389+
390+
tls = remote_options['tls']
391+
if 'ca-certificate' in tls:
392+
self.assertIn(
393+
f'StreamDriver.CAFile="{CERT_DIR}/{ca_cert_name}.pem"', config
394+
)
395+
396+
if 'certificate' in tls:
397+
self.assertIn(
398+
f'StreamDriver.CertFile="{CERT_DIR}/{client_cert_name}.pem"',
399+
config,
400+
)
401+
self.assertIn(
402+
f'StreamDriver.KeyFile="{CERT_DIR}/{client_cert_name}.key"',
403+
config,
404+
)
405+
406+
if 'auth-mode' in tls:
407+
value = tls['auth-mode']
408+
auth_mode = value if value == 'anon' else f'x509/{value}'
409+
self.assertIn(f'StreamDriverAuthMode="{auth_mode}"', config)
410+
411+
if 'permitted-peers' in tls:
412+
value = tls['permitted-peers']
413+
self.assertIn(f'StreamDriverPermittedPeers="{value}"', config)
414+
242415
def test_vrf_source_address(self):
243416
rhosts = {
244417
'169.254.0.10': { },

0 commit comments

Comments
 (0)