From 2d62388eb17b785b23979c91b7cd74624be11b82 Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Sun, 13 Dec 2020 18:34:43 +0100 Subject: [PATCH] phasing in celery for configuring the vpn server --- requirements.txt | 3 + uncloud/__init__.py | 4 + uncloud/celery.py | 22 +++++ .../0003_wireguardvpnpool_wg_name.py | 19 ++++ .../migrations/0004_auto_20201213_1734.py | 17 ++++ uncloud_net/models.py | 10 ++ uncloud_net/services.py | 99 ++++--------------- uncloud_net/tasks.py | 66 +++++++++++++ 8 files changed, 160 insertions(+), 80 deletions(-) create mode 100644 uncloud/celery.py create mode 100644 uncloud_net/migrations/0003_wireguardvpnpool_wg_name.py create mode 100644 uncloud_net/migrations/0004_auto_20201213_1734.py create mode 100644 uncloud_net/tasks.py diff --git a/requirements.txt b/requirements.txt index 12e01f0..2394073 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,3 +23,6 @@ uritemplate # Comprehensive interface to validate VAT numbers, making use of the VIES # service for European countries. vat-validator + +# Tasks +celery diff --git a/uncloud/__init__.py b/uncloud/__init__.py index 4bda45f..e073dd5 100644 --- a/uncloud/__init__.py +++ b/uncloud/__init__.py @@ -1,5 +1,6 @@ from django.utils.translation import gettext_lazy as _ import decimal +from .celery import app as celery_app # Define DecimalField properties, used to represent amounts of money. AMOUNT_MAX_DIGITS=10 @@ -248,3 +249,6 @@ COUNTRIES = ( ('ZR', _('Zaire')), ('ZW', _('Zimbabwe')), ) + + +__all__ = ('celery_app',) diff --git a/uncloud/celery.py b/uncloud/celery.py new file mode 100644 index 0000000..7bcaaae --- /dev/null +++ b/uncloud/celery.py @@ -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}') diff --git a/uncloud_net/migrations/0003_wireguardvpnpool_wg_name.py b/uncloud_net/migrations/0003_wireguardvpnpool_wg_name.py new file mode 100644 index 0000000..9ecf52c --- /dev/null +++ b/uncloud_net/migrations/0003_wireguardvpnpool_wg_name.py @@ -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, + ), + ] diff --git a/uncloud_net/migrations/0004_auto_20201213_1734.py b/uncloud_net/migrations/0004_auto_20201213_1734.py new file mode 100644 index 0000000..24e46e7 --- /dev/null +++ b/uncloud_net/migrations/0004_auto_20201213_1734.py @@ -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'), + ), + ] diff --git a/uncloud_net/models.py b/uncloud_net/models.py index 88c0ec8..5c1da3d 100644 --- a/uncloud_net/models.py +++ b/uncloud_net/models.py @@ -13,6 +13,16 @@ class WireGuardVPNPool(models.Model): 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_mask = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(128)]) diff --git a/uncloud_net/services.py b/uncloud_net/services.py index ec7a266..4f80c44 100644 --- a/uncloud_net/services.py +++ b/uncloud_net/services.py @@ -1,7 +1,8 @@ from django.db import transaction -from .models import * +from .models import * from .selectors import * +from .tasks import * @transaction.atomic 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] count = pool.wireguardvpn_set.count() - # First object - 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 re-using previously used networks first try: free_lease = WireGuardVPNFreeLeases.objects.get(vpnpool=pool) @@ -40,69 +21,27 @@ def create_wireguard_vpn(owner, public_key, network_mask): free_lease.delete() - return vpn - except WireGuardVPNFreeLeases.DoesNotExist: - pass - @property - def wireguard_config_filename(self): - return '/etc/wireguard/{}.conf'.format(self.network) + # First object + if count == 0: + vpn = WireGuardVPN.objects.create(owner=owner, + vpnpool=pool, + pool_index=0, + wireguard_public_key=public_key) - @property - def wireguard_config(self): - wireguard_config = [ - """ -[Interface] -ListenPort = 51820 -PrivateKey = {privatekey} -""".format(privatekey=self.wireguard_private_key) ] + else: # Select last network and try +1 it + last_net = WireGuardVPN.objects.filter(vpnpool=pool).order_by('pool_index').last() - peers = [] + next_index = last_net.pool_index + 1 - 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 + if next_index <= pool.max_pool_index: + vpn = WireGuardVPN.objects.create(owner=owner, + vpnpool=pool, + pool_index=next_index, + wireguard_public_key=public_key) - 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) + configure_wireguard_server(pool) + return vpn diff --git a/uncloud_net/tasks.py b/uncloud_net/tasks.py new file mode 100644 index 0000000..ca2a05d --- /dev/null +++ b/uncloud_net/tasks.py @@ -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)