bundlewrap/bundles/wireguard/metadata.py

240 lines
5.9 KiB
Python

from ipaddress import ip_network
from re import sub
from bundlewrap.exceptions import NoSuchNode
from bundlewrap.metadata import atomic
defaults = {
'apt': {
'packages': {
'wireguard': {},
},
'repos': {
'backports': {
'install_gpg_key': False, # default debian signing key
'items': {
'deb http://deb.debian.org/debian {os_release}-backports main',
},
},
},
},
'wireguard': {
'privatekey': repo.libs.keys.gen_privkey(repo, f'{node.name} wireguard privatekey'),
},
}
if node.has_bundle('telegraf'):
defaults['telegraf'] = {
'input_plugins': {
'builtin': {
'wireguard': [{}],
},
},
'additional_capabilities': {
'CAP_NET_ADMIN',
},
}
@metadata_reactor.provides(
'wireguard/peers',
)
def peer_psks_and_iface_names(metadata):
peers = {}
for peer_name in metadata.get('wireguard/peers', {}):
peers[peer_name] = {
'iface': sub('[^a-z0-9-_]+', '_', peer_name)[:12],
}
if node.name < peer_name:
peers[peer_name]['psk'] = repo.vault.random_bytes_as_base64_for(f'{node.name} wireguard {peer_name}')
else:
peers[peer_name]['psk'] = repo.vault.random_bytes_as_base64_for(f'{peer_name} wireguard {node.name}')
return {
'wireguard': {
'peers': peers,
},
}
@metadata_reactor.provides(
'wireguard/peers',
)
def peer_pubkeys(metadata):
peers = {}
for peer_name in metadata.get('wireguard/peers', {}):
try:
rnode = repo.get_node(peer_name)
except NoSuchNode:
continue
peers[peer_name] = {
'pubkey': repo.libs.keys.get_pubkey_from_privkey(
repo,
f'{rnode.name} wireguard pubkey',
rnode.metadata.get('wireguard/privatekey'),
),
}
return {
'wireguard': {
'peers': peers,
},
}
@metadata_reactor.provides(
'wireguard/peers',
)
def peer_ips_and_ports(metadata):
peers = {}
base_port = 51820
for number, peer_name in enumerate(sorted(metadata.get('wireguard/peers', {}).keys())):
try:
rnode = repo.get_node(peer_name)
except NoSuchNode:
continue
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] = {
'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 {
'wireguard': {
'peers': peers,
},
}
@metadata_reactor.provides(
'icinga2_api/wireguard/services',
)
def icinga2(metadata):
services = {}
for peer, config in 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(f'sudo /usr/local/share/icinga/plugins/check_wireguard_connected wg_{config["iface"]} {{}}'),
}
return {
'icinga2_api': {
'wireguard': {
'services': services,
},
},
}
@metadata_reactor.provides(
'firewall/port_rules',
)
def firewall(metadata):
ports = {}
for name, config in metadata.get('wireguard/peers').items():
try:
rnode = repo.get_node(name)
except NoSuchNode: # roadwarrior
ports['{}/udp'.format(config['my_port'])] = atomic(set(metadata.get('wireguard/restrict-to', set())))
else:
ports['{}/udp'.format(config['my_port'])] = atomic({name})
return {
'firewall': {
'port_rules': ports,
},
}
@metadata_reactor.provides(
'interfaces',
)
def interface_ips(metadata):
interfaces = {}
for peer, config in sorted(metadata.get('wireguard/peers', {}).items()):
if '/' in config['my_ip']:
my_ip = config['my_ip']
else:
my_ip = '{}/31'.format(config['my_ip'])
interfaces[f'wg_{config["iface"]}'] = {
'ips': {
my_ip,
},
}
return {
'interfaces': interfaces,
}
@metadata_reactor.provides(
'nftables/rules/10-wireguard',
)
def snat(metadata):
if not node.has_bundle('nftables') or node.os == 'arch':
raise DoNotRunAgain
rules = set()
for peer, config in sorted(metadata.get('wireguard/peers', {}).items()):
rules.add(f'inet filter forward iifname wg_{config["iface"]} accept')
rules.add(f'inet filter forward oifname wg_{config["iface"]} accept')
if 'snat_to' in config:
rules.add('nat postrouting ip saddr {} ip daddr != {} snat to {}'.format(
config['my_ip'],
config['their_ip'],
config['snat_to'],
))
return {
'nftables': {
'rules': {
'10-wireguard': sorted(rules),
},
},
}