]> git.immae.eu Git - perso/Immae/Config/Nix.git/commitdiff
Migrate to proftpd
authorIsmaël Bouya <ismael.bouya@normalesup.org>
Wed, 20 Oct 2021 14:40:57 +0000 (16:40 +0200)
committerIsmaël Bouya <ismael.bouya@normalesup.org>
Wed, 20 Oct 2021 18:13:34 +0000 (20:13 +0200)
modules/private/environment.nix
modules/private/ftp.nix
modules/private/ftp_sync.sh [new file with mode: 0755]
modules/private/system/eldiron.nix
pkgs/default.nix
pkgs/proftpd/default.nix [new file with mode: 0644]

index 65d9f0a5ab0975737185da7ba96470a9d7080582..837d24be95e65a9251ba93b54163fc48b6f4a113 100644 (file)
@@ -621,7 +621,10 @@ in
       description = "FTP configuration";
       type = submodule {
         options = {
-          ldap = mkLdapOptions "FTP" {};
+          ldap = mkLdapOptions "FTP" {
+            proftpd_filter = mkOption { type = str; description = "Filter for proftpd listing in LDAP"; };
+            pure-ftpd_filter = mkOption { type = str; description = "Filter for pure-ftpd listing in LDAP"; };
+          };
         };
       };
     };
index 142819870b5f17bce2f5630e2a40445d05d2eab7..111d6bccbadd237ae62af92746d3d2cfd8d57938 100644 (file)
@@ -1,34 +1,52 @@
 { lib, pkgs, config, ... }:
 let
   package = pkgs.pure-ftpd.override { ldapFtpId = "immaeFtp"; };
