1 { lib, pkgs, config, dns-nix, ... }:
3 options.myServices.dns = {
4 enable = lib.mkEnableOption "enable DNS resolver";
5 helpers = lib.mkOption {
8 Some useful constants or functions for zones definition
11 servers = config.myEnv.servers;
12 ips = i: { A = i.ip4; AAAA = i.ip6; };
13 letsencrypt = [ { tag = "issue"; value = "letsencrypt.org"; issuerCritical = false; } ];
14 toKV = a: builtins.concatStringsSep ";" (builtins.attrValues (builtins.mapAttrs (n: v: "${n}=${v}") a));
18 mxes = lib.filterAttrs (n: v: v ? mx && v.mx.enable) servers;
20 lib.mapAttrs' (n: v: lib.nameValuePair v.mx.subdomain (ips v.ips.main)) mxes;
25 # yyyymmdd?? (increment ?? at each change)
26 serial = 2022121902; # Don't change this value, it is replaced automatically!
30 minimum = 10800; # negative cache ttl
31 adminEmail = "hostmaster@immae.eu"; #email-address s/@/./
32 nameServer = "ns1.immae.eu.";
37 subdomains._domainkey.subdomains.eldiron.TXT = [
38 (toKV config.myEnv.mail.dkim.eldiron.public)
40 # old key, may still be used by verifiers
41 subdomains._domainkey.subdomains.immae_eu.TXT = [
42 (toKV config.myEnv.mail.dkim.immae_eu.public)
47 mxes = lib.filterAttrs (n: v: v ? mx && v.mx.enable) servers;
49 lib.mapAttrsToList (n: v: { preference = v.mx.priority; exchange = "${v.mx.subdomain}.${name}."; }) mxes;
51 # https://tools.ietf.org/html/rfc6186
53 { service = "submission"; proto = "tcp"; priority = 0; weight = 1; port = 587; target = "smtp.immae.eu."; }
54 { service = "submissions"; proto = "tcp"; priority = 0; weight = 1; port = 465; target = "smtp.immae.eu."; }
56 { service = "imap"; proto = "tcp"; priority = 0; weight = 1; port = 143; target = "imap.immae.eu."; }
57 { service = "imaps"; proto = "tcp"; priority = 0; weight = 1; port = 993; target = "imap.immae.eu."; }
58 { service = "sieve"; proto = "tcp"; priority = 0; weight = 1; port = 4190; target = "imap.immae.eu."; }
60 { service = "pop3"; proto = "tcp"; priority = 10; weight = 1; port = 110; target = "pop3.immae.eu."; }
61 { service = "pop3s"; proto = "tcp"; priority = 10; weight = 1; port = 995; target = "pop3.immae.eu."; }
66 # https://blog.delouw.ch/2018/12/16/using-mta-sts-to-enhance-email-transport-security-and-privacy/
67 # https://support.google.com/a/answer/9261504
68 _mta-sts.TXT = [ (toKV { v = "STSv1"; id = "20200109150200Z"; }) ]; # Don't change this value, it is updated automatically!
69 _tls.subdomains._smtp.TXT = [ (toKV { v = "TLSRPTv1"; "rua" = "mailto:postmaster+mta-sts@immae.eu"; }) ];
70 mta-sts = ips servers.eldiron.ips.main;
73 _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"; }) ];
77 TXT = [ (toKV { v = "spf1 mx ~all"; }) ];
81 zones = lib.mkOption {
82 type = lib.types.attrsOf (dns-nix.lib.types.zone.substSubModules (
83 dns-nix.lib.types.zone.getSubModules ++ [
86 hasEmail = lib.mkEnableOption "This domain has e-mails configuration";
87 emailPolicies = lib.mkOption {
89 type = lib.types.attrsOf (lib.types.submodule {
91 receive = lib.mkEnableOption "Configure this domain to receive e-mail";
94 apply = builtins.mapAttrs (n: v: v // {
96 fqdn = if n == "" then name else "${n}.${name}";
99 extraConfig = lib.mkOption {
100 type = lib.types.lines;
101 description = "Extra zone configuration for bind";
107 slaves = lib.mkOption {
108 type = lib.types.listOf lib.types.str;
109 description = "NS slave groups of this zone";
113 type = lib.types.listOf lib.types.str;
120 toNS = n: builtins.map (d: "${d}.") (builtins.concatMap (s: builtins.attrNames config.myEnv.dns.ns."${s}") n);
122 builtins.mapAttrs (n: v: v // { NS = v.NS or [] ++ toNS (v.ns); });
125 attrset of zones to configure
130 cfg = config.services.bind;
131 in lib.mkIf config.myServices.dns.enable {
132 myServices.chatonsProperties.hostings.dns-secondaire = {
133 file.datetime = "2022-08-22T02:00:00";
135 name = "DNS secondaire";
136 description = "DNS secondaire";
137 website = "ns1.immae.eu";
139 status.description = "OK";
140 registration.load = "OPEN";
141 install.type = "PACKAGE";
145 website = pkgs.bind.meta.homepage;
146 license.url = pkgs.bind.meta.license.url;
147 license.name = pkgs.bind.meta.license.fullName;
148 version = pkgs.bind.version;
149 source.url = "https://www.isc.org/download/";
152 myServices.dns.zones = with config.myServices.dns.helpers; {
153 "imsite.eu" = lib.mkMerge [
155 (ips servers.eldiron.ips.main)
161 "immae.dev" = lib.mkMerge [
166 slaves = [ "raito" ];
169 (ips servers.eldiron.ips.integration)
171 ns = [ "immae" "raito" ];
175 "immae.eu" = lib.mkMerge [
180 slaves = [ "raito" ];
183 (ips servers.eldiron.ips.production)
185 ns = [ "immae" "raito" ];
188 # ns1 has glue records in gandi.net
189 subdomains.ns1 = ips servers.eldiron.ips.main;
190 # raito / kurisu.dual.lahfa.xyz ; replace with eldiron in case of problem
191 subdomains.ns2.A = builtins.map (address: { inherit address; ttl = 600; }) servers.eldiron.ips.main.ip4;
192 subdomains.ns2.AAAA = builtins.map (address: { inherit address; ttl = 600; }) servers.eldiron.ips.main.ip6;
195 # Machines local users
196 emailPolicies.localhost.receive = false;
197 subdomains.localhost = lib.mkMerge [ (mailCommon "immae.eu") mailSend ];
198 emailPolicies.eldiron.receive = true;
199 subdomains.eldiron = lib.mkMerge [ (mailCommon "immae.eu") mailSend ];
202 # For each server "server" and each server ip group "ipgroup",
203 # define ipgroup.server.immae.eu
204 # "main" is set as server.immae.eu instead
205 # if main has an "alias", it is duplicated with this alias.
206 # If the server is a vm, use the v.immae.eu namespace (only main is created)
208 vms = lib.filterAttrs (n: v: v.isVm) servers;
209 bms = lib.filterAttrs (n: v: !v.isVm) servers;
210 toIps = type: builtins.mapAttrs (n: v: ips v.ips."${type}");
215 { v.subdomains = toIps "main" vms; }
217 (lib.mapAttrs (_: v: {
218 subdomains = lib.mapAttrs'
219 (n': v': lib.nameValuePair "${if v'.alias == null then n' else v'.alias}" (ips v'))
220 (lib.filterAttrs (n': v': n' != "main" || v'.alias != null) v.ips);
227 status = ips servers.monitoring-1.ips.main;
232 networking.firewall.allowedUDPPorts = [ 53 ];
233 networking.firewall.allowedTCPPorts = [ 53 ];
234 users.users.named.extraGroups = [ "keys" ];
237 cacheNetworks = ["any"];
239 allow-recursion { 127.0.0.1; };
240 allow-transfer { none; };
242 notify-source ${lib.head config.myEnv.servers.eldiron.ips.main.ip4};
243 notify-source-v6 ${lib.head config.myEnv.servers.eldiron.ips.main.ip6};
249 builtins.mapAttrs (name: v: {
251 extraConfig = v.extraConfig;
254 lib.flatten (map (n: builtins.attrValues config.myEnv.dns.ns.${n}) v.slaves);
255 file = pkgs.runCommand "${name}.zone" {
257 passAsFile = [ "text" ];
258 # Automatically change the increment when relevant change
259 # happened (both serial and mta-sts)
262 increment=$(( 100*($(date -u +%-H) * 60 + $(date -u +%-M))/1440 ))
263 sed -i -e "s/2022121902/$(date -u +%Y%m%d)$increment/g" $out
264 sed -i -e "s/20200109150200Z/$(date -u +%Y%m%d%H%M%SZ)/g" $out
266 }) config.myServices.dns.zones;
268 myServices.monitoring.fromMasterActivatedPlugins = [ "dns" ];
269 myServices.monitoring.fromMasterObjects.service = lib.mkMerge (lib.mapAttrsToList (name: z:
270 lib.optional (builtins.elem "immae" z.ns) {
271 service_description = "eldiron dns is active and authoritative for ${name}";
272 host_name = config.hostEnv.fqdn;
274 check_command = ["check_dns" name "-A"];
276 servicegroups = "webstatus-dns";
277 _webstatus_name = name;
279 lib.optional (builtins.elem "raito" z.ns) {
280 service_description = "raito dns is active and authoritative for ${name}";
281 host_name = config.hostEnv.fqdn;
283 check_command = ["check_external_dns" "kurisu.dual.lahfa.xyz" name "-A"];
285 servicegroups = "webstatus-dns";
286 _webstatus_name = "${name} (Secondary DNS Raito)";
288 ) config.myServices.dns.zones);