mirror of
https://github.com/pgsty/minio.git
synced 2026-09-10 04:24:03 +03:00
fix: send the SSE-KMS context as a JSON object on federated copies
putOptsFromReq handed the parsed kms.Context straight to
encrypt.NewSSEKMS. kms.Context implements encoding.TextMarshaler, so the
SDK serialized it as a JSON string, and a request without a context
still produced one because the nil Context is a typed nil inside the
interface value and marshals to "{}". The receiving ParseHTTP rejects
both forms, so every federated CopyObject to an SSE-KMS destination
failed with InvalidArgument once the forwarded stream was correct.
Pass a plain map, or nothing when no context was requested, and cover
SSE-KMS destinations with and without an explicit context.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FodsDpa6VkghaeRE6WjmEe
Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
@@ -433,7 +433,16 @@ func putOptsFromHeaders(ctx context.Context, hdr http.Header, metadata map[strin
|
||||
if err != nil {
|
||||
return ObjectOptions{}, err
|
||||
}
|
||||
sseKms, err := encrypt.NewSSEKMS(keyID, context)
|
||||
// kms.Context implements encoding.TextMarshaler, so handing it to the
|
||||
// SDK's interface{} parameter would serialize the context as a JSON
|
||||
// string, which the receiving ParseHTTP rejects; a nil Context is a
|
||||
// typed nil there and would be sent as "{}". Pass a plain map, or
|
||||
// nothing when no context was requested.
|
||||
var sdkContext any
|
||||
if context != nil {
|
||||
sdkContext = map[string]string(context)
|
||||
}
|
||||
sseKms, err := encrypt.NewSSEKMS(keyID, sdkContext)
|
||||
if err != nil {
|
||||
return ObjectOptions{}, err
|
||||
}
|
||||
|
||||
@@ -496,10 +496,11 @@ func testAPIFederatedCopyObjectInheritedChecksum(objectAPI ObjectLayer, instance
|
||||
const federationTestKMSKeyID = "federation-test-key"
|
||||
|
||||
// federationSSEHeaders returns the request headers that select one server-side
|
||||
// encryption kind: "plain", "s3", "kms" or "c". SSE-C keys derive from keyByte
|
||||
// so a case can name two distinct customer keys. With copySource the SSE-C key
|
||||
// is returned in its x-amz-copy-source-* form, the only kind a copy has to
|
||||
// name for its source; the other kinds then return nothing.
|
||||
// encryption kind: "plain", "s3", "kms", "kms-context" (SSE-KMS with an
|
||||
// explicit encryption context) or "c". SSE-C keys derive from keyByte so a
|
||||
// case can name two distinct customer keys. With copySource the SSE-C key is
|
||||
// returned in its x-amz-copy-source-* form, the only kind a copy has to name
|
||||
// for its source; the other kinds then return nothing.
|
||||
func federationSSEHeaders(kind string, keyByte byte, copySource bool) map[string]string {
|
||||
h := map[string]string{}
|
||||
if copySource && kind != "c" {
|
||||
@@ -509,9 +510,12 @@ func federationSSEHeaders(kind string, keyByte byte, copySource bool) map[string
|
||||
case "plain":
|
||||
case "s3":
|
||||
h[xhttp.AmzServerSideEncryption] = xhttp.AmzEncryptionAES
|
||||
case "kms":
|
||||
case "kms", "kms-context":
|
||||
h[xhttp.AmzServerSideEncryption] = xhttp.AmzEncryptionKMS
|
||||
h[xhttp.AmzServerSideEncryptionKmsID] = federationTestKMSKeyID
|
||||
if kind == "kms-context" {
|
||||
h[xhttp.AmzServerSideEncryptionKmsContext] = base64.StdEncoding.EncodeToString([]byte(`{"tenant":"federation"}`))
|
||||
}
|
||||
case "c":
|
||||
key := bytes.Repeat([]byte{keyByte}, 32)
|
||||
sum := md5.Sum(key)
|
||||
@@ -609,6 +613,8 @@ func testAPIFederatedCopyObjectSSE(objectAPI ObjectLayer, instanceType, bucketNa
|
||||
pairs := []struct{ src, dst string }{
|
||||
{"plain", "s3"}, {"s3", "plain"}, {"s3", "s3"},
|
||||
{"plain", "c"}, {"c", "plain"}, {"c", "c"}, {"s3", "c"},
|
||||
{"plain", "kms"}, {"kms", "plain"}, {"kms", "kms"},
|
||||
{"plain", "kms-context"}, {"kms-context", "kms-context"},
|
||||
}
|
||||
const srcKeyByte, dstKeyByte = 0x11, 0x22
|
||||
|
||||
@@ -624,8 +630,8 @@ func testAPIFederatedCopyObjectSSE(objectAPI ObjectLayer, instanceType, bucketNa
|
||||
if err != nil {
|
||||
t.Fatalf("%s: GetObjectInfo(source) failed: %v", instanceType, err)
|
||||
}
|
||||
if got := federationStoredSSE(before.UserDefined); got != pair.src {
|
||||
t.Fatalf("%s: source stored as %s, want %s", instanceType, got, pair.src)
|
||||
if got, want := federationStoredSSE(before.UserDefined), strings.TrimSuffix(pair.src, "-context"); got != want {
|
||||
t.Fatalf("%s: source stored as %s, want %s", instanceType, got, want)
|
||||
}
|
||||
if compressed := body.ext == ".txt" && pair.src != "c"; before.IsCompressed() != compressed {
|
||||
t.Fatalf("%s: source compressed=%v, want %v", instanceType, before.IsCompressed(), compressed)
|
||||
@@ -666,8 +672,8 @@ func testAPIFederatedCopyObjectSSE(objectAPI ObjectLayer, instanceType, bucketNa
|
||||
if err != nil {
|
||||
t.Fatalf("%s: GetObjectInfo(destination) failed: %v", instanceType, err)
|
||||
}
|
||||
if got := federationStoredSSE(after.UserDefined); got != pair.dst {
|
||||
t.Fatalf("%s: destination stored as %s, want %s", instanceType, got, pair.dst)
|
||||
if got, want := federationStoredSSE(after.UserDefined), strings.TrimSuffix(pair.dst, "-context"); got != want {
|
||||
t.Fatalf("%s: destination stored as %s, want %s", instanceType, got, want)
|
||||
}
|
||||
if pair.dst != "plain" && !after.IsCompressed() {
|
||||
once := ObjectInfo{Size: int64(len(body.data))}
|
||||
|
||||
Reference in New Issue
Block a user