mirror of
https://github.com/pgsty/minio.git
synced 2026-10-31 12:52:27 +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 {
|
if err != nil {
|
||||||
return ObjectOptions{}, err
|
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 {
|
if err != nil {
|
||||||
return ObjectOptions{}, err
|
return ObjectOptions{}, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -496,10 +496,11 @@ func testAPIFederatedCopyObjectInheritedChecksum(objectAPI ObjectLayer, instance
|
|||||||
const federationTestKMSKeyID = "federation-test-key"
|
const federationTestKMSKeyID = "federation-test-key"
|
||||||
|
|
||||||
// federationSSEHeaders returns the request headers that select one server-side
|
// federationSSEHeaders returns the request headers that select one server-side
|
||||||
// encryption kind: "plain", "s3", "kms" or "c". SSE-C keys derive from keyByte
|
// encryption kind: "plain", "s3", "kms", "kms-context" (SSE-KMS with an
|
||||||
// so a case can name two distinct customer keys. With copySource the SSE-C key
|
// explicit encryption context) or "c". SSE-C keys derive from keyByte so a
|
||||||
// is returned in its x-amz-copy-source-* form, the only kind a copy has to
|
// case can name two distinct customer keys. With copySource the SSE-C key is
|
||||||
// name for its source; the other kinds then return nothing.
|
// 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 {
|
func federationSSEHeaders(kind string, keyByte byte, copySource bool) map[string]string {
|
||||||
h := map[string]string{}
|
h := map[string]string{}
|
||||||
if copySource && kind != "c" {
|
if copySource && kind != "c" {
|
||||||
@@ -509,9 +510,12 @@ func federationSSEHeaders(kind string, keyByte byte, copySource bool) map[string
|
|||||||
case "plain":
|
case "plain":
|
||||||
case "s3":
|
case "s3":
|
||||||
h[xhttp.AmzServerSideEncryption] = xhttp.AmzEncryptionAES
|
h[xhttp.AmzServerSideEncryption] = xhttp.AmzEncryptionAES
|
||||||
case "kms":
|
case "kms", "kms-context":
|
||||||
h[xhttp.AmzServerSideEncryption] = xhttp.AmzEncryptionKMS
|
h[xhttp.AmzServerSideEncryption] = xhttp.AmzEncryptionKMS
|
||||||
h[xhttp.AmzServerSideEncryptionKmsID] = federationTestKMSKeyID
|
h[xhttp.AmzServerSideEncryptionKmsID] = federationTestKMSKeyID
|
||||||
|
if kind == "kms-context" {
|
||||||
|
h[xhttp.AmzServerSideEncryptionKmsContext] = base64.StdEncoding.EncodeToString([]byte(`{"tenant":"federation"}`))
|
||||||
|
}
|
||||||
case "c":
|
case "c":
|
||||||
key := bytes.Repeat([]byte{keyByte}, 32)
|
key := bytes.Repeat([]byte{keyByte}, 32)
|
||||||
sum := md5.Sum(key)
|
sum := md5.Sum(key)
|
||||||
@@ -609,6 +613,8 @@ func testAPIFederatedCopyObjectSSE(objectAPI ObjectLayer, instanceType, bucketNa
|
|||||||
pairs := []struct{ src, dst string }{
|
pairs := []struct{ src, dst string }{
|
||||||
{"plain", "s3"}, {"s3", "plain"}, {"s3", "s3"},
|
{"plain", "s3"}, {"s3", "plain"}, {"s3", "s3"},
|
||||||
{"plain", "c"}, {"c", "plain"}, {"c", "c"}, {"s3", "c"},
|
{"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
|
const srcKeyByte, dstKeyByte = 0x11, 0x22
|
||||||
|
|
||||||
@@ -624,8 +630,8 @@ func testAPIFederatedCopyObjectSSE(objectAPI ObjectLayer, instanceType, bucketNa
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("%s: GetObjectInfo(source) failed: %v", instanceType, err)
|
t.Fatalf("%s: GetObjectInfo(source) failed: %v", instanceType, err)
|
||||||
}
|
}
|
||||||
if got := federationStoredSSE(before.UserDefined); 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, pair.src)
|
t.Fatalf("%s: source stored as %s, want %s", instanceType, got, want)
|
||||||
}
|
}
|
||||||
if compressed := body.ext == ".txt" && pair.src != "c"; before.IsCompressed() != compressed {
|
if compressed := body.ext == ".txt" && pair.src != "c"; before.IsCompressed() != compressed {
|
||||||
t.Fatalf("%s: source compressed=%v, want %v", instanceType, 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 {
|
if err != nil {
|
||||||
t.Fatalf("%s: GetObjectInfo(destination) failed: %v", instanceType, err)
|
t.Fatalf("%s: GetObjectInfo(destination) failed: %v", instanceType, err)
|
||||||
}
|
}
|
||||||
if got := federationStoredSSE(after.UserDefined); 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, pair.dst)
|
t.Fatalf("%s: destination stored as %s, want %s", instanceType, got, want)
|
||||||
}
|
}
|
||||||
if pair.dst != "plain" && !after.IsCompressed() {
|
if pair.dst != "plain" && !after.IsCompressed() {
|
||||||
once := ObjectInfo{Size: int64(len(body.data))}
|
once := ObjectInfo{Size: int64(len(body.data))}
|
||||||
|
|||||||
Reference in New Issue
Block a user