Migrating from ProFTPD
Moving from a ProFTPD with virtual users (AuthUserFile, ftpd.passwd)
to CraftFileGate.
The model
ProFTPD puts identity, home and rights in one ftpd.passwd entry and in
<Directory> blocks. CraftFileGate separates them: users.toml says who you
are (username, password_hash, authorized_keys, authorities),
roles.toml what you see ([[backends]], [[roles]] and their
mounts). The home belongs to a role’s mount, not to the user:
Users, roles and mounts.
Translating a ftpd.passwd entry
alice:$6$rounds=5000$xyz...:1001:1001:Alice:/srv/ftp/alice:/bin/false
# users.toml
[[users]]
username = "alice"
password_hash = "$6$rounds=5000$xyz..." # kept as is, see below
authorized_keys = ["ssh-ed25519 AAAA... alice@poste"]
authorities = ["clients"]
# roles.toml: one role for all clients
[[backends]]
name = "ftp"
type = "local"
root = "/srv/ftp"
[[roles]]
name = "clients"
[[roles.mounts]]
backend = "ftp"
home_dir = "/{username}" # DefaultRoot ~: alice sees /srv/ftp/alice as /
create_home = true # CreateHome on
acl = [{ path = "/", rights = ["read", "write", "list", "delete", "rename"], recursive = true }]
| ProFTPD field | Becomes |
|---|---|
| password | password_hash |
| uid, gid | nothing: everything is written by the service account, without setuid |
| home | the mount’s home_dir, {username} for %u |
| shell, gecos | nothing: only the sftp subsystem is served |
{username} accepts only A-Z a-z 0-9 . _ -, 64 characters at most, with no
leading dot; any other name cannot connect to that mount.
Passwords
[auth.methods]
local = { enabled = true, allow_sha512_crypt = true } # or allow_bcrypt
$6$ and bcrypt are taken as is (Hashes);
DES and MD5 ($1$) need a new password or a public key.
Keeping the hashes is a migration step: sha512-crypt is not
memory-hard. Regenerate them as argon2id at the first opportunity:
echo -n "mot-de-passe" | craft-file-gate hash-password --user alice # a [[users]] block
An account that logs in only by key keeps a password_hash (the field is
required): put the hash of a random secret in it, or turn off passwords
for everyone (local.password = false). An identity provider can
also replace users.toml: see Authentication.
Directive mapping
| ProFTPD | CraftFileGate |
|---|---|
AuthUserFile | auth.methods.local.users_file |
AuthGroupFile | authorities and [[roles]] |
DefaultRoot ~ | home_dir = "/{username}" |
CreateHome on | create_home = true (local backend) |
<Directory> and <Limit> | [[roles.mounts.acl]], see below |
MaxLoginAttempts | [sftp.ban] max_failures: bans the address |
MaxClientsPerUser | server.max_sessions_per_user |
MaxClients | nothing; [sftp.rate_limit] bounds the connection rate |
HiddenStores on | [server.hidden_stores] enabled = true; disabled by default, as in ProFTPD |
mod_quotatab | the mount’s max_file_mb: a cap per file, not a cumulative volume |
TransferLog, ExtendedLog | the audit trail, see Audit |
TLSEngine (FTPS), PassivePorts, MasqueradeAddress | nothing: SFTP on a single TCP port |
SFTPHostKey | sftp.host_keys |
SFTPAuthorizedUserKeys | the user’s authorized_keys |
mod_sql, mod_ldap | [auth.jwt] or auth.authz_base_url |
Umask | the umask inherited at launch, for client files (Security) |
<Anonymous> | a dedicated account, with a read-only role |
With hidden_stores, the in-progress file is named as in ProFTPD, with
a token: .in.rapport.csv.<token>. (Atomic writes).
Translating <Limit> blocks
| FTP commands | Right |
|---|---|
RETR, READ | read |
STOR, APPE, MKD, WRITE | write |
LIST, NLST, CWD, DIRS | list |
DELE, RMD | delete |
RNFR, RNTO | rename |
There is no inheritance: the most specific entry decides alone, and a
read-only subtree (<Limit WRITE> DenyAll) restates everything still
allowed there, { path = "/archive", rights = ["read", "list"], recursive = true }.
A path that no entry covers is refused. See ACL.
The cutover
| Topic | CraftFileGate |
|---|---|
upload resume (AllowStoreRestart) | served, except with hidden_stores and on S3 |
| file ownership | the service account; any other ownership goes through the directory’s permissions (setgid, POSIX ACL) |
| FTP and FTPS | not served: each client moves to SFTP or to the REST API |
- Inventory the clients; extract names, hashes and homes from
ftpd.passwd. - Write
roles.tomlandusers.toml; launch on another port. - Validate with a test account (each right, one refusal), distribute the secrets.
- Shut down ProFTPD, switch the port, follow the audit trail.