[refactor] cleaning up uncloud_net for Wireguardvpn

This commit is contained in:
Nico Schottelius 2020-12-13 11:38:41 +01:00
parent 074cffcbd7
commit 10d5a72c5a
82 changed files with 403 additions and 2180 deletions

View file

@ -1,9 +1,6 @@
# 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
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
@ -11,23 +8,14 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('uncloud_pay', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='VM',
fields=[
('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)),
('vmid', models.IntegerField(primary_key=True, serialize=False)),
('data', django.contrib.postgres.fields.jsonb.JSONField()),
('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)),
('data', models.JSONField()),
],
options={
'abstract': False,
},
),
]

View file

@ -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'),
('opennebula', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='vm',
name='order',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='uncloud_pay.Order'),
),
]

View file

@ -1,23 +0,0 @@
# Generated by Django 3.1 on 2020-08-08 19:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('opennebula', '0002_auto_20200801_2332'),
]
operations = [
migrations.AlterField(
model_name='vm',
name='data',
field=models.JSONField(),
),
migrations.AlterField(
model_name='vm',
name='extra_data',
field=models.JSONField(blank=True, editable=False, null=True),
),
]

View file

@ -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'),
('opennebula', '0003_auto_20200808_1953'),
]
operations = [
migrations.RemoveField(
model_name='vm',
name='order',
),
migrations.AddField(
model_name='vm',
name='orders',
field=models.ManyToManyField(to='uncloud_pay.Order'),
),
]

View file

@ -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 = [
('opennebula', '0004_auto_20200809_1237'),
]
operations = [
migrations.RemoveField(
model_name='vm',
name='orders',
),
]

View file

@ -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 = [
('opennebula', '0005_remove_vm_orders'),
]
operations = [
migrations.RemoveField(
model_name='vm',
name='extra_data',
),
migrations.RemoveField(
model_name='vm',
name='owner',
),
migrations.RemoveField(
model_name='vm',
name='status',
),
]

File diff suppressed because one or more lines are too long

View file

@ -1,18 +0,0 @@
# Generated by Django 3.1 on 2020-10-11 20:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('uncloud', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='uncloudprovider',
name='ending_date',
field=models.DateField(blank=True, null=True),
),
]

View file

@ -1,27 +0,0 @@
# Generated by Django 3.1 on 2020-10-11 20:09
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('uncloud_net', '0010_auto_20201011_2009'),
('uncloud', '0002_auto_20201011_2001'),
]
operations = [
migrations.AddField(
model_name='uncloudprovider',
name='billing_network',
field=models.ForeignKey(default=0, on_delete=django.db.models.deletion.CASCADE, related_name='uncloudproviderbill', to='uncloud_net.uncloudnetwork'),
preserve_default=False,
),
migrations.AddField(
model_name='uncloudprovider',
name='referral_network',
field=models.ForeignKey(default=0, on_delete=django.db.models.deletion.CASCADE, related_name='uncloudproviderreferral', to='uncloud_net.uncloudnetwork'),
preserve_default=False,
),
]

File diff suppressed because one or more lines are too long

View file

@ -1,21 +0,0 @@
# Generated by Django 3.1 on 2020-10-12 17:32
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('uncloud_net', '0010_auto_20201011_2009'),
('uncloud', '0004_auto_20201011_2031'),
]
operations = [
migrations.AddField(
model_name='uncloudprovider',
name='coupon_network',
field=models.ForeignKey(default=0, on_delete=django.db.models.deletion.CASCADE, related_name='uncloudprovidercoupon', to='uncloud_net.uncloudnetwork'),
preserve_default=False,
),
]

View file

@ -1,40 +0,0 @@
# Generated by Django 3.1 on 2020-10-25 19:31
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('uncloud', '0005_uncloudprovider_coupon_network'),
]
operations = [
migrations.CreateModel(
name='UncloudNetwork',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('network_address', models.GenericIPAddressField(unique=True)),
('network_mask', models.IntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(128)])),
('description', models.CharField(max_length=256)),
],
),
migrations.AlterField(
model_name='uncloudprovider',
name='billing_network',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='uncloudproviderbill', to='uncloud.uncloudnetwork'),
),
migrations.AlterField(
model_name='uncloudprovider',
name='coupon_network',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='uncloudprovidercoupon', to='uncloud.uncloudnetwork'),
),
migrations.AlterField(
model_name='uncloudprovider',
name='referral_network',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='uncloudproviderreferral', to='uncloud.uncloudnetwork'),
),
]
2

View file

@ -42,8 +42,8 @@ router.register(r'v1/service/generic', serviceviews.GenericServiceProductViewSet
# Net
router.register(r'v1/net/vpn', netviews.VPNNetworkViewSet, basename='vpnnetwork')
router.register(r'v1/admin/vpnreservation', netviews.VPNNetworkReservationViewSet, basename='vpnnetreservation')
#router.register(r'v1/net/vpn', netviews.VPNNetworkViewSet, basename='vpnnetwork')
#router.register(r'v1/admin/vpnreservation', netviews.VPNNetworkReservationViewSet, basename='vpnnetreservation')
# Pay
@ -59,7 +59,7 @@ router.register(r'v1/admin/payment', payviews.AdminPaymentViewSet, basename='adm
router.register(r'v1/admin/order', payviews.AdminOrderViewSet, basename='admin/order')
router.register(r'v1/admin/vmhost', vmviews.VMHostViewSet)
router.register(r'v1/admin/vmcluster', vmviews.VMClusterViewSet)
router.register(r'v1/admin/vpnpool', netviews.VPNPoolViewSet)
#router.register(r'v1/admin/vpnpool', netviews.VPNPoolViewSet)
#router.register(r'v1/admin/opennebula', oneviews.VMViewSet, basename='opennebula')
# User/Account
@ -77,7 +77,7 @@ urlpatterns = [
description="uncloud API",
version="1.0.0"
), name='openapi-schema'),
path('vpn/create/', netviews.VPNCreateView.as_view(), name="vpncreate"),
path('vpn/create/', netviews.WireGuardVPNCreateView.as_view(), name="vpncreate"),
path('login/', authviews.LoginView.as_view(), name="login"),
path('logout/', authviews.LogoutView.as_view(), name="logout"),

View file

@ -1,4 +1,4 @@
# Generated by Django 3.0.6 on 2020-08-01 16:38
# Generated by Django 3.1 on 2020-12-13 10:38
import django.contrib.auth.models
import django.contrib.auth.validators
@ -12,7 +12,7 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0011_update_proxy_permissions'),
('auth', '0012_alter_user_first_name_max_length'),
]
operations = [
@ -24,7 +24,7 @@ class Migration(migrations.Migration):
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),

View file

@ -1,18 +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_auth', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='user',
name='first_name',
field=models.CharField(blank=True, max_length=150, verbose_name='first name'),
),
]

View file

@ -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
View 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" ]

View file

@ -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,
},
),
]

View file

@ -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'),
),
]

View file

@ -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),
),
]

View file

@ -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'),
),
]

View file

@ -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',
),
]

View file

@ -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',
),
]

View file

@ -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)])),
],
),
]

View file

@ -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),
),
]

View file

@ -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,
),
]

View file

@ -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',
),
]

View file

@ -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',
),
]

View file

@ -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):

View file

@ -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 ])

View file

@ -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
View 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)

View file

@ -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>

View file

@ -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):