]> git.immae.eu Git - perso/Immae/Config/Nix.git/blame - systems/eldiron/dns.nix
Migrate to borg backup
[perso/Immae/Config/Nix.git] / systems / eldiron / dns.nix
CommitLineData
1a64deeb 1{ lib, pkgs, config, dns-nix, ... }:
97787a9d
IB
2let
3 zonesWithDNSSec = lib.filterAttrs (k: v: v.dnssec.enable) config.myServices.dns.zones;
4 zoneToFile = name: v: pkgs.runCommand "${name}.zone" {
5 text = v;
6 passAsFile = [ "text" ];
7 # Automatically change the increment when relevant change
8 # happened (both serial and mta-sts)
9 } ''
10 mv "$textPath" $out
11 increment=$(( 100*($(date -u +%-H) * 60 + $(date -u +%-M))/1440 ))
12 sed -i -e "s/2022121902/$(date -u +%Y%m%d)$increment/g" $out
13 sed -i -e "s/20200109150200Z/$(date -u +%Y%m%d%H%M%SZ)/g" $out
14 '';
15in
1a64deeb
IB
16{
17 options.myServices.dns = {
18 enable = lib.mkEnableOption "enable DNS resolver";
19 helpers = lib.mkOption {
20 readOnly = true;
21 description = ''
22 Some useful constants or functions for zones definition
23 '';
24 default = rec {
25 servers = config.myEnv.servers;
26 ips = i: { A = i.ip4; AAAA = i.ip6; };
27 letsencrypt = [ { tag = "issue"; value = "letsencrypt.org"; issuerCritical = false; } ];
97787a9d
IB
28 toKV = a: let
29 removeOrder = n: lib.last (builtins.split "__" n);
30 in
31 builtins.concatStringsSep ";" (builtins.attrValues (builtins.mapAttrs (n: v: "${removeOrder n}=${v}") a));
1a64deeb
IB
32 mailMX = {
33 hasEmail = true;
34 subdomains = let
35 mxes = lib.filterAttrs (n: v: v ? mx && v.mx.enable) servers;
36 in
37 lib.mapAttrs' (n: v: lib.nameValuePair v.mx.subdomain (ips v.ips.main)) mxes;
38 };
39 zoneHeader = {
40 TTL = 3*60*60;
41 SOA = {
42 # yyyymmdd?? (increment ?? at each change)
43 serial = 2022121902; # Don't change this value, it is replaced automatically!
97787a9d
IB
44 refresh = 3*60*60;
45 retry = 60*60;
46 expire = 14*24*60*60;
47 minimum = 3*60*60; # negative cache ttl
1a64deeb
IB
48 adminEmail = "hostmaster@immae.eu"; #email-address s/@/./
49 nameServer = "ns1.immae.eu.";
50 };
51 };
52 mailSend = {
c4511c38
IB
53 # DKIM 2048b
54 subdomains._domainkey.subdomains.eldiron2.TXT = [
55 (toKV config.myEnv.mail.dkim.eldiron2.public)
56 ];
57 # DKIM 1024b
1a64deeb
IB
58 subdomains._domainkey.subdomains.eldiron.TXT = [
59 (toKV config.myEnv.mail.dkim.eldiron.public)
60 ];
61 # old key, may still be used by verifiers
62 subdomains._domainkey.subdomains.immae_eu.TXT = [
63 (toKV config.myEnv.mail.dkim.immae_eu.public)
64 ];
65 };
97787a9d 66 mailCommon = name: quarantine: {
1a64deeb
IB
67 MX = let
68 mxes = lib.filterAttrs (n: v: v ? mx && v.mx.enable) servers;
69 in
70 lib.mapAttrsToList (n: v: { preference = v.mx.priority; exchange = "${v.mx.subdomain}.${name}."; }) mxes;
71
72 # https://tools.ietf.org/html/rfc6186
73 SRV = [
74 { service = "submission"; proto = "tcp"; priority = 0; weight = 1; port = 587; target = "smtp.immae.eu."; }
75 { service = "submissions"; proto = "tcp"; priority = 0; weight = 1; port = 465; target = "smtp.immae.eu."; }
76
77 { service = "imap"; proto = "tcp"; priority = 0; weight = 1; port = 143; target = "imap.immae.eu."; }
78 { service = "imaps"; proto = "tcp"; priority = 0; weight = 1; port = 993; target = "imap.immae.eu."; }
79 { service = "sieve"; proto = "tcp"; priority = 0; weight = 1; port = 4190; target = "imap.immae.eu."; }
80
81 { service = "pop3"; proto = "tcp"; priority = 10; weight = 1; port = 110; target = "pop3.immae.eu."; }
82 { service = "pop3s"; proto = "tcp"; priority = 10; weight = 1; port = 995; target = "pop3.immae.eu."; }
83 ];
84
85 subdomains = {
86 # MTA-STS
87 # https://blog.delouw.ch/2018/12/16/using-mta-sts-to-enhance-email-transport-security-and-privacy/
88 # https://support.google.com/a/answer/9261504
97787a9d
IB
89 _mta-sts.TXT = [ (toKV { _00__v = "STSv1"; id = "20200109150200Z"; }) ]; # Don't change this value, it is updated automatically!
90 _tls.subdomains._smtp.TXT = [ (toKV { _00__v = "TLSRPTv1"; rua = "mailto:postmaster+mta-sts@immae.eu"; }) ];
1a64deeb
IB
91 mta-sts = ips servers.eldiron.ips.main;
92
93 # DMARC
97787a9d
IB
94 # p needs to be the first tag
95 _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"; }) ];
6ce9fbeb
IB
96
97 # Autoconfiguration for Outlook
98 autodiscover = ips servers.eldiron.ips.main;
99 # Autoconfiguration for Mozilla
100 autoconfig = ips servers.eldiron.ips.main;
1a64deeb
IB
101 };
102
103 # SPF
97787a9d 104 TXT = [ (toKV { _00__v = "spf1 mx ~all"; }) ];
1a64deeb
IB
105 };
106 };
107 };
108 zones = lib.mkOption {
109 type = lib.types.attrsOf (dns-nix.lib.types.zone.substSubModules (
110 dns-nix.lib.types.zone.getSubModules ++ [
111 ({ name, ... }: {
112 options = {
97787a9d
IB
113 dnssec = lib.mkOption {
114 default.enable = false;
115 type = lib.types.submodule {
116 options = {
117 enable = lib.mkEnableOption "Configure dnssec for this domain";
118 };
119 };
120 };
1a64deeb
IB
121 hasEmail = lib.mkEnableOption "This domain has e-mails configuration";
122 emailPolicies = lib.mkOption {
123 default = {};
124 type = lib.types.attrsOf (lib.types.submodule {
125 options = {
126 receive = lib.mkEnableOption "Configure this domain to receive e-mail";
127 };
128 });
129 apply = builtins.mapAttrs (n: v: v // {
130 domain = name;
131 fqdn = if n == "" then name else "${n}.${name}";
132 });
133 };
134 extraConfig = lib.mkOption {
135 type = lib.types.lines;
136 description = "Extra zone configuration for bind";
137 example = ''
138 notify yes;
139 '';
140 default = "";
141 };
142 slaves = lib.mkOption {
143 type = lib.types.listOf lib.types.str;
144 description = "NS slave groups of this zone";
145 default = [];
146 };
147 ns = lib.mkOption {
148 type = lib.types.listOf lib.types.str;
149 default = [];
150 };
151 };
152 })
153 ]));
154 apply = let
155 toNS = n: builtins.map (d: "${d}.") (builtins.concatMap (s: builtins.attrNames config.myEnv.dns.ns."${s}") n);
156 in
157 builtins.mapAttrs (n: v: v // { NS = v.NS or [] ++ toNS (v.ns); });
158 default = {};
159 description = ''
160 attrset of zones to configure
161 '';
162 };
163 };
164 config = let
165 cfg = config.services.bind;
166 in lib.mkIf config.myServices.dns.enable {
167 myServices.chatonsProperties.hostings.dns-secondaire = {
168 file.datetime = "2022-08-22T02:00:00";
169 hosting = {
170 name = "DNS secondaire";
171 description = "DNS secondaire";
172 website = "ns1.immae.eu";
173 status.level = "OK";
174 status.description = "OK";
175 registration.load = "OPEN";
176 install.type = "PACKAGE";
177 };
178 software = {
179 name = "bind9";
180 website = pkgs.bind.meta.homepage;
181 license.url = pkgs.bind.meta.license.url;
182 license.name = pkgs.bind.meta.license.fullName;
183 version = pkgs.bind.version;
184 source.url = "https://www.isc.org/download/";
185 };
186 };
187 myServices.dns.zones = with config.myServices.dns.helpers; {
188 "imsite.eu" = lib.mkMerge [
189 zoneHeader
190 (ips servers.eldiron.ips.main)
191 {
97787a9d
IB
192 dnssec.enable = true;
193 ns = [ "immae" "raito" ];
1a64deeb 194 CAA = letsencrypt;
97787a9d
IB
195 extraConfig = ''
196 notify yes;
197 '';
198 slaves = [ "raito" ];
1a64deeb
IB
199 }
200 ];
201 "immae.dev" = lib.mkMerge [
202 {
97787a9d 203 dnssec.enable = true;
1a64deeb
IB
204 extraConfig = ''
205 notify yes;
206 '';
207 slaves = [ "raito" ];
208 }
209 zoneHeader
210 (ips servers.eldiron.ips.integration)
211 {
212 ns = [ "immae" "raito" ];
213 CAA = letsencrypt;
214 }
215 ];
216 "immae.eu" = lib.mkMerge [
217 {
97787a9d 218 dnssec.enable = true;
1a64deeb
IB
219 extraConfig = ''
220 notify yes;
221 '';
222 slaves = [ "raito" ];
223 }
224 zoneHeader
225 (ips servers.eldiron.ips.production)
226 {
97787a9d
IB
227 ns = [ "immae" ];
228 # Cannot put ns2.immae.eu as glue record as it takes ages to propagate.
229 # And gandi only accepts NS records with glues in their interface
230 NS = [ "kurisu.dual.lahfa.xyz." ];
1a64deeb
IB
231 CAA = letsencrypt;
232
233 # ns1 has glue records in gandi.net
234 subdomains.ns1 = ips servers.eldiron.ips.main;
235 # raito / kurisu.dual.lahfa.xyz ; replace with eldiron in case of problem
236 subdomains.ns2.A = builtins.map (address: { inherit address; ttl = 600; }) servers.eldiron.ips.main.ip4;
237 subdomains.ns2.AAAA = builtins.map (address: { inherit address; ttl = 600; }) servers.eldiron.ips.main.ip6;
238 }
239 {
240 # Machines local users
241 emailPolicies.localhost.receive = false;
97787a9d 242 subdomains.localhost = lib.mkMerge [ (mailCommon "immae.eu" true) mailSend ];
1a64deeb 243 emailPolicies.eldiron.receive = true;
97787a9d 244 subdomains.eldiron = lib.mkMerge [ (mailCommon "immae.eu" true) mailSend ];
1a64deeb
IB
245 }
246 {
247 # For each server "server" and each server ip group "ipgroup",
248 # define ipgroup.server.immae.eu
249 # "main" is set as server.immae.eu instead
250 # if main has an "alias", it is duplicated with this alias.
251 # If the server is a vm, use the v.immae.eu namespace (only main is created)
252 subdomains = let
253 vms = lib.filterAttrs (n: v: v.isVm) servers;
254 bms = lib.filterAttrs (n: v: !v.isVm) servers;
255 toIps = type: builtins.mapAttrs (n: v: ips v.ips."${type}");
256 in
257 lib.mkMerge [
258 (toIps "main" bms)
259
260 { v.subdomains = toIps "main" vms; }
261
262 (lib.mapAttrs (_: v: {
263 subdomains = lib.mapAttrs'
264 (n': v': lib.nameValuePair "${if v'.alias == null then n' else v'.alias}" (ips v'))
265 (lib.filterAttrs (n': v': n' != "main" || v'.alias != null) v.ips);
266 }) bms)
267 ];
268 }
269 {
270 # Outils
271 subdomains = {
272 status = ips servers.monitoring-1.ips.main;
273 };
274 }
275 ];
276 };
277 networking.firewall.allowedUDPPorts = [ 53 ];
278 networking.firewall.allowedTCPPorts = [ 53 ];
279 users.users.named.extraGroups = [ "keys" ];
280 services.bind = {
281 enable = true;
282 cacheNetworks = ["any"];
283 extraOptions = ''
284 allow-recursion { 127.0.0.1; };
285 allow-transfer { none; };
286
287 notify-source ${lib.head config.myEnv.servers.eldiron.ips.main.ip4};
288 notify-source-v6 ${lib.head config.myEnv.servers.eldiron.ips.main.ip6};
289 version none;
290 hostname none;
291 server-id none;
292 '';
293 zones =
294 builtins.mapAttrs (name: v: {
295 master = true;
97787a9d
IB
296 extraConfig = v.extraConfig + lib.optionalString v.dnssec.enable ''
297 key-directory "/var/lib/named/dnssec_keys";
298 dnssec-policy default;
299 inline-signing yes;
300 '';
1a64deeb
IB
301 masters = [];
302 slaves =
303 lib.flatten (map (n: builtins.attrValues config.myEnv.dns.ns.${n}) v.slaves);
97787a9d 304 file = if v.dnssec.enable then "/var/run/named/dnssec-${name}.zone" else zoneToFile name v;
1a64deeb
IB
305 }) config.myServices.dns.zones;
306 };
97787a9d
IB
307 systemd.services.bind.serviceConfig.StateDirectory = "named";
308 systemd.services.bind.preStart = lib.mkAfter
309 (builtins.concatStringsSep "\n" (lib.mapAttrsToList (name: v: ''
310 install -m444 ${zoneToFile name v} /var/run/named/dnssec-${name}.zone
311 '') zonesWithDNSSec) + ''
312 install -dm755 -o named /var/lib/named/dnssec_keys
313 '');
1a64deeb 314 myServices.monitoring.fromMasterActivatedPlugins = [ "dns" ];
d006558d
IB
315 myServices.monitoring.fromMasterObjects.contactgroup.dns-raito = {
316 alias = "Secondary DNS Raito";
317 members = "immae";
318 };
1a64deeb
IB
319 myServices.monitoring.fromMasterObjects.service = lib.mkMerge (lib.mapAttrsToList (name: z:
320 lib.optional (builtins.elem "immae" z.ns) {
321 service_description = "eldiron dns is active and authoritative for ${name}";
322 host_name = config.hostEnv.fqdn;
323 use = "dns-service";
324 check_command = ["check_dns" name "-A"];
325
326 servicegroups = "webstatus-dns";
327 _webstatus_name = name;
328 } ++
97787a9d
IB
329 lib.optionals (builtins.elem "raito" z.ns) [
330 {
331 service_description = "raito dns is active and authoritative for ${name}";
332 host_name = config.hostEnv.fqdn;
333 use = "dns-service";
334 check_command = ["check_external_dns" "kurisu.dual.lahfa.xyz" name "-A"];
d006558d 335 contact_groups = "dns-raito";
97787a9d
IB
336
337 servicegroups = "webstatus-dns";
338 _webstatus_name = "${name} (Secondary DNS Raito)";
339 }
340 {
341 service_description = "raito dns is up to date for ${name}";
342 host_name = config.hostEnv.fqdn;
343 use = "dns-service";
344 check_command = ["check_dns_soa" "kurisu.dual.lahfa.xyz" name config.hostEnv.fqdn];
d006558d 345 contact_groups = "dns-raito";
97787a9d
IB
346
347 servicegroups = "webstatus-dns";
348 _webstatus_name = "${name} (Secondary DNS Raito up to date)";
349 }
350 ] ++
351 lib.optional z.dnssec.enable {
352 service_description = "DNSSEC is active and not expired for ${name}";
1a64deeb
IB
353 host_name = config.hostEnv.fqdn;
354 use = "dns-service";
97787a9d 355 check_command = ["check_dnssec" name];
1a64deeb
IB
356
357 servicegroups = "webstatus-dns";
97787a9d 358 _webstatus_name = "${name} (DNSSEC)";
1a64deeb
IB
359 }
360 ) config.myServices.dns.zones);
361 };
362}