fix: merge CoreDNS ACL per-service and add reload plugin; add peer/cell e2e tests

- _build_acl_block: put all blocked IPs for a service in ONE acl block instead
  of one block per peer — the first block's allow-all was silently granting
  access to every peer after the first blocked one (first-match semantics)
- generate_corefile: add 'reload' plugin so SIGUSR1 triggers Corefile reload
  in newer CoreDNS builds (without it the signal was a no-op)
- tests/test_firewall_manager.py: new tests for single merged ACL block and
  the reload directive
- tests/e2e/api/test_peer_access_update.py: e2e tests for service_access,
  internet_access, and peer_access updates persisting live to iptables/CoreDNS
- tests/e2e/api/test_cell_to_cell.py: e2e tests for cell-to-cell connection
  management, permissions API, and cross-cell service access restrictions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-02 04:57:37 -04:00
parent f1666ba19c
commit c521fab1cb
4 changed files with 878 additions and 4 deletions
+20
View File
@@ -69,6 +69,21 @@ class TestBuildAclBlock(unittest.TestCase):
self.assertIn('10.0.0.2/32', result)
self.assertIn('10.0.0.3/32', result)
def test_multiple_peers_in_single_acl_block(self):
# Both IPs must be in ONE acl block, not separate blocks.
# Separate blocks cause the first block's allow-all to match before
# the second block's block rule — silently granting access.
blocked = {'mail': ['10.0.0.2', '10.0.0.3'], 'calendar': [], 'files': [], 'webdav': []}
result = firewall_manager._build_acl_block(blocked)
self.assertEqual(result.count('acl mail.cell.'), 1,
'Both blocked peers must share a single acl block')
# Both block lines must appear before the allow-all
idx_block_2 = result.index('block net 10.0.0.2/32')
idx_block_3 = result.index('block net 10.0.0.3/32')
idx_allow = result.index('allow net 0.0.0.0/0')
self.assertLess(idx_block_2, idx_allow)
self.assertLess(idx_block_3, idx_allow)
# ---------------------------------------------------------------------------
# generate_corefile
@@ -112,6 +127,11 @@ class TestGenerateCorefile(unittest.TestCase):
firewall_manager.generate_corefile(peers, self.path)
self.assertNotIn('block net', open(self.path).read())
def test_corefile_contains_reload_plugin(self):
firewall_manager.generate_corefile([], self.path)
content = open(self.path).read()
self.assertIn('reload', content)
def test_returns_false_on_write_error(self):
result = firewall_manager.generate_corefile([], '/nonexistent/path/Corefile')
self.assertFalse(result)