# SPDX-FileCopyrightText: 2022 Luke Granger-Brown # # SPDX-License-Identifier: Apache-2.0 { pkgs, config, depot, lib, ... }: let inherit (lib) mkOption types mkBefore optionalAttrs mkDefault mkIf mapAttrsToList mkAfter; acmeCertificates = builtins.attrValues config.my.vault.acmeCertificates; # Work out where we're being asked to write things, and which groups, so we can correctly get permissions. fullchainPath = c: pathFor c "fullchain.pem"; chainPath = c: pathFor c "chain.pem"; keyPath = c: pathFor c "privkey.pem"; pathFor = c: suffix: "/var/lib/acme/${c.name}/${suffix}"; isNginx = c: builtins.length c.nginxVirtualHosts > 0; defaultGroup = c: if isNull c.group then if isNginx c then "nginx" else "acme" else c.group; groupOrDefault = p: c: if isNull p then defaultGroup c else p; allRestartableUnits = builtins.concatMap (c: c.reloadOrRestartUnits) acmeCertificates; allGroups = lib.unique (map (c: c.group) acmeCertificates); in { imports = [ ./secretsmgr.nix ]; options.my.vault.acmeCertificates = mkOption { default = {}; type = with types; attrsOf (submodule ({ name, config, ... }: { options = let isNginx = builtins.length config.nginxVirtualHosts > 0; in { name = mkOption { type = str; default = name; description = "Path to put the certificate."; }; nginxVirtualHosts = mkOption { type = listOf str; default = []; description = "List of nginx virtual hosts to apply SSL to."; }; caddyVirtualHosts = mkOption { type = listOf str; default = []; description = "List of caddy virtual hosts to apply SSL to."; }; group = mkOption { type = str; default = if isNginx then "nginx" else "acme"; description = "Owner group to set for the ${what}. If null, taken from parent."; }; role = mkOption { type = str; default = "google-cloudflare"; description = "Which role to use for certificate issuance."; }; hostnames = mkOption { type = listOf str; description = "List of hostnames to issue certificate for."; }; reloadOrRestartUnits = mkOption { type = listOf str; default = lib.optional isNginx "nginx.service"; description = "List of systemd units to reload/restart after obtaining a new certificate."; }; }; })); }; config = mkIf config.my.vault.secretsmgr.acmeCertificates.enable { services.nginx = optionalAttrs config.my.vault.enable { virtualHosts = builtins.listToAttrs (builtins.concatMap (certData: map (hostName: lib.nameValuePair hostName { sslCertificate = mkDefault (fullchainPath certData); sslCertificateKey = mkDefault (keyPath certData); sslTrustedCertificate = mkDefault (chainPath certData); }) certData.nginxVirtualHosts) acmeCertificates); }; services.caddy = optionalAttrs config.my.vault.enable { virtualHosts = builtins.listToAttrs (builtins.concatMap (certData: map (hostName: lib.nameValuePair hostName { extraConfig = lib.mkBefore '' tls ${fullchainPath certData} ${keyPath certData} ''; }) certData.caddyVirtualHosts) acmeCertificates); }; my.vault.secretsmgr.groups = mkAfter allGroups; my.vault.secretsmgr.restartableUnits = mkAfter allRestartableUnits; my.vault.secretsmgr.acmeCertificates.config = mapAttrsToList (_: c: { inherit (c) group hostnames role; output_name = c.name; units_to_reload_or_restart = c.reloadOrRestartUnits; }) config.my.vault.acmeCertificates; users.groups = lib.listToAttrs (lib.map (groupName: lib.nameValuePair groupName {}) allGroups); }; }