nixos-config/lib/fudo/acme-certs.nix
2021-11-05 07:06:08 -07:00

163 lines
5.2 KiB
Nix

{ config, lib, pkgs, ... } @ toplevel:
with lib;
let
domainOpts = { name, ... }: let
domain = name;
in {
options = with types; {
email = mkOption {
type = str;
description = "Domain administrator email.";
default = "admin@${domain}";
};
extra-domains = mkOption {
type = listOf str;
description = "List of domains to add to this certificate.";
default = [];
};
local-copies = let
localCopyOpts = { name, ... }: let
copy = name;
in {
options = with types; let
target-path = "/var/run/${domain}/${copy}";
in {
user = mkOption {
type = str;
description = "User to which this copy belongs.";
};
group = mkOption {
type = nullOr str;
description = "Group to which this copy belongs.";
default = null;
};
service = mkOption {
type = str;
description = "systemd job to copy certs.";
default = "fudo-${domain}-${copy}-certs.service";
};
certificate = mkOption {
type = str;
description = "Full path to the local copy certificate.";
default = "${target-path}/cert.pem";
};
full-certificate = mkOption {
type = str;
description = "Full path to the local copy certificate.";
default = "${target-path}/fullchain.pem";
};
chain = mkOption {
type = str;
description = "Full path to the local copy certificate.";
default = "${target-path}/chain.pem";
};
private-key = mkOption {
type = str;
description = "Full path to the local copy certificate.";
default = "${target-path}/key.pem";
};
};
};
in mkOption {
type = attrsOf (submodule localCopyOpts);
description = "Map of copies to make for use by services.";
default = {};
};
};
};
head-or-null = lst: if (lst == []) then null else head lst;
rm-service-ext = filename:
head-or-null (builtins.match "^(.+)\.service$" filename);
concatMapAttrs = f: attrs:
foldr (a: b: a // b) {} (mapAttrsToList f attrs);
hostname = config.instance.hostname;
cfg = config.fudo.acme;
localDomains = if (hasAttr hostname cfg.host-domains) then
cfg.host-domains.${hostname} else {};
optionalStringOr = str: default:
if (str != null) then str else default;
in {
options.fudo.acme = with types; {
host-domains = mkOption {
type = attrsOf (attrsOf (submodule domainOpts));
description = "Map of host to domains to domain options.";
default = { };
};
};
config = {
security.acme.certs = mapAttrs (domain: domainOpts: {
email = domainOpts.email;
extraDomainNames = domainOpts.extra-domains;
}) localDomains;
systemd = {
tmpfiles.rules = let
copies = concatMapAttrs (domain: domainOpts:
domainOpts.local-copies) localDomains;
perms = copyOpts: if (copyOpts.group != null) then "0550" else "0500";
copy-paths = mapAttrsToList (copy: copyOpts:
let
dir-entry = copyOpts: file: "D \"${dirOf file}\" ${perms copyOpts} ${copyOpts.user} ${optionalStringOr copyOpts.group "-"} - -";
in map (dir-entry copyOpts) [
copyOpts.certificate
copyOpts.full-certificate
copyOpts.chain
copyOpts.private-key
]) copies;
in unique (concatMap (i: unique i) copy-paths);
services = concatMapAttrs (domain: domainOpts:
mapAttrs' (copy: copyOpts: let
key-perms = copyOpts: if (copyOpts.group != null) then "0440" else "0400";
source = config.security.acme.certs.${domain}.directory;
target = copyOpts.path;
install-certs = pkgs.writeShellScript "fudo-install-${domain}-${copy}-certs.sh" ''
cp cert.pem ${copyOpts.certificate}
chmod 0444 ${copyOpts.certificate}
cp full.pem ${copyOpts.full-certificate}
chmod 0444 ${copyOpts.full-certificate}
cp chain.pem ${copyOpts.chain}
chmod 0444 ${copyOpts.chain}
cp key.pem ${copyOpts.private-key}
chmod ${key-perms copyOpts} ${copyOpts.private-key}
'';
remove-certs = pkgs.writeShellScript "fudo-remove-${domain}-${copy}-certs.sh" ''
rm -f ${copyOpts.private-key}
rm -f ${copyOpts.chain}
rm -f ${copyOpts.full-certificate}
rm -f ${copyOpts.certificate}
'';
in nameValuePair
(rm-service-ext copyOpts.service) {
description = "Copy ${domain} ACME certs for ${copy}.";
after = [ "acme-${domain}.service" ];
serviceConfig = {
Type = "oneshot";
ExecStart = install-certs;
ExecStop = remove-certs;
RemainAfterExit = true;
StandardOutput = "journal";
};
}) domainOpts.local-copies) localDomains;
};
};
}