Merge branch 'master' of code.ungleich.ch:uncloud/uncloud

This commit is contained in:
Nico Schottelius 2020-04-08 12:07:11 +02:00
commit 1838eaf7dd
7 changed files with 139 additions and 88 deletions

View file

@ -9,3 +9,17 @@
** Route a /40 network to its IPv6 address
** Install wireguard on it
** TODO Enable wireguard on boot
** TODO Create a new VPNPool on uncloud with
*** the network address (selecting from our existing pool)
*** the network size (/...)
*** the vpn host that provides the network (selecting the created VM)
*** the wireguard private key of the vpn host (using wg genkey)
*** http command
```
http -a nicoschottelius:$(pass
ungleich.ch/nico.schottelius@ungleich.ch)
http://localhost:8000/admin/vpnpool/ network=2a0a:e5c1:200:: \
network_size=40 subnetwork_size=48
vpn_hostname=vpn-2a0ae5c1200.ungleich.ch
wireguard_private_key=...
```

View file

@ -0,0 +1,44 @@
import sys
from datetime import datetime
from django.core.management.base import BaseCommand
from django.contrib.auth import get_user_model
from opennebula.models import VM as VMModel
from uncloud_vm.models import VMHost, VMProduct, VMNetworkCard, VMDiskImageProduct, VMDiskProduct, VMCluster
import logging
log = logging.getLogger(__name__)
wireguard_template="""
[Interface]
ListenPort = 51820
PrivateKey = {privatekey}
# Nico, 2019-01-23, Switzerland
#[Peer]
#PublicKey = kL1S/Ipq6NkFf1MAsNRou4b9VoUsnnb4ZxgiBrH0zA8=
#AllowedIPs = 2a0a:e5c1:101::/48
"""
class Command(BaseCommand):
help = 'General uncloud commands'
def add_arguments(self, parser):
parser.add_argument('--hostname', action='store_true', help='Name of this VPN Host',
required=True)
def handle(self, *args, **options):
# for net
if options['bootstrap']:
self.bootstrap()
self.create_vpn_config(options['hostname'])
def create_vpn_config(self, hostname):
for pool in VPNPool.objects.filter(vpn_hostname
default_cluster = VPNNetwork.objects.get_or_create(name="default")
# local_host =

View file

@ -1,4 +1,4 @@
# Generated by Django 3.0.5 on 2020-04-03 17:27
# Generated by Django 3.0.5 on 2020-04-06 21:38
from django.conf import settings
import django.contrib.postgres.fields.jsonb
@ -28,22 +28,23 @@ class Migration(migrations.Migration):
name='VPNPool',
fields=[
('extra_data', django.contrib.postgres.fields.jsonb.JSONField(blank=True, editable=False, null=True)),
('network', models.GenericIPAddressField(editable=False, primary_key=True, serialize=False)),
('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='VPNProduct',
name='VPNNetworkReservation',
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)),
('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='PENDING', max_length=32)),
('network', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='uncloud_net.VPNPool')),
('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)),
('address', models.GenericIPAddressField(primary_key=True, serialize=False)),
('vpnpool', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='uncloud_net.VPNPool')),
],
options={
'abstract': False,
@ -53,8 +54,12 @@ class Migration(migrations.Migration):
name='VPNNetwork',
fields=[
('extra_data', django.contrib.postgres.fields.jsonb.JSONField(blank=True, editable=False, null=True)),
('network', models.GenericIPAddressField(editable=False, primary_key=True, serialize=False)),
('vpnpool', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='uncloud_net.VPNPool')),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('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='PENDING', max_length=32)),
('wireguard_public_key', models.CharField(max_length=48)),
('network', models.ForeignKey(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)),
],
options={
'abstract': False,

View file

@ -1,70 +0,0 @@
# Generated by Django 3.0.5 on 2020-04-06 20:21
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('uncloud_pay', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('uncloud_net', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='vpnnetwork',
name='order',
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, to='uncloud_pay.Order'),
),
migrations.AddField(
model_name='vpnnetwork',
name='owner',
field=models.ForeignKey(default=0, editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
preserve_default=False,
),
migrations.AddField(
model_name='vpnnetwork',
name='status',
field=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='PENDING', max_length=32),
),
migrations.AlterField(
model_name='vpnnetwork',
name='network',
field=models.GenericIPAddressField(editable=False, unique=True),
),
migrations.AddField(
model_name='vpnnetwork',
name='uuid',
field=models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False),
),
migrations.AddField(
model_name='vpnnetwork',
name='wireguard_public_key',
field=models.CharField(default='', max_length=48),
preserve_default=False,
),
migrations.AddField(
model_name='vpnpool',
name='vpn_hostname',
field=models.CharField(default='', max_length=256),
preserve_default=False,
),
migrations.AddField(
model_name='vpnpool',
name='wireguard_private_key',
field=models.CharField(default='', max_length=48),
preserve_default=False,
),
migrations.AlterField(
model_name='vpnpool',
name='network',
field=models.GenericIPAddressField(primary_key=True, serialize=False),
),
migrations.DeleteModel(
name='VPNProduct',
),
]

View file

@ -1,3 +1,5 @@
import uuid
from django.db import models
from django.contrib.auth import get_user_model
from django.core.validators import MinValueValidator, MaxValueValidator
@ -15,24 +17,36 @@ class VPNPool(UncloudModel):
Network address pools from which VPNs can be created
"""
network = models.GenericIPAddressField(primary_key=True)
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
network = models.GenericIPAddressField(unique=True)
network_size = models.IntegerField(validators=[MinValueValidator(0),
MaxValueValidator(128)])
subnetwork_size = models.IntegerField(validators=[MinValueValidator(0),
MaxValueValidator(128)])
vpn_hostname = models.CharField(max_length=256)
wireguard_private_key = models.CharField(max_length=48)
class VPNNetwork(Product):
class VPNNetworkReservation(UncloudModel):
"""
A selected network. Used for tracking reservations / used networks
This class tracks the used VPN networks. It will be deleted, when the product is cancelled.
"""
vpnpool = models.ForeignKey(VPNPool,
on_delete=models.CASCADE)
network = models.GenericIPAddressField(editable=False,
unique=True)
address = models.GenericIPAddressField(primary_key=True)
class VPNNetwork(Product):
"""
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)

View file

@ -1,4 +1,7 @@
import base64
from django.contrib.auth import get_user_model
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from .models import *
@ -10,9 +13,48 @@ class VPNPoolSerializer(serializers.ModelSerializer):
class VPNNetworkSerializer(serializers.ModelSerializer):
network_size = serializers.IntegerField(min_value=0,
max_value=128)
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)
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...
"""
print(value)
try:
base64.standard_b64decode(value)
except Exception as e:
raise serializers.ValidationError(msg)
if '\n' in value:
raise serializers.ValidationError(msg)
return value
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 ]
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)
return data
def create(self, validated_data):
from_pool =

View file

@ -1,4 +1,6 @@
from django.shortcuts import render
from rest_framework import viewsets, permissions