summaryrefslogtreecommitdiff
path: root/hosts/surtr/tls/default.nix
blob: b1c05888cf999ff172a1e1c9f0b6c603f5781248 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
{ config, lib, customUtils, pkgs, ... }:

with lib;

let
  tsigSecretName = domain: "${domain}_tsig-secret";
  tsigKey = domain:
    let
      tsigKeyPath = ./tsig_keys + "/${domain}";
    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;

  cfg = config.security.acme;
in {
  options = {
    security.acme = {
      # This file introduces an additional nixos module option
      # `security.acme.rfc2136Domains`.
      # The new option is an attrset of domain names mapping to
      # additional settings.
      rfc2136Domains = mkOption {
        type = types.attrsOf (types.submodule {
          options = {
            wildcard = mkOption {
              type = types.bool;
              default = false;
            };
            restartUnits = mkOption {
              type = types.listOf types.str;
              default = [];
            };
          };
        });
        default = {};
      };
    };
  };

  config = {
    security.acme = {
      # Some default/global ACME settings

      acceptTerms = true;
      # DNS challenge is slow
      preliminarySelfsigned = true;
      defaults = {
        email = "phikeebaogobaegh@141.li";
        # We don't like NIST curves and Let's Encrypt doesn't support
        # anything better
        keyType = "rsa4096";
      };

      # For each domain specified in
      # `config.security.acme.rfc2136Domains`, configure an additional
      # entry in `config.security.acme.certs` containing appropriate
      # settings to provision the certificate via DNS-01
      certs = mapAttrs (domain: domainCfg: {
        inherit domain;
        extraDomainNames = optional domainCfg.wildcard "*.${domain}";
        dnsResolver = "127.0.0.1:53";
        dnsProvider = "rfc2136";
        credentialsFile = pkgs.writeText "${domain}_credentials.env" ''
          RFC2136_NAMESERVER=127.0.0.1:53
          RFC2136_TSIG_ALGORITHM=hmac-sha256.
          RFC2136_TSIG_KEY=${domain}_acme_key
          RFC2136_TSIG_SECRET_FILE=/run/credentials/acme-${domain}.service/${tsigSecretName domain}
          RFC2136_TTL=0
          RFC2136_PROPAGATION_TIMEOUT=60
          RFC2136_POLLING_INTERVAL=2
          RFC2136_SEQUENCE_INTERVAL=1
        '';
        dnsPropagationCheck = false;
        postRun = mkIf (domainCfg.restartUnits != []) ''
          systemctl --no-block try-restart ${escapeShellArgs domainCfg.restartUnits}
        '';
      }) cfg.rfc2136Domains;
    };

    # Decrypt all `tsig_keys/*` at runtime
    sops.secrets = mapAttrs' (domain: domainCfg: nameValuePair (tsigSecretName domain) {
      format = "binary";
      sopsFile = tsigKey domain;
      restartUnits = [ "acme-${domain}.service" ];
    }) cfg.rfc2136Domains;

    # Provide appropriate `tsig_key/*` to systemd service performing
    # certificate provisioning
    systemd.services = mapAttrs' (domain: domainCfg: nameValuePair "acme-${domain}" {
      after = [ "knot.service" ];
      bindsTo = [ "knot.service" ];
      serviceConfig = {
        LoadCredential = [ "${tsigSecretName domain}:${config.sops.secrets.${tsigSecretName domain}.path}" ];
        SystemCallFilter = mkForce [ "@system-service" "~@privileged" "@chown" ];
      };
    }) cfg.rfc2136Domains;
  };
}