Skip to content

Commit f50ec45

Browse files
codesankalppandafy
authored andcommitted
[fix] Invalidate checksum cache on VPN server change #667
Fixes #667 (cherry picked from commit ac0399c)
1 parent a0ffb96 commit f50ec45

File tree

7 files changed

+108
-3
lines changed

7 files changed

+108
-3
lines changed

README.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2990,6 +2990,17 @@ The signal is emitted when subnets and IP addresses have been provisioned
29902990
for a ``VpnClient`` for a VPN server with a subnet with
29912991
`subnet division rule <#subnet-division-app>`_.
29922992

2993+
``vpn_server_modified``
2994+
~~~~~~~~~~~~~~~~~~~~~~~
2995+
2996+
**Path**: ``openwisp_controller.config.signals.vpn_server_modified``
2997+
2998+
**Arguments**:
2999+
3000+
- ``instance``: instance of ``Vpn``.
3001+
3002+
The signal is emitted when the VPN server is modified.
3003+
29933004
``vpn_peers_changed``
29943005
~~~~~~~~~~~~~~~~~~~~~
29953006

openwisp_controller/config/apps.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
device_group_changed,
1919
device_name_changed,
2020
vpn_peers_changed,
21+
vpn_server_modified,
2122
)
2223

2324
# ensure Device.hardware_id field is not flagged as unique
@@ -211,6 +212,7 @@ def enable_cache_invalidation(self):
211212
device_cache_invalidation_handler,
212213
devicegroup_change_handler,
213214
devicegroup_delete_handler,
215+
vpn_server_change_handler,
214216
)
215217

216218
post_save.connect(
@@ -252,6 +254,11 @@ def enable_cache_invalidation(self):
252254
sender=self.device_model,
253255
dispatch_uid='device.invalidate_cache',
254256
)
257+
vpn_server_modified.connect(
258+
vpn_server_change_handler,
259+
sender=self.vpn_model,
260+
dispatch_uid='vpn.invalidate_checksum_cache',
261+
)
255262

