]>
Commit | Line | Data |
---|---|---|
1a64deeb | 1 | { lib, pkgs, config, dns-nix, ... }: |
97787a9d IB |
2 | let |
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 | ''; | |
15 | in | |
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 | } |