[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=" }
This commit is contained in:
		
					parent
					
						
							
								b55254b9b1
							
						
					
				
			
			
				commit
				
					
						85b4d70592
					
				
			
		
					 4 changed files with 100 additions and 50 deletions
				
			
		| 
						 | 
					@ -23,3 +23,12 @@ http -a nicoschottelius:$(pass
 | 
				
			||||||
  vpn_hostname=vpn-2a0ae5c1200.ungleich.ch
 | 
					  vpn_hostname=vpn-2a0ae5c1200.ungleich.ch
 | 
				
			||||||
  wireguard_private_key=...
 | 
					  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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,11 +12,6 @@ import logging
 | 
				
			||||||
log = logging.getLogger(__name__)
 | 
					log = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
wireguard_template="""
 | 
					 | 
				
			||||||
[Interface]
 | 
					 | 
				
			||||||
ListenPort = 51820
 | 
					 | 
				
			||||||
PrivateKey = {privatekey}
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
peer_template="""
 | 
					peer_template="""
 | 
				
			||||||
# {username}
 | 
					# {username}
 | 
				
			||||||
| 
						 | 
					@ -44,21 +39,6 @@ class Command(BaseCommand):
 | 
				
			||||||
        configs = []
 | 
					        configs = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for pool in VPNPool.objects.filter(vpn_hostname=hostname):
 | 
					        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)
 | 
					            configs.append(pool_config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        print(configs)
 | 
					        print(configs)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -47,11 +47,15 @@ class VPNPool(UncloudModel):
 | 
				
			||||||
        maximum_networks = 2^(24-8)
 | 
					        maximum_networks = 2^(24-8)
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return 2**(subnetwork_size - network_size)
 | 
					        return 2**(self.subnetwork_size - self.network_size)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def used_networks(self):
 | 
					    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
 | 
					    @property
 | 
				
			||||||
    def num_used_networks(self):
 | 
					    def num_used_networks(self):
 | 
				
			||||||
| 
						 | 
					@ -59,27 +63,18 @@ class VPNPool(UncloudModel):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def num_free_networks(self):
 | 
					    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
 | 
					    @property
 | 
				
			||||||
    def next_free_network(self):
 | 
					    def next_free_network(self):
 | 
				
			||||||
        free_net = self.vpnnetworkreservation_set.objects.filter(vpnpool=self,
 | 
					        if self.num_free_networks == 0:
 | 
				
			||||||
                                                                 status='free')
 | 
					            # FIXME: use right exception
 | 
				
			||||||
 | 
					 | 
				
			||||||
        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:
 | 
					 | 
				
			||||||
            raise Exception("No free networks")
 | 
					            raise Exception("No free networks")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if len(free_net) > 0:
 | 
					        if len(self.free_networks) > 0:
 | 
				
			||||||
            return free_net[0].address
 | 
					            return self.free_networks[0].address
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if len(used_net) > 0:
 | 
					        if len(self.used_networks) > 0:
 | 
				
			||||||
            """
 | 
					            """
 | 
				
			||||||
            sample:
 | 
					            sample:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -89,7 +84,7 @@ class VPNPool(UncloudModel):
 | 
				
			||||||
            next:
 | 
					            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]
 | 
					            last_net_ip = last_net[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if last_net_ip.version == 6:
 | 
					            if last_net_ip.version == 6:
 | 
				
			||||||
| 
						 | 
					@ -99,7 +94,52 @@ class VPNPool(UncloudModel):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            next_net_ip = last_net_ip + offset_to_next
 | 
					            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,
 | 
					    vpnpool = models.ForeignKey(VPNPool,
 | 
				
			||||||
                                 on_delete=models.CASCADE)
 | 
					                                 on_delete=models.CASCADE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    address = models.GenericIPAddressField(primary_key=True)
 | 
					    address = models.GenericIPAddressField(primary_key=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    status = models.CharField(max_length=256,
 | 
					    status = models.CharField(max_length=256,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,7 +19,8 @@ class VPNNetworkSerializer(serializers.ModelSerializer):
 | 
				
			||||||
    # This is required for finding the VPN pool, but does not
 | 
					    # This is required for finding the VPN pool, but does not
 | 
				
			||||||
    # exist in the model
 | 
					    # exist in the model
 | 
				
			||||||
    network_size = serializers.IntegerField(min_value=0,
 | 
					    network_size = serializers.IntegerField(min_value=0,
 | 
				
			||||||
                                            max_value=128)
 | 
					                                            max_value=128,
 | 
				
			||||||
 | 
					                                            write_only=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def validate_wireguard_public_key(self, value):
 | 
					    def validate_wireguard_public_key(self, value):
 | 
				
			||||||
        msg = _("Supplied key is not a valid wireguard public key")
 | 
					        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,
 | 
					        Creating a new vpnnetwork - there are a couple of race conditions,
 | 
				
			||||||
        especially when run in parallel.
 | 
					        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:
 | 
					        for pool in pools:
 | 
				
			||||||
            if pool.num_free_networks > 0:
 | 
					            if pool.num_free_networks > 0:
 | 
				
			||||||
                found_pool = True
 | 
					                next_address = pool.next_free_network
 | 
				
			||||||
#                address = pool.
 | 
					
 | 
				
			||||||
#                reservation = VPNNetworkReservation(vpnpool=pool,
 | 
					                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 vpn_network
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return VPNNetwork(**validated_data)
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue