From 4ebb9621f44bf440ed8f35fc3ec3329840139136 Mon Sep 17 00:00:00 2001 From: rnhmjoj Date: Mon, 8 Jan 2018 15:09:33 +0100 Subject: [PATCH] Revert "nixos/dnscrypt-proxy: remove" This reverts commit 5dc2853981b6e9287668dd17477375adfeb60ebf. The project has a new maintainer. --- nixos/doc/manual/release-notes/rl-1803.xml | 6 - nixos/modules/module-list.nix | 1 + nixos/modules/rename.nix | 3 - .../services/networking/dnscrypt-proxy.nix | 321 ++++++++++++++++++ .../services/networking/dnscrypt-proxy.xml | 69 ++++ nixos/release.nix | 1 + nixos/tests/dnscrypt-proxy.nix | 32 ++ 7 files changed, 424 insertions(+), 9 deletions(-) create mode 100644 nixos/modules/services/networking/dnscrypt-proxy.nix create mode 100644 nixos/modules/services/networking/dnscrypt-proxy.xml create mode 100644 nixos/tests/dnscrypt-proxy.nix diff --git a/nixos/doc/manual/release-notes/rl-1803.xml b/nixos/doc/manual/release-notes/rl-1803.xml index d2d4a0d32bb..12ff2e39a1b 100644 --- a/nixos/doc/manual/release-notes/rl-1803.xml +++ b/nixos/doc/manual/release-notes/rl-1803.xml @@ -139,12 +139,6 @@ following incompatible changes: will be accessible at /run/memcached/memcached.sock. - - - The DNSCrypt proxy module has been removed, the upstream project - is no longer maintained. - - diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 8846bf9e8b1..8d329b5b4b2 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -446,6 +446,7 @@ ./services/networking/dhcpd.nix ./services/networking/dnscache.nix ./services/networking/dnschain.nix + ./services/networking/dnscrypt-proxy.nix ./services/networking/dnscrypt-wrapper.nix ./services/networking/dnsmasq.nix ./services/networking/ejabberd.nix diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix index cd6ae35c0d9..562be13a3f6 100644 --- a/nixos/modules/rename.nix +++ b/nixos/modules/rename.nix @@ -89,9 +89,6 @@ with lib; # Tarsnap (mkRenamedOptionModule [ "services" "tarsnap" "config" ] [ "services" "tarsnap" "archives" ]) - # dnscrypt-proxy - (mkRemovedOptionModule [ "services" "dnscrypt-proxy" "enable" ] "") - # ibus (mkRenamedOptionModule [ "programs" "ibus" "plugins" ] [ "i18n" "inputMethod" "ibus" "engines" ]) diff --git a/nixos/modules/services/networking/dnscrypt-proxy.nix b/nixos/modules/services/networking/dnscrypt-proxy.nix new file mode 100644 index 00000000000..ed658258c7f --- /dev/null +++ b/nixos/modules/services/networking/dnscrypt-proxy.nix @@ -0,0 +1,321 @@ +{ config, lib, pkgs, ... }: +with lib; + +let + cfg = config.services.dnscrypt-proxy; + + stateDirectory = "/var/lib/dnscrypt-proxy"; + + # The minisign public key used to sign the upstream resolver list. + # This is somewhat more flexible than preloading the key as an + # embedded string. + upstreamResolverListPubKey = pkgs.fetchurl { + url = https://raw.githubusercontent.com/jedisct1/dnscrypt-proxy/master/minisign.pub; + sha256 = "18lnp8qr6ghfc2sd46nn1rhcpr324fqlvgsp4zaigw396cd7vnnh"; + }; + + # Internal flag indicating whether the upstream resolver list is used. + useUpstreamResolverList = cfg.customResolver == null; + + # The final local address. + localAddress = "${cfg.localAddress}:${toString cfg.localPort}"; + + # The final resolvers list path. + resolverList = "${stateDirectory}/dnscrypt-resolvers.csv"; + + # Build daemon command line + + resolverArgs = + if (cfg.customResolver == null) + then + [ "-L ${resolverList}" + "-R ${cfg.resolverName}" + ] + else with cfg.customResolver; + [ "-N ${name}" + "-k ${key}" + "-r ${address}:${toString port}" + ]; + + daemonArgs = + [ "-a ${localAddress}" ] + ++ resolverArgs + ++ cfg.extraArgs; +in + +{ + meta = { + maintainers = with maintainers; [ joachifm ]; + doc = ./dnscrypt-proxy.xml; + }; + + options = { + # Before adding another option, consider whether it could + # equally well be passed via extraArgs. + + services.dnscrypt-proxy = { + enable = mkOption { + default = false; + type = types.bool; + description = "Whether to enable the DNSCrypt client proxy"; + }; + + localAddress = mkOption { + default = "127.0.0.1"; + type = types.str; + description = '' + Listen for DNS queries to relay on this address. The only reason to + change this from its default value is to proxy queries on behalf + of other machines (typically on the local network). + ''; + }; + + localPort = mkOption { + default = 53; + type = types.int; + description = '' + Listen for DNS queries to relay on this port. The default value + assumes that the DNSCrypt proxy should relay DNS queries directly. + When running as a forwarder for another DNS client, set this option + to a different value; otherwise leave the default. + ''; + }; + + resolverName = mkOption { + default = "random"; + example = "dnscrypt.eu-nl"; + type = types.nullOr types.str; + description = '' + The name of the DNSCrypt resolver to use, taken from + ${resolverList}. The default is to + pick a random non-logging resolver that supports DNSSEC. + ''; + }; + + customResolver = mkOption { + default = null; + description = '' + Use an unlisted resolver (e.g., a private DNSCrypt provider). For + advanced users only. If specified, this option takes precedence. + ''; + type = types.nullOr (types.submodule ({ ... }: { options = { + address = mkOption { + type = types.str; + description = "IP address"; + example = "208.67.220.220"; + }; + + port = mkOption { + type = types.int; + description = "Port"; + default = 443; + }; + + name = mkOption { + type = types.str; + description = "Fully qualified domain name"; + example = "2.dnscrypt-cert.example.com"; + }; + + key = mkOption { + type = types.str; + description = "Public key"; + example = "B735:1140:206F:225D:3E2B:D822:D7FD:691E:A1C3:3CC8:D666:8D0C:BE04:BFAB:CA43:FB79"; + }; + }; })); + }; + + extraArgs = mkOption { + default = []; + type = types.listOf types.str; + description = '' + Additional command-line arguments passed verbatim to the daemon. + See dnscrypt-proxy + 8 for details. + ''; + example = [ "-X libdcplugin_example_cache.so,--min-ttl=60" ]; + }; + }; + }; + + config = mkIf cfg.enable (mkMerge [{ + assertions = [ + { assertion = (cfg.customResolver != null) || (cfg.resolverName != null); + message = "please configure upstream DNSCrypt resolver"; + } + ]; + + users.users.dnscrypt-proxy = { + description = "dnscrypt-proxy daemon user"; + isSystemUser = true; + group = "dnscrypt-proxy"; + }; + users.groups.dnscrypt-proxy = {}; + + systemd.sockets.dnscrypt-proxy = { + description = "dnscrypt-proxy listening socket"; + documentation = [ "man:dnscrypt-proxy(8)" ]; + + wantedBy = [ "sockets.target" ]; + + socketConfig = { + ListenStream = localAddress; + ListenDatagram = localAddress; + }; + }; + + systemd.services.dnscrypt-proxy = { + description = "dnscrypt-proxy daemon"; + documentation = [ "man:dnscrypt-proxy(8)" ]; + + before = [ "nss-lookup.target" ]; + after = [ "network.target" ]; + requires = [ "dnscrypt-proxy.socket "]; + + serviceConfig = { + NonBlocking = "true"; + ExecStart = "${pkgs.dnscrypt-proxy}/bin/dnscrypt-proxy ${toString daemonArgs}"; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + + User = "dnscrypt-proxy"; + + PrivateTmp = true; + PrivateDevices = true; + ProtectHome = true; + }; + }; + } + + (mkIf config.security.apparmor.enable { + systemd.services.dnscrypt-proxy.after = [ "apparmor.service" ]; + + security.apparmor.profiles = singleton (pkgs.writeText "apparmor-dnscrypt-proxy" '' + ${pkgs.dnscrypt-proxy}/bin/dnscrypt-proxy { + /dev/null rw, + /dev/urandom r, + + /etc/passwd r, + /etc/group r, + ${config.environment.etc."nsswitch.conf".source} r, + + ${getLib pkgs.glibc}/lib/*.so mr, + ${pkgs.tzdata}/share/zoneinfo/** r, + + network inet stream, + network inet6 stream, + network inet dgram, + network inet6 dgram, + + ${getLib pkgs.dnscrypt-proxy}/lib/dnscrypt-proxy/libdcplugin*.so mr, + + ${getLib pkgs.gcc.cc}/lib/libssp.so.* mr, + ${getLib pkgs.libsodium}/lib/libsodium.so.* mr, + ${getLib pkgs.systemd}/lib/libsystemd.so.* mr, + ${getLib pkgs.xz}/lib/liblzma.so.* mr, + ${getLib pkgs.libgcrypt}/lib/libgcrypt.so.* mr, + ${getLib pkgs.libgpgerror}/lib/libgpg-error.so.* mr, + ${getLib pkgs.libcap}/lib/libcap.so.* mr, + ${getLib pkgs.lz4}/lib/liblz4.so.* mr, + ${getLib pkgs.attr}/lib/libattr.so.* mr, # */ + + ${resolverList} r, + + /run/systemd/notify rw, + } + ''); + }) + + (mkIf useUpstreamResolverList { + systemd.services.init-dnscrypt-proxy-statedir = { + description = "Initialize dnscrypt-proxy state directory"; + + wantedBy = [ "dnscrypt-proxy.service" ]; + before = [ "dnscrypt-proxy.service" ]; + + script = '' + mkdir -pv ${stateDirectory} + chown -c dnscrypt-proxy:dnscrypt-proxy ${stateDirectory} + cp -uv \ + ${pkgs.dnscrypt-proxy}/share/dnscrypt-proxy/dnscrypt-resolvers.csv \ + ${stateDirectory} + ''; + + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + }; + + systemd.services.update-dnscrypt-resolvers = { + description = "Update list of DNSCrypt resolvers"; + + requires = [ "init-dnscrypt-proxy-statedir.service" ]; + after = [ "init-dnscrypt-proxy-statedir.service" ]; + + path = with pkgs; [ curl diffutils dnscrypt-proxy minisign ]; + script = '' + cd ${stateDirectory} + domain=raw.githubusercontent.com + get="curl -fSs --resolve $domain:443:$(hostip -r 8.8.8.8 $domain | head -1)" + $get -o dnscrypt-resolvers.csv.tmp \ + https://$domain/jedisct1/dnscrypt-proxy/master/dnscrypt-resolvers.csv + $get -o dnscrypt-resolvers.csv.minisig.tmp \ + https://$domain/jedisct1/dnscrypt-proxy/master/dnscrypt-resolvers.csv.minisig + mv dnscrypt-resolvers.csv.minisig{.tmp,} + if ! minisign -q -V -p ${upstreamResolverListPubKey} \ + -m dnscrypt-resolvers.csv.tmp -x dnscrypt-resolvers.csv.minisig ; then + echo "failed to verify resolver list!" >&2 + exit 1 + fi + [[ -f dnscrypt-resolvers.csv ]] && mv dnscrypt-resolvers.csv{,.old} + mv dnscrypt-resolvers.csv{.tmp,} + if cmp dnscrypt-resolvers.csv{,.old} ; then + echo "no change" + else + echo "resolver list updated" + fi + ''; + + serviceConfig = { + PrivateTmp = true; + PrivateDevices = true; + ProtectHome = true; + ProtectSystem = "strict"; + ReadWritePaths = "${dirOf stateDirectory} ${stateDirectory}"; + SystemCallFilter = "~@mount"; + }; + }; + + systemd.timers.update-dnscrypt-resolvers = { + wantedBy = [ "timers.target" ]; + timerConfig = { + OnBootSec = "5min"; + OnUnitActiveSec = "6h"; + }; + }; + }) + ]); + + imports = [ + (mkRenamedOptionModule [ "services" "dnscrypt-proxy" "port" ] [ "services" "dnscrypt-proxy" "localPort" ]) + + (mkChangedOptionModule + [ "services" "dnscrypt-proxy" "tcpOnly" ] + [ "services" "dnscrypt-proxy" "extraArgs" ] + (config: + let val = getAttrFromPath [ "services" "dnscrypt-proxy" "tcpOnly" ] config; in + optional val "-T")) + + (mkChangedOptionModule + [ "services" "dnscrypt-proxy" "ephemeralKeys" ] + [ "services" "dnscrypt-proxy" "extraArgs" ] + (config: + let val = getAttrFromPath [ "services" "dnscrypt-proxy" "ephemeralKeys" ] config; in + optional val "-E")) + + (mkRemovedOptionModule [ "services" "dnscrypt-proxy" "resolverList" ] '' + The current resolver listing from upstream is always used + unless a custom resolver is specified. + '') + ]; +} diff --git a/nixos/modules/services/networking/dnscrypt-proxy.xml b/nixos/modules/services/networking/dnscrypt-proxy.xml new file mode 100644 index 00000000000..555c6df4d55 --- /dev/null +++ b/nixos/modules/services/networking/dnscrypt-proxy.xml @@ -0,0 +1,69 @@ + + + DNSCrypt client proxy + + + The DNSCrypt client proxy relays DNS queries to a DNSCrypt enabled + upstream resolver. The traffic between the client and the upstream + resolver is encrypted and authenticated, mitigating the risk of MITM + attacks, DNS poisoning attacks, and third-party snooping (assuming the + upstream is trustworthy). + + + Basic configuration + + + To enable the client proxy, set + + services.dnscrypt-proxy.enable = true; + + + + + Enabling the client proxy does not alter the system nameserver; to + relay local queries, prepend 127.0.0.1 to + . + + + + + As a forwarder for another DNS client + + + To run the DNSCrypt proxy client as a forwarder for another + DNS client, change the default proxy listening port to a + non-standard value and point the other client to it: + + services.dnscrypt-proxy.localPort = 43; + + + + dnsmasq + + + { + services.dnsmasq.enable = true; + services.dnsmasq.servers = [ "127.0.0.1#43" ]; + } + + + + + unbound + + + { + services.unbound.enable = true; + services.unbound.forwardAddresses = [ "127.0.0.1@43" ]; + } + + + + + + + diff --git a/nixos/release.nix b/nixos/release.nix index 5ae32928240..cf3fe6abd48 100644 --- a/nixos/release.nix +++ b/nixos/release.nix @@ -255,6 +255,7 @@ in rec { tests.docker = hydraJob (import tests/docker.nix { system = "x86_64-linux"; }); tests.docker-edge = hydraJob (import tests/docker-edge.nix { system = "x86_64-linux"; }); tests.dovecot = callTest tests/dovecot.nix {}; + tests.dnscrypt-proxy = callTest tests/dnscrypt-proxy.nix { system = "x86_64-linux"; }; tests.ecryptfs = callTest tests/ecryptfs.nix {}; tests.etcd = hydraJob (import tests/etcd.nix { system = "x86_64-linux"; }); tests.ec2-nixops = hydraJob (import tests/ec2.nix { system = "x86_64-linux"; }).boot-ec2-nixops; diff --git a/nixos/tests/dnscrypt-proxy.nix b/nixos/tests/dnscrypt-proxy.nix new file mode 100644 index 00000000000..84562336825 --- /dev/null +++ b/nixos/tests/dnscrypt-proxy.nix @@ -0,0 +1,32 @@ +import ./make-test.nix ({ pkgs, ... }: { + name = "dnscrypt-proxy"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ joachifm ]; + }; + + nodes = { + # A client running the recommended setup: DNSCrypt proxy as a forwarder + # for a caching DNS client. + client = + { config, pkgs, ... }: + let localProxyPort = 43; in + { + security.apparmor.enable = true; + + services.dnscrypt-proxy.enable = true; + services.dnscrypt-proxy.localPort = localProxyPort; + services.dnscrypt-proxy.extraArgs = [ "-X libdcplugin_example.so" ]; + + services.dnsmasq.enable = true; + services.dnsmasq.servers = [ "127.0.0.1#${toString localProxyPort}" ]; + }; + }; + + testScript = '' + $client->waitForUnit("dnsmasq"); + + # The daemon is socket activated; sending a single ping should activate it. + $client->execute("${pkgs.iputils}/bin/ping -c1 example.com"); + $client->succeed("systemctl is-active dnscrypt-proxy"); + ''; +})