195 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			195 lines
		
	
	
		
			5.6 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)}
 | 
						|
    ${pkgs.certmgr}/bin/certmgr -f ${certmgrYaml} check
 | 
						|
  '';
 | 
						|
in
 | 
						|
{
 | 
						|
  options.services.certmgr = {
 | 
						|
    enable = mkEnableOption "certmgr";
 | 
						|
 | 
						|
    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 = literalExample ''
 | 
						|
      {
 | 
						|
        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 (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.";
 | 
						|
          };
 | 
						|
        };
 | 
						|
    }) path);
 | 
						|
      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 = "${pkgs.certmgr}/bin/certmgr -f ${certmgrYaml}";
 | 
						|
      };
 | 
						|
    };
 | 
						|
  };
 | 
						|
}
 |