Skip to content

Commit 2d6a1d4

Browse files
feat(sftp): add SFTPv3 protocol support
Added full SFTPv3 functionality. Added SFTP subsystem to the protocols crate, gated behind the new sftp cargo feature. The driver translates each SFTPv3 packet into an S3 call against the existing StorageBackend, so SFTP shares the same bucket layout, IAM, and lifecycle rules as the existing FTPS, WebDAV, and Swift drivers. SSH username maps to the IAM access key and SSH password to the secret key. Added russh 0.60 and russh-sftp 2.1 as workspace dependencies. A temporary [patch.crates-io] entry pins russh to a fork branch carrying the upstream fix at Eugeny/russh#702. The patch resolves an rsa 0.10.0-rc.18 vs pkcs5 prerelease conflict that no released russh version handles. It is removed once the russh PR merges and a release ships. Added 33 SFTPv3 compliance test cases (test_sftp_compliance_suite shared-session, test_sftp_compliance_readonly, test_sftp_compliance_standalone one-spawn-per-case) plus four regression-prevention layers guarding against silent feature deletion: compile-time module assertion, module-presence unit test, cross-module Protocol enum assertion, and end-to-end SSH banner test against the running binary. Refs rustfs#2478.
1 parent 03045ff commit 2d6a1d4

44 files changed

Lines changed: 16179 additions & 94 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2222
- XML-formatted error responses compatible with S3 API
2323
- Comprehensive integration documentation with manual testing guide
2424
- **32 unit and integration tests** covering middleware, auth handlers, task-local storage, and role detection
25+
- **SFTPv3 Protocol Support**: SSH-hosted SFTPv3 subsystem that translates each file operation into S3 calls against the local object store. Authentication uses IAM credentials (SSH username = access key, SSH password = secret key).
26+
- Full SFTPv3 packet coverage: open, read, write, stat, lstat, fstat, mkdir, rmdir, rename, remove, opendir, readdir, realpath, close, plus the rest of the 21-packet specification
27+
- Streaming multipart write up to S3's 5 TiB per-file ceiling
28+
- Per-handle read-ahead cache with configurable window size and process-wide memory ceiling
29+
- Per-session liveness watchdog: Linux probes `/proc/net/tcp` and cancels wedged sessions on the order of 45 seconds; non-Linux falls back to an inactivity ceiling on the order of 30 minutes
30+
- 30-second SSH handshake deadline, per-call backend operation timeout, bounded multipart-abort fan-out, graceful-shutdown cascade
31+
- 33 SFTPv3 compliance test cases under `crates/e2e_test/src/protocols/sftp_compliance.rs` spread across three entry points: `test_sftp_compliance_suite` (shared session), `test_sftp_compliance_readonly` (read-only mode), and `test_sftp_compliance_standalone` (one rustfs spawn per case)
32+
- Four-layer regression-prevention tests guard against silent feature deletion: compile-time module assertion, module-presence unit test, cross-module `Protocol` enum assertion, end-to-end SSH banner test against the running binary
2533

2634
### Changed
2735
- **HTTP Server Stack**: Integrated `KeystoneAuthLayer` middleware from `rustfs-keystone` crate into service stack (positioned after ReadinessGateLayer)
2836
- **IAMAuth**: Enhanced `get_secret_key()` to return empty secret for Keystone credentials (bypasses signature validation)
2937
- **Auth Module**: Modified `check_key_valid()` to retrieve Keystone credentials from task-local storage and determine admin status
38+
- **`StorageBackend` trait**: extended with multipart upload methods (`create_multipart_upload`, `upload_part`, `complete_multipart_upload`, `abort_multipart_upload`) plus `upload_part_copy`. Streaming-upload code path is now available to FTPS, WebDAV, and Swift drivers as well.
39+
- **`Protocol` enum**: new `Protocol::Sftp` variant with corresponding `S3Action` mappings. Every match arm on `Protocol` updated to handle the new variant exhaustively.
3040

