diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index e99e344b932..4ec4c5a6f5b 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -370,6 +370,7 @@ ./services/networking/dhcpd.nix ./services/networking/dnschain.nix ./services/networking/dnscrypt-proxy.nix + ./services/networking/dnscrypt-wrapper.nix ./services/networking/dnsmasq.nix ./services/networking/ejabberd.nix ./services/networking/fan.nix diff --git a/nixos/modules/services/networking/dnscrypt-wrapper.nix b/nixos/modules/services/networking/dnscrypt-wrapper.nix new file mode 100644 index 00000000000..85fac660d52 --- /dev/null +++ b/nixos/modules/services/networking/dnscrypt-wrapper.nix @@ -0,0 +1,187 @@ +{ config, lib, pkgs, ... }: +with lib; + +let + cfg = config.services.dnscrypt-wrapper; + dataDir = "/var/lib/dnscrypt-wrapper"; + + daemonArgs = with cfg; [ + "--listen-address=${address}:${toString port}" + "--resolver-address=${upstream.address}:${toString upstream.port}" + "--provider-name=${providerName}" + "--provider-publickey-file=public.key" + "--provider-secretkey-file=secret.key" + "--provider-cert-file=${providerName}.crt" + "--crypt-secretkey-file=${providerName}.key" + ]; + + genKeys = '' + # generates time-limited keypairs + keyGen() { + dnscrypt-wrapper --gen-crypt-keypair \ + --crypt-secretkey-file=${cfg.providerName}.key + + dnscrypt-wrapper --gen-cert-file \ + --crypt-secretkey-file=${cfg.providerName}.key \ + --provider-cert-file=${cfg.providerName}.crt \ + --provider-publickey-file=public.key \ + --provider-secretkey-file=secret.key \ + --cert-file-expire-days=${toString cfg.keys.expiration} + } + + cd ${dataDir} + + # generate provider keypair (first run only) + if [ ! -f public.key ] || [ ! -f secret.key ]; then + dnscrypt-wrapper --gen-provider-keypair + fi + + # generate new keys for rotation + if [ ! -f ${cfg.providerName}.key ] || [ ! -f ${cfg.providerName}.crt ]; then + keyGen + fi + ''; + + rotateKeys = '' + # check if keys are not expired + keyValid() { + fingerprint=$(dnscrypt-wrapper --show-provider-publickey-fingerprint | awk '{print $(NF)}') + dnscrypt-proxy --test=${toString (cfg.keys.checkInterval + 1)} \ + --resolver-address=127.0.0.1:${toString cfg.port} \ + --provider-name=${cfg.providerName} \ + --provider-key=$fingerprint + } + + cd ${dataDir} + + # archive old keys and restart the service + if ! keyValid; then + mkdir -p oldkeys + mv ${cfg.providerName}.key oldkeys/${cfg.providerName}-$(date +%F-%T).key + mv ${cfg.providerName}.crt oldkeys/${cfg.providerName}-$(date +%F-%T).crt + systemctl restart dnscrypt-wrapper + fi + ''; + +in { + + + ###### interface + + options.services.dnscrypt-wrapper = { + enable = mkEnableOption "DNSCrypt wrapper"; + + address = mkOption { + type = types.str; + default = "127.0.0.1"; + description = '' + The DNSCrypt wrapper will bind to this IP address. + ''; + }; + + port = mkOption { + type = types.int; + default = 5353; + description = '' + The DNSCrypt wrapper will listen for DNS queries on this port. + ''; + }; + + providerName = mkOption { + type = types.str; + default = "2.dnscrypt-cert.${config.networking.hostName}"; + example = "2.dnscrypt-cert.myresolver"; + description = '' + The name that will be given to this DNSCrypt resolver. + Note: the resolver name must start with 2.dnscrypt-cert.. + ''; + }; + + upstream.address = mkOption { + type = types.str; + default = "127.0.0.1"; + description = '' + The IP address of the upstream DNS server DNSCrypt will "wrap". + ''; + }; + + upstream.port = mkOption { + type = types.int; + default = 53; + description = '' + The port of the upstream DNS server DNSCrypt will "wrap". + ''; + }; + + keys.expiration = mkOption { + type = types.int; + default = 30; + description = '' + The duration (in days) of the time-limited secret key. + This will be automatically rotated before expiration. + ''; + }; + + keys.checkInterval = mkOption { + type = types.int; + default = 1440; + description = '' + The time interval (in minutes) between key expiration checks. + ''; + }; + + }; + + + ###### implementation + + config = mkIf cfg.enable { + + users.users.dnscrypt-wrapper = { + description = "dnscrypt-wrapper daemon user"; + home = "${dataDir}"; + createHome = true; + }; + users.groups.dnscrypt-wrapper = { }; + + + systemd.services.dnscrypt-wrapper = { + description = "dnscrypt-wrapper daemon"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pkgs.dnscrypt-wrapper ]; + + serviceConfig = { + User = "dnscrypt-wrapper"; + WorkingDirectory = dataDir; + Restart = "on-failure"; + ExecStart = "${pkgs.dnscrypt-wrapper}/bin/dnscrypt-wrapper ${toString daemonArgs}"; + }; + + preStart = genKeys; + }; + + + systemd.services.dnscrypt-wrapper-rotate = { + after = [ "network.target" ]; + requires = [ "dnscrypt-wrapper.service" ]; + description = "Rotates DNSCrypt wrapper keys if soon to expire"; + + path = with pkgs; [ dnscrypt-wrapper dnscrypt-proxy gawk ]; + script = rotateKeys; + }; + + + systemd.timers.dnscrypt-wrapper-rotate = { + description = "Periodically check DNSCrypt wrapper keys for expiration"; + wantedBy = [ "multi-user.target" ]; + + timerConfig = { + Unit = "dnscrypt-wrapper-rotate.service"; + OnBootSec = "1min"; + OnUnitActiveSec = cfg.keys.checkInterval * 60; + }; + }; + + }; +}