fix: harden CORS and replication request trust

Keep pre-authentication CORS lookups resident-only so attacker-controlled path segments cannot trigger metadata I/O or grow the metadata cache. Preserve fail-closed behavior for startup, load failures, invalid metadata, and the internal namespace.

Centralize replication request trust after authentication, distinguish general replication from replica-only privileges, and gate SSE-C ciphertext handling, source metadata, object-lock bypasses, event suppression, delete semantics, and replica status on the appropriate permission. Add least-privilege, multipart, PostPolicy, CORS amplification, and compatibility regressions.

Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
Feng Ruohang
2026-09-01 20:50:08 +08:00
parent d5e763b072
commit 938603458d
15 changed files with 1377 additions and 164 deletions
+44
View File
@@ -33,6 +33,7 @@ import (
"time"
"github.com/dustin/go-humanize"
xhttp "github.com/minio/minio/internal/http"
)
const (
@@ -184,6 +185,49 @@ func TestPostPolicyBucketHandler(t *testing.T) {
ExecObjectLayerTest(t, testPostPolicyBucketHandler)
}
func TestPostPolicyCannotForgeReplicationStatus(t *testing.T) {
ExecObjectLayerTest(t, testPostPolicyCannotForgeReplicationStatus)
}
func testPostPolicyCannotForgeReplicationStatus(obj ObjectLayer, instanceType string, t TestErrHandler) {
if err := newTestConfig(globalMinioDefaultRegion, obj); err != nil {
t.Fatalf("Initializing config.json failed")
}
bucketName := getRandomBucketName()
if err := obj.MakeBucket(context.Background(), bucketName, MakeBucketOptions{}); err != nil {
t.Fatalf("%s: make bucket: %v", instanceType, err)
}
apiRouter := initTestAPIEndPoints(obj, []string{"PostPolicy"})
credentials := globalActiveCred
now := UTCNow()
region := globalMinioDefaultRegion
objectPrefix := "post-policy-replication-status"
policyBytes := buildGenericPolicy(now, credentials.AccessKey, region, bucketName, objectPrefix, false)
policyText := strings.TrimSuffix(string(policyBytes), "]}") +
`,["eq","$x-amz-replication-status","REPLICA"]]}`
req, err := newPostRequestV4Generic("", bucketName, objectPrefix, []byte("post policy payload"),
credentials.AccessKey, credentials.SecretKey, region, now, []byte(policyText),
map[string]string{xhttp.AmzBucketReplicationStatus: "REPLICA"}, false, false, false)
if err != nil {
t.Fatalf("%s: create post request: %v", instanceType, err)
}
rec := httptest.NewRecorder()
apiRouter.ServeHTTP(rec, req)
if rec.Code != http.StatusNoContent {
t.Fatalf("%s: POST status %d: %s", instanceType, rec.Code, rec.Body.String())
}
info, err := obj.GetObjectInfo(context.Background(), bucketName, objectPrefix+"/upload.txt", ObjectOptions{})
if err != nil {
t.Fatalf("%s: get object info: %v", instanceType, err)
}
if got := info.UserDefined[xhttp.AmzBucketReplicationStatus]; got != "" {
t.Fatalf("%s: forged replication status persisted as %q", instanceType, got)
}
if !info.ReplicationStatus.Empty() {
t.Fatalf("%s: forged replication status reached ObjectInfo: %q", instanceType, info.ReplicationStatus)
}
}
// testPostPolicyBucketHandler - Tests validate post policy handler uploading objects.
func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErrHandler) {
if err := newTestConfig(globalMinioDefaultRegion, obj); err != nil {