3
3
import json
4
4
import logging
5
5
import subprocess
6
+ from copy import deepcopy
6
7
7
8
import shortuuid
8
9
from cache_memoize import cache_memoize
20
21
from .. import settings as app_settings
21
22
from ..api .zerotier_service import ZerotierService
22
23
from ..signals import vpn_peers_changed , vpn_server_modified
23
- from ..tasks import create_vpn_dh , trigger_vpn_server_endpoint
24
+ from ..tasks import (
25
+ create_vpn_dh ,
26
+ trigger_vpn_server_endpoint ,
27
+ trigger_zerotier_server_delete ,
28
+ trigger_zerotier_server_update ,
29
+ )
24
30
from .base import BaseConfig
25
31
26
32
logger = logging .getLogger (__name__ )
@@ -118,6 +124,8 @@ class AbstractVpn(ShareableOrgMixinUniqueName, BaseConfig):
118
124
# needed for wireguard
119
125
public_key = models .CharField (blank = True , max_length = 44 )
120
126
private_key = models .CharField (blank = True , max_length = 44 )
127
+ # needed for zerotier
128
+ network_id = models .CharField (blank = True , max_length = 16 )
121
129
122
130
__vpn__ = True
123
131
@@ -144,6 +152,7 @@ def clean(self, *args, **kwargs):
144
152
self ._validate_org_relation ('cert' )
145
153
self ._validate_org_relation ('subnet' )
146
154
self ._validate_subnet_ip ()
155
+ self ._validate_authtoken ()
147
156
148
157
def _validate_backend (self ):
149
158
if self ._state .adding :
@@ -181,7 +190,7 @@ def _validate_keys(self):
181
190
self .private_key = ''
182
191
183
192
def _validate_subnet_ip (self ):
184
- if self ._is_backend_type ('wireguard' ):
193
+ if self ._is_backend_type ('wireguard' ) or self . _is_backend_type ( 'zerotier' ) :
185
194
if not self .subnet :
186
195
raise ValidationError (
187
196
{'subnet' : _ ('Subnet is required for this VPN backend.' )}
@@ -191,6 +200,11 @@ def _validate_subnet_ip(self):
191
200
{'ip' : _ ('VPN IP address must be within the VPN subnet' )}
192
201
)
193
202
203
+ def _validate_authtoken (self ):
204
+ if self ._is_backend_type ('zerotier' ):
205
+ if not self .auth_token :
206
+ raise ValidationError ({'auth_token' : _ ('Auth token is required' )})
207
+
194
208
def save (self , * args , ** kwargs ):
195
209
"""
196
210
Calls _auto_create_cert() if cert is not set
@@ -199,33 +213,6 @@ def save(self, *args, **kwargs):
199
213
if not created :
200
214
self ._check_changes ()
201
215
create_dh = False
202
- if self ._is_backend_type ('zerotier' ):
203
- if not self .auth_token :
204
- raise ValidationError (
205
- {'auth_token' : _ ('Zerotier auth token is required' )}
206
- )
207
-
208
- # The network name is similar
209
- # to the VPN backend's name field
210
- zerotier_config = self .config .get ('zerotier' )[0 ]
211
- zerotier_config ['name' ] = self .name
212
-
213
- if zerotier_config and not zerotier_config .get ('id' ):
214
- response = ZerotierService (self .host , self .auth_token ).create_network (
215
- self .config .get ('zerotier' )[0 ]
216
- )
217
- # Append newly created zerotier
218
- # controller network configuration
219
- self .config = {** self .config , 'zerotier' : [response ]}
220
-
221
- # When zerotier network already exists
222
- if zerotier_config and zerotier_config .get ('id' ):
223
- response = ZerotierService (self .host , self .auth_token ).update_network (
224
- self .config .get ('zerotier' )[0 ]
225
- )
226
- # update the existing network configuration
227
- self .config = {** self .config , 'zerotier' : [response ]}
228
-
229
216
if not self .cert and self .ca :
230
217
self .cert = self ._auto_create_cert ()
231
218
if self ._is_backend_type ('openvpn' ) and not self .dh :
@@ -235,6 +222,13 @@ def save(self, *args, **kwargs):
235
222
self ._generate_wireguard_keys ()
236
223
if self .subnet and not self .ip :
237
224
self .ip = self ._auto_create_ip ()
225
+ if self ._is_backend_type ('zerotier' ):
226
+ config = deepcopy (self .config ['zerotier' ][0 ])
227
+ config ['name' ] = self .name
228
+ if created :
229
+ self ._create_zerotier_server (config )
230
+ else :
231
+ self ._update_zerotier_server (config )
238
232
super ().save (* args , ** kwargs )
239
233
if create_dh :
240
234
transaction .on_commit (lambda : create_vpn_dh .delay (self .id ))
@@ -256,6 +250,7 @@ def _check_changes(self):
256
250
'dh' ,
257
251
'public_key' ,
258
252
'private_key' ,
253
+ 'network_id' ,
259
254
]
260
255
current = self ._meta .model .objects .only (* attrs ).get (pk = self .pk )
261
256
for attr in attrs :
@@ -284,25 +279,53 @@ class method for ``post_delete`` signal
284
279
"""
285
280
if not instance ._is_backend_type ('zerotier' ):
286
281
return
287
- network_id = instance .config .get ('zerotier' )[0 ].get ('id' )
288
- ZerotierService (instance .host , instance .auth_token ).delete_network (network_id )
282
+ transaction .on_commit (
283
+ lambda : trigger_zerotier_server_delete .delay (
284
+ host = instance .host ,
285
+ auth_token = instance .auth_token ,
286
+ network_id = instance .network_id ,
287
+ vpn_id = instance .pk ,
288
+ )
289
+ )
290
+
291
+ def _create_zerotier_server (self , config ):
292
+ server_config = ZerotierService (
293
+ self .host , self .auth_token , self .subnet , self .ip
294
+ ).create_network (config )
295
+ self .network_id = server_config .pop ('id' , None )
296
+ self .config = {** self .config , 'zerotier' : [server_config ]}
297
+
298
+ def _update_zerotier_server (self , config ):
299
+ config = config or {}
300
+ if config and self ._is_backend_type ('zerotier' ):
301
+ if self .host and self .auth_token :
302
+ transaction .on_commit (
303
+ lambda : trigger_zerotier_server_update .delay (
304
+ config = config ,
305
+ vpn_id = self .pk ,
306
+ )
307
+ )
308
+ else :
309
+ logger .info (
310
+ f'Cannot update configuration of { self .name } '
311
+ 'Zerotier VPN server, host and authentication token are empty.'
312
+ )
289
313
290
314
def update_vpn_server_configuration (instance , ** kwargs ):
291
- if not instance ._is_backend_type ('wireguard' ):
292
- return
293
- if instance .webhook_endpoint and instance .auth_token :
294
- transaction .on_commit (
295
- lambda : trigger_vpn_server_endpoint .delay (
296
- endpoint = instance .webhook_endpoint ,
297
- auth_token = instance .auth_token ,
298
- vpn_id = instance .pk ,
315
+ if instance ._is_backend_type ('wireguard' ):
316
+ if instance .webhook_endpoint and instance .auth_token :
317
+ transaction .on_commit (
318
+ lambda : trigger_vpn_server_endpoint .delay (
319
+ endpoint = instance .webhook_endpoint ,
320
+ auth_token = instance .auth_token ,
321
+ vpn_id = instance .pk ,
322
+ )
323
+ )
324
+ else :
325
+ logger .info (
326
+ f'Cannot update configuration of { instance .name } VPN server, '
327
+ 'webhook endpoint and authentication token are empty.'
299
328
)
300
- )
301
- else :
302
- logger .info (
303
- f'Cannot update configuration of { instance .name } VPN server, '
304
- 'webhook endpoint and authentication token are empty.'
305
- )
306
329
307
330
def _auto_create_cert (self ):
308
331
"""
@@ -360,6 +383,8 @@ def get_context(self):
360
383
c ['subnet_prefixlen' ] = str (self .subnet .subnet .prefixlen )
361
384
if self .ip :
362
385
c ['ip_address' ] = self .ip .ip_address
386
+ if self .network_id :
387
+ c ['network_id' ] = self .network_id
363
388
c .update (sorted (super ().get_context ().items ()))
364
389
return c
365
390
@@ -391,6 +416,9 @@ def get_vpn_server_context(self):
391
416
context_keys ['server_ip_network' ]
392
417
] = f'{ self .ip .ip_address } /{ self .subnet .subnet .max_prefixlen } '
393
418
context [context_keys ['vpn_subnet' ]] = str (self .subnet .subnet )
419
+ if self ._is_backend_type ('zerotier' ) and self .network_id :
420
+ context [context_keys ['network_name' ]] = self .name
421
+ context [context_keys ['network_id' ]] = self .network_id
394
422
return context
395
423
396
424
def get_system_context (self ):
@@ -422,6 +450,8 @@ def _get_auto_context_keys(self):
422
450
* ip address
423
451
VXLAN:
424
452
* vni (VXLAN Network Identifier)
453
+ ZeroTier
454
+ * network_id (ZeroTier Network Identifier)
425
455
"""
426
456
pk = self .pk .hex
427
457
context_keys = {
@@ -457,6 +487,9 @@ def _get_auto_context_keys(self):
457
487
'server_ip_network' : 'server_ip_network_{}' .format (pk ),
458
488
}
459
489
)
490
+ if self ._is_backend_type ('zerotier' ):
491
+ context_keys .update ({'network_id' : 'network_id_{}' .format (pk )})
492
+ context_keys .update ({'network_name' : 'network_name_{}' .format (pk )})
460
493
return context_keys
461
494
462
495
def auto_client (self , auto_cert = True , template_backend_class = None ):
@@ -492,7 +525,8 @@ def auto_client(self, auto_cert=True, template_backend_class=None):
492
525
# call auto_client and update the config
493
526
elif self ._is_backend_type ('zerotier' ) and template_backend_class :
494
527
auto = backend .auto_client (
495
- host = self .host ,
528
+ name = self .name ,
529
+ nwid = self .network_id ,
496
530
server = self .config [config_dict_key ][0 ],
497
531
** context_keys ,
498
532
)
0 commit comments