about summary refs log tree commit diff
path: root/nixos/modules/services/networking/dnsdist.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/services/networking/dnsdist.nix')
-rw-r--r--nixos/modules/services/networking/dnsdist.nix143
1 files changed, 138 insertions, 5 deletions
diff --git a/nixos/modules/services/networking/dnsdist.nix b/nixos/modules/services/networking/dnsdist.nix
index 483300111df9..792185c9fbea 100644
--- a/nixos/modules/services/networking/dnsdist.nix
+++ b/nixos/modules/services/networking/dnsdist.nix
@@ -4,10 +4,79 @@ with lib;
 
 let
   cfg = config.services.dnsdist;
+
+  toLua = lib.generators.toLua {};
+
+  mkBind = cfg: toLua "${cfg.listenAddress}:${toString cfg.listenPort}";
+
   configFile = pkgs.writeText "dnsdist.conf" ''
-    setLocal('${cfg.listenAddress}:${toString cfg.listenPort}')
+    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 = {
@@ -15,15 +84,69 @@ in {
 
       listenAddress = mkOption {
         type = types.str;
-        description = lib.mdDoc "Listen IP Address";
+        description = lib.mdDoc "Listen IP address";
         default = "0.0.0.0";
       };
       listenPort = mkOption {
-        type = types.int;
+        type = types.port;
         description = lib.mdDoc "Listen port";
         default = 53;
       };
 
+      dnscrypt = {
+        enable = mkEnableOption (lib.mdDoc "a DNSCrypt endpoint to dnsdist");
+
+        listenAddress = mkOption {
+          type = types.str;
+          description = lib.mdDoc "Listen IP address of the endpoint";
+          default = "0.0.0.0";
+        };
+
+        listenPort = mkOption {
+          type = types.port;
+          description = lib.mdDoc "Listen port of the endpoint";
+          default = 443;
+        };
+
+        providerName = mkOption {
+          type = types.str;
+          default = "2.dnscrypt-cert.${config.networking.hostName}";
+          defaultText = literalExpression "2.dnscrypt-cert.\${config.networking.hostName}";
+          example = "2.dnscrypt-cert.myresolver";
+          description = lib.mdDoc ''
+            The name that will be given to this DNSCrypt resolver.
+
+            ::: {.note}
+            The provider name must start with `2.dnscrypt-cert.`.
+            :::
+          '';
+        };
+
+        providerKey = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          description = lib.mdDoc ''
+            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 = mkOption {
+          type = types.ints.positive;
+          default = 15;
+          description = lib.mdDoc ''
+            The lifetime (in minutes) of the resolver certificate.
+            This will be automatically rotated before expiration.
+          '';
+        };
+
+      };
+
       extraConfig = mkOption {
         type = types.lines;
         default = "";
@@ -35,6 +158,14 @@ in {
   };
 
   config = mkIf cfg.enable {
+    users.users.dnsdist = {
+      description = "dnsdist daemons user";
+      isSystemUser = true;
+      group = "dnsdist";
+    };
+
+    users.groups.dnsdist = {};
+
     systemd.packages = [ pkgs.dnsdist ];
 
     systemd.services.dnsdist = {
@@ -42,8 +173,10 @@ in {
 
       startLimitIntervalSec = 0;
       serviceConfig = {
-        DynamicUser = true;
-
+        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}" ];