at 18.09-beta 5.6 kB view raw
1{ config, lib, pkgs, ... }: 2with lib; 3 4let 5 cfg = config.services.dnscrypt-wrapper; 6 dataDir = "/var/lib/dnscrypt-wrapper"; 7 8 daemonArgs = with cfg; [ 9 "--listen-address=${address}:${toString port}" 10 "--resolver-address=${upstream.address}:${toString upstream.port}" 11 "--provider-name=${providerName}" 12 "--provider-publickey-file=public.key" 13 "--provider-secretkey-file=secret.key" 14 "--provider-cert-file=${providerName}.crt" 15 "--crypt-secretkey-file=${providerName}.key" 16 ]; 17 18 genKeys = '' 19 # generates time-limited keypairs 20 keyGen() { 21 dnscrypt-wrapper --gen-crypt-keypair \ 22 --crypt-secretkey-file=${cfg.providerName}.key 23 24 dnscrypt-wrapper --gen-cert-file \ 25 --crypt-secretkey-file=${cfg.providerName}.key \ 26 --provider-cert-file=${cfg.providerName}.crt \ 27 --provider-publickey-file=public.key \ 28 --provider-secretkey-file=secret.key \ 29 --cert-file-expire-days=${toString cfg.keys.expiration} 30 } 31 32 cd ${dataDir} 33 34 # generate provider keypair (first run only) 35 if [ ! -f public.key ] || [ ! -f secret.key ]; then 36 dnscrypt-wrapper --gen-provider-keypair 37 fi 38 39 # generate new keys for rotation 40 if [ ! -f ${cfg.providerName}.key ] || [ ! -f ${cfg.providerName}.crt ]; then 41 keyGen 42 fi 43 ''; 44 45 rotateKeys = '' 46 # check if keys are not expired 47 keyValid() { 48 fingerprint=$(dnscrypt-wrapper --show-provider-publickey | awk '{print $(NF)}') 49 dnscrypt-proxy --test=${toString (cfg.keys.checkInterval + 1)} \ 50 --resolver-address=127.0.0.1:${toString cfg.port} \ 51 --provider-name=${cfg.providerName} \ 52 --provider-key=$fingerprint 53 } 54 55 cd ${dataDir} 56 57 # archive old keys and restart the service 58 if ! keyValid; then 59 echo "certificate soon to become invalid; backing up old cert" 60 mkdir -p oldkeys 61 mv -v ${cfg.providerName}.key oldkeys/${cfg.providerName}-$(date +%F-%T).key 62 mv -v ${cfg.providerName}.crt oldkeys/${cfg.providerName}-$(date +%F-%T).crt 63 systemctl restart dnscrypt-wrapper 64 fi 65 ''; 66 67in { 68 69 70 ###### interface 71 72 options.services.dnscrypt-wrapper = { 73 enable = mkEnableOption "DNSCrypt wrapper"; 74 75 address = mkOption { 76 type = types.str; 77 default = "127.0.0.1"; 78 description = '' 79 The DNSCrypt wrapper will bind to this IP address. 80 ''; 81 }; 82 83 port = mkOption { 84 type = types.int; 85 default = 5353; 86 description = '' 87 The DNSCrypt wrapper will listen for DNS queries on this port. 88 ''; 89 }; 90 91 providerName = mkOption { 92 type = types.str; 93 default = "2.dnscrypt-cert.${config.networking.hostName}"; 94 example = "2.dnscrypt-cert.myresolver"; 95 description = '' 96 The name that will be given to this DNSCrypt resolver. 97 Note: the resolver name must start with <literal>2.dnscrypt-cert.</literal>. 98 ''; 99 }; 100 101 upstream.address = mkOption { 102 type = types.str; 103 default = "127.0.0.1"; 104 description = '' 105 The IP address of the upstream DNS server DNSCrypt will "wrap". 106 ''; 107 }; 108 109 upstream.port = mkOption { 110 type = types.int; 111 default = 53; 112 description = '' 113 The port of the upstream DNS server DNSCrypt will "wrap". 114 ''; 115 }; 116 117 keys.expiration = mkOption { 118 type = types.int; 119 default = 30; 120 description = '' 121 The duration (in days) of the time-limited secret key. 122 This will be automatically rotated before expiration. 123 ''; 124 }; 125 126 keys.checkInterval = mkOption { 127 type = types.int; 128 default = 1440; 129 description = '' 130 The time interval (in minutes) between key expiration checks. 131 ''; 132 }; 133 134 }; 135 136 137 ###### implementation 138 139 config = mkIf cfg.enable { 140 141 users.users.dnscrypt-wrapper = { 142 description = "dnscrypt-wrapper daemon user"; 143 home = "${dataDir}"; 144 createHome = true; 145 }; 146 users.groups.dnscrypt-wrapper = { }; 147 148 security.polkit.extraConfig = '' 149 // Allow dnscrypt-wrapper user to restart dnscrypt-wrapper.service 150 polkit.addRule(function(action, subject) { 151 if (action.id == "org.freedesktop.systemd1.manage-units" && 152 action.lookup("unit") == "dnscrypt-wrapper.service" && 153 subject.user == "dnscrypt-wrapper") { 154 return polkit.Result.YES; 155 } 156 }); 157 ''; 158 159 systemd.services.dnscrypt-wrapper = { 160 description = "dnscrypt-wrapper daemon"; 161 after = [ "network.target" ]; 162 wantedBy = [ "multi-user.target" ]; 163 path = [ pkgs.dnscrypt-wrapper ]; 164 165 serviceConfig = { 166 User = "dnscrypt-wrapper"; 167 WorkingDirectory = dataDir; 168 Restart = "on-failure"; 169 ExecStart = "${pkgs.dnscrypt-wrapper}/bin/dnscrypt-wrapper ${toString daemonArgs}"; 170 }; 171 172 preStart = genKeys; 173 }; 174 175 176 systemd.services.dnscrypt-wrapper-rotate = { 177 after = [ "network.target" ]; 178 requires = [ "dnscrypt-wrapper.service" ]; 179 description = "Rotates DNSCrypt wrapper keys if soon to expire"; 180 181 path = with pkgs; [ dnscrypt-wrapper dnscrypt-proxy gawk ]; 182 script = rotateKeys; 183 serviceConfig.User = "dnscrypt-wrapper"; 184 }; 185 186 187 systemd.timers.dnscrypt-wrapper-rotate = { 188 description = "Periodically check DNSCrypt wrapper keys for expiration"; 189 wantedBy = [ "multi-user.target" ]; 190 191 timerConfig = { 192 Unit = "dnscrypt-wrapper-rotate.service"; 193 OnBootSec = "1min"; 194 OnUnitActiveSec = cfg.keys.checkInterval * 60; 195 }; 196 }; 197 198 }; 199}