]> git.immae.eu Git - perso/Immae/Config/Nix.git/blobdiff - systems/eldiron/dns.nix
Add dnssec
[perso/Immae/Config/Nix.git] / systems / eldiron / dns.nix
index 486fcc1ad4aa49dfac69d5afc5840048c40cc0b2..7645b69494261501914de5d40f80e977234104a8 100644 (file)
@@ -1,4 +1,18 @@
 { lib, pkgs, config, dns-nix, ... }:
+let
+  zonesWithDNSSec = lib.filterAttrs (k: v: v.dnssec.enable) config.myServices.dns.zones;
+  zoneToFile = name: v: pkgs.runCommand "${name}.zone" {
+    text = v;
+    passAsFile = [ "text" ];
+    # Automatically change the increment when relevant change
+    # happened (both serial and mta-sts)
+  } ''
+    mv "$textPath" $out
+    increment=$(( 100*($(date -u +%-H) * 60 + $(date -u +%-M))/1440 ))
+    sed -i -e "s/2022121902/$(date -u +%Y%m%d)$increment/g" $out
+    sed -i -e "s/20200109150200Z/$(date -u +%Y%m%d%H%M%SZ)/g" $out
+  '';
+in
 {
   options.myServices.dns = {
     enable = lib.mkEnableOption "enable DNS resolver";
         servers = config.myEnv.servers;
         ips = i: { A = i.ip4; AAAA = i.ip6; };
         letsencrypt = [ { tag = "issue"; value = "letsencrypt.org"; issuerCritical = false; } ];
-        toKV = a: builtins.concatStringsSep ";" (builtins.attrValues (builtins.mapAttrs (n: v: "${n}=${v}") a));
+        toKV = a: let
+          removeOrder = n: lib.last (builtins.split "__" n);
+        in
+          builtins.concatStringsSep ";" (builtins.attrValues (builtins.mapAttrs (n: v: "${removeOrder n}=${v}") a));
         mailMX = {
           hasEmail = true;
           subdomains = let
           SOA = {
             # yyyymmdd?? (increment ?? at each change)
             serial = 2022121902; # Don't change this value, it is replaced automatically!
-            refresh = 10800;
-            retry = 3600;
-            expire = 604800;
-            minimum = 10800; # negative cache ttl
+            refresh = 3*60*60;
+            retry = 60*60;
+            expire = 14*24*60*60;
+            minimum = 3*60*60; # negative cache ttl
             adminEmail = "hostmaster@immae.eu"; #email-address s/@/./
             nameServer = "ns1.immae.eu.";
           };
@@ -42,7 +59,7 @@
             (toKV config.myEnv.mail.dkim.immae_eu.public)
           ];
         };
-        mailCommon = name: {
+        mailCommon = name: quarantine: {
           MX = let
             mxes = lib.filterAttrs (n: v: v ? mx && v.mx.enable) servers;
           in
             # MTA-STS
             # https://blog.delouw.ch/2018/12/16/using-mta-sts-to-enhance-email-transport-security-and-privacy/
             # https://support.google.com/a/answer/9261504
-            _mta-sts.TXT = [ (toKV { v = "STSv1"; id = "20200109150200Z"; }) ]; # Don't change this value, it is updated automatically!
-            _tls.subdomains._smtp.TXT = [ (toKV { v = "TLSRPTv1"; "rua" = "mailto:postmaster+mta-sts@immae.eu"; }) ];
+            _mta-sts.TXT = [ (toKV { _00__v = "STSv1"; id = "20200109150200Z"; }) ]; # Don't change this value, it is updated automatically!
+            _tls.subdomains._smtp.TXT = [ (toKV { _00__v = "TLSRPTv1"; rua = "mailto:postmaster+mta-sts@immae.eu"; }) ];
             mta-sts = ips servers.eldiron.ips.main;
 
             # DMARC
-            _dmarc.TXT = [ (toKV { v = "DMARC1"; p = "none"; adkim = "r"; aspf = "r"; fo = "1"; rua = "mailto:postmaster+rua@immae.eu"; ruf = "mailto:postmaster+ruf@immae.eu"; }) ];
+            # p needs to be the first tag
+            _dmarc.TXT = [ (toKV { _00__v = "DMARC1"; _01__p = if quarantine then "quarantine" else "none"; adkim = "s"; aspf = "s"; fo = "1"; rua = "mailto:postmaster+rua@immae.eu"; ruf = "mailto:postmaster+ruf@immae.eu"; }) ];
           };
 
           # SPF
-          TXT = [ (toKV { v = "spf1 mx ~all"; }) ];
+          TXT = [ (toKV { _00__v = "spf1 mx ~all"; }) ];
         };
       };
     };
         dns-nix.lib.types.zone.getSubModules ++ [
           ({ name, ... }: {
             options = {
+              dnssec = lib.mkOption {
+                default.enable = false;
+                type = lib.types.submodule {
+                  options = {
+                    enable = lib.mkEnableOption "Configure dnssec for this domain";
+                  };
+                };
+              };
               hasEmail = lib.mkEnableOption "This domain has e-mails configuration";
               emailPolicies = lib.mkOption {
                 default = {};
         zoneHeader
         (ips servers.eldiron.ips.main)
         {
-          ns = [ "immae" ];
+          dnssec.enable = true;
+          ns = [ "immae" "raito" ];
           CAA = letsencrypt;
+          extraConfig = ''
+            notify yes;
+          '';
+          slaves = [ "raito" ];
         }
       ];
       "immae.dev" = lib.mkMerge [
         {
+          dnssec.enable = true;
           extraConfig = ''
             notify yes;
           '';
       ];
       "immae.eu" = lib.mkMerge [
         {
+          dnssec.enable = true;
           extraConfig = ''
             notify yes;
           '';
         zoneHeader
         (ips servers.eldiron.ips.production)
         {
-          ns = [ "immae" "raito" ];
+          ns = [ "immae" ];
+          # Cannot put ns2.immae.eu as glue record as it takes ages to propagate.
+          # And gandi only accepts NS records with glues in their interface
+          NS = [ "kurisu.dual.lahfa.xyz." ];
           CAA = letsencrypt;
 
           # ns1 has glue records in gandi.net
         {
           # Machines local users
           emailPolicies.localhost.receive = false;
-          subdomains.localhost = lib.mkMerge [ (mailCommon "immae.eu") mailSend ];
+          subdomains.localhost = lib.mkMerge [ (mailCommon "immae.eu" true) mailSend ];
           emailPolicies.eldiron.receive = true;
-          subdomains.eldiron = lib.mkMerge [ (mailCommon "immae.eu") mailSend ];
+          subdomains.eldiron = lib.mkMerge [ (mailCommon "immae.eu" true) mailSend ];
         }
         {
           # For each server "server" and each server ip group "ipgroup",
       zones =
         builtins.mapAttrs (name: v: {
           master = true;
-          extraConfig = v.extraConfig;
+          extraConfig = v.extraConfig + lib.optionalString v.dnssec.enable ''
+            key-directory "/var/lib/named/dnssec_keys";
+            dnssec-policy default;
+            inline-signing yes;
+          '';
           masters = [];
           slaves =
             lib.flatten (map (n: builtins.attrValues config.myEnv.dns.ns.${n}) v.slaves);
-          file = pkgs.runCommand "${name}.zone" {
-            text = v;
-            passAsFile = [ "text" ];
-            # Automatically change the increment when relevant change
-            # happened (both serial and mta-sts)
-          } ''
-            mv "$textPath" $out
-            increment=$(( 100*($(date -u +%-H) * 60 + $(date -u +%-M))/1440 ))
-            sed -i -e "s/2022121902/$(date -u +%Y%m%d)$increment/g" $out
-            sed -i -e "s/20200109150200Z/$(date -u +%Y%m%d%H%M%SZ)/g" $out
-          '';
+          file = if v.dnssec.enable then "/var/run/named/dnssec-${name}.zone" else zoneToFile name v;
         }) config.myServices.dns.zones;
     };
+    systemd.services.bind.serviceConfig.StateDirectory = "named";
+    systemd.services.bind.preStart = lib.mkAfter
+      (builtins.concatStringsSep "\n" (lib.mapAttrsToList (name: v: ''
+        install -m444 ${zoneToFile name v} /var/run/named/dnssec-${name}.zone
+      '') zonesWithDNSSec) + ''
+        install -dm755 -o named /var/lib/named/dnssec_keys
+      '');
     myServices.monitoring.fromMasterActivatedPlugins = [ "dns" ];
     myServices.monitoring.fromMasterObjects.service = lib.mkMerge (lib.mapAttrsToList (name: z:
       lib.optional (builtins.elem "immae" z.ns) {
         servicegroups = "webstatus-dns";
         _webstatus_name = name;
       } ++
-      lib.optional (builtins.elem "raito" z.ns) {
-        service_description = "raito dns is active and authoritative for ${name}";
+      lib.optionals (builtins.elem "raito" z.ns) [
+        {
+          service_description = "raito dns is active and authoritative for ${name}";
+          host_name = config.hostEnv.fqdn;
+          use = "dns-service";
+          check_command = ["check_external_dns" "kurisu.dual.lahfa.xyz" name "-A"];
+
+          servicegroups = "webstatus-dns";
+          _webstatus_name = "${name} (Secondary DNS Raito)";
+        }
+        {
+          service_description = "raito dns is up to date for ${name}";
+          host_name = config.hostEnv.fqdn;
+          use = "dns-service";
+          check_command = ["check_dns_soa" "kurisu.dual.lahfa.xyz" name config.hostEnv.fqdn];
+
+          servicegroups = "webstatus-dns";
+          _webstatus_name = "${name} (Secondary DNS Raito up to date)";
+        }
+      ] ++
+      lib.optional z.dnssec.enable {
+        service_description = "DNSSEC is active and not expired for ${name}";
         host_name = config.hostEnv.fqdn;
         use = "dns-service";
-        check_command = ["check_external_dns" "kurisu.dual.lahfa.xyz" name "-A"];
+        check_command = ["check_dnssec" name];
 
         servicegroups = "webstatus-dns";
-        _webstatus_name = "${name} (Secondary DNS Raito)";
+        _webstatus_name = "${name} (DNSSEC)";
       }
     ) config.myServices.dns.zones);
   };