From: Ismaƫl Bouya Date: Wed, 15 Jan 2020 19:41:19 +0000 (+0100) Subject: Upgrade acme bot X-Git-Url: https://git.immae.eu/?p=perso%2FImmae%2FConfig%2FNix.git;a=commitdiff_plain;h=981fa80354fd6f00f49446777c38f77bd8a65f65 Upgrade acme bot --- diff --git a/modules/acme2.nix b/modules/acme2.nix new file mode 100644 index 0000000..408c098 --- /dev/null +++ b/modules/acme2.nix @@ -0,0 +1,340 @@ +{ 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 + RemainAfterExit = true; + SuccessExitStatus = [ "0" "1" ]; + User = data.user; + Group = data.group; + PrivateTmp = true; + StateDirectory = lpath; + StateDirectoryMode = rights; + 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; + + 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; + }; +} diff --git a/modules/default.nix b/modules/default.nix index 9ff6ea6..98dc77d 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -19,4 +19,5 @@ php-application = ./websites/php-application.nix; websites = ./websites; + acme2 = ./acme2.nix; } // (if builtins.pathExists ./private then import ./private else {}) diff --git a/modules/private/certificates.nix b/modules/private/certificates.nix index 2d24579..f057200 100644 --- a/modules/private/certificates.nix +++ b/modules/private/certificates.nix @@ -4,7 +4,7 @@ enable = lib.mkEnableOption "enable certificates"; certConfig = lib.mkOption { default = { - webroot = "${config.security.acme.directory}/acme-challenge"; + webroot = "/var/lib/acme/acme-challenge"; email = "ismael@bouya.org"; postRun = builtins.concatStringsSep "\n" [ (lib.optionalString config.services.httpd.Prod.enable "systemctl reload httpdProd.service") @@ -12,7 +12,7 @@ (lib.optionalString config.services.httpd.Inte.enable "systemctl reload httpdInte.service") (lib.optionalString config.services.nginx.enable "systemctl reload nginx.service") ]; - plugins = [ "cert.pem" "chain.pem" "fullchain.pem" "full.pem" "key.pem" "account_key.json" ]; + plugins = [ "cert.pem" "chain.pem" "fullchain.pem" "full.pem" "key.pem" "account_key.json" "account_reg.json"]; }; description = "Default configuration for certificates"; }; @@ -20,7 +20,7 @@ config = lib.mkIf config.myServices.certificates.enable { services.duplyBackup.profiles.system.excludeFile = '' - + ${config.security.acme.directory} + + /var/lib/acme/acme-challenge ''; services.nginx = { recommendedTlsSettings = true; @@ -30,9 +30,9 @@ myServices.databasesCerts = config.myServices.certificates.certConfig; myServices.ircCerts = config.myServices.certificates.certConfig; - security.acme.preliminarySelfsigned = true; + security.acme2.preliminarySelfsigned = true; - security.acme.certs = { + security.acme2.certs = { "${name}" = config.myServices.certificates.certConfig // { domain = config.hostEnv.fqdn; }; @@ -41,17 +41,17 @@ systemd.services = lib.attrsets.mapAttrs' (k: v: lib.attrsets.nameValuePair "acme-selfsigned-${k}" (lib.mkBefore { script = (lib.optionalString (builtins.elem "cert.pem" v.plugins) '' - cp $workdir/server.crt ${config.security.acme.directory}/${k}/cert.pem - chown '${v.user}:${v.group}' ${config.security.acme.directory}/${k}/cert.pem - chmod ${if v.allowKeysForGroup then "750" else "700"} ${config.security.acme.directory}/${k}/cert.pem + cp $workdir/server.crt ${config.security.acme2.certs."${k}".directory}/cert.pem + chown '${v.user}:${v.group}' ${config.security.acme2.certs."${k}".directory}/cert.pem + chmod ${if v.allowKeysForGroup then "750" else "700"} ${config.security.acme2.certs."${k}".directory}/cert.pem '') + (lib.optionalString (builtins.elem "chain.pem" v.plugins) '' - cp $workdir/ca.crt ${config.security.acme.directory}/${k}/chain.pem - chown '${v.user}:${v.group}' ${config.security.acme.directory}/${k}/chain.pem - chmod ${if v.allowKeysForGroup then "750" else "700"} ${config.security.acme.directory}/${k}/chain.pem + cp $workdir/ca.crt ${config.security.acme2.certs."${k}".directory}/chain.pem + chown '${v.user}:${v.group}' ${config.security.acme2.certs."${k}".directory}/chain.pem + chmod ${if v.allowKeysForGroup then "750" else "700"} ${config.security.acme2.certs."${k}".directory}/chain.pem '') ; }) - ) config.security.acme.certs // { + ) config.security.acme2.certs // { httpdProd = lib.mkIf config.services.httpd.Prod.enable { after = [ "acme-selfsigned-certificates.target" ]; wants = [ "acme-selfsigned-certificates.target" ]; }; httpdTools = lib.mkIf config.services.httpd.Tools.enable diff --git a/modules/private/databases/mariadb.nix b/modules/private/databases/mariadb.nix index 3359064..ed647ea 100644 --- a/modules/private/databases/mariadb.nix +++ b/modules/private/databases/mariadb.nix @@ -96,8 +96,8 @@ in { dataDir = cfg.dataDir; extraOptions = '' ssl_ca = ${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt - ssl_key = ${config.security.acme.directory}/mysql/key.pem - ssl_cert = ${config.security.acme.directory}/mysql/fullchain.pem + ssl_key = ${config.security.acme2.certs.mysql.directory}/key.pem + ssl_cert = ${config.security.acme2.certs.mysql.directory}/fullchain.pem # for replication log-bin=mariadb-bin @@ -110,10 +110,10 @@ in { }; users.users.mysql.extraGroups = [ "keys" ]; - security.acme.certs."mysql" = config.myServices.databasesCerts // { + security.acme2.certs."mysql" = config.myServices.databasesCerts // { user = "mysql"; group = "mysql"; - plugins = [ "fullchain.pem" "key.pem" "account_key.json" ]; + plugins = [ "fullchain.pem" "key.pem" "account_key.json" "account_reg.json" ]; domain = "db-1.immae.eu"; postRun = '' systemctl restart mysql.service diff --git a/modules/private/databases/openldap/default.nix b/modules/private/databases/openldap/default.nix index 22f6f7b..d7d61db 100644 --- a/modules/private/databases/openldap/default.nix +++ b/modules/private/databases/openldap/default.nix @@ -24,9 +24,9 @@ let overlay syncprov syncprov-checkpoint 100 10 - TLSCertificateFile ${config.security.acme.directory}/ldap/cert.pem - TLSCertificateKeyFile ${config.security.acme.directory}/ldap/key.pem - TLSCACertificateFile ${config.security.acme.directory}/ldap/fullchain.pem + TLSCertificateFile ${config.security.acme2.certs.ldap.directory}/cert.pem + TLSCertificateKeyFile ${config.security.acme2.certs.ldap.directory}/key.pem + TLSCACertificateFile ${config.security.acme2.certs.ldap.directory}/fullchain.pem TLSCACertificatePath ${pkgs.cacert.unbundled}/etc/ssl/certs/ #This makes openldap crash #TLSCipherSuite DEFAULT @@ -117,10 +117,10 @@ in users.users.openldap.extraGroups = [ "keys" ]; networking.firewall.allowedTCPPorts = [ 636 389 ]; - security.acme.certs."ldap" = config.myServices.databasesCerts // { + security.acme2.certs."ldap" = config.myServices.databasesCerts // { user = "openldap"; group = "openldap"; - plugins = [ "fullchain.pem" "key.pem" "cert.pem" "account_key.json" ]; + plugins = [ "fullchain.pem" "key.pem" "cert.pem" "account_key.json" "account_reg.json" ]; domain = "ldap.immae.eu"; postRun = '' systemctl restart openldap.service diff --git a/modules/private/databases/postgresql.nix b/modules/private/databases/postgresql.nix index 3dcd311..27ea59c 100644 --- a/modules/private/databases/postgresql.nix +++ b/modules/private/databases/postgresql.nix @@ -107,10 +107,10 @@ in { config = lib.mkIf cfg.enable { networking.firewall.allowedTCPPorts = [ 5432 ]; - security.acme.certs."postgresql" = config.myServices.databasesCerts // { + security.acme2.certs."postgresql" = config.myServices.databasesCerts // { user = "postgres"; group = "postgres"; - plugins = [ "fullchain.pem" "key.pem" "account_key.json" ]; + plugins = [ "fullchain.pem" "key.pem" "account_key.json" "account_reg.json" ]; domain = "db-1.immae.eu"; postRun = '' systemctl reload postgresql.service @@ -165,8 +165,8 @@ in { # makes it order of magnitudes quicker synchronous_commit = off ssl = on - ssl_cert_file = '${config.security.acme.directory}/postgresql/fullchain.pem' - ssl_key_file = '${config.security.acme.directory}/postgresql/key.pem' + ssl_cert_file = '${config.security.acme2.certs.postgresql.directory}/fullchain.pem' + ssl_key_file = '${config.security.acme2.certs.postgresql.directory}/key.pem' ''; authentication = let hosts = builtins.concatStringsSep "\n" ( diff --git a/modules/private/ejabberd/default.nix b/modules/private/ejabberd/default.nix index 5e717f4..3537c24 100644 --- a/modules/private/ejabberd/default.nix +++ b/modules/private/ejabberd/default.nix @@ -14,7 +14,7 @@ in }; config = lib.mkIf cfg.enable { - security.acme.certs = { + security.acme2.certs = { "ejabberd" = config.myServices.certificates.certConfig // { user = "ejabberd"; group = "ejabberd"; @@ -58,7 +58,7 @@ in text = '' host_config: "immae.fr": - domain_certfile: "${config.security.acme.directory}/ejabberd/full.pem" + domain_certfile: "${config.security.acme2.certs.ejabberd.directory}/full.pem" auth_method: [ldap] ldap_servers: ["${config.myEnv.jabber.ldap.host}"] ldap_encrypt: tls @@ -81,7 +81,7 @@ in ERLANG_NODE=ejabberd@localhost ''; configFile = pkgs.runCommand "ejabberd.yml" { - certificatePrivateKeyAndFullChain = "${config.security.acme.directory}/ejabberd/full.pem"; + certificatePrivateKeyAndFullChain = "${config.security.acme2.certs.ejabberd.directory}/full.pem"; certificateCA = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"; sql_config_file = config.secrets.fullPaths."ejabberd/psql.yml"; host_config_file = config.secrets.fullPaths."ejabberd/host.yml"; diff --git a/modules/private/ftp.nix b/modules/private/ftp.nix index e3c1f70..585fe63 100644 --- a/modules/private/ftp.nix +++ b/modules/private/ftp.nix @@ -17,7 +17,7 @@ in services.duplyBackup.profiles.ftp = { rootDir = "/var/lib/ftp"; }; - security.acme.certs."ftp" = config.myServices.certificates.certConfig // { + security.acme2.certs."ftp" = config.myServices.certificates.certConfig // { domain = "eldiron.immae.eu"; postRun = '' systemctl restart pure-ftpd.service @@ -113,7 +113,7 @@ in MaxDiskUsage 99 CustomerProof yes TLS 1 - CertFile ${config.security.acme.directory}/ftp/full.pem + CertFile ${config.security.acme2.certs.ftp.directory}/full.pem ''; in { description = "Pure-FTPd server"; diff --git a/modules/private/irc.nix b/modules/private/irc.nix index 4e6eaab..1054b96 100644 --- a/modules/private/irc.nix +++ b/modules/private/irc.nix @@ -20,7 +20,7 @@ in services.duplyBackup.profiles.irc = { rootDir = "/var/lib/bitlbee"; }; - security.acme.certs."irc" = config.myServices.ircCerts // { + security.acme2.certs."irc" = config.myServices.ircCerts // { domain = "irc.immae.eu"; postRun = '' systemctl restart stunnel.service @@ -49,7 +49,7 @@ in bitlbee = { accept = 6697; connect = 6667; - cert = "${config.security.acme.directory}/irc/full.pem"; + cert = "${config.security.acme2.certs.irc.directory}/full.pem"; }; }; }; diff --git a/modules/private/mail/default.nix b/modules/private/mail/default.nix index b50e346..1c64e15 100644 --- a/modules/private/mail/default.nix +++ b/modules/private/mail/default.nix @@ -13,7 +13,7 @@ options.myServices.mailBackup.enable = lib.mkEnableOption "enable MX backup services"; config = lib.mkIf config.myServices.mail.enable { - security.acme.certs."mail" = config.myServices.certificates.certConfig // { + security.acme2.certs."mail" = config.myServices.certificates.certConfig // { domain = config.hostEnv.fqdn; extraDomains = let zonesWithMx = builtins.filter (zone: diff --git a/modules/private/mail/dovecot.nix b/modules/private/mail/dovecot.nix index 4facef5..523c017 100644 --- a/modules/private/mail/dovecot.nix +++ b/modules/private/mail/dovecot.nix @@ -269,7 +269,7 @@ in [ "0 2 * * * root ${cron_script}/bin/cleanup-imap-folders" ]; - security.acme.certs."mail" = { + security.acme2.certs."mail" = { postRun = '' systemctl restart dovecot2.service ''; diff --git a/modules/private/mail/postfix.nix b/modules/private/mail/postfix.nix index bd284cb..8fe06da 100644 --- a/modules/private/mail/postfix.nix +++ b/modules/private/mail/postfix.nix @@ -417,7 +417,7 @@ }; }; }; - security.acme.certs."mail" = { + security.acme2.certs."mail" = { postRun = '' systemctl restart postfix.service ''; diff --git a/modules/private/mail/relay.nix b/modules/private/mail/relay.nix index 9111350..e0aa387 100644 --- a/modules/private/mail/relay.nix +++ b/modules/private/mail/relay.nix @@ -1,7 +1,7 @@ { lib, pkgs, config, nodes, name, ... }: { config = lib.mkIf config.myServices.mailBackup.enable { - security.acme.certs."mail" = config.myServices.certificates.certConfig // { + security.acme2.certs."mail" = config.myServices.certificates.certConfig // { postRun = '' systemctl restart postfix.service ''; diff --git a/modules/private/monitoring/status.nix b/modules/private/monitoring/status.nix index d25d934..2860e96 100644 --- a/modules/private/monitoring/status.nix +++ b/modules/private/monitoring/status.nix @@ -34,7 +34,7 @@ locations."/".proxyPass = "http://unix:/run/naemon-status/socket.sock:/"; }; }; - security.acme.certs."${name}".extraDomains."status.immae.eu" = null; + security.acme2.certs."${name}".extraDomains."status.immae.eu" = null; myServices.certificates.enable = true; networking.firewall.allowedTCPPorts = [ 80 443 ]; diff --git a/modules/private/tasks/default.nix b/modules/private/tasks/default.nix index c4f065b..c0cc87b 100644 --- a/modules/private/tasks/default.nix +++ b/modules/private/tasks/default.nix @@ -192,9 +192,9 @@ in { myServices.websites.webappDirs._task = ./www; - security.acme.certs."task" = config.myServices.certificates.certConfig // { + security.acme2.certs."task" = config.myServices.certificates.certConfig // { inherit user group; - plugins = [ "fullchain.pem" "key.pem" "cert.pem" "account_key.json" ]; + plugins = [ "fullchain.pem" "key.pem" "cert.pem" "account_key.json" "account_reg.json" ]; domain = fqdn; postRun = '' systemctl restart taskserver.service @@ -244,9 +244,9 @@ in { inherit fqdn; listenHost = "::"; pki.manual.ca.cert = "${server_vardir}/keys/ca.cert"; - pki.manual.server.cert = "${config.security.acme.directory}/task/fullchain.pem"; - pki.manual.server.crl = "${config.security.acme.directory}/task/invalid.crl"; - pki.manual.server.key = "${config.security.acme.directory}/task/key.pem"; + pki.manual.server.cert = "${config.security.acme2.certs.task.directory}/fullchain.pem"; + pki.manual.server.crl = "${config.security.acme2.certs.task.directory}/invalid.crl"; + pki.manual.server.key = "${config.security.acme2.certs.task.directory}/key.pem"; requestLimit = 104857600; }; diff --git a/modules/private/websites/default.nix b/modules/private/websites/default.nix index 7f3e463..90f24a4 100644 --- a/modules/private/websites/default.nix +++ b/modules/private/websites/default.nix @@ -125,7 +125,7 @@ in system.activationScripts = { httpd = '' - install -d -m 0755 ${config.security.acme.directory}/acme-challenge + install -d -m 0755 /var/lib/acme/acme-challenge install -d -m 0750 -o wwwrun -g wwwrun /var/lib/php/sessions ''; }; diff --git a/modules/private/websites/florian/integration.nix b/modules/private/websites/florian/integration.nix index 00de761..ef7d13a 100644 --- a/modules/private/websites/florian/integration.nix +++ b/modules/private/websites/florian/integration.nix @@ -8,7 +8,7 @@ in { options.myServices.websites.florian.integration.enable = lib.mkEnableOption "enable Florian's website integration"; config = lib.mkIf cfg.enable { - security.acme.certs."ftp".extraDomains."florian.immae.eu" = null; + security.acme2.certs."ftp".extraDomains."florian.immae.eu" = null; services.websites.env.integration.modules = adminer.apache.modules; services.websites.env.integration.vhostConfs.florian = { diff --git a/modules/private/websites/florian/production.nix b/modules/private/websites/florian/production.nix index 8d3dfb0..1abc715 100644 --- a/modules/private/websites/florian/production.nix +++ b/modules/private/websites/florian/production.nix @@ -8,7 +8,7 @@ in { options.myServices.websites.florian.production.enable = lib.mkEnableOption "enable Florian's website production"; config = lib.mkIf cfg.enable { - security.acme.certs."ftp".extraDomains."tellesflorian.com" = null; + security.acme2.certs."ftp".extraDomains."tellesflorian.com" = null; services.websites.env.production.modules = adminer.apache.modules; services.websites.env.production.vhostConfs.florian = { diff --git a/modules/private/websites/nassime/production.nix b/modules/private/websites/nassime/production.nix index f9468f9..293519f 100644 --- a/modules/private/websites/nassime/production.nix +++ b/modules/private/websites/nassime/production.nix @@ -9,7 +9,7 @@ in { config = lib.mkIf cfg.enable { services.webstats.sites = [ { name = "nassime.bouya.org"; } ]; - security.acme.certs."ftp".extraDomains."nassime.bouya.org" = null; + security.acme2.certs."ftp".extraDomains."nassime.bouya.org" = null; services.websites.env.production.vhostConfs.nassime = { certName = "nassime"; diff --git a/modules/private/websites/naturaloutil/production.nix b/modules/private/websites/naturaloutil/production.nix index 628e129..a276c47 100644 --- a/modules/private/websites/naturaloutil/production.nix +++ b/modules/private/websites/naturaloutil/production.nix @@ -10,7 +10,7 @@ in { config = lib.mkIf cfg.enable { services.webstats.sites = [ { name = "naturaloutil.immae.eu"; } ]; - security.acme.certs."ftp".extraDomains."naturaloutil.immae.eu" = null; + security.acme2.certs."ftp".extraDomains."naturaloutil.immae.eu" = null; secrets.keys = [{ dest = "webapps/prod-naturaloutil"; diff --git a/modules/private/websites/papa/surveillance.nix b/modules/private/websites/papa/surveillance.nix index 1bb6ac8..f6e1772 100644 --- a/modules/private/websites/papa/surveillance.nix +++ b/modules/private/websites/papa/surveillance.nix @@ -6,7 +6,7 @@ in { options.myServices.websites.papa.surveillance.enable = lib.mkEnableOption "enable Papa surveillance's website"; config = lib.mkIf cfg.enable { - security.acme.certs."ftp".extraDomains."surveillance.maison.bbc.bouya.org" = null; + security.acme2.certs."ftp".extraDomains."surveillance.maison.bbc.bouya.org" = null; services.cron = { systemCronJobs = let diff --git a/modules/private/websites/teliotortay/production.nix b/modules/private/websites/teliotortay/production.nix index 59090f5..2c62d10 100644 --- a/modules/private/websites/teliotortay/production.nix +++ b/modules/private/websites/teliotortay/production.nix @@ -10,7 +10,7 @@ in { config = lib.mkIf cfg.enable { services.webstats.sites = [ { name = "telio-tortay.immae.eu"; } ]; - security.acme.certs."ftp".extraDomains."telio-tortay.immae.eu" = null; + security.acme2.certs."ftp".extraDomains."telio-tortay.immae.eu" = null; system.activationScripts.telio-tortay = { deps = [ "httpd" ]; diff --git a/modules/websites/default.nix b/modules/websites/default.nix index 6ba0d68..e69080e 100644 --- a/modules/websites/default.nix +++ b/modules/websites/default.nix @@ -149,7 +149,7 @@ in serverAliases = [ "*" ]; enableSSL = false; logFormat = "combinedVhost"; - documentRoot = "${config.security.acme.directory}/acme-challenge"; + documentRoot = "/var/lib/acme/acme-challenge"; extraConfig = '' RewriteEngine on RewriteCond "%{REQUEST_URI}" "!^/\.well-known" @@ -178,9 +178,9 @@ in }; toVhost = ips: vhostConf: { enableSSL = true; - sslServerCert = "${config.security.acme.directory}/${vhostConf.certName}/cert.pem"; - sslServerKey = "${config.security.acme.directory}/${vhostConf.certName}/key.pem"; - sslServerChain = "${config.security.acme.directory}/${vhostConf.certName}/chain.pem"; + sslServerCert = "${config.security.acme2.certs."${vhostConf.certName}".directory}/cert.pem"; + sslServerKey = "${config.security.acme2.certs."${vhostConf.certName}".directory}/key.pem"; + sslServerChain = "${config.security.acme2.certs."${vhostConf.certName}".directory}/chain.pem"; logFormat = "combinedVhost"; listen = map (ip: { inherit ip; port = 443; }) ips; hostName = builtins.head vhostConf.hosts; @@ -223,7 +223,7 @@ in } ) cfg.env; - config.security.acme.certs = let + config.security.acme2.certs = let typesToManage = attrsets.filterAttrs (k: v: v.enable) cfg.env; flatVhosts = lists.flatten (attrsets.mapAttrsToList (k: v: attrValues v.vhostConfs diff --git a/pkgs/certbot/default.nix b/pkgs/certbot/default.nix new file mode 100644 index 0000000..8fdbfd1 --- /dev/null +++ b/pkgs/certbot/default.nix @@ -0,0 +1,65 @@ +{ stdenv, python37Packages, fetchFromGitHub, fetchurl, dialog, autoPatchelfHook }: + + +python37Packages.buildPythonApplication rec { + pname = "certbot"; + version = "1.0.0"; + + src = fetchFromGitHub { + owner = pname; + repo = pname; + rev = "v${version}"; + sha256 = "180x7gcpfbrzw8k654s7b5nxdy2yg61lq513dykyn3wz4gssw465"; + }; + + patches = [ + ./0001-Don-t-use-distutils.StrictVersion-that-cannot-handle.patch + ]; + + propagatedBuildInputs = with python37Packages; [ + ConfigArgParse + acme + configobj + cryptography + distro + josepy + parsedatetime + psutil + pyRFC3339 + pyopenssl + pytz + six + zope_component + zope_interface + ]; + + buildInputs = [ dialog ] ++ (with python37Packages; [ mock gnureadline ]); + + checkInputs = with python37Packages; [ + pytest_xdist + pytest + dateutil + ]; + + postPatch = '' + cd certbot + substituteInPlace certbot/_internal/notify.py --replace "/usr/sbin/sendmail" "/run/wrappers/bin/sendmail" + ''; + + postInstall = '' + for i in $out/bin/*; do + wrapProgram "$i" --prefix PYTHONPATH : "$PYTHONPATH" \ + --prefix PATH : "${dialog}/bin:$PATH" + done + ''; + + doCheck = true; + + meta = with stdenv.lib; { + homepage = src.meta.homepage; + description = "ACME client that can obtain certs and extensibly update server configurations"; + platforms = platforms.unix; + maintainers = [ maintainers.domenkozar ]; + license = licenses.asl20; + }; +} diff --git a/pkgs/default.nix b/pkgs/default.nix index 82be20e..54868ba 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -48,6 +48,9 @@ rec { naemon = callPackage ./naemon { inherit mylibs monitoring-plugins; }; naemon-livestatus = callPackage ./naemon-livestatus { inherit mylibs naemon; }; + simp_le_0_17 = callPackage ./simp_le {}; + certbot = callPackage ./certbot {}; + private = if builtins.pathExists (./. + "/private") then import ./private { inherit pkgs; } else { webapps = {}; }; diff --git a/pkgs/simp_le/default.nix b/pkgs/simp_le/default.nix new file mode 100644 index 0000000..eaefba3 --- /dev/null +++ b/pkgs/simp_le/default.nix @@ -0,0 +1,32 @@ +{ stdenv, python3Packages, bash }: + +python3Packages.buildPythonApplication rec { + pname = "simp_le-client"; + version = "0.17.0"; + + src = python3Packages.fetchPypi { + inherit pname version; + sha256 = "0m1jynar4calaffp2zdxr5yy9vnhw2qf2hsfxwzfwf8fqb5h7bjb"; + }; + + postPatch = '' + # drop upper bound of idna requirement + sed -ri "s/'(idna)<[^']+'/'\1'/" setup.py + substituteInPlace simp_le.py \ + --replace "/bin/sh" "${bash}/bin/sh" + ''; + + checkPhase = '' + $out/bin/simp_le --test + ''; + + propagatedBuildInputs = with python3Packages; [ acme setuptools_scm josepy idna ]; + + meta = with stdenv.lib; { + homepage = https://github.com/zenhack/simp_le; + description = "Simple Let's Encrypt client"; + license = licenses.gpl3; + maintainers = with maintainers; [ gebner makefu ]; + platforms = platforms.linux; + }; +}