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
|
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):
|
def write_named_conf_local(zones):
|
||||||
"""Rewrite named.conf.local from zone list."""
|
"""Rewrite named.conf.local from zone list."""
|
||||||
lines = ['// Local zone configurations - managed by DNS Web UI\n']
|
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}")
|
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()
|
existing = parse_named_conf_local()
|
||||||
|
all_managed = parse_all_managed_zones()
|
||||||
if any(z["name"] == zone_name for z in existing):
|
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")
|
return render_template("zone_form.html")
|
||||||
|
|
||||||
# Generate initial zone file
|
# Generate initial zone file
|
||||||
|
|||||||
Reference in New Issue
Block a user