From b8f2fdde41dff3dc3b8db669c1d42d30ca5c1d3d Mon Sep 17 00:00:00 2001 From: Mikhail Khadarenka Date: Mon, 14 Sep 2026 17:37:55 +0200 Subject: [PATCH] fix(replication): avoid persisting aws-chunked metadata Signed-off-by: Mikhail Khadarenka --- cmd/handler-utils.go | 56 +++++++++++++++++++++++---------------- cmd/handler-utils_test.go | 13 ++++++++- 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/cmd/handler-utils.go b/cmd/handler-utils.go index 0569f02b3..c70d18073 100644 --- a/cmd/handler-utils.go +++ b/cmd/handler-utils.go @@ -246,16 +246,6 @@ func extractMetadata(ctx context.Context, mimesHeader ...textproto.MIMEHeader) ( // extractMetadata extracts metadata from map values. func extractMetadataFromMime(ctx context.Context, v textproto.MIMEHeader, m map[string]string) error { - return extractMetadataFromMimeWithReplication(ctx, v, m, false) -} - -// extractReplicationMetadataFromMime restores replication-only metadata after the -// caller has validated that the request is a trusted replication write. -func extractReplicationMetadataFromMime(ctx context.Context, v textproto.MIMEHeader, m map[string]string) error { - return extractMetadataFromMimeWithReplication(ctx, v, m, true) -} - -func extractMetadataFromMimeWithReplication(ctx context.Context, v textproto.MIMEHeader, m map[string]string, allowReplication bool) error { if v == nil { bugLogIf(ctx, errInvalidArgument) return errInvalidArgument @@ -267,18 +257,14 @@ func extractMetadataFromMimeWithReplication(ctx context.Context, v textproto.MIM nv[http.CanonicalHeaderKey(k)] = kv } - // Save all supported headers. + // Save ordinary object metadata. Replication-only headers are restored only + // after the request has been validated as a trusted replication write. for _, supportedHeader := range supportedHeaders { - value, ok := nv[http.CanonicalHeaderKey(supportedHeader)] - if ok { - if v, ok := replicationToInternalHeaders[supportedHeader]; ok { - if !allowReplication { - continue - } - m[v] = strings.Join(value, ",") - } else { - m[supportedHeader] = strings.Join(value, ",") - } + if _, ok := replicationToInternalHeaders[supportedHeader]; ok { + continue + } + if value, ok := nv[http.CanonicalHeaderKey(supportedHeader)]; ok { + m[supportedHeader] = strings.Join(value, ",") } } @@ -287,8 +273,7 @@ func extractMetadataFromMimeWithReplication(ctx context.Context, v textproto.MIM if !stringsHasPrefixFold(key, prefix) { continue } - value, ok := nv[http.CanonicalHeaderKey(key)] - if ok { + if value, ok := nv[http.CanonicalHeaderKey(key)]; ok { m[key] = strings.Join(value, ",") break } @@ -297,6 +282,31 @@ func extractMetadataFromMimeWithReplication(ctx context.Context, v textproto.MIM return nil } +// extractReplicationMetadataFromMime restores replication-only metadata after the +// caller has validated that the request is a trusted replication write. +func extractReplicationMetadataFromMime(ctx context.Context, v textproto.MIMEHeader, m map[string]string) error { + if v == nil { + bugLogIf(ctx, errInvalidArgument) + return errInvalidArgument + } + + nv := make(textproto.MIMEHeader, len(v)) + for k, kv := range v { + // Canonicalize all headers, to remove any duplicates. + nv[http.CanonicalHeaderKey(k)] = kv + } + + // Ordinary object metadata has already been extracted and normalized before + // replication trust is evaluated. Restoring it here would reintroduce raw + // transport headers such as Content-Encoding: aws-chunked. + for header, internalHeader := range replicationToInternalHeaders { + if value, ok := nv[http.CanonicalHeaderKey(header)]; ok { + m[internalHeader] = strings.Join(value, ",") + } + } + return nil +} + // Returns access credentials in the request Authorization header. func getReqAccessCred(r *http.Request, region string) (cred auth.Credentials) { cred, _, _ = getReqAccessKeyV4(r, region, serviceS3) diff --git a/cmd/handler-utils_test.go b/cmd/handler-utils_test.go index 15c206dc4..6a4c7598b 100644 --- a/cmd/handler-utils_test.go +++ b/cmd/handler-utils_test.go @@ -254,6 +254,9 @@ func TestExtractMetadataFromRequestKeepsQueryCompatibility(t *testing.T) { func TestExtractReplicationMetadataHeaders(t *testing.T) { header := http.Header{ + "Content-Type": []string{"application/wasm"}, + "Content-Encoding": []string{"aws-chunked"}, + "X-Amz-Meta-Source": []string{"client"}, "X-Minio-Replication-Server-Side-Encryption-Sealed-Key": []string{"sealed-key"}, "X-Minio-Replication-Server-Side-Encryption-Seal-Algorithm": []string{"DAREv2-HMAC-SHA256"}, "X-Minio-Replication-Server-Side-Encryption-Iv": []string{"iv"}, @@ -262,12 +265,17 @@ func TestExtractReplicationMetadataHeaders(t *testing.T) { ReplicationSsecChecksumHeader: []string{"checksum"}, } - metadata := make(map[string]string) + metadata := map[string]string{ + "content-type": "application/wasm", + "x-amz-meta-source": "client", + } if err := extractReplicationMetadataFromMime(t.Context(), textproto.MIMEHeader(header), metadata); err != nil { t.Fatalf("failed to extract replication metadata: %v", err) } expected := map[string]string{ + "content-type": "application/wasm", + "x-amz-meta-source": "client", "X-Minio-Internal-Server-Side-Encryption-Sealed-Key": "sealed-key", "X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm": "DAREv2-HMAC-SHA256", "X-Minio-Internal-Server-Side-Encryption-Iv": "iv", @@ -279,6 +287,9 @@ func TestExtractReplicationMetadataHeaders(t *testing.T) { if !reflect.DeepEqual(metadata, expected) { t.Fatalf("unexpected replication metadata: expected %#v, got %#v", expected, metadata) } + if _, ok := metadata["content-encoding"]; ok { + t.Fatalf("replication metadata restored transport content-encoding: %#v", metadata) + } } func TestGetCopyObjectMetadataFromHeaderReplication(t *testing.T) {