[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…
Reference in a new issue