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')
|
||||
|
||||
network = ip_network(node.metadata['wireguard']['my_ip'], strict=False)
|
||||
|
||||
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',
|
||||
'source': 'wg.netdev',
|
||||
'context': {
|
||||
'network': f'{network.network_address}/{network.prefixlen}',
|
||||
**node.metadata['wireguard'],
|
||||
'endpoint': config.get('endpoint'),
|
||||
'number': number,
|
||||
'peer': peer,
|
||||
'port': config['my_port'],
|
||||
'privatekey': node.metadata.get('wireguard/privatekey'),
|
||||
'psk': config['psk'],
|
||||
'pubkey': config['pubkey'],
|
||||
},
|
||||
'needs': {
|
||||
'pkg_apt:wireguard',
|
||||
|
@ -17,15 +27,4 @@ files = {
|
|||
'triggers': {
|
||||
'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(
|
||||
'wireguard/peers',
|
||||
)
|
||||
def peer_ips_and_endpoints(metadata):
|
||||
def peer_ips_and_ports(metadata):
|
||||
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:
|
||||
rnode = repo.get_node(peer_name)
|
||||
except NoSuchNode:
|
||||
continue
|
||||
|
||||
ips = rnode.metadata.get('wireguard/subnets', set())
|
||||
ips.add(rnode.metadata.get('wireguard/my_ip').split('/')[0])
|
||||
ips = repo.libs.tools.remove_more_specific_subnets(ips)
|
||||
ip_a, ip_b = repo.libs.s2s.get_subnet_for_connection(repo, *sorted({node.name, peer_name}))
|
||||
|
||||
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] = {
|
||||
'endpoint': '{}:51820'.format(rnode.metadata.get('wireguard/external_hostname', rnode.hostname)),
|
||||
'ips': ips,
|
||||
'my_ip': str(my_ip),
|
||||
'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 {
|
||||
|
@ -133,12 +167,12 @@ def peer_ips_and_endpoints(metadata):
|
|||
def icinga2(metadata):
|
||||
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):
|
||||
continue
|
||||
|
||||
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 {
|
||||
|
@ -154,63 +188,33 @@ def icinga2(metadata):
|
|||
'firewall/port_rules',
|
||||
)
|
||||
def firewall(metadata):
|
||||
sources = set(metadata.get('wireguard/restrict-to', set()))
|
||||
for peer_name in metadata.get('wireguard/peers'):
|
||||
ports = {}
|
||||
for name, config in metadata.get('wireguard/peers').items():
|
||||
try:
|
||||
rnode = repo.get_node(peer_name)
|
||||
rnode = repo.get_node(name)
|
||||
except NoSuchNode: # roadwarrior
|
||||
continue
|
||||
ports['{}/udp'.format(config['my_port'])] = atomic(set(metadata.get('wireguard/restrict-to', set())))
|
||||
else:
|
||||
sources.add(peer_name)
|
||||
ports['{}/udp'.format(config['my_port'])] = atomic({name})
|
||||
|
||||
return {
|
||||
'firewall': {
|
||||
'port_rules': {
|
||||
'51820/udp': atomic(sources),
|
||||
},
|
||||
'port_rules': ports,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'interfaces/wg0/ips',
|
||||
'interfaces',
|
||||
)
|
||||
def interface_ips(metadata):
|
||||
return {
|
||||
'interfaces': {
|
||||
'wg0': {
|
||||
interfaces = {}
|
||||
for number, (peer, config) in enumerate(sorted(metadata.get('wireguard/peers', {}).items())):
|
||||
interfaces[f'wg{number}'] = {
|
||||
'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 {
|
||||
'interfaces': {
|
||||
'wg0': {
|
||||
'routes': routes,
|
||||
},
|
||||
},
|
||||
'interfaces': interfaces,
|
||||
}
|
||||
|
|
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