From fcbdf67afe262bf6b35a4047956b2f8c12a04cb1 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Isma=C3=ABl=20Bouya?= Date: Wed, 20 Oct 2021 16:40:57 +0200 Subject: [PATCH] Migrate to proftpd --- modules/private/environment.nix | 5 +- modules/private/ftp.nix | 146 ++++++++++++++++++++++++++--- modules/private/ftp_sync.sh | 47 ++++++++++ modules/private/system/eldiron.nix | 2 +- pkgs/default.nix | 1 + pkgs/proftpd/default.nix | 23 +++++ 6 files changed, 211 insertions(+), 13 deletions(-) create mode 100755 modules/private/ftp_sync.sh create mode 100644 pkgs/proftpd/default.nix diff --git a/modules/private/environment.nix b/modules/private/environment.nix index 65d9f0a..837d24b 100644 --- a/modules/private/environment.nix +++ b/modules/private/environment.nix @@ -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"; }; + }; }; }; }; diff --git a/modules/private/ftp.nix b/modules/private/ftp.nix index 1428198..111d6bc 100644 --- a/modules/private/ftp.nix +++ b/modules/private/ftp.nix @@ -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 + + DenyAll + + + + 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 + + ''; + 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 index 0000000..8b0d9c5 --- /dev/null +++ b/modules/private/ftp_sync.sh @@ -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" diff --git a/modules/private/system/eldiron.nix b/modules/private/system/eldiron.nix index 2c339a5..5fb9887 100644 --- a/modules/private/system/eldiron.nix +++ b/modules/private/system/eldiron.nix @@ -116,7 +116,7 @@ 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"; diff --git a/pkgs/default.nix b/pkgs/default.nix index 616a462..5f5df82 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -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 index 0000000..af9d6c6 --- /dev/null +++ b/pkgs/proftpd/default.nix @@ -0,0 +1,23 @@ +{ pkgs ? import {} }: +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 + ''; +} -- 2.41.0