]> git.immae.eu Git - perso/Immae/Config/Nix.git/commitdiff
Upgrade acme bot
authorIsmaël Bouya <ismael.bouya@normalesup.org>
Wed, 15 Jan 2020 19:41:19 +0000 (20:41 +0100)
committerIsmaël Bouya <ismael.bouya@normalesup.org>
Wed, 15 Jan 2020 19:41:19 +0000 (20:41 +0100)
26 files changed:
modules/acme2.nix [new file with mode: 0644]
modules/default.nix
modules/private/certificates.nix
modules/private/databases/mariadb.nix
modules/private/databases/openldap/default.nix
modules/private/databases/postgresql.nix
modules/private/ejabberd/default.nix
modules/private/ftp.nix
modules/private/irc.nix
modules/private/mail/default.nix
modules/private/mail/dovecot.nix
modules/private/mail/postfix.nix
modules/private/mail/relay.nix
modules/private/monitoring/status.nix
modules/private/tasks/default.nix
modules/private/websites/default.nix
modules/private/websites/florian/integration.nix
modules/private/websites/florian/production.nix
modules/private/websites/nassime/production.nix
modules/private/websites/naturaloutil/production.nix
modules/private/websites/papa/surveillance.nix
modules/private/websites/teliotortay/production.nix
modules/websites/default.nix
pkgs/certbot/default.nix [new file with mode: 0644]
pkgs/default.nix
pkgs/simp_le/default.nix [new file with mode: 0644]

diff --git a/modules/acme2.nix b/modules/acme2.nix
new file mode 100644 (file)
index 0000000..408c098
--- /dev/null
@@ -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.
+          <filename>.well-known/acme-challenge/</filename> directory
+          will be created below the webroot if it doesn't exist.
+          <literal>http://example.org/.well-known/acme-challenge/</literal> 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
+          (<option>security.acme2.cert.&lt;name&gt;.group</option>) 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 <filename>fullchain.pem</filename>,
+          private key in <filename>key.pem</filename> and those two previous
+          files combined in <filename>full.pem</filename> 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
+          <citerefentry><refentrytitle>systemd.time</refentrytitle>
+          <manvolnum>7</manvolnum></citerefentry>.
+        '';
+      };
+
+      server = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          ACME Directory Resource URI. Defaults to let's encrypt
+          production endpoint,
+          <literal>https://acme-v02.api.letsencrypt.org/directory</literal>, 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
+          <literal>acme-''${cert}.{service,timer}</literal> 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;
+  };
+}
index 9ff6ea62a2096f4f7f0b02439407d7911ad25a59..98dc77d8c34ff3e7678e958bd8338e88eab1fcdb 100644 (file)
@@ -19,4 +19,5 @@
 
   php-application = ./websites/php-application.nix;
   websites = ./websites;
+  acme2 = ./acme2.nix;
 } // (if builtins.pathExists ./private then import ./private else {})
index 2d245792919ed6c0b5b5f59fc375d224e7ea892d..f057200263416c9aeaa33d20c80b20c4f19e4098 100644 (file)
@@ -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;
       };
     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
index 3359064b9d3f6141be744f183c02225177c3dbe0..ed647ea662f5b5e96c2a603ffa4db6dc60541e9a 100644 (file)
@@ -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
index 22f6f7b3de3b8b5d9223b6034b3a203c32cc6a1d..d7d61db1f696598fd71a86d87e341e98ff0ca477 100644 (file)
@@ -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
index 3dcd311798f7232166537cffc1c707f92dddfc50..27ea59cbf6fd561c78e815f363f7617efa302b14 100644 (file)
@@ -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" (
index 5e717f4dc01b07ce9ed8ede8694f1c482cb53d8c..3537c246fe04a63c38872ff592afc08725a0401c 100644 (file)
@@ -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";
index e3c1f7041589c1d6f4320bf54758cfb0a5e3971f..585fe638b429e3402537b9a14c215438511f5d20 100644 (file)
@@ -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";
index 4e6eaabd9ee2f656d48cd2742cce9dd6eb09c200..1054b965eff7a7897fac9343da8e9456c6df84f3 100644 (file)
@@ -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";
         };
       };
     };
index b50e346a10495ed844ad15b2e2809a5f19795b1a..1c64e158be5aa035a5718eb41fb55493c052d098 100644 (file)
@@ -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:
index 4facef588478f6424c13c3778908472c410d5fad..523c017a39aeb72395ff7a600e19fe9bf367db5f 100644 (file)
@@ -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
       '';
index bd284cbf1d3a4015b344ac7be7e5c6c35a2a2273..8fe06dac426d70a3fa3a9c06961b8e9757034a0a 100644 (file)
         };
       };
     };
-    security.acme.certs."mail" = {
+    security.acme2.certs."mail" = {
       postRun = ''
         systemctl restart postfix.service
         '';
index 9111350cbdc17addc380ff85ab2b95a003e1dc8a..e0aa38776a87e6e192d6a9a45599979c4ecbc2b9 100644 (file)
@@ -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
         '';
index d25d9344247d35acc806cc7c6322d54d4a93ed44..2860e966989de9794b8e514f7e71ab62e98435b7 100644 (file)
@@ -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 ];
index c4f065b20f33be7d14ed5a6b420387b3cf187d81..c0cc87bc353000c2f05d2d49ac267d7517014677 100644 (file)
@@ -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;
     };
 
index 7f3e463166825b1533cd2dde4e82c96ce2c3c1c1..90f24a455ce357512c15059e6e85d2b8d2cf6a38 100644 (file)
@@ -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
         '';
     };
index 00de76195a12a9adee3d43e096fec2f9d98706b1..ef7d13a511a4733560eb74867b7c1e83c65ae211 100644 (file)
@@ -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 = {
index 8d3dfb04a3f54b1c1b1a8afc17af05a4cfc6a874..1abc7158641041c55be7b2e189898c639798a81c 100644 (file)
@@ -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 = {
index f9468f92e872ad044f9056dc681541e83635f2a7..293519f10b64ca7912d8eadd89eea0b138241463 100644 (file)
@@ -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";
index 628e1291668a3c915d62710b3d6740cef2a9bf75..a276c478ef828303e01a09418d91170f6e44f1f5 100644 (file)
@@ -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";
index 1bb6ac898c7ac2669e1b7ba241add2a2982c2ffa..f6e17721e8d61c68ca256f193a4f3a93864928af 100644 (file)
@@ -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
index 59090f5b34e9d7e153d1befcc7c141e89b546425..2c62d101f851fab630e91edde72cac3af4945be4 100644 (file)
@@ -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" ];
index 6ba0d687d2069c38b08a8df81c0b087914a1edb4..e69080e9dc2ae14c43f785796c8379468e5aeb9c 100644 (file)
@@ -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 (file)
index 0000000..8fdbfd1
--- /dev/null
@@ -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;
+  };
+}
index 82be20e55e54a610af00542dc3b674aeafae11e9..54868ba9aaa51b593baaa931b066bd6a90f92c41 100644 (file)
@@ -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 (file)
index 0000000..eaefba3
--- /dev/null
@@ -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;
+  };
+}