@@ -286,6 +286,74 @@ def _assert_vpn_client_cert(cert, vpn_client, cert_ct, vpn_client_ct):
286286 vpnclient .save ()
287287 _assert_vpn_client_cert (cert , vpnclient , 1 , 0 )
288288
289+ def test_vpn_client_post_delete_on_template_removal (self ):
290+ """Regression test for #1221: VpnClient.post_delete must fire
291+ when a VPN template is removed so that peer cache is invalidated
292+ and certificates are properly revoked."""
293+ org = self ._get_org ()
294+ vpn = self ._create_vpn ()
295+ t = self ._create_template (name = "vpn-test" , type = "vpn" , vpn = vpn , auto_cert = True )
296+ c = self ._create_config (organization = org )
297+ c .templates .add (t )
298+ vpnclient = c .vpnclient_set .first ()
299+ self .assertIsNotNone (vpnclient )
300+ cert_pk = vpnclient .cert .pk
301+ with mock .patch .object (Vpn , "_invalidate_peer_cache" ) as mock_invalidate :
302+ c .templates .remove (t )
303+ mock_invalidate .assert_called_once ()
304+ self .assertFalse (VpnClient .objects .filter (pk = vpnclient .pk ).exists ())
305+ # Certificate should be revoked (auto_cert=True)
306+ self .assertTrue (Cert .objects .get (pk = cert_pk ).revoked )
307+
308+ def test_vpn_client_post_delete_on_device_deactivation (self ):
309+ """Regression test for #1221: VpnClient.post_delete must fire
310+ when a device is deactivated so that peer cache is invalidated
311+ and certificates are properly revoked."""
312+ org = self ._get_org ()
313+ vpn = self ._create_vpn ()
314+ t = self ._create_template (name = "vpn-test" , type = "vpn" , vpn = vpn , auto_cert = True )
315+ d = self ._create_device (organization = org )
316+ c = self ._create_config (device = d )
317+ c .templates .add (t )
318+ vpnclient = c .vpnclient_set .first ()
319+ self .assertIsNotNone (vpnclient )
320+ cert_pk = vpnclient .cert .pk
321+ with mock .patch .object (Vpn , "_invalidate_peer_cache" ) as mock_invalidate :
322+ d .deactivate ()
323+ mock_invalidate .assert_called_once ()
324+ self .assertFalse (VpnClient .objects .filter (pk = vpnclient .pk ).exists ())
325+ # Certificate should be revoked (auto_cert=True)
326+ self .assertTrue (Cert .objects .get (pk = cert_pk ).revoked )
327+
328+ def test_vpn_client_post_delete_multiple_clients (self ):
329+ """Regression test for #1221: when a device has multiple VPN templates,
330+ removing all of them must delete every VpnClient, invalidate peer cache
331+ for each VPN, and revoke all auto-created certificates."""
332+ org = self ._get_org ()
333+ vpn1 = self ._create_vpn (name = "vpn1" )
334+ vpn2 = self ._create_vpn (name = "vpn2" , ca = vpn1 .ca )
335+ t1 = self ._create_template (
336+ name = "vpn-t1" , type = "vpn" , vpn = vpn1 , auto_cert = True
337+ )
338+ t2 = self ._create_template (
339+ name = "vpn-t2" , type = "vpn" , vpn = vpn2 , auto_cert = True
340+ )
341+ d = self ._create_device (organization = org )
342+ c = self ._create_config (device = d )
343+ c .templates .add (t1 , t2 )
344+ self .assertEqual (c .vpnclient_set .count (), 2 )
345+ vpnclient1 = c .vpnclient_set .get (vpn = vpn1 )
346+ vpnclient2 = c .vpnclient_set .get (vpn = vpn2 )
347+ cert_pk1 = vpnclient1 .cert .pk
348+ cert_pk2 = vpnclient2 .cert .pk
349+ with mock .patch .object (Vpn , "_invalidate_peer_cache" ) as mock_invalidate :
350+ d .deactivate ()
351+ self .assertEqual (mock_invalidate .call_count , 2 )
352+ self .assertFalse (VpnClient .objects .filter (pk = vpnclient1 .pk ).exists ())
353+ self .assertFalse (VpnClient .objects .filter (pk = vpnclient2 .pk ).exists ())
354+ self .assertTrue (Cert .objects .get (pk = cert_pk1 ).revoked )
355+ self .assertTrue (Cert .objects .get (pk = cert_pk2 ).revoked )
356+
289357 def test_vpn_client_get_common_name (self ):
290358 vpn = self ._create_vpn ()
291359 d = self ._create_device ()
@@ -728,6 +796,18 @@ def test_trigger_vpn_server_endpoint_invalid_vpn_id(self):
728796 f"VPN Server UUID: { vpn_id } does not exist."
729797 )
730798
799+ def test_wireguard_vpnclient_ip_released_on_template_removal (self ):
800+ """Regression test for #1221: when a Wireguard VPN template is removed,
801+ the allocated IP address must be released (deleted)."""
802+ device , vpn , template = self ._create_wireguard_vpn_template ()
803+ vpnclient = device .config .vpnclient_set .first ()
804+ self .assertIsNotNone (vpnclient )
805+ self .assertIsNotNone (vpnclient .ip )
806+ ip_pk = vpnclient .ip .pk
807+ device .config .templates .remove (template )
808+ self .assertFalse (VpnClient .objects .filter (pk = vpnclient .pk ).exists ())
809+ self .assertFalse (IpAddress .objects .filter (pk = ip_pk ).exists ())
810+
731811
732812class TestWireguardTransaction (BaseTestVpn , TestWireguardVpnMixin , TransactionTestCase ):
733813 mock_response = mock .Mock (spec = requests .Response )
0 commit comments