From 85b4d70592d1ba56d0d9d3a1d4750f5bcf7eacab Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Sun, 12 Apr 2020 15:40:39 +0200 Subject: [PATCH] [vpn] make a vpn creat-able! [15:40] line:~% http -a nicoschottelius:$(pass ungleich.ch/nico.schottelius@ungleich.ch) http://localhost:8000/net/vpn/ network_size=48 wireguard_public_key=$(wg genkey | wg pubkey) HTTP/1.1 201 Created Allow: GET, POST, HEAD, OPTIONS Content-Length: 206 Content-Type: application/json Date: Sun, 12 Apr 2020 13:40:26 GMT Server: WSGIServer/0.2 CPython/3.7.3 Vary: Accept X-Content-Type-Options: nosniff X-Frame-Options: DENY { "extra_data": null, "network": "2a0a:e5c1:203::", "order": null, "owner": 30, "status": "PENDING", "uuid": "8f977a8f-e06a-4346-94ae-8f525df58b7b", "wireguard_public_key": "JvCuUTZHm9unasJkGsLKN0Bf/hu6ZSIv7dnIGPyJ6xA=" } --- .../uncloud/doc/README-vpn.org | 9 +++ .../uncloud_net/management/commands/vpn.py | 20 ----- .../uncloud/uncloud_net/models.py | 79 ++++++++++++++----- .../uncloud/uncloud_net/serializers.py | 42 +++++++--- 4 files changed, 100 insertions(+), 50 deletions(-) diff --git a/uncloud_django_based/uncloud/doc/README-vpn.org b/uncloud_django_based/uncloud/doc/README-vpn.org index e7255d8..7d041cb 100644 --- a/uncloud_django_based/uncloud/doc/README-vpn.org +++ b/uncloud_django_based/uncloud/doc/README-vpn.org @@ -23,3 +23,12 @@ http -a nicoschottelius:$(pass vpn_hostname=vpn-2a0ae5c1200.ungleich.ch wireguard_private_key=... ``` +* Example http commands / REST calls +** creating a new vpn pool + http -a nicoschottelius:$(pass + ungleich.ch/nico.schottelius@ungleich.ch) + http://localhost:8000/admin/vpnpool/ network_size=40 + subnetwork_size=48 network=2a0a:e5c1:200:: + vpn_hostname=vpn-2a0ae5c1200.ungleich.ch wireguard_private_key=$(wg + genkey) +** Creating a new vpn network diff --git a/uncloud_django_based/uncloud/uncloud_net/management/commands/vpn.py b/uncloud_django_based/uncloud/uncloud_net/management/commands/vpn.py index 6d717b8..9fdc80d 100644 --- a/uncloud_django_based/uncloud/uncloud_net/management/commands/vpn.py +++ b/uncloud_django_based/uncloud/uncloud_net/management/commands/vpn.py @@ -12,11 +12,6 @@ import logging log = logging.getLogger(__name__) -wireguard_template=""" -[Interface] -ListenPort = 51820 -PrivateKey = {privatekey} -""" peer_template=""" # {username} @@ -44,21 +39,6 @@ class Command(BaseCommand): configs = [] for pool in VPNPool.objects.filter(vpn_hostname=hostname): - pool_config = { - 'private_key': pool.wireguard_private_key, - 'subnetwork_size': pool.subnetwork_size, - 'config_file': '/etc/wireguard/{}.conf'.format(pool.network), - 'peers': [] - } - - for vpnnetwork in VPNNetworkReservation.objects.filter(vpnpool=pool): - pool_config['peers'].append({ - 'vpnnetwork': "{}/{}".format(vpnnetwork.address, - pool_config['subnetwork_size']), - 'public_key': vpnnetwork.wireguard_public_key, - } - ) - configs.append(pool_config) print(configs) diff --git a/uncloud_django_based/uncloud/uncloud_net/models.py b/uncloud_django_based/uncloud/uncloud_net/models.py index ba7adfc..940606b 100644 --- a/uncloud_django_based/uncloud/uncloud_net/models.py +++ b/uncloud_django_based/uncloud/uncloud_net/models.py @@ -47,11 +47,15 @@ class VPNPool(UncloudModel): maximum_networks = 2^(24-8) """ - return 2**(subnetwork_size - network_size) + return 2**(self.subnetwork_size - self.network_size) @property def used_networks(self): - return self.vpnnetworkreservation_set.objects.filter(vpnpool=self, status='used') + return self.vpnnetworkreservation_set.filter(vpnpool=self, status='used') + + @property + def free_networks(self): + return self.vpnnetworkreservation_set.filter(vpnpool=self, status='free') @property def num_used_networks(self): @@ -59,27 +63,18 @@ class VPNPool(UncloudModel): @property def num_free_networks(self): - return self.num_maximum_networks - self.num_used_networks + return self.num_maximum_networks - self.num_used_networks + len(self.free_networks) @property def next_free_network(self): - free_net = self.vpnnetworkreservation_set.objects.filter(vpnpool=self, - status='free') - - used_net = self.vpnnetworkreservation_set.objects.filter(vpnpool=self, - status='used') - - this_net = ipaddress.ip_network("{}/{}".format( - self.network, self.network_size)) - - - if num_free_networks == 0: + if self.num_free_networks == 0: + # FIXME: use right exception raise Exception("No free networks") - if len(free_net) > 0: - return free_net[0].address + if len(self.free_networks) > 0: + return self.free_networks[0].address - if len(used_net) > 0: + if len(self.used_networks) > 0: """ sample: @@ -89,7 +84,7 @@ class VPNPool(UncloudModel): next: """ - last_net = ipaddress.ip_network(self.used_networks.last()) + last_net = ipaddress.ip_network(self.used_networks.last().address) last_net_ip = last_net[0] if last_net_ip.version == 6: @@ -99,7 +94,52 @@ class VPNPool(UncloudModel): next_net_ip = last_net_ip + offset_to_next - return next_net_ip + return str(next_net_ip) + else: + # first network to be created + return self.network + + @property + def wireguard_config_filename(self): + return '/etc/wireguard/{}.conf'.format(self.network) + + @property + def wireguard_config(self): + wireguard_config = [ + """ +[Interface] +ListenPort = 51820 +PrivateKey = {privatekey} +""".format(privatekey=self.wireguard_private_key) ] + + peers = [] + + for vpnnetwork in self.vpnnetworkreservation_set: + public_key = vpnnetwork.wireguard_public_key + peer_network = "{}/{}".format(vpnnetwork.address, self.subnetwork_size) + + peers.append(""" +[Peer] +PublicKey = {public_key} +AllowedIPs = {peer_network} +""") + + wireguard_config.extend(peers) + + return "\n".join(wireguard_config) + + + def configure_wireguard_vpnserver(self): + """ + This method is designed to run as a celery task and should + not be called directly from the web + """ + + # subprocess, ssh + + pass + + @@ -109,6 +149,7 @@ class VPNNetworkReservation(UncloudModel): """ vpnpool = models.ForeignKey(VPNPool, on_delete=models.CASCADE) + address = models.GenericIPAddressField(primary_key=True) status = models.CharField(max_length=256, diff --git a/uncloud_django_based/uncloud/uncloud_net/serializers.py b/uncloud_django_based/uncloud/uncloud_net/serializers.py index 7c7b4a2..e1c4d79 100644 --- a/uncloud_django_based/uncloud/uncloud_net/serializers.py +++ b/uncloud_django_based/uncloud/uncloud_net/serializers.py @@ -19,7 +19,8 @@ class VPNNetworkSerializer(serializers.ModelSerializer): # This is required for finding the VPN pool, but does not # exist in the model network_size = serializers.IntegerField(min_value=0, - max_value=128) + max_value=128, + write_only=True) def validate_wireguard_public_key(self, value): msg = _("Supplied key is not a valid wireguard public key") @@ -58,18 +59,37 @@ class VPNNetworkSerializer(serializers.ModelSerializer): """ Creating a new vpnnetwork - there are a couple of race conditions, especially when run in parallel. - """ - pools = VPNPool.objects.filter(subnetwork_size=data['network_size']) - found_pool = False + What we should be doing: + + - create a reservation race free + - map the reservation to a network (?) + """ + + pools = VPNPool.objects.filter(subnetwork_size=validated_data['network_size']) + + vpn_network = None + for pool in pools: if pool.num_free_networks > 0: - found_pool = True -# address = pool. -# reservation = VPNNetworkReservation(vpnpool=pool, + next_address = pool.next_free_network + + reservation, created = VPNNetworkReservation.objects.update_or_create( + vpnpool=pool, address=next_address, + defaults = { + 'status': 'used' + }) + + vpn_network = VPNNetwork.objects.create( + owner=self.context['request'].user, + network=reservation, + wireguard_public_key=validated_data['wireguard_public_key'] + ) + + break + if not vpn_network: + # FIXME: use correct exception + raise Exception("Did not find any free pool") - pool = VPNPool.objects.first(subnetwork_size=data['network_size']) - - - return VPNNetwork(**validated_data) + return vpn_network