blob: 7645b69494261501914de5d40f80e977234104a8 (
plain) (
tree)
|
|
{ lib, pkgs, config, dns-nix, ... }:
let
zonesWithDNSSec = lib.filterAttrs (k: v: v.dnssec.enable) config.myServices.dns.zones;
zoneToFile = name: v: 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
'';
in
{
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: let
removeOrder = n: lib.last (builtins.split "__" n);
in
builtins.concatStringsSep ";" (builtins.attrValues (builtins.mapAttrs (n: v: "${removeOrder 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 = 3*60*60;
retry = 60*60;
expire = 14*24*60*60;
minimum = 3*60*60; # 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: quarantine: {
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 { _00__v = "STSv1"; id = "20200109150200Z"; }) ]; # Don't change this value, it is updated automatically!
_tls.subdomains._smtp.TXT = [ (toKV { _00__v = "TLSRPTv1"; rua = "mailto:postmaster+mta-sts@immae.eu"; }) ];
mta-sts = ips servers.eldiron.ips.main;
# DMARC
# p needs to be the first tag
_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"; }) ];
};
# SPF
TXT = [ (toKV { _00__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 = {
dnssec = lib.mkOption {
default.enable = false;
type = lib.types.submodule {
options = {
enable = lib.mkEnableOption "Configure dnssec for this domain";
};
};
};
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)
{
dnssec.enable = true;
ns = [ "immae" "raito" ];
CAA = letsencrypt;
extraConfig = ''
notify yes;
'';
slaves = [ "raito" ];
}
];
"immae.dev" = lib.mkMerge [
{
dnssec.enable = true;
extraConfig = ''
notify yes;
'';
slaves = [ "raito" ];
}
zoneHeader
(ips servers.eldiron.ips.integration)
{
ns = [ "immae" "raito" ];
CAA = letsencrypt;
}
];
"immae.eu" = lib.mkMerge [
{
dnssec.enable = true;
extraConfig = ''
notify yes;
'';
slaves = [ "raito" ];
}
zoneHeader
(ips servers.eldiron.ips.production)
{
ns = [ "immae" ];
# Cannot put ns2.immae.eu as glue record as it takes ages to propagate.
# And gandi only accepts NS records with glues in their interface
NS = [ "kurisu.dual.lahfa.xyz." ];
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" true) mailSend ];
emailPolicies.eldiron.receive = true;
subdomains.eldiron = lib.mkMerge [ (mailCommon "immae.eu" true) 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 + lib.optionalString v.dnssec.enable ''
key-directory "/var/lib/named/dnssec_keys";
dnssec-policy default;
inline-signing yes;
'';
masters = [];
slaves =
lib.flatten (map (n: builtins.attrValues config.myEnv.dns.ns.${n}) v.slaves);
file = if v.dnssec.enable then "/var/run/named/dnssec-${name}.zone" else zoneToFile name v;
}) config.myServices.dns.zones;
};
systemd.services.bind.serviceConfig.StateDirectory = "named";
systemd.services.bind.preStart = lib.mkAfter
(builtins.concatStringsSep "\n" (lib.mapAttrsToList (name: v: ''
install -m444 ${zoneToFile name v} /var/run/named/dnssec-${name}.zone
'') zonesWithDNSSec) + ''
install -dm755 -o named /var/lib/named/dnssec_keys
'');
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.optionals (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)";
}
{
service_description = "raito dns is up to date for ${name}";
host_name = config.hostEnv.fqdn;
use = "dns-service";
check_command = ["check_dns_soa" "kurisu.dual.lahfa.xyz" name config.hostEnv.fqdn];
servicegroups = "webstatus-dns";
_webstatus_name = "${name} (Secondary DNS Raito up to date)";
}
] ++
lib.optional z.dnssec.enable {
service_description = "DNSSEC is active and not expired for ${name}";
host_name = config.hostEnv.fqdn;
use = "dns-service";
check_command = ["check_dnssec" name];
servicegroups = "webstatus-dns";
_webstatus_name = "${name} (DNSSEC)";
}
) config.myServices.dns.zones);
};
}
|