python3Packages.orjson: Disable failing tests on 32 bit
[NixPkgs.git] / nixos / modules / services / networking / dnscrypt-wrapper.nix
blob082e0195093efb092e52342edbe174201ade547f
1 { config, lib, pkgs, ... }:
2 with lib;
4 let
5   cfg     = config.services.dnscrypt-wrapper;
6   dataDir = "/var/lib/dnscrypt-wrapper";
8   mkPath = path: default:
9     if path != null
10       then toString path
11       else default;
13   publicKey = mkPath cfg.providerKey.public "${dataDir}/public.key";
14   secretKey = mkPath cfg.providerKey.secret "${dataDir}/secret.key";
16   daemonArgs = with cfg; [
17     "--listen-address=${address}:${toString port}"
18     "--resolver-address=${upstream.address}:${toString upstream.port}"
19     "--provider-name=${providerName}"
20     "--provider-publickey-file=${publicKey}"
21     "--provider-secretkey-file=${secretKey}"
22     "--provider-cert-file=${providerName}.crt"
23     "--crypt-secretkey-file=${providerName}.key"
24   ];
26   genKeys = ''
27     # generates time-limited keypairs
28     keyGen() {
29       dnscrypt-wrapper --gen-crypt-keypair \
30         --crypt-secretkey-file=${cfg.providerName}.key
32       dnscrypt-wrapper --gen-cert-file \
33         --crypt-secretkey-file=${cfg.providerName}.key \
34         --provider-cert-file=${cfg.providerName}.crt \
35         --provider-publickey-file=${publicKey} \
36         --provider-secretkey-file=${secretKey} \
37         --cert-file-expire-days=${toString cfg.keys.expiration}
38     }
40     cd ${dataDir}
42     # generate provider keypair (first run only)
43     ${optionalString (cfg.providerKey.public == null || cfg.providerKey.secret == null) ''
44       if [ ! -f ${publicKey} ] || [ ! -f ${secretKey} ]; then
45         dnscrypt-wrapper --gen-provider-keypair
46       fi
47     ''}
49     # generate new keys for rotation
50     if [ ! -f ${cfg.providerName}.key ] || [ ! -f ${cfg.providerName}.crt ]; then
51       keyGen
52     fi
53   '';
55   rotateKeys = ''
56     # check if keys are not expired
57     keyValid() {
58       fingerprint=$(dnscrypt-wrapper \
59         --show-provider-publickey \
60         --provider-publickey-file=${publicKey} \
61         | awk '{print $(NF)}')
62       dnscrypt-proxy --test=${toString (cfg.keys.checkInterval + 1)} \
63         --resolver-address=127.0.0.1:${toString cfg.port} \
64         --provider-name=${cfg.providerName} \
65         --provider-key=$fingerprint
66     }
68     cd ${dataDir}
70     # archive old keys and restart the service
71     if ! keyValid; then
72       echo "certificate soon to become invalid; backing up old cert"
73       mkdir -p oldkeys
74       mv -v ${cfg.providerName}.key oldkeys/${cfg.providerName}-$(date +%F-%T).key
75       mv -v ${cfg.providerName}.crt oldkeys/${cfg.providerName}-$(date +%F-%T).crt
76       systemctl restart dnscrypt-wrapper
77     fi
78   '';
81   # This is the fork of the original dnscrypt-proxy maintained by Dyne.org.
82   # dnscrypt-proxy2 doesn't provide the `--test` feature that is needed to
83   # correctly implement key rotation of dnscrypt-wrapper ephemeral keys.
84   dnscrypt-proxy1 = pkgs.callPackage
85     ({ stdenv, fetchFromGitHub, autoreconfHook
86     , pkg-config, libsodium, ldns, openssl, systemd }:
88     stdenv.mkDerivation rec {
89       pname = "dnscrypt-proxy";
90       version = "2019-08-20";
92       src = fetchFromGitHub {
93         owner = "dyne";
94         repo = "dnscrypt-proxy";
95         rev = "07ac3825b5069adc28e2547c16b1d983a8ed8d80";
96         sha256 = "0c4mq741q4rpmdn09agwmxap32kf0vgfz7pkhcdc5h54chc3g3xy";
97       };
99       configureFlags = optional stdenv.isLinux "--with-systemd";
101       nativeBuildInputs = [ autoreconfHook pkg-config ];
103       # <ldns/ldns.h> depends on <openssl/ssl.h>
104       buildInputs = [ libsodium openssl.dev ldns ] ++ optional stdenv.isLinux systemd;
106       postInstall = ''
107         # Previous versions required libtool files to load plugins; they are
108         # now strictly optional.
109         rm $out/lib/dnscrypt-proxy/*.la
110       '';
112       meta = {
113         description = "A tool for securing communications between a client and a DNS resolver";
114         homepage = "https://github.com/dyne/dnscrypt-proxy";
115         license = licenses.isc;
116         maintainers = with maintainers; [ rnhmjoj ];
117         platforms = platforms.linux;
118       };
119     }) { };
121 in {
124   ###### interface
126   options.services.dnscrypt-wrapper = {
127     enable = mkEnableOption (lib.mdDoc "DNSCrypt wrapper");
129     address = mkOption {
130       type = types.str;
131       default = "127.0.0.1";
132       description = lib.mdDoc ''
133         The DNSCrypt wrapper will bind to this IP address.
134       '';
135     };
137     port = mkOption {
138       type = types.port;
139       default = 5353;
140       description = lib.mdDoc ''
141         The DNSCrypt wrapper will listen for DNS queries on this port.
142       '';
143     };
145     providerName = mkOption {
146       type = types.str;
147       default = "2.dnscrypt-cert.${config.networking.hostName}";
148       defaultText = literalExpression ''"2.dnscrypt-cert.''${config.networking.hostName}"'';
149       example = "2.dnscrypt-cert.myresolver";
150       description = lib.mdDoc ''
151         The name that will be given to this DNSCrypt resolver.
152         Note: the resolver name must start with `2.dnscrypt-cert.`.
153       '';
154     };
156     providerKey.public = mkOption {
157       type = types.nullOr types.path;
158       default = null;
159       example = "/etc/secrets/public.key";
160       description = lib.mdDoc ''
161         The filepath to the provider public key. If not given a new
162         provider key pair will be generated on the first run.
163       '';
164     };
166     providerKey.secret = mkOption {
167       type = types.nullOr types.path;
168       default = null;
169       example = "/etc/secrets/secret.key";
170       description = lib.mdDoc ''
171         The filepath to the provider secret key. If not given a new
172         provider key pair will be generated on the first run.
173       '';
174     };
176     upstream.address = mkOption {
177       type = types.str;
178       default = "127.0.0.1";
179       description = lib.mdDoc ''
180         The IP address of the upstream DNS server DNSCrypt will "wrap".
181       '';
182     };
184     upstream.port = mkOption {
185       type = types.port;
186       default = 53;
187       description = lib.mdDoc ''
188         The port of the upstream DNS server DNSCrypt will "wrap".
189       '';
190     };
192     keys.expiration = mkOption {
193       type = types.int;
194       default = 30;
195       description = lib.mdDoc ''
196         The duration (in days) of the time-limited secret key.
197         This will be automatically rotated before expiration.
198       '';
199     };
201     keys.checkInterval = mkOption {
202       type = types.int;
203       default = 1440;
204       description = lib.mdDoc ''
205         The time interval (in minutes) between key expiration checks.
206       '';
207     };
209   };
212   ###### implementation
214   config = mkIf cfg.enable {
216     users.users.dnscrypt-wrapper = {
217       description = "dnscrypt-wrapper daemon user";
218       home = "${dataDir}";
219       createHome = true;
220       isSystemUser = true;
221       group = "dnscrypt-wrapper";
222     };
223     users.groups.dnscrypt-wrapper = { };
225     security.polkit.extraConfig = ''
226       // Allow dnscrypt-wrapper user to restart dnscrypt-wrapper.service
227       polkit.addRule(function(action, subject) {
228           if (action.id == "org.freedesktop.systemd1.manage-units" &&
229               action.lookup("unit") == "dnscrypt-wrapper.service" &&
230               subject.user == "dnscrypt-wrapper") {
231               return polkit.Result.YES;
232           }
233         });
234     '';
236     systemd.services.dnscrypt-wrapper = {
237       description = "dnscrypt-wrapper daemon";
238       after    = [ "network.target" ];
239       wantedBy = [ "multi-user.target" ];
240       path     = [ pkgs.dnscrypt-wrapper ];
242       serviceConfig = {
243         User = "dnscrypt-wrapper";
244         WorkingDirectory = dataDir;
245         Restart   = "on-failure";
246         ExecStart = "${pkgs.dnscrypt-wrapper}/bin/dnscrypt-wrapper ${toString daemonArgs}";
247       };
249       preStart = genKeys;
250     };
253     systemd.services.dnscrypt-wrapper-rotate = {
254       after    = [ "network.target" ];
255       requires = [ "dnscrypt-wrapper.service" ];
256       description = "Rotates DNSCrypt wrapper keys if soon to expire";
258       path   = with pkgs; [ dnscrypt-wrapper dnscrypt-proxy1 gawk ];
259       script = rotateKeys;
260       serviceConfig.User = "dnscrypt-wrapper";
261     };
264     systemd.timers.dnscrypt-wrapper-rotate = {
265       description = "Periodically check DNSCrypt wrapper keys for expiration";
266       wantedBy = [ "multi-user.target" ];
268       timerConfig = {
269         Unit = "dnscrypt-wrapper-rotate.service";
270         OnBootSec = "1min";
271         OnUnitActiveSec = cfg.keys.checkInterval * 60;
272       };
273     };
275     assertions = with cfg; [
276       { assertion = (providerKey.public == null && providerKey.secret == null) ||
277                     (providerKey.secret != null && providerKey.public != null);
278         message = "The secret and public provider key must be set together.";
279       }
280     ];
282   };
284   meta.maintainers = with lib.maintainers; [ rnhmjoj ];