02cf88bb76
GitOrigin-RevId: c4a0efdd5a728e20791b8d8d2f26f90ac228ee8d
201 lines
5.8 KiB
Nix
201 lines
5.8 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
|
|
with lib;
|
|
|
|
let
|
|
cfg = config.services.certmgr;
|
|
|
|
specs = mapAttrsToList (n: v: rec {
|
|
name = n + ".json";
|
|
path = if isAttrs v then pkgs.writeText name (builtins.toJSON v) else v;
|
|
}) cfg.specs;
|
|
|
|
allSpecs = pkgs.linkFarm "certmgr.d" specs;
|
|
|
|
certmgrYaml = pkgs.writeText "certmgr.yaml" (builtins.toJSON {
|
|
dir = allSpecs;
|
|
default_remote = cfg.defaultRemote;
|
|
svcmgr = cfg.svcManager;
|
|
before = cfg.validMin;
|
|
interval = cfg.renewInterval;
|
|
inherit (cfg) metricsPort metricsAddress;
|
|
});
|
|
|
|
specPaths = map dirOf (concatMap (spec:
|
|
if isAttrs spec then
|
|
collect isString (filterAttrsRecursive (n: v: isAttrs v || n == "path") spec)
|
|
else
|
|
[ spec ]
|
|
) (attrValues cfg.specs));
|
|
|
|
preStart = ''
|
|
${concatStringsSep " \\\n" (["mkdir -p"] ++ map escapeShellArg specPaths)}
|
|
${cfg.package}/bin/certmgr -f ${certmgrYaml} check
|
|
'';
|
|
in
|
|
{
|
|
options.services.certmgr = {
|
|
enable = mkEnableOption "certmgr";
|
|
|
|
package = mkOption {
|
|
type = types.package;
|
|
default = pkgs.certmgr;
|
|
defaultText = literalExpression "pkgs.certmgr";
|
|
description = lib.mdDoc "Which certmgr package to use in the service.";
|
|
};
|
|
|
|
defaultRemote = mkOption {
|
|
type = types.str;
|
|
default = "127.0.0.1:8888";
|
|
description = lib.mdDoc "The default CA host:port to use.";
|
|
};
|
|
|
|
validMin = mkOption {
|
|
default = "72h";
|
|
type = types.str;
|
|
description = lib.mdDoc "The interval before a certificate expires to start attempting to renew it.";
|
|
};
|
|
|
|
renewInterval = mkOption {
|
|
default = "30m";
|
|
type = types.str;
|
|
description = lib.mdDoc "How often to check certificate expirations and how often to update the cert_next_expires metric.";
|
|
};
|
|
|
|
metricsAddress = mkOption {
|
|
default = "127.0.0.1";
|
|
type = types.str;
|
|
description = lib.mdDoc "The address for the Prometheus HTTP endpoint.";
|
|
};
|
|
|
|
metricsPort = mkOption {
|
|
default = 9488;
|
|
type = types.ints.u16;
|
|
description = lib.mdDoc "The port for the Prometheus HTTP endpoint.";
|
|
};
|
|
|
|
specs = mkOption {
|
|
default = {};
|
|
example = literalExpression ''
|
|
{
|
|
exampleCert =
|
|
let
|
|
domain = "example.com";
|
|
secret = name: "/var/lib/secrets/''${name}.pem";
|
|
in {
|
|
service = "nginx";
|
|
action = "reload";
|
|
authority = {
|
|
file.path = secret "ca";
|
|
};
|
|
certificate = {
|
|
path = secret domain;
|
|
};
|
|
private_key = {
|
|
owner = "root";
|
|
group = "root";
|
|
mode = "0600";
|
|
path = secret "''${domain}-key";
|
|
};
|
|
request = {
|
|
CN = domain;
|
|
hosts = [ "mail.''${domain}" "www.''${domain}" ];
|
|
key = {
|
|
algo = "rsa";
|
|
size = 2048;
|
|
};
|
|
names = {
|
|
O = "Example Organization";
|
|
C = "USA";
|
|
};
|
|
};
|
|
};
|
|
otherCert = "/var/certmgr/specs/other-cert.json";
|
|
}
|
|
'';
|
|
type = with types; attrsOf (either path (submodule {
|
|
options = {
|
|
service = mkOption {
|
|
type = nullOr str;
|
|
default = null;
|
|
description = "The service on which to perform <action> after fetching.";
|
|
};
|
|
|
|
action = mkOption {
|
|
type = addCheck str (x: cfg.svcManager == "command" || elem x ["restart" "reload" "nop"]);
|
|
default = "nop";
|
|
description = "The action to take after fetching.";
|
|
};
|
|
|
|
# These ought all to be specified according to certmgr spec def.
|
|
authority = mkOption {
|
|
type = attrs;
|
|
description = "certmgr spec authority object.";
|
|
};
|
|
|
|
certificate = mkOption {
|
|
type = nullOr attrs;
|
|
description = "certmgr spec certificate object.";
|
|
};
|
|
|
|
private_key = mkOption {
|
|
type = nullOr attrs;
|
|
description = "certmgr spec private_key object.";
|
|
};
|
|
|
|
request = mkOption {
|
|
type = nullOr attrs;
|
|
description = "certmgr spec request object.";
|
|
};
|
|
};
|
|
}));
|
|
description = lib.mdDoc ''
|
|
Certificate specs as described by:
|
|
<https://github.com/cloudflare/certmgr#certificate-specs>
|
|
These will be added to the Nix store, so they will be world readable.
|
|
'';
|
|
};
|
|
|
|
svcManager = mkOption {
|
|
default = "systemd";
|
|
type = types.enum [ "circus" "command" "dummy" "openrc" "systemd" "sysv" ];
|
|
description = lib.mdDoc ''
|
|
This specifies the service manager to use for restarting or reloading services.
|
|
See: <https://github.com/cloudflare/certmgr#certmgryaml>.
|
|
For how to use the "command" service manager in particular,
|
|
see: <https://github.com/cloudflare/certmgr#command-svcmgr-and-how-to-use-it>.
|
|
'';
|
|
};
|
|
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
assertions = [
|
|
{
|
|
assertion = cfg.specs != {};
|
|
message = "Certmgr specs cannot be empty.";
|
|
}
|
|
{
|
|
assertion = !any (hasAttrByPath [ "authority" "auth_key" ]) (attrValues cfg.specs);
|
|
message = ''
|
|
Inline services.certmgr.specs are added to the Nix store rendering them world readable.
|
|
Specify paths as specs, if you want to use include auth_key - or use the auth_key_file option."
|
|
'';
|
|
}
|
|
];
|
|
|
|
systemd.services.certmgr = {
|
|
description = "certmgr";
|
|
path = mkIf (cfg.svcManager == "command") [ pkgs.bash ];
|
|
after = [ "network-online.target" ];
|
|
wantedBy = [ "multi-user.target" ];
|
|
inherit preStart;
|
|
|
|
serviceConfig = {
|
|
Restart = "always";
|
|
RestartSec = "10s";
|
|
ExecStart = "${cfg.package}/bin/certmgr -f ${certmgrYaml}";
|
|
};
|
|
};
|
|
};
|
|
}
|