]> git.immae.eu Git - perso/Immae/Config/Nix.git/blobdiff - systems/eldiron/dns.nix
Squash changes containing private information
[perso/Immae/Config/Nix.git] / systems / eldiron / dns.nix
diff --git a/systems/eldiron/dns.nix b/systems/eldiron/dns.nix
new file mode 100644 (file)
index 0000000..486fcc1
--- /dev/null
@@ -0,0 +1,290 @@
+{ lib, pkgs, config, dns-nix, ... }:
+{
+  options.myServices.dns = {
+    enable = lib.mkEnableOption "enable DNS resolver";
+    helpers = lib.mkOption {
+      readOnly = true;
+      description = ''
+        Some useful constants or functions for zones definition
+      '';
+      default = rec {
+        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));
+        mailMX = {
+          hasEmail = true;
+          subdomains = let
+            mxes = lib.filterAttrs (n: v: v ? mx && v.mx.enable) servers;
+          in
+            lib.mapAttrs' (n: v: lib.nameValuePair v.mx.subdomain (ips v.ips.main)) mxes;
+        };
+        zoneHeader = {
+          TTL = 3*60*60;
+          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
+            adminEmail = "hostmaster@immae.eu"; #email-address s/@/./
+            nameServer = "ns1.immae.eu.";
+          };
+        };
+        mailSend = {
+          # DKIM
+          subdomains._domainkey.subdomains.eldiron.TXT = [
+            (toKV config.myEnv.mail.dkim.eldiron.public)
+          ];
+          # old key, may still be used by verifiers
+          subdomains._domainkey.subdomains.immae_eu.TXT = [
+            (toKV config.myEnv.mail.dkim.immae_eu.public)
+          ];
+        };
+        mailCommon = name: {
+          MX = let
+            mxes = lib.filterAttrs (n: v: v ? mx && v.mx.enable) servers;
+          in
+            lib.mapAttrsToList (n: v: { preference = v.mx.priority; exchange = "${v.mx.subdomain}.${name}."; }) mxes;
+
+          # https://tools.ietf.org/html/rfc6186
+          SRV = [
+            { service = "submission"; proto = "tcp"; priority = 0; weight = 1; port = 587; target = "smtp.immae.eu."; }
+            { service = "submissions"; proto = "tcp"; priority = 0; weight = 1; port = 465; target = "smtp.immae.eu."; }
+
+            { service = "imap"; proto = "tcp"; priority = 0; weight = 1; port = 143; target = "imap.immae.eu."; }
+            { service = "imaps"; proto = "tcp"; priority = 0; weight = 1; port = 993; target = "imap.immae.eu."; }
+            { service = "sieve"; proto = "tcp"; priority = 0; weight = 1; port = 4190; target = "imap.immae.eu."; }
+
+            { service = "pop3"; proto = "tcp"; priority = 10; weight = 1; port = 110; target = "pop3.immae.eu."; }
+            { service = "pop3s"; proto = "tcp"; priority = 10; weight = 1; port = 995; target = "pop3.immae.eu."; }
+          ];
+
+          subdomains = {
+            # 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 = 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"; }) ];
+          };
+
+          # SPF
+          TXT = [ (toKV { v = "spf1 mx ~all"; }) ];
+        };
+      };
+    };
+    zones = lib.mkOption {
+      type = lib.types.attrsOf (dns-nix.lib.types.zone.substSubModules (
+        dns-nix.lib.types.zone.getSubModules ++ [
+          ({ name, ... }: {
+            options = {
+              hasEmail = lib.mkEnableOption "This domain has e-mails configuration";
+              emailPolicies = lib.mkOption {
+                default = {};
+                type = lib.types.attrsOf (lib.types.submodule {
+                  options = {
+                    receive = lib.mkEnableOption "Configure this domain to receive e-mail";
+                  };
+                });
+                apply = builtins.mapAttrs (n: v: v // {
+                  domain = name;
+                  fqdn = if n == "" then name else "${n}.${name}";
+                });
+              };
+              extraConfig = lib.mkOption {
+                type = lib.types.lines;
+                description = "Extra zone configuration for bind";
+                example = ''
+                  notify yes;
+                '';
+                default = "";
+              };
+              slaves = lib.mkOption {
+                type = lib.types.listOf lib.types.str;
+                description = "NS slave groups of this zone";
+                default = [];
+              };
+              ns = lib.mkOption {
+                type = lib.types.listOf lib.types.str;
+                default = [];
+              };
+            };
+          })
+        ]));
+      apply = let
+          toNS = n: builtins.map (d: "${d}.") (builtins.concatMap (s: builtins.attrNames config.myEnv.dns.ns."${s}") n);
+        in
+          builtins.mapAttrs (n: v: v // { NS = v.NS or [] ++ toNS (v.ns); });
+      default = {};
+      description = ''
+        attrset of zones to configure
+      '';
+    };
+  };
+  config = let
+    cfg = config.services.bind;
+  in lib.mkIf config.myServices.dns.enable {
+    myServices.chatonsProperties.hostings.dns-secondaire = {
+      file.datetime = "2022-08-22T02:00:00";
+      hosting = {
+        name = "DNS secondaire";
+        description = "DNS secondaire";
+        website = "ns1.immae.eu";
+        status.level = "OK";
+        status.description = "OK";
+        registration.load = "OPEN";
+        install.type = "PACKAGE";
+      };
+      software = {
+        name = "bind9";
+        website = pkgs.bind.meta.homepage;
+        license.url = pkgs.bind.meta.license.url;
+        license.name = pkgs.bind.meta.license.fullName;
+        version = pkgs.bind.version;
+        source.url = "https://www.isc.org/download/";
+      };
+    };
+    myServices.dns.zones = with config.myServices.dns.helpers; {
+      "imsite.eu" = lib.mkMerge [
+        zoneHeader
+        (ips servers.eldiron.ips.main)
+        {
+          ns = [ "immae" ];
+          CAA = letsencrypt;
+        }
+      ];
+      "immae.dev" = lib.mkMerge [
+        {
+          extraConfig = ''
+            notify yes;
+          '';
+          slaves = [ "raito" ];
+        }
+        zoneHeader
+        (ips servers.eldiron.ips.integration)
+        {
+          ns = [ "immae" "raito" ];
+          CAA = letsencrypt;
+        }
+      ];
+      "immae.eu" = lib.mkMerge [
+        {
+          extraConfig = ''
+            notify yes;
+          '';
+          slaves = [ "raito" ];
+        }
+        zoneHeader
+        (ips servers.eldiron.ips.production)
+        {
+          ns = [ "immae" "raito" ];
+          CAA = letsencrypt;
+
+          # ns1 has glue records in gandi.net
+          subdomains.ns1 = ips servers.eldiron.ips.main;
+          # raito / kurisu.dual.lahfa.xyz ; replace with eldiron in case of problem
+          subdomains.ns2.A = builtins.map (address: { inherit address; ttl = 600; }) servers.eldiron.ips.main.ip4;
+          subdomains.ns2.AAAA = builtins.map (address: { inherit address; ttl = 600; }) servers.eldiron.ips.main.ip6;
+        }
+        {
+          # Machines local users
+          emailPolicies.localhost.receive = false;
+          subdomains.localhost = lib.mkMerge [ (mailCommon "immae.eu") mailSend ];
+          emailPolicies.eldiron.receive = true;
+          subdomains.eldiron = lib.mkMerge [ (mailCommon "immae.eu") mailSend ];
+        }
+        {
+          # For each server "server" and each server ip group "ipgroup",
+          # define ipgroup.server.immae.eu
+          # "main" is set as server.immae.eu instead
+          # if main has an "alias", it is duplicated with this alias.
+          # If the server is a vm, use the v.immae.eu namespace (only main is created)
+          subdomains = let
+            vms = lib.filterAttrs (n: v: v.isVm) servers;
+            bms = lib.filterAttrs (n: v: !v.isVm) servers;
+            toIps = type: builtins.mapAttrs (n: v: ips v.ips."${type}");
+          in
+            lib.mkMerge [
+              (toIps "main" bms)
+
+              { v.subdomains = toIps "main" vms; }
+
+              (lib.mapAttrs (_: v: {
+                subdomains = lib.mapAttrs'
+                  (n': v': lib.nameValuePair "${if v'.alias == null then n' else v'.alias}" (ips v'))
+                  (lib.filterAttrs (n': v': n' != "main" || v'.alias != null) v.ips);
+                }) bms)
+            ];
+        }
+        {
+          # Outils
+          subdomains = {
+            status = ips servers.monitoring-1.ips.main;
+          };
+        }
+      ];
+    };
+    networking.firewall.allowedUDPPorts = [ 53 ];
+    networking.firewall.allowedTCPPorts = [ 53 ];
+    users.users.named.extraGroups = [ "keys" ];
+    services.bind = {
+      enable = true;
+      cacheNetworks = ["any"];
+      extraOptions = ''
+        allow-recursion { 127.0.0.1; };
+        allow-transfer  { none; };
+
+        notify-source    ${lib.head config.myEnv.servers.eldiron.ips.main.ip4};
+        notify-source-v6 ${lib.head config.myEnv.servers.eldiron.ips.main.ip6};
+        version   none;
+        hostname  none;
+        server-id none;
+        '';
+      zones =
+        builtins.mapAttrs (name: v: {
+          master = true;
+          extraConfig = v.extraConfig;
+          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
+          '';
+        }) config.myServices.dns.zones;
+    };
+    myServices.monitoring.fromMasterActivatedPlugins = [ "dns" ];
+    myServices.monitoring.fromMasterObjects.service = lib.mkMerge (lib.mapAttrsToList (name: z:
+      lib.optional (builtins.elem "immae" z.ns) {
+        service_description = "eldiron dns is active and authoritative for ${name}";
+        host_name = config.hostEnv.fqdn;
+        use = "dns-service";
+        check_command = ["check_dns" name "-A"];
+
+        servicegroups = "webstatus-dns";
+        _webstatus_name = name;
+      } ++
+      lib.optional (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)";
+      }
+    ) config.myServices.dns.zones);
+  };
+}