#!/usr/bin/env python3 # # Copyright (C) 2018 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 . # # import sys import os import re import jinja2 from vyos.config import Config from vyos import ConfigError ########### config templates #### /etc/rsyslog.d/vyos-rsyslog.conf ### configs = ''' ## generated by syslog.py ## ## file based logging {% if files['global']['marker'] %} $ModLoad immark {% if files['global']['marker-interval'] %} $MarkMessagePeriod {{files['global']['marker-interval']}} {% endif %} {% endif %} {% for file in files %} $outchannel {{file}},{{files[file]['log-file']}},{{files[file]['max-size']}},{{files[file]['action-on-max-size']}} {{files[file]['selectors']}} :omfile:${{file}} {% endfor %} {% if console %} ## console logging {% for con in console %} {{console[con]['selectors']}} /dev/console {% endfor %} {% endif %} {% if hosts %} ## remote logging {% for host in hosts %} {% if hosts[host]['proto'] == 'tcp' %} {{hosts[host]['selectors']}} @@{{host}} {% else %} {{hosts[host]['selectors']}} @{{host}} {% endif %} {% endfor %} {% endif %} {% if user %} {% for u in user %} {{user[u]['selectors']}} :omusrmsg:{{u}} {% endfor %} {% endif %} ''' logrotate_configs = ''' {% for file in files %} {{files[file]['log-file']}} { missingok notifempty create rotate {{files[file]['max-files']}} size={{ files[file]['max-size']//1024}}k postrotate invoke-rc.d rsyslog rotate > /dev/null endscript } {% endfor %} ''' ############# config templates end def get_config(): c = Config() if not c.exists('system syslog'): return None c.set_level('system syslog') config_data = { 'files' : {}, 'console' : {}, 'hosts' : {}, 'user' : {} } ##### # /etc/rsyslog.d/vyos-rsyslog.conf # 'set system syslog global' ##### config_data['files'].update( { 'global' : { 'log-file' : '/var/log/messages', 'max-size' : 262144, 'action-on-max-size' : '/usr/sbin/logrotate /etc/logrotate.d/vyos-rsyslog', 'selectors' : '*.notice;local7.debug', 'max-files' : '5' } } ) if c.exists('global marker'): config_data['files']['global']['marker'] = True if c.exists('global marker interval'): config_data['files']['global']['marker-interval'] = c.return_value('global marker interval') if c.exists('global facility'): config_data['files']['global']['selectors'] = generate_selectors(c, 'global facility') if c.exists('global archive size'): config_data['files']['global']['max-size'] = int(c.return_value('global archive size'))* 1024 if c.exists('global archive files'): config_data['files']['global']['max-files'] = c.return_value('global archive files') ### # set system syslog file ### if c.exists('file'): filenames = c.list_nodes('file') for filename in filenames: config_data['files'].update( { filename : { 'log-file' : '/var/log/user/' + filename, 'max-files' : '5', 'action-on-max-size' : '/usr/sbin/logrotate /etc/logrotate.d/' + filename, 'selectors' : '*.err', 'max-size' : 262144 } } ) if c.exists('file ' + filename + ' facility'): config_data['files'][filename]['selectors'] = generate_selectors(c, 'file ' + filename + ' facility') if c.exists('file ' + filename + ' archive size'): config_data['files'][filename]['max-size'] = int(c.return_value('file ' + filename + ' archive size'))* 1024 if c.exists('file ' + filename + ' archive files'): config_data['files'][filename]['max-files'] = c.return_value('file ' + filename + ' archive files') ## set system syslog console if c.exists('console'): config_data['console'] = { '/dev/console' : { 'selectors' : '*.err' } } for f in c.list_nodes('console facility'): if c.exists('console facility ' + f + ' level'): config_data['console'] = { '/dev/console' : { 'selectors' : generate_selectors(c, 'console facility') } } ## set system syslog host if c.exists('host'): proto = 'udp' rhosts = c.list_nodes('host') for rhost in rhosts: for fac in c.list_nodes('host ' + rhost + ' facility'): if c.exists('host ' + rhost + ' facility ' + fac + ' protocol'): proto = c.return_value('host ' + rhost + ' facility ' + fac + ' protocol') config_data['hosts'].update( { rhost : { 'selectors' : generate_selectors(c, 'host ' + rhost + ' facility'), 'proto' : proto } } ) ## set system syslog user if c.exists('user'): usrs = c.list_nodes('user') for usr in usrs: config_data['user'].update( { usr : { 'selectors' : generate_selectors(c, 'user ' + usr + ' facility') } } ) return config_data def generate_selectors(c, config_node): ## protocols and security are being mapped here ## for backward compatibility with old configs ## security and protocol mappings can be removed later if c.is_tag(config_node): nodes = c.list_nodes(config_node) selectors = "" for node in nodes: lvl = c.return_value( config_node + ' ' + node + ' level') if lvl == None: lvl = "err" if lvl == 'all': lvl = '*' if node == 'all' and node != nodes[-1]: selectors += "*." + lvl + ";" elif node == 'all': selectors += "*." + lvl elif node != nodes[-1]: if node == 'protocols': node = 'local7' if node == 'security': node = 'auth' selectors += node + "." + lvl + ";" else: if node == 'protocols': node = 'local7' if node == 'security': node = 'auth' selectors += node + "." + lvl return selectors def generate(c): tmpl = jinja2.Template(configs, trim_blocks=True) config_text = tmpl.render(c) with open('/etc/rsyslog.d/vyos-rsyslog.conf', 'w') as f: f.write(config_text) ## eventually write for each file its own logrotate file, since size is defined it shouldn't matter tmpl = jinja2.Template(logrotate_configs, trim_blocks=True) config_text = tmpl.render(c) with open('/etc/logrotate.d/vyos-rsyslog', 'w') as f: f.write(config_text) def verify(c): # # /etc/rsyslog.conf is generated somewhere and copied over the original (exists in /opt/vyatta/etc/rsyslog.conf) # it interferes with the global logging, to make sure we are using a single base, template is enforced here # if not os.path.islink('/etc/rsyslog.conf'): os.remove('/etc/rsyslog.conf') os.symlink('/usr/share/vyos/templates/rsyslog/rsyslog.conf', '/etc/rsyslog.conf') # /var/log/vyos-rsyslog were the old files, we may want to clean those up, but currently there # is a chance that someone still needs it, so I don't automatically remove them if c == None: return None fac = ['*','auth','authpriv','cron','daemon','kern','lpr','mail','mark','news','protocols','security',\ 'syslog','user','uucp','local0','local1','local2','local3','local4','local5','local6','local7'] lvl = ['emerg','alert','crit','err','warning','notice','info','debug','*'] for conf in c: if c[conf]: for item in c[conf]: for s in c[conf][item]['selectors'].split(";"): f = re.sub("\..*$","",s) if f not in fac: print (c[conf]) raise ConfigError('Invalid facility ' + s + ' set in '+ conf + ' ' + item) l = re.sub("^.+\.","",s) if l not in lvl: raise ConfigError('Invalid logging level ' + s + ' set in '+ conf + ' ' + item) def apply(c): ### vyatta-log.conf is being generated somewhere ### this is just a quick hack to remove the old configfile if os.path.exists('/etc/rsyslog.d/vyatta-log.conf'): os.remove('/etc/rsyslog.d/vyatta-log.conf') os.system("sudo systemctl restart rsyslog >/dev/null") if __name__ == '__main__': try: c = get_config() verify(c) generate(c) apply(c) except ConfigError as e: print(e) sys.exit(1)