summaryrefslogtreecommitdiff
path: root/src/conf_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-xsrc/conf_mode/snmp.py1
-rwxr-xr-xsrc/conf_mode/syslog.py266
-rwxr-xr-xsrc/conf_mode/vrrp.py338
3 files changed, 605 insertions, 0 deletions
diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py
index 1ef69ceb2..001e6c6b4 100755
--- a/src/conf_mode/snmp.py
+++ b/src/conf_mode/snmp.py
@@ -144,6 +144,7 @@ agentaddress unix:/run/snmpd.socket{% if listen_on %}{% for li in listen_on %},{
{% if c.network -%}
{% for network in c.network %}
{{ c.authorization }}community {{ c.name }} {{ network }}
+{{ c.authorization }}community6 {{ c.name }} {{ network }}
{% endfor %}
{% else %}
{{ c.authorization }}community {{ c.name }}
diff --git a/src/conf_mode/syslog.py b/src/conf_mode/syslog.py
new file mode 100755
index 000000000..5dfc6f390
--- /dev/null
+++ b/src/conf_mode/syslog.py
@@ -0,0 +1,266 @@
+#!/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 <http://www.gnu.org/licenses/>.
+#
+#
+
+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
+{% 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/vyos-rsyslog',
+ '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 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)
+ #print (config_text)
+ 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)
+ #print (config_text)
+ with open('/etc/logrotate.d/vyos-rsyslog', 'w') as f:
+ f.write(config_text)
+
+def verify(c):
+ 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)
diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/vrrp.py
new file mode 100755
index 000000000..155b71aa8
--- /dev/null
+++ b/src/conf_mode/vrrp.py
@@ -0,0 +1,338 @@
+#!/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 <http://www.gnu.org/licenses/>.
+#
+
+import os
+import sys
+import subprocess
+import ipaddress
+
+import jinja2
+
+import vyos.config
+import vyos.keepalived
+
+from vyos import ConfigError
+
+
+config_file = "/etc/keepalived/keepalived.conf"
+
+config_tmpl = """
+# Autogenerated by VyOS
+# Do not edit this file, all your changes will be lost
+# on next commit or reboot
+
+{% for group in groups -%}
+
+{% if group.health_check_script -%}
+vrrp_script healthcheck_{{ group.name }} {
+ script {{ group.health_check_script }}
+ interval {{ group.health_check_interval }}
+ fall {{ group.health_check_count }}
+ rise 1
+
+}
+{% endif %}
+
+vrrp_instance {{ group.name }} {
+ {% if group.description -%}
+ # {{ group.description }}
+ {% endif -%}
+
+ state BACKUP
+ interface {{ group.interface }}
+ virtual_router_id {{ group.vrid }}
+ priority {{ group.priority }}
+ advert_int {{ group.advertise_interval }}
+
+ {% if group.preempt -%}
+ preempt_delay {{ group.preempt_delay }}
+ {% else -%}
+ nopreempt
+ {% endif -%}
+
+ {% if group.peer_address -%}
+ unicast_peer { {{ group.peer_address }} }
+ {% endif -%}
+
+ {% if group.hello_source -%}
+ {%- if group.peer_address -%}
+ unicast_src_ip {{ group.hello_source }}
+ {%- else -%}
+ mcast_src_ip {{ group.hello_source }}
+ {%- endif %}
+ {% endif -%}
+
+ {% if group.use_vmac -%}
+ use_vmac {{group.interface}}v{{group.vrid}}
+ {% endif -%}
+
+ {% if group.auth_password -%}
+ authentication {
+ auth_pass {{ group.auth_password }}
+ auth_type {{ group.auth_type }}
+ }
+ {% endif -%}
+
+ virtual_ipaddress {
+ {% for addr in group.virtual_addresses -%}
+ {{ addr }}
+ {% endfor -%}
+ }
+
+ {% if group.health_check_script -%}
+ track_script {
+ healthcheck_{{ group.name }}
+ }
+ {% endif -%}
+
+ {% if group.master_script -%}
+ notify_master "/usr/libexec/vyos/system/vrrp-script-wrapper.py --script {{ group.master_script }} --state master --group {{ group.name }} --interface {{ group.interface }}"
+ {% endif -%}
+
+ {% if group.backup_script -%}
+ notify_backup "/usr/libexec/vyos/system/vrrp-script-wrapper.py --script {{ group.backup_script }} --state backup --group {{ group.name }} --interface {{ group.interface }}"
+ {% endif -%}
+
+ {% if group.fault_script -%}
+ notify_fault "/usr/libexec/vyos/system/vrrp-script-wrapper.py --script {{ group.fault_script }} --state fault --group {{ group.name }} --interface {{ group.interface }}"
+ {% endif -%}
+}
+
+{% endfor -%}
+
+{% for sync_group in sync_groups -%}
+vrrp_sync_group {{ sync_group.name }} {
+ group {
+ {% for member in sync_group.members -%}
+ {{ member }}
+ {% endfor -%}
+ }
+
+ {% if sync_group.conntrack_sync -%}
+ notify_master "/opt/vyatta/sbin/vyatta-vrrp-conntracksync.sh master {{ sync_group.name }}"
+ notify_backup "/opt/vyatta/sbin/vyatta-vrrp-conntracksync.sh backup {{ sync_group.name }}"
+ notify_fault "/opt/vyatta/sbin/vyatta-vrrp-conntracksync.sh fault {{ sync_group.name }}"
+ {% endif -%}
+}
+
+{% endfor -%}
+
+"""
+
+def get_config():
+ vrrp_groups = []
+ sync_groups = []
+
+ config = vyos.config.Config()
+
+ # Get the VRRP groups
+ for group_name in config.list_nodes("high-availability vrrp group"):
+ config.set_level("high-availability vrrp group {0}".format(group_name))
+
+ # Retrieve the values
+ group = {"preempt": True, "use_vmac": False, "disable": False}
+
+ if config.exists("disable"):
+ group["disable"] = True
+
+ group["name"] = group_name
+ group["vrid"] = config.return_value("vrid")
+ group["interface"] = config.return_value("interface")
+ group["description"] = config.return_value("description")
+ group["advertise_interval"] = config.return_value("advertise-interval")
+ group["priority"] = config.return_value("priority")
+ group["hello_source"] = config.return_value("hello-source-address")
+ group["peer_address"] = config.return_value("peer-address")
+ group["sync_group"] = config.return_value("sync-group")
+ group["preempt_delay"] = config.return_value("preempt-delay")
+ group["virtual_addresses"] = config.return_values("virtual-address")
+
+ group["auth_password"] = config.return_value("authentication password")
+ group["auth_type"] = config.return_value("authentication type")
+
+ group["health_check_script"] = config.return_value("health-check script")
+ group["health_check_interval"] = config.return_value("health-check interval")
+ group["health_check_count"] = config.return_value("health-check failure-count")
+
+ group["master_script"] = config.return_value("transition-script master")
+ group["backup_script"] = config.return_value("transition-script backup")
+ group["fault_script"] = config.return_value("transition-script fault")
+
+ if config.exists("no-preempt"):
+ group["preempt"] = False
+ if config.exists("rfc3768-compatibility"):
+ group["use_vmac"] = True
+
+ # Substitute defaults where applicable
+ if not group["advertise_interval"]:
+ group["advertise_interval"] = 1
+ if not group["priority"]:
+ group["priority"] = 100
+ if not group["preempt_delay"]:
+ group["preempt_delay"] = 5 * 60
+ if not group["health_check_interval"]:
+ group["health_check_interval"] = 60
+ if not group["health_check_count"]:
+ group["health_check_count"] = 3
+
+ # FIXUP: translate our option for auth type to keepalived's syntax
+ # for simplicity
+ if group["auth_type"]:
+ if group["auth_type"] == "plaintext-password":
+ group["auth_type"] = "PASS"
+ else:
+ group["auth_type"] = "AH"
+
+ vrrp_groups.append(group)
+
+ config.set_level("")
+
+ # Get the sync group used for conntrack-sync
+ conntrack_sync_group = None
+ if config.exists("service conntrack-sync failover-mechanism vrrp"):
+ conntrack_sync_group = config.return_value("service conntrack-sync failover-mechanism vrrp sync-group")
+
+ # Get the sync groups
+ for sync_group_name in config.list_nodes("high-availability vrrp sync-group"):
+ config.set_level("high-availability vrrp sync-group {0}".format(sync_group_name))
+
+ sync_group = {"conntrack_sync": False}
+ sync_group["name"] = sync_group_name
+ sync_group["members"] = config.return_values("member")
+ if conntrack_sync_group:
+ if conntrack_sync_group == sync_group_name:
+ sync_group["conntrack_sync"] = True
+
+ sync_groups.append(sync_group)
+
+ return (vrrp_groups, sync_groups)
+
+def verify(data):
+ vrrp_groups, sync_groups = data
+
+ for group in vrrp_groups:
+ # Check required fields
+ if not group["vrid"]:
+ raise ConfigError("vrid is required but not set in VRRP group {0}".format(group["name"]))
+ if not group["interface"]:
+ raise ConfigError("interface is required but not set in VRRP group {0}".format(group["name"]))
+ if not group["virtual_addresses"]:
+ raise ConfigError("virtual-address is required but not set in VRRP group {0}".format(group["name"]))
+
+ if group["auth_password"] and (not group["auth_type"]):
+ raise ConfigError("authentication type is required but not set in VRRP group {0}".format(group["name"]))
+
+ # Keepalived doesn't allow mixing IPv4 and IPv6 in one group, so we mirror that restriction
+
+ # XXX: filter on map object is destructive, so we force it to list.
+ # Additionally, filter objects always evaluate to True, empty or not,
+ # so we force them to lists as well.
+ vaddrs = list(map(lambda i: ipaddress.ip_interface(i), group["virtual_addresses"]))
+ vaddrs4 = list(filter(lambda x: isinstance(x, ipaddress.IPv4Interface), vaddrs))
+ vaddrs6 = list(filter(lambda x: isinstance(x, ipaddress.IPv6Interface), vaddrs))
+
+ if vaddrs4 and vaddrs6:
+ raise ConfigError("VRRP group {0} mixes IPv4 and IPv6 virtual addresses, this is not allowed. Create separate groups for IPv4 and IPv6".format(group["name"]))
+
+ if vaddrs4:
+ if group["hello_source"]:
+ hsa = ipaddress.ip_address(group["hello_source"])
+ if isinstance(hsa, ipaddress.IPv6Address):
+ raise ConfigError("VRRP group {0} uses IPv4 but its hello-source-address is IPv6".format(group["name"]))
+ if group["peer_address"]:
+ pa = ipaddress.ip_address(group["peer_address"])
+ if isinstance(hsa, ipaddress.IPv6Address):
+ raise ConfigError("VRRP group {0} uses IPv4 but its peer-address is IPv6".format(group["name"]))
+
+ if vaddrs6:
+ if group["hello_source"]:
+ hsa = ipaddress.ip_address(group["hello_source"])
+ if isinstance(hsa, ipaddress.IPv4Address):
+ raise ConfigError("VRRP group {0} uses IPv6 but its hello-source-address is IPv4".format(group["name"]))
+ if group["peer_address"]:
+ pa = ipaddress.ip_address(group["peer_address"])
+ if isinstance(hsa, ipaddress.IPv4Address):
+ raise ConfigError("VRRP group {0} uses IPv6 but its peer-address is IPv4".format(group["name"]))
+
+ # Disallow same VRID on multiple interfaces
+ _groups = sorted(vrrp_groups, key=(lambda x: x["interface"]))
+ count = len(_groups) - 1
+ index = 0
+ while (index < count):
+ if _groups[index]["vrid"] == _groups[index + 1]["vrid"]:
+ raise ConfigError("VRID {0} is used in groups {1} and {2} that both use interface {3}. Groups on the same interface must use different VRIDs".format(
+ _groups[index]["vrid"], _groups[index]["name"], _groups[index + 1]["name"], _groups[index]["interface"]))
+ else:
+ index += 1
+
+ # Check sync groups
+ vrrp_group_names = list(map(lambda x: x["name"], vrrp_groups))
+
+ for sync_group in sync_groups:
+ for m in sync_group["members"]:
+ if not (m in vrrp_group_names):
+ raise ConfigError("VRRP sync-group {0} refers to VRRP group {1}, but group {1} does not exist".format(sync_group["name"], m))
+
+def generate(data):
+ vrrp_groups, sync_groups = data
+
+ # Remove disabled groups from the sync group member lists
+ for sync_group in sync_groups:
+ for member in sync_group["members"]:
+ g = list(filter(lambda x: x["name"] == member, vrrp_groups))[0]
+ if g["disable"]:
+ print("Warning: ignoring disabled VRRP group {0} in sync-group {1}".format(g["name"], sync_group["name"]))
+ # Filter out disabled groups
+ vrrp_groups = list(filter(lambda x: x["disable"] != True, vrrp_groups))
+
+ tmpl = jinja2.Template(config_tmpl)
+ config_text = tmpl.render({"groups": vrrp_groups, "sync_groups": sync_groups})
+
+ with open(config_file, 'w') as f:
+ f.write(config_text)
+ return None
+
+def apply(data):
+ vrrp_groups, sync_groups = data
+ if vrrp_groups:
+ if not vyos.keepalived.vrrp_running():
+ print("Starting the VRRP process")
+ ret = subprocess.call("sudo systemctl restart keepalived.service", shell=True)
+ else:
+ print("Reloading the VRRP process")
+ ret = subprocess.call("sudo systemctl reload keepalived.service", shell=True)
+
+ if ret != 0:
+ raise ConfigError("keepalived failed to start")
+ else:
+ # VRRP is removed in the commit
+ print("Stopping the VRRP process")
+ subprocess.call("sudo systemctl stop keepalived.service", shell=True)
+ os.unlink(config_file)
+
+ return None
+
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print("VRRP error: {0}".format(str(e)))
+ sys.exit(1)