diff options
Diffstat (limited to 'nixpkgs/nixos/modules/services/networking/dnscrypt-proxy.nix')
-rw-r--r-- | nixpkgs/nixos/modules/services/networking/dnscrypt-proxy.nix | 328 |
1 files changed, 328 insertions, 0 deletions
diff --git a/nixpkgs/nixos/modules/services/networking/dnscrypt-proxy.nix b/nixpkgs/nixos/modules/services/networking/dnscrypt-proxy.nix new file mode 100644 index 000000000000..8edcf925dbfa --- /dev/null +++ b/nixpkgs/nixos/modules/services/networking/dnscrypt-proxy.nix @@ -0,0 +1,328 @@ +{ 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/dyne/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 + <filename>${resolverList}</filename>. 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 <citerefentry><refentrytitle>dnscrypt-proxy</refentrytitle> + <manvolnum>8</manvolnum></citerefentry> 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"; + } + ]; + + # make man 8 dnscrypt-proxy work + environment.systemPackages = [ pkgs.dnscrypt-proxy ]; + + 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/random r, + /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.utillinuxMinimal.out}/lib/libmount.so.* mr, + ${getLib pkgs.utillinuxMinimal.out}/lib/libblkid.so.* mr, + ${getLib pkgs.utillinuxMinimal.out}/lib/libuuid.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/dyne/dnscrypt-proxy/master/dnscrypt-resolvers.csv + $get -o dnscrypt-resolvers.csv.minisig.tmp \ + https://$domain/dyne/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. + '') + ]; +} |