fix: use configured domain in CoreDNS Corefile generation
Two bugs caused DNS to fail when the domain name changes: 1. generate_corefile() hardcoded 'cell' as the zone name instead of using the configured domain — on startup it would silently reset any domain change back to 'cell' 2. apply_domain() regex replaced ALL non-dot zones (including local.cell) with the new domain → duplicate zone blocks → CoreDNS crash Fix: add a domain parameter to generate_corefile/apply_all_dns_rules, add _configured_domain() helper in app.py, and delegate Corefile updates in apply_domain() to generate_corefile() so the logic is in one place. Also parameterise SERVICE_HOSTS ACL entries via the domain argument. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+8
-4
@@ -188,11 +188,15 @@ cell_link_manager = CellLinkManager(
|
||||
)
|
||||
|
||||
# Apply firewall + DNS rules from stored peer settings (survives API restarts)
|
||||
def _configured_domain() -> str:
|
||||
return config_manager.configs.get('_identity', {}).get('domain', 'cell')
|
||||
|
||||
|
||||
def _apply_startup_enforcement():
|
||||
try:
|
||||
peers = peer_registry.list_peers()
|
||||
firewall_manager.apply_all_peer_rules(peers)
|
||||
firewall_manager.apply_all_dns_rules(peers, COREFILE_PATH)
|
||||
firewall_manager.apply_all_dns_rules(peers, COREFILE_PATH, _configured_domain())
|
||||
logger.info(f"Applied enforcement rules for {len(peers)} peers on startup")
|
||||
except Exception as e:
|
||||
logger.warning(f"Startup enforcement failed (non-fatal): {e}")
|
||||
@@ -1387,7 +1391,7 @@ def apply_wireguard_enforcement():
|
||||
try:
|
||||
peers = peer_registry.list_peers()
|
||||
firewall_manager.apply_all_peer_rules(peers)
|
||||
firewall_manager.apply_all_dns_rules(peers, COREFILE_PATH)
|
||||
firewall_manager.apply_all_dns_rules(peers, COREFILE_PATH, _configured_domain())
|
||||
return jsonify({'ok': True, 'peers': len(peers)})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
@@ -1528,7 +1532,7 @@ def add_peer():
|
||||
if success:
|
||||
# Apply server-side enforcement immediately
|
||||
firewall_manager.apply_peer_rules(peer_info['ip'], peer_info)
|
||||
firewall_manager.apply_all_dns_rules(peer_registry.list_peers(), COREFILE_PATH)
|
||||
firewall_manager.apply_all_dns_rules(peer_registry.list_peers(), COREFILE_PATH, _configured_domain())
|
||||
return jsonify({"message": f"Peer {data['name']} added successfully", "ip": assigned_ip}), 201
|
||||
else:
|
||||
return jsonify({"error": f"Peer {data['name']} already exists"}), 400
|
||||
@@ -1564,7 +1568,7 @@ def update_peer(peer_name):
|
||||
updated_peer = peer_registry.get_peer(peer_name)
|
||||
if updated_peer:
|
||||
firewall_manager.apply_peer_rules(updated_peer['ip'], updated_peer)
|
||||
firewall_manager.apply_all_dns_rules(peer_registry.list_peers(), COREFILE_PATH)
|
||||
firewall_manager.apply_all_dns_rules(peer_registry.list_peers(), COREFILE_PATH, _configured_domain())
|
||||
result = {"message": f"Peer {peer_name} updated", "config_changed": config_changed}
|
||||
return jsonify(result)
|
||||
else:
|
||||
|
||||
+21
-21
@@ -212,30 +212,27 @@ def apply_all_peer_rules(peers: List[Dict[str, Any]]) -> None:
|
||||
# DNS ACL (CoreDNS Corefile generation)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Map service name → DNS hostname in .cell zone
|
||||
SERVICE_HOSTS = {
|
||||
'calendar': 'calendar.cell.',
|
||||
'files': 'files.cell.',
|
||||
'mail': 'mail.cell.',
|
||||
'webdav': 'webdav.cell.',
|
||||
}
|
||||
# Service subdomains that get per-peer ACL rules in the CoreDNS zone block
|
||||
_ACL_SERVICES = ('calendar', 'files', 'mail', 'webdav')
|
||||
|
||||
|
||||
def _build_acl_block(blocked_peers_by_service: Dict[str, List[str]]) -> str:
|
||||
def _build_acl_block(blocked_peers_by_service: Dict[str, List[str]],
|
||||
domain: str = 'cell') -> str:
|
||||
"""
|
||||
Build CoreDNS ACL plugin stanzas.
|
||||
|
||||
blocked_peers_by_service: { 'calendar': ['10.0.0.2', '10.0.0.3'], ... }
|
||||
Returns a string to embed in the `cell { }` zone block.
|
||||
Returns a string to embed in the primary zone block.
|
||||
"""
|
||||
if not blocked_peers_by_service:
|
||||
return ''
|
||||
|
||||
lines = []
|
||||
for service, peer_ips in blocked_peers_by_service.items():
|
||||
host = SERVICE_HOSTS.get(service)
|
||||
if not host or not peer_ips:
|
||||
for service in _ACL_SERVICES:
|
||||
peer_ips = blocked_peers_by_service.get(service, [])
|
||||
if not peer_ips:
|
||||
continue
|
||||
host = f'{service}.{domain}.'
|
||||
for ip in peer_ips:
|
||||
lines.append(f' acl {host} {{')
|
||||
lines.append(f' block net {ip}/32')
|
||||
@@ -245,10 +242,12 @@ def _build_acl_block(blocked_peers_by_service: Dict[str, List[str]]) -> str:
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def generate_corefile(peers: List[Dict[str, Any]], corefile_path: str = COREFILE_PATH) -> bool:
|
||||
def generate_corefile(peers: List[Dict[str, Any]], corefile_path: str = COREFILE_PATH,
|
||||
domain: str = 'cell') -> bool:
|
||||
"""
|
||||
Rewrite the CoreDNS Corefile with per-peer ACL rules and reload plugin.
|
||||
The file is written to corefile_path (API-side path mapped into CoreDNS container).
|
||||
domain: the configured cell domain (e.g. 'cell', 'dev') — must match zone file names.
|
||||
"""
|
||||
try:
|
||||
# Collect which peers block which services
|
||||
@@ -262,12 +261,12 @@ def generate_corefile(peers: List[Dict[str, Any]], corefile_path: str = COREFILE
|
||||
if service not in allowed_services:
|
||||
blocked[service].append(ip)
|
||||
|
||||
acl_block = _build_acl_block(blocked)
|
||||
acl_block = _build_acl_block(blocked, domain)
|
||||
|
||||
cell_zone_block = 'cell {\n file /data/cell.zone\n log\n'
|
||||
primary_zone_block = f'{domain} {{\n file /data/{domain}.zone\n log\n'
|
||||
if acl_block:
|
||||
cell_zone_block += acl_block + '\n'
|
||||
cell_zone_block += '}\n'
|
||||
primary_zone_block += acl_block + '\n'
|
||||
primary_zone_block += '}\n'
|
||||
|
||||
corefile = f""". {{
|
||||
forward . 8.8.8.8 1.1.1.1
|
||||
@@ -276,8 +275,8 @@ def generate_corefile(peers: List[Dict[str, Any]], corefile_path: str = COREFILE
|
||||
health
|
||||
}}
|
||||
|
||||
{cell_zone_block}
|
||||
local.cell {{
|
||||
{primary_zone_block}
|
||||
local.{domain} {{
|
||||
file /data/local.zone
|
||||
log
|
||||
}}
|
||||
@@ -307,9 +306,10 @@ def reload_coredns() -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def apply_all_dns_rules(peers: List[Dict[str, Any]], corefile_path: str = COREFILE_PATH) -> bool:
|
||||
def apply_all_dns_rules(peers: List[Dict[str, Any]], corefile_path: str = COREFILE_PATH,
|
||||
domain: str = 'cell') -> bool:
|
||||
"""Regenerate Corefile and reload CoreDNS."""
|
||||
ok = generate_corefile(peers, corefile_path)
|
||||
ok = generate_corefile(peers, corefile_path, domain)
|
||||
if ok:
|
||||
reload_coredns()
|
||||
return ok
|
||||
|
||||
+9
-22
@@ -401,30 +401,17 @@ class NetworkManager(BaseServiceManager):
|
||||
except Exception as e:
|
||||
warnings.append(f"dnsmasq domain update failed: {e}")
|
||||
|
||||
# 2. Update Corefile: replace old primary zone block with new domain
|
||||
# 2. Regenerate Corefile using generate_corefile so it always stays consistent
|
||||
try:
|
||||
import firewall_manager as _fm
|
||||
corefile = os.path.join(self.config_dir, 'dns', 'Corefile')
|
||||
if os.path.exists(corefile):
|
||||
with open(corefile) as f:
|
||||
content = f.read()
|
||||
import re
|
||||
# Replace first named zone block (not the catch-all .) with new domain
|
||||
# Matches: <word> { ... } blocks (zone names like "cell", "oldname")
|
||||
def replace_zone(m):
|
||||
zone = m.group(1)
|
||||
if zone == '.':
|
||||
return m.group(0) # keep catch-all
|
||||
# Replace zone name with new domain; update file path reference
|
||||
body = m.group(2)
|
||||
body = re.sub(r'file\s+/data/\S+\.zone',
|
||||
f'file /data/{domain}.zone', body)
|
||||
return f'{domain} {{{body}}}'
|
||||
new_content = re.sub(
|
||||
r'(\S+)\s*\{([^}]*)\}',
|
||||
replace_zone, content, flags=re.DOTALL
|
||||
)
|
||||
with open(corefile, 'w') as f:
|
||||
f.write(new_content)
|
||||
peers_file = os.path.join(self.data_dir, 'peers.json')
|
||||
try:
|
||||
import json as _json
|
||||
peers = _json.loads(open(peers_file).read()) if os.path.exists(peers_file) else []
|
||||
except Exception:
|
||||
peers = []
|
||||
_fm.generate_corefile(peers, corefile, domain)
|
||||
except Exception as e:
|
||||
warnings.append(f"Corefile domain update failed: {e}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user