[refactor] cleaning up uncloud_net for Wireguardvpn
This commit is contained in:
parent
074cffcbd7
commit
10d5a72c5a
82 changed files with 389 additions and 2166 deletions
|
|
@ -1,6 +1,7 @@
|
|||
from django.contrib import admin
|
||||
from .models import ReverseDNSEntry
|
||||
|
||||
from .models import *
|
||||
|
||||
|
||||
for m in [ ReverseDNSEntry ]:
|
||||
for m in [ ReverseDNSEntry, WireGuardVPNPool, WireGuardVPN ]:
|
||||
admin.site.register(m)
|
||||
|
|
|
|||
12
uncloud_net/forms.py
Normal file
12
uncloud_net/forms.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
from django import forms
|
||||
|
||||
from .models import *
|
||||
from .selectors import *
|
||||
|
||||
class WireGuardVPNForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = WireGuardVPN
|
||||
|
||||
network_size = forms.ChoiceField(choices=allowed_vpn_network_reservation_size)
|
||||
|
||||
fields = [ "wireguard_public_key" ]
|
||||
|
|
@ -1,11 +1,9 @@
|
|||
# Generated by Django 3.0.6 on 2020-08-01 16:38
|
||||
# Generated by Django 3.1 on 2020-12-13 10:38
|
||||
|
||||
from django.conf import settings
|
||||
import django.contrib.postgres.fields.jsonb
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
|
@ -14,7 +12,6 @@ class Migration(migrations.Migration):
|
|||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('uncloud_pay', '__first__'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
|
@ -25,45 +22,31 @@ class Migration(migrations.Migration):
|
|||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='VPNPool',
|
||||
fields=[
|
||||
('extra_data', django.contrib.postgres.fields.jsonb.JSONField(blank=True, editable=False, null=True)),
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('network', models.GenericIPAddressField(unique=True)),
|
||||
('network_size', models.IntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(128)])),
|
||||
('subnetwork_size', models.IntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(128)])),
|
||||
('vpn_hostname', models.CharField(max_length=256)),
|
||||
('wireguard_private_key', models.CharField(max_length=48)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='VPNNetworkReservation',
|
||||
fields=[
|
||||
('extra_data', django.contrib.postgres.fields.jsonb.JSONField(blank=True, editable=False, null=True)),
|
||||
('address', models.GenericIPAddressField(primary_key=True, serialize=False)),
|
||||
('status', models.CharField(choices=[('used', 'used'), ('free', 'free')], default='used', max_length=256)),
|
||||
('vpnpool', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='uncloud_net.VPNPool')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='VPNNetwork',
|
||||
name='WireGuardVPNPool',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('extra_data', django.contrib.postgres.fields.jsonb.JSONField(blank=True, editable=False, null=True)),
|
||||
('status', models.CharField(choices=[('PENDING', 'Pending'), ('AWAITING_PAYMENT', 'Awaiting payment'), ('BEING_CREATED', 'Being created'), ('SCHEDULED', 'Scheduled'), ('ACTIVE', 'Active'), ('MODIFYING', 'Modifying'), ('DELETED', 'Deleted'), ('DISABLED', 'Disabled'), ('UNUSABLE', 'Unusable')], default='AWAITING_PAYMENT', max_length=32)),
|
||||
('wireguard_public_key', models.CharField(max_length=48)),
|
||||
('network', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='uncloud_net.VPNNetworkReservation')),
|
||||
('order', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, to='uncloud_pay.Order')),
|
||||
('owner', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
('network', models.GenericIPAddressField(unique=True)),
|
||||
('network_mask', models.IntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(128)])),
|
||||
('subnetwork_mask', models.IntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(128)])),
|
||||
('vpn_server_hostname', models.CharField(max_length=256)),
|
||||
('wireguard_private_key', models.CharField(max_length=48)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='WireGuardVPN',
|
||||
fields=[
|
||||
('address', models.GenericIPAddressField(primary_key=True, serialize=False)),
|
||||
('wireguard_public_key', models.CharField(max_length=48)),
|
||||
('vpnpool', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='uncloud_net.wireguardvpnpool')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ReverseDNSEntry',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('ip_address', models.GenericIPAddressField(unique=True)),
|
||||
('name', models.CharField(max_length=253)),
|
||||
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
# Generated by Django 3.0.8 on 2020-08-01 23:32
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('uncloud_pay', '0003_auto_20200801_2332'),
|
||||
('uncloud_net', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='vpnnetwork',
|
||||
name='order',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='uncloud_pay.Order'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
# Generated by Django 3.1 on 2020-08-08 19:53
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('uncloud_net', '0002_auto_20200801_2332'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='vpnnetwork',
|
||||
name='extra_data',
|
||||
field=models.JSONField(blank=True, editable=False, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='vpnnetworkreservation',
|
||||
name='extra_data',
|
||||
field=models.JSONField(blank=True, editable=False, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='vpnpool',
|
||||
name='extra_data',
|
||||
field=models.JSONField(blank=True, editable=False, null=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
# Generated by Django 3.1 on 2020-08-09 12:37
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('uncloud_pay', '0013_auto_20200809_1237'),
|
||||
('uncloud_net', '0003_auto_20200808_1953'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='vpnnetwork',
|
||||
name='order',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='vpnnetwork',
|
||||
name='orders',
|
||||
field=models.ManyToManyField(to='uncloud_pay.Order'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
# Generated by Django 3.1 on 2020-09-28 18:44
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('uncloud_net', '0004_auto_20200809_1237'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='vpnnetwork',
|
||||
name='orders',
|
||||
),
|
||||
]
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
# Generated by Django 3.1 on 2020-09-28 18:58
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('uncloud_net', '0005_remove_vpnnetwork_orders'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='vpnnetwork',
|
||||
name='extra_data',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='vpnnetwork',
|
||||
name='owner',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='vpnnetwork',
|
||||
name='status',
|
||||
),
|
||||
]
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
# Generated by Django 3.1 on 2020-10-11 19:20
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('uncloud_net', '0006_auto_20200928_1858'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='UncloudNetwork',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('network_address', models.GenericIPAddressField()),
|
||||
('network_mask', models.IntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(128)])),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
# Generated by Django 3.1 on 2020-10-11 19:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('uncloud_net', '0007_uncloudnetwork'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='uncloudnetwork',
|
||||
name='network_address',
|
||||
field=models.GenericIPAddressField(unique=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
# Generated by Django 3.1 on 2020-10-11 19:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('uncloud_net', '0008_auto_20201011_1924'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='uncloudnetwork',
|
||||
name='description',
|
||||
field=models.CharField(default='', max_length=256),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
# Generated by Django 3.1 on 2020-10-11 20:09
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('uncloud_net', '0009_uncloudnetwork_description'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='vpnnetworkreservation',
|
||||
name='extra_data',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='vpnpool',
|
||||
name='extra_data',
|
||||
),
|
||||
]
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
# Generated by Django 3.1 on 2020-10-25 19:31
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('uncloud', '0006_auto_20201025_1931'),
|
||||
('uncloud_net', '0010_auto_20201011_2009'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ReverseDNSEntry',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('ip_address', models.GenericIPAddressField(unique=True)),
|
||||
('name', models.CharField(max_length=253)),
|
||||
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='UncloudNetwork',
|
||||
),
|
||||
]
|
||||
|
|
@ -8,180 +8,39 @@ from django.core.exceptions import FieldError, ValidationError
|
|||
|
||||
from uncloud_pay.models import Order
|
||||
|
||||
class MACAdress(models.Model):
|
||||
default_prefix = 0x420000000000
|
||||
|
||||
class VPNPool(models.Model):
|
||||
class WireGuardVPNPool(models.Model):
|
||||
"""
|
||||
Network address pools from which VPNs can be created
|
||||
"""
|
||||
|
||||
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
|
||||
network = models.GenericIPAddressField(unique=True)
|
||||
network_size = models.IntegerField(validators=[MinValueValidator(0),
|
||||
network_mask = models.IntegerField(validators=[MinValueValidator(0),
|
||||
MaxValueValidator(128)])
|
||||
|
||||
subnetwork_size = models.IntegerField(validators=[
|
||||
MinValueValidator(0),
|
||||
MaxValueValidator(128)
|
||||
])
|
||||
|
||||
vpn_hostname = models.CharField(max_length=256)
|
||||
subnetwork_mask = models.IntegerField(validators=[
|
||||
MinValueValidator(0),
|
||||
MaxValueValidator(128)
|
||||
])
|
||||
|
||||
vpn_server_hostname = models.CharField(max_length=256)
|
||||
wireguard_private_key = models.CharField(max_length=48)
|
||||
|
||||
@property
|
||||
def num_maximum_networks(self):
|
||||
"""
|
||||
sample:
|
||||
network_size = 40
|
||||
subnetwork_size = 48
|
||||
maximum_networks = 2^(48-40)
|
||||
|
||||
2nd sample:
|
||||
network_size = 8
|
||||
subnetwork_size = 24
|
||||
maximum_networks = 2^(24-8)
|
||||
"""
|
||||
|
||||
return 2**(self.subnetwork_size - self.network_size)
|
||||
|
||||
@property
|
||||
def used_networks(self):
|
||||
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):
|
||||
return len(self.used_networks)
|
||||
|
||||
@property
|
||||
def num_free_networks(self):
|
||||
return self.num_maximum_networks - self.num_used_networks + len(self.free_networks)
|
||||
|
||||
@property
|
||||
def next_free_network(self):
|
||||
if self.num_free_networks == 0:
|
||||
# FIXME: use right exception
|
||||
raise Exception("No free networks")
|
||||
|
||||
if len(self.free_networks) > 0:
|
||||
return self.free_networks[0].address
|
||||
|
||||
if len(self.used_networks) > 0:
|
||||
"""
|
||||
sample:
|
||||
|
||||
pool = 2a0a:e5c1:200::/40
|
||||
last_used = 2a0a:e5c1:204::/48
|
||||
|
||||
next:
|
||||
"""
|
||||
|
||||
last_net = ipaddress.ip_network(self.used_networks.last().address)
|
||||
last_net_ip = last_net[0]
|
||||
|
||||
if last_net_ip.version == 6:
|
||||
offset_to_next = 2**(128 - self.subnetwork_size)
|
||||
elif last_net_ip.version == 4:
|
||||
offset_to_next = 2**(32 - self.subnetwork_size)
|
||||
|
||||
next_net_ip = last_net_ip + offset_to_next
|
||||
|
||||
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 reservation in self.vpnnetworkreservation_set.filter(status='used'):
|
||||
public_key = reservation.vpnnetwork_set.first().wireguard_public_key
|
||||
peer_network = "{}/{}".format(reservation.address, self.subnetwork_size)
|
||||
owner = reservation.vpnnetwork_set.first().owner
|
||||
|
||||
peers.append("""
|
||||
# Owner: {owner}
|
||||
[Peer]
|
||||
PublicKey = {public_key}
|
||||
AllowedIPs = {peer_network}
|
||||
""".format(
|
||||
owner=owner,
|
||||
public_key=public_key,
|
||||
peer_network=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
|
||||
|
||||
|
||||
class VPNNetworkReservation(models.Model):
|
||||
class WireGuardVPN(models.Model):
|
||||
"""
|
||||
This class tracks the used VPN networks. It will be deleted, when the product is cancelled.
|
||||
"""
|
||||
vpnpool = models.ForeignKey(VPNPool,
|
||||
Created VPNNetworks
|
||||
"""
|
||||
vpnpool = models.ForeignKey(WireGuardVPNPool,
|
||||
on_delete=models.CASCADE)
|
||||
|
||||
address = models.GenericIPAddressField(primary_key=True)
|
||||
|
||||
status = models.CharField(max_length=256,
|
||||
default='used',
|
||||
choices = (
|
||||
('used', 'used'),
|
||||
('free', 'free')
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class VPNNetwork(models.Model):
|
||||
"""
|
||||
A selected network. Used for tracking reservations / used networks
|
||||
"""
|
||||
network = models.ForeignKey(VPNNetworkReservation,
|
||||
on_delete=models.CASCADE,
|
||||
editable=False)
|
||||
|
||||
wireguard_public_key = models.CharField(max_length=48)
|
||||
|
||||
@property
|
||||
def recurring_price(self):
|
||||
return 120
|
||||
|
||||
################################################################################
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
self.network.status = 'free'
|
||||
self.network.save()
|
||||
super().save(*args, **kwargs)
|
||||
print("deleted {}".format(self))
|
||||
|
||||
class MACAdress(models.Model):
|
||||
default_prefix = 0x420000000000
|
||||
|
||||
|
||||
class ReverseDNSEntry(models.Model):
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
from django.db.models import Count
|
||||
from django.db import transaction
|
||||
from django.db.models import Count, F
|
||||
|
||||
|
||||
from .models import *
|
||||
|
||||
@transaction.atomic
|
||||
def get_suitable_pool(subnetwork_size):
|
||||
"""
|
||||
Find suitable pools for a certain network size.
|
||||
|
|
@ -21,3 +23,20 @@ def get_suitable_pool(subnetwork_size):
|
|||
max_reservations=2**(F('subnetwork_size')-F('network_size'))).filter(
|
||||
num_reservations__lt=F('max_reservations'),
|
||||
subnetwork_size=subnetwork_size)
|
||||
|
||||
|
||||
def allowed_vpn_network_reservation_size():
|
||||
"""
|
||||
Find all possible sizes of subnetworks that are available.
|
||||
|
||||
Select all pools with free networks.
|
||||
|
||||
Get their subnetwork sizes, reduce to a set
|
||||
|
||||
"""
|
||||
|
||||
pools = VPNPool.objects.annotate(num_reservations=Count('vpnnetworkreservation'),
|
||||
max_reservations=2**(F('subnetwork_size')-F('network_size'))).filter(
|
||||
num_reservations__lt=F('max_reservations'))
|
||||
|
||||
return set([ pool.subnetwork_size for pool in pools ])
|
||||
|
|
|
|||
|
|
@ -6,95 +6,95 @@ from rest_framework import serializers
|
|||
|
||||
from .models import *
|
||||
|
||||
class VPNPoolSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = VPNPool
|
||||
fields = '__all__'
|
||||
# class WireGuardVPNPoolSerializer(serializers.ModelSerializer):
|
||||
# class Meta:
|
||||
# model = WireGuardVPNPool
|
||||
# fields = '__all__'
|
||||
|
||||
class VPNNetworkReservationSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = VPNNetworkReservation
|
||||
fields = '__all__'
|
||||
# class WireGuardVPNSerializer(serializers.ModelSerializer):
|
||||
# class Meta:
|
||||
# model = VPNNetworkReservation
|
||||
# fields = '__all__'
|
||||
|
||||
|
||||
class VPNNetworkSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = VPNNetwork
|
||||
fields = '__all__'
|
||||
# class VPNNetworkSerializer(serializers.ModelSerializer):
|
||||
# class Meta:
|
||||
# model = VPNNetwork
|
||||
# fields = '__all__'
|
||||
|
||||
# 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,
|
||||
write_only=True)
|
||||
# # 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,
|
||||
# write_only=True)
|
||||
|
||||
def validate_wireguard_public_key(self, value):
|
||||
msg = _("Supplied key is not a valid wireguard public key")
|
||||
# def validate_wireguard_public_key(self, value):
|
||||
# msg = _("Supplied key is not a valid wireguard public key")
|
||||
|
||||
""" FIXME: verify that this does not create broken wireguard config files,
|
||||
i.e. contains \n or similar!
|
||||
We might even need to be more strict to not break wireguard...
|
||||
"""
|
||||
# """ FIXME: verify that this does not create broken wireguard config files,
|
||||
# i.e. contains \n or similar!
|
||||
# We might even need to be more strict to not break wireguard...
|
||||
# """
|
||||
|
||||
try:
|
||||
base64.standard_b64decode(value)
|
||||
except Exception as e:
|
||||
raise serializers.ValidationError(msg)
|
||||
# try:
|
||||
# base64.standard_b64decode(value)
|
||||
# except Exception as e:
|
||||
# raise serializers.ValidationError(msg)
|
||||
|
||||
if '\n' in value:
|
||||
raise serializers.ValidationError(msg)
|
||||
# if '\n' in value:
|
||||
# raise serializers.ValidationError(msg)
|
||||
|
||||
return value
|
||||
# return value
|
||||
|
||||
def validate(self, data):
|
||||
# def validate(self, data):
|
||||
|
||||
# FIXME: filter for status = active or similar
|
||||
all_pools = VPNPool.objects.all()
|
||||
sizes = [ p.subnetwork_size for p in all_pools ]
|
||||
# # FIXME: filter for status = active or similar
|
||||
# all_pools = VPNPool.objects.all()
|
||||
# sizes = [ p.subnetwork_size for p in all_pools ]
|
||||
|
||||
pools = VPNPool.objects.filter(subnetwork_size=data['network_size'])
|
||||
# pools = VPNPool.objects.filter(subnetwork_size=data['network_size'])
|
||||
|
||||
if len(pools) == 0:
|
||||
msg = _("No pool available for networks with size = {}. Available are: {}".format(data['network_size'], sizes))
|
||||
raise serializers.ValidationError(msg)
|
||||
# if len(pools) == 0:
|
||||
# msg = _("No pool available for networks with size = {}. Available are: {}".format(data['network_size'], sizes))
|
||||
# raise serializers.ValidationError(msg)
|
||||
|
||||
return data
|
||||
# return data
|
||||
|
||||
def create(self, validated_data):
|
||||
"""
|
||||
Creating a new vpnnetwork - there are a couple of race conditions,
|
||||
especially when run in parallel.
|
||||
# def create(self, validated_data):
|
||||
# """
|
||||
# Creating a new vpnnetwork - there are a couple of race conditions,
|
||||
# especially when run in parallel.
|
||||
|
||||
What we should be doing:
|
||||
# What we should be doing:
|
||||
|
||||
- create a reservation race free
|
||||
- map the reservation to a network (?)
|
||||
"""
|
||||
# - create a reservation race free
|
||||
# - map the reservation to a network (?)
|
||||
# """
|
||||
|
||||
pools = VPNPool.objects.filter(subnetwork_size=validated_data['network_size'])
|
||||
# pools = VPNPool.objects.filter(subnetwork_size=validated_data['network_size'])
|
||||
|
||||
vpn_network = None
|
||||
# vpn_network = None
|
||||
|
||||
for pool in pools:
|
||||
if pool.num_free_networks > 0:
|
||||
next_address = pool.next_free_network
|
||||
# for pool in pools:
|
||||
# if pool.num_free_networks > 0:
|
||||
# next_address = pool.next_free_network
|
||||
|
||||
reservation, created = VPNNetworkReservation.objects.update_or_create(
|
||||
vpnpool=pool, address=next_address,
|
||||
defaults = {
|
||||
'status': 'used'
|
||||
})
|
||||
# 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']
|
||||
)
|
||||
# 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")
|
||||
# break
|
||||
# if not vpn_network:
|
||||
# # FIXME: use correct exception
|
||||
# raise Exception("Did not find any free pool")
|
||||
|
||||
|
||||
return vpn_network
|
||||
# return vpn_network
|
||||
|
|
|
|||
118
uncloud_net/services.py
Normal file
118
uncloud_net/services.py
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
from django.db import transaction
|
||||
from .models import *
|
||||
|
||||
@transaction.atomic
|
||||
def create_vpn(*,
|
||||
public_key: str,
|
||||
network_size: int
|
||||
) -> VPNNetwork:
|
||||
|
||||
# Select suitable pool
|
||||
pools = VPNPool.objects.filter(subnetwork_size=network_size)
|
||||
|
||||
# FIXME: exception - which?
|
||||
if not pools:
|
||||
return None
|
||||
|
||||
|
||||
|
||||
# Find all pools with the correct size
|
||||
|
||||
# For each pool see if it has still space:
|
||||
# num network reversations < 2**(subnetwork_size-network_size)
|
||||
|
||||
|
||||
def next_free_network(self):
|
||||
if self.num_free_networks == 0:
|
||||
# FIXME: use right exception
|
||||
raise Exception("No free networks")
|
||||
|
||||
if len(self.free_networks) > 0:
|
||||
return self.free_networks[0].address
|
||||
|
||||
if len(self.used_networks) > 0:
|
||||
"""
|
||||
sample:
|
||||
|
||||
pool = 2a0a:e5c1:200::/40
|
||||
last_used = 2a0a:e5c1:204::/48
|
||||
|
||||
next:
|
||||
"""
|
||||
|
||||
last_net = ipaddress.ip_network(self.used_networks.last().address)
|
||||
last_net_ip = last_net[0]
|
||||
|
||||
if last_net_ip.version == 6:
|
||||
offset_to_next = 2**(128 - self.subnetwork_size)
|
||||
elif last_net_ip.version == 4:
|
||||
offset_to_next = 2**(32 - self.subnetwork_size)
|
||||
|
||||
next_net_ip = last_net_ip + offset_to_next
|
||||
|
||||
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 reservation in self.vpnnetworkreservation_set.filter(status='used'):
|
||||
public_key = reservation.vpnnetwork_set.first().wireguard_public_key
|
||||
peer_network = "{}/{}".format(reservation.address, self.subnetwork_size)
|
||||
owner = reservation.vpnnetwork_set.first().owner
|
||||
|
||||
peers.append("""
|
||||
# Owner: {owner}
|
||||
[Peer]
|
||||
PublicKey = {public_key}
|
||||
AllowedIPs = {peer_network}
|
||||
""".format(
|
||||
owner=owner,
|
||||
public_key=public_key,
|
||||
peer_network=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
|
||||
|
||||
|
||||
|
||||
def num_maximum_networks(self):
|
||||
"""
|
||||
sample:
|
||||
network_size = 40
|
||||
subnetwork_size = 48
|
||||
maximum_networks = 2^(48-40)
|
||||
|
||||
2nd sample:
|
||||
network_size = 8
|
||||
subnetwork_size = 24
|
||||
maximum_networks = 2^(24-8)
|
||||
"""
|
||||
|
||||
return 2**(self.subnetwork_mask - self.network_mask)
|
||||
|
|
@ -5,19 +5,9 @@
|
|||
<div class="row">
|
||||
<div class="col">
|
||||
<h1>
|
||||
<h1>Generate new prefix</h1>
|
||||
<h1>Create a VPN Network</h1>
|
||||
<p>
|
||||
A new random prefix will be generated for you.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
All ULA prefixes are /48 networks. Simply add the first IP address
|
||||
(without any netmask, for instance fd23:2323:2323::).
|
||||
|
||||
You can choose the name of your liking and an organization name.
|
||||
</p>
|
||||
<p>
|
||||
ULA prefixes are always subnets of the fd00::/8 network.
|
||||
Create a new wireguard based VPN network.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,45 +9,41 @@ from rest_framework import viewsets, permissions
|
|||
|
||||
from .models import *
|
||||
from .serializers import *
|
||||
from .selectors import *
|
||||
from .forms import *
|
||||
|
||||
# class VPNPoolViewSet(viewsets.ModelViewSet):
|
||||
# serializer_class = VPNPoolSerializer
|
||||
# permission_classes = [permissions.IsAdminUser]
|
||||
# queryset = VPNPool.objects.all()
|
||||
|
||||
# class VPNNetworkReservationViewSet(viewsets.ModelViewSet):
|
||||
# serializer_class = VPNNetworkReservationSerializer
|
||||
# permission_classes = [permissions.IsAdminUser]
|
||||
# queryset = VPNNetworkReservation.objects.all()
|
||||
|
||||
|
||||
class VPNPoolViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = VPNPoolSerializer
|
||||
permission_classes = [permissions.IsAdminUser]
|
||||
queryset = VPNPool.objects.all()
|
||||
# class VPNNetworkViewSet(viewsets.ModelViewSet):
|
||||
# serializer_class = VPNNetworkSerializer
|
||||
# permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
class VPNNetworkReservationViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = VPNNetworkReservationSerializer
|
||||
permission_classes = [permissions.IsAdminUser]
|
||||
queryset = VPNNetworkReservation.objects.all()
|
||||
# def get_queryset(self):
|
||||
# if self.request.user.is_superuser:
|
||||
# obj = VPNNetwork.objects.all()
|
||||
# else:
|
||||
# obj = VPNNetwork.objects.filter(owner=self.request.user)
|
||||
|
||||
# return obj
|
||||
|
||||
|
||||
class VPNNetworkViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = VPNNetworkSerializer
|
||||
# permission_classes = [permissions.IsAdminUser]
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_superuser:
|
||||
obj = VPNNetwork.objects.all()
|
||||
else:
|
||||
obj = VPNNetwork.objects.filter(owner=self.request.user)
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
|
||||
|
||||
class VPNCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
model = VPNNetwork
|
||||
class WireGuardVPNCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
model = WireGuardVPN
|
||||
|
||||
login_url = '/login/'
|
||||
success_url = '/'
|
||||
success_message = "%(the_prefix)s/48 was created successfully"
|
||||
success_message = "%(network) was created successfully"
|
||||
|
||||
gen_method = "undef"
|
||||
|
||||
fields = [ "wireguard_public_key" ]
|
||||
form_class = WireGuardVPNForm
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
return self.success_message % dict(cleaned_data,
|
||||
|
|
@ -55,5 +51,5 @@ class VPNCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['method'] = self.gen_method
|
||||
context['available_sizes'] = 2
|
||||
return context
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue