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