mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-12 06:48:45 +01: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
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/gorilla/mux"
|
||||
iamlib "github.com/seaweedfs/seaweedfs/weed/iam"
|
||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -228,6 +229,22 @@ func TestObjectLockActionsRoundTrip(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Anonymous misses must not disclose bucket or key existence: 404 is only
|
||||
// served when anonymous listing is allowed.
|
||||
func TestAnonymousBucketListAuthorization(t *testing.T) {
|
||||
s3a := &S3ApiServer{iam: &IdentityAccessManagement{}, bucketConfigCache: NewBucketConfigCache(time.Minute)}
|
||||
s3a.bucketConfigCache.Set("open", &BucketConfig{Name: "open", IsPublicRead: true})
|
||||
s3a.bucketConfigCache.Set("private", &BucketConfig{Name: "private"})
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "http://localhost:8333/open/key", nil)
|
||||
if got := s3a.authorizeAnonymousBucketList(req, "open"); got != s3err.ErrNone {
|
||||
t.Errorf("listable bucket: %v, want ErrNone", got)
|
||||
}
|
||||
if got := s3a.authorizeAnonymousBucketList(req, "private"); got != s3err.ErrAccessDenied {
|
||||
t.Errorf("private bucket: %v, want AccessDenied", got)
|
||||
}
|
||||
}
|
||||
|
||||
// Internal Seaweed-X-Amz-* headers carry server-validated object lock values
|
||||
// into entry metadata; client-supplied ones must be dropped on entry or the
|
||||
// dedicated-permission check on x-amz-object-lock-* is bypassed.
|
||||
|
||||
Reference in new issue
Block a user