mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-26 08:18:18 +00:00
b0c6c4cbce
* fix(filemeta): add state-aware file info validation
* fix(filemeta): validate shard arithmetic and delete paths
* fix(ecstore): add fallible erasure construction
* fix(ecstore): resolve storage parity per pool
* fix(storage): report heterogeneous erasure layouts
* fix(admin): publish prepared storage config atomically
* fix(storage): harden per-pool parity boundaries
* fix(storage): address pre-PR validation findings
* test(ci): fix strict-topology validation fixtures
* fix(heal): preserve delete markers during repair
* refactor(filemeta): drop unused ValidatedFileInfo witness
ValidatedFileInfo wrapped an unread `_file_info` reference alongside an `Option<ValidatedErasureLayout>`, but only the layout was ever consumed. Return the layout directly from `FileInfo::validate` so the sole production consumer (`LocalDisk::check_parts`) and the two unit tests read it without the extra witness type and lifetime.
No behavior change.
* fix(filemeta): keep compressed and MinIO-migrated tiered objects readable
The new decode-path validation rejected several legitimate on-disk shapes that older RustFS and MinIO-migrated data carry, turning readable objects into FileCorrupt:
- Compressed objects written with an unknown upload size persist a negative per-part actual_size (the documented "unknown size" sentinel that ObjectInfo::get_actual_size already tolerates). validate_collection_contents rejected it via usize::try_from; now a negative actual_size skips shard validation and only real, non-negative sizes are checked.
- MinIO-migrated objects transitioned to a versioned remote tier store the tier version id as a UUID string, not 16 raw bytes. MetaObject::into_fileinfo returned FileCorrupt (main tolerated it as None), making all versions of the object unreadable; MetaDeleteMarker free-version records took a Some(nil) sentinel path with the same effect, which also breaks free-version expiry (remote-tier leak). Both now decode through a shared transitioned_version_id_from_meta_sys helper: 16 raw bytes or a UUID string are accepted, anything else is tolerated as None instead of failing the read.
Regression tests updated to assert the readable/compat behavior, with new tests covering MinIO string-form recovery.
* fix(scanner): build the delete-marker test fixture without erasure geometry
get_size_counts_delete_markers_separately_from_versions built its delete marker with `FileInfo::new(object, 1, 1)`, which attaches erasure geometry (data=1/parity=1/distribution). This PR classifies versions by shape via `is_storage_delete_marker()` (no geometry) rather than the raw `deleted` flag, so a geometry-bearing "delete marker" is correctly serialized as a purge-pending payload Object and counted as a version — CI saw summary.versions=3, expected 2.
Real delete markers carry no erasure geometry (delete paths build them as `FileInfo { deleted: true, ..Default::default() }`), so construct the fixture the same way. It then classifies as a storage delete marker and the counts (versions=2, delete_markers=1) hold. This keeps the PR's more-correct classification, which prevents a purge-pending object's geometry from being dropped when serialized as a bare delete marker.
* docs(changelog): note per-pool parity fix and storage-class startup upgrade caveat
Records the #4801 per-pool erasure parity fix under Fixed, and documents the upgrade behavior where a persisted storage class that a small or heterogeneous pool cannot satisfy now fails startup — with the RUSTFS_STORAGE_CLASS_STANDARD recovery steps. Docs-only; covers R4 from the on-disk compatibility audit.
* fix(heal): report parity from erasure geometry, not is_valid()
heal_object set HealResultItem.parity_blocks via `if lfi.is_valid()`, which was missed by the migration of the other quorum/metadata predicates. With the new `is_valid()` semantics (full payload validation; delete markers now return false), a delete marker or a geometry-bearing version with a benign collection quirk would misreport parity as the pool default instead of its own. Use `has_valid_erasure_geometry()` — the narrow "does this carry erasure geometry" predicate the rest of the migration uses — so reporting matches the object's actual layout. Reporting-only; no data-path change.
* fix(filemeta): do not silently serialize a non-canonical deleted FileInfo as an Object
`From<FileInfo> for FileMetaVersion` classifies by `is_storage_delete_marker()` (shape), which correctly routes canonical delete markers to Delete and purge-pending payloads (deleted=true with real erasure geometry) to Object. But a `deleted` FileInfo that is neither a canonical marker nor a valid erasure payload would silently serialize as a zero-geometry MetaObject that later fails `validate_for_metadata_read`. Write paths validate first (`validate_for_erasure_write` / `validate_for_metadata_read`), so this is a caller bug; `From` is infallible, so surface it with a structured `warn!` on the malformed branch instead of writing corrupt metadata silently. Legitimate purge-pending objects (valid geometry) are unaffected — the guard only fires for `deleted && !has_valid_erasure_geometry()`.
* test(filemeta): assert real historical xl.meta versions pass metadata-read validation
Empirical companion to the code-reasoned decode-tolerance invariants (docs/architecture/erasure-coding.md §11) and the rolling-upgrade / MinIO-migration compatibility concern: the tightened `validate_for_metadata_read` runs on every local disk read and peer-RPC-decoded FileInfo, so it must accept every version of real historically-written xl.meta, never reject it as FileCorrupt.
Loads five real fixtures — MinIO small-inline, MinIO versioned (two object versions + a delete marker), MinIO large multipart, a legacy V1 (xl.json-derived) object, and a legacy meta_ver 2 object — decodes every version with parts materialized, and asserts validate_for_metadata_read() is Ok for each. Reverting the tolerant handling (delete-marker shape, legacy per-part checksums, string/short transitioned-versionID, negative actual_size) turns this red.
* fix(ci): remove duplicate storage test re-exports
---------
Co-authored-by: overtrue <anzhengchao@gmail.com>
19 KiB
19 KiB
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
Fixed
- Helm Ingress:
customAnnotationsare now merged with class-specific annotations (nginx/traefik) instead of being ignored wheningress.classNameis set. - Per-pool erasure parity: Erasure parity (STANDARD and reduced-redundancy) is now resolved independently for every pool instead of reusing the first pool's value. A heterogeneous topology — for example a 4-drive pool plus a 2-drive pool created during expansion — previously inherited the first pool's parity and could resolve to zero data shards in the smaller pool, panicking Reed-Solomon construction on write. Automatic parity now resolves per pool (for example
2+2in the 4-drive pool and1+1in the 2-drive pool). Fixes #4801.
Added
- NATS JetStream Publish Path: Opt-in at-least-once delivery for the NATS notify and audit targets. A NATS Core publish flushes to the connection without awaiting a broker acknowledgement, so an event can be lost across a broker restart or a reconnect after the send queue has already cleared it. A queued event now clears only after the JetStream
PublishAck, so bucket notifications survive those interruptions. Off by default and byte-identical to the NATS Core path when disabled.- Three configuration keys per target:
JETSTREAM_ENABLE,JETSTREAM_STREAM_NAME, andJETSTREAM_ACK_TIMEOUT_SECS, under theRUSTFS_NOTIFY_NATS_andRUSTFS_AUDIT_NATS_prefixes - Durable store-and-forward with a stable dedup id sent as the
Nats-Msg-Idheader, so a replay after a crash is collapsed by the server duplicate window - Pre-flight stream validation, and a bounded failed-events store (count and TTL). Only a non-retryable rejection is recorded in the failed-events store. A retryable condition keeps the entry on the live queue until it is delivered
- Operator guide at
docs/operations/nats-jetstream.md
- Three configuration keys per target:
- OpenStack Keystone Authentication Integration: Full support for OpenStack Keystone authentication via X-Auth-Token headers
- Tower-based middleware (
KeystoneAuthLayer) self-contained withinrustfs-keystonecrate - Task-local storage for async-safe credential passing between middleware and auth handlers
- Automatic detection of Keystone credentials (access keys prefixed with
keystone:) - Role-based permission mapping (admin/reseller_admin roles grant owner permissions)
- Token caching for high-performance validation with configurable cache size and TTL
- Dual authentication support: Keystone and standard AWS Signature v4 work simultaneously
- Immediate 401 response for invalid tokens (no fallback to local auth)
- XML-formatted error responses compatible with S3 API
- Comprehensive integration documentation with manual testing guide
- 32 unit and integration tests covering middleware, auth handlers, task-local storage, and role detection
- Tower-based middleware (
- 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).
- 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
- Streaming multipart write up to the part size times 10000 parts (156.25 GiB at the default part size)
- Per-handle read-ahead cache with configurable window size and process-wide memory ceiling
- Per-session liveness watchdog: Linux probes
/proc/net/tcpand cancels wedged sessions on the order of 45 seconds; non-Linux falls back to an inactivity ceiling on the order of 30 minutes - 30-second SSH handshake deadline, per-call backend operation timeout, bounded multipart-abort fan-out, graceful-shutdown cascade
- 34 SFTPv3 compliance test cases under
crates/e2e_test/src/protocols/sftp_compliance.rsspread across three entry points:test_sftp_compliance_suite(shared session),test_sftp_compliance_readonly(read-only mode), andtest_sftp_compliance_standalone(one rustfs spawn per case) - Four-layer regression-prevention tests guard against silent feature deletion: compile-time module assertion, module-presence unit test, cross-module
Protocolenum assertion, end-to-end SSH banner test against the running binary
Changed
- HTTP Server Stack: Integrated
KeystoneAuthLayermiddleware fromrustfs-keystonecrate into service stack (positioned after ReadinessGateLayer) - Storage-class validation on startup (upgrade note): A persisted explicit storage class (
RUSTFS_STORAGE_CLASS_STANDARD/RUSTFS_STORAGE_CLASS_RRS, for exampleEC:2) is now validated against the actual per-pool drive counts at startup and rejected when a pool cannot satisfy it. This is fail-closed and correct, but a cluster that persisted a storage class larger than a small or heterogeneous pool can hold (for exampleEC:2alongside a 2-drive pool), which earlier releases accepted and silently resolved to an invalid layout, will now refuse to start after upgrade. To recover, unsetRUSTFS_STORAGE_CLASS_STANDARDso the server derives a valid per-pool default automatically, or set it to a value every pool can satisfy. - IAMAuth: Enhanced
get_secret_key()to return empty secret for Keystone credentials (bypasses signature validation) - Auth Module: Modified
check_key_valid()to retrieve Keystone credentials from task-local storage and determine admin status StorageBackendtrait: extended with multipart upload methods (create_multipart_upload,upload_part,complete_multipart_upload,abort_multipart_upload) plusupload_part_copy. Streaming-upload code path is now available to FTPS, WebDAV, and Swift drivers as well.Protocolenum: newProtocol::Sftpvariant with correspondingS3Actionmappings. Every match arm onProtocolupdated to handle the new variant exhaustively.
Technical Details
- Middleware is self-contained in
rustfs-keystonecrate following the trusted-proxies pattern for integration-specific middleware - Uses
BoxBodypattern for Hyper 1.x compatibility - Task-local storage provides request-scoped credential passing without modifying HTTP request/response types
- Integration preserves existing S3 authentication flow while adding Keystone support
- Zero breaking changes to existing functionality
- No new top-level directories in main binary crate (middleware lives in integration crate)
- SSH/SFTP wire handling via the
russhandrussh-sftpcrates. SFTPv3 framing is implemented byrussh-sftp; the rustfs-sideSftpDriverimplementsrussh_sftp::server::Handlerand dispatches to the storage backend - Drop-time abort for in-flight multipart uploads honours IAM Deny on
AbortMultipartUpload.start_multipart_uploadcaches the authorisation decision so the synchronousDroppath can honour Allow / Deny policies without re-querying IAM - Per-handle read cache uses an
Arc<AtomicU64>shared across everySftpDriverinstance 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 - Per-session liveness watchdog runs as a tokio task per accepted connection. Reads
/proc/net/tcpand/proc/net/tcp6to look up the (local, peer) tuple's TCP state and cancels viatokio_util::sync::CancellationTokenwhen wedge conditions are confirmed across two consecutive ticks - Path canonicalisation rejects paths containing
\0,\r, or\nand resolves traversal viapath::clean()before any backend dispatch - 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
- Per-session handle cap (default 64, configurable 8 to 1024) with UUID-generated handle ids
- Crate-level
#![deny(unsafe_code)]is in force acrosscrates/protocols. Socket fd duplication for the watchdog uses the safeAsFd::try_clone_to_ownedpath (Linux). Non-Linux targets use the inactivity-ceiling watchdog - Platform-specific imports are cfg-gated. Unix enforces owner-only host-key mode bits (no group or other permission bits). Windows loads host keys without a mode check and trusts operator-managed NTFS ACLs. Targets that are neither Unix nor Windows fail SFTP at config-load with SftpInitError::UnsupportedPlatform
Documentation
- Updated
crates/keystone/README.mdwith complete integration architecture and workflow - Added detailed manual testing guide with 10 test scenarios
- Updated main
README.mdto list Keystone authentication as available feature - Added troubleshooting section for common integration issues
- Module-level rustdoc on
crates/protocols/src/sftp/mod.rsdescribing the public API surface, configuration contract, and the architecture of the read cache and the wedge watchdog
Configuration
New environment variables:
RUSTFS_KEYSTONE_ENABLE- Enable/disable Keystone authentication (default: false)RUSTFS_KEYSTONE_AUTH_URL- Keystone API endpoint URLRUSTFS_KEYSTONE_VERSION- Keystone API version (v3)RUSTFS_KEYSTONE_ADMIN_USER- Admin username for privileged operationsRUSTFS_KEYSTONE_ADMIN_PASSWORD- Admin passwordRUSTFS_KEYSTONE_ADMIN_PROJECT- Admin project nameRUSTFS_KEYSTONE_ADMIN_DOMAIN- Admin domain name (default: Default)RUSTFS_KEYSTONE_CACHE_SIZE- Token cache size (default: 10000)RUSTFS_KEYSTONE_CACHE_TTL- Token cache TTL in seconds (default: 300)RUSTFS_KEYSTONE_VERIFY_SSL- Verify SSL certificates (default: true)RUSTFS_SFTP_ENABLE- Enable/disable SFTP (default: false)RUSTFS_SFTP_ADDRESS- Listen address (default: 0.0.0.0:2222)RUSTFS_SFTP_HOST_KEY_DIR- Directory containing host key files (must exist). On Unix each file must grant no group or other permission bits (owner access only). On Windows the files load without a mode check and rustfs trusts the directory NTFS ACLRUSTFS_SFTP_HOST_KEY_RELOAD_ENABLE- Rescan the host-key directory without a restart (default: false)RUSTFS_SFTP_HOST_KEY_RELOAD_INTERVAL- Host-key rescan interval in seconds, minimum 5 (default: 30)RUSTFS_SFTP_IDLE_TIMEOUT- Session idle timeout in seconds (default: 600)RUSTFS_SFTP_PART_SIZE- Multipart part size in bytes (default: 16 MiB)RUSTFS_SFTP_READ_ONLY- Reject write packets at the protocol layer (default: false)RUSTFS_SFTP_BANNER- SSH protocol identification string, must begin withSSH-2.0-(default:SSH-2.0-RustFS)RUSTFS_SFTP_HANDLES_PER_SESSION- Per-session open-handle cap, 8 to 1024 (default: 64)RUSTFS_SFTP_BACKEND_OP_TIMEOUT_SECS- Per-call backend deadline in seconds, 5 to 600 (default: 60)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)RUSTFS_SFTP_READ_CACHE_TOTAL_MEM_BYTES- Process-wide read-cache memory ceiling in bytes, 16 MiB minimum (default: 256 MiB)
Files Added
crates/protocols/src/sftp/mod.rs- SFTP module entry point, public API surface, crate-level rustdoc, regression-prevention testcrates/protocols/src/sftp/config.rs-SftpConfigandSftpInitErrortypes, env-var resolvers, host-key directory loader with permission enforcementcrates/protocols/src/sftp/constants.rs- Named constants grouped by purpose: S3 error codes, HTTP error codes, POSIX mode bits, protocol identifiers, operational limitscrates/protocols/src/sftp/server.rs-SftpServerSSH server, russh handler, password authentication against IAM, accept loop, per-session task spawncrates/protocols/src/sftp/driver.rs-SftpDriverper-session SFTPv3 handler dispatching each operation onto theStorageBackendcrates/protocols/src/sftp/state.rs-HandleStatevariants for read, write-buffering, write-streaming, write-failed handlescrates/protocols/src/sftp/lifecycle.rs- Per-session activity stamp, weak-ref registry,/proc/net/tcpprobe for the wedge watchdogcrates/protocols/src/sftp/wedge_watchdog.rs- Per-session liveness watchdog cancelling sessions silent at the SFTP layer while the kernel reports CLOSE_WAITcrates/protocols/src/sftp/fallback_watchdog.rs- Per-session silence-only liveness backstop for non-Linux targets, cancelling sessions only at the fallback idle ceilingcrates/protocols/src/sftp/read_cache.rs- Per-handle in-memory read-ahead cache with shared atomic accumulator for the process-wide memory ceilingcrates/protocols/src/sftp/attrs.rs- SFTPv3FileAttributesmapping for objects and directories, longname formatting, mtime clampingcrates/protocols/src/sftp/dir.rs- OPENDIR / READDIR pagination, root-bucket listing, sub-directory listing under a prefixcrates/protocols/src/sftp/errors.rs-SftpErrorthiserror enum and S3-error classification into SFTPv3 status codescrates/protocols/src/sftp/paths.rs- Path canonicalisation, traversal rejection,\0/\r/\nrejection, bucket+key decompositioncrates/protocols/src/sftp/read.rs- READ packet handler, EOF semantics,MAX_READ_LENbound, integration with the read cachecrates/protocols/src/sftp/write.rs- WRITE packet handler, in-memory buffering up to part size, transition to streaming multipart, CLOSE finalisationcrates/protocols/src/sftp/test_support.rs- Test fixtures and helper builders for SFTP unit testscrates/protocols/src/common/dummy_storage.rs- In-memoryStorageBackendtest backend covering every method, used by SFTP unit tests and the FTPS / Swift / WebDAV test suitescrates/e2e_test/src/protocols/sftp_core.rs- End-to-end regressions for the handshake deadline, idle-timeout disconnect, and the wedge watchdogcrates/e2e_test/src/protocols/sftp_compliance.rs- SFTPv3 compliance suite entry points (test_sftp_compliance_suite,test_sftp_compliance_readonly,test_sftp_compliance_standalone)crates/e2e_test/src/protocols/sftp_compliance_tests.rs- Per-case test bodies (CMPTST-01..34), shared fixture helpers, lifecycle counterscrates/e2e_test/src/protocols/sftp_helpers.rs- SFTP-specific test helpers and fixture seeders
Files Modified
crates/keystone/src/middleware.rs- Created Keystone authentication middleware (self-contained in keystone crate)crates/keystone/src/lib.rs- Exported middleware module and KEYSTONE_CREDENTIALScrates/keystone/Cargo.toml- Added Tower/HTTP dependencies for middleware functionalityrustfs/src/server/http.rs- Integrated KeystoneAuthLayer from rustfs-keystone craterustfs/src/auth.rs- Enhanced IAMAuth and check_key_valid for Keystone support, imported KEYSTONE_CREDENTIALS from rustfs-keystonecrates/keystone/README.md- Comprehensive integration documentationREADME.md- Added Keystone as available featureCargo.toml- Added thesftpfeature alongside the existing protocol featuresCargo.lock- Updated to include the newrussh,russh-sftp,socket2,tokio-util,subtle,uuiddependencies and their transitive cratescrates/protocols/Cargo.toml- Declaredrussh,russh-sftp,socket2,tokio-util,subtle,uuidunder thesftpfeature flagcrates/protocols/src/lib.rs- Addedpub mod sftpbehind#[cfg(feature = "sftp")]plus the crate-level#![deny(unsafe_code)]lintcrates/protocols/src/common/client/s3.rs- Extended theStorageBackendtrait withcreate_multipart_upload,upload_part,complete_multipart_upload,abort_multipart_upload, andupload_part_copycrates/protocols/src/common/session.rs- Added theProtocol::Sftpvariant and itsS3Actionmappingscrates/protocols/src/common/gateway.rs- Handles the newProtocol::Sftpvariant exhaustivelycrates/protocols/src/common/mod.rs- Exposed the newdummy_storagemodulecrates/protocols/src/constants.rs- Added shared POSIX mode-bit constants used by SFTP and other protocolscrates/config/src/constants/protocols.rs-RUSTFS_SFTP_*environment variable names and defaultscrates/utils/src/retry.rs- Added the generic exponential-backoff retry helper used by the SFTP write pathcrates/e2e_test/Cargo.toml- Added the e2e test dependencies for SFTP (paramiko fixture, SSH keypair generation)crates/e2e_test/src/protocols/mod.rs- Registered the newsftp_core,sftp_compliance,sftp_compliance_tests, andsftp_helpersmodulescrates/e2e_test/src/protocols/README.md- Documented the SFTP test entry points and case indexcrates/e2e_test/src/protocols/test_env.rs- Added SFTP host-key directory provisioning to the shared protocol test environmentcrates/e2e_test/src/protocols/test_runner.rs- Wired the SFTP entry points into the runnerrustfs/Cargo.toml- Added thesftpfeature flagrustfs/src/lib.rs- One-line addition exporting the SFTP wiringrustfs/src/init.rs- Build and start theSftpServerwhenRUSTFS_SFTP_ENABLEis truerustfs/src/main.rs- Routed shutdown signals to the SFTP server alongside the other protocolsrustfs/src/protocols/client.rs- Client-builder support for the newProtocol::Sftpvariant
Testing
- 16 unit tests in rustfs-keystone crate (config, auth, middleware, identity)
- 10 integration tests in rustfs-keystone crate (task-local storage, middleware layer, scope isolation)
- 6 auth unit tests in rustfs crate (role detection, task-local storage, Keystone credential handling)
- Total: 32 tests passing with zero compilation errors
- Manual testing guide provided for end-to-end validation
- All Keystone tests passing with
cargo test --all --exclude e2e_test - 34 SFTPv3 compliance test cases (CMPTST-01..34) 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-34) - Regression-prevention tests at four layers: compile-time module assertion in
crates/protocols/src/lib.rs, module-presence unit test incrates/protocols/src/sftp/mod.rs, cross-moduleProtocolenum assertion, and end-to-end SSH banner test against the running binary - 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)
- Inline unit tests in every SFTP source file covering pure helpers (path canonicalisation, attribute mapping, S3-error classification, env-var bound resolvers)
- Strict-KEX (CVE-2023-48795) marker presence assertion as a unit test in
crates/protocols/src/sftp/server.rs - All tests passing with
cargo test --all --features sftpagainst a 64-bit Linux target
Previous Releases
See GitHub Releases for previous version history.