{ config, lib, pkgs, ... }: let cfg = config.services.dnsdist; toLua = lib.generators.toLua {}; mkBind = cfg: toLua "${cfg.listenAddress}:${toString cfg.listenPort}"; configFile = pkgs.writeText "dnsdist.conf" '' setLocal(${mkBind cfg}) ${lib.optionalString cfg.dnscrypt.enable dnscryptSetup} ${cfg.extraConfig} ''; dnscryptSetup = '' last_rotation = 0 cert_serial = 0 provider_key = ${toLua cfg.dnscrypt.providerKey} cert_lifetime = ${toLua cfg.dnscrypt.certLifetime} * 60 function file_exists(name) local f = io.open(name, "r") return f ~= nil and io.close(f) end function dnscrypt_setup() -- generate provider keys on first run if provider_key == nil then provider_key = "/var/lib/dnsdist/private.key" if not file_exists(provider_key) then generateDNSCryptProviderKeys("/var/lib/dnsdist/public.key", "/var/lib/dnsdist/private.key") print("DNSCrypt: generated provider keypair") end end -- generate resolver certificate local now = os.time() generateDNSCryptCertificate( provider_key, "/run/dnsdist/resolver.cert", "/run/dnsdist/resolver.key", cert_serial, now - 60, now + cert_lifetime) addDNSCryptBind( ${mkBind cfg.dnscrypt}, ${toLua cfg.dnscrypt.providerName}, "/run/dnsdist/resolver.cert", "/run/dnsdist/resolver.key") end function maintenance() -- certificate rotation local now = os.time() local dnscrypt = getDNSCryptBind(0) if ((now - last_rotation) > 0.9 * cert_lifetime) then -- generate and start using a new certificate dnscrypt:generateAndLoadInMemoryCertificate( provider_key, cert_serial + 1, now - 60, now + cert_lifetime) -- stop advertising the last certificate dnscrypt:markInactive(cert_serial) -- remove the second to last certificate if (cert_serial > 1) then dnscrypt:removeInactiveCertificate(cert_serial - 1) end print("DNSCrypt: rotated certificate") -- increment serial number cert_serial = cert_serial + 1 last_rotation = now end end dnscrypt_setup() ''; in { options = { services.dnsdist = { enable = lib.mkEnableOption "dnsdist domain name server"; listenAddress = lib.mkOption { type = lib.types.str; description = "Listen IP address"; default = "0.0.0.0"; }; listenPort = lib.mkOption { type = lib.types.port; description = "Listen port"; default = 53; }; dnscrypt = { enable = lib.mkEnableOption "a DNSCrypt endpoint to dnsdist"; listenAddress = lib.mkOption { type = lib.types.str; description = "Listen IP address of the endpoint"; default = "0.0.0.0"; }; listenPort = lib.mkOption { type = lib.types.port; description = "Listen port of the endpoint"; default = 443; }; providerName = lib.mkOption { type = lib.types.str; default = "2.dnscrypt-cert.${config.networking.hostName}"; defaultText = lib.literalExpression "2.dnscrypt-cert.\${config.networking.hostName}"; example = "2.dnscrypt-cert.myresolver"; description = '' The name that will be given to this DNSCrypt resolver. ::: {.note} The provider name must start with `2.dnscrypt-cert.`. ::: ''; }; providerKey = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; description = '' The filepath to the provider secret key. If not given a new provider key pair will be generated in /var/lib/dnsdist on the first run. ::: {.note} The file must be readable by the dnsdist user/group. ::: ''; }; certLifetime = lib.mkOption { type = lib.types.ints.positive; default = 15; description = '' The lifetime (in minutes) of the resolver certificate. This will be automatically rotated before expiration. ''; }; }; extraConfig = lib.mkOption { type = lib.types.lines; default = ""; description = '' Extra lines to be added verbatim to dnsdist.conf. ''; }; }; }; config = lib.mkIf cfg.enable { users.users.dnsdist = { description = "dnsdist daemons user"; isSystemUser = true; group = "dnsdist"; }; users.groups.dnsdist = {}; systemd.packages = [ pkgs.dnsdist ]; systemd.services.dnsdist = { wantedBy = [ "multi-user.target" ]; startLimitIntervalSec = 0; serviceConfig = { User = "dnsdist"; Group = "dnsdist"; RuntimeDirectory = "dnsdist"; StateDirectory = "dnsdist"; # upstream overrides for better nixos compatibility ExecStartPre = [ "" "${pkgs.dnsdist}/bin/dnsdist --check-config --config ${configFile}" ]; ExecStart = [ "" "${pkgs.dnsdist}/bin/dnsdist --supervised --disable-syslog --config ${configFile}" ]; }; }; }; }