3141
### Technical Details
3242
- Middleware is self-contained in `rustfs-keystone` crate following the trusted-proxies pattern for integration-specific middleware
@@ -35,12 +45,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3545
- Integration preserves existing S3 authentication flow while adding Keystone support
3646
- Zero breaking changes to existing functionality
3747
- No new top-level directories in main binary crate (middleware lives in integration crate)
48+
- SSH/SFTP wire handling via the `russh` and `russh-sftp` crates. SFTPv3 framing is implemented by `russh-sftp`; the rustfs-side `SftpDriver` implements `russh_sftp::server::Handler` and dispatches to the storage backend
49+
- Drop-time abort for in-flight multipart uploads honours IAM Deny on `AbortMultipartUpload`. `start_multipart_upload` caches the authorisation decision so the synchronous `Drop` path can honour Allow / Deny policies without re-querying IAM
50+
- Per-handle read cache uses an `Arc<AtomicU64>` shared across every `SftpDriver` instance to enforce a process-wide memory ceiling. On ceiling breach the populate is skipped and the read serves correctly via a single-call backend fetch
51+
- Per-session liveness watchdog runs as a tokio task per accepted connection. Reads `/proc/net/tcp` and `/proc/net/tcp6` to look up the (local, peer) tuple's TCP state and cancels via `tokio_util::sync::CancellationToken` when wedge conditions are confirmed across two consecutive ticks
52+
- Path canonicalisation rejects paths containing `\0`, `\r`, or `\n` and resolves traversal via `path::clean()` before any backend dispatch
53+
- Cipher / KEX / MAC / host-key algorithm allowlists are hardcoded with no environment override. Strict-KEX (CVE-2023-48795 / Terrapin) marker presence asserted by unit test
54+
- Per-session handle cap (default 64, configurable 8 to 1024) with UUID-generated handle ids
55+
- Crate-level `#![deny(unsafe_code)]` is in force across `crates/protocols`. Socket fd duplication for the watchdog uses the safe `AsFd::try_clone_to_owned` path (Linux/Unix); non-Unix falls back to the inactivity ceiling
56+
- `cfg(unix)` gating around platform-specific imports (`std::os::fd::AsFd`, `std::os::unix::fs::PermissionsExt`); non-Unix targets fail SFTP at config-load with `SftpInitError::UnsupportedPlatform`
3857

3958
### Documentation
4059
- Updated `crates/keystone/README.md` with complete integration architecture and workflow
4160
- Added detailed manual testing guide with 10 test scenarios
4261
- Updated main `README.md` to list Keystone authentication as available feature
4362
- Added troubleshooting section for common integration issues
63+
- Module-level rustdoc on `crates/protocols/src/sftp/mod.rs` describing the public API surface, configuration contract, and the architecture of the read cache and the wedge watchdog
4464