+  pure-ftpd-enabled = config.myServices.ftp.pure-ftpd.enable;
+  proftpd-enabled = config.myServices.ftp.proftpd.enable;
 in
 {
   options = {
-    services.pure-ftpd.enable = lib.mkOption {
+    myServices.ftp.enable = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = ''
+        Whether to enable ftp.
+      '';
+    };
+    myServices.ftp.pure-ftpd.enable = lib.mkOption {
       type = lib.types.bool;
       default = false;
       description = ''
         Whether to enable pure-ftpd.
       '';
     };
+    myServices.ftp.proftpd.enable = lib.mkOption {
+      type = lib.types.bool;
+      default = true;
+      description = ''
+        Whether to enable proftpd.
+      '';
+    };
   };
 
-  config = lib.mkIf config.services.pure-ftpd.enable {
+  config = lib.mkIf config.myServices.ftp.enable {
     services.duplyBackup.profiles.ftp = {
       rootDir = "/var/lib/ftp";
       remotes = [ "eriomem" "ovh" ];
     };
     security.acme.certs."ftp" = config.myServices.certificates.certConfig // {
       domain = "eldiron.immae.eu";
-      postRun = ''
+      postRun = (lib.optionalString pure-ftpd-enabled ''
         systemctl restart pure-ftpd.service
-      '';
+      '') + (lib.optionalString proftpd-enabled ''
+        systemctl restart proftpd.service
+      '');
       extraDomains = { "ftp.immae.eu" = null; };
     };
 
     networking = {
       firewall = {
-        allowedTCPPorts = [ 21 ];
+        allowedTCPPorts = [ 21 115 ];
         allowedTCPPortRanges = [ { from = 40000; to = 50000; } ];
       };
     };
@@ -43,11 +61,13 @@ in
 
     users.groups.ftp.gid = config.ids.gids.ftp;
 
-    system.activationScripts.pure-ftpd = ''
+    system.activationScripts.ftp = ''
       install -m 0755 -o ftp -g ftp -d /var/lib/ftp
-      '';
+    '' + (lib.optionalString proftpd-enabled ''
+      install -m 0755 -o nobody -g nogroup -d /var/lib/proftpd/authorized_keys
+      '');
 
-    secrets.keys."pure-ftpd-ldap" = {
+    secrets.keys."pure-ftpd-ldap" = lib.mkIf pure-ftpd-enabled {
       permissions = "0400";
       user = "ftp";
       group = "ftp";
@@ -62,7 +82,7 @@ in
         LDAPForceDefaultUID False
         LDAPDefaultGID      100
         LDAPForceDefaultGID False
-        LDAPFilter          ${config.myEnv.ftp.ldap.filter}
+        LDAPFilter          ${config.myEnv.ftp.ldap.pure-ftpd_filter}
 
         LDAPAuthMethod      BIND
 
@@ -71,15 +91,42 @@ in
         LDAPHomeDir         immaeFtpDirectory
         '';
     };
+    secrets.keys."proftpd-ldap.conf" = lib.mkIf proftpd-enabled {
+      permissions = "0400";
+      user = "ftp";
+      group = "ftp";
+      text = ''
+        LDAPServer          ldaps://${config.myEnv.ftp.ldap.host}:636/??sub
+        LDAPUseTLS          on
+        LDAPAuthBinds       on
+        LDAPBindDN          "${config.myEnv.ftp.ldap.dn}" "${config.myEnv.ftp.ldap.password}"
+        LDAPSearchScope     subtree
+        LDAPAuthBinds       on
+        LDAPDefaultGID      100
+        LDAPDefaultUID      500
+        LDAPForceDefaultUID off
+        LDAPForceDefaultGID off
+        LDAPAttr            gidNumber immaeFtpGid
+        LDAPAttr            uidNumber immaeFtpUid
+        LDAPAttr            homeDirectory immaeFtpDirectory
+        LDAPUsers           "${config.myEnv.ftp.ldap.base}" "${config.myEnv.ftp.ldap.proftpd_filter}"
+        LDAPGroups          "${config.myEnv.ftp.ldap.base}"
+      '';
+    };
 
-    services.filesWatcher.pure-ftpd = {
+    services.filesWatcher.pure-ftpd = lib.mkIf pure-ftpd-enabled {
       restart = true;
       paths = [ config.secrets.fullPaths."pure-ftpd-ldap" ];
     };
+    services.filesWatcher.proftpd = lib.mkIf proftpd-enabled {
+      restart = true;
+      paths = [ config.secrets.fullPaths."proftpd-ldap.conf" ];
+    };
 
     systemd.services.pure-ftpd = let
       configFile = pkgs.writeText "pure-ftpd.conf" ''
         PassivePortRange             40000 50000
+        Bind                         42
         ChrootEveryone               yes
         CreateHomeDir                yes
         BrokenClientsCompatibility   yes
@@ -112,7 +159,7 @@ in
         TLS                          1
         CertFile                     ${config.security.acme.certs.ftp.directory}/full.pem
         '';
-    in {
+    in lib.mkIf pure-ftpd-enabled {
       description = "Pure-FTPd server";
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
@@ -121,6 +168,83 @@ in
       serviceConfig.Type = "forking";
       serviceConfig.PIDFile = "/run/pure-ftpd.pid";
     };
+
+    systemd.services.proftpd = let
+      configFile = pkgs.writeText "proftpd.conf" ''
+        ServerName             "ProFTPD"
+        ServerType             standalone
+        DefaultServer          on
+
+        Port                   21
+        UseIPv6                        on
+        Umask                  022
+        MaxInstances           30
+        MaxClients             50
+        MaxClientsPerHost      8
+
+        # Set the user and group under which the server will run.
+        User                   ftp
+        Group                  ftp
+
+        CreateHome             on
+        DefaultRoot            ~
+
+        AllowOverwrite         on
+
+        TLSEngine              on
+        TLSRequired            off
+        TLSProtocol            TLSv1.1 TLSv1.2 TLSv1.3
+
+        TLSCertificateChainFile        ${config.security.acme.certs.ftp.directory}/fullchain.pem
+        TLSECCertificateFile   ${config.security.acme.certs.ftp.directory}/cert.pem
+        TLSECCertificateKeyFile        ${config.security.acme.certs.ftp.directory}/key.pem
+        TLSRenegotiate none
+        PidFile                        /run/proftpd/proftpd.pid
+
+        ScoreboardFile         /run/proftpd/proftpd.scoreboard
+
+        PassivePorts           40000 50000
+        #DebugLevel            10
+        Include                        ${config.secrets.fullPaths."proftpd-ldap.conf"}
+
+        RequireValidShell      off
+
+        # Bar use of SITE CHMOD by default
+        <Limit SITE_CHMOD>
+          DenyAll
+        </Limit>
+
+        <VirtualHost 0.0.0.0>
+          Umask                                022
+          Port                         115
+          SFTPEngine                   on
+          CreateHome                   on
+          DefaultRoot                  ~
+
+          AllowOverwrite               on
+
+          SFTPHostKey                  /etc/ssh/ssh_host_ed25519_key
+          SFTPHostKey                  /etc/ssh/ssh_host_rsa_key
+          Include                      ${config.secrets.fullPaths."proftpd-ldap.conf"}
+          RequireValidShell            off
+          SFTPAuthorizedUserKeys       file:/var/lib/proftpd/authorized_keys/%u
+          SFTPAuthMethods              password publickey
+        </VirtualHost>
+        '';
+    in lib.mkIf proftpd-enabled {
+      description = "ProFTPD server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig.ExecStart = "${pkgs.proftpd}/bin/proftpd -c ${configFile}";
+      serviceConfig.Type = "forking";
+      serviceConfig.PIDFile = "/run/proftpd/proftpd.pid";
+      serviceConfig.RuntimeDirectory = "proftpd";
+    };
+
+    services.cron.systemCronJobs = lib.mkIf proftpd-enabled [
+      "*/2 * * * * nobody ${./ftp_sync.sh}"
+    ];
   };
 
 }
diff --git a/modules/private/ftp_sync.sh b/modules/private/ftp_sync.sh
new file mode 100755 (executable)
index 0000000..8b0d9c5
--- /dev/null
@@ -0,0 +1,47 @@
+#!/usr/bin/env bash
+
+LDAPSEARCH=ldapsearch
+
+LDAP_BIND="cn=ssh,ou=services,dc=immae,dc=eu"
+LDAP_PASS=$(cat /etc/ssh/ldap_password)
+LDAP_HOST="ldap.immae.eu"
+LDAP_BASE="dc=immae,dc=eu"
+LDAP_FILTER="(memberOf=cn=users,cn=ftp,ou=services,dc=immae,dc=eu)"
+
+handle_keys() {
+  uids="$1"
+  keys="$2"
+  if [ -n "$uids" ]; then
+    for uid in $uids; do
+      echo "$keys" | while read key; do
+        if [ -n "$key" ]; then
+          ssh-keygen -e -f <(echo "$key")
+        fi
+      done > /var/lib/proftpd/authorized_keys/$uid
+    done
+  fi
+}
+
+mkdir -p /var/lib/proftpd/authorized_keys
+
+while read i; do
+  if [[ "$i" =~ ^dn: ]]; then
+    handle_keys "$uids" "$keys"
+    uids=""
+    keys=""
+  fi;
+  if [[ "$i" =~ ^uid: ]]; then
+    uids="$uids ${i#uid: }"
+  fi
+  if [[ "$i" =~ ^immaeSshKey: ]]; then
+    key="${i#immaeSshKey: }"
+    if [[ "$key" =~ ^ssh- ]]; then
+      keys="$keys
+$key"
+    elif echo "$key" | cut -d" " -f1 | grep -q "\bftp\b"; then
+      keys="$keys
+$(echo "$key" | cut -d" " -f2-)"
+    fi
+  fi
+done < <(ldapsearch -h "$LDAP_HOST" -ZZ -LLL -D "$LDAP_BIND" -w "$LDAP_PASS" -b "$LDAP_BASE" -x -o ldif-wrap=no "$LDAP_FILTER" uid immaeSshKey)
+handle_keys "$uids" "$keys"
index 2c339a52888aac150ce1f9908dfae60ad3e3e196..5fb9887cd2f79f95de85ac6c60546c8c88a15f64 100644 (file)
   myServices.mail.enable = true;
   myServices.ejabberd.enable = true;
   myServices.vpn.enable = true;
-  services.pure-ftpd.enable = true;
+  myServices.ftp.enable = true;
   services.duplyBackup.enable = false;
   services.duplyBackup.profiles.oldies.rootDir = "/var/lib/oldies";
 
index 616a462ecff00a636fdc88be0ad5bfd4294fa9e7..5f5df82d11e6f0e20ee7f9d7a0ee32c3b6a14305 100644 (file)
@@ -38,6 +38,7 @@ rec {
   iota-cli-app = callPackage ./crypto/iota-cli-app { inherit mylibs; };
   sia = callPackage ./crypto/sia {};
 
+  proftpd = callPackage ./proftpd {};
   pure-ftpd = callPackage ./pure-ftpd {};
 
   composerEnv = callPackage ./composer-env {};
diff --git a/pkgs/proftpd/default.nix b/pkgs/proftpd/default.nix
new file mode 100644 (file)
index 0000000..af9d6c6
--- /dev/null
@@ -0,0 +1,23 @@
+{ pkgs ? import <nixpkgs> {} }:
+with pkgs;
+
+stdenv.mkDerivation rec {
+  pname = "proftpd";
+  version = "1.3.7c";
+  src = fetchurl {
+    url = "https://github.com/proftpd/proftpd/archive/refs/tags/v${version}.tar.gz";
+    sha256 = "1nh02j00ly814fk885wn9zx1lb63cqd8qv3mgz719xkckf5rcw3h";
+  };
+  postPatch = ''
+    sed -i -e "s@/usr/bin/file@${file}/bin/file@" configure
+  '';
+  dontDisableStatic = 1;
+  configureFlags = "--enable-openssl --with-modules=mod_ldap:mod_sftp:mod_tls --with-includes=${libsodium.dev}/include --with-libraries=${libsodium}/lib";
+  preInstall = ''
+    installFlagsArray=(INSTALL_USER=$(id -u) INSTALL_GROUP=$(id -g))
+  '';
+  buildInputs = [ openssl libsodium ncurses cyrus_sasl openldap pkg-config ];
+  postInstall = ''
+    rmdir $out/var $out/libexec $out/lib/proftpd $out/share/locale
+  '';
+}