mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-22 14:04:28 +02:00
s3: mask anonymous misses behind AccessDenied unless the bucket is listable (#11857)
* s3: mask anonymous misses behind AccessDenied unless the bucket is listable Deferred anonymous GET/HEAD authorization only runs once an entry is selected, so misses returned 404 while existing keys returned 403 — an unauthenticated existence oracle for bucket names and object keys. Route not-found responses in Get/HeadObjectHandler through a check that only discloses 404 when anonymous listing is allowed (policy, identity or a public-read bucket); otherwise the miss is indistinguishable from a denied read, matching AWS and 4.48 behavior. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * s3: mask missing-directory misses for anonymous reads too Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * s3: check a fixed request shape when masking anonymous misses The miss check passed the caller's request through, so query parameters could retarget the resolved action (?policy) or satisfy an s3:prefix condition on an unrelated subtree. Each check now runs on a request with a fixed shape: a ListBucket scoped to the probed key and a plain read of the key, so the miss only shows when the caller could learn the same fact directly. * s3: mask conditional-header misses for anonymous reads An If-Match conditional on a missing key wrote NoSuchKey directly from processConditionalHeaders, before the handlers reached the masked miss path, so anonymous callers could still distinguish a missing key from a denied read. * s3: keep the versionId when masking anonymous misses The read leg must evaluate the action the caller's request resolves to: a versioned miss is s3:GetObjectVersion, and dropping versionId would let a plain GetObject grant disclose versions the caller may not read. --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
6936bbdc3e
commit
0de6347b11
4 files changed
+116
-22
No files matched your search
@@ -174,7 +174,7 @@ func TestAnonymousObjectACLVersionedEmptyDirectoryMarker(t *testing.T) {
|
||||
require.Equal(t, "null", rr.Header().Get("x-amz-version-id"))
|
||||
}
|
||||
rr := serveAnonymousRead(s3a, method, "folder", "", nil)
|
||||
require.Equal(t, 404, rr.Code, "the bare prefix is still not an object")
|
||||
require.Equal(t, 403, rr.Code, "the bare prefix is not an object, and the miss stays masked for a non-listable bucket")
|
||||
marker.Extended[s3_constants.ExtAmzAclKey] = []byte(`[]`)
|
||||
rr = serveAnonymousRead(s3a, method, "folder/", "", nil)
|
||||
require.Equal(t, 403, rr.Code, rr.Body.String())
|
||||
@@ -238,8 +238,12 @@ func TestAnonymousObjectACLVersionedDirectoryIdentity(t *testing.T) {
|
||||
f.entries["/buckets/b/folder/.versions"] = &filer_pb.Entry{IsDirectory: true, Extended: map[string][]byte{s3_constants.ExtLatestVersionIdKey: []byte("")}}
|
||||
}
|
||||
// Equivalent filer-path spellings must not recover a different object's data.
|
||||
for _, object := range []string{"folder/", "folder//", "folder\\"} {
|
||||
assertMissing(serveAnonymousRead(s3a, method, object, route.query, headers))
|
||||
assertMissing(serveAnonymousRead(s3a, method, "folder/", route.query, headers))
|
||||
// Spellings the policy's folder/ grant does not cover are
|
||||
// masked: a miss must be indistinguishable from a denied read.
|
||||
for _, object := range []string{"folder//", "folder\\"} {
|
||||
rr := serveAnonymousRead(s3a, method, object, route.query, headers)
|
||||
require.Equal(t, http.StatusForbidden, rr.Code, rr.Body.String())
|
||||
}
|
||||
if kind == "regular file" || kind == "prefix object" || kind == "empty prefix object" {
|
||||
// A slash-only policy must not authorize data belonging to the bare key.
|
||||
|
||||
Reference in new issue
Block a user