4565
### Configuration
4666
New environment variables:
@@ -54,6 +74,40 @@ New environment variables:
5474
- `RUSTFS_KEYSTONE_CACHE_SIZE` - Token cache size (default: 10000)
5575
- `RUSTFS_KEYSTONE_CACHE_TTL` - Token cache TTL in seconds (default: 300)
5676
- `RUSTFS_KEYSTONE_VERIFY_SSL` - Verify SSL certificates (default: true)
77+
- `RUSTFS_SFTP_ENABLE` - Enable/disable SFTP (default: false)
78+
- `RUSTFS_SFTP_ADDRESS` - Listen address (default: 0.0.0.0:2222)
79+
- `RUSTFS_SFTP_HOST_KEY_DIR` - Directory containing host key files (must exist; each file must be 0o600 or 0o400)
80+
- `RUSTFS_SFTP_IDLE_TIMEOUT` - Session idle timeout in seconds (default: 600)
81+
- `RUSTFS_SFTP_PART_SIZE` - Multipart part size in bytes (default: 16 MiB)
82+
- `RUSTFS_SFTP_READ_ONLY` - Reject write packets at the protocol layer (default: false)
83+
- `RUSTFS_SFTP_BANNER` - Optional SSH banner text
84+
- `RUSTFS_SFTP_HANDLES_PER_SESSION` - Per-session open-handle cap, 8 to 1024 (default: 64)
85+
- `RUSTFS_SFTP_BACKEND_OP_TIMEOUT_SECS` - Per-call backend deadline in seconds, 5 to 600 (default: 60)
86+
- `RUSTFS_SFTP_READ_CACHE_WINDOW_BYTES` - Per-handle read-cache window in bytes, 256 KiB to 64 MiB or 0 to disable (default: 4 MiB)
87+
- `RUSTFS_SFTP_READ_CACHE_TOTAL_MEM_BYTES` - Process-wide read-cache memory ceiling in bytes, 16 MiB minimum (default: 256 MiB)
88+
89+
### Files Added
90+
- `crates/protocols/src/sftp/mod.rs` - SFTP module entry point, public API surface, crate-level rustdoc, regression-prevention test
91+
- `crates/protocols/src/sftp/config.rs` - `SftpConfig` and `SftpInitError` types, env-var resolvers, host-key directory loader with permission enforcement
92+
- `crates/protocols/src/sftp/constants.rs` - Named constants grouped by purpose: S3 error codes, HTTP error codes, POSIX mode bits, protocol identifiers, operational limits
93+
- `crates/protocols/src/sftp/server.rs` - `SftpServer` SSH server, russh handler, password authentication against IAM, accept loop, per-session task spawn
94+
- `crates/protocols/src/sftp/driver.rs` - `SftpDriver` per-session SFTPv3 handler dispatching each operation onto the `StorageBackend`
95+
- `crates/protocols/src/sftp/state.rs` - `HandleState` variants for read, write-buffering, write-streaming, write-failed handles
96+
- `crates/protocols/src/sftp/lifecycle.rs` - Per-session activity stamp, weak-ref registry, `/proc/net/tcp` probe for the wedge watchdog
97+
- `crates/protocols/src/sftp/wedge_watchdog.rs` - Per-session liveness watchdog cancelling sessions silent at the SFTP layer while the kernel reports CLOSE_WAIT
98+
- `crates/protocols/src/sftp/read_cache.rs` - Per-handle in-memory read-ahead cache with shared atomic accumulator for the process-wide memory ceiling
99+
- `crates/protocols/src/sftp/attrs.rs` - SFTPv3 `FileAttributes` mapping for objects and directories, longname formatting, mtime clamping
100+
- `crates/protocols/src/sftp/dir.rs` - OPENDIR / READDIR pagination, root-bucket listing, sub-directory listing under a prefix
101+
- `crates/protocols/src/sftp/errors.rs` - `SftpError` thiserror enum and S3-error classification into SFTPv3 status codes
102+
- `crates/protocols/src/sftp/paths.rs` - Path canonicalisation, traversal rejection, `\0` / `\r` / `\n` rejection, bucket+key decomposition
103+
- `crates/protocols/src/sftp/read.rs` - READ packet handler, EOF semantics, `MAX_READ_LEN` bound, integration with the read cache
104+
- `crates/protocols/src/sftp/write.rs` - WRITE packet handler, in-memory buffering up to part size, transition to streaming multipart, CLOSE finalisation
105+
- `crates/protocols/src/sftp/test_support.rs` - Test fixtures and helper builders for SFTP unit tests
106+
- `crates/protocols/src/common/dummy_storage.rs` - In-memory `StorageBackend` test backend covering every method, used by SFTP unit tests and the FTPS / Swift / WebDAV test suites
107+
- `crates/e2e_test/src/protocols/sftp_core.rs` - End-to-end regressions for the handshake deadline, idle-timeout disconnect, and the wedge watchdog
108+
- `crates/e2e_test/src/protocols/sftp_compliance.rs` - SFTPv3 compliance suite entry points (`test_sftp_compliance_suite`, `test_sftp_compliance_readonly`, `test_sftp_compliance_standalone`)
109+
- `crates/e2e_test/src/protocols/sftp_compliance_tests.rs` - Per-case test bodies (CMPTST-01..33), shared fixture helpers, lifecycle counters
110+
- `crates/e2e_test/src/protocols/sftp_helpers.rs` - SFTP-specific test helpers and fixture seeders
57111

