begin phasing in config of vpn via cdist

This commit is contained in:
Nico Schottelius 2020-12-20 12:20:54 +01:00
parent e2b36c8bca
commit 054886fd9c
5 changed files with 119 additions and 39 deletions

View file

@ -170,7 +170,7 @@ VPNNetworks can be managed by all authenticated users.
* Developer Handbook * Developer Handbook
The following section describe decisions / architecture of The following section describe decisions / architecture of
uncloud. These chapters are intended to be read by developers. uncloud. These chapters are intended to be read by developers.
** Documentation ** This Documentation
This documentation is written in org-mode. To compile it to This documentation is written in org-mode. To compile it to
html/pdf, just open emacs and press *C-c C-e l p*. html/pdf, just open emacs and press *C-c C-e l p*.
** Models ** Models
@ -234,6 +234,53 @@ VPNNetworks can be managed by all authenticated users.
*** Decision *** Decision
We use integers, because they are easy. We use integers, because they are easy.
** Distributing/Dispatching/Orchestrating
*** Variant 1: using cdist
- The uncloud server can git commit things
- The uncloud server loads cdist and configures the server
- Advantages
- Fully integrated into normal flow
- Disadvantage
- web frontend has access to more data than it needs
- On compromise of the machine, more data leaks
- Some cdist usual delay
*** Variant 2: via celery
- The uncloud server dispatches via celery
- Every decentral node also runs celery/connects to the broker
- Summary brokers:
- If local only celery -> good to use redis - Broker
- If remote: probably better to use rabbitmq
- redis
- simpler
- rabbitmq
- more versatile
- made for remote connections
- quorom queues would be nice, but not clear if supported
- https://github.com/celery/py-amqp/issues/302
- https://github.com/celery/celery/issues/6067
- Cannot be installed on alpine Linux at the moment
- Advantage
- Very python / django integrated
- Rather instant
- Disadvantages
- Every decentral node needs to have the uncloud code available
- Decentral nodes *might* need to access the database
- Tasks can probably be written to work without that
(i.e. only strings/bytes)
**** log/tests
(venv) [19:54] vpn-2a0ae5c1200:~/uncloud$ celery -A uncloud -b redis://bridge.place7.ungleich.ch worker -n worker1@%h --logfile ~/celery.log -
Q vpn-2a0ae5c1200.ungleich.ch
*** Variant 3: dedicated cdist instance via message broker
- A separate VM/machine
- Has Checkout of ~/.cdist
- Has cdist checkout
- Tiny API for management
- Not directly web accessible
- "cdist" queue
** Milestones :uncloud: ** Milestones :uncloud:
*** 1.1 (cleanup 1) *** 1.1 (cleanup 1)
**** TODO [#C] Unify ValidationError, FieldError - define proper Exception **** TODO [#C] Unify ValidationError, FieldError - define proper Exception

View file

@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/3.0/ref/settings/
""" """
import os import os
import re
import ldap import ldap
from django.core.management.utils import get_random_secret_key from django.core.management.utils import get_random_secret_key
@ -199,14 +200,29 @@ AUTH_LDAP_USER_SEARCH = LDAPSearch("dc=example,dc=com",
# where to create customers # where to create customers
LDAP_CUSTOMER_DN="ou=customer,dc=example,dc=com" LDAP_CUSTOMER_DN="ou=customer,dc=example,dc=com"
CELERY_TASK_ROUTES = { def route_task(name, args, kwargs, options, task=None, **kw):
'*': { print(f"{name} - {args} - {kwargs}")
'queue': 'vpn1' # if name == 'myapp.tasks.compress_video':
} return {'queue': 'vpn1' }
} # 'exchange_type': 'topic',
# 'routing_key': 'video.compress'}
CELERY_BROKER_URL = 'redis://bridge.place7.ungleich.ch:6379/0'
CELERY_RESULT_BACKEND = 'redis://bridge.place7.ungleich.ch:6379/0' CELERY_TASK_ROUTES = (route_task,)
# CELERY_TASK_ROUTES = {
# '*': {
# 'queue': 'vpn1'
# }
# }
CELERY_BROKER_URL = 'redis://:uncloud.example.com:6379/0'
CELERY_RESULT_BACKEND = 'redis://:uncloud.example.com:6379/0'
CELERY_TASK_ROUTES = {
(re.compile(r'.*\.cdist\..*'), { 'queue': 'cdist' }
}
# CELERY_TASK_CREATE_MISSING_QUEUES = False # CELERY_TASK_CREATE_MISSING_QUEUES = False

View file

@ -53,6 +53,29 @@ class WireGuardVPNPool(models.Model):
def __str__(self): def __str__(self):
return f"{self.ip_network} (subnets: /{self.subnetwork_mask})" return f"{self.ip_network} (subnets: /{self.subnetwork_mask})"
@property
def wireguard_config(self):
wireguard_config = [
"[Interface]\nListenPort = 51820\nPrivateKey = {self.wireguard_private_key}\n".format(
privatekey=self.wireguard_private_key)
]
peers = []
for vpn in self.wireguardvpn_set.all():
public_key = vpn.wireguard_public_key
peer_network = "{}/{}".format(vpn.address, self.subnetwork_mask)
owner = vpn.owner
peers.append("# Owner: {owner}\n[Peer]\nPublicKey = {public_key}\nAllowedIPs = {peer_network}\n\n".format(
owner=owner,
public_key=public_key,
peer_network=peer_network))
wireguard_config.extend(peers)
return "\n".join(wireguard_config)
class WireGuardVPN(models.Model): class WireGuardVPN(models.Model):
""" """

View file

@ -8,38 +8,30 @@ def whereami():
print(os.uname()) print(os.uname())
return os.uname() return os.uname()
def configure_wireguard_server(wireguardvpnpool):
"""
- Create wireguard config (DB query -> string)
- Submit config to cdist worker
- Change config locally on worker / commit / shared
"""
config = wireguardvpnpool.wireguard_config
server = wireguardvpnpool.vpn_server_hostname
print(f"Configuring {vpnpool.vpn_server_hostname}: {osa}")
cdist_configure_wireguard_server(config, server):
@shared_task @shared_task
def configure_wireguard_server(vpnpool): def cdist_configure_wireguard_server(config, server):
print(f"Configuring {vpnpool.vpn_server_hostname}") """
Create config and configure server.
wireguard_config_filename = '/etc/wireguard/{}.conf'.format(vpnpool.network) To be executed on the cdist workers.
"""
@property fname = f"/home/app/.cdist/type/__ungleich_wireguard/files/{server}"
def wireguard_config(self):
wireguard_config = [
"""
[Interface]
ListenPort = 51820
PrivateKey = {privatekey}
""".format(privatekey=self.wireguard_private_key) ]
peers = [] with open(fname, "w") as fd:
fd.write(config)
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)

View file

@ -12,6 +12,7 @@ from .serializers import *
from .selectors import * from .selectors import *
from .services import * from .services import *
from .forms import * from .forms import *
from .tasks import *
# class VPNPoolViewSet(viewsets.ModelViewSet): # class VPNPoolViewSet(viewsets.ModelViewSet):
# serializer_class = VPNPoolSerializer # serializer_class = VPNPoolSerializer
@ -39,6 +40,7 @@ class WireGuardVPNViewSet(viewsets.ModelViewSet):
public_key=serializer.validated_data['wireguard_public_key'], public_key=serializer.validated_data['wireguard_public_key'],
network_mask=serializer.validated_data['network_mask'] network_mask=serializer.validated_data['network_mask']
) )
configure_wireguard_server.apply_async((vpn.vpnpool,))
return Response(WireGuardVPNSerializer(vpn).data) return Response(WireGuardVPNSerializer(vpn).data)