diff options
author | Ismaël Bouya <ismael.bouya@normalesup.org> | 2021-10-20 16:40:57 +0200 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@normalesup.org> | 2021-10-20 20:13:34 +0200 |
commit | fcbdf67afe262bf6b35a4047956b2f8c12a04cb1 (patch) | |
tree | 2829eadcc9a2b7407c17296d2bd47745c6fe471c | |
parent | 65da25ba402a308f72dd3692335fbffbe1409f56 (diff) | |
download | Nix-fcbdf67afe262bf6b35a4047956b2f8c12a04cb1.tar.gz Nix-fcbdf67afe262bf6b35a4047956b2f8c12a04cb1.tar.zst Nix-fcbdf67afe262bf6b35a4047956b2f8c12a04cb1.zip |
Migrate to proftpd
-rw-r--r-- | modules/private/environment.nix | 5 | ||||
-rw-r--r-- | modules/private/ftp.nix | 146 | ||||
-rwxr-xr-x | modules/private/ftp_sync.sh | 47 | ||||
-rw-r--r-- | modules/private/system/eldiron.nix | 2 | ||||
-rw-r--r-- | pkgs/default.nix | 1 | ||||
-rw-r--r-- | pkgs/proftpd/default.nix | 23 |
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, ... }: |
2 | let | 2 | let |
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; | ||
4 | in | 6 | in |
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 | |||
3 | LDAPSEARCH=ldapsearch | ||
4 | |||
5 | LDAP_BIND="cn=ssh,ou=services,dc=immae,dc=eu" | ||
6 | LDAP_PASS=$(cat /etc/ssh/ldap_password) | ||
7 | LDAP_HOST="ldap.immae.eu" | ||
8 | LDAP_BASE="dc=immae,dc=eu" | ||
9 | LDAP_FILTER="(memberOf=cn=users,cn=ftp,ou=services,dc=immae,dc=eu)" | ||
10 | |||
11 | handle_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 | |||
25 | mkdir -p /var/lib/proftpd/authorized_keys | ||
26 | |||
27 | while 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 | ||
46 | 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) | ||
47 | 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 @@ | |||
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> {} }: | ||
2 | with pkgs; | ||
3 | |||
4 | stdenv.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 | } | ||