58112
### Files Modified
59113
- `crates/keystone/src/middleware.rs` - Created Keystone authentication middleware (self-contained in keystone crate)
@@ -63,14 +117,41 @@ New environment variables:
63117
- `rustfs/src/auth.rs` - Enhanced IAMAuth and check_key_valid for Keystone support, imported KEYSTONE_CREDENTIALS from rustfs-keystone
64118
- `crates/keystone/README.md` - Comprehensive integration documentation
65119
- `README.md` - Added Keystone as available feature
120+
- `Cargo.toml` - Added the `sftp` feature alongside the existing protocol features
121+
- `Cargo.lock` - Updated to include the new `russh`, `russh-sftp`, `socket2`, `tokio-util`, `subtle`, `uuid` dependencies and their transitive crates
122+
- `crates/protocols/Cargo.toml` - Declared `russh`, `russh-sftp`, `socket2`, `tokio-util`, `subtle`, `uuid` under the `sftp` feature flag
123+
- `crates/protocols/src/lib.rs` - Added `pub mod sftp` behind `#[cfg(feature = "sftp")]` plus the crate-level `#![deny(unsafe_code)]` lint
124+
- `crates/protocols/src/common/client/s3.rs` - Extended the `StorageBackend` trait with `create_multipart_upload`, `upload_part`, `complete_multipart_upload`, `abort_multipart_upload`, and `upload_part_copy`
125+
- `crates/protocols/src/common/session.rs` - Added the `Protocol::Sftp` variant and its `S3Action` mappings
126+
- `crates/protocols/src/common/gateway.rs` - Handles the new `Protocol::Sftp` variant exhaustively
127+
- `crates/protocols/src/common/mod.rs` - Exposed the new `dummy_storage` module
128+
- `crates/protocols/src/constants.rs` - Added shared POSIX mode-bit constants used by SFTP and other protocols
129+
- `crates/config/src/constants/protocols.rs` - `RUSTFS_SFTP_*` environment variable names and defaults
130+
- `crates/utils/src/retry.rs` - Added the generic exponential-backoff retry helper used by the SFTP write path
131+
- `crates/e2e_test/Cargo.toml` - Added the e2e test dependencies for SFTP (paramiko fixture, SSH keypair generation)
132+
- `crates/e2e_test/src/protocols/mod.rs` - Registered the new `sftp_core`, `sftp_compliance`, `sftp_compliance_tests`, and `sftp_helpers` modules
133+
- `crates/e2e_test/src/protocols/README.md` - Documented the SFTP test entry points and case index
134+
- `crates/e2e_test/src/protocols/test_env.rs` - Added SFTP host-key directory provisioning to the shared protocol test environment
135+
- `crates/e2e_test/src/protocols/test_runner.rs` - Wired the SFTP entry points into the runner
136+
- `rustfs/Cargo.toml` - Added the `sftp` feature flag
137+
- `rustfs/src/lib.rs` - One-line addition exporting the SFTP wiring
138+
- `rustfs/src/init.rs` - Build and start the `SftpServer` when `RUSTFS_SFTP_ENABLE` is true
139+
- `rustfs/src/main.rs` - Routed shutdown signals to the SFTP server alongside the other protocols
140+
- `rustfs/src/protocols/client.rs` - Client-builder support for the new `Protocol::Sftp` variant
66141

67142
### Testing
68143
- 16 unit tests in rustfs-keystone crate (config, auth, middleware, identity)
69144
- 10 integration tests in rustfs-keystone crate (task-local storage, middleware layer, scope isolation)
70145
- 6 auth unit tests in rustfs crate (role detection, task-local storage, Keystone credential handling)
71146
- **Total: 32 tests** passing with zero compilation errors
72147
- Manual testing guide provided for end-to-end validation
73-
- All tests passing with `cargo test --all --exclude e2e_test`
148+
- All Keystone tests passing with `cargo test --all --exclude e2e_test`
149+
- 33 SFTPv3 compliance test cases (CMPTST-01..33) split across three entry points: `test_sftp_compliance_suite` (shared session, cases 01-14), `test_sftp_compliance_readonly` (read-only mode, cases 15-23), `test_sftp_compliance_standalone` (one rustfs spawn per case, cases 24-33)
150+
- Regression-prevention tests at four layers: compile-time module assertion in `crates/protocols/src/lib.rs`, module-presence unit test in `crates/protocols/src/sftp/mod.rs`, cross-module `Protocol` enum assertion, and end-to-end SSH banner test against the running binary
151+
- Standalone end-to-end regressions for the SSH handshake deadline, the idle-timeout disconnect path, and the wedge watchdog (Linux fast-kill and the cross-platform fallback path)
152+
- Inline unit tests in every SFTP source file covering pure helpers (path canonicalisation, attribute mapping, S3-error classification, env-var bound resolvers)
153+
- Strict-KEX (CVE-2023-48795) marker presence assertion as a unit test in `crates/protocols/src/sftp/server.rs`
154+
- All tests passing with `cargo test --all --features sftp` against a 64-bit Linux target
74155

75156
---
76157

0 commit comments

Comments
 (0)