summaryrefslogtreecommitdiff
path: root/hosts/surtr/tls/default.nix
diff options
context:
space:
mode:
Diffstat (limited to 'hosts/surtr/tls/default.nix')
-rw-r--r--hosts/surtr/tls/default.nix155
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 @@
3with lib; 3with lib;
4 4
5let 5let
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 };
28in { 13in {
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}