depot/ops/nixos/lib/secretsmgr-acme.nix

95 lines
3.4 KiB
Nix

# SPDX-FileCopyrightText: 2022 Luke Granger-Brown <depot@lukegb.com>
#
# 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.";
};
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: let
fullchain = fullchainPath certData;
chain = chainPath certData;
key = keyPath certData;
in map (hostName: lib.nameValuePair hostName {
sslCertificate = mkDefault (fullchainPath certData);
sslCertificateKey = mkDefault (keyPath certData);
sslTrustedCertificate = mkDefault (chainPath certData);
}) certData.nginxVirtualHosts) 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);
};
}