fix: detect zone name collisions with named.conf main config
创建 zone 时只检查 BIND_CONF_LOCAL 里的重复声明,遗漏了用户在 named.conf 主配置里手动声明的同名 zone —— 后果是 named reload 时直接报 'zone already exists' 错误,named 起不来。 新增 parse_all_managed_zones(),通过 named-checkconf -p 拿到 named 当前加载的所有 zone,区分 source=local(Web UI 管理的)和 source=main (用户在 named.conf 主配置里写的)。 zone_create() 在写入新声明前检查这两个来源: - 同名在 BIND_CONF_LOCAL:拒绝并提示 - 同名在 named.conf 主配置:拒绝并提示用户先去主配置里注释掉 回归测试在 dev 环境通过:识别 4 个 builtin zone (source=main) + 3 个 web UI zone (source=local)。
This commit is contained in:
@@ -216,6 +216,53 @@ def parse_named_conf_local():
|
||||
return zones
|
||||
|
||||
|
||||
def parse_all_managed_zones():
|
||||
"""Return all zone names that BIND currently has loaded (master/slave/in-view),
|
||||
including zones declared in the main named.conf outside BIND_CONF_LOCAL.
|
||||
|
||||
Uses `named-checkconf -p` which prints the fully-resolved configuration with
|
||||
all included files expanded, so we can detect duplicates the user might have
|
||||
added manually (e.g. a zone declared in /etc/named.conf that web UI doesn't
|
||||
know about).
|
||||
|
||||
Returns a dict: zone_name -> {"type": str, "file": str, "source": "local"|"main"}.
|
||||
"""
|
||||
rc, out, err = run_cmd("named-checkconf -p 2>&1", timeout=15)
|
||||
if rc != 0:
|
||||
# If named-checkconf fails (e.g. broken config), fall back to local-only
|
||||
# so the UI doesn't break entirely.
|
||||
return {z["name"]: {"type": z["type"], "file": z["file"], "source": "local"}
|
||||
for z in parse_named_conf_local()}
|
||||
|
||||
# Parse the dumped config: zone statements with a non-builtin type (i.e. not
|
||||
# "hint" and not under automatic empty-zones). We deliberately include builtin
|
||||
# zones too and let callers filter.
|
||||
result = {}
|
||||
pattern = re.compile(
|
||||
r'zone\s+"([^"]+)"\s+(?:IN\s+)?\{\s*'
|
||||
r'type\s+(\w+)\s*;\s*'
|
||||
r'(?:file\s+"([^"]+)"\s*;\s*)?',
|
||||
re.MULTILINE,
|
||||
)
|
||||
for m in pattern.finditer(out):
|
||||
name, ztype, zfile = m.group(1), m.group(2), m.group(3) or ""
|
||||
# Skip builtin/hint zones and the "empty" automatic zones (no file).
|
||||
if ztype == "hint" or not zfile:
|
||||
continue
|
||||
result[name] = {"type": ztype, "file": zfile, "source": "unknown"}
|
||||
|
||||
# Mark which ones came from BIND_CONF_LOCAL for the UI to know whether to
|
||||
# let the user edit/delete them.
|
||||
local_names = {z["name"] for z in parse_named_conf_local()}
|
||||
for name in result:
|
||||
if name in local_names:
|
||||
result[name]["source"] = "local"
|
||||
else:
|
||||
result[name]["source"] = "main"
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def write_named_conf_local(zones):
|
||||
"""Rewrite named.conf.local from zone list."""
|
||||
lines = ['// Local zone configurations - managed by DNS Web UI\n']
|
||||
@@ -687,10 +734,22 @@ def zone_create():
|
||||
|
||||
zone_file = os.path.join(BIND_ZONES_DIR, f"db.{zone_name}")
|
||||
|
||||
# Check duplicate
|
||||
# Check duplicate — both against zones we manage in BIND_CONF.local AND
|
||||
# against zones declared elsewhere in named.conf (e.g. user manually
|
||||
# added a zone block in /etc/named.conf). Without the second check,
|
||||
# creating a zone with a name already declared in the main config would
|
||||
# produce a duplicate zone error on BIND reload.
|
||||
existing = parse_named_conf_local()
|
||||
all_managed = parse_all_managed_zones()
|
||||
if any(z["name"] == zone_name for z in existing):
|
||||
flash(f"域名 {zone_name} 已存在", "error")
|
||||
flash(f"域名 {zone_name} 已在 Web UI 中存在", "error")
|
||||
return render_template("zone_form.html")
|
||||
if zone_name in all_managed:
|
||||
flash(
|
||||
f"域名 {zone_name} 已在 named.conf 中声明(不在 Web UI 管理范围内),"
|
||||
f"请先到 named.conf 中注释/删除后再创建",
|
||||
"error",
|
||||
)
|
||||
return render_template("zone_form.html")
|
||||
|
||||
# Generate initial zone file
|
||||
|
||||
Reference in New Issue
Block a user