aboutsummaryrefslogtreecommitdiff
path: root/modules/private/certificates.nix
blob: c564d34f17832d784875ef60ea0950b9e23a4198 (plain) (blame)
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
{ lib, pkgs, config, name, ... }:
{
  options.myServices.certificates = {
    enable = lib.mkEnableOption "enable certificates";
    certConfig = lib.mkOption {
      default = {
        webroot = "/var/lib/acme/acme-challenges";
        email = "ismael@bouya.org";
        postRun = builtins.concatStringsSep "\n" [
          (lib.optionalString config.services.httpd.Prod.enable "systemctl reload httpdProd.service")
          (lib.optionalString config.services.httpd.Tools.enable "systemctl reload httpdTools.service")
          (lib.optionalString config.services.httpd.Inte.enable "systemctl reload httpdInte.service")
          (lib.optionalString config.services.nginx.enable "systemctl reload nginx.service")
        ];
      };
      description = "Default configuration for certificates";
    };
  };

  config = lib.mkIf config.myServices.certificates.enable {
    services.duplyBackup.profiles.system.excludeFile = ''
      + /var/lib/acme/acme-challenges
      '';
    services.nginx = {
      recommendedTlsSettings = true;
      virtualHosts = {
        "${config.hostEnv.fqdn}" = {
          acmeRoot = config.security.acme.certs."${name}".webroot;
          useACMEHost = name;
          forceSSL = true;
        };
      };
    };
    services.websites.certs = config.myServices.certificates.certConfig;
    myServices.databasesCerts = config.myServices.certificates.certConfig;
    myServices.ircCerts = config.myServices.certificates.certConfig;

    security.acme.acceptTerms = true;
    security.acme.preliminarySelfsigned = true;

    security.acme.certs = {
      "${name}" = config.myServices.certificates.certConfig // {
        domain = config.hostEnv.fqdn;
      };
    };

    systemd.services = lib.attrsets.mapAttrs' (k: v:
      lib.attrsets.nameValuePair "acme-selfsigned-${k}" {
          wantedBy = [ "acme-selfsigned-certificates.target" ];
          script = lib.mkAfter ''
          cp $workdir/server.crt ${config.security.acme.certs."${k}".directory}/cert.pem
          chown '${v.user}:${v.group}' ${config.security.acme.certs."${k}".directory}/cert.pem
          chmod ${if v.allowKeysForGroup then "750" else "700"} ${config.security.acme.certs."${k}".directory}/cert.pem

          cp $workdir/ca.crt ${config.security.acme.certs."${k}".directory}/chain.pem
          chown '${v.user}:${v.group}' ${config.security.acme.certs."${k}".directory}/chain.pem
          chmod ${if v.allowKeysForGroup then "750" else "700"} ${config.security.acme.certs."${k}".directory}/chain.pem
          '';
        }
      ) config.security.acme.certs //
    lib.attrsets.mapAttrs' (k: data:
      lib.attrsets.nameValuePair "acme-${k}" {
        after = lib.mkAfter [ "bind.service" ];
        serviceConfig.ExecStartPre =
          let
            script = pkgs.writeScript "acme-pre-start" ''
              #!${pkgs.runtimeShell} -e
              mkdir -p '${data.webroot}/.well-known/acme-challenge'
              chmod a+w '${data.webroot}/.well-known/acme-challenge'
              #doesn't work for multiple concurrent runs
              #chown -R '${data.user}:${data.group}' '${data.webroot}/.well-known/acme-challenge'
            '';
          in
          "+${script}";
        # This is a workaround to
        # https://github.com/NixOS/nixpkgs/issues/84409
        # https://github.com/NixOS/nixpkgs/issues/84633
        serviceConfig.RemainAfterExit = lib.mkForce false;
        serviceConfig.WorkingDirectory = lib.mkForce "/var/lib/acme/${k}/.lego";
        serviceConfig.StateDirectory = lib.mkForce "acme/${k}/.lego acme/${k}";
        serviceConfig.ExecStartPost =
          let
            keyName = builtins.replaceStrings ["*"] ["_"] data.domain;
            fileMode = if data.allowKeysForGroup then "640" else "600";
            spath = "/var/lib/acme/${k}/.lego";
            script = pkgs.writeScript "acme-post-start" ''
              #!${pkgs.runtimeShell} -e
              cd /var/lib/acme/${k}

              # Test that existing cert is older than new cert
              KEY=${spath}/certificates/${keyName}.key
              if [ -e $KEY -a $KEY -nt key.pem ]; then
                cp -p ${spath}/certificates/${keyName}.key key.pem
                cp -p ${spath}/certificates/${keyName}.crt fullchain.pem
                cp -p ${spath}/certificates/${keyName}.issuer.crt chain.pem
                ln -sf fullchain.pem cert.pem
                cat key.pem fullchain.pem > full.pem

                ${data.postRun}
              fi

              chmod ${fileMode} *.pem
              chown '${data.user}:${data.group}' *.pem
            '';
          in
            lib.mkForce "+${script}";

      }
    ) config.security.acme.certs //
    {
      httpdProd = lib.mkIf config.services.httpd.Prod.enable
        { after = [ "acme-selfsigned-certificates.target" ]; wants = [ "acme-selfsigned-certificates.target" ]; };
      httpdTools = lib.mkIf config.services.httpd.Tools.enable
        { after = [ "acme-selfsigned-certificates.target" ]; wants = [ "acme-selfsigned-certificates.target" ]; };
      httpdInte = lib.mkIf config.services.httpd.Inte.enable
        { after = [ "acme-selfsigned-certificates.target" ]; wants = [ "acme-selfsigned-certificates.target" ]; };
    };
  };
}