256263
def register_dashboard_charts(self):
257264
register_dashboard_chart(

openwisp_controller/config/base/vpn.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from ...base import ShareableOrgMixinUniqueName
1919
from .. import crypto
2020
from .. import settings as app_settings
21-
from ..signals import vpn_peers_changed
21+
from ..signals import vpn_peers_changed, vpn_server_modified
2222
from ..tasks import create_vpn_dh, trigger_vpn_server_endpoint
2323
from .base import BaseConfig
2424

@@ -127,6 +127,11 @@ class Meta:
127127
unique_together = ('organization', 'name')
128128
abstract = True
129129

130+
def __init__(self, *args, **kwargs):
131+
super().__init__(*args, **kwargs)
132+
# for internal usage
133+
self._send_vpn_modified_after_save = False
134+
130135
def clean(self, *args, **kwargs):
131136
super().clean(*args, **kwargs)
132137
self._validate_backend()
@@ -190,6 +195,9 @@ def save(self, *args, **kwargs):
190195
"""
191196
Calls _auto_create_cert() if cert is not set
192197
"""
198+
created = self._state.adding
199+
if not created:
200+
self._check_changes()
193201
create_dh = False
194202
if not self.cert and self.ca:
195203
self.cert = self._auto_create_cert()
@@ -203,8 +211,35 @@ def save(self, *args, **kwargs):
203211
super().save(*args, **kwargs)
204212
if create_dh:
205213
transaction.on_commit(lambda: create_vpn_dh.delay(self.id))
214+
if not created and self._send_vpn_modified_after_save:
215+
self._send_vpn_modified_signal()
216+
self._send_vpn_modified_after_save = False
206217
self.update_vpn_server_configuration()
207218

219+
def _check_changes(self):
220+
attrs = [
221+
'config',
222+
'host',
223+
'ca',
224+
'cert',
225+
'key',
226+
'backend',
227+
'subnet',
228+
'ip',
229+
'dh',
230+
'public_key',
231+
'private_key',
232+
]
233+
current = self._meta.model.objects.only(*attrs).get(pk=self.pk)
234+
for attr in attrs:
235+
if getattr(self, attr) == getattr(current, attr):
236+
continue
237+
self._send_vpn_modified_after_save = True
238+
break
239+
240+
def _send_vpn_modified_signal(self):
241+
vpn_server_modified.send(sender=self.__class__, instance=self)
242+
208243
@classmethod
209244
def dhparam(cls, length):
210245
"""
@@ -734,3 +769,14 @@ def _auto_ip(self):
734769
if func(self):
735770
return
736771
self.ip = self.vpn.subnet.request_ip()
772+
773+
@classmethod
774+
def invalidate_clients_cache(cls, vpn):
775+
"""
776+
Invalidate checksum cache for clients that uses this VPN server
777+
"""
778+
for client in vpn.vpnclient_set.iterator():
779+
# invalidate cache for device
780+
client.config._send_config_modified_signal(
781+
action='related_template_changed'
782+
)

openwisp_controller/config/handlers.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from django.db import transaction
12
from django.dispatch import receiver
23
from django.utils.translation import gettext_lazy as _
34
from openwisp_notifications.signals import notify
@@ -62,3 +63,9 @@ def device_cache_invalidation_handler(instance, **kwargs):
6263
view = DeviceChecksumView()
6364
setattr(view, 'kwargs', {'pk': str(instance.pk)})
6465
view.get_device.invalidate(view)
66+
67+
68+
def vpn_server_change_handler(instance, **kwargs):
69+
transaction.on_commit(
70+
lambda: tasks.invalidate_vpn_server_devices_cache_change.delay(instance.id)
71+
)

openwisp_controller/config/signals.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,7 @@
3737
vpn_peers_changed.__doc__ = """
3838
providing arguments: ['instance']
3939
"""
40+
vpn_server_modified = Signal()
41+
vpn_server_modified.__doc__ = """
42+
providing arguments: ['instance']
43+
"""

openwisp_controller/config/tasks.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,14 @@ def invalidate_devicegroup_cache_change(instance_id, model_name):
7272
DeviceGroupCommonName.certificate_change_invalidates_cache(instance_id)
7373

7474

75+
@shared_task(base=OpenwispCeleryTask)
76+
def invalidate_vpn_server_devices_cache_change(vpn_pk):
77+
Vpn = load_model('config', 'Vpn')
78+
VpnClient = load_model('config', 'VpnClient')
79+
vpn = Vpn.objects.get(pk=vpn_pk)
80+
VpnClient.invalidate_clients_cache(vpn)
81+
82+
7583
@shared_task(base=OpenwispCeleryTask)
7684
def invalidate_devicegroup_cache_delete(instance_id, model_name, **kwargs):
7785
from .api.views import DeviceGroupCommonName

openwisp_controller/config/tests/test_vpn.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
from ...vpn_backends import OpenVpn
1515
from .. import settings as app_settings
16-
from ..signals import vpn_peers_changed
16+
from ..signals import config_modified, vpn_peers_changed, vpn_server_modified
1717
from ..tasks import create_vpn_dh
1818
from .utils import (
1919
CreateConfigTemplateMixin,
@@ -442,7 +442,7 @@ def test_cert_validation(self):
442442
self.assertIn('CA is required with this VPN backend', message_dict['ca'])
443443

444444

445-
class TestVpnTransaction(BaseTestVpn, TransactionTestCase):
445+
class TestVpnTransaction(BaseTestVpn, TestWireguardVpnMixin, TransactionTestCase):
446446
@mock.patch.object(create_vpn_dh, 'delay')
447447
def test_create_vpn_dh_with_vpn_create(self, delay):
448448
vpn = self._create_vpn(dh='')
@@ -463,6 +463,28 @@ def test_update_vpn_dh(self, dhparam):
463463
self.assertNotEqual(vpn.dh, Vpn._placeholder_dh)
464464
dhparam.assert_called_once()
465465

466+
def test_vpn_server_change_invalidates_device_cache(self):
467+
device, vpn, template = self._create_wireguard_vpn_template()
468+
with catch_signal(
469+
vpn_server_modified
470+
) as mocked_vpn_server_modified, catch_signal(
471+
config_modified
472+
) as mocked_config_modified:
473+
vpn.host = 'localhost'
474+
vpn.save(update_fields=['host'])
475+
mocked_vpn_server_modified.assert_called_once_with(
476+
signal=vpn_server_modified, sender=Vpn, instance=vpn
477+
)
478+
mocked_config_modified.assert_called_once_with(
479+
signal=config_modified,
480+
sender=Config,
481+
instance=device.config,
482+
previous_status='modified',
483+
action='related_template_changed',
484+
config=device.config,
485+
device=device,
486+
)
487+
466488

467489
class TestWireguard(BaseTestVpn, TestWireguardVpnMixin, TestCase):
468490
def test_wireguard_config_creation(self):

0 commit comments

Comments
 (0)