diff options
Diffstat (limited to 'hosts/surtr/tls/default.nix')
-rw-r--r-- | hosts/surtr/tls/default.nix | 156 |
1 files changed, 156 insertions, 0 deletions
diff --git a/hosts/surtr/tls/default.nix b/hosts/surtr/tls/default.nix new file mode 100644 index 00000000..a1548feb --- /dev/null +++ b/hosts/surtr/tls/default.nix | |||
@@ -0,0 +1,156 @@ | |||
1 | { config, lib, customUtils, pkgs, ... }: | ||
2 | |||
3 | with lib; | ||
4 | |||
5 | let | ||
6 | inherit (customUtils) mapFilterAttrs; | ||
7 | |||
8 | tsigSecretName = domain: "${domain}_tsig-secret"; | ||
9 | |||
10 | cfg = config.security.acme; | ||
11 | knotCfg = config.services.knot; | ||
12 | |||
13 | knotDNSCredentials = domain: let | ||
14 | zone = if cfg.domains.${domain}.zone == null then domain else cfg.domains.${domain}.zone; | ||
15 | in pkgs.writeText "lego-credentials" '' | ||
16 | EXEC_PATH=${knotDNSExec zone}/bin/update-dns.sh | ||
17 | EXEC_PROPAGATION_TIMEOUT=300 | ||
18 | EXEC_POLLING_INTERVAL=5 | ||
19 | ''; | ||
20 | knotDNSExec = zone: pkgs.writeScriptBin "update-dns.sh" '' | ||
21 | #!${pkgs.zsh}/bin/zsh -xe | ||
22 | |||
23 | mode=$1 | ||
24 | fqdn=$2 | ||
25 | challenge=$3 | ||
26 | |||
27 | owner=''${fqdn%".${zone}."} | ||
28 | |||
29 | commited= | ||
30 | function abort() { | ||
31 | [[ -n "''${commited}" ]] || ${knotCfg.cliWrappers}/bin/knotc zone-abort "${zone}" | ||
32 | } | ||
33 | |||
34 | ${knotCfg.cliWrappers}/bin/knotc zone-begin "${zone}" | ||
35 | trap abort EXIT | ||
36 | |||
37 | case "''${mode}" in | ||
38 | present) | ||
39 | if ${knotCfg.cliWrappers}/bin/knotc zone-get ${zone} "''${owner}" TXT; then | ||
40 | ${knotCfg.cliWrappers}/bin/knotc zone-unset ${zone} "''${owner}" TXT '""' | ||
41 | fi | ||
42 | ${knotCfg.cliWrappers}/bin/knotc zone-set ${zone} "''${owner}" 30 TXT "''${challenge}" | ||
43 | ;; | ||
44 | cleanup) | ||
45 | ${knotCfg.cliWrappers}/bin/knotc zone-unset ${zone} "''${owner}" TXT "''${challenge}" | ||
46 | ${knotCfg.cliWrappers}/bin/knotc zone-set ${zone} "''${owner}" 30 TXT '""' | ||
47 | ;; | ||
48 | *) | ||
49 | exit 2 | ||
50 | ;; | ||
51 | esac | ||
52 | |||
53 | ${knotCfg.cliWrappers}/bin/knotc zone-commit "${zone}" | ||
54 | commited=yes | ||
55 | ''; | ||
56 | |||
57 | domainOptions = { | ||
58 | options = { | ||
59 | wildcard = mkOption { | ||
60 | type = types.bool; | ||
61 | default = false; | ||
62 | }; | ||
63 | zone = mkOption { | ||
64 | type = types.nullOr types.str; | ||
65 | default = null; | ||
66 | }; | ||
67 | certCfg = mkOption { | ||
68 | type = types.attrs; | ||
69 | default = {}; | ||
70 | }; | ||
71 | }; | ||
72 | }; | ||
73 | in { | ||
74 | options = { | ||
75 | security.acme = { | ||
76 | domains = mkOption { | ||
77 | type = types.attrsOf (types.submodule domainOptions); | ||
78 | default = {}; | ||
79 | }; | ||
80 | }; | ||
81 | }; | ||
82 | |||
83 | config = { | ||
84 | security.acme.domains = genAttrs ["dirty-haskell.org" "141.li" "xmpp.li" "yggdrasil.li" "praseodym.org" "rheperire.org" "kleen.li" "nights.email"] (domain: { wildcard = true; }); | ||
85 | |||
86 | fileSystems."/var/lib/acme" = | ||
87 | { device = "surtr/safe/var-lib-acme"; | ||
88 | fsType = "zfs"; | ||
89 | }; | ||
90 | |||
91 | security.acme = { | ||
92 | acceptTerms = true; | ||
93 | preliminarySelfsigned = true; # DNS challenge is slow | ||
94 | defaults = { | ||
95 | email = "phikeebaogobaegh@141.li"; | ||
96 | keyType = "rsa4096"; # we don't like NIST curves | ||
97 | # extraLegoFlags = ["--preferred-chain" "ISRG Root X1"]; | ||
98 | }; | ||
99 | certs = | ||
100 | let | ||
101 | domainAttrset = domain: let | ||
102 | tsigPath = ./tsig_keys + "/${domain}"; | ||
103 | tsigSecret = config.sops.secrets.${tsigSecretName domain}; | ||
104 | isTsig = pathExists tsigPath; | ||
105 | shared = { | ||
106 | inherit domain; | ||
107 | extraDomainNames = optional cfg.domains.${domain}.wildcard "*.${domain}"; | ||
108 | dnsResolver = "127.0.0.1:5353"; | ||
109 | }; | ||
110 | mkKnotc = shared // { | ||
111 | dnsProvider = "exec"; | ||
112 | credentialsFile = knotDNSCredentials domain; | ||
113 | }; | ||
114 | mkRFC2136 = let | ||
115 | tsigInfo = readYaml tsigPath; | ||
116 | in shared // { | ||
117 | dnsProvider = "rfc2136"; | ||
118 | credentialsFile = pkgs.writeText "${domain}_credentials.env" '' | ||
119 | RFC2136_NAMESERVER=127.0.0.1:53 | ||
120 | RFC2136_TSIG_ALGORITHM=hmac-sha256. | ||
121 | RFC2136_TSIG_KEY=${domain}_acme | ||
122 | RFC2136_TSIG_SECRET_FILE=${tsigSecret.path} | ||
123 | RFC2136_PROPAGATION_TIMEOUT=300 | ||
124 | RFC2136_POLLING_INTERVAL=5 | ||
125 | RFC2136_TTL=300 | ||
126 | ''; | ||
127 | }; | ||
128 | in // cfg.domains.${domain}.certCfg; | ||
129 | in genAttrs (attrNames cfg.domains) domainAttrset; | ||
130 | }; | ||
131 | |||
132 | sops.secrets = let | ||
133 | toTSIGSecret = n: v: | ||
134 | if v == "regular" || v == "symlink" | ||
135 | then nameValuePair (tsigSecretName n) { | ||
136 | format = "binary"; | ||
137 | owner = if config.security.acme.useRoot then "root" else "acme"; | ||
138 | group = "acme"; | ||
139 | sopsFile = ./tsig_keys + "/${n}"; | ||
140 | } else null; | ||
141 | in mapFilterAttrs (_: v: v != null) toTSIGSecret (readDir ./tsig_keys); | ||
142 | |||
143 | systemd.services = | ||
144 | let | ||
145 | serviceAttrset = domain: { | ||
146 | after = [ "knot.service" ]; | ||
147 | bindsTo = [ "knot.service" ]; | ||
148 | serviceConfig = { | ||
149 | ReadWritePaths = ["/run/knot/knot.sock"]; | ||
150 | SupplementaryGroups = ["knot"]; | ||
151 | RestrictAddressFamilies = ["AF_UNIX"]; | ||
152 | }; | ||
153 | }; | ||
154 | in mapAttrs' (domain: nameValuePair "acme-${domain}") (genAttrs (attrNames config.security.acme.certs) serviceAttrset); | ||
155 | }; | ||
156 | } | ||