# A Let's Encrypt certificate for the web UI, proven with a DNS record (DNS-01): # no port needs to be open to the internet, and wildcards work. NixOS's # security.acme (lego) orders and renews it; the web UI picks up the files. { config, lib, ... }: let cfg = config.router; acme = cfg.acme; # Shared with the web UI: the providers lego knows, and those with a single # token (the variable names the file holding it). The others take an # environment file with whatever variables they need. providers = builtins.fromJSON (builtins.readFile ../../pkgs/router-webui/providers.json); tokenVariables = providers.acmeToken; prov = cfg.dnsProviders.${if acme.provider == null then "" else acme.provider} or { type = "none"; secretFile = null; environmentFile = null; }; token = tokenVariables ? ${prov.type}; secret = if token then prov.secretFile else prov.environmentFile; has = acme.provider != null && cfg.dnsProviders ? ${acme.provider}; name = if acme.domains == [ ] then "unset" else lib.head acme.domains; in { config = lib.mkIf (cfg.enable && acme.enable) { assertions = [ { assertion = acme.domains != [ ]; message = "router.acme: no domains for the certificate."; } { assertion = has; message = "router.acme.provider must name a connection in router.dnsProviders."; } { assertion = !has || lib.elem prov.type providers.acme; message = "router.acme: lego has no DNS provider ${prov.type}."; } { assertion = !has || !lib.elem prov.type providers.acme || (secret != null && !lib.hasPrefix "/nix/store" secret); message = "router.dnsProviders.${toString acme.provider} needs ${if token then "secretFile" else "environmentFile"} for certificates, a file outside the Nix store (the secret must not be copied into it)."; } { assertion = acme.email != null; message = "router.acme.email: Let's Encrypt needs a contact address."; } { assertion = acme.acceptTerms; message = "router.acme.acceptTerms: the Let's Encrypt subscriber agreement must be accepted."; } ]; security.acme = { # Ours is the assertion above. acceptTerms = true; defaults.email = acme.email; certs.${name} = { extraDomainNames = lib.tail acme.domains; dnsProvider = prov.type; inherit (acme) dnsResolver; credentialFiles = lib.optionalAttrs token { ${tokenVariables.${prov.type}} = prov.secretFile; }; environmentFile = lib.mkIf (!token) prov.environmentFile; server = lib.mkIf acme.staging "https://acme-staging-v02.api.letsencrypt.org/directory"; }; }; }; }