knot: replace nsd as authoritative DNS
authorJulien Moutinho <julm@sourcephile.fr>
Mon, 10 Feb 2020 21:10:26 +0000 (22:10 +0100)
committerJulien Moutinho <julm@sourcephile.fr>
Wed, 12 Feb 2020 01:00:31 +0000 (02:00 +0100)
nixos/modules.nix
nixos/modules/services/networking/knot.nix [new file with mode: 0644]
servers/mermet.nix
servers/mermet/knot.nix [new file with mode: 0644]
servers/mermet/knot/sourcephile.fr.nix [new file with mode: 0644]
servers/mermet/production/lesptts.nix
servers/mermet/production/shorewall.nix
servers/mermet/rspamd/sourcephile.fr.nix
servers/mermet/unbound.nix
shell.nix

index 56de0a0d7da99d3d6269c2f18db0e8314f1f74f0..3cb10e028294047cddaed83ecebbe32210db1fba 100644 (file)
@@ -4,8 +4,12 @@
 {
 imports = [
   modules/services/networking/domains.nix
+  modules/services/networking/knot.nix
   modules/services/databases/openldap.nix
 ];
+disabledModules = [
+  "services/networking/knot.nix"
+];
 }
 
 /*
diff --git a/nixos/modules/services/networking/knot.nix b/nixos/modules/services/networking/knot.nix
new file mode 100644 (file)
index 0000000..05060db
--- /dev/null
@@ -0,0 +1,98 @@
+{ 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
+  '';
+  keymgr = pkgs.writeShellScriptBin "keymgr" ''
+    ${pkgs.systemd}/bin/systemd-run --pipe \
+     --uid knot --working-directory="$PWD" \
+     -p DynamicUser=yes -p StateDirectory=knot \
+    ${cfg.package}/bin/keymgr --config=${configFile} "$@"
+  '';
+  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}"
+      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;
+        defaultText = "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 keymgr ];
+  };
+}
index c39975e18523168af25b9d232d99a2080b923cce..a9b1fb8f1a6a09630842f06d88cf94e395562b3d 100644 (file)
@@ -22,7 +22,8 @@ in
   imports =
     [ ../nixos/defaults.nix
       mermet/unbound.nix
-      mermet/nsd.nix
+      #mermet/nsd.nix
+      mermet/knot.nix
       mermet/openldap.nix
       mermet/gitolite.nix
       mermet/nginx.nix
diff --git a/servers/mermet/knot.nix b/servers/mermet/knot.nix
new file mode 100644 (file)
index 0000000..3fc3331
--- /dev/null
@@ -0,0 +1,114 @@
+{ pkgs, lib, config, ... }:
+let
+  inherit (lib) types;
+  inherit (config.services) knot;
+in
+{
+imports = [
+  knot/sourcephile.fr.nix
+];
+options.services.knot = {
+  zones = lib.mkOption {
+    default = {};
+    type = types.attrsOf (types.submodule ({domain, ...}: {
+      #config.domain = lib.mkDefault domain;
+      options = {
+        conf = lib.mkOption {
+          type = types.lines;
+        };
+        data = lib.mkOption {
+          type = types.nullOr types.lines;
+        };
+      };
+    }));
+  };
+};
+config = {
+systemd.services.knot.preStart = lib.concatStringsSep "\n" (lib.mapAttrsToList (domain: {data, ...}:
+  lib.optionalString (data != null) ''
+    install -D -o knot -g knot -m 700 ${pkgs.writeText "${domain}.zone" data} /var/lib/knot/zones/${domain}.zone
+  '') knot.zones);
+/*
+systemd.services.knot.postStart = lib.mkAfter ''
+  PATH="/run/current-system/sw/bin:$PATH"
+  knotc zone-freeze ${domain}.
+  while ! knotc zone-status ${domain}. +freeze | grep -q 'freeze: yes'; do sleep 1; done
+  knotc zone-flush ${domain}.
+  install -o knot -g knot -m 700 ${zone} /var/lib/knot/signed/${domain}.zone
+  knotc zone-reload ${domain}.
+  knotc zone-thaw ${domain}.
+'';
+*/
+services.knot = {
+  enable = true;
+  extraArgs = [ "-v" ];
+  # https://www.knot-dns.cz/docs/2.6/html/reference.html
+  extraConfig = ''
+    template:
+      - id: default
+        dnssec-signing: off
+        # move databases below the state directory, because they need to be writable
+        storage: /var/lib/knot/zones
+        # 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
+
+    database:
+        journal-db: /var/lib/knot/journal
+        kasp-db: /var/lib/knot/kasp
+        timer-db: /var/lib/knot/timer
+
+    log:
+      - target: syslog
+        any: info
+
+    remote:
+      - id: local_resolver
+        address: 127.0.0.1@53
+
+      - id: secondary_gandi
+        address: 217.70.177.40@53
+
+    submission:
+      - id: dnssec_validating_resolver
+        parent: local_resolver
+
+    policy:
+      - id: rsa
+        single-type-signing: false
+        ksk-shared: false
+        algorithm: RSASHA256
+        ksk-size: 4096
+        zsk-size: 2048
+        zsk-lifetime: 30d
+        ksk-lifetime: 365d
+        ksk-submission: dnssec_validating_resolver
+
+      - id: ed25519
+        single-type-signing: false
+        ksk-shared: false
+        algorithm: ED25519
+        ksk-size: 256
+        zsk-size: 256
+        zsk-lifetime: 30d
+        ksk-lifetime: 365d
+        cds-cdnskey-publish: always
+        ksk-submission: dnssec_validating_resolver
+
+    acl:
+      - id: acl_localhost
+        address: 127.0.0.1
+        action: transfer
+
+      # DOC: https://docs.gandi.net/en/domain_names/advanced_users/secondary_nameserver.html
+      - id: acl_gandi
+        address: 217.70.177.40
+        action: transfer
+
+  '' + lib.concatStringsSep "\n" (lib.mapAttrsToList (domain: {conf, ...}: conf) knot.zones);
+};
+};
+}
diff --git a/servers/mermet/knot/sourcephile.fr.nix b/servers/mermet/knot/sourcephile.fr.nix
new file mode 100644 (file)
index 0000000..b2d8639
--- /dev/null
@@ -0,0 +1,72 @@
+{ pkgs, lib, config, ... }:
+with builtins;
+let
+  inherit (builtins.extraBuiltins) pass git;
+  inherit (pkgs.lib) unlinesAttrs types;
+  inherit (config) networking;
+  inherit (config.services) knot;
+  # Use the Git commit time of the ${domain}.nix file to set the serial number.
+  # WARNING: the ${domain}.nix must be committed into Git for this to work.
+  # WARNING: this does not take other .nix into account, though they may contribute to the zone's data.
+  serial = domain: toString (git ./. [ "log" "-1" "--format=%ct" "--" (domain + ".nix") ]);
+  mermetIPv4 = "80.67.180.129";
+  domain = "sourcephile.fr";
+  # TODO: increase the TTL once things have settled down
+in
+{
+services.knot.zones."${domain}" = {
+  conf = ''
+    zone:
+      - domain: ${domain}
+        file: ${domain}.zone
+        serial-policy: increment
+        semantic-checks: on
+        notify: secondary_gandi
+        acl: acl_gandi
+        acl: acl_localhost
+        dnssec-signing: on
+        dnssec-policy: rsa
+  '';
+  data = ''
+    $ORIGIN ${domain}.
+    $TTL 500
+
+    ; SOA (Start Of Authority)
+    @ SOA ns admin (
+      ${serial domain} ; Serial number
+      24h   ; Refresh
+      15m   ; Retry
+      1000h ; Expire (1000h)
+      1d    ; Negative caching
+    )
+
+    ; NS (Name Server)
+    @ NS ns
+    @ NS ns6.gandi.net.
+
+    ; A (DNS -> IPv4)
+    @ A ${mermetIPv4}
+    mermet     A ${mermetIPv4}
+    autoconfig A ${mermetIPv4}
+    code       A ${mermetIPv4}
+    git        A ${mermetIPv4}
+    imap       A ${mermetIPv4}
+    mail       A ${mermetIPv4}
+    ns         A ${mermetIPv4}
+    pop        A ${mermetIPv4}
+    smtp       A ${mermetIPv4}
+    submission A ${mermetIPv4}
+    www        A ${mermetIPv4}
+
+    ; SPF (Sender Policy Framework)
+    @ 3600 IN SPF "v=spf1 mx ip4:${mermetIPv4} -all"
+    @ 3600 IN TXT "v=spf1 mx ip4:${mermetIPv4} -all"
+
+    ; MX (Mail eXchange)
+    @ 180 MX 5 mail
+
+    ; SRV (SeRVice)
+    _git._tcp.git 18000 IN SRV 0 0 9418 git
+  '';
+};
+}
index 969d777656b4267a38601a578eddb5a353bc2cf0..20095d5860822ada92c1efa35c522556d4d7ce10 100644 (file)
@@ -117,7 +117,12 @@ in
   # (though / may still be encrypted at this point).
   # boot.kernelParams = [ "boot.shell_on_fail" ];
 
