feat: dynamic ip_range propagation to DNS, firewall, and docker-compose
When ip_range changes in Settings, the new subnet is now applied to: - DNS zone records (network_manager.apply_ip_range) - Caddy virtual IPs (firewall_manager.ensure_caddy_virtual_ips) - iptables per-service rules (firewall_manager.update_service_ips) - docker-compose.yml static IPs if writable (ip_utils.update_docker_compose_ips) New module ip_utils.py derives all container IPs from the subnet using fixed offsets so the entire stack stays consistent from one setting. 321 tests pass (72 new tests added for ip_utils, apply_ip_range, update_service_ips). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -271,5 +271,57 @@ class TestClearPeerRules(unittest.TestCase):
|
||||
mock_restore.assert_not_called()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# update_service_ips
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestUpdateServiceIps(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
# Restore default SERVICE_IPS after each test
|
||||
firewall_manager.update_service_ips('172.20.0.0/16')
|
||||
|
||||
def test_default_ips_are_172_20(self):
|
||||
self.assertEqual(firewall_manager.SERVICE_IPS['calendar'], '172.20.0.21')
|
||||
self.assertEqual(firewall_manager.SERVICE_IPS['webdav'], '172.20.0.24')
|
||||
|
||||
def test_update_changes_all_virtual_ips(self):
|
||||
firewall_manager.update_service_ips('10.0.0.0/24')
|
||||
self.assertEqual(firewall_manager.SERVICE_IPS['calendar'], '10.0.0.21')
|
||||
self.assertEqual(firewall_manager.SERVICE_IPS['files'], '10.0.0.22')
|
||||
self.assertEqual(firewall_manager.SERVICE_IPS['mail'], '10.0.0.23')
|
||||
self.assertEqual(firewall_manager.SERVICE_IPS['webdav'], '10.0.0.24')
|
||||
|
||||
def test_update_replaces_not_extends(self):
|
||||
firewall_manager.update_service_ips('10.0.0.0/24')
|
||||
# Should only have the four virtual-IP keys
|
||||
self.assertEqual(set(firewall_manager.SERVICE_IPS.keys()),
|
||||
{'calendar', 'files', 'mail', 'webdav'})
|
||||
|
||||
def test_apply_peer_rules_uses_updated_ips(self):
|
||||
firewall_manager.update_service_ips('10.0.0.0/24')
|
||||
called_with = []
|
||||
|
||||
def fake_wg_exec(args):
|
||||
called_with.append(args)
|
||||
m = MagicMock()
|
||||
m.returncode = 1 # simulate rule-doesn't-exist → _ensure_rule inserts
|
||||
return m
|
||||
|
||||
with patch.object(firewall_manager, '_wg_exec', side_effect=fake_wg_exec), \
|
||||
patch.object(firewall_manager, 'clear_peer_rules'):
|
||||
firewall_manager.apply_peer_rules('10.0.0.5', {
|
||||
'internet_access': True,
|
||||
'service_access': ['calendar'],
|
||||
'peer_access': True,
|
||||
})
|
||||
|
||||
iptables_calls = [c for c in called_with if c and c[0] == 'iptables']
|
||||
dest_ips = [c[c.index('-d') + 1] for c in iptables_calls if '-d' in c]
|
||||
# calendar vIP should now be 10.0.0.21
|
||||
self.assertIn('10.0.0.21', dest_ips)
|
||||
# old IP must not appear
|
||||
self.assertNotIn('172.20.0.21', dest_ips)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user