aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2021-10-20 16:40:57 +0200
committerIsmaël Bouya <ismael.bouya@normalesup.org>2021-10-20 20:13:34 +0200
commitfcbdf67afe262bf6b35a4047956b2f8c12a04cb1 (patch)
tree2829eadcc9a2b7407c17296d2bd47745c6fe471c
parent65da25ba402a308f72dd3692335fbffbe1409f56 (diff)
downloadNix-fcbdf67afe262bf6b35a4047956b2f8c12a04cb1.tar.gz
Nix-fcbdf67afe262bf6b35a4047956b2f8c12a04cb1.tar.zst
Nix-fcbdf67afe262bf6b35a4047956b2f8c12a04cb1.zip
Migrate to proftpd
-rw-r--r--modules/private/environment.nix5
-rw-r--r--modules/private/ftp.nix146
-rwxr-xr-xmodules/private/ftp_sync.sh47
-rw-r--r--modules/private/system/eldiron.nix2
-rw-r--r--pkgs/default.nix1
-rw-r--r--pkgs/proftpd/default.nix23
6 files changed, 211 insertions, 13 deletions
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
621 description = "FTP configuration"; 621 description = "FTP configuration";
622 type = submodule { 622 type = submodule {
623 options = { 623 options = {
624 ldap = mkLdapOptions "FTP" {}; 624 ldap = mkLdapOptions "FTP" {
625 proftpd_filter = mkOption { type = str; description = "Filter for proftpd listing in LDAP"; };
626 pure-ftpd_filter = mkOption { type = str; description = "Filter for pure-ftpd listing in LDAP"; };
627 };
625 }; 628 };
626 }; 629 };
627 }; 630 };
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 @@
1{ lib, pkgs, config, ... }: 1{ lib, pkgs, config, ... }:
2let 2let
3 package = pkgs.pure-ftpd.override { ldapFtpId = "immaeFtp"; }; 3 package = pkgs.pure-ftpd.override { ldapFtpId = "immaeFtp"; };
4 pure-ftpd-enabled = config.myServices.ftp.pure-ftpd.enable;
5 proftpd-enabled = config.myServices.ftp.proftpd.enable;
4in 6in
5{ 7{
6 options = { 8 options = {
7 services.pure-ftpd.enable = lib.mkOption { 9 myServices.ftp.enable = lib.mkOption {
10 type = lib.types.bool;
11 default = false;
12 description = ''
13 Whether to enable ftp.
14 '';
15 };
16 myServices.ftp.pure-ftpd.enable = lib.mkOption {
8 type = lib.types.bool; 17 type = lib.types.bool;
9 default = false; 18 default = false;
10 description = '' 19 description = ''
11 Whether to enable pure-ftpd. 20 Whether to enable pure-ftpd.
12 ''; 21 '';
13 }; 22 };
23 myServices.ftp.proftpd.enable = lib.mkOption {
24 type = lib.types.bool;
25 default = true;
26 description = ''
27 Whether to enable proftpd.
28 '';
29 };
14 }; 30 };
15 31
16 config = lib.mkIf config.services.pure-ftpd.enable { 32 config = lib.mkIf config.myServices.ftp.enable {
17 services.duplyBackup.profiles.ftp = { 33 services.duplyBackup.profiles.ftp = {
18 rootDir = "/var/lib/ftp"; 34 rootDir = "/var/lib/ftp";
19 remotes = [ "eriomem" "ovh" ]; 35 remotes = [ "eriomem" "ovh" ];
20 }; 36 };
21 security.acme.certs."ftp" = config.myServices.certificates.certConfig // { 37 security.acme.certs."ftp" = config.myServices.certificates.certConfig // {
22 domain = "eldiron.immae.eu"; 38 domain = "eldiron.immae.eu";
23 postRun = '' 39 postRun = (lib.optionalString pure-ftpd-enabled ''
24 systemctl restart pure-ftpd.service 40 systemctl restart pure-ftpd.service
25 ''; 41 '') + (lib.optionalString proftpd-enabled ''
42 systemctl restart proftpd.service
43 '');
26 extraDomains = { "ftp.immae.eu" = null; }; 44 extraDomains = { "ftp.immae.eu" = null; };
27 }; 45 };
28 46
29 networking = { 47 networking = {
30 firewall = { 48 firewall = {
31 allowedTCPPorts = [ 21 ]; 49 allowedTCPPorts = [ 21 115 ];
32 allowedTCPPortRanges = [ { from = 40000; to = 50000; } ]; 50 allowedTCPPortRanges = [ { from = 40000; to = 50000; } ];
33 }; 51 };
34 }; 52 };
@@ -43,11 +61,13 @@ in
43 61
44 users.groups.ftp.gid = config.ids.gids.ftp; 62 users.groups.ftp.gid = config.ids.gids.ftp;
45 63
46 system.activationScripts.pure-ftpd = '' 64 system.activationScripts.ftp = ''
47 install -m 0755 -o ftp -g ftp -d /var/lib/ftp 65 install -m 0755 -o ftp -g ftp -d /var/lib/ftp
48 ''; 66 '' + (lib.optionalString proftpd-enabled ''
67 install -m 0755 -o nobody -g nogroup -d /var/lib/proftpd/authorized_keys
68 '');
49 69
50 secrets.keys."pure-ftpd-ldap" = { 70 secrets.keys."pure-ftpd-ldap" = lib.mkIf pure-ftpd-enabled {
51 permissions = "0400"; 71 permissions = "0400";
52 user = "ftp"; 72 user = "ftp";
53 group = "ftp"; 73 group = "ftp";
@@ -62,7 +82,7 @@ in
62 LDAPForceDefaultUID False 82 LDAPForceDefaultUID False
63 LDAPDefaultGID 100 83 LDAPDefaultGID 100
64 LDAPForceDefaultGID False 84 LDAPForceDefaultGID False
65 LDAPFilter ${config.myEnv.ftp.ldap.filter} 85 LDAPFilter ${config.myEnv.ftp.ldap.pure-ftpd_filter}
66 86
67 LDAPAuthMethod BIND 87 LDAPAuthMethod BIND
68 88
@@ -71,15 +91,42 @@ in
71 LDAPHomeDir immaeFtpDirectory 91 LDAPHomeDir immaeFtpDirectory
72 ''; 92 '';
73 }; 93 };
94 secrets.keys."proftpd-ldap.conf" = lib.mkIf proftpd-enabled {
95 permissions = "0400";
96 user = "ftp";
97 group = "ftp";
98 text = ''
99 LDAPServer ldaps://${config.myEnv.ftp.ldap.host}:636/??sub
100 LDAPUseTLS on
101 LDAPAuthBinds on
102 LDAPBindDN "${config.myEnv.ftp.ldap.dn}" "${config.myEnv.ftp.ldap.password}"
103 LDAPSearchScope subtree
104 LDAPAuthBinds on
105 LDAPDefaultGID 100
106 LDAPDefaultUID 500
107 LDAPForceDefaultUID off
108 LDAPForceDefaultGID off
109 LDAPAttr gidNumber immaeFtpGid
110 LDAPAttr uidNumber immaeFtpUid
111 LDAPAttr homeDirectory immaeFtpDirectory
112 LDAPUsers "${config.myEnv.ftp.ldap.base}" "${config.myEnv.ftp.ldap.proftpd_filter}"
113 LDAPGroups "${config.myEnv.ftp.ldap.base}"
114 '';
115 };
74 116
75 services.filesWatcher.pure-ftpd = { 117 services.filesWatcher.pure-ftpd = lib.mkIf pure-ftpd-enabled {
76 restart = true; 118 restart = true;
77 paths = [ config.secrets.fullPaths."pure-ftpd-ldap" ]; 119 paths = [ config.secrets.fullPaths."pure-ftpd-ldap" ];
78 }; 120 };
121 services.filesWatcher.proftpd = lib.mkIf proftpd-enabled {
122 restart = true;
123 paths = [ config.secrets.fullPaths."proftpd-ldap.conf" ];
124 };
79 125
80 systemd.services.pure-ftpd = let 126 systemd.services.pure-ftpd = let
81 configFile = pkgs.writeText "pure-ftpd.conf" '' 127 configFile = pkgs.writeText "pure-ftpd.conf" ''
82 PassivePortRange 40000 50000 128 PassivePortRange 40000 50000
129 Bind 42
83 ChrootEveryone yes 130 ChrootEveryone yes
84 CreateHomeDir yes 131 CreateHomeDir yes
85 BrokenClientsCompatibility yes 132 BrokenClientsCompatibility yes
@@ -112,7 +159,7 @@ in
112 TLS 1 159 TLS 1
113 CertFile ${config.security.acme.certs.ftp.directory}/full.pem 160 CertFile ${config.security.acme.certs.ftp.directory}/full.pem
114 ''; 161 '';
115 in { 162 in lib.mkIf pure-ftpd-enabled {
116 description = "Pure-FTPd server"; 163 description = "Pure-FTPd server";
117 wantedBy = [ "multi-user.target" ]; 164 wantedBy = [ "multi-user.target" ];
118 after = [ "network.target" ]; 165 after = [ "network.target" ];
@@ -121,6 +168,83 @@ in
121 serviceConfig.Type = "forking"; 168 serviceConfig.Type = "forking";
122 serviceConfig.PIDFile = "/run/pure-ftpd.pid"; 169 serviceConfig.PIDFile = "/run/pure-ftpd.pid";
123 }; 170 };
171
172 systemd.services.proftpd = let
173 configFile = pkgs.writeText "proftpd.conf" ''
174 ServerName "ProFTPD"
175 ServerType standalone
176 DefaultServer on
177
178 Port 21
179 UseIPv6 on
180 Umask 022
181 MaxInstances 30
182 MaxClients 50
183 MaxClientsPerHost 8
184
185 # Set the user and group under which the server will run.
186 User ftp
187 Group ftp
188
189 CreateHome on
190 DefaultRoot ~
191
192 AllowOverwrite on
193
194 TLSEngine on
195 TLSRequired off
196 TLSProtocol TLSv1.1 TLSv1.2 TLSv1.3
197
198 TLSCertificateChainFile ${config.security.acme.certs.ftp.directory}/fullchain.pem
199 TLSECCertificateFile ${config.security.acme.certs.ftp.directory}/cert.pem
200 TLSECCertificateKeyFile ${config.security.acme.certs.ftp.directory}/key.pem
201 TLSRenegotiate none
202 PidFile /run/proftpd/proftpd.pid
203
204 ScoreboardFile /run/proftpd/proftpd.scoreboard
205
206 PassivePorts 40000 50000
207 #DebugLevel 10
208 Include ${config.secrets.fullPaths."proftpd-ldap.conf"}
209
210 RequireValidShell off
211
212 # Bar use of SITE CHMOD by default
213 <Limit SITE_CHMOD>
214 DenyAll
215 </Limit>
216
217 <VirtualHost 0.0.0.0>
218 Umask 022
219 Port 115
220 SFTPEngine on
221 CreateHome on
222 DefaultRoot ~
223
224 AllowOverwrite on
225
226 SFTPHostKey /etc/ssh/ssh_host_ed25519_key
227 SFTPHostKey /etc/ssh/ssh_host_rsa_key
228 Include ${config.secrets.fullPaths."proftpd-ldap.conf"}
229 RequireValidShell off
230 SFTPAuthorizedUserKeys file:/var/lib/proftpd/authorized_keys/%u
231 SFTPAuthMethods password publickey
232 </VirtualHost>
233 '';
234 in lib.mkIf proftpd-enabled {
235 description = "ProFTPD server";
236 wantedBy = [ "multi-user.target" ];
237 after = [ "network.target" ];
238
239 serviceConfig.ExecStart = "${pkgs.proftpd}/bin/proftpd -c ${configFile}";
240 serviceConfig.Type = "forking";
241 serviceConfig.PIDFile = "/run/proftpd/proftpd.pid";
242 serviceConfig.RuntimeDirectory = "proftpd";
243 };
244
245 services.cron.systemCronJobs = lib.mkIf proftpd-enabled [
246 "*/2 * * * * nobody ${./ftp_sync.sh}"
247 ];
124 }; 248 };
125 249
126} 250}
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 @@
1#!/usr/bin/env bash
2
3LDAPSEARCH=ldapsearch
4
5LDAP_BIND="cn=ssh,ou=services,dc=immae,dc=eu"
6LDAP_PASS=$(cat /etc/ssh/ldap_password)
7LDAP_HOST="ldap.immae.eu"
8LDAP_BASE="dc=immae,dc=eu"
9LDAP_FILTER="(memberOf=cn=users,cn=ftp,ou=services,dc=immae,dc=eu)"
10
11handle_keys() {
12 uids="$1"
13 keys="$2"
14 if [ -n "$uids" ]; then
15 for uid in $uids; do
16 echo "$keys" | while read key; do
17 if [ -n "$key" ]; then
18 ssh-keygen -e -f <(echo "$key")
19 fi
20 done > /var/lib/proftpd/authorized_keys/$uid
21 done
22 fi
23}
24
25mkdir -p /var/lib/proftpd/authorized_keys
26
27while read i; do
28 if [[ "$i" =~ ^dn: ]]; then
29 handle_keys "$uids" "$keys"
30 uids=""
31 keys=""
32 fi;
33 if [[ "$i" =~ ^uid: ]]; then
34 uids="$uids ${i#uid: }"
35 fi
36 if [[ "$i" =~ ^immaeSshKey: ]]; then
37 key="${i#immaeSshKey: }"
38 if [[ "$key" =~ ^ssh- ]]; then
39 keys="$keys
40$key"
41 elif echo "$key" | cut -d" " -f1 | grep -q "\bftp\b"; then
42 keys="$keys
43$(echo "$key" | cut -d" " -f2-)"
44 fi
45 fi
46done < <(ldapsearch -h "$LDAP_HOST" -ZZ -LLL -D "$LDAP_BIND" -w "$LDAP_PASS" -b "$LDAP_BASE" -x -o ldif-wrap=no "$LDAP_FILTER" uid immaeSshKey)
47handle_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 @@
116 myServices.mail.enable = true; 116 myServices.mail.enable = true;
117 myServices.ejabberd.enable = true; 117 myServices.ejabberd.enable = true;
118 myServices.vpn.enable = true; 118 myServices.vpn.enable = true;
119 services.pure-ftpd.enable = true; 119 myServices.ftp.enable = true;
120 services.duplyBackup.enable = false; 120 services.duplyBackup.enable = false;
121 services.duplyBackup.profiles.oldies.rootDir = "/var/lib/oldies"; 121 services.duplyBackup.profiles.oldies.rootDir = "/var/lib/oldies";
122 122
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 {
38 iota-cli-app = callPackage ./crypto/iota-cli-app { inherit mylibs; }; 38 iota-cli-app = callPackage ./crypto/iota-cli-app { inherit mylibs; };
39 sia = callPackage ./crypto/sia {}; 39 sia = callPackage ./crypto/sia {};
40 40
41 proftpd = callPackage ./proftpd {};
41 pure-ftpd = callPackage ./pure-ftpd {}; 42 pure-ftpd = callPackage ./pure-ftpd {};
42 43
43 composerEnv = callPackage ./composer-env {}; 44 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 @@
1{ pkgs ? import <nixpkgs> {} }:
2with pkgs;
3
4stdenv.mkDerivation rec {
5 pname = "proftpd";
6 version = "1.3.7c";
7 src = fetchurl {
8 url = "https://github.com/proftpd/proftpd/archive/refs/tags/v${version}.tar.gz";
9 sha256 = "1nh02j00ly814fk885wn9zx1lb63cqd8qv3mgz719xkckf5rcw3h";
10 };
11 postPatch = ''
12 sed -i -e "s@/usr/bin/file@${file}/bin/file@" configure
13 '';
14 dontDisableStatic = 1;
15 configureFlags = "--enable-openssl --with-modules=mod_ldap:mod_sftp:mod_tls --with-includes=${libsodium.dev}/include --with-libraries=${libsodium}/lib";
16 preInstall = ''
17 installFlagsArray=(INSTALL_USER=$(id -u) INSTALL_GROUP=$(id -g))
18 '';
19 buildInputs = [ openssl libsodium ncurses cyrus_sasl openldap pkg-config ];
20 postInstall = ''
21 rmdir $out/var $out/libexec $out/lib/proftpd $out/share/locale
22 '';
23}