diff options
| author | Gregor Kleen <gkleen@yggdrasil.li> | 2023-01-30 12:20:23 +0100 |
|---|---|---|
| committer | Gregor Kleen <gkleen@yggdrasil.li> | 2023-01-30 12:20:23 +0100 |
| commit | cfc871cce6aefaa0ff64619780a807cba761c6b2 (patch) | |
| tree | 965e8276ed36f11698b6c7d6eadab9f88d5f97c5 /hosts/surtr/tls/default.nix | |
| parent | aa54fe89b98d354d21141c589332ce7950ef2e59 (diff) | |
| download | nixos-cfc871cce6aefaa0ff64619780a807cba761c6b2.tar nixos-cfc871cce6aefaa0ff64619780a807cba761c6b2.tar.gz nixos-cfc871cce6aefaa0ff64619780a807cba761c6b2.tar.bz2 nixos-cfc871cce6aefaa0ff64619780a807cba761c6b2.tar.xz nixos-cfc871cce6aefaa0ff64619780a807cba761c6b2.zip | |
...
Diffstat (limited to 'hosts/surtr/tls/default.nix')
| -rw-r--r-- | hosts/surtr/tls/default.nix | 155 |
1 files changed, 69 insertions, 86 deletions
diff --git a/hosts/surtr/tls/default.nix b/hosts/surtr/tls/default.nix index f1a515db..b1c05888 100644 --- a/hosts/surtr/tls/default.nix +++ b/hosts/surtr/tls/default.nix | |||
| @@ -3,111 +3,94 @@ | |||
| 3 | with lib; | 3 | with lib; |
| 4 | 4 | ||
| 5 | let | 5 | let |
| 6 | inherit (customUtils) mapFilterAttrs; | ||
| 7 | |||
| 8 | tsigSecretName = domain: "${domain}_tsig-secret"; | 6 | tsigSecretName = domain: "${domain}_tsig-secret"; |
| 7 | tsigKey = domain: | ||
| 8 | let | ||
| 9 | tsigKeyPath = ./tsig_keys + "/${domain}"; | ||
| 10 | in assert assertMsg (pathExists tsigKeyPath) "‘${domain}’ does not exist in `tls/tsig_keys` -- is this a new ACME domain and you forgot to generate the TSIG key? If so, run `gup tls/tsig_keys/${domain}`"; tsigKeyPath; | ||
| 9 | 11 | ||
| 10 | cfg = config.security.acme; | 12 | cfg = config.security.acme; |
| 11 | |||
| 12 | domainOptions = { | ||
| 13 | options = { | ||
| 14 | wildcard = mkOption { | ||
| 15 | type = types.bool; | ||
| 16 | default = false; | ||
| 17 | }; | ||
| 18 | zone = mkOption { | ||
| 19 | type = types.nullOr types.str; | ||
| 20 | default = null; | ||
| 21 | }; | ||
| 22 | certCfg = mkOption { | ||
| 23 | type = types.attrs; | ||
| 24 | default = {}; | ||
| 25 | }; | ||
| 26 | }; | ||
| 27 | }; | ||
| 28 | in { | 13 | in { |
| 29 | options = { | 14 | options = { |
| 30 | security.acme = { | 15 | security.acme = { |
| 31 | domains = mkOption { | 16 | # This file introduces an additional nixos module option |
| 32 | type = types.attrsOf (types.submodule domainOptions); | 17 | # `security.acme.rfc2136Domains`. |
| 18 | # The new option is an attrset of domain names mapping to | ||
| 19 | # additional settings. | ||
| 20 | rfc2136Domains = mkOption { | ||
| 21 | type = types.attrsOf (types.submodule { | ||
| 22 | options = { | ||
| 23 | wildcard = mkOption { | ||
| 24 | type = types.bool; | ||
| 25 | default = false; | ||
| 26 | }; | ||
| 27 | restartUnits = mkOption { | ||
| 28 | type = types.listOf types.str; | ||
| 29 | default = []; | ||
| 30 | }; | ||
| 31 | }; | ||
| 32 | }); | ||
| 33 | default = {}; | 33 | default = {}; |
| 34 | }; | 34 | }; |
| 35 | }; | 35 | }; |
| 36 | }; | 36 | }; |
| 37 | 37 | ||
| 38 | config = { | 38 | config = { |
| 39 | security.acme.domains = genAttrs ["dirty-haskell.org" "141.li" "xmpp.li" "synapse.li" "yggdrasil.li" "praseodym.org" "rheperire.org" "kleen.li" "nights.email" "bouncy.email" "kleen.consulting"] (domain: { wildcard = true; }); | ||
| 40 | |||
| 41 | fileSystems."/var/lib/acme" = | ||
| 42 | { device = "surtr/safe/var-lib-acme"; | ||
| 43 | fsType = "zfs"; | ||
| 44 | }; | ||
| 45 | |||
| 46 | security.acme = { | 39 | security.acme = { |
| 40 | # Some default/global ACME settings | ||
| 41 | |||
| 47 | acceptTerms = true; | 42 | acceptTerms = true; |
| 48 | preliminarySelfsigned = true; # DNS challenge is slow | 43 | # DNS challenge is slow |
| 44 | preliminarySelfsigned = true; | ||
| 49 | defaults = { | 45 | defaults = { |
| 50 | email = "phikeebaogobaegh@141.li"; | 46 | email = "phikeebaogobaegh@141.li"; |
| 51 | keyType = "rsa4096"; # we don't like NIST curves | 47 | # We don't like NIST curves and Let's Encrypt doesn't support |
| 52 | extraLegoRenewFlags = [ | 48 | # anything better |
| 53 | # "--preferred-chain" "ISRG Root X1" | 49 | keyType = "rsa4096"; |
| 54 | # "--always-deactivate-authorizations" "true" | ||
| 55 | ]; | ||
| 56 | extraLegoRunFlags = config.security.acme.defaults.extraLegoRenewFlags; | ||
| 57 | }; | 50 | }; |
| 58 | certs = | ||
| 59 | let | ||
| 60 | domainAttrset = domain: let | ||
| 61 | tsigPath = ./tsig_keys + "/${domain}"; | ||
| 62 | isTsig = pathExists tsigPath; | ||
| 63 | shared = { | ||
| 64 | inherit domain; | ||
| 65 | extraDomainNames = optional cfg.domains.${domain}.wildcard "*.${domain}"; | ||
| 66 | dnsResolver = "127.0.0.1:5353"; | ||
| 67 | }; | ||
| 68 | mkRFC2136 = shared // rec { | ||
| 69 | dnsProvider = "rfc2136"; | ||
| 70 | credentialsFile = pkgs.writeText "${domain}_credentials.env" '' | ||
| 71 | RFC2136_NAMESERVER=127.0.0.1:53 | ||
| 72 | RFC2136_TSIG_ALGORITHM=hmac-sha256. | ||
| 73 | RFC2136_TSIG_KEY=${domain}_acme_key | ||
| 74 | RFC2136_TSIG_SECRET_FILE=/run/credentials/acme-${domain}.service/tsig_secret | ||
| 75 | RFC2136_TTL=0 | ||
| 76 | RFC2136_PROPAGATION_TIMEOUT=60 | ||
| 77 | RFC2136_POLLING_INTERVAL=2 | ||
| 78 | RFC2136_SEQUENCE_INTERVAL=1 | ||
| 79 | ''; | ||
| 80 | dnsPropagationCheck = false; | ||
| 81 | }; | ||
| 82 | in assert isTsig; mkRFC2136 // cfg.domains.${domain}.certCfg; | ||
| 83 | in genAttrs (attrNames cfg.domains) domainAttrset; | ||
| 84 | }; | ||
| 85 | 51 | ||
| 86 | sops.secrets = let | 52 | # For each domain specified in |
| 87 | toTSIGSecret = n: v: | 53 | # `config.security.acme.rfc2136Domains`, configure an additional |
| 88 | if v == "regular" || v == "symlink" | 54 | # entry in `config.security.acme.certs` containing appropriate |
| 89 | then nameValuePair (tsigSecretName n) { | 55 | # settings to provision the certificate via DNS-01 |
| 90 | format = "binary"; | 56 | certs = mapAttrs (domain: domainCfg: { |
| 91 | sopsFile = ./tsig_keys + "/${n}"; | 57 | inherit domain; |
| 92 | } else null; | 58 | extraDomainNames = optional domainCfg.wildcard "*.${domain}"; |
| 93 | in mapFilterAttrs (_: v: v != null) toTSIGSecret (builtins.readDir ./tsig_keys); | 59 | dnsResolver = "127.0.0.1:53"; |
| 60 | dnsProvider = "rfc2136"; | ||
| 61 | credentialsFile = pkgs.writeText "${domain}_credentials.env" '' | ||
| 62 | RFC2136_NAMESERVER=127.0.0.1:53 | ||
| 63 | RFC2136_TSIG_ALGORITHM=hmac-sha256. | ||
| 64 | RFC2136_TSIG_KEY=${domain}_acme_key | ||
| 65 | RFC2136_TSIG_SECRET_FILE=/run/credentials/acme-${domain}.service/${tsigSecretName domain} | ||
| 66 | RFC2136_TTL=0 | ||
| 67 | RFC2136_PROPAGATION_TIMEOUT=60 | ||
| 68 | RFC2136_POLLING_INTERVAL=2 | ||
| 69 | RFC2136_SEQUENCE_INTERVAL=1 | ||
| 70 | ''; | ||
| 71 | dnsPropagationCheck = false; | ||
| 72 | postRun = mkIf (domainCfg.restartUnits != []) '' | ||
| 73 | systemctl --no-block try-restart ${escapeShellArgs domainCfg.restartUnits} | ||
| 74 | ''; | ||
| 75 | }) cfg.rfc2136Domains; | ||
| 76 | }; | ||
| 94 | 77 | ||
| 95 | systemd.services = | 78 | # Decrypt all `tsig_keys/*` at runtime |
| 96 | let | 79 | sops.secrets = mapAttrs' (domain: domainCfg: nameValuePair (tsigSecretName domain) { |
| 97 | serviceAttrset = domain: { | 80 | format = "binary"; |
| 98 | after = [ "knot.service" ]; | 81 | sopsFile = tsigKey domain; |
| 99 | bindsTo = [ "knot.service" ]; | 82 | restartUnits = [ "acme-${domain}.service" ]; |
| 100 | serviceConfig = { | 83 | }) cfg.rfc2136Domains; |
| 101 | LoadCredential = ["tsig_secret:${config.sops.secrets.${tsigSecretName domain}.path}"]; | ||
| 102 | SystemCallFilter = mkForce [ "@system-service" "~@privileged" "@chown" ]; | ||
| 103 | }; | ||
| 104 | }; | ||
| 105 | in mapAttrs' (domain: nameValuePair "acme-${domain}") (genAttrs (attrNames config.security.acme.certs) serviceAttrset); | ||
| 106 | 84 | ||
| 107 | services.certspotter = { | 85 | # Provide appropriate `tsig_key/*` to systemd service performing |
| 108 | extraOptions = [ "-verbose" "-num_workers" "4" "-batch_size" "2000" ]; | 86 | # certificate provisioning |
| 109 | watchList = map (domain: ".${domain}") (attrNames cfg.domains); | 87 | systemd.services = mapAttrs' (domain: domainCfg: nameValuePair "acme-${domain}" { |
| 110 | logs = "https://www.gstatic.com/ct/log_list/v2/all_logs_list.json"; | 88 | after = [ "knot.service" ]; |
| 111 | }; | 89 | bindsTo = [ "knot.service" ]; |
| 90 | serviceConfig = { | ||
| 91 | LoadCredential = [ "${tsigSecretName domain}:${config.sops.secrets.${tsigSecretName domain}.path}" ]; | ||
| 92 | SystemCallFilter = mkForce [ "@system-service" "~@privileged" "@chown" ]; | ||
| 93 | }; | ||
| 94 | }) cfg.rfc2136Domains; | ||
| 112 | }; | 95 | }; |
| 113 | } | 96 | } |
