70 lines
3.0 KiB
Python
70 lines
3.0 KiB
Python
"""Run BIND as an authoritative server or a separate caching resolver."""
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import socket
|
|
import sys
|
|
import time
|
|
|
|
ROOT = Path("/tmp/dns-lab")
|
|
# A fixed key is only used by rndc on loopback inside these disposable lab containers.
|
|
KEY = 'key "lab-control" { algorithm hmac-sha256; secret "c2VtaW5hci03LWxvY2FsLWRucy1jb250cm9sLWtleSE="; };'
|
|
|
|
|
|
def write_zone(address, ttl):
|
|
old = json.loads((ROOT / "state.json").read_text()) if (ROOT / "state.json").exists() else {}
|
|
serial = max(int(time.time()), old.get("serial", 0) + 1)
|
|
(ROOT / "lab.test.zone").write_text(f"""$TTL {ttl}
|
|
@ IN SOA ns.lab.test. hostmaster.lab.test. ({serial} 60 60 3600 {ttl})
|
|
IN NS ns.lab.test.
|
|
ns IN A {socket.gethostbyname(socket.gethostname())}
|
|
orders IN A {address}
|
|
www IN CNAME orders.lab.test.
|
|
""")
|
|
(ROOT / "state.json").write_text(json.dumps({"address": address, "ttl": ttl, "serial": serial}))
|
|
|
|
|
|
def main():
|
|
role = sys.argv[1]
|
|
ROOT.mkdir(parents=True, exist_ok=True)
|
|
control = KEY + '\ncontrols { inet 127.0.0.1 port 953 allow { 127.0.0.1; } keys { "lab-control"; }; };\n'
|
|
(ROOT / "rndc.conf").write_text(KEY + '\noptions { default-server 127.0.0.1; default-key "lab-control"; };\n')
|
|
common = f"""directory "{ROOT}";
|
|
listen-on {{ any; }};
|
|
listen-on-v6 {{ none; }};
|
|
allow-query {{ any; }};
|
|
minimal-responses no;
|
|
dnssec-validation no;
|
|
empty-zones-enable no;
|
|
pid-file "{ROOT}/named.pid";
|
|
session-keyfile "{ROOT}/session.key";
|
|
"""
|
|
if role == "auth":
|
|
write_zone("192.0.2.10", 60)
|
|
options = "recursion no;"
|
|
zones = f'zone "lab.test" {{ type primary; file "{ROOT}/lab.test.zone"; }};'
|
|
elif role in ("root", "tld"):
|
|
address = socket.gethostbyname(socket.gethostname())
|
|
zone, ns = (".", "ns.root.") if role == "root" else ("test", "ns.test.")
|
|
child, child_ns, child_host = ("test.", "ns.test.", "tld-dns") if role == "root" else ("lab.test.", "ns.lab.test.", "auth")
|
|
child_address = socket.gethostbyname(child_host)
|
|
records = f"$TTL 60\n@ IN SOA {ns} hostmaster.{ns} (1 60 60 3600 60)\n@ IN NS {ns}\n{ns} IN A {address}\n{child} IN NS {child_ns}\n{child_ns} IN A {child_address}\n"
|
|
(ROOT / "hierarchy.zone").write_text(records)
|
|
options = "recursion no;"
|
|
zones = f'zone "{zone}" {{ type primary; file "{ROOT}/hierarchy.zone"; }};'
|
|
elif role == "resolver":
|
|
address = socket.gethostbyname("auth")
|
|
options = """recursion yes; allow-recursion { any; }; allow-query-cache { any; };
|
|
prefetch 0; stale-answer-enable no; stale-cache-enable no; servfail-ttl 0;"""
|
|
zones = "\n".join(f'zone "{name}" {{ type forward; forward only; forwarders {{ {address}; }}; }};'
|
|
for name in ("lab.test", "broken.test"))
|
|
else:
|
|
raise ValueError("Unknown DNS role")
|
|
config = control + f"options {{ {common}\n{options} }};\n" + zones
|
|
(ROOT / "named.conf").write_text(config)
|
|
os.execvp("named", ["named", "-g", "-c", str(ROOT / "named.conf"), "-u", "root", "-n", "1"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|