X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=modules%2Facme2.nix;fp=modules%2Facme2.nix;h=0000000000000000000000000000000000000000;hb=72300eb8116c960935a462564d96db6fac355bca;hp=b22e4ccc3e7687f51b103f82656cdb5ae743bfd7;hpb=22e09a87fb538a0eaf81837750b0948f26f79cf1;p=perso%2FImmae%2FConfig%2FNix%2FNUR.git diff --git a/modules/acme2.nix b/modules/acme2.nix deleted file mode 100644 index b22e4ccc..00000000 --- a/modules/acme2.nix +++ /dev/null @@ -1,353 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; - -let - - cfg = config.security.acme2; - - certOpts = { name, ... }: { - options = { - webroot = mkOption { - type = types.str; - example = "/var/lib/acme/acme-challenges"; - description = '' - Where the webroot of the HTTP vhost is located. - .well-known/acme-challenge/ directory - will be created below the webroot if it doesn't exist. - http://example.org/.well-known/acme-challenge/ must also - be available (notice unencrypted HTTP). - ''; - }; - - server = mkOption { - type = types.nullOr types.str; - default = null; - description = '' - ACME Directory Resource URI. Defaults to let's encrypt - production endpoint, - https://acme-v02.api.letsencrypt.org/directory, if unset. - ''; - }; - - domain = mkOption { - type = types.str; - default = name; - description = "Domain to fetch certificate for (defaults to the entry name)"; - }; - - email = mkOption { - type = types.nullOr types.str; - default = null; - description = "Contact email address for the CA to be able to reach you."; - }; - - user = mkOption { - type = types.str; - default = "root"; - description = "User running the ACME client."; - }; - - group = mkOption { - type = types.str; - default = "root"; - description = "Group running the ACME client."; - }; - - allowKeysForGroup = mkOption { - type = types.bool; - default = false; - description = '' - Give read permissions to the specified group - () to read SSL private certificates. - ''; - }; - - postRun = mkOption { - type = types.lines; - default = ""; - example = "systemctl reload nginx.service"; - description = '' - Commands to run after new certificates go live. Typically - the web server and other servers using certificates need to - be reloaded. - - Executed in the same directory with the new certificate. - ''; - }; - - plugins = mkOption { - type = types.listOf (types.enum [ - "cert.der" "cert.pem" "chain.pem" "external.sh" - "fullchain.pem" "full.pem" "key.der" "key.pem" "account_key.json" "account_reg.json" - ]); - default = [ "fullchain.pem" "full.pem" "key.pem" "account_key.json" "account_reg.json" ]; - description = '' - Plugins to enable. With default settings simp_le will - store public certificate bundle in fullchain.pem, - private key in key.pem and those two previous - files combined in full.pem in its state directory. - ''; - }; - - directory = mkOption { - type = types.str; - readOnly = true; - default = "/var/lib/acme/${name}"; - description = "Directory where certificate and other state is stored."; - }; - - extraDomains = mkOption { - type = types.attrsOf (types.nullOr types.str); - default = {}; - example = literalExample '' - { - "example.org" = "/srv/http/nginx"; - "mydomain.org" = null; - } - ''; - description = '' - A list of extra domain names, which are included in the one certificate to be issued, with their - own server roots if needed. - ''; - }; - }; - }; - -in - -{ - - ###### interface - imports = [ - (mkRemovedOptionModule [ "security" "acme2" "production" ] '' - Use security.acme2.server to define your staging ACME server URL instead. - - To use the let's encrypt staging server, use security.acme2.server = - "https://acme-staging-v02.api.letsencrypt.org/directory". - '' - ) - (mkRemovedOptionModule [ "security" "acme2" "directory"] "ACME Directory is now hardcoded to /var/lib/acme and its permisisons are managed by systemd. See https://github.com/NixOS/nixpkgs/issues/53852 for more info.") - (mkRemovedOptionModule [ "security" "acme" "preDelay"] "This option has been removed. If you want to make sure that something executes before certificates are provisioned, add a RequiredBy=acme-\${cert}.service to the service you want to execute before the cert renewal") - (mkRemovedOptionModule [ "security" "acme" "activationDelay"] "This option has been removed. If you want to make sure that something executes before certificates are provisioned, add a RequiredBy=acme-\${cert}.service to the service you want to execute before the cert renewal") - ]; - options = { - security.acme2 = { - - validMin = mkOption { - type = types.int; - default = 30 * 24 * 3600; - description = "Minimum remaining validity before renewal in seconds."; - }; - - renewInterval = mkOption { - type = types.str; - default = "weekly"; - description = '' - Systemd calendar expression when to check for renewal. See - systemd.time - 7. - ''; - }; - - server = mkOption { - type = types.nullOr types.str; - default = null; - description = '' - ACME Directory Resource URI. Defaults to let's encrypt - production endpoint, - https://acme-v02.api.letsencrypt.org/directory, if unset. - ''; - }; - - preliminarySelfsigned = mkOption { - type = types.bool; - default = true; - description = '' - Whether a preliminary self-signed certificate should be generated before - doing ACME requests. This can be useful when certificates are required in - a webserver, but ACME needs the webserver to make its requests. - - With preliminary self-signed certificate the webserver can be started and - can later reload the correct ACME certificates. - ''; - }; - - certs = mkOption { - default = { }; - type = with types; attrsOf (submodule certOpts); - description = '' - Attribute set of certificates to get signed and renewed. Creates - acme-''${cert}.{service,timer} systemd units for - each certificate defined here. Other services can add dependencies - to those units if they rely on the certificates being present, - or trigger restarts of the service if certificates get renewed. - ''; - example = literalExample '' - { - "example.com" = { - webroot = "/var/www/challenges/"; - email = "foo@example.com"; - extraDomains = { "www.example.com" = null; "foo.example.com" = "/var/www/foo/"; }; - }; - "bar.example.com" = { - webroot = "/var/www/challenges/"; - email = "bar@example.com"; - }; - } - ''; - }; - }; - }; - - ###### implementation - config = mkMerge [ - (mkIf (cfg.certs != { }) { - - systemd.services = let - services = concatLists servicesLists; - servicesLists = mapAttrsToList certToServices cfg.certs; - certToServices = cert: data: - let - lpath = "acme/${cert}"; - rights = if data.allowKeysForGroup then "750" else "700"; - cmdline = [ "-v" "-d" data.domain "--default_root" data.webroot "--valid_min" cfg.validMin ] - ++ optionals (data.email != null) [ "--email" data.email ] - ++ concatMap (p: [ "-f" p ]) data.plugins - ++ concatLists (mapAttrsToList (name: root: [ "-d" (if root == null then name else "${name}:${root}")]) data.extraDomains) - ++ optionals (cfg.server != null || data.server != null) ["--server" (if data.server == null then cfg.server else data.server)]; - acmeService = { - description = "Renew ACME Certificate for ${cert}"; - after = [ "network.target" "network-online.target" ]; - wants = [ "network-online.target" ]; - # simp_le uses requests, which uses certifi under the hood, - # which doesn't respect the system trust store. - # At least in the acme test, we provision a fake CA, impersonating the LE endpoint. - # REQUESTS_CA_BUNDLE is a way to teach python requests to use something else - environment.REQUESTS_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt"; - serviceConfig = { - Type = "oneshot"; - # With RemainAfterExit the service is considered active even - # after the main process having exited, which means when it - # gets changed, the activation phase restarts it, meaning - # the permissions of the StateDirectory get adjusted - # according to the specified group - # Edit: Timers will never run because of this - # RemainAfterExit = true; - SuccessExitStatus = [ "0" "1" ]; - User = data.user; - Group = data.group; - PrivateTmp = true; - StateDirectory = lpath; - StateDirectoryMode = rights; - ExecStartPre = - let - script = pkgs.writeScript "acme-pre-start" '' - #!${pkgs.runtimeShell} -e - mkdir -p '${data.webroot}/.well-known/acme-challenge' - chmod a+w '${data.webroot}/.well-known/acme-challenge' - #doesn't work for multiple concurrent runs - #chown -R '${data.user}:${data.group}' '${data.webroot}/.well-known/acme-challenge' - ''; - in - "+${script}"; - WorkingDirectory = "/var/lib/${lpath}"; - ExecStart = "${pkgs.simp_le_0_17}/bin/simp_le ${escapeShellArgs cmdline}"; - ExecStartPost = - let - script = pkgs.writeScript "acme-post-start" '' - #!${pkgs.runtimeShell} -e - ${data.postRun} - ''; - in - "+${script}"; - }; - - }; - selfsignedService = { - description = "Create preliminary self-signed certificate for ${cert}"; - path = [ pkgs.openssl ]; - script = - '' - workdir="$(mktemp -d)" - - # Create CA - openssl genrsa -des3 -passout pass:xxxx -out $workdir/ca.pass.key 2048 - openssl rsa -passin pass:xxxx -in $workdir/ca.pass.key -out $workdir/ca.key - openssl req -new -key $workdir/ca.key -out $workdir/ca.csr \ - -subj "/C=UK/ST=Warwickshire/L=Leamington/O=OrgName/OU=Security Department/CN=example.com" - openssl x509 -req -days 1 -in $workdir/ca.csr -signkey $workdir/ca.key -out $workdir/ca.crt - - # Create key - openssl genrsa -des3 -passout pass:xxxx -out $workdir/server.pass.key 2048 - openssl rsa -passin pass:xxxx -in $workdir/server.pass.key -out $workdir/server.key - openssl req -new -key $workdir/server.key -out $workdir/server.csr \ - -subj "/C=UK/ST=Warwickshire/L=Leamington/O=OrgName/OU=IT Department/CN=example.com" - openssl x509 -req -days 1 -in $workdir/server.csr -CA $workdir/ca.crt \ - -CAkey $workdir/ca.key -CAserial $workdir/ca.srl -CAcreateserial \ - -out $workdir/server.crt - - # Copy key to destination - cp $workdir/server.key /var/lib/${lpath}/key.pem - - # Create fullchain.pem (same format as "simp_le ... -f fullchain.pem" creates) - cat $workdir/{server.crt,ca.crt} > "/var/lib/${lpath}/fullchain.pem" - - # Create full.pem for e.g. lighttpd - cat $workdir/{server.key,server.crt,ca.crt} > "/var/lib/${lpath}/full.pem" - - # Give key acme permissions - chown '${data.user}:${data.group}' "/var/lib/${lpath}/"{key,fullchain,full}.pem - chmod ${rights} "/var/lib/${lpath}/"{key,fullchain,full}.pem - ''; - serviceConfig = { - Type = "oneshot"; - PrivateTmp = true; - StateDirectory = lpath; - User = data.user; - Group = data.group; - }; - unitConfig = { - # Do not create self-signed key when key already exists - ConditionPathExists = "!/var/lib/${lpath}/key.pem"; - }; - }; - in ( - [ { name = "acme-${cert}"; value = acmeService; } ] - ++ optional cfg.preliminarySelfsigned { name = "acme-selfsigned-${cert}"; value = selfsignedService; } - ); - servicesAttr = listToAttrs services; - in - servicesAttr; - - # FIXME: this doesn't work for multiple users - systemd.tmpfiles.rules = - flip mapAttrsToList cfg.certs - (cert: data: "d ${data.webroot}/.well-known/acme-challenge - ${data.user} ${data.group}"); - - systemd.timers = flip mapAttrs' cfg.certs (cert: data: nameValuePair - ("acme-${cert}") - ({ - description = "Renew ACME Certificate for ${cert}"; - wantedBy = [ "timers.target" ]; - timerConfig = { - OnCalendar = cfg.renewInterval; - Unit = "acme-${cert}.service"; - Persistent = "yes"; - AccuracySec = "5m"; - RandomizedDelaySec = "1h"; - }; - }) - ); - - systemd.targets.acme-selfsigned-certificates = mkIf cfg.preliminarySelfsigned {}; - systemd.targets.acme-certificates = {}; - }) - - ]; - - meta = { - maintainers = with lib.maintainers; [ abbradar fpletz globin ]; - #doc = ./acme.xml; - }; -}