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