1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.services.dnsdist;
9
10 toLua = lib.generators.toLua { };
11
12 mkBind = cfg: toLua "${cfg.listenAddress}:${toString cfg.listenPort}";
13
14 configFile = pkgs.writeText "dnsdist.conf" ''
15 setLocal(${mkBind cfg})
16 ${lib.optionalString cfg.dnscrypt.enable dnscryptSetup}
17 ${cfg.extraConfig}
18 '';
19
20 dnscryptSetup = ''
21 last_rotation = 0
22 cert_serial = 0
23 provider_key = ${toLua cfg.dnscrypt.providerKey}
24 cert_lifetime = ${toLua cfg.dnscrypt.certLifetime} * 60
25
26 function file_exists(name)
27 local f = io.open(name, "r")
28 return f ~= nil and io.close(f)
29 end
30
31 function dnscrypt_setup()
32 -- generate provider keys on first run
33 if provider_key == nil then
34 provider_key = "/var/lib/dnsdist/private.key"
35 if not file_exists(provider_key) then
36 generateDNSCryptProviderKeys("/var/lib/dnsdist/public.key",
37 "/var/lib/dnsdist/private.key")
38 print("DNSCrypt: generated provider keypair")
39 end
40 end
41
42 -- generate resolver certificate
43 local now = os.time()
44 generateDNSCryptCertificate(
45 provider_key, "/run/dnsdist/resolver.cert", "/run/dnsdist/resolver.key",
46 cert_serial, now - 60, now + cert_lifetime)
47 addDNSCryptBind(
48 ${mkBind cfg.dnscrypt}, ${toLua cfg.dnscrypt.providerName},
49 "/run/dnsdist/resolver.cert", "/run/dnsdist/resolver.key")
50 end
51
52 function maintenance()
53 -- certificate rotation
54 local now = os.time()
55 local dnscrypt = getDNSCryptBind(0)
56
57 if ((now - last_rotation) > 0.9 * cert_lifetime) then
58 -- generate and start using a new certificate
59 dnscrypt:generateAndLoadInMemoryCertificate(
60 provider_key, cert_serial + 1,
61 now - 60, now + cert_lifetime)
62
63 -- stop advertising the last certificate
64 dnscrypt:markInactive(cert_serial)
65
66 -- remove the second to last certificate
67 if (cert_serial > 1) then
68 dnscrypt:removeInactiveCertificate(cert_serial - 1)
69 end
70
71 print("DNSCrypt: rotated certificate")
72
73 -- increment serial number
74 cert_serial = cert_serial + 1
75 last_rotation = now
76 end
77 end
78
79 dnscrypt_setup()
80 '';
81
82in
83{
84 options = {
85 services.dnsdist = {
86 enable = lib.mkEnableOption "dnsdist domain name server";
87
88 listenAddress = lib.mkOption {
89 type = lib.types.str;
90 description = "Listen IP address";
91 default = "0.0.0.0";
92 };
93 listenPort = lib.mkOption {
94 type = lib.types.port;
95 description = "Listen port";
96 default = 53;
97 };
98
99 dnscrypt = {
100 enable = lib.mkEnableOption "a DNSCrypt endpoint to dnsdist";
101
102 listenAddress = lib.mkOption {
103 type = lib.types.str;
104 description = "Listen IP address of the endpoint";
105 default = "0.0.0.0";
106 };
107
108 listenPort = lib.mkOption {
109 type = lib.types.port;
110 description = "Listen port of the endpoint";
111 default = 443;
112 };
113
114 providerName = lib.mkOption {
115 type = lib.types.str;
116 default = "2.dnscrypt-cert.${config.networking.hostName}";
117 defaultText = lib.literalExpression "2.dnscrypt-cert.\${config.networking.hostName}";
118 example = "2.dnscrypt-cert.myresolver";
119 description = ''
120 The name that will be given to this DNSCrypt resolver.
121
122 ::: {.note}
123 The provider name must start with `2.dnscrypt-cert.`.
124 :::
125 '';
126 };
127
128 providerKey = lib.mkOption {
129 type = lib.types.nullOr lib.types.path;
130 default = null;
131 description = ''
132 The filepath to the provider secret key.
133 If not given a new provider key pair will be generated in
134 /var/lib/dnsdist on the first run.
135
136 ::: {.note}
137 The file must be readable by the dnsdist user/group.
138 :::
139 '';
140 };
141
142 certLifetime = lib.mkOption {
143 type = lib.types.ints.positive;
144 default = 15;
145 description = ''
146 The lifetime (in minutes) of the resolver certificate.
147 This will be automatically rotated before expiration.
148 '';
149 };
150
151 };
152
153 extraConfig = lib.mkOption {
154 type = lib.types.lines;
155 default = "";
156 description = ''
157 Extra lines to be added verbatim to dnsdist.conf.
158 '';
159 };
160 };
161 };
162
163 config = lib.mkIf cfg.enable {
164 users.users.dnsdist = {
165 description = "dnsdist daemons user";
166 isSystemUser = true;
167 group = "dnsdist";
168 };
169
170 users.groups.dnsdist = { };
171
172 systemd.packages = [ pkgs.dnsdist ];
173
174 systemd.services.dnsdist = {
175 wantedBy = [ "multi-user.target" ];
176
177 startLimitIntervalSec = 0;
178 serviceConfig = {
179 User = "dnsdist";
180 Group = "dnsdist";
181 RuntimeDirectory = "dnsdist";
182 StateDirectory = "dnsdist";
183 # upstream overrides for better nixos compatibility
184 ExecStartPre = [
185 ""
186 "${pkgs.dnsdist}/bin/dnsdist --check-config --config ${configFile}"
187 ];
188 ExecStart = [
189 ""
190 "${pkgs.dnsdist}/bin/dnsdist --supervised --disable-syslog --config ${configFile}"
191 ];
192 };
193 };
194 };
195}