summary refs log tree commit diff
path: root/nixos/modules/services/security/certmgr.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/services/security/certmgr.nix')
-rw-r--r--nixos/modules/services/security/certmgr.nix201
1 files changed, 201 insertions, 0 deletions
diff --git a/nixos/modules/services/security/certmgr.nix b/nixos/modules/services/security/certmgr.nix
new file mode 100644
index 00000000000..d302a4e0002
--- /dev/null
+++ b/nixos/modules/services/security/certmgr.nix
@@ -0,0 +1,201 @@
+{ 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 = "Which certmgr package to use in the service.";
+    };
+
+    defaultRemote = mkOption {
+      type = types.str;
+      default = "127.0.0.1:8888";
+      description = "The default CA host:port to use.";
+    };
+
+    validMin = mkOption {
+      default = "72h";
+      type = types.str;
+      description = "The interval before a certificate expires to start attempting to renew it.";
+    };
+
+    renewInterval = mkOption {
+      default = "30m";
+      type = types.str;
+      description = "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 = "The address for the Prometheus HTTP endpoint.";
+    };
+
+    metricsPort = mkOption {
+      default = 9488;
+      type = types.ints.u16;
+      description = "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 = ''
+        Certificate specs as described by:
+        <link xlink:href="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 = ''
+        This specifies the service manager to use for restarting or reloading services.
+        See: <link xlink:href="https://github.com/cloudflare/certmgr#certmgryaml" />.
+        For how to use the "command" service manager in particular,
+        see: <link xlink:href="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}";
+      };
+    };
+  };
+}