Commit Graph

1923 Commits

Author SHA1 Message Date
Zhengchao An 2bf2ce1ff2 feat(admin): apply the requested key listing filters (#5608) 2026-08-02 14:03:57 +08:00
cxymds c1955a8498 fix(replication): harden live delete admission (#5599) 2026-08-02 12:52:11 +08:00
Zhengchao An 8e3e552576 fix(s3): strip every encryption marker from a copy destination (#5597)
fix(sse): strip inherited SSE key-id and algorithm on copy

strip_managed_encryption_metadata cleared the MinIO spellings of the
managed-SSE key id and seal algorithm but not their RustFS-native
counterparts, so a CopyObject destination kept the source object's
x-rustfs-encryption-key-id and x-rustfs-encryption-algorithm.

When the destination resolves to no server-side encryption, nothing
rewrites those keys. is_object_encryption_marker treats any remaining
x-rustfs-encryption-* key as proof the payload is encrypted, so the
plaintext destination reports ObjectInfo::is_encrypted, the reader takes
its encrypted branch, and the read fails closed with "encrypted object
metadata is incomplete" because the actual key material was stripped.

Add both constants to the strip list so a destination inherits no
encryption marker it has no material for.
2026-08-02 10:27:23 +08:00
Zhengchao An 557a616ae6 feat(kms): report the configuration references that block a key deletion (#5598)
* feat(kms): report configuration references that block a key deletion

Adds a KeyImpactReport that states which configuration still points at a
key, how exhaustively the sources were read, and which sources were not
consulted at all. The report deliberately carries no in-use or
safe-to-delete claim: it covers the configuration layer only, so an empty
reference list means nothing was found in the scanned sources, never that
the key is unreferenced.

Immediate deletion destroys key material without ever reaching the
deletion worker, so it never passed the worker's reference gate. The
manager now consults the same checker on that path and refuses with a
typed KeyStillReferenced error. This only ever adds a refusal; the
scheduled deletion path and the worker's blocking behaviour are
unchanged.

* test(kms): cover the immediate-deletion reference refusal

* feat(kms): surface configuration references on the admin key endpoints

DeleteKey and DescribeKey now return an impact section listing the
configuration that points at the key, so an operator scheduling a
deletion sees what will refuse to destroy the material instead of
learning it from a server-side log once the window has run out.

The section is reported, never acted on: scheduling still succeeds while
references exist, and the deletion worker's gate remains the only thing
that decides whether material is destroyed. An immediate deletion that
the manager refuses for an outstanding reference now answers 409.

* test(kms): pin the impact wire shape and the unreferenced force-delete path

* fix(kms): make the DescribeKey impact section opt-in

Collecting the section lists every bucket, and DescribeKey is polled, so
carrying that fan-out on the default read path trades a hot path's cost
for a diagnostic. It is now collected only for impact=true; without the
parameter the endpoint does exactly the work it did before and returns
no impact field.

A value that is neither true nor false is refused rather than read as
off, so a typo cannot answer a request for the section with a response
that merely lacks one. DeleteKey still reports unconditionally: that is
the request whose consequences the caller cannot otherwise see, and it
is not polled.

* fix(kms): box the query-parse refusal now that responses carry impact

The delete response grew an impact section, which pushed it past the
size clippy accepts inline in a Result. It is a full response body
rather than an error code, so it is boxed at the one place that returns
it as an error; the wire shape and the public field type are unchanged.
2026-08-02 08:37:37 +08:00
Zhengchao An 7528a0b916 feat(kms): accept the AWS backend through KMS configuration (#5592)
* feat(kms): accept the AWS backend through KMS configuration

The AWS KMS backend could be constructed but not selected: the admin
configure API had no AWS variant and startup rejected the backend name.

The configure request pins the region rather than defaulting it, because
that configuration is persisted once and replayed on every node: leaving
the region to each node's ambient provider chain would let nodes address
different regions, and therefore different keys, while reporting an
identical configuration. The request accepts no credential fields, so
credentials stay with the aws-config provider chain on each node, and
`deny_unknown_fields` refuses attempts to submit them anyway.

* test(kms): cover AWS backend selection through the service manager

An end-to-end check that an admin configure request selects the AWS
backend, builds a client, and passes the startup health check. Marked
#[ignore]: it needs real AWS credentials, though it creates no key and
is therefore not billable on its own.
2026-08-02 03:58:20 +08:00
Zhengchao An f1a85c6a93 fix(admin): refuse immediate KMS key deletion through the query string (#5589)
fix(admin): retire the query-string form of immediate KMS key deletion

Immediate deletion destroys master key material outright, and every
object encrypted under that key becomes permanently unreadable. The
delete endpoint accepted that request as a query parameter, which is the
form most easily issued by accident and the one that made the waiting
window bypassable.

The query string can now only schedule a deletion: `force_immediate`
with any value other than `false`, or a `confirm_key_id` parameter, is
refused with 400 rather than downgraded to a scheduled deletion, so a
caller cannot read the answer as "destroyed". The JSON body form is
unchanged and remains the single way to reach the service gate that
enforces the server opt-in and the echoed confirmation.

Classify the route accordingly: `RouteRiskLevel` gains `Critical` for
routes whose worst case is permanent loss of user data, and the KMS key
deletion route is the only member, pinned in both directions by a matrix
test. Endpoint-level coverage for the 7-30 day window bound is added for
every configured backend.

Refs rustfs/backlog#1585 (part of rustfs/backlog#1562)
2026-08-02 03:46:11 +08:00
houseme fbec33bd29 Expose target-scoped durable MRF backlog metrics (#5584)
* feat(replication): expose target durable mrf backlog

Add target ARN attribution to durable MRF entries and surface target-scoped durable backlog metrics without changing existing bucket-only metric labels.

Keep legacy MRF files bucket-only by defaulting missing targetARNs to an empty list, and expose target snapshots through an additive API so existing DurableMrfBacklogSummary callers remain source-compatible.

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat(replication): expose runtime target backlog (#5586)

Track runtime replication backlog by target ARN for regular, large, delete, and MRF admission paths while preserving the existing bucket-level backlog semantics.

Add target-scoped current backlog metrics and merge them with durable target backlog snapshots for observability.

Co-authored-by: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
2026-08-01 17:31:50 +00:00
GatewayJ 4c83bff81c fix(s3select): support two-byte CSV record delimiters (#5565) 2026-08-01 17:08:31 +00:00
GatewayJ 51f9d1b74f fix(s3select): guarantee terminal event delivery (#5563) 2026-08-02 00:53:04 +08:00
Zhengchao An 81fc61db41 feat(admin): expose KMS key description and tag endpoints (#5575)
* feat(kms): add admin endpoints for key description and tag updates

Wire the KMS key metadata updates landed at the service layer to the admin
API: POST /v3/kms/keys/{update-description,tag,untag}. Each endpoint gates on
a dedicated KMS action scoped to the key its body names, records a handler-owned
audit entry for both the authorization denial and the outcome, and maps a
backend that cannot update key metadata to 501 rather than 404.

Refs rustfs/backlog#1586 (part of rustfs/backlog#1562)

* fix(admin): audit metadata attempts refused by an unavailable KMS

* test(admin): register the new KMS metadata routes in the matrix
2026-08-01 15:57:26 +00:00
唐小鸭 c8016cbcdb fix(replication): enforce bucket replication switches (#5449)
* fix(replication): enforce bucket replication switches

* fix(replication): satisfy delete admission clippy lint

* fix(replication): restore MinIO tag filter behavior

---------

Co-authored-by: Zhengchao An <anzhengchao@gmail.com>
Co-authored-by: cxymds <cxymds@gmail.com>
2026-08-01 15:03:57 +00:00
Zhengchao An f2d09d1426 feat(admin): expose KMS backup and restore behind explicit guards (#5579)
* feat(kms): add backup and restore admin API

Wires the merged KMS backup contract, Local export and Local restore into
the admin API: export a sealed bundle, run a zero-write restore preflight,
execute a confirmed restore, roll an interrupted restore back, and report
subsystem readiness.

- Dedicated kms:Backup / kms:Restore actions, recorded in the admin route
  matrix. Neither is reachable through any other KMS action.
- Restore requires two independent confirmations: an echo of the bundle
  manifest's backup id, and an explicitly named conflict policy (the
  default never writes).
- The backup KEK comes from the environment and is refused when it reuses
  a secret of the configured backend, compared both as the literal value
  and as raw key bytes.
- No endpoint accepts a path: bundles are addressed by a validated name
  under a configured root, and the restore target is always the server's
  own configured key directory.
- Bundles now carry a sanitized configuration artifact built as an
  allowlist projection, so a future backend credential field cannot leak
  into a bundle by default. Restore verifies it and never applies it.
- Audit entries go through the existing KMS admin wiring and carry
  identifiers only.

* test(kms): pin the backup admin API gates

Fixes the test KEK to a real 32-byte value and drives the export refusal
from the configured backend rather than from the handle that happens to
be available, so a Local handle cannot export on behalf of a backend
whose material RustFS does not own.
2026-08-01 14:31:25 +00:00
Zhengchao An 860ad68afd feat(sse): attach KMS attribution to S3 audit entries (#5577)
feat(kms): attach the SSE data-plane KMS summary to S3 audit entries
2026-08-01 13:58:30 +00:00
GatewayJ 816849a8ee fix(s3select): cancel queries after client disconnect (#5560) 2026-08-01 13:38:30 +00:00
Zhengchao An ad721bff42 fix(kms): honour the configured metadata cache TTL and metrics switch (#5569)
* fix(kms): honour the configured metadata cache TTL and metrics switch

KmsManager::new built the KmsCache from cache_config.max_keys alone, so
cache_config.ttl was dead configuration: every deployment ran the
hardcoded 300s window whatever the admin configure API was given, while
CacheSummary and the KMS config endpoint reported the configured value
back. cache_config.enable_metrics was never read anywhere.

Build the cache from the whole CacheConfig. The documented default is
reconciled down to the 300s the cache has always used rather than up to
the advertised 3600s, and now lives in one place (DEFAULT_CACHE_TTL)
instead of being duplicated across the four configure-request
converters, so the default path behaves exactly as before.

Behaviour change: a deployment configured through the admin API already
has ttl 3600 persisted, because the old converters wrote that default
into the stored config, so its describe_key staleness window widens from
an effective 300s to the 3600s it asked for. No cryptographic or
authorization path widens - encrypt, decrypt and generate_data_key go
straight to the backend and never read this cache. The Vault Transit
backend's own metadata cache, which does gate crypto through
ensure_key_state_allows, stays fixed at 300s and is now documented as
deliberately not operator-tunable.

The configured duration now reaches moka's builder, which panics above
1000 years, so CacheConfig::effective_ttl clamps to a 24h maximum the
way effective_timeout already clamps its own, and validate rejects a
zero TTL beside the existing max_keys check. Both config summaries and
the KMS config endpoint report the effective value, so the admin API
cannot advertise a lifetime the cache does not honour.

enable_metrics gates publication of the rustfs_kms_metadata_cache_*
families only; the counters behind the admin status API keep running
either way. No configure-request field sets it yet.

Refs rustfs/backlog#1584

* docs(kms): state why the Transit metadata TTL is not bound to the default

The comment claimed the constant matches config::DEFAULT_CACHE_TTL, which
reads as an invariant the code does not enforce. Say plainly that the
equality is a coincidence rather than a contract, and why binding the two
would be wrong: this cache gates crypto through ensure_key_state_allows,
so a later change to the operator-facing describe-cache default must not
be able to widen its staleness window.
2026-08-01 13:27:33 +00:00
Zhengchao An 3c00ad6048 fix(kms): make the deletion waiting window non-bypassable (#5535) 2026-08-01 12:23:28 +00:00
Zhengchao An bfec547d36 feat(health): drive KMS readiness from the probe status (#5552)
* feat(health): drive KMS readiness from the probe status

Readiness reported the KMS ready on the service status bit alone, which says
the manager started, not that the backend can still serve a request. Consume
the background probe snapshot instead: a running service is withdrawn only
when a fresh snapshot shows failures at the threshold.

The readiness path stays free of backend calls — it reads the lock-free
snapshot — so a struggling KMS cannot be amplified into probe traffic of its
own. Everything the probe cannot speak to (no worker, no completed round,
a snapshot older than three probe intervals) leaves the previous status-bit
verdict standing, and the check remains off by default.

Refs rustfs/backlog#1584 (part of rustfs/backlog#1562)

* test(health): pin the readiness default at compile time
2026-08-01 19:15:12 +08:00
Zhengchao An 0bd53becb5 feat(admin): emit KMS management audit events (#5554)
* feat(s3-types): add KMS service-control audit events

Configuration changes and service start/stop are management-plane actions
with no event name of their own, so they could not reach the audit
pipeline at all. Append three variants for them, following the existing
rule that KMS events are audit-only and live outside the `s3:` namespace,
so no bucket notification selector can expand to them.

* feat(admin): audit KMS management operations

Every KMS admin endpoint now builds an OperationContext from the
authenticated caller and hands it to the KMS layer, so the record the
manager already produces carries the principal, source address and
canonical request id instead of the internal placeholder.

A new adapter maps those records onto the server's existing AuditEntry
format and installs itself as the KMS audit sink at service assembly, so
KMS activity reaches the targets a deployment already operates. The
handlers emit directly for what the KMS layer cannot see: a request the
authorization gate rejects, and the endpoints with no context-aware KMS
entry point (data-key derivation and service control).

Only the failure class is recorded, never the error message, and the new
module joins the logging guardrail's checked files alongside the handlers
it serves.
2026-08-01 19:14:31 +08:00
GatewayJ bc41e567a5 fix(oidc): warn on request-header redirect fallback (#5561)
* fix(oidc): warn on request-header redirect fallback

* test(oidc): cover startup warning publication
2026-08-01 18:10:37 +08:00
houseme 8218248000 fix(hotpath): pin mimalloc allocator backend (#5550)
* fix(hotpath): pin mimalloc allocator backend

* test(hotpath): verify mimalloc allocator backend

Co-Authored-By: heihutu <heihutu@gmail.com>

* chore(hotpath): document unsafe allocator tests

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat(kms): record real cache hit, miss and eviction metrics (#5531)

* feat(kms): record real cache hit, miss and eviction metrics

The metadata cache reported (entry_count, 0) because moka exposes no hit
or miss counts, so the miss half of every cache report was a constant.

Track lookups and removals in the cache itself: hit/miss counters on the
lookup path, a moka eviction listener classifying removals by cause, and
an entry gauge refreshed whenever the entry set changes. The counters are
exported through the metrics facade under the rustfs_kms_ prefix with
static label values only, matching the operation-policy metrics, and are
also returned as a KmsCacheStats snapshot in place of the old tuple.

Cache semantics are unchanged: capacity, TTL and invalidation points are
the same, and remove now flushes pending maintenance so the gauge and the
removal notification describe the cache the caller sees.

Refs rustfs/backlog#1584

* fix(kms): report real cache counters through the admin status API

KmsStatusResponse.cache_stats mapped the old (entry_count, 0) tuple onto
hit_count and miss_count, so operators polling KMS status read the entry
count as a hit count and a miss count that was always zero.

Map the fields to the counters they claim to be, and add entry_count and
eviction_count as additive, defaulted fields so the entry number that
hit_count used to carry is still available.

Refs rustfs/backlog#1584

* fix(kms): refresh the cache entry gauge on lookup misses

The entry gauge was published only from the write paths, so an entry
dropped by TTL expiry left `rustfs_kms_metadata_cache_entries` reporting
a population that no longer existed until the next put, remove or clear.
A cache that goes quiet — entries ageing out with no further writes —
kept over-reporting indefinitely.

Republish the gauge from the lookup path when the lookup misses. A miss
is where expiry surfaces, and moka reaps expired entries in the
maintenance it runs during that same lookup, so the count read
afterwards reflects the reaping. Hits stay free of the extra work.

* docs(kms): correct the entry gauge convergence claim on the miss path

The comment on the miss-path gauge refresh said moka reaps expired
entries in the maintenance it runs on that same lookup. It does not:
`should_apply_reads` is gated on a full read log or an elapsed
housekeeping interval, so the removal that decrements `entry_count` and
reaches the eviction listener may land on a later lookup.

The behaviour and the test are unchanged — the gauge still converges,
and the test drives `run_pending_tasks` explicitly rather than riding on
that interval. Only the stated guarantee was wrong, so say interval
instead of same-lookup and record why forcing maintenance on the read
path was not the trade taken.

* chore(deps): refresh cargo dependencies

Co-Authored-By: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
Co-authored-by: Zhengchao An <anzhengchao@gmail.com>
2026-08-01 07:04:54 +00:00
Zhengchao An 322ce21b9a feat(kms): add an AWS KMS backend (#5553) 2026-08-01 06:07:32 +00:00
Zhengchao An 4e34f97dd7 feat(kms): converge KMS configuration across nodes after a runtime change (#5551) 2026-08-01 05:41:02 +00:00
Zhengchao An 782c78e0ef feat(sse): enforce per-key KMS authorization on the SSE-KMS data path (#5538) 2026-08-01 05:25:18 +00:00
Zhengchao An 8387528c9b feat(kms): record real cache hit, miss and eviction metrics (#5531)
* feat(kms): record real cache hit, miss and eviction metrics

The metadata cache reported (entry_count, 0) because moka exposes no hit
or miss counts, so the miss half of every cache report was a constant.

Track lookups and removals in the cache itself: hit/miss counters on the
lookup path, a moka eviction listener classifying removals by cause, and
an entry gauge refreshed whenever the entry set changes. The counters are
exported through the metrics facade under the rustfs_kms_ prefix with
static label values only, matching the operation-policy metrics, and are
also returned as a KmsCacheStats snapshot in place of the old tuple.

Cache semantics are unchanged: capacity, TTL and invalidation points are
the same, and remove now flushes pending maintenance so the gauge and the
removal notification describe the cache the caller sees.

Refs rustfs/backlog#1584

* fix(kms): report real cache counters through the admin status API

KmsStatusResponse.cache_stats mapped the old (entry_count, 0) tuple onto
hit_count and miss_count, so operators polling KMS status read the entry
count as a hit count and a miss count that was always zero.

Map the fields to the counters they claim to be, and add entry_count and
eviction_count as additive, defaulted fields so the entry number that
hit_count used to carry is still available.

Refs rustfs/backlog#1584

* fix(kms): refresh the cache entry gauge on lookup misses

The entry gauge was published only from the write paths, so an entry
dropped by TTL expiry left `rustfs_kms_metadata_cache_entries` reporting
a population that no longer existed until the next put, remove or clear.
A cache that goes quiet — entries ageing out with no further writes —
kept over-reporting indefinitely.

Republish the gauge from the lookup path when the lookup misses. A miss
is where expiry surfaces, and moka reaps expired entries in the
maintenance it runs during that same lookup, so the count read
afterwards reflects the reaping. Hits stay free of the extra work.

* docs(kms): correct the entry gauge convergence claim on the miss path

The comment on the miss-path gauge refresh said moka reaps expired
entries in the maintenance it runs on that same lookup. It does not:
`should_apply_reads` is gated on a full read log or an elapsed
housekeeping interval, so the removal that decrements `entry_count` and
reaches the eviction listener may land on a later lookup.

The behaviour and the test are unchanged — the gauge still converges,
and the test drives `run_pending_tasks` explicitly rather than riding on
that interval. Only the stated guarantee was wrong, so say interval
instead of same-lookup and record why forcing maintenance on the read
path was not the trade taken.
2026-08-01 05:19:25 +00:00
Zhengchao An 793c193a6b feat(admin): scope KMS admin authorization to the target key (#5533) 2026-08-01 04:18:52 +00:00
GatewayJ 3d4f4bb86d fix(sts): align AssumeRole authorization (#5281)
* fix(sts): align AssumeRole authorization with MinIO

* test(sts): cover AssumeRole OPA contract

* fix(iam): fail closed on unresolved policies

* fix(iam): fail closed while OPA initializes

---------

Co-authored-by: cxymds <cxymds@gmail.com>
Co-authored-by: Zhengchao An <anzhengchao@gmail.com>
2026-08-01 11:48:58 +08:00
houseme 4b6b6f14bd feat(hotpath): use mimalloc in inner counting allocator (#5523)
* feat(hotpath): use mimalloc in counting allocator

* fix(hotpath): adapt mimalloc for allocation counting

---------

Co-authored-by: overtrue <anzhengchao@gmail.com>
2026-08-01 10:43:52 +08:00
cxymds c09d11ff3b fix(config): fence persisted config updates and reloads (#5512)
* fix(config): fence persisted config updates and reloads

* fix(ci): unblock config and e2e checks

Co-Authored-By: heihutu <heihutu@gmail.com>

---------

Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: heihutu <heihutu@gmail.com>
2026-08-01 00:33:46 +00:00
houseme 04c5921850 fix(s3): allow CopyObject COPY request metadata (#5514)
* fix(s3): allow CopyObject COPY request metadata

Co-Authored-By: heihutu <heihutu@gmail.com>

* fix(kms): remove stale KmsClient import

Co-Authored-By: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-31 17:52:00 +00:00
houseme 40eee6177a test: add formal hotpath ABBA runner (#5507)
Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-31 11:33:10 +00:00
houseme 78d6918c52 feat: extend hotpath coverage across crates (#5505)
Add opt-in hotpath feature surfaces to every workspace crate and wire the root rustfs feature passthrough for function, allocation, and CPU profiling.

Add a focused set of function-level measurements for scanner, heal, lock, target replay, IAM, KMS, Keystone, trusted proxy, and capacity paths without adding request-scoped primitive wrappers.

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-31 05:42:19 +00:00
houseme e06c9c02c6 feat: add hotpath primitive profiling coverage (#5492)
* chore(deps): refresh google cloud dependencies

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat: add hotpath primitive coverage

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat: extend hotpath profiling features

Co-Authored-By: heihutu <heihutu@gmail.com>

* fix: gate OPA hotpath client wrapping

Co-Authored-By: heihutu <heihutu@gmail.com>

* fix: avoid request-scoped hotpath primitive wrappers

Preserve the original bounded channel and stream semantics in EC and RIO request paths while keeping hotpath CPU profiling as a separate opt-in feature that implies base hotpath instrumentation.

Co-Authored-By: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-31 01:59:13 +00:00
Zhengchao An a2fe5d7d88 feat(admin): add KMS key lifecycle endpoints and deletion reference gate (#5496)
* feat(kms): add key lifecycle operations to the backend contract

Add enable_key/disable_key/rotate_key to KmsBackend with conservative
defaults returning the typed UnsupportedCapability error, mirroring
remove_expired_key. KmsManager gains matching pass-through methods and
drops cached key metadata after every successful state mutation so the
next describe observes backend truth. The local backend overrides
enable/disable, delegating to its state-machine-gated client methods;
rotation stays rejected, matching its advertised capabilities. New
dedicated policy actions kms:EnableKey and kms:DisableKey complete the
KMS action taxonomy alongside the existing kms:RotateKey.

* feat(admin): add KMS key enable/disable/rotate endpoints

POST /v3/kms/keys/enable, /v3/kms/keys/disable and /v3/kms/keys/rotate,
following the existing /v3/kms/keys handler conventions: key_id body
with keyId query fallback, {success, message, key_id, key_metadata}
responses, and 503 JSON while the KMS service is absent. Error mapping
keeps InvalidOperation/ValidationError at 400 like the sibling handlers
and surfaces UnsupportedCapability as 501 so a backend capability gap is
never mistaken for a missing key. Existing /v3/kms/keys handlers are
untouched apart from a visibility change on a private query helper.

* feat(kms): gate scheduled key deletion on bucket encryption references

Implement the DeletionReferenceChecker seam left by the deletion worker:
before any material is destroyed, every bucket's SSE configuration is
checked for a default KMS key reference and a hit blocks the removal.
The gate fails closed - an unpublished object store, a failed bucket
listing or an unreadable per-bucket encryption config all report a
blocking reference - because destroying key material is irreversible
while a blocked removal is simply retried on the next sweep. Registered
during init_kms_system before the service can start, so every worker
spawn observes it. Storage access goes through a new kms section of the
root storage facade.
2026-07-31 01:37:07 +00:00
Zhengchao An b4901abd17 fix(admin): keep data usage endpoint available (#5494) 2026-07-31 00:11:14 +00:00
Zhengchao An 342ee1df78 feat(kms): add backend capability discovery (#5485) 2026-07-31 06:09:43 +08:00
houseme 7051a5ce41 feat: add opt-in hotpath profiling (#5488)
* feat: add opt-in hotpath profiling

Co-Authored-By: heihutu <heihutu@gmail.com>

* test: fix vault kms client construction

Co-Authored-By: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-31 01:49:19 +08:00
Zhengchao An d4f2efa2ad fix(kms): report accurate Vault KV2 security contract and disable unsafe rotation (#5474) 2026-07-30 22:56:43 +08:00
Zhengchao An 19cdd806a2 fix(kms): fail closed on missing or corrupt key material (#5475) 2026-07-30 22:56:30 +08:00
唐小鸭 ad7663afd1 refactor(sse): decouple ecstore and harden KMS lifecycle (#5435)
* refactor(sse): decouple encryption from ecstore

* feat(kms): enhance KMS service manager with runtime state and persistence support

* feat(kms): add local key export functionality for SSE-S3 migration tests

* fix(kms): keep local key export narrowly scoped

* fix(sse): validate copy source customer algorithm

---------

Co-authored-by: Zhengchao An <anzhengchao@gmail.com>
2026-07-30 12:39:25 +08:00
Zhengchao An 2d4f77fd3b fix(iam): add correctly named policy APIs (#5457) 2026-07-30 00:55:23 +00:00
Zhengchao An 3a6212f597 fix(admin): fail closed for empty server context slots (#5452)
* fix(admin): fail closed for empty server context slots

* test(embedded): cover uninitialized context slot window

* test(embedded): authenticate startup probe as server B

* fix(admin): satisfy strict context-slot test lints
2026-07-30 07:25:33 +08:00
Zhengchao An d77439929c fix(server): preserve non-S3 trace context (#5450) 2026-07-30 07:24:23 +08:00
Henry Guo abc5f2e818 fix(scanner): persist portable usage cache keys (#5444)
* fix(scanner): persist portable usage cache keys

* fix(scanner): validate complete bucket cache graphs

Co-Authored-By: heihutu <heihutu@gmail.com>

---------

Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-29 23:22:30 +00:00
Zhengchao An 719c0d6ef0 feat(rpc): add replay-scoped internode authentication (#5455) 2026-07-29 23:12:38 +00:00
cxymds 0247c48ce0 fix(tiering): bind recovery to transaction metadata (#5409)
* fix(tiering): bind recovery to transaction metadata

* fix(tier): add operator transition reconciliation (#5410)

* fix(tier): add operator transition reconciliation

* fix(tiering): require live fleet capability proof (#5423)
2026-07-29 18:44:44 +00:00
Zhengchao An 88fa3877c1 fix(ecstore): serialize every bucket config write under the transaction lock (#5445)
Only replication-target writes took the bucket transaction lock. Every other
config write (policy, tagging, lifecycle, versioning, ...) went straight to
the process-local metadata-system guard, which serializes nothing across
nodes.

Each config write is a read-modify-write of one whole BucketMetadata blob:
load the blob, replace one field, save the blob back. The namespace locks
inside read_config/save_config are taken and released separately, so they do
not span that cycle. Two nodes updating different config files of the same
bucket therefore both load the same blob, each set their own field, and the
later save drops the other's -- with both clients already told 2xx. This is
not last-writer-wins on one document; an orthogonal config silently vanishes.

Route update(), delete() and update_config_with() through
acquire_config_write_guards(), which takes the cluster-wide transaction lock
first and the metadata-system write guard second. That order is load-bearing:
taking the process-local guard first would park every local reader and writer
of every bucket behind a lock whose holder may be another node, turning
remote contention into a local stall.

The lock is per bucket rather than per config file, since a per-file key
would let exactly the offending pair run concurrently. Rename the helper to
acquire_bucket_metadata_transaction_lock to match, but deliberately keep the
lock resource string as "bucket-targets/{bucket}/transaction.lock": the key
is what nodes agree on, so renaming it would leave a mixed-version cluster
with two disjoint keys and stop old and new nodes from excluding each other
on the very writes that are serialized today.

update_config_with() already narrowed its staleness window to a single load
and save, but its exclusion was explicitly process-local; it is now
cluster-wide, so its doc comment no longer disclaims cross-node races.

Also make update_and_parse load through self.api instead of the ambient store
handle, so the read and the write of one read-modify-write cannot resolve to
different instances.

The new tests drive two BucketMetadataSys instances over one ECStore -- the
in-process stand-in for two nodes, since they share no RwLock and can only be
serialized by the namespace lock. Verified the lost-update test has teeth by
removing the lock and confirming it fails on round 0, with the tagging config
clobbered to empty by the concurrent policy write.
2026-07-29 17:17:09 +00:00
GatewayJ 2dea4a9acf fix(s3): correlate server-owned request IDs (#5433)
* fix(s3): correlate server-owned request IDs

* fix(server): preserve trace context and Swift routing

---------

Co-authored-by: houseme <housemecn@gmail.com>
2026-07-29 23:50:59 +08:00
houseme 09157485aa fix(notify): reconcile persisted bucket rules (#5437)
Restore persisted bucket notification rules after the notification target runtime converges so restarted nodes rebuild their local rule engine without requiring an unchanged PUT bucket notification request.

Fixes #5428

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-29 15:00:13 +00:00
cxymds 55be5af661 fix(ecstore): fence bucket metadata generations (#5427) 2026-07-29 22:34:11 +08:00
houseme f7c1b13c0f refactor(deps): replace md5 crate with md-5 (#5432)
Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-29 11:29:37 +00:00