08a775839d
We reconfigure the secondary nameserver VM to do all the same things that it did before, but now over the XDP interface.
203 lines
6.1 KiB
Nix
203 lines
6.1 KiB
Nix
import ./make-test-python.nix ({ pkgs, lib, ...} :
|
|
let
|
|
common = {
|
|
networking.firewall.enable = false;
|
|
networking.useDHCP = false;
|
|
};
|
|
exampleZone = pkgs.writeTextDir "example.com.zone" ''
|
|
@ SOA ns.example.com. noc.example.com. 2019031301 86400 7200 3600000 172800
|
|
@ NS ns1
|
|
@ NS ns2
|
|
ns1 A 192.168.0.1
|
|
ns1 AAAA fd00::1
|
|
ns2 A 192.168.0.2
|
|
ns2 AAAA fd00::2
|
|
www A 192.0.2.1
|
|
www AAAA 2001:DB8::1
|
|
sub NS ns.example.com.
|
|
'';
|
|
delegatedZone = pkgs.writeTextDir "sub.example.com.zone" ''
|
|
@ SOA ns.example.com. noc.example.com. 2019031301 86400 7200 3600000 172800
|
|
@ NS ns1.example.com.
|
|
@ NS ns2.example.com.
|
|
@ A 192.0.2.2
|
|
@ AAAA 2001:DB8::2
|
|
'';
|
|
|
|
knotZonesEnv = pkgs.buildEnv {
|
|
name = "knot-zones";
|
|
paths = [ exampleZone delegatedZone ];
|
|
};
|
|
# DO NOT USE pkgs.writeText IN PRODUCTION. This put secrets in the nix store!
|
|
tsigFile = pkgs.writeText "tsig.conf" ''
|
|
key:
|
|
- id: xfr_key
|
|
algorithm: hmac-sha256
|
|
secret: zOYgOgnzx3TGe5J5I/0kxd7gTcxXhLYMEq3Ek3fY37s=
|
|
'';
|
|
in {
|
|
name = "knot";
|
|
meta = with pkgs.lib.maintainers; {
|
|
maintainers = [ hexa ];
|
|
};
|
|
|
|
|
|
nodes = {
|
|
primary = { lib, ... }: {
|
|
imports = [ common ];
|
|
|
|
# trigger sched_setaffinity syscall
|
|
virtualisation.cores = 2;
|
|
|
|
networking.interfaces.eth1 = {
|
|
ipv4.addresses = lib.mkForce [
|
|
{ address = "192.168.0.1"; prefixLength = 24; }
|
|
];
|
|
ipv6.addresses = lib.mkForce [
|
|
{ address = "fd00::1"; prefixLength = 64; }
|
|
];
|
|
};
|
|
services.knot.enable = true;
|
|
services.knot.extraArgs = [ "-v" ];
|
|
services.knot.keyFiles = [ tsigFile ];
|
|
services.knot.settings = {
|
|
server = {
|
|
listen = [
|
|
"0.0.0.0@53"
|
|
"::@53"
|
|
];
|
|
automatic-acl = true;
|
|
};
|
|
|
|
acl.secondary_acl = {
|
|
address = "192.168.0.2";
|
|
key = "xfr_key";
|
|
action = "transfer";
|
|
};
|
|
|
|
remote.secondary.address = "192.168.0.2@53";
|
|
|
|
template.default = {
|
|
storage = knotZonesEnv;
|
|
notify = [ "secondary" ];
|
|
acl = [ "secondary_acl" ];
|
|
dnssec-signing = true;
|
|
# Input-only zone files
|
|
# https://www.knot-dns.cz/docs/2.8/html/operation.html#example-3
|
|
# prevents modification of the zonefiles, since the zonefiles are immutable
|
|
zonefile-sync = -1;
|
|
zonefile-load = "difference";
|
|
journal-content = "changes";
|
|
};
|
|
|
|
zone = {
|
|
"example.com".file = "example.com.zone";
|
|
"sub.example.com".file = "sub.example.com.zone";
|
|
};
|
|
|
|
log.syslog.any = "info";
|
|
};
|
|
};
|
|
|
|
secondary = { lib, ... }: {
|
|
imports = [ common ];
|
|
networking.interfaces.eth1 = {
|
|
ipv4.addresses = lib.mkForce [
|
|
{ address = "192.168.0.2"; prefixLength = 24; }
|
|
];
|
|
ipv6.addresses = lib.mkForce [
|
|
{ address = "fd00::2"; prefixLength = 64; }
|
|
];
|
|
};
|
|
services.knot.enable = true;
|
|
services.knot.keyFiles = [ tsigFile ];
|
|
services.knot.extraArgs = [ "-v" ];
|
|
services.knot.settings = {
|
|
server = {
|
|
automatic-acl = true;
|
|
};
|
|
|
|
xdp = {
|
|
listen = [
|
|
"eth1"
|
|
];
|
|
tcp = true;
|
|
};
|
|
|
|
remote.primary = {
|
|
address = "192.168.0.1@53";
|
|
key = "xfr_key";
|
|
};
|
|
|
|
template.default = {
|
|
master = "primary";
|
|
# zonefileless setup
|
|
# https://www.knot-dns.cz/docs/2.8/html/operation.html#example-2
|
|
zonefile-sync = "-1";
|
|
zonefile-load = "none";
|
|
journal-content = "all";
|
|
};
|
|
|
|
zone = {
|
|
"example.com".file = "example.com.zone";
|
|
"sub.example.com".file = "sub.example.com.zone";
|
|
};
|
|
|
|
log.syslog.any = "debug";
|
|
};
|
|
};
|
|
client = { lib, nodes, ... }: {
|
|
imports = [ common ];
|
|
networking.interfaces.eth1 = {
|
|
ipv4.addresses = [
|
|
{ address = "192.168.0.3"; prefixLength = 24; }
|
|
];
|
|
ipv6.addresses = [
|
|
{ address = "fd00::3"; prefixLength = 64; }
|
|
];
|
|
};
|
|
environment.systemPackages = [ pkgs.knot-dns ];
|
|
};
|
|
};
|
|
|
|
testScript = { nodes, ... }: let
|
|
primary4 = (lib.head nodes.primary.config.networking.interfaces.eth1.ipv4.addresses).address;
|
|
primary6 = (lib.head nodes.primary.config.networking.interfaces.eth1.ipv6.addresses).address;
|
|
|
|
secondary4 = (lib.head nodes.secondary.config.networking.interfaces.eth1.ipv4.addresses).address;
|
|
secondary6 = (lib.head nodes.secondary.config.networking.interfaces.eth1.ipv6.addresses).address;
|
|
in ''
|
|
import re
|
|
|
|
start_all()
|
|
|
|
client.wait_for_unit("network.target")
|
|
primary.wait_for_unit("knot.service")
|
|
secondary.wait_for_unit("knot.service")
|
|
|
|
|
|
def test(host, query_type, query, pattern):
|
|
out = client.succeed(f"khost -t {query_type} {query} {host}").strip()
|
|
client.log(f"{host} replied with: {out}")
|
|
assert re.search(pattern, out), f'Did not match "{pattern}"'
|
|
|
|
|
|
for host in ("${primary4}", "${primary6}", "${secondary4}", "${secondary6}"):
|
|
with subtest(f"Interrogate {host}"):
|
|
test(host, "SOA", "example.com", r"start of authority.*noc\.example\.com\.")
|
|
test(host, "A", "example.com", r"has no [^ ]+ record")
|
|
test(host, "AAAA", "example.com", r"has no [^ ]+ record")
|
|
|
|
test(host, "A", "www.example.com", r"address 192.0.2.1$")
|
|
test(host, "AAAA", "www.example.com", r"address 2001:db8::1$")
|
|
|
|
test(host, "NS", "sub.example.com", r"nameserver is ns\d\.example\.com.$")
|
|
test(host, "A", "sub.example.com", r"address 192.0.2.2$")
|
|
test(host, "AAAA", "sub.example.com", r"address 2001:db8::2$")
|
|
|
|
test(host, "RRSIG", "www.example.com", r"RR set signature is")
|
|
test(host, "DNSKEY", "example.com", r"DNSSEC key is")
|
|
|
|
primary.log(primary.succeed("systemd-analyze security knot.service | grep -v '✓'"))
|
|
'';
|
|
})
|