from ipaddress import ip_network 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', ], }, }, }, 'iptables': { 'bundle_rules': { 'wireguard': [ 'iptables_both -A FORWARD -i wg0 -j ACCEPT', 'iptables_both -A FORWARD -o wg0 -j ACCEPT', ], }, }, 'wireguard': { 'privatekey': repo.libs.keys.gen_privkey(repo, f'{node.name} wireguard privatekey'), }, } @metadata_reactor.provides( 'wireguard/peers', ) def peer_psks(metadata): peers = {} for peer_name in metadata.get('wireguard/peers', {}): peers[peer_name] = {} 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_endpoints(metadata): peers = {} for peer_name in metadata.get('wireguard/peers', {}): 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) peers[rnode.name] = { 'endpoint': '{}:51820'.format(rnode.metadata.get('wireguard/external_hostname', rnode.hostname)), 'ips': ips, } return { 'wireguard': { 'peers': peers, }, } @metadata_reactor.provides( 'icinga2_api/wireguard/services', ) def icinga2(metadata): services = {} for peer, config in 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 {}'), } return { 'icinga2_api': { 'wireguard': { 'services': services, }, }, } @metadata_reactor.provides( 'iptables/port_rules', ) def iptables(metadata): sources = set(metadata.get('wireguard/restrict-to', set())) for peer_name in metadata.get('wireguard/peers'): try: rnode = repo.get_node(peer_name) except NoSuchNode: # roadwarrior continue else: sources.add(peer_name) return { 'iptables': { 'port_rules': { '51820/udp': atomic(sources), }, }, } @metadata_reactor.provides( 'interfaces/wg0/ips', ) def interface_ips(metadata): return { 'interfaces': { 'wg0': { 'ips': { metadata.get('wireguard/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, }, }, }