parent
5b0502dc85
commit
a978d3dcd2
@ -0,0 +1,95 @@ |
||||
{ config, lib, pkgs, ... }: |
||||
|
||||
with lib; |
||||
|
||||
let |
||||
cfg = config.services.knot; |
||||
|
||||
configFile = pkgs.writeText "knot.conf" cfg.extraConfig; |
||||
socketFile = "/run/knot/knot.sock"; |
||||
|
||||
knotConfCheck = file: pkgs.runCommand "knot-config-checked" |
||||
{ buildInputs = [ cfg.package ]; } '' |
||||
ln -s ${configFile} $out |
||||
knotc --config=${configFile} conf-check |
||||
''; |
||||
|
||||
knot-cli-wrappers = pkgs.stdenv.mkDerivation { |
||||
name = "knot-cli-wrappers"; |
||||
buildInputs = [ pkgs.makeWrapper ]; |
||||
buildCommand = '' |
||||
mkdir -p $out/bin |
||||
makeWrapper ${cfg.package}/bin/knotc "$out/bin/knotc" \ |
||||
--add-flags "--config=${configFile}" \ |
||||
--add-flags "--socket=${socketFile}" |
||||
makeWrapper ${cfg.package}/bin/keymgr "$out/bin/keymgr" \ |
||||
--add-flags "--config=${configFile}" |
||||
for executable in kdig khost kjournalprint knsec3hash knsupdate kzonecheck |
||||
do |
||||
ln -s "${cfg.package}/bin/$executable" "$out/bin/$executable" |
||||
done |
||||
mkdir -p "$out/share" |
||||
ln -s '${cfg.package}/share/man' "$out/share/" |
||||
''; |
||||
}; |
||||
in { |
||||
options = { |
||||
services.knot = { |
||||
enable = mkEnableOption "Knot authoritative-only DNS server"; |
||||
|
||||
extraArgs = mkOption { |
||||
type = types.listOf types.str; |
||||
default = []; |
||||
description = '' |
||||
List of additional command line paramters for knotd |
||||
''; |
||||
}; |
||||
|
||||
extraConfig = mkOption { |
||||
type = types.lines; |
||||
default = ""; |
||||
description = '' |
||||
Extra lines to be added verbatim to knot.conf |
||||
''; |
||||
}; |
||||
|
||||
package = mkOption { |
||||
type = types.package; |
||||
default = pkgs.knot-dns; |
||||
description = '' |
||||
Which Knot DNS package to use |
||||
''; |
||||
}; |
||||
}; |
||||
}; |
||||
|
||||
config = mkIf config.services.knot.enable { |
||||
systemd.services.knot = { |
||||
unitConfig.Documentation = "man:knotd(8) man:knot.conf(5) man:knotc(8) https://www.knot-dns.cz/docs/${cfg.package.version}/html/"; |
||||
description = cfg.package.meta.description; |
||||
wantedBy = [ "multi-user.target" ]; |
||||
wants = [ "network.target" ]; |
||||
after = ["network.target" ]; |
||||
|
||||
serviceConfig = { |
||||
Type = "notify"; |
||||
ExecStart = "${cfg.package}/bin/knotd --config=${knotConfCheck configFile} --socket=${socketFile} ${concatStringsSep " " cfg.extraArgs}"; |
||||
ExecReload = "${knot-cli-wrappers}/bin/knotc reload"; |
||||
CapabilityBoundingSet = "CAP_NET_BIND_SERVICE CAP_SETPCAP"; |
||||
AmbientCapabilities = "CAP_NET_BIND_SERVICE CAP_SETPCAP"; |
||||
NoNewPrivileges = true; |
||||
DynamicUser = "yes"; |
||||
RuntimeDirectory = "knot"; |
||||
StateDirectory = "knot"; |
||||
StateDirectoryMode = "0700"; |
||||
PrivateDevices = true; |
||||
RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6"; |
||||
SystemCallArchitectures = "native"; |
||||
Restart = "on-abort"; |
||||
}; |
||||
}; |
||||
|
||||
environment.systemPackages = [ knot-cli-wrappers ]; |
||||
}; |
||||
} |
||||
|
@ -0,0 +1,197 @@ |
||||
import ./make-test.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 ]; |
||||
}; |
||||
in { |
||||
name = "knot"; |
||||
|
||||
nodes = { |
||||
master = { lib, ... }: { |
||||
imports = [ common ]; |
||||
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.extraConfig = '' |
||||
server: |
||||
listen: 0.0.0.0@53 |
||||
listen: ::@53 |
||||
|
||||
acl: |
||||
- id: slave_acl |
||||
address: 192.168.0.2 |
||||
action: transfer |
||||
|
||||
remote: |
||||
- id: slave |
||||
address: 192.168.0.2@53 |
||||
|
||||
template: |
||||
- id: default |
||||
storage: ${knotZonesEnv} |
||||
notify: [slave] |
||||
acl: [slave_acl] |
||||
dnssec-signing: on |
||||
# 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 |
||||
# move databases below the state directory, because they need to be writable |
||||
journal-db: /var/lib/knot/journal |
||||
kasp-db: /var/lib/knot/kasp |
||||
timer-db: /var/lib/knot/timer |
||||
|
||||
zone: |
||||
- domain: example.com |
||||
file: example.com.zone |
||||
|
||||
- domain: sub.example.com |
||||
file: sub.example.com.zone |
||||
|
||||
log: |
||||
- target: syslog |
||||
any: info |
||||
''; |
||||
}; |
||||
|
||||
slave = { 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.extraArgs = [ "-v" ]; |
||||
services.knot.extraConfig = '' |
||||
server: |
||||
listen: 0.0.0.0@53 |
||||
listen: ::@53 |
||||
|
||||
acl: |
||||
- id: notify_from_master |
||||
address: 192.168.0.1 |
||||
action: notify |
||||
|
||||
remote: |
||||
- id: master |
||||
address: 192.168.0.1@53 |
||||
|
||||
template: |
||||
- id: default |
||||
master: master |
||||
acl: [notify_from_master] |
||||
# zonefileless setup |
||||
# https://www.knot-dns.cz/docs/2.8/html/operation.html#example-2 |
||||
zonefile-sync: -1 |
||||
zonefile-load: none |
||||
journal-content: all |
||||
# move databases below the state directory, because they need to be writable |
||||
journal-db: /var/lib/knot/journal |
||||
kasp-db: /var/lib/knot/kasp |
||||
timer-db: /var/lib/knot/timer |
||||
|
||||
zone: |
||||
- domain: example.com |
||||
file: example.com.zone |
||||
|
||||
- domain: sub.example.com |
||||
file: sub.example.com.zone |
||||
|
||||
log: |
||||
- target: syslog |
||||
any: info |
||||
''; |
||||
}; |
||||
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 |
||||
master4 = (lib.head nodes.master.config.networking.interfaces.eth1.ipv4.addresses).address; |
||||
master6 = (lib.head nodes.master.config.networking.interfaces.eth1.ipv6.addresses).address; |
||||
|
||||
slave4 = (lib.head nodes.slave.config.networking.interfaces.eth1.ipv4.addresses).address; |
||||
slave6 = (lib.head nodes.slave.config.networking.interfaces.eth1.ipv6.addresses).address; |
||||
in '' |
||||
startAll; |
||||
|
||||
$client->waitForUnit("network.target"); |
||||
$master->waitForUnit("knot.service"); |
||||
$slave->waitForUnit("knot.service"); |
||||
|
||||
sub assertResponse { |
||||
my ($knot, $query_type, $query, $expected) = @_; |
||||
my $out = $client->succeed("khost -t $query_type $query $knot"); |
||||
$client->log("$knot replies with: $out"); |
||||
chomp $out; |
||||
die "DNS query for $query ($query_type) against $knot gave '$out' instead of '$expected'" |
||||
if ($out !~ $expected); |
||||
} |
||||
|
||||
foreach ("${master4}", "${master6}", "${slave4}", "${slave6}") { |
||||
subtest $_, sub { |
||||
assertResponse($_, "SOA", "example.com", qr/start of authority.*?noc\.example\.com/); |
||||
assertResponse($_, "A", "example.com", qr/has no [^ ]+ record/); |
||||
assertResponse($_, "AAAA", "example.com", qr/has no [^ ]+ record/); |
||||
|
||||
assertResponse($_, "A", "www.example.com", qr/address 192.0.2.1$/); |
||||
assertResponse($_, "AAAA", "www.example.com", qr/address 2001:db8::1$/); |
||||
|
||||
assertResponse($_, "NS", "sub.example.com", qr/nameserver is ns\d\.example\.com.$/); |
||||
assertResponse($_, "A", "sub.example.com", qr/address 192.0.2.2$/); |
||||
assertResponse($_, "AAAA", "sub.example.com", qr/address 2001:db8::2$/); |
||||
|
||||
assertResponse($_, "RRSIG", "www.example.com", qr/RR set signature is/); |
||||
assertResponse($_, "DNSKEY", "example.com", qr/DNSSEC key is/); |
||||
}; |
||||
} |
||||
''; |
||||
}) |
Loading…
Reference in new issue