phasing in celery

for configuring the vpn server
This commit is contained in:
Nico Schottelius 2020-12-13 18:34:43 +01:00
parent aec79cba74
commit 2d62388eb1
8 changed files with 160 additions and 80 deletions

View file

@ -23,3 +23,6 @@ uritemplate
# Comprehensive interface to validate VAT numbers, making use of the VIES # Comprehensive interface to validate VAT numbers, making use of the VIES
# service for European countries. # service for European countries.
vat-validator vat-validator
# Tasks
celery

View file

@ -1,5 +1,6 @@
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
import decimal import decimal
from .celery import app as celery_app
# Define DecimalField properties, used to represent amounts of money. # Define DecimalField properties, used to represent amounts of money.
AMOUNT_MAX_DIGITS=10 AMOUNT_MAX_DIGITS=10
@ -248,3 +249,6 @@ COUNTRIES = (
('ZR', _('Zaire')), ('ZR', _('Zaire')),
('ZW', _('Zimbabwe')), ('ZW', _('Zimbabwe')),
) )
__all__ = ('celery_app',)

22
uncloud/celery.py Normal file
View file

@ -0,0 +1,22 @@
import os
from celery import Celery
# set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'uncloud.settings')
app = Celery('uncloud')
# Using a string here means the worker doesn't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys
# should have a `CELERY_` prefix.
app.config_from_object('django.conf:settings', namespace='CELERY')
# Load task modules from all registered Django app configs.
app.autodiscover_tasks()
@app.task(bind=True)
def debug_task(self):
print(f'Request: {self.request!r}')

View file

@ -0,0 +1,19 @@
# Generated by Django 3.1 on 2020-12-13 17:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('uncloud_net', '0002_wireguardvpnpool_wireguard_public_key'),
]
operations = [
migrations.AddField(
model_name='wireguardvpnpool',
name='wg_name',
field=models.CharField(default='wg0', max_length=15),
preserve_default=False,
),
]

View file

@ -0,0 +1,17 @@
# Generated by Django 3.1 on 2020-12-13 17:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('uncloud_net', '0003_wireguardvpnpool_wg_name'),
]
operations = [
migrations.AddConstraint(
model_name='wireguardvpnpool',
constraint=models.UniqueConstraint(fields=('wg_name', 'vpn_server_hostname'), name='unique_interface_name_per_host'),
),
]

View file

@ -13,6 +13,16 @@ class WireGuardVPNPool(models.Model):
Network address pools from which VPNs can be created Network address pools from which VPNs can be created
""" """
class Meta:
constraints = [
models.UniqueConstraint(fields=['wg_name', 'vpn_server_hostname' ],
name='unique_interface_name_per_host')
]
# Linux interface naming is restricing to max 15 characters
wg_name = models.CharField(max_length=15)
network = models.GenericIPAddressField(unique=True) network = models.GenericIPAddressField(unique=True)
network_mask = models.IntegerField(validators=[MinValueValidator(0), network_mask = models.IntegerField(validators=[MinValueValidator(0),
MaxValueValidator(128)]) MaxValueValidator(128)])

View file

@ -2,6 +2,7 @@ from django.db import transaction
from .models import * from .models import *
from .selectors import * from .selectors import *
from .tasks import *
@transaction.atomic @transaction.atomic
def create_wireguard_vpn(owner, public_key, network_mask): def create_wireguard_vpn(owner, public_key, network_mask):
@ -9,27 +10,7 @@ def create_wireguard_vpn(owner, public_key, network_mask):
pool = get_suitable_pools(network_mask)[0] pool = get_suitable_pools(network_mask)[0]
count = pool.wireguardvpn_set.count() count = pool.wireguardvpn_set.count()
# First object # Try re-using previously used networks first
if count == 0:
return WireGuardVPN.objects.create(owner=owner,
vpnpool=pool,
pool_index=0,
wireguard_public_key=public_key)
else: # Select last network and try +1 it
last_net = WireGuardVPN.objects.filter(vpnpool=pool).order_by('pool_index').last()
next_index = last_net.pool_index + 1
if next_index <= pool.max_pool_index:
return WireGuardVPN.objects.create(owner=owner,
vpnpool=pool,
pool_index=next_index,
wireguard_public_key=public_key)
# Still there? Then we need to lookup previously used networks
try: try:
free_lease = WireGuardVPNFreeLeases.objects.get(vpnpool=pool) free_lease = WireGuardVPNFreeLeases.objects.get(vpnpool=pool)
@ -40,69 +21,27 @@ def create_wireguard_vpn(owner, public_key, network_mask):
free_lease.delete() free_lease.delete()
return vpn
except WireGuardVPNFreeLeases.DoesNotExist: except WireGuardVPNFreeLeases.DoesNotExist:
pass
@property # First object
def wireguard_config_filename(self): if count == 0:
return '/etc/wireguard/{}.conf'.format(self.network) vpn = WireGuardVPN.objects.create(owner=owner,
vpnpool=pool,
pool_index=0,
wireguard_public_key=public_key)
@property else: # Select last network and try +1 it
def wireguard_config(self): last_net = WireGuardVPN.objects.filter(vpnpool=pool).order_by('pool_index').last()
wireguard_config = [
"""
[Interface]
ListenPort = 51820
PrivateKey = {privatekey}
""".format(privatekey=self.wireguard_private_key) ]
peers = [] next_index = last_net.pool_index + 1
for reservation in self.vpnnetworkreservation_set.filter(status='used'): if next_index <= pool.max_pool_index:
public_key = reservation.vpnnetwork_set.first().wireguard_public_key vpn = WireGuardVPN.objects.create(owner=owner,
peer_network = "{}/{}".format(reservation.address, self.subnetwork_size) vpnpool=pool,
owner = reservation.vpnnetwork_set.first().owner pool_index=next_index,
wireguard_public_key=public_key)
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): configure_wireguard_server(pool)
""" return vpn
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)

66
uncloud_net/tasks.py Normal file
View file

@ -0,0 +1,66 @@
from celery import shared_task
from .models import *
@shared_task
def configure_wireguard_server(vpnpool):
print(f"Configuring {vpnpool.vpn_server_hostname}")
wireguard_config_filename = '/etc/wireguard/{}.conf'.format(vpnpool.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)