Merge branch 'master' of code.ungleich.ch:uncloud/uncloud
This commit is contained in:
commit
1838eaf7dd
7 changed files with 139 additions and 88 deletions
|
@ -9,3 +9,17 @@
|
||||||
** Route a /40 network to its IPv6 address
|
** Route a /40 network to its IPv6 address
|
||||||
** Install wireguard on it
|
** Install wireguard on it
|
||||||
** TODO Enable wireguard on boot
|
** 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=...
|
||||||
|
```
|
||||||
|
|
|
@ -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 =
|
|
@ -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
|
from django.conf import settings
|
||||||
import django.contrib.postgres.fields.jsonb
|
import django.contrib.postgres.fields.jsonb
|
||||||
|
@ -28,22 +28,23 @@ class Migration(migrations.Migration):
|
||||||
name='VPNPool',
|
name='VPNPool',
|
||||||
fields=[
|
fields=[
|
||||||
('extra_data', django.contrib.postgres.fields.jsonb.JSONField(blank=True, editable=False, null=True)),
|
('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)])),
|
('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={
|
options={
|
||||||
'abstract': False,
|
'abstract': False,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='VPNProduct',
|
name='VPNNetworkReservation',
|
||||||
fields=[
|
fields=[
|
||||||
('extra_data', django.contrib.postgres.fields.jsonb.JSONField(blank=True, editable=False, null=True)),
|
('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)),
|
('address', models.GenericIPAddressField(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)),
|
('vpnpool', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='uncloud_net.VPNPool')),
|
||||||
('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)),
|
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
'abstract': False,
|
||||||
|
@ -53,8 +54,12 @@ class Migration(migrations.Migration):
|
||||||
name='VPNNetwork',
|
name='VPNNetwork',
|
||||||
fields=[
|
fields=[
|
||||||
('extra_data', django.contrib.postgres.fields.jsonb.JSONField(blank=True, editable=False, null=True)),
|
('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)),
|
||||||
('vpnpool', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='uncloud_net.VPNPool')),
|
('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={
|
options={
|
||||||
'abstract': False,
|
'abstract': False,
|
||||||
|
|
|
@ -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',
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import uuid
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||||
|
@ -15,24 +17,36 @@ class VPNPool(UncloudModel):
|
||||||
Network address pools from which VPNs can be created
|
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),
|
network_size = models.IntegerField(validators=[MinValueValidator(0),
|
||||||
MaxValueValidator(128)])
|
MaxValueValidator(128)])
|
||||||
|
|
||||||
|
subnetwork_size = models.IntegerField(validators=[MinValueValidator(0),
|
||||||
|
MaxValueValidator(128)])
|
||||||
|
|
||||||
vpn_hostname = models.CharField(max_length=256)
|
vpn_hostname = models.CharField(max_length=256)
|
||||||
|
|
||||||
wireguard_private_key = models.CharField(max_length=48)
|
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,
|
vpnpool = models.ForeignKey(VPNPool,
|
||||||
on_delete=models.CASCADE)
|
on_delete=models.CASCADE)
|
||||||
|
|
||||||
network = models.GenericIPAddressField(editable=False,
|
address = models.GenericIPAddressField(primary_key=True)
|
||||||
unique=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)
|
wireguard_public_key = models.CharField(max_length=48)
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
import base64
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from .models import *
|
from .models import *
|
||||||
|
@ -10,9 +13,48 @@ class VPNPoolSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class VPNNetworkSerializer(serializers.ModelSerializer):
|
class VPNNetworkSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
network_size = serializers.IntegerField(min_value=0,
|
|
||||||
max_value=128)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VPNNetwork
|
model = VPNNetwork
|
||||||
fields = '__all__'
|
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 =
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
|
||||||
from rest_framework import viewsets, permissions
|
from rest_framework import viewsets, permissions
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue