* fix(s3): accept fresh admin tokens when context carries stale authorization (#12390)
Signed-off-by: Mohd Quamar Tyagi <104281681+Tyagiquamar@users.noreply.github.com>
* security: share the admin Bearer check between S3 and admin-plane peers
Move the authorization scan into security.CheckGrpcAdminToken, built on
IsFilerAdminToken, and call it from CheckGrpcAdminPeer and the S3 IAM cache
check. Both now accept a valid admin token in any authorization value
instead of S3 re-implementing the decode and claim checks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* filer: accept a fresh admin token after a stale one on IAM gRPC
The S3 IAM cache check is meant to mirror the filer's, so route the filer's
checkAdminAuth through security.CheckGrpcAdminToken too. IamGrpcStore appends
its token to the caller's context, so a forwarded older token would otherwise
still shadow it here.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* security: parse at most four authorization values per admin call
Scanning every value let one request repeat a junk Bearer token (cheap over
HPACK) tens of thousands of times and burn about half a second of JWT parsing
on an unauthenticated port. Real callers send their own token plus at most one
forwarded from their context.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* credential: match the other WithS3InternalAdminAuth replace fix
The plugin worker fix makes the same set-instead-of-append change to
WithS3InternalAdminAuth with different code and a different test at the same
spot, so whichever lands second would conflict. Use the identical code and
test so the two merge cleanly in either order.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* security: test an expired admin token ahead of a fresh one
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* security: test that a same-key session token is not an admin token
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* filer: test IAM gRPC with only invalid authorization values
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Signed-off-by: Mohd Quamar Tyagi <104281681+Tyagiquamar@users.noreply.github.com>
Co-authored-by: Chris Lu <chrislusf@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* s3: restrict admin gRPC to local callers when no signing key
The S3 gateway's gRPC port (default 0.0.0.0:19000, always on) serves the
IAM cache and internal lifecycle admin services. checkAdminAuth was a
no-op when jwt.filer_signing.key was unset, so any reachable host could
PutIdentity an admin identity and take over the bucket data.
Without a shared key callers cannot be distinguished, so admin RPCs are
now limited to unix-socket, loopback, and the server's own interface
addresses. Remote filer-to-S3 propagation and lifecycle workers must set
jwt.filer_signing.key; the Bearer-token path is unchanged.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* s3: fail closed on nil guard and refresh local addresses per call
Review feedback: a nil filerGuard bypassed all checks — treat it like a
missing key and require a local peer. The own-address set was cached
forever, so interfaces added later were rejected; enumerate per call
instead since admin RPCs are rare. Nil ctx is denied rather than panics.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* s3: read the signing key once and bound interface enumeration
Review feedback: reading SigningKey twice could straddle a SIGHUP reload
— an old nonempty key skipped the local-peer check while the new empty
key verified the token. And enumerating interfaces per no-key call is
wasteful for co-located workers dialing the announced address; cache the
address set for 30s so new interfaces still become usable promptly.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* s3: enumerate interface addresses per no-key admin call
A cached address set keeps trusting an IP after it is removed from the
host and reassigned to another machine — that host would then hold
unauthenticated admin access for the cache TTL. Per-call enumeration only
runs for non-loopback TCP peers on the no-key path, which is low-volume
admin traffic, so the freshness is worth the syscall.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
---------
Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* s3: gate IAM-cache gRPC RPCs behind admin Bearer auth
The SeaweedS3IamCacheServer registered on the S3 gateway's internal gRPC
port (default 0.0.0.0:18333) accepted PutIdentity/RemoveIdentity/PutPolicy/
DeletePolicy/GetPolicy/ListPolicies/PutGroup/RemoveGroup with no per-RPC
authentication. An unauthenticated network peer could call PutIdentity with
Actions:[Admin] and write straight into the live accessKeyIdent map that
the SigV4 path reads, bypassing S3 authentication entirely.
Mirror the filer's IamGrpcServer.checkAdminAuth: require a Bearer token
signed with jwt.filer_signing.key (read from the existing s3a.filerGuard)
at the top of every IAM-cache RPC. With no key configured the check is a
no-op, matching the rest of SeaweedFS's gRPC surface.
* credential: attach admin Bearer token to S3 IAM-cache propagation
The filer's PropagatingCredentialStore fans IAM mutations out to peer S3
servers over the SeaweedS3IamCache gRPC service. Now that the S3 handlers
require a Bearer token signed with jwt.filer_signing.key, attach one to the
outgoing propagation context (mirroring shell/iamAdminAuthContext). With no
key configured it is a no-op, so deployments that run without the signing
key keep working.
* credential: mint IAM-cache admin token after master discovery
propagateChange attached the admin Bearer token before ListClusterNodes,
so master-client retries could run down the (default 10s) token lifetime
before the peer S3 fan-out began, leaving peers to reject an expired token
and IAM caches stale. Move withIamCacheAdminAuth to after discovery
succeeds, immediately before the propagation timeout is derived.
* credential: cap IAM-cache propagation timeout below JWT lifetime
The propagation fan-out used a fixed 10s timeout. If an operator
configures jwt.filer_signing.expires_after_seconds below 10, the admin
token can expire while slower S3 peers are still being contacted, leaving
their IAM caches stale. Derive the propagation deadline as
min(10s, tokenTTL) so it never outlives the token. withIamCacheAdminAuth
now returns the token's lifetime (0 = no expiry) for this purpose.