about summary refs log tree commit diff
path: root/nixpkgs/nixos/modules/services/networking/dnsdist.nix
blob: cf17a87f649f4bd7d8a8a152a88fac3290d8a348 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
{ config, lib, pkgs, ... }:

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(${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 = mkEnableOption "dnsdist domain name server";

      listenAddress = mkOption {
        type = types.str;
        description = "Listen IP address";
        default = "0.0.0.0";
      };
      listenPort = mkOption {
        type = types.port;
        description = "Listen port";
        default = 53;
      };

      dnscrypt = {
        enable = mkEnableOption "a DNSCrypt endpoint to dnsdist";

        listenAddress = mkOption {
          type = types.str;
          description = "Listen IP address of the endpoint";
          default = "0.0.0.0";
        };

        listenPort = mkOption {
          type = types.port;
          description = "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 = ''
            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 = ''
            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 = ''
            The lifetime (in minutes) of the resolver certificate.
            This will be automatically rotated before expiration.
          '';
        };

      };

      extraConfig = mkOption {
        type = types.lines;
        default = "";
        description = ''
          Extra lines to be added verbatim to dnsdist.conf.
        '';
      };
    };
  };

  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 = {
      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}" ];
      };
    };
  };
}