nixos/nsd: automatic DNSSEC using BIND toolset

This commit is contained in:
Gregor Kleen 2017-11-15 15:33:30 +01:00
parent 089e0aaf72
commit 9826f5cc3c
3 changed files with 142 additions and 11 deletions

View file

@ -248,6 +248,46 @@ let
Use imports or pkgs.lib.readFile if you don't want this data in your config file. Use imports or pkgs.lib.readFile if you don't want this data in your config file.
''; '';
}; };
dnssec = mkEnableOption "DNSSEC";
dnssecPolicy = {
algorithm = mkOption {
type = types.str;
default = "RSASHA256";
description = "Which algorithm to use for DNSSEC";
};
keyttl = mkOption {
type = types.str;
default = "1h";
description = "TTL for dnssec records";
};
coverage = mkOption {
type = types.str;
default = "1y";
description = ''
The length of time to ensure that keys will be correct; no action will be taken to create new keys to be activated after this time.
'';
};
zsk = mkOption {
type = keyPolicy;
default = { keySize = 2048;
prePublish = "1w";
postPublish = "1w";
rollPeriod = "1mo";
};
description = "Key policy for zone signing keys";
};
ksk = mkOption {
type = keyPolicy;
default = { keySize = 4096;
prePublish = "1mo";
postPublish = "1mo";
rollPeriod = "0";
};
description = "Key policy for key signing keys";
};
};
maxRefreshSecs = mkOption { maxRefreshSecs = mkOption {
type = types.nullOr types.int; type = types.nullOr types.int;
@ -365,10 +405,61 @@ let
and stats_noreset. and stats_noreset.
''; '';
}; };
}; };
}; };
keyPolicy = types.submodule {
options = {
keySize = mkOption {
type = types.int;
description = "Key size in bits";
};
prePublish = mkOption {
type = types.str;
description = "How long in advance to publish new keys";
};
postPublish = mkOption {
type = types.str;
description = "How long after deactivation to keep a key in the zone";
};
rollPeriod = mkOption {
type = types.str;
description = "How frequently to change keys";
};
};
};
dnssecZones = (filterAttrs (n: v: if v ? dnssec then v.dnssec else false) zoneConfigs);
dnssec = length (attrNames dnssecZones) != 0;
signZones = optionalString dnssec ''
mkdir -p ${stateDir}/dnssec
chown ${username}:${username} ${stateDir}/dnssec
chmod 0600 ${stateDir}/dnssec
${concatStrings (mapAttrsToList signZone dnssecZones)}
'';
signZone = name: zone: ''
${pkgs.bind}/bin/dnssec-keymgr -g ${pkgs.bind}/bin/dnssec-keygen -s ${pkgs.bind}/bin/dnssec-settime -K ${stateDir}/dnssec -c ${policyFile name zone.dnssecPolicy} ${name}
${pkgs.bind}/bin/dnssec-signzone -S -K ${stateDir}/dnssec -o ${name} -O full -N date ${stateDir}/zones/${name}
${nsdPkg}/sbin/nsd-checkzone ${name} ${stateDir}/zones/${name}.signed && mv -v ${stateDir}/zones/${name}.signed ${stateDir}/zones/${name}
'';
policyFile = name: policy: pkgs.writeText "${name}.policy" ''
zone ${name} {
algorithm ${policy.algorithm};
key-size zsk ${toString policy.zsk.keySize};
key-size ksk ${toString policy.ksk.keySize};
keyttl ${policy.keyttl};
pre-publish zsk ${policy.zsk.prePublish};
pre-publish ksk ${policy.ksk.prePublish};
post-publish zsk ${policy.zsk.postPublish};
post-publish ksk ${policy.ksk.postPublish};
roll-period zsk ${policy.zsk.rollPeriod};
roll-period ksk ${policy.ksk.rollPeriod};
coverage ${policy.coverage};
};
'';
in in
{ {
# options are ordered alphanumerically # options are ordered alphanumerically
@ -378,6 +469,14 @@ in
bind8Stats = mkEnableOption "BIND8 like statistics"; bind8Stats = mkEnableOption "BIND8 like statistics";
dnssecInterval = mkOption {
type = types.str;
default = "1h";
description = ''
How often to check whether dnssec key rollover is required
'';
};
extraConfig = mkOption { extraConfig = mkOption {
type = types.str; type = types.str;
default = ""; default = "";
@ -739,7 +838,6 @@ in
}; };
zones = mkOption { zones = mkOption {
type = types.attrsOf zoneOptions; type = types.attrsOf zoneOptions;
default = {}; default = {};
@ -783,7 +881,6 @@ in
serverGroup1. serverGroup1.
''; '';
}; };
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable {
@ -828,9 +925,9 @@ in
mkdir -m 0700 -p "${stateDir}/var" mkdir -m 0700 -p "${stateDir}/var"
cat > "${stateDir}/don't touch anything in here" << EOF cat > "${stateDir}/don't touch anything in here" << EOF
Everything in this directory except NSD's state in var is Everything in this directory except NSD's state in var and dnssec
automatically generated and will be purged and redeployed is automatically generated and will be purged and redeployed by
by the nsd.service pre-start script. the nsd.service pre-start script.
EOF EOF
chown ${username}:${username} -R "${stateDir}/private" chown ${username}:${username} -R "${stateDir}/private"
@ -844,5 +941,33 @@ in
''; '';
}; };
nixpkgs.config = mkIf dnssec {
bind.enablePython = true;
};
systemd.timers."nsd-dnssec" = mkIf dnssec {
description = "Automatic DNSSEC key rollover";
wantedBy = [ "nsd.service" ];
timerConfig = {
OnActiveSec = cfg.dnssecInterval;
OnUnitActiveSec = cfg.dnssecInterval;
};
};
systemd.services."nsd-dnssec" = mkIf dnssec {
description = "DNSSEC key rollover";
wantedBy = [ "nsd.service" ];
before = [ "nsd.service" ];
script = signZones;
postStop = ''
${pkgs.systemd}/bin/systemctl kill -s SIGHUP nsd.service
'';
};
}; };
} }

