summaryrefslogtreecommitdiff
path: root/src/conf_mode/interfaces-wireguard.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode/interfaces-wireguard.py')
-rwxr-xr-xsrc/conf_mode/interfaces-wireguard.py433
1 files changed, 227 insertions, 206 deletions
diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py
index ff12a5172..8bf81c747 100755
--- a/src/conf_mode/interfaces-wireguard.py
+++ b/src/conf_mode/interfaces-wireguard.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018 VyOS maintainers and contributors
+# Copyright (C) 2018-2020 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
@@ -13,263 +13,284 @@
#
# 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 subprocess
+
+from sys import exit
from copy import deepcopy
from netifaces import interfaces
-from vyos import ConfigError
from vyos.config import Config
from vyos.configdict import list_diff
from vyos.ifconfig import WireGuardIf
+from vyos.util import chown, is_bridge_member, chmod_750
+from vyos.util import call
+from vyos import ConfigError
kdir = r'/config/auth/wireguard'
+default_config_data = {
+ 'intfc': '',
+ 'address': [],
+ 'address_remove': [],
+ 'description': '',
+ 'lport': None,
+ 'deleted': False,
+ 'disable': False,
+ 'fwmark': 0x00,
+ 'mtu': 1420,
+ 'peer': [],
+ 'peer_remove': [], # stores public keys of peers to remove
+ 'pk': f'{kdir}/default/private.key',
+ 'vrf': ''
+}
def _check_kmod():
- if not os.path.exists('/sys/module/wireguard'):
- if os.system('sudo modprobe wireguard') != 0:
- raise ConfigError("modprobe wireguard failed")
+ modules = ['wireguard']
+ for module in modules:
+ if not os.path.exists(f'/sys/module/{module}'):
+ if call(f'modprobe {module}') != 0:
+ raise ConfigError(f'Loading Kernel module {module} failed')
def _migrate_default_keys():
- if os.path.exists('{}/private.key'.format(kdir)) and not os.path.exists('{}/default/private.key'.format(kdir)):
- old_umask = os.umask(0o027)
- location = '{}/default'.format(kdir)
- subprocess.call(['sudo mkdir -p ' + location], shell=True)
- subprocess.call(['sudo chgrp vyattacfg ' + location], shell=True)
- subprocess.call(['sudo chmod 750 ' + location], shell=True)
- os.rename('{}/private.key'.format(kdir),
- '{}/private.key'.format(location))
- os.rename('{}/public.key'.format(kdir),
- '{}/public.key'.format(location))
- os.umask(old_umask)
+ if os.path.exists(f'{kdir}/private.key') and not os.path.exists(f'{kdir}/default/private.key'):
+ location = f'{kdir}/default'
+ if not os.path.exists(location):
+ os.makedirs(location)
+
+ chown(location, 'root', 'vyattacfg')
+ chmod_750(location)
+ os.rename(f'{kdir}/private.key', f'{location}/private.key')
+ os.rename(f'{kdir}/public.key', f'{location}/public.key')
def get_config():
- c = Config()
- if not c.exists(['interfaces', 'wireguard']):
- return None
+ conf = Config()
+ base = ['interfaces', 'wireguard']
+
+ # determine tagNode instance
+ if 'VYOS_TAGNODE_VALUE' not in os.environ:
+ raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified')
+
+ wg = deepcopy(default_config_data)
+ wg['intf'] = os.environ['VYOS_TAGNODE_VALUE']
+
+ # Check if interface has been removed
+ if not conf.exists(base + [wg['intf']]):
+ wg['deleted'] = True
+ return wg
+
+ conf.set_level(base + [wg['intf']])
+
+ # retrieve configured interface addresses
+ if conf.exists(['address']):
+ wg['address'] = conf.return_values(['address'])
+
+ # get interface addresses (currently effective) - to determine which
+ # address is no longer valid and needs to be removed
+ eff_addr = conf.return_effective_values(['address'])
+ wg['address_remove'] = list_diff(eff_addr, wg['address'])
+
+ # retrieve interface description
+ if conf.exists(['description']):
+ wg['description'] = conf.return_value(['description'])
+
+ # disable interface
+ if conf.exists(['disable']):
+ wg['disable'] = True
+
+ # local port to listen on
+ if conf.exists(['port']):
+ wg['lport'] = conf.return_value(['port'])
+
+ # fwmark value
+ if conf.exists(['fwmark']):
+ wg['fwmark'] = int(conf.return_value(['fwmark']))
+
+ # Maximum Transmission Unit (MTU)
+ if conf.exists('mtu'):
+ wg['mtu'] = int(conf.return_value(['mtu']))
+
+ # retrieve VRF instance
+ if conf.exists('vrf'):
+ wg['vrf'] = conf.return_value('vrf')
+
+ # private key
+ if conf.exists(['private-key']):
+ wg['pk'] = "{0}/{1}/private.key".format(
+ kdir, conf.return_value(['private-key']))
+
+ # peer removal, wg identifies peers by its pubkey
+ peer_eff = conf.list_effective_nodes(['peer'])
+ peer_rem = list_diff(peer_eff, conf.list_nodes(['peer']))
+ for peer in peer_rem:
+ wg['peer_remove'].append(
+ conf.return_effective_value(['peer', peer, 'pubkey']))
+
+ # peer settings
+ if conf.exists(['peer']):
+ for p in conf.list_nodes(['peer']):
+ # set new config level for this peer
+ conf.set_level(base + [wg['intf'], 'peer', p])
+ peer = {
+ 'allowed-ips': [],
+ 'address': '',
+ 'name': p,
+ 'persistent_keepalive': '',
+ 'port': '',
+ 'psk': '',
+ 'pubkey': ''
+ }
+
+ # peer allowed-ips
+ if conf.exists(['allowed-ips']):
+ peer['allowed-ips'] = conf.return_values(['allowed-ips'])
+
+ # peer address
+ if conf.exists(['address']):
+ peer['address'] = conf.return_value(['address'])
+
+ # peer port
+ if conf.exists(['port']):
+ peer['port'] = conf.return_value(['port'])
+
+ # persistent-keepalive
+ if conf.exists(['persistent-keepalive']):
+ peer['persistent_keepalive'] = conf.return_value(['persistent-keepalive'])
+
+ # preshared-key
+ if conf.exists(['preshared-key']):
+ peer['psk'] = conf.return_value(['preshared-key'])
+
+ # peer pubkeys
+ if conf.exists(['pubkey']):
+ key_eff = conf.return_effective_value(['pubkey'])
+ key_cfg = conf.return_value(['pubkey'])
+ peer['pubkey'] = key_cfg
+
+ # on a pubkey change we need to remove the pubkey first
+ # peers are identified by pubkey, so key update means
+ # peer removal and re-add
+ if key_eff != key_cfg and key_eff != None:
+ wg['peer_remove'].append(key_cfg)
+
+ # if a peer is disabled, we have to exec a remove for it's pubkey
+ if conf.exists(['disable']):
+ wg['peer_remove'].append(peer['pubkey'])
+ else:
+ wg['peer'].append(peer)
- dflt_cnf = {
- 'intfc': '',
- 'addr': [],
- 'addr_remove': [],
- 'descr': '',
- 'lport': None,
- 'delete': False,
- 'state': 'up',
- 'fwmark': 0x00,
- 'mtu': 1420,
- 'peer': {},
- 'peer_remove': [],
- 'pk': '{}/default/private.key'.format(kdir)
- }
-
- if os.getenv('VYOS_TAGNODE_VALUE'):
- ifname = str(os.environ['VYOS_TAGNODE_VALUE'])
- wg = deepcopy(dflt_cnf)
- wg['intfc'] = ifname
- wg['descr'] = ifname
- else:
- print("ERROR: VYOS_TAGNODE_VALUE undefined")
- sys.exit(1)
-
- c.set_level(['interfaces', 'wireguard'])
-
- # interface removal state
- if not c.exists(ifname) and c.exists_effective(ifname):
- wg['delete'] = True
-
- if not wg['delete']:
- c.set_level(['interfaces', 'wireguard', ifname])
- if c.exists(['address']):
- wg['addr'] = c.return_values(['address'])
-
- # determine addresses which need to be removed
- eff_addr = c.return_effective_values(['address'])
- wg['addr_remove'] = list_diff(eff_addr, wg['addr'])
-
- # ifalias description
- if c.exists(['description']):
- wg['descr'] = c.return_value(['description'])
-
- # link state
- if c.exists(['disable']):
- wg['state'] = 'down'
-
- # local port to listen on
- if c.exists(['port']):
- wg['lport'] = c.return_value(['port'])
-
- # fwmark value
- if c.exists(['fwmark']):
- wg['fwmark'] = c.return_value(['fwmark'])
-
- # mtu
- if c.exists('mtu'):
- wg['mtu'] = c.return_value('mtu')
-
- # private key
- if c.exists(['private-key']):
- wg['pk'] = "{0}/{1}/private.key".format(
- kdir, c.return_value(['private-key']))
-
- # peer removal, wg identifies peers by its pubkey
- peer_eff = c.list_effective_nodes(['peer'])
- peer_rem = list_diff(peer_eff, c.list_nodes(['peer']))
- for p in peer_rem:
- wg['peer_remove'].append(
- c.return_effective_value(['peer', p, 'pubkey']))
-
- # peer settings
- if c.exists(['peer']):
- for p in c.list_nodes(['peer']):
- if not c.exists(['peer', p, 'disable']):
- wg['peer'].update(
- {
- p: {
- 'allowed-ips': [],
- 'endpoint': '',
- 'pubkey': ''
- }
- }
- )
- # peer allowed-ips
- if c.exists(['peer', p, 'allowed-ips']):
- wg['peer'][p]['allowed-ips'] = c.return_values(
- ['peer', p, 'allowed-ips'])
- # peer endpoint
- if c.exists(['peer', p, 'endpoint']):
- wg['peer'][p]['endpoint'] = c.return_value(
- ['peer', p, 'endpoint'])
- # persistent-keepalive
- if c.exists(['peer', p, 'persistent-keepalive']):
- wg['peer'][p]['persistent-keepalive'] = c.return_value(
- ['peer', p, 'persistent-keepalive'])
- # preshared-key
- if c.exists(['peer', p, 'preshared-key']):
- wg['peer'][p]['psk'] = c.return_value(
- ['peer', p, 'preshared-key'])
- # peer pubkeys
- key_eff = c.return_effective_value(['peer', p, 'pubkey'])
- key_cfg = c.return_value(['peer', p, 'pubkey'])
- wg['peer'][p]['pubkey'] = key_cfg
-
- # on a pubkey change we need to remove the pubkey first
- # peers are identified by pubkey, so key update means
- # peer removal and re-add
- if key_eff != key_cfg and key_eff != None:
- wg['peer_remove'].append(key_cfg)
-
- # if a peer is disabled, we have to exec a remove for it's pubkey
- else:
- peer_key = c.return_value(['peer', p, 'pubkey'])
- wg['peer_remove'].append(peer_key)
return wg
-def verify(c):
- if not c:
- return None
+def verify(wg):
+ interface = wg['intf']
- if not os.path.exists(c['pk']):
- raise ConfigError(
- "No keys found, generate them by executing: \'run generate wireguard [keypair|named-keypairs]\'")
-
- if not c['delete']:
- if not c['addr']:
- raise ConfigError("ERROR: IP address required")
- if not c['peer']:
- raise ConfigError("ERROR: peer required")
- for p in c['peer']:
- if not c['peer'][p]['allowed-ips']:
- raise ConfigError("ERROR: allowed-ips required for peer " + p)
- if not c['peer'][p]['pubkey']:
- raise ConfigError("peer pubkey required for peer " + p)
-
-
-def apply(c):
- # no wg configs left, remove all interface from system
- # maybe move it into ifconfig.py
- if not c:
- net_devs = os.listdir('/sys/class/net/')
- for dev in net_devs:
- if os.path.isdir('/sys/class/net/' + dev):
- 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))
- subprocess.call(
- ['ip l d dev ' + wg_intf + ' >/dev/null'], shell=True)
+ if wg['deleted']:
+ is_member, bridge = is_bridge_member(interface)
+ if is_member:
+ # can not use a f'' formatted-string here as bridge would not get
+ # expanded in the print statement
+ raise ConfigError('Can not delete interface "{0}" as it ' \
+ 'is a member of bridge "{1}"!'.format(interface, bridge))
return None
+ vrf_name = wg['vrf']
+ if vrf_name and vrf_name not in interfaces():
+ raise ConfigError(f'VRF "{vrf_name}" does not exist')
+
+ if not os.path.exists(wg['pk']):
+ raise ConfigError('No keys found, generate them by executing:\n' \
+ '"run generate wireguard [keypair|named-keypairs]"')
+
+ if not wg['address']:
+ raise ConfigError(f'IP address required for interface "{interface}"!')
+
+ if not wg['peer']:
+ raise ConfigError(f'Peer required for interface "{interface}"!')
+
+ # run checks on individual configured WireGuard peer
+ for peer in wg['peer']:
+ peer_name = peer['name']
+ if not peer['allowed-ips']:
+ raise ConfigError(f'Peer allowed-ips required for peer "{peer_name}"!')
+
+ if not peer['pubkey']:
+ raise ConfigError(f'Peer public-key required for peer "{peer_name}"!')
+
+
+def apply(wg):
# init wg class
- intfc = WireGuardIf(c['intfc'])
+ w = WireGuardIf(wg['intf'])
# single interface removal
- if c['delete']:
- intfc.remove()
+ if wg['deleted']:
+ w.remove()
return None
- # remove IP addresses
- for ip in c['addr_remove']:
- intfc.del_addr(ip)
+ # Configure interface address(es)
+ # - not longer required addresses get removed first
+ # - newly addresses will be added second
+ for addr in wg['address_remove']:
+ w.del_addr(addr)
+ for addr in wg['address']:
+ w.add_addr(addr)
- # add IP addresses
- for ip in c['addr']:
- intfc.add_addr(ip)
+ # Maximum Transmission Unit (MTU)
+ w.set_mtu(wg['mtu'])
- # interface mtu
- intfc.set_mtu(int(c['mtu']))
+ # update interface description used e.g. within SNMP
+ w.set_alias(wg['description'])
- # ifalias for snmp from description
- intfc.set_alias(str(c['descr']))
+ # assign/remove VRF
+ w.set_vrf(wg['vrf'])
# remove peers
- if c['peer_remove']:
- for pkey in c['peer_remove']:
- intfc.remove_peer(pkey)
+ for pub_key in wg['peer_remove']:
+ w.remove_peer(pub_key)
# peer pubkey
# setting up the wg interface
- intfc.config['private-key'] = c['pk']
- for p in c['peer']:
+ w.config['private-key'] = c['pk']
+
+ for peer in wg['peer']:
# peer pubkey
- intfc.config['pubkey'] = str(c['peer'][p]['pubkey'])
+ w.config['pubkey'] = peer['pubkey']
# peer allowed-ips
- intfc.config['allowed-ips'] = c['peer'][p]['allowed-ips']
+ w.config['allowed-ips'] = peer['allowed-ips']
# local listen port
- if c['lport']:
- intfc.config['port'] = c['lport']
+ if wg['lport']:
+ w.config['port'] = wg['lport']
# fwmark
if c['fwmark']:
- intfc.config['fwmark'] = c['fwmark']
+ w.config['fwmark'] = wg['fwmark']
+
# endpoint
- if c['peer'][p]['endpoint']:
- intfc.config['endpoint'] = c['peer'][p]['endpoint']
+ if peer['address'] and peer['port']:
+ w.config['endpoint'] = '{}:{}'.format(
+ peer['address'], peer['port'])
# persistent-keepalive
- if 'persistent-keepalive' in c['peer'][p]:
- intfc.config['keepalive'] = c['peer'][p]['persistent-keepalive']
+ if peer['persistent_keepalive']:
+ w.config['keepalive'] = peer['persistent_keepalive']
# maybe move it into ifconfig.py
# preshared-key - needs to be read from a file
- if 'psk' in c['peer'][p]:
+ if peer['psk']:
psk_file = '/config/auth/wireguard/psk'
- old_umask = os.umask(0o077)
- open(psk_file, 'w').write(str(c['peer'][p]['psk']))
- os.umask(old_umask)
- intfc.config['psk'] = psk_file
- intfc.update()
+ with open(psk_file, 'w') as f:
+ f.write(peer['psk'])
+ w.config['psk'] = psk_file
- # interface state
- intfc.set_state(c['state'])
+ w.update()
+
+ # Enable/Disable interface
+ if wg['disable']:
+ w.set_admin_state('down')
+ else:
+ w.set_admin_state('up')
return None
@@ -282,4 +303,4 @@ if __name__ == '__main__':
apply(c)
except ConfigError as e:
print(e)
- sys.exit(1)
+ exit(1)