at 23.11-pre 6.0 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.services.certmgr; 7 8 specs = mapAttrsToList (n: v: rec { 9 name = n + ".json"; 10 path = if isAttrs v then pkgs.writeText name (builtins.toJSON v) else v; 11 }) cfg.specs; 12 13 allSpecs = pkgs.linkFarm "certmgr.d" specs; 14 15 certmgrYaml = pkgs.writeText "certmgr.yaml" (builtins.toJSON { 16 dir = allSpecs; 17 default_remote = cfg.defaultRemote; 18 svcmgr = cfg.svcManager; 19 before = cfg.validMin; 20 interval = cfg.renewInterval; 21 inherit (cfg) metricsPort metricsAddress; 22 }); 23 24 specPaths = map dirOf (concatMap (spec: 25 if isAttrs spec then 26 collect isString (filterAttrsRecursive (n: v: isAttrs v || n == "path") spec) 27 else 28 [ spec ] 29 ) (attrValues cfg.specs)); 30 31 preStart = '' 32 ${concatStringsSep " \\\n" (["mkdir -p"] ++ map escapeShellArg specPaths)} 33 ${cfg.package}/bin/certmgr -f ${certmgrYaml} check 34 ''; 35in 36{ 37 options.services.certmgr = { 38 enable = mkEnableOption (lib.mdDoc "certmgr"); 39 40 package = mkOption { 41 type = types.package; 42 default = pkgs.certmgr; 43 defaultText = literalExpression "pkgs.certmgr"; 44 description = lib.mdDoc "Which certmgr package to use in the service."; 45 }; 46 47 defaultRemote = mkOption { 48 type = types.str; 49 default = "127.0.0.1:8888"; 50 description = lib.mdDoc "The default CA host:port to use."; 51 }; 52 53 validMin = mkOption { 54 default = "72h"; 55 type = types.str; 56 description = lib.mdDoc "The interval before a certificate expires to start attempting to renew it."; 57 }; 58 59 renewInterval = mkOption { 60 default = "30m"; 61 type = types.str; 62 description = lib.mdDoc "How often to check certificate expirations and how often to update the cert_next_expires metric."; 63 }; 64 65 metricsAddress = mkOption { 66 default = "127.0.0.1"; 67 type = types.str; 68 description = lib.mdDoc "The address for the Prometheus HTTP endpoint."; 69 }; 70 71 metricsPort = mkOption { 72 default = 9488; 73 type = types.ints.u16; 74 description = lib.mdDoc "The port for the Prometheus HTTP endpoint."; 75 }; 76 77 specs = mkOption { 78 default = {}; 79 example = literalExpression '' 80 { 81 exampleCert = 82 let 83 domain = "example.com"; 84 secret = name: "/var/lib/secrets/''${name}.pem"; 85 in { 86 service = "nginx"; 87 action = "reload"; 88 authority = { 89 file.path = secret "ca"; 90 }; 91 certificate = { 92 path = secret domain; 93 }; 94 private_key = { 95 owner = "root"; 96 group = "root"; 97 mode = "0600"; 98 path = secret "''${domain}-key"; 99 }; 100 request = { 101 CN = domain; 102 hosts = [ "mail.''${domain}" "www.''${domain}" ]; 103 key = { 104 algo = "rsa"; 105 size = 2048; 106 }; 107 names = { 108 O = "Example Organization"; 109 C = "USA"; 110 }; 111 }; 112 }; 113 otherCert = "/var/certmgr/specs/other-cert.json"; 114 } 115 ''; 116 type = with types; attrsOf (either path (submodule { 117 options = { 118 service = mkOption { 119 type = nullOr str; 120 default = null; 121 description = lib.mdDoc "The service on which to perform \<action\> after fetching."; 122 }; 123 124 action = mkOption { 125 type = addCheck str (x: cfg.svcManager == "command" || elem x ["restart" "reload" "nop"]); 126 default = "nop"; 127 description = lib.mdDoc "The action to take after fetching."; 128 }; 129 130 # These ought all to be specified according to certmgr spec def. 131 authority = mkOption { 132 type = attrs; 133 description = lib.mdDoc "certmgr spec authority object."; 134 }; 135 136 certificate = mkOption { 137 type = nullOr attrs; 138 description = lib.mdDoc "certmgr spec certificate object."; 139 }; 140 141 private_key = mkOption { 142 type = nullOr attrs; 143 description = lib.mdDoc "certmgr spec private_key object."; 144 }; 145 146 request = mkOption { 147 type = nullOr attrs; 148 description = lib.mdDoc "certmgr spec request object."; 149 }; 150 }; 151 })); 152 description = lib.mdDoc '' 153 Certificate specs as described by: 154 <https://github.com/cloudflare/certmgr#certificate-specs> 155 These will be added to the Nix store, so they will be world readable. 156 ''; 157 }; 158 159 svcManager = mkOption { 160 default = "systemd"; 161 type = types.enum [ "circus" "command" "dummy" "openrc" "systemd" "sysv" ]; 162 description = lib.mdDoc '' 163 This specifies the service manager to use for restarting or reloading services. 164 See: <https://github.com/cloudflare/certmgr#certmgryaml>. 165 For how to use the "command" service manager in particular, 166 see: <https://github.com/cloudflare/certmgr#command-svcmgr-and-how-to-use-it>. 167 ''; 168 }; 169 170 }; 171 172 config = mkIf cfg.enable { 173 assertions = [ 174 { 175 assertion = cfg.specs != {}; 176 message = "Certmgr specs cannot be empty."; 177 } 178 { 179 assertion = !any (hasAttrByPath [ "authority" "auth_key" ]) (attrValues cfg.specs); 180 message = '' 181 Inline services.certmgr.specs are added to the Nix store rendering them world readable. 182 Specify paths as specs, if you want to use include auth_key - or use the auth_key_file option." 183 ''; 184 } 185 ]; 186 187 systemd.services.certmgr = { 188 description = "certmgr"; 189 path = mkIf (cfg.svcManager == "command") [ pkgs.bash ]; 190 after = [ "network-online.target" ]; 191 wantedBy = [ "multi-user.target" ]; 192 inherit preStart; 193 194 serviceConfig = { 195 Restart = "always"; 196 RestartSec = "10s"; 197 ExecStart = "${cfg.package}/bin/certmgr -f ${certmgrYaml}"; 198 }; 199 }; 200 }; 201}