siftp

Quickstart

From nothing to FTP, FTPS, and SFTP transfers on loopback ports that need no privileges. Production installs (package, container, systemd) are in Operations.

1. Get the binary

Download a release for Linux x86_64 or aarch64 from Download, or build it:

cargo build --release -p siftpd     # Rust 1.89+; a C compiler for ring and the asm kernels
./target/release/siftpd --version

2. Write a config

Hash a password, then put it in siftp.toml:

./target/release/siftpd hash-password 'choose a strong password'
# $argon2id$v=19$m=65536,t=3,p=4$...
[server]
root = "/tmp/siftp-demo/srv"
user_store = "/tmp/siftp-demo/users.toml"

[ftp]
bind = "127.0.0.1:2121"
passive_port_range = [40000, 40100]

[ftps]
# FTPS stays off until these files exist (step 4).
cert_file = "/tmp/siftp-demo/tls.crt"
key_file = "/tmp/siftp-demo/tls.key"
implicit_bind = "127.0.0.1:2990"

[sftp]
bind = "127.0.0.1:2222"
host_key_file = "/tmp/siftp-demo/ssh_host_ed25519_key"

[admin]
bind = "127.0.0.1:8080"
token = "${ENV:SIFTP_ADMIN_TOKEN}"

[logging]
audit_file = "/tmp/siftp-demo/audit.jsonl"

[[users]]
name = "alice"
password_hash = "$argon2id$v=19$m=65536,t=3,p=4$...paste the hash..."
permissions = "rwdnml"

alice's root defaults to /tmp/siftp-demo/srv/alice and is created on first login. Check the file; problems come back as JSON naming the field:

export SIFTP_ADMIN_TOKEN=$(openssl rand -hex 32)
./target/release/siftpd --check-config --config siftp.toml

3. Run and transfer

./target/release/siftpd --config siftp.toml

In another terminal:

echo hello > hello.txt
curl -T hello.txt ftp://alice:'choose a strong password'@127.0.0.1:2121/
curl ftp://alice:'choose a strong password'@127.0.0.1:2121/hello.txt
sftp -P 2222 alice@127.0.0.1                                  # interactive

For SFTP in a script, pin the host key instead of trusting the first connection. The fingerprint comes from the key file:

fp=$(ssh-keygen -lf /tmp/siftp-demo/ssh_host_ed25519_key | awk '{print $2}' | cut -d: -f2)
curl --hostpubsha256 "$fp" -u alice:'choose a strong password' \
  sftp://127.0.0.1:2222/hello.txt

4. Add FTPS

Put a PEM certificate and key at the paths in [ftps] and restart. For a local test, a self-signed pair:

openssl req -x509 -newkey rsa:2048 -nodes -days 30 -subj /CN=localhost \
  -keyout /tmp/siftp-demo/tls.key -out /tmp/siftp-demo/tls.crt

Explicit FTPS uses the FTP port (curl --ssl-reqd ftp://...), implicit FTPS the implicit_bind port (curl ftps://...). Set require_tls = true under [ftp] to refuse plaintext logins. A new certificate is picked up on kill -HUP without a restart.

5. Manage users through the API

curl -H "Authorization: Bearer $SIFTP_ADMIN_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"name":"bob","password":"bob-password","permissions":"rwl"}' \
  http://127.0.0.1:8080/api/v1/users
curl -H "Authorization: Bearer $SIFTP_ADMIN_TOKEN" http://127.0.0.1:8080/api/v1/users

Without permissions, a user gets the union of their groups' permissions, or rl (read and list) if they have no groups. API users are written to user_store and survive restarts. Users in the config file stay read-only through the API.

Next