feature: a VPN user has WireGuard peers, each a device with its public key and IPs, kept in the directory and written into wg0.conf by setup.
Release / release (push) Successful in 13s

This commit is contained in:
slawek committed 2026-10-26 02:33:58 +01:00
1 parent 015fba50ca
commit f1d9c3bc36
8 files changed
+226 -33

No files matched your search

@@ -38,6 +38,9 @@ SITE_TO_SITE = SWANCTL_CONF_D / 'site-to-site.conf'
ROAD_WARRIOR = SWANCTL_CONF_D / 'road-warrior.conf'
P2S_FORWARDER = pathlib.Path('/etc/systemd/resolved.conf.d/p2s-forwarder.conf')
WG_CONF = pathlib.Path('/etc/wireguard/wg0.conf')
#: The lines around the [Peer] sections of the users' WireGuard peers in WG_CONF, which setup writes from the directory.
WG_PEERS_BEGIN = "# BEGIN WireGuard peers of the users of the router's directory"
WG_PEERS_END = "# END WireGuard peers of the users of the router's directory"
OPENVPN_CONF = pathlib.Path('/etc/openvpn/server/vpn-router.conf')
OPENVPN_LDAP = pathlib.Path('/etc/openvpn/server/vpn-router-ldap.conf')
@@ -887,6 +890,8 @@ def build_context(cfg, public_ip=None):
'wg_enabled': _get(cfg, 'wireguard', 'enabled'),
'wg_address': _get(cfg, 'wireguard', 'address'),
'wg_listen_port': _get(cfg, 'wireguard', 'listen_port'),
'wg_peers_begin': WG_PEERS_BEGIN,
'wg_peers_end': WG_PEERS_END,
'openvpn_enabled': _get(cfg, 'openvpn', 'enabled'),
'openvpn_pool': openvpn_pool,
'openvpn_network': str(openvpn_network.network_address) if openvpn_network else '',
@@ -981,27 +986,38 @@ def render(env, ctx, template_name, dest, mode):
def existing_peers(path):
"""Return the [Peer] sections of an existing WireGuard config.
"""The block of the users' peers and the [Peer] sections added by hand of an existing WireGuard config, each ''
without one.
Peers are added by the operator by hand, so re-rendering the [Interface]
section must not discard them.
The block lies between WG_PEERS_BEGIN and WG_PEERS_END; the [Peer] sections outside it are the operator's, which
re-rendering the file keeps.
"""
path = pathlib.Path(path)
if not path.is_file():
return ''
lines = path.read_text(encoding='utf-8').splitlines(keepends=True)
return '', ''
text = path.read_text(encoding='utf-8')
block = ''
begin, end = text.find(WG_PEERS_BEGIN), text.find(WG_PEERS_END)
if begin != -1 and end > begin:
block = text[begin:end + len(WG_PEERS_END)]
text = text[:begin] + text[end + len(WG_PEERS_END):]
lines = text.splitlines(keepends=True)
for index, line in enumerate(lines):
if line.strip().lower() == '[peer]':
return ''.join(lines[index:])
return ''
return block, ''.join(lines[index:])
return block, ''
def render_wg_conf(env, ctx, dest=WG_CONF, mode=0o600):
"""Render wg0.conf, carrying any operator-defined peers across."""
content = env.get_template('wg0.conf.j2').render(ctx)
peers = existing_peers(dest)
if peers:
content = content.rstrip('\n') + '\n\n' + peers
"""Render wg0.conf: the [Interface], the block of the users' peers of ctx's wg_peers, the block the file holds while
wg_peers is None, and the peers the operator added by hand, carried across."""
content = env.get_template('wg0.conf.j2').render(ctx).rstrip('\n') + '\n'
block, own = existing_peers(dest)
if ctx['wg_peers'] is not None:
block = env.get_template('wg0-peers.conf.j2').render(ctx).strip('\n') if ctx['wg_peers'] else ''
for part in (block, own):
if part.strip():
content += '\n' + part.strip('\n') + '\n'
dest = pathlib.Path(dest)
if dest.exists() and dest.read_text(encoding='utf-8') == content:
@@ -1070,6 +1086,13 @@ def configure(cfg=None, public_ip=None):
notices = [f'the PKI is left as it is: the directory cannot be reached: {exc}']
for notice in notices:
warn(notice)
# The WireGuard peers of the users; None keeps those of wg0.conf while the directory cannot be reached.
ctx['wg_peers'] = None
if wg_ready(ctx):
try:
ctx['wg_peers'] = vpnrouter_ldap.wireguard_peers()
except ldap.LDAPError as exc:
warn(f'the WireGuard peers of the users are left as they are: the directory cannot be reached: {exc}')
# What needs the router's own certificates.
ctx['server_certificate'] = vpnrouter_pki.VPN_SERVER_CERT.is_file()
for connection in ctx['connections']: