diff options
-rw-r--r-- | data/templates/rsyslog/override.conf.j2 | 11 | ||||
-rw-r--r-- | data/templates/rsyslog/rsyslog.conf.j2 | 125 | ||||
-rw-r--r-- | debian/vyos-1x.install | 1 | ||||
-rw-r--r-- | interface-definitions/system_syslog.xml.in | 17 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_system_syslog.py | 151 | ||||
-rwxr-xr-x | src/conf_mode/system_option.py | 2 | ||||
-rwxr-xr-x | src/conf_mode/system_syslog.py | 14 | ||||
-rw-r--r-- | src/etc/rsyslog.conf | 67 | ||||
-rw-r--r-- | src/etc/systemd/system/rsyslog.service.d/override.conf | 10 | ||||
-rw-r--r-- | src/migration-scripts/system/28-to-29 | 7 |
10 files changed, 235 insertions, 170 deletions
diff --git a/data/templates/rsyslog/override.conf.j2 b/data/templates/rsyslog/override.conf.j2 deleted file mode 100644 index 5f6a87edf..000000000 --- a/data/templates/rsyslog/override.conf.j2 +++ /dev/null @@ -1,11 +0,0 @@ -{% set vrf_command = 'ip vrf exec ' ~ vrf ~ ' ' if vrf is vyos_defined else '' %} -[Unit] -StartLimitIntervalSec=0 - -[Service] -ExecStart= -ExecStart={{ vrf_command }}/usr/sbin/rsyslogd -n -iNONE -Restart=always -RestartPreventExitStatus= -RestartSec=10 -RuntimeDirectoryPreserve=yes diff --git a/data/templates/rsyslog/rsyslog.conf.j2 b/data/templates/rsyslog/rsyslog.conf.j2 index 2e3d19afd..efb23ca7d 100644 --- a/data/templates/rsyslog/rsyslog.conf.j2 +++ b/data/templates/rsyslog/rsyslog.conf.j2 @@ -1,51 +1,126 @@ ### Autogenerated by system_syslog.py ### +#### MODULES #### +# Load input modules for local logging and kernel logging + +# Old-style log file format with low-precision timestamps +# A modern-style logfile format with high-precision timestamps and timezone info +# RSYSLOG_FileFormat +module(load="builtin:omfile" Template="RSYSLOG_TraditionalFileFormat") +module(load="imuxsock") # provides support for local system logging +module(load="imklog") # provides kernel logging support + +# Import logs from journald +module( + load="imjournal" + StateFile="/var/spool/rsyslog/imjournal.state" # Persistent state file to track the journal cursor + Ratelimit.Interval="0" # Disable rate limiting (set to "0" for no limit) + RateLimit.Burst="0" +) + +#### GLOBAL DIRECTIVES #### +### TODO - remove +# Filter duplicated messages +# https://www.rsyslog.com/doc/configuration/action/rsconf1_repeatedmsgreduction.html +$RepeatedMsgReduction on + +########################################## +#### AUTH.LOG CHANNEL AND LOGGING RULES #### +########################################## + +# Log specific programs to auth.log, then stop further processing +if ( + $programname == "CRON" or + $programname == "sudo" or + $programname == "su" +) then { + action(type="omfile" file="/var/log/auth.log") + stop +} + +global(workDirectory="/var/spool/rsyslog") + +############### +#### RULES #### +############### + +# Send emergency messages to all logged-in users +*.emerg action(type="omusrmsg" users="*") + {% if global.marker is vyos_defined %} -$ModLoad immark -{% if global.marker.interval is vyos_defined %} -$MarkMessagePeriod {{ global.marker.interval }} -{% endif %} +# Load the immark module for periodic --MARK-- message capability +module(load="immark" interval="{{ global.marker.interval }}") {% endif %} {% if global.preserve_fqdn is vyos_defined %} -$PreserveFQDN on +# Preserve the fully qualified domain name (FQDN) in log messages +global(preserveFQDN="on") {% endif %} - {% if global.local_host_name is vyos_defined %} -$LocalHostName {{ global.local_host_name }} +# Set the local hostname for log messages +global(localHostname="{{ global.local_host_name }}") {% endif %} -# We always log to /var/log/messages -$outchannel global,/var/log/messages,262144,/usr/sbin/logrotate {{ logrotate }} +#### GLOBAL LOGGING #### {% if global.facility is vyos_defined %} {% set tmp = [] %} -{% for facility, facility_options in global.facility.items() %} -{% set _ = tmp.append(facility.replace('all', '*') + '.' + facility_options.level.replace('all', '*')) %} -{% endfor %} -{{ tmp | join(';') }} :omfile:$global +{% if global.facility is vyos_defined %} +{% for facility, facility_options in global.facility.items() %} +{% set _ = tmp.append(facility.replace('all', '*') ~ "." ~ facility_options.level.replace('all', 'debug')) %} +{% endfor %} +if prifilt("{{ tmp | join(',') }}") then { + action( + type="omfile" + file="/var/log/messages" + queue.size="262144" + rotation.sizeLimitCommand="/usr/sbin/logrotate {{ logrotate }}" + ) +} +{% endif %} {% endif %} +#### CONSOLE LOGGING #### {% if console.facility is vyos_defined %} -# Console logging {% set tmp = [] %} -{% for facility, facility_options in console.facility.items() %} -{% set _ = tmp.append(facility.replace('all', '*') + '.' + facility_options.level.replace('all', '*')) %} -{% endfor %} -{{ tmp | join(';') }} /dev/console +{% if console.facility is vyos_defined %} +{% for facility, facility_options in console.facility.items() %} +{% set _ = tmp.append(facility.replace('all', '*') ~ "." ~ facility_options.level.replace('all', 'debug')) %} +{% endfor %} +if prifilt("{{ tmp | join(',') }}") then { + action(type="omfile" file="/dev/console") +} +{% endif %} {% endif %} +#### REMOTE LOGGING #### {% if remote is vyos_defined %} -# Remote logging {% for remote_name, remote_options in remote.items() %} {% set tmp = [] %} {% if remote_options.facility is vyos_defined %} {% for facility, facility_options in remote_options.facility.items() %} -{% set _ = tmp.append(facility.replace('all', '*') + '.' + facility_options.level.replace('all', '*')) %} +{% set _ = tmp.append(facility.replace('all', '*') ~ "." ~ facility_options.level.replace('all', 'debug')) %} {% endfor %} -{% endif %} -{% if remote_options.protocol is vyos_defined('tcp') %} -{{ tmp | join(';') }} @@{{ '(o)' if remote_options.format.octet_counted is vyos_defined }}{{ remote_name | bracketize_ipv6 }}:{{ remote_options.port }}{{ ';RSYSLOG_SyslogProtocol23Format' if remote_options.format.include_timezone is vyos_defined }} -{% else %} -{{ tmp | join(';') }} @{{ remote_name | bracketize_ipv6 }}:{{ remote_options.port }}{{ ';RSYSLOG_SyslogProtocol23Format' if remote_options.format.include_timezone is vyos_defined }} +{% set _ = tmp.sort() %} +# Remote syslog to {{ remote_name }} +if prifilt("{{ tmp | join(',') }}") then { + action( + type="omfwd" + # Remote syslog server where we send our logs to + target="{{ remote_name | bracketize_ipv6 }}" + # Port on the remote syslog server + port="{{ remote_options.port }}" + protocol="{{ remote_options.protocol }}" +{% if remote_options.format.include_timezone is vyos_defined %} + template="SyslogProtocol23Format" +{% endif %} + TCP_Framing="{{ 'octed-counted' if remote_options.format.octet_counted is vyos_defined else 'traditional' }}" +{% if vrf is vyos_defined %} + Device="{{ vrf }}" +{% endif %} + ) +} {% endif %} {% endfor %} {% endif %} + +# Include all configuration files in /etc/rsyslog.d/ +include(file="/etc/rsyslog.d/*.conf") diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install index 5fcff959a..4e312a648 100644 --- a/debian/vyos-1x.install +++ b/debian/vyos-1x.install @@ -9,7 +9,6 @@ etc/netplug etc/opennhrp etc/modprobe.d etc/ppp -etc/rsyslog.conf etc/securetty etc/security etc/skel diff --git a/interface-definitions/system_syslog.xml.in b/interface-definitions/system_syslog.xml.in index 68cb11423..acc7b4747 100644 --- a/interface-definitions/system_syslog.xml.in +++ b/interface-definitions/system_syslog.xml.in @@ -10,7 +10,7 @@ <children> <tagNode name="remote"> <properties> - <help>Logging to remote host</help> + <help>Log to remote host</help> <constraint> <validator name="ip-address"/> <validator name="fqdn"/> @@ -59,21 +59,26 @@ </tagNode> <node name="global"> <properties> - <help>Logging to system standard location</help> + <help>Log to standard system location /var/log/messages</help> </properties> <children> #include <include/syslog-facility.xml.i> <node name="marker"> <properties> - <help>mark messages sent to syslog</help> + <help>Mark messages sent to syslog</help> </properties> <children> <leafNode name="interval"> <properties> - <help>time interval how often a mark message is being sent in seconds</help> + <help>Mark message interval</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Time in seconds</description> + </valueHelp> <constraint> - <validator name="numeric" argument="--positive"/> + <validator name="numeric" argument="--range 1-86400"/> </constraint> + <constraintErrorMessage>Port number must be in range 1 to 86400</constraintErrorMessage> </properties> <defaultValue>1200</defaultValue> </leafNode> @@ -89,7 +94,7 @@ </node> <node name="console"> <properties> - <help>logging to serial console</help> + <help>Log to system console (/dev/console)</help> </properties> <children> #include <include/syslog-facility.xml.i> diff --git a/smoketest/scripts/cli/test_system_syslog.py b/smoketest/scripts/cli/test_system_syslog.py index 961b7a6f4..de2e9b260 100755 --- a/smoketest/scripts/cli/test_system_syslog.py +++ b/smoketest/scripts/cli/test_system_syslog.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2024 VyOS maintainers and contributors +# Copyright (C) 2019-2025 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,18 +20,24 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.utils.file import read_file +from vyos.utils.process import cmd from vyos.utils.process import process_named_running from vyos.xml_ref import default_value PROCESS_NAME = 'rsyslogd' -RSYSLOG_CONF = '/etc/rsyslog.d/00-vyos.conf' +RSYSLOG_CONF = '/run/rsyslog/rsyslog.conf' base_path = ['system', 'syslog'] -def get_config_value(key): - tmp = read_file(RSYSLOG_CONF) - tmp = re.findall(r'\n?{}\s+(.*)'.format(key), tmp) - return tmp[0] +def get_config(string=''): + """ + Retrieve current "running configuration" from FRR + string: search for a specific start string in the configuration + """ + command = 'cat /run/rsyslog/rsyslog.conf' + if string: + command += f' | sed -n "/^{string}$/,/}}/p"' # }} required to escape } in f-string + return cmd(command) class TestRSYSLOGService(VyOSUnitTestSHIM.TestCase): @classmethod @@ -53,37 +59,72 @@ class TestRSYSLOGService(VyOSUnitTestSHIM.TestCase): # Check for running process self.assertFalse(process_named_running(PROCESS_NAME)) - def test_syslog_console(self): - self.cli_set(base_path + ['console', 'facility', 'all', 'level', 'warning']) + def test_console(self): + level = 'warning' + self.cli_set(base_path + ['console', 'facility', 'all', 'level', level]) self.cli_commit() - self.assertIn('/dev/console', get_config_value('\*.warning')) - def test_syslog_global(self): + rsyslog_conf = get_config() + config = [ + f'if prifilt("*.{level}") then {{', # {{ required to escape { in f-string + 'action(type="omfile" file="/dev/console")', + ] + for tmp in config: + self.assertIn(tmp, rsyslog_conf) + + def test_global(self): hostname = 'vyos123' - domainname = 'example.local' + domain_name = 'example.local' + default_marker_interval = default_value(base_path + ['global', + 'marker', 'interval']) + + facility = { + 'auth': {'level': 'info'}, + 'kern': {'level': 'debug'}, + 'all': {'level': 'notice'}, + } + self.cli_set(['system', 'host-name', hostname]) - self.cli_set(['system', 'domain-name', domainname]) - self.cli_set(base_path + ['global', 'marker', 'interval', '600']) + self.cli_set(['system', 'domain-name', domain_name]) self.cli_set(base_path + ['global', 'preserve-fqdn']) - self.cli_set(base_path + ['global', 'facility', 'kern', 'level', 'err']) + + for tmp, tmp_options in facility.items(): + level = tmp_options['level'] + self.cli_set(base_path + ['global', 'facility', tmp, 'level', level]) self.cli_commit() - config = read_file(RSYSLOG_CONF) + config = get_config('') expected = [ - '$MarkMessagePeriod 600', - '$PreserveFQDN on', - 'kern.err', - f'$LocalHostName {hostname}.{domainname}', + f'module(load="immark" interval="{default_marker_interval}")', + 'global(preserveFQDN="on")', + f'global(localHostname="{hostname}.{domain_name}")', ] - for e in expected: self.assertIn(e, config) - def test_syslog_remote(self): + config = get_config('#### GLOBAL LOGGING ####') + prifilt = [] + for tmp, tmp_options in facility.items(): + if tmp == 'all': + tmp = '*' + level = tmp_options['level'] + prifilt.append(f'{tmp}.{level}') + + prifilt.sort() + prifilt = ','.join(prifilt) + + self.assertIn(f'if prifilt("{prifilt}") then {{', config) + self.assertIn( ' action(', config) + self.assertIn( ' type="omfile"', config) + self.assertIn( ' file="/var/log/messages"', config) + self.assertIn( ' queue.size="262144"', config) + self.assertIn( ' rotation.sizeLimitCommand="/usr/sbin/logrotate /etc/logrotate.d/vyos-rsyslog"', config) + + def test_remote(self): rhosts = { '169.254.0.1': { - 'facility': {'name' : 'auth', 'level': 'info'}, + 'facility': {'auth' : {'level': 'info'}}, 'protocol': 'udp', }, '169.254.0.2': { @@ -91,11 +132,17 @@ class TestRSYSLOGService(VyOSUnitTestSHIM.TestCase): 'protocol': 'udp', }, '169.254.0.3': { + 'facility': {'auth' : {'level': 'info'}, + 'kern' : {'level': 'debug'}, + 'all' : {'level': 'notice'}, + }, 'format': ['include-timezone', 'octet-counted'], 'protocol': 'tcp', + 'port': '10514', }, } default_port = default_value(base_path + ['remote', next(iter(rhosts)), 'port']) + default_protocol = default_value(base_path + ['remote', next(iter(rhosts)), 'protocol']) for remote, remote_options in rhosts.items(): remote_base = base_path + ['remote', remote] @@ -103,13 +150,10 @@ class TestRSYSLOGService(VyOSUnitTestSHIM.TestCase): if 'port' in remote_options: self.cli_set(remote_base + ['port', remote_options['port']]) - if ('facility' in remote_options and - 'name' in remote_options['facility'] and - 'level' in remote_options['facility'] - ): - facility = remote_options['facility']['name'] - level = remote_options['facility']['level'] - self.cli_set(remote_base + ['facility', facility, 'level', level]) + if 'facility' in remote_options: + for facility, facility_options in remote_options['facility'].items(): + level = facility_options['level'] + self.cli_set(remote_base + ['facility', facility, 'level', level]) if 'format' in remote_options: for format in remote_options['format']: @@ -123,32 +167,43 @@ class TestRSYSLOGService(VyOSUnitTestSHIM.TestCase): config = read_file(RSYSLOG_CONF) for remote, remote_options in rhosts.items(): - tmp = ' ' - if ('facility' in remote_options and - 'name' in remote_options['facility'] and - 'level' in remote_options['facility'] - ): - facility = remote_options['facility']['name'] - level = remote_options['facility']['level'] - tmp = f'{facility}.{level} ' - - tmp += '@' - if 'protocol' in remote_options and remote_options['protocol'] == 'tcp': - tmp += '@' - - if 'format' in remote_options and 'octet-counted' in remote_options['format']: - tmp += '(o)' + config = get_config(f'# Remote syslog to {remote}') + prifilt = [] + if 'facility' in remote_options: + for facility, facility_options in remote_options['facility'].items(): + level = facility_options['level'] + if facility == 'all': + facility = '*' + prifilt.append(f'{facility}.{level}') + + prifilt.sort() + prifilt = ','.join(prifilt) + if not prifilt: + # Skip test - as we do not render anything if no facility is set + continue + + self.assertIn(f'if prifilt("{prifilt}") then {{', config) + self.assertIn( ' type="omfwd"', config) + self.assertIn(f' target="{remote}"', config) port = default_port if 'port' in remote_options: port = remote_options['port'] + self.assertIn(f'port="{port}"', config) - tmp += f'{remote}:{port}' + protocol = default_protocol + if 'protocol' in remote_options: + protocol = remote_options['protocol'] + self.assertIn(f'protocol="{protocol}"', config) - if 'format' in remote_options and 'include-timezone' in remote_options['format']: - tmp += ';RSYSLOG_SyslogProtocol23Format' + if 'format' in remote_options: + if 'include-timezone' in remote_options['format']: + self.assertIn( ' template="SyslogProtocol23Format"', config) - self.assertIn(tmp, config) + if 'octet-counted' in remote_options['format']: + self.assertIn( ' TCP_Framing="octed-counted"', config) + else: + self.assertIn( ' TCP_Framing="traditional"', config) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/system_option.py b/src/conf_mode/system_option.py index e2832cde6..064a1aa91 100755 --- a/src/conf_mode/system_option.py +++ b/src/conf_mode/system_option.py @@ -86,7 +86,7 @@ def verify(options): if 'source_address' in config: if not is_addr_assigned(config['source_address']): - raise ConfigError('No interface with give address specified!') + raise ConfigError('No interface with given address specified!') if 'ssh_client' in options: config = options['ssh_client'] diff --git a/src/conf_mode/system_syslog.py b/src/conf_mode/system_syslog.py index eb2f02eb3..78840a5f5 100755 --- a/src/conf_mode/system_syslog.py +++ b/src/conf_mode/system_syslog.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2024 VyOS maintainers and contributors +# Copyright (C) 2018-2025 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 @@ -22,15 +22,15 @@ from vyos.base import Warning from vyos.config import Config from vyos.configdict import is_node_changed from vyos.configverify import verify_vrf +from vyos.utils.network import is_addr_assigned from vyos.utils.process import call from vyos.template import render from vyos import ConfigError from vyos import airbag airbag.enable() -rsyslog_conf = '/etc/rsyslog.d/00-vyos.conf' +rsyslog_conf = '/run/rsyslog/rsyslog.conf' logrotate_conf = '/etc/logrotate.d/vyos-rsyslog' -systemd_override = r'/run/systemd/system/rsyslog.service.d/override.conf' def get_config(config=None): if config: @@ -70,8 +70,8 @@ def verify(syslog): if not syslog: return None - if 'host' in syslog: - for host, host_options in syslog['host'].items(): + if 'remote' in syslog: + for host, host_options in syslog['remote'].items(): if 'protocol' in host_options and host_options['protocol'] == 'udp': if 'format' in host_options and 'octet_counted' in host_options['format']: Warning(f'Syslog UDP transport for "{host}" should not use octet-counted format!') @@ -88,11 +88,7 @@ def generate(syslog): return None render(rsyslog_conf, 'rsyslog/rsyslog.conf.j2', syslog) - render(systemd_override, 'rsyslog/override.conf.j2', syslog) render(logrotate_conf, 'rsyslog/logrotate.j2', syslog) - - # Reload systemd manager configuration - call('systemctl daemon-reload') return None def apply(syslog): diff --git a/src/etc/rsyslog.conf b/src/etc/rsyslog.conf deleted file mode 100644 index b3f41acb6..000000000 --- a/src/etc/rsyslog.conf +++ /dev/null @@ -1,67 +0,0 @@ -################# -#### MODULES #### -################# - -$ModLoad imuxsock # provides support for local system logging -$ModLoad imklog # provides kernel logging support (previously done by rklogd) -#$ModLoad immark # provides --MARK-- message capability - -$OmitLocalLogging off -$SystemLogSocketName /run/systemd/journal/syslog - -$KLogPath /proc/kmsg - -########################### -#### GLOBAL DIRECTIVES #### -########################### - -# Use traditional timestamp format. -# To enable high precision timestamps, comment out the following line. -# A modern-style logfile format similar to TraditionalFileFormat, buth with high-precision timestamps and timezone information -#$ActionFileDefaultTemplate RSYSLOG_FileFormat -# The "old style" default log file format with low-precision timestamps -$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat - -# Filter duplicated messages -$RepeatedMsgReduction on - -# -# Set the default permissions for all log files. -# -$FileOwner root -$FileGroup adm -$FileCreateMode 0640 -$DirCreateMode 0755 -$Umask 0022 - -# -# Stop excessive logging of sudo -# -:msg, contains, " pam_unix(sudo:session): session opened for user root(uid=0) by" stop -:msg, contains, "pam_unix(sudo:session): session closed for user root" stop - -# -# Include all config files in /etc/rsyslog.d/ -# -$IncludeConfig /etc/rsyslog.d/*.conf - -# The lines below cause all listed daemons/processes to be logged into -# /var/log/auth.log, then drops the message so it does not also go to the -# regular syslog so that messages are not duplicated - -$outchannel auth_log,/var/log/auth.log -if $programname == 'CRON' or - $programname == 'sudo' or - $programname == 'su' - then :omfile:$auth_log - -if $programname == 'CRON' or - $programname == 'sudo' or - $programname == 'su' - then stop - -############### -#### RULES #### -############### -# Emergencies are sent to everybody logged in. -*.emerg :omusrmsg:*
\ No newline at end of file diff --git a/src/etc/systemd/system/rsyslog.service.d/override.conf b/src/etc/systemd/system/rsyslog.service.d/override.conf new file mode 100644 index 000000000..665b994d9 --- /dev/null +++ b/src/etc/systemd/system/rsyslog.service.d/override.conf @@ -0,0 +1,10 @@ +[Unit] +StartLimitIntervalSec=0 + +[Service] +ExecStart= +ExecStart=/usr/sbin/rsyslogd -n -iNONE -f /run/rsyslog/rsyslog.conf +Restart=always +RestartPreventExitStatus= +RestartSec=10 +RuntimeDirectoryPreserve=yes diff --git a/src/migration-scripts/system/28-to-29 b/src/migration-scripts/system/28-to-29 index 2f55d425a..1addad035 100644 --- a/src/migration-scripts/system/28-to-29 +++ b/src/migration-scripts/system/28-to-29 @@ -16,6 +16,7 @@ # T6989: # - remove syslog arbitrary file logging # - remove syslog user console logging +# - rename "host" to "remote" from vyos.configtree import ConfigTree @@ -24,14 +25,16 @@ base = ['system', 'syslog'] def migrate(config: ConfigTree) -> None: if not config.exists(base): return - + # Drop support for custom file logging if config.exists(base + ['file']): config.delete(base + ['file']) + # Drop support for logging to a user tty + # This should be dynamically added via an op-mode command like "terminal monitor" if config.exists(base + ['user']): config.delete(base + ['user']) - # rename host -> remote + # Rename host x.x.x.x -> remote x.x.x.x if config.exists(base + ['host']): config.set(base + ['remote']) config.set_tag(base + ['remote']) |