#!/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 syslog as sl import subprocess from vyos.config import Config from vyos import ConfigError dir = r'/config/auth/wireguard' pk = dir + '/private.key' pub = dir + '/public.key' ### check_kmod may be removed in the future, ### just want to have everything smoothly running after reboot def check_kmod(): if not os.path.exists('/sys/module/wireguard'): sl.syslog(sl.LOG_NOTICE, "loading wirguard kmod") if os.system('sudo modprobe wireguard') != 0: sl.syslog(sl.LOG_NOTICE, "modprobe wireguard failed") raise ConfigError("modprobe wireguard failed") def get_config(): config_data = { 'interfaces' : {} } c = Config() if not c.exists('interfaces wireguard'): return None c.set_level('interfaces') intfcs = c.list_nodes('wireguard') intfcs_eff = c.list_effective_nodes('wireguard') new_lst = list( set(intfcs) - set(intfcs_eff) ) del_lst = list( set(intfcs_eff) - set(intfcs) ) ### setting deafult and determine status of the config for intfc in intfcs: cnf = 'wireguard ' + intfc # default data struct config_data['interfaces'].update ( { intfc : { 'addr' : '', 'descr' : intfc, ## snmp ifAlias 'lport' : '', 'status' : 'exists', 'state' : 'enabled', 'mtu' : 1420, 'peer' : {} } } ) for i in new_lst: config_data['interfaces'][i]['status'] = 'create' for i in del_lst: config_data['interfaces'].update ( { i : { 'status': 'delete' } } ) ### based on the status, set real values for intfc in intfcs: cnf = 'wireguard ' + intfc if config_data['interfaces'][intfc]['status'] != 'delete': #### addresses if c.exists(cnf + ' address'): config_data['interfaces'][intfc]['addr'] = c.return_values(cnf + ' address') ### listen port if c.exists(cnf + ' listen-port'): config_data['interfaces'][intfc]['lport'] = c.return_value(cnf + ' listen-port') ### description if c.exists(cnf + ' description'): config_data['interfaces'][intfc]['descr'] = c.return_value(cnf + ' description') ### mtu if c.exists(cnf + ' mtu'): config_data['interfaces'][intfc]['mtu'] = c.return_value(cnf + ' mtu') ### peers if c.exists(cnf + ' peer'): for p in c.list_nodes(cnf + ' peer'): config_data['interfaces'][intfc]['peer'].update ( { p : { 'allowed-ips' : [], 'endpoint' : '' } } ) if c.exists(cnf + ' peer ' + p + ' allowed-ips'): config_data['interfaces'][intfc]['peer'][p]['allowed-ips'] = c.return_values(cnf + ' peer ' + p + ' allowed-ips') if c.exists(cnf + ' peer ' + p + ' endpoint'): config_data['interfaces'][intfc]['peer'][p]['endpoint'] = c.return_value(cnf + ' peer ' + p + ' endpoint') ### persistent-keepalive if c.exists(cnf + ' peer ' + p + ' persistent-keepalive'): config_data['interfaces'][intfc]['peer'][p]['persistent-keepalive'] = c.return_value(cnf + ' peer ' + p + ' persistent-keepalive') #print (config_data) return config_data def verify(c): if not c: return None for i in c['interfaces']: if c['interfaces'][i]['status'] != 'delete': if not c['interfaces'][i]['addr']: raise ConfigError("address required for interface " + i) if not c['interfaces'][i]['lport']: raise ConfigError("listen-port required for interface " + i) if not c['interfaces'][i]['peer']: raise ConfigError("peer required on interface " + i) else: for p in c['interfaces'][i]['peer']: if not c['interfaces'][i]['peer'][p]['allowed-ips']: raise ConfigError("allowed-ips required on interface " + i + " for peer " + p) ### eventually check allowed-ips (if it's an ip and valid CIDR or so) ### endpoint needs to be IP:port def apply(c): ### no wg config left, delete all wireguard devices on the os if not c: net_devs = os.listdir('/sys/class/net/') for dev in net_devs: buf = open('/sys/class/net/' + dev + '/uevent','r').read() if re.search("DEVTYPE=wireguard", buf, re.I|re.M): wg_intf = re.sub("INTERFACE=","", re.search("INTERFACE=.*", buf, re.I|re.M).group(0) ) sl.syslog(sl.LOG_NOTICE, "removing interface " + wg_intf) subprocess.call(['ip l d dev ' + wg_intf + ' >/dev/null'], shell=True) return None ### ## to find the diffs between old config an new config ## so we only configure/delete what was not previously configured ### c_eff = Config() c_eff.set_level('interfaces wireguard') ### deletion of specific interface for intf in c['interfaces']: if c['interfaces'][intf]['status'] == 'delete': sl.syslog(sl.LOG_NOTICE, "removing interface " + intf) subprocess.call(['ip l d dev ' + intf + ' &>/dev/null'], shell=True) ### new config if c['interfaces'][intf]['status'] == 'create': if not os.path.exists(pk): raise ConfigError("No keys found, generate them by executing: \'run generate wireguard keypair\'") subprocess.call(['ip l a dev ' + intf + ' type wireguard 2>/dev/null'], shell=True) for addr in c['interfaces'][intf]['addr']: add_addr(intf, addr) configure_interface(c,intf) subprocess.call(['ip l set up dev ' + intf + ' &>/dev/null'], shell=True) ### config updates if c['interfaces'][intf]['status'] == 'exists': ### IP address change addr_eff = re.sub("\'", "", c_eff.return_effective_values(intf + ' address')).split() addr_rem = list( set(addr_eff) - set(c['interfaces'][intf]['addr']) ) addr_add = list( set(c['interfaces'][intf]['addr']) - set(addr_eff) ) if len(addr_rem) !=0: for addr in addr_rem: del_addr(intf, addr) if len(addr_add) !=0: for addr in addr_add: add_addr(intf, addr) ### persistent-keepalive for p in c_eff.list_nodes(intf + ' peer'): pklv_eff = c_eff.return_effective_value(intf + ' peer ' + p + ' persistent-keepalive') pklv = c_eff.return_value(intf + ' peer ' + p + ' persistent-keepalive') if pklv_eff == pklv: del c['interfaces'][intf]['peer'][p]['persistent-keepalive'] ## wg command call configure_interface(c,intf) ### ifalias for snmp from description descr_eff = c_eff.return_effective_value(intf + ' description') cnf_descr = c['interfaces'][intf]['descr'] if descr_eff != cnf_descr: open('/sys/class/net/' + str(intf) + '/ifalias','w').write(str(cnf_descr)) def configure_interface(c, intf): for p in c['interfaces'][intf]['peer']: cmd = "wg set " + intf + \ " listen-port " + c['interfaces'][intf]['lport'] + \ " private-key " + pk + \ " peer " + p cmd += " allowed-ips " for ap in c['interfaces'][intf]['peer'][p]['allowed-ips']: if ap != c['interfaces'][intf]['peer'][p]['allowed-ips'][-1]: cmd += ap + "," else: cmd += ap ## endpoint is only required if wg runs as client if c['interfaces'][intf]['peer'][p]['endpoint']: cmd += " endpoint " + c['interfaces'][intf]['peer'][p]['endpoint'] if c['interfaces'][intf]['peer'][p]['persistent-keepalive']: cmd += " persistent-keepalive " + str( c['interfaces'][intf]['peer'][p]['persistent-keepalive']) sl.syslog(sl.LOG_NOTICE, "sudo " + cmd) subprocess.call([ 'sudo ' + cmd], shell=True) def add_addr(intf, addr): ret = subprocess.call(['ip a a dev ' + intf + ' ' + addr + ' &>/dev/null'], shell=True) if ret != 0: raise ConfigError('Can\'t set IP ' + addr + ' on ' + intf ) else: sl.syslog(sl.LOG_NOTICE, "ip a a dev " + intf + " " + addr) def del_addr(intf, addr): ret = subprocess.call(['ip a d dev ' + intf + ' ' + addr + ' &>/dev/null'], shell=True) if ret != 0: raise ConfigError('Can\'t delete IP ' + addr + ' on ' + intf ) else: sl.syslog(sl.LOG_NOTICE, "ip a d dev " + intf + " " + addr) if __name__ == '__main__': try: check_kmod() c = get_config() verify(c) #generate(c) apply(c) except ConfigError as e: print(e) sys.exit(1)