View file

@ -1,7 +1,9 @@
{ stdenv, lib, fetchurl, openssl, libtool, perl, libxml2 { stdenv, lib, fetchurl, openssl, libtool, perl, libxml2
, enableSeccomp ? false, libseccomp ? null }: , enableSeccomp ? false, libseccomp ? null
, enablePython ? false, python ? null }:
assert enableSeccomp -> libseccomp != null; assert enableSeccomp -> libseccomp != null;
assert enablePython -> python != null;
let version = "9.11.2"; in let version = "9.11.2"; in
@ -18,8 +20,9 @@ stdenv.mkDerivation rec {
patches = [ ./dont-keep-configure-flags.patch ./remove-mkdir-var.patch ] ++ patches = [ ./dont-keep-configure-flags.patch ./remove-mkdir-var.patch ] ++
stdenv.lib.optional stdenv.isDarwin ./darwin-openssl-linking-fix.patch; stdenv.lib.optional stdenv.isDarwin ./darwin-openssl-linking-fix.patch;
buildInputs = [ openssl libtool perl libxml2 ] ++ buildInputs = [ openssl libtool perl libxml2 ]
stdenv.lib.optional enableSeccomp libseccomp; ++ lib.optional enableSeccomp libseccomp
++ lib.optional enablePython python;
STD_CDEFINES = [ "-DDIG_SIGCHASE=1" ]; # support +sigchase STD_CDEFINES = [ "-DDIG_SIGCHASE=1" ]; # support +sigchase
@ -28,6 +31,7 @@ stdenv.mkDerivation rec {
"--with-libtool" "--with-libtool"
"--with-libxml2=${libxml2.dev}" "--with-libxml2=${libxml2.dev}"
"--with-openssl=${openssl.dev}" "--with-openssl=${openssl.dev}"
(if enablePython then "--with-python" else "--without-python")
"--without-atf" "--without-atf"
"--without-dlopen" "--without-dlopen"
"--without-docbook-xsl" "--without-docbook-xsl"
@ -36,7 +40,6 @@ stdenv.mkDerivation rec {
"--without-idnlib" "--without-idnlib"
"--without-pkcs11" "--without-pkcs11"
"--without-purify" "--without-purify"
"--without-python"
] ++ lib.optional enableSeccomp "--enable-seccomp"; ] ++ lib.optional enableSeccomp "--enable-seccomp";
postInstall = '' postInstall = ''

View file

@ -11462,7 +11462,10 @@ with pkgs;
bftpd = callPackage ../servers/ftp/bftpd {}; bftpd = callPackage ../servers/ftp/bftpd {};
bind = callPackage ../servers/dns/bind { }; bind = callPackage ../servers/dns/bind {
enablePython = config.bind.enablePython or false;
python = python.withPackages (ps: with ps; [ ply ]);
};
dnsutils = bind.dnsutils; dnsutils = bind.dnsutils;
bird = callPackage ../servers/bird { }; bird = callPackage ../servers/bird { };