bundles/wireguard: use one wireguard connection per peer instead of one for all
This commit is contained in:
parent
8110ec508e
commit
902840ee7f
5 changed files with 110 additions and 94 deletions
17
bundles/wireguard/files/wg.netdev
Normal file
17
bundles/wireguard/files/wg.netdev
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
[NetDev]
|
||||||
|
Name=wg${number}
|
||||||
|
Kind=wireguard
|
||||||
|
Description=WireGuard connection to ${peer}
|
||||||
|
|
||||||
|
[WireGuard]
|
||||||
|
PrivateKey=${privatekey}
|
||||||
|
ListenPort=${port}
|
||||||
|
|
||||||
|
[WireGuardPeer]
|
||||||
|
PublicKey=${pubkey}
|
||||||
|
AllowedIPs=0.0.0.0/0
|
||||||
|
PresharedKey=${psk}
|
||||||
|
% if endpoint:
|
||||||
|
Endpoint=${endpoint}
|
||||||
|
% endif
|
||||||
|
PersistentKeepalive=30
|
|
@ -1,25 +0,0 @@
|
||||||
[NetDev]
|
|
||||||
Name=wg0
|
|
||||||
Kind=wireguard
|
|
||||||
Description=WireGuard server
|
|
||||||
|
|
||||||
[WireGuard]
|
|
||||||
PrivateKey=${privatekey}
|
|
||||||
ListenPort=51820
|
|
||||||
|
|
||||||
% for peer, config in sorted(peers.items()):
|
|
||||||
# Peer ${peer}
|
|
||||||
[WireGuardPeer]
|
|
||||||
PublicKey=${config['pubkey']}
|
|
||||||
% if len(peers) == 1: # FIXME
|
|
||||||
AllowedIPs=${network}
|
|
||||||
% else:
|
|
||||||
AllowedIPs=${','.join(sorted(config['ips']))}
|
|
||||||
% endif
|
|
||||||
PresharedKey=${config['psk']}
|
|
||||||
% if 'endpoint' in config:
|
|
||||||
Endpoint=${config['endpoint']}
|
|
||||||
% endif
|
|
||||||
PersistentKeepalive=30
|
|
||||||
|
|
||||||
% endfor
|
|
|
@ -2,14 +2,24 @@ from ipaddress import ip_network
|
||||||
|
|
||||||
repo.libs.tools.require_bundle(node, 'systemd-networkd')
|
repo.libs.tools.require_bundle(node, 'systemd-networkd')
|
||||||
|
|
||||||
network = ip_network(node.metadata['wireguard']['my_ip'], strict=False)
|
|
||||||
|
|
||||||
files = {
|
files = {
|
||||||
'/etc/systemd/network/wg0.netdev': {
|
'/usr/local/share/icinga/plugins/check_wireguard_connected': {
|
||||||
|
'mode': '0755',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for number, (peer, config) in enumerate(sorted(node.metadata.get('wireguard/peers', {}).items())):
|
||||||
|
files[f'/etc/systemd/network/wg{number}.netdev'] = {
|
||||||
'content_type': 'mako',
|
'content_type': 'mako',
|
||||||
|
'source': 'wg.netdev',
|
||||||
'context': {
|
'context': {
|
||||||
'network': f'{network.network_address}/{network.prefixlen}',
|
'endpoint': config.get('endpoint'),
|
||||||
**node.metadata['wireguard'],
|
'number': number,
|
||||||
|
'peer': peer,
|
||||||
|
'port': config['my_port'],
|
||||||
|
'privatekey': node.metadata.get('wireguard/privatekey'),
|
||||||
|
'psk': config['psk'],
|
||||||
|
'pubkey': config['pubkey'],
|
||||||
},
|
},
|
||||||
'needs': {
|
'needs': {
|
||||||
'pkg_apt:wireguard',
|
'pkg_apt:wireguard',
|
||||||
|
@ -17,15 +27,4 @@ files = {
|
||||||
'triggers': {
|
'triggers': {
|
||||||
'svc_systemd:systemd-networkd:restart',
|
'svc_systemd:systemd-networkd:restart',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
'/usr/local/share/icinga/plugins/check_wireguard_connected': {
|
|
||||||
'mode': '0755',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if node.has_bundle('pppd'):
|
|
||||||
files['/etc/ppp/ip-up.d/reconnect-wireguard'] = {
|
|
||||||
'source': 'pppd-ip-up',
|
|
||||||
'content_type': 'mako',
|
|
||||||
'mode': '0755',
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,22 +102,56 @@ def peer_pubkeys(metadata):
|
||||||
@metadata_reactor.provides(
|
@metadata_reactor.provides(
|
||||||
'wireguard/peers',
|
'wireguard/peers',
|
||||||
)
|
)
|
||||||
def peer_ips_and_endpoints(metadata):
|
def peer_ips_and_ports(metadata):
|
||||||
peers = {}
|
peers = {}
|
||||||
|
base_port = 51820
|
||||||
|
|
||||||
for peer_name in metadata.get('wireguard/peers', {}):
|
for number, peer_name in enumerate(sorted(metadata.get('wireguard/peers', {}).keys())):
|
||||||
try:
|
try:
|
||||||
rnode = repo.get_node(peer_name)
|
rnode = repo.get_node(peer_name)
|
||||||
except NoSuchNode:
|
except NoSuchNode:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
ips = rnode.metadata.get('wireguard/subnets', set())
|
ip_a, ip_b = repo.libs.s2s.get_subnet_for_connection(repo, *sorted({node.name, peer_name}))
|
||||||
ips.add(rnode.metadata.get('wireguard/my_ip').split('/')[0])
|
|
||||||
ips = repo.libs.tools.remove_more_specific_subnets(ips)
|
if peer_name < node.name:
|
||||||
|
my_ip = ip_a
|
||||||
|
their_ip = ip_b
|
||||||
|
else:
|
||||||
|
my_ip = ip_b
|
||||||
|
their_ip = ip_a
|
||||||
|
|
||||||
peers[rnode.name] = {
|
peers[rnode.name] = {
|
||||||
'endpoint': '{}:51820'.format(rnode.metadata.get('wireguard/external_hostname', rnode.hostname)),
|
'my_ip': str(my_ip),
|
||||||
'ips': ips,
|
'my_port': base_port + number,
|
||||||
|
'their_ip': str(their_ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'wireguard': {
|
||||||
|
'peers': peers,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@metadata_reactor.provides(
|
||||||
|
'wireguard/peers',
|
||||||
|
)
|
||||||
|
def peer_endpoints(metadata):
|
||||||
|
peers = {}
|
||||||
|
|
||||||
|
for name, config in metadata.get('wireguard/peers', {}).items():
|
||||||
|
try:
|
||||||
|
rnode = repo.get_node(name)
|
||||||
|
except NoSuchNode:
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
peers[rnode.name] = {
|
||||||
|
'endpoint': '{}:{}'.format(
|
||||||
|
rnode.metadata.get('wireguard/external_hostname', rnode.hostname),
|
||||||
|
rnode.metadata.get(f'wireguard/peers/{node.name}/my_port', 51820),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -133,12 +167,12 @@ def peer_ips_and_endpoints(metadata):
|
||||||
def icinga2(metadata):
|
def icinga2(metadata):
|
||||||
services = {}
|
services = {}
|
||||||
|
|
||||||
for peer, config in metadata.get('wireguard/peers', {}).items():
|
for number, (peer, config) in enumerate(sorted(metadata.get('wireguard/peers', {}).items())):
|
||||||
if config.get('exclude_from_monitoring', False):
|
if config.get('exclude_from_monitoring', False):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
services[f'WIREGUARD CONNECTION {peer}'] = {
|
services[f'WIREGUARD CONNECTION {peer}'] = {
|
||||||
'command_on_monitored_host': config['pubkey'].format_into('sudo /usr/local/share/icinga/plugins/check_wireguard_connected wg0 {}'),
|
'command_on_monitored_host': config['pubkey'].format_into(f'sudo /usr/local/share/icinga/plugins/check_wireguard_connected wg{number} {{}}'),
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -154,63 +188,33 @@ def icinga2(metadata):
|
||||||
'firewall/port_rules',
|
'firewall/port_rules',
|
||||||
)
|
)
|
||||||
def firewall(metadata):
|
def firewall(metadata):
|
||||||
sources = set(metadata.get('wireguard/restrict-to', set()))
|
ports = {}
|
||||||
for peer_name in metadata.get('wireguard/peers'):
|
for name, config in metadata.get('wireguard/peers').items():
|
||||||
try:
|
try:
|
||||||
rnode = repo.get_node(peer_name)
|
rnode = repo.get_node(name)
|
||||||
except NoSuchNode: # roadwarrior
|
except NoSuchNode: # roadwarrior
|
||||||
continue
|
ports['{}/udp'.format(config['my_port'])] = atomic(set(metadata.get('wireguard/restrict-to', set())))
|
||||||
else:
|
else:
|
||||||
sources.add(peer_name)
|
ports['{}/udp'.format(config['my_port'])] = atomic({name})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'firewall': {
|
'firewall': {
|
||||||
'port_rules': {
|
'port_rules': ports,
|
||||||
'51820/udp': atomic(sources),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
@metadata_reactor.provides(
|
||||||
'interfaces/wg0/ips',
|
'interfaces',
|
||||||
)
|
)
|
||||||
def interface_ips(metadata):
|
def interface_ips(metadata):
|
||||||
return {
|
interfaces = {}
|
||||||
'interfaces': {
|
for number, (peer, config) in enumerate(sorted(metadata.get('wireguard/peers', {}).items())):
|
||||||
'wg0': {
|
interfaces[f'wg{number}'] = {
|
||||||
'ips': {
|
'ips': {
|
||||||
metadata.get('wireguard/my_ip'),
|
'{}/31'.format(config['my_ip']),
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
|
||||||
'interfaces/wg0/routes',
|
|
||||||
)
|
|
||||||
def routes(metadata):
|
|
||||||
network = ip_network(metadata.get('wireguard/my_ip'), strict=False)
|
|
||||||
ips = {
|
|
||||||
f'{network.network_address}/{network.prefixlen}',
|
|
||||||
}
|
|
||||||
routes = {}
|
|
||||||
|
|
||||||
for _, peer_config in metadata.get('wireguard/peers', {}).items():
|
|
||||||
for ip in peer_config['ips']:
|
|
||||||
ips.add(ip)
|
|
||||||
|
|
||||||
if '0.0.0.0/0' in ips:
|
|
||||||
ips.remove('0.0.0.0/0')
|
|
||||||
|
|
||||||
for ip in repo.libs.tools.remove_more_specific_subnets(ips):
|
|
||||||
routes[ip] = {}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'interfaces': {
|
'interfaces': interfaces,
|
||||||
'wg0': {
|
|
||||||
'routes': routes,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
21
libs/s2s.py
Normal file
21
libs/s2s.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
from ipaddress import IPv4Network
|
||||||
|
|
||||||
|
AS_NUMBERS = {
|
||||||
|
# 4290xxxxxx
|
||||||
|
'home': 4290000138,
|
||||||
|
'htz-cloud': 4290000137,
|
||||||
|
'ovh': 4290000001,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_subnet_for_connection(repo, peer_a, peer_b):
|
||||||
|
# XXX this assumes there are never more than 128 nodes which match that expression
|
||||||
|
nodes = sorted({node.name for node in repo.nodes if node.has_bundle('wireguard')})
|
||||||
|
|
||||||
|
assert peer_a in nodes
|
||||||
|
assert peer_b in nodes
|
||||||
|
|
||||||
|
pos_peer_a = nodes.index(peer_a)
|
||||||
|
pos_peer_b = nodes.index(peer_b)
|
||||||
|
|
||||||
|
vpn_subnet = list(IPv4Network('169.254.0.0/16').subnets(new_prefix=24))[pos_peer_a]
|
||||||
|
return list(IPv4Network(vpn_subnet).subnets(new_prefix=31))[pos_peer_b]
|
Loading…
Reference in a new issue