-  services.nsd.interfaces = [ netIPv4 ];
+  #services.nsd.interfaces = [ netIPv4 ];
+  services.knot.extraConfig = lib.mkBefore ''
+    server:
+      listen: ${netIPv4}@53
+      #listen: ::@53
+  '';
   networking = {
     useDHCP = false;
     defaultGateway = {
index 1bb679c8cac61cafe3b6c0d195cf322ec8afbda8..16159bb57214831e3d0b0fdc737d787ef14271cf 100644 (file)
@@ -10,8 +10,9 @@ let
 
     # By port
     DNS(ACCEPT)    $FW net {user=${users.users.unbound.name}}
+    DNS(ACCEPT)    $FW net:217.70.177.40 # for knot to notify ns6.gandi.net
     Git(ACCEPT)    $FW net
-    HKP(ACCEPT)    $FW net {user=julm}
+    HKP(ACCEPT)    $FW net {user=${users.users.julm.name}}
     HTTP(ACCEPT)   $FW net
     HTTPS(ACCEPT)  $FW net
     SMTP(ACCEPT)   $FW net
index 33743c2491a43ca48404cc1586d5d5d75f868ebc..b4992643b7ff59db6fd3f448997758523175e7be 100644 (file)
@@ -1,6 +1,7 @@
 { pkgs, lib, config, ... }:
 let
   inherit (builtins.extraBuiltins) pass;
+  inherit (lib) types;
   inherit (config.services) rspamd;
   domain = "sourcephile.fr";
   selector = "20200101";
@@ -19,6 +20,16 @@ services.rspamd.dkimSelectorMap = ''
   mermet    ${selector}
   ${domain} ${selector}
 '';
+services.knot.zones."${domain}".data = ''
+  20200101._domainkey IN TXT ( "v=DKIM1; k=rsa; "
+    "p=MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7EKzverbG+5JF+yFjH3MrxLyauiHyLqBbV/8LEMunoKXF8sqhBpQtAQXruLqsyUkxR/4CAyPMyzmcdrU43boMj9yFqLrg/kEz2RIvai9jXBqRoWRW1y7F0LbZmdtOTncuDSP8Zzo02XUzsOC4f/C3tEQHS5rc"
+    "hzfhU5FY1CeO6eBMV79qKBOvGMKahQTrrtU6olAAJxOhn6wRuwSf"
+    "+m3on1OqiuXYYIgNHKdRhJ8gDwIm/3LEpYMD0gTgJiyclCLoLGHGtKZy1Wf9xV9/7V6fHE4JW5SDivwslVTL+KPXOlIpo5NDHpMxPYOcIg2K4Rj/j7jhavo+fG43q1LhwaPkEMQMbplgnjeMY8300odRiklTkMMpH0m35ZNeHQJSRpEtV8y5xUNxVaGzfqX5iStwV/mQ1Kn"
+    "ZSe8ORTNq+eTTFnDk6zdUXjagcf0wO6QsSTeAz/G8CqOBbwmrU+q"
+    "F8WbGAeRnhz51mH6fTTfsQ1nwjAiF4ou+eQGTkTMN23KkCKpuozJnxqx4DCEr6J1bL83fhXw7CgcfgKgTOk/HFJpeiGhqodw18r4DWBA6G57z9utm7Mr/9SoVnMq6iK9iEcbCllLR8Sz4viatLSRzhodbk7hfvXS3jmCFjILAjFmA7aMTemDMBDQhpAGF9F8sjFUbEJIZjK"
+    "rWWtSTdO8DilDqN8CAwEAAQ=="
+  )
+'';
 services.nsd.zones."${domain}".data = ''
   20200101._domainkey IN TXT ( "v=DKIM1; k=rsa; "
     "p=MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7EKzverbG+5JF+yFjH3MrxLyauiHyLqBbV/8LEMunoKXF8sqhBpQtAQXruLqsyUkxR/4CAyPMyzmcdrU43boMj9yFqLrg/kEz2RIvai9jXBqRoWRW1y7F0LbZmdtOTncuDSP8Zzo02XUzsOC4f/C3tEQHS5rc"
index 8bf9b1d3317405860e37ffdd1f4906344e693c42..f7d3bd45ac7935cbf65fb343d81cc74a3845b5f2 100644 (file)
@@ -9,6 +9,9 @@ let inherit (config) users; in
       port: 53
       verbosity: 1
 
+      server:
+        log-queries: no
+
       # The file which contains the listing of primary root DNS servers. 
       # To be updated once every six months.
       root-hints: /var/lib/unbound/named.root
index 20cd0b224300ed96112ccd46c91bc48b38399db9..c142be8d2f9940f1f0d5d945420e4cadeb213621 100644 (file)
--- a/shell.nix
+++ b/shell.nix
@@ -30,11 +30,13 @@ let
       sha256 = "15zs2146zh54jg1gywrcwyqxpx7izc35vlakk3cvrlqwwsvlr2rf";
     }
   ];
+  localNixpkgsPatches = [
+  ];
   nixpkgs = originPkgs.stdenv.mkDerivation {
     name = "nixpkgs-patched";
     src = originNixpkgs;
     phases = [ "unpackPhase" "patchPhase" ];
-    patches = map originPkgs.fetchpatch nixpkgsPatches;
+    patches = map originPkgs.fetchpatch nixpkgsPatches ++ localNixpkgsPatches;
     postPatch = ''
       patch=$(printf '%s\n' ${builtins.concatStringsSep " " (map (p: p.sha256) nixpkgsPatches)} |
         sort | sha256sum | cut -c -7)