From 6e423c24fb7240af11613a47e72c6ef197afd44b Mon Sep 17 00:00:00 2001 From: Franziska Kunsmann Date: Thu, 1 Apr 2021 16:27:31 +0200 Subject: [PATCH] bundles/wireguard: rework metadata.py --- bundles/wireguard/files/wg0.network | 8 -- bundles/wireguard/items.py | 21 ++-- bundles/wireguard/metadata.py | 147 ++++++++++++++++++++++------ libs/tools.py | 23 +++++ nodes/home/router.py | 12 +-- nodes/ovh/icinga2.py | 2 +- nodes/ovh/wireguard.py | 6 +- 7 files changed, 155 insertions(+), 64 deletions(-) delete mode 100644 bundles/wireguard/files/wg0.network diff --git a/bundles/wireguard/files/wg0.network b/bundles/wireguard/files/wg0.network deleted file mode 100644 index b9d37a1..0000000 --- a/bundles/wireguard/files/wg0.network +++ /dev/null @@ -1,8 +0,0 @@ -[Match] -Name=wg0 - -[Network] -Address=${my_ip} - -[Route] -Destination=${network} diff --git a/bundles/wireguard/items.py b/bundles/wireguard/items.py index 24b8ea7..65451c6 100644 --- a/bundles/wireguard/items.py +++ b/bundles/wireguard/items.py @@ -1,21 +1,16 @@ +from ipaddress import ip_network + assert node.has_bundle('systemd-networkd') +network = ip_network(node.metadata['wireguard']['my_ip'], strict=False) + files = { - '/etc/systemd/network/99-wg0.netdev': { - 'source': 'wg0.netdev', + '/etc/systemd/network/wg0.netdev': { 'content_type': 'mako', - 'context': node.metadata['wireguard'], - 'needs': { - 'pkg_apt:wireguard', + 'context': { + 'network': f'{network.network_address}/{network.prefixlen}', + **node.metadata['wireguard'], }, - 'triggers': { - 'svc_systemd:systemd-networkd:restart', - }, - }, - '/etc/systemd/network/99-wg0.network': { - 'source': 'wg0.network', - 'content_type': 'mako', - 'context': node.metadata['wireguard'], 'needs': { 'pkg_apt:wireguard', }, diff --git a/bundles/wireguard/metadata.py b/bundles/wireguard/metadata.py index d861865..3a59f3c 100644 --- a/bundles/wireguard/metadata.py +++ b/bundles/wireguard/metadata.py @@ -1,5 +1,9 @@ +from ipaddress import ip_network + +from bundlewrap.exceptions import NoSuchNode from bundlewrap.metadata import atomic + defaults = { 'apt': { 'packages': { @@ -16,10 +20,10 @@ defaults = { }, 'iptables': { 'bundle_rules': { - 'wireguard': [ + 'wireguard': { 'iptables_both -A FORWARD -i wg0 -j ACCEPT', 'iptables_both -A FORWARD -o wg0 -j ACCEPT', - ], + }, }, }, 'wireguard': { @@ -29,51 +33,77 @@ defaults = { @metadata_reactor.provides( - 'wireguard/network', + 'wireguard/peers', ) -def get_wireguard_network_from_server(metadata): - # FIXME This will break if more than one node sets 'wireguard/network' - for rnode in repo.nodes: - if not rnode.has_bundle('wireguard'): - continue +def peer_psks(metadata): + peers = {} - if node.name in rnode.metadata.get('wireguard/peers', {}).keys(): - network = rnode.metadata.get('wireguard/network', None) + for peer_name in metadata.get('wireguard/peers', {}): + peers[peer_name] = {} - if network: - return { - 'wireguard': { - 'network': network, - }, - } + 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 {} + return { + 'wireguard': { + 'peers': peers, + }, + } @metadata_reactor.provides( 'wireguard/peers', ) -def get_my_wireguard_peers(metadata): +def peer_pubkeys(metadata): peers = {} - for rnode in repo.nodes: - if not rnode.has_bundle('wireguard'): + for peer_name in metadata.get('wireguard/peers', {}): + try: + rnode = repo.get_node(peer_name) + except NoSuchNode: continue - if node.name in rnode.metadata.get('wireguard/peers', {}).keys(): - peers[rnode.name] = { - 'pubkey': repo.libs.keys.get_pubkey_from_privkey(repo, f'{node.name} wireguard {rnode.name}', rnode.metadata.get('wireguard/privatekey')), - 'psk': rnode.metadata.get('wireguard/psk', metadata.get('wireguard/psk', None)), - } + peers[peer_name] = { + 'pubkey': repo.libs.keys.get_pubkey_from_privkey( + repo, + f'{rnode.name} wireguard pubkey', + rnode.metadata.get('wireguard/privatekey'), + ), + } - if not rnode.metadata.get(f'wireguard/peers/{node.name}/do_not_initiate_a_connection_from_your_side', False): - peers[rnode.name]['endpoint'] = f'{rnode.hostname}:51820' + return { + 'wireguard': { + 'peers': peers, + }, + } - peers[rnode.name]['ips'] = rnode.metadata.get('wireguard/subnets', set()) - your_ip = rnode.metadata.get('wireguard/my_ip', None) - if your_ip: - peers[rnode.name]['ips'].add(your_ip) +@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': { @@ -109,10 +139,63 @@ def icinga2(metadata): '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(metadata.get('wireguard/restrict-to', set(metadata.get('wireguard/peers', {}).keys()))), + '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, }, }, } diff --git a/libs/tools.py b/libs/tools.py index 0f60c36..3050bb3 100644 --- a/libs/tools.py +++ b/libs/tools.py @@ -55,3 +55,26 @@ def resolve_identifier(repo, identifier): ip_dict['ipv6'].add(ip) return ip_dict + + +def remove_more_specific_subnets(input_subnets) -> list: + final_subnets = [] + + for subnet in sorted(input_subnets): + source = ip_network(subnet) + + if not source in final_subnets: + subnet_found = False + + for dest_subnet in final_subnets: + if source.subnet_of(dest_subnet): + subnet_found = True + + if not subnet_found: + final_subnets.append(source) + + out = [] + for net in final_subnets: + out.append(str(net)) + + return out diff --git a/nodes/home/router.py b/nodes/home/router.py index 775723c..79e9e71 100644 --- a/nodes/home/router.py +++ b/nodes/home/router.py @@ -172,17 +172,15 @@ nodes['home.router'] = { }, }, 'wireguard': { - # TODO autogenerate? - 'my_ip': '172.19.136.2/32', + 'external_hostname': 'franzi-home.kunbox.net', # Set via DynDNS + 'my_ip': '172.19.136.2/22', + 'peers': { + 'ovh.wireguard': {}, + }, 'subnets': { '172.19.138.0/24', '172.19.139.0/24', }, - 'peers': { - 'ovh.wireguard': { - 'do_not_initiate_a_connection_from_your_side': True, - }, - }, }, }, } diff --git a/nodes/ovh/icinga2.py b/nodes/ovh/icinga2.py index eba560e..f47f2bf 100644 --- a/nodes/ovh/icinga2.py +++ b/nodes/ovh/icinga2.py @@ -113,7 +113,7 @@ nodes['ovh.icinga2'] = { 'service_filter': '"checks_with_sms" in service.groups' }, 'wireguard': { - 'my_ip': '172.19.136.3/32', + 'my_ip': '172.19.136.3/22', 'peers': { 'ovh.wireguard': {}, }, diff --git a/nodes/ovh/wireguard.py b/nodes/ovh/wireguard.py index 2d6efba..041d3b0 100644 --- a/nodes/ovh/wireguard.py +++ b/nodes/ovh/wireguard.py @@ -24,10 +24,10 @@ nodes['ovh.wireguard'] = { 'ram': 2, }, 'wireguard': { - 'network': '172.19.136.0/22', - 'my_ip': '172.19.136.1/32', - 'psk': vault.random_bytes_as_base64_for('ovh.icinga2 wireguard psk'), + 'my_ip': '172.19.136.1/22', 'peers': { + 'ovh.icinga2': {}, + 'home.router': {}, 'kunsi-oneplus3': { 'ips': { '172.19.136.100/32',