summary refs log tree commit diff
diff options
context:
space:
mode:
authorMartin Weinelt <martin@linuxlounge.net>2019-03-13 01:12:56 +0100
committerMartin Weinelt <hexa@darmstadt.ccc.de>2019-03-14 01:28:53 +0100
commita978d3dcd2aa24d7b82d2e99255d3d2354cf81a0 (patch)
tree185a5ac68dcd58674eca9e1ce9513d9752723e5a
parent5b0502dc85a413e0ad26f84078492a4271e7f456 (diff)
downloadnixpkgs-a978d3dcd2aa24d7b82d2e99255d3d2354cf81a0.tar
nixpkgs-a978d3dcd2aa24d7b82d2e99255d3d2354cf81a0.tar.gz
nixpkgs-a978d3dcd2aa24d7b82d2e99255d3d2354cf81a0.tar.bz2
nixpkgs-a978d3dcd2aa24d7b82d2e99255d3d2354cf81a0.tar.lz
nixpkgs-a978d3dcd2aa24d7b82d2e99255d3d2354cf81a0.tar.xz
nixpkgs-a978d3dcd2aa24d7b82d2e99255d3d2354cf81a0.tar.zst
nixpkgs-a978d3dcd2aa24d7b82d2e99255d3d2354cf81a0.zip
nixos/knot: init
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/services/networking/knot.nix95
-rw-r--r--nixos/tests/all-tests.nix1
-rw-r--r--nixos/tests/knot.nix197
4 files changed, 294 insertions, 0 deletions
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 032452428f1..48750a8a54a 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -578,6 +578,7 @@
   ./services/networking/keepalived/default.nix
   ./services/networking/keybase.nix
   ./services/networking/kippo.nix
+  ./services/networking/knot.nix
   ./services/networking/kresd.nix
   ./services/networking/lambdabot.nix
   ./services/networking/libreswan.nix
diff --git a/nixos/modules/services/networking/knot.nix b/nixos/modules/services/networking/knot.nix
new file mode 100644
index 00000000000..1cc1dd3f2f6
--- /dev/null
+++ b/nixos/modules/services/networking/knot.nix
@@ -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 ];
+  };
+}
+
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 2ddb54bcc3d..de5b8bbb7c0 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -117,6 +117,7 @@ in
   kernel-latest = handleTest ./kernel-latest.nix {};
   kernel-lts = handleTest ./kernel-lts.nix {};
   keymap = handleTest ./keymap.nix {};
+  knot = handleTest ./knot.nix {};
   kubernetes.dns = handleTestOn ["x86_64-linux"] ./kubernetes/dns.nix {};
   # kubernetes.e2e should eventually replace kubernetes.rbac when it works
   #kubernetes.e2e = handleTestOn ["x86_64-linux"] ./kubernetes/e2e.nix {};
diff --git a/nixos/tests/knot.nix b/nixos/tests/knot.nix
new file mode 100644
index 00000000000..e46159836cc
--- /dev/null
+++ b/nixos/tests/knot.nix
@@ -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/);
+      };
+    }
+  '';
+})