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