Skip to content

v0.3.0

Choose a tag to compare

@o-kolomoiets o-kolomoiets released this 09 Oct 21:22
· 166 commits to main since this release
2bd0b1f

Hardening for servers exposed to the internet. Read
docs/security/hardening.md before upgrading a
public server; two changes need attention: serve refuses to run as root
without --allow-root, and uploads stop below 1 GiB of free space unless
min_free_space says otherwise.

Added

  • Connection limits checked right after accept, before the SSH handshake:
    limits.max_connections (256), max_connections_per_ip (16, per IPv4
    address or IPv6 /64) and max_preauth_connections (64). Refused
    connections are audited as conn.reject, at most 10 per second.
  • Bans ([auth.ban]): a source with 10 failures within 10 minutes is
    refused at accept for 30 minutes, and its open connections get no further
    password checks. Every wrong password is a failure at once; rejected keys
    count once per connection, not per offered key; loopback is exempt by
    default; the table is bounded. New audit event auth.ban.
  • server.idle_timeout (15m) closes connections without SFTP traffic;
    server.keepalive_interval (30s) closes connections that leave 3
    keepalive@openssh.com requests unanswered. conn.close reports
    idle_timeout or keepalive_timeout.
  • Opt-in password login: auth.methods = ["publickey", "password"] and
    password_hash (argon2id; bcrypt accepted for imported accounts). Each
    attempt checks one hash; unknown users are checked against a stand-in of
    the costliest configured hash, and failures wait until it would have
    finished, so the response time does not reveal which users exist (fully
    with one kind of hash; config validate warns about mixed ones). Checks
    run at most one per available CPU. A method missing from auth.methods is
    refused, and its keys or passwords are not loaded. gosftpd user hash-password [--stdin] prints a hash, and
    user add --password-hash adds it to a user.
  • server.crypto_policy: modern (default, as before) or compat, which
    adds NIST curve and SHA-2 Diffie-Hellman key exchange and non-ETM MACs for
    old clients.
  • on_conflict = "version": an upload or posix-rename onto an existing file
    replaces it and moves the old file to
    .versions/<path>/<stem>.<UTC time><ext>, keeping versions.keep (10)
    versions per file for at most versions.max_age (30 days); only users who
    may delete or overwrite files make older versions drop out. The versions
    directory is not listed (sync tools would delete it); clients can open it
    by path and read it, but not change it. Replacing a file this way
    needs only the write permission. fs.upload and fs.rename get
    conflict = "versioned" and version_path.
  • atomic_uploads: uploads are written to a hidden temporary file
    (.gosftpd-*.part) and take their name only when closed, by the conflict
    policy at that moment; an aborted upload leaves nothing. Resume is not
    possible then. fsync flushes such uploads before they are published.
    Temporary files left by a crash are removed once 24 hours old (checked at
    start and every 6 hours) and when their directory is removed.
  • max_file_size (off by default), checked against the end of every write
    and on truncation, and min_free_space (1 GiB), checked when a file is
    opened for writing. Sizes accept units such as "10GiB".
  • Fuzzing: six targets (path resolution, confinement on a tree with
    symlinks, conflict copy names, the configuration, authorized_keys, and
    arbitrary SFTP packets against the real handlers) run 60 seconds each on
    every change and 10 minutes each night.
  • CI checks coverage floors of the security packages (unit and interop
    coverage merged), audits crypto_policy = "modern" with ssh-audit, and
    runs OpenSSF Scorecard weekly; all actions are pinned by commit SHA.
  • Documents: threat model,
    hardening guide, a manual WinSCP checklist,
    and ADR 0004 on
    limits@openssh.com.

Changed

  • BREAKING: gosftpd serve refuses to run as root unless --allow-root
    is given.
  • A connection keeps the user name of its first authentication request, as
    sshd does; attempts with another name are refused.
  • auth.success has key_fp only for public-key logins.
  • Uploads are refused while the mount's filesystem has less than 1 GiB free
    (min_free_space; "0" turns the check off).
  • fs.rename for a posix-rename that replaced its target under
    on_conflict = "overwrite" reports conflict = "overwritten" instead of
    "none".
  • Names starting with .gosftpd- (in any case) are reserved: hidden from
    listings and refused in paths.
  • BREAKING: rename_template must contain {n} exactly once and be
    valid UTF-8 of at most 64 bytes, so that copy names always fit 255 bytes;
    config validate names a template that does not.

Fixed

  • A client that disconnected right after logging in left no auth.success
    and conn.close events.
  • A client that sent requests with a guessed handle while an OPEN was in
    progress raced with the OPEN inside pkg/sftp, which could crash the
    server; gosftpd now passes an OPEN on only after the earlier requests are
    answered, and requests with a handle only after the OPEN is (found by
    fuzzing).
  • Copy names under on_conflict = "rename" could exceed 255 bytes for
    names with a very long extension; a shortened name could equal the
    original.
  • A configuration file whose first line ended inside an escape sequence
    (a = "C:\) made serve and config validate panic instead of
    reporting the error (found by fuzzing).