From 98d3913a1e8cff1ccfedf368dabe7709113db788 Mon Sep 17 00:00:00 2001 From: Andreas Auernhammer Date: Thu, 20 Jun 2019 02:37:08 +0200 Subject: [PATCH] enable SSE-KMS pass-through on S3 gateway (#7788) This commit relaxes the restriction that the MinIO gateway does not accept SSE-KMS headers. Now, the S3 gateway allows SSE-KMS headers for PUT and MULTIPART PUT requests and forwards them to the S3 gateway backend (AWS). This is considered SSE pass-through mode. Fixes #7753 --- cmd/api-router.go | 7 ++++- cmd/crypto/header.go | 20 +++++++++++++ cmd/crypto/header_test.go | 50 ++++++++++++++++++++++++++++++++ cmd/encryption-v1.go | 11 +++++++ cmd/gateway-main.go | 3 +- cmd/gateway/s3/gateway-s3-sse.go | 3 +- cmd/object-handlers.go | 8 ++--- cmd/routers.go | 5 ++-- cmd/test-utils_test.go | 4 +-- 9 files changed, 100 insertions(+), 11 deletions(-) diff --git a/cmd/api-router.go b/cmd/api-router.go index 0d74276e1..ab3dab3d5 100644 --- a/cmd/api-router.go +++ b/cmd/api-router.go @@ -28,10 +28,12 @@ type objectAPIHandlers struct { CacheAPI func() CacheObjectLayer // Returns true of handlers should interpret encryption. EncryptionEnabled func() bool + // Returns true if handlers allow SSE-KMS encryption headers. + AllowSSEKMS func() bool } // registerAPIRouter - registers S3 compatible APIs. -func registerAPIRouter(router *mux.Router, encryptionEnabled bool) { +func registerAPIRouter(router *mux.Router, encryptionEnabled, allowSSEKMS bool) { // Initialize API. api := objectAPIHandlers{ ObjectAPI: newObjectLayerFn, @@ -39,6 +41,9 @@ func registerAPIRouter(router *mux.Router, encryptionEnabled bool) { EncryptionEnabled: func() bool { return encryptionEnabled }, + AllowSSEKMS: func() bool { + return allowSSEKMS + }, } // API Router diff --git a/cmd/crypto/header.go b/cmd/crypto/header.go index 1a2a07dee..adf5f8294 100644 --- a/cmd/crypto/header.go +++ b/cmd/crypto/header.go @@ -18,6 +18,7 @@ import ( "bytes" "crypto/md5" "encoding/base64" + "encoding/json" "net/http" "strings" ) @@ -125,6 +126,25 @@ func (s3KMS) IsRequested(h http.Header) bool { return false } +// ParseHTTP parses the SSE-KMS headers and returns the SSE-KMS key ID +// and context, if present, on success. +func (s3KMS) ParseHTTP(h http.Header) (string, interface{}, error) { + algorithm := h.Get(SSEHeader) + if algorithm != SSEAlgorithmKMS { + return "", nil, ErrInvalidEncryptionMethod + } + + contextStr, ok := h[SSEKmsContext] + if ok { + var context map[string]interface{} + if err := json.Unmarshal([]byte(contextStr[0]), &context); err != nil { + return "", nil, err + } + return h.Get(SSEKmsID), context, nil + } + return h.Get(SSEKmsID), nil, nil +} + var ( // SSEC represents AWS SSE-C. It provides functionality to handle // SSE-C requests. diff --git a/cmd/crypto/header_test.go b/cmd/crypto/header_test.go index 6a0299757..5085092fd 100644 --- a/cmd/crypto/header_test.go +++ b/cmd/crypto/header_test.go @@ -54,6 +54,56 @@ func TestKMSIsRequested(t *testing.T) { } } +var kmsParseHTTPTests = []struct { + Header http.Header + ShouldFail bool +}{ + {Header: http.Header{}, ShouldFail: true}, // 0 + {Header: http.Header{"X-Amz-Server-Side-Encryption": []string{"aws:kms"}}, ShouldFail: false}, // 1 + {Header: http.Header{ + "X-Amz-Server-Side-Encryption": []string{"aws:kms"}, + "X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id": []string{"s3-007-293847485-724784"}, + }, ShouldFail: false}, // 2 + {Header: http.Header{ + "X-Amz-Server-Side-Encryption": []string{"aws:kms"}, + "X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id": []string{"s3-007-293847485-724784"}, + "X-Amz-Server-Side-Encryption-Context": []string{"{}"}, + }, ShouldFail: false}, // 3 + {Header: http.Header{ + "X-Amz-Server-Side-Encryption": []string{"aws:kms"}, + "X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id": []string{"s3-007-293847485-724784"}, + "X-Amz-Server-Side-Encryption-Context": []string{"{\"bucket\": \"some-bucket\"}"}, + }, ShouldFail: false}, // 4 + {Header: http.Header{ + "X-Amz-Server-Side-Encryption": []string{"aws:kms"}, + "X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id": []string{"s3-007-293847485-724784"}, + "X-Amz-Server-Side-Encryption-Context": []string{"{\"bucket\": \"some-bucket\"}"}, + }, ShouldFail: false}, // 5 + {Header: http.Header{ + "X-Amz-Server-Side-Encryption": []string{"AES256"}, + "X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id": []string{"s3-007-293847485-724784"}, + "X-Amz-Server-Side-Encryption-Context": []string{"{\"bucket\": \"some-bucket\"}"}, + }, ShouldFail: true}, // 6 + {Header: http.Header{ + "X-Amz-Server-Side-Encryption": []string{"aws:kms"}, + "X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id": []string{"s3-007-293847485-724784"}, + "X-Amz-Server-Side-Encryption-Context": []string{"{\"bucket\": \"some-bucket\""}, // invalid JSON + }, ShouldFail: true}, // 7 + +} + +func TestKMSParseHTTP(t *testing.T) { + for i, test := range kmsParseHTTPTests { + _, _, err := S3KMS.ParseHTTP(test.Header) + if err == nil && test.ShouldFail { + t.Errorf("Test %d: should fail but succeeded", i) + } + if err != nil && !test.ShouldFail { + t.Errorf("Test %d: should pass but failed with: %v", i, err) + } + } +} + var s3IsRequestedTests = []struct { Header http.Header Expected bool diff --git a/cmd/encryption-v1.go b/cmd/encryption-v1.go index 4fb33f858..6f6a64634 100644 --- a/cmd/encryption-v1.go +++ b/cmd/encryption-v1.go @@ -1238,6 +1238,17 @@ func putOpts(ctx context.Context, r *http.Request, bucket, object string, metada opts.UserDefined = metadata return } + if crypto.S3KMS.IsRequested(r.Header) { + keyID, context, err := crypto.S3KMS.ParseHTTP(r.Header) + if err != nil { + return ObjectOptions{}, err + } + sseKms, err := encrypt.NewSSEKMS(keyID, context) + if err != nil { + return ObjectOptions{}, err + } + return ObjectOptions{ServerSideEncryption: sseKms, UserDefined: metadata}, nil + } // default case of passing encryption headers and UserDefined metadata to backend return getDefaultOpts(r.Header, false, metadata) } diff --git a/cmd/gateway-main.go b/cmd/gateway-main.go index 90f0a0e1c..c137fbdc4 100644 --- a/cmd/gateway-main.go +++ b/cmd/gateway-main.go @@ -181,9 +181,10 @@ func StartGateway(ctx *cli.Context, gw Gateway) { // Currently only NAS and S3 gateway support encryption headers. encryptionEnabled := gatewayName == "s3" || gatewayName == "nas" + allowSSEKMS := gatewayName == "s3" // Only S3 can support SSE-KMS (as pass-through) // Add API router. - registerAPIRouter(router, encryptionEnabled) + registerAPIRouter(router, encryptionEnabled, allowSSEKMS) var getCert certs.GetCertificateFunc if globalTLSCerts != nil { diff --git a/cmd/gateway/s3/gateway-s3-sse.go b/cmd/gateway/s3/gateway-s3-sse.go index 3c332bfe2..49b667d72 100644 --- a/cmd/gateway/s3/gateway-s3-sse.go +++ b/cmd/gateway/s3/gateway-s3-sse.go @@ -442,7 +442,8 @@ func (l *s3EncObjects) PutObject(ctx context.Context, bucket string, object stri // Decide if sse options needed to be passed to backend if opts.ServerSideEncryption != nil && ((minio.GlobalGatewaySSE.SSEC() && opts.ServerSideEncryption.Type() == encrypt.SSEC) || - (minio.GlobalGatewaySSE.SSES3() && opts.ServerSideEncryption.Type() == encrypt.S3)) { + (minio.GlobalGatewaySSE.SSES3() && opts.ServerSideEncryption.Type() == encrypt.S3) || + opts.ServerSideEncryption.Type() == encrypt.KMS) { sseOpts = opts.ServerSideEncryption } if opts.ServerSideEncryption == nil { diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go index f31f68cf2..02aeaff3a 100644 --- a/cmd/object-handlers.go +++ b/cmd/object-handlers.go @@ -1052,7 +1052,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) return } - if crypto.S3KMS.IsRequested(r.Header) { + if crypto.S3KMS.IsRequested(r.Header) && !api.AllowSSEKMS() { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) // SSE-KMS is not supported return } @@ -1178,7 +1178,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req } // This request header needs to be set prior to setting ObjectOptions - if globalAutoEncryption && !crypto.SSEC.IsRequested(r.Header) { + if globalAutoEncryption && !crypto.SSEC.IsRequested(r.Header) && !crypto.S3KMS.IsRequested(r.Header) { r.Header.Add(crypto.SSEHeader, crypto.SSEAlgorithmAES256) } @@ -1315,7 +1315,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) return } - if crypto.S3KMS.IsRequested(r.Header) { + if crypto.S3KMS.IsRequested(r.Header) && !api.AllowSSEKMS() { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) // SSE-KMS is not supported return } @@ -1333,7 +1333,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r } // This request header needs to be set prior to setting ObjectOptions - if globalAutoEncryption && !crypto.SSEC.IsRequested(r.Header) { + if globalAutoEncryption && !crypto.SSEC.IsRequested(r.Header) && !crypto.S3KMS.IsRequested(r.Header) { r.Header.Add(crypto.SSEHeader, crypto.SSEAlgorithmAES256) } diff --git a/cmd/routers.go b/cmd/routers.go index 374abc1ab..9a1f55ee1 100644 --- a/cmd/routers.go +++ b/cmd/routers.go @@ -119,8 +119,9 @@ func configureServerHandler(endpoints EndpointList) (http.Handler, error) { } } - // Add API router, additionally all server mode support encryption. - registerAPIRouter(router, true) + // Add API router, additionally all server mode support encryption + // but don't allow SSE-KMS. + registerAPIRouter(router, true, false) // Register rest of the handlers. return registerHandlers(router, globalHandlers...), nil diff --git a/cmd/test-utils_test.go b/cmd/test-utils_test.go index 89c0de8e0..19d00e951 100644 --- a/cmd/test-utils_test.go +++ b/cmd/test-utils_test.go @@ -2135,7 +2135,7 @@ func registerBucketLevelFunc(bucket *mux.Router, api objectAPIHandlers, apiFunct func registerAPIFunctions(muxRouter *mux.Router, objLayer ObjectLayer, apiFunctions ...string) { if len(apiFunctions) == 0 { // Register all api endpoints by default. - registerAPIRouter(muxRouter, true) + registerAPIRouter(muxRouter, true, false) return } // API Router. @@ -2176,7 +2176,7 @@ func initTestAPIEndPoints(objLayer ObjectLayer, apiFunctions []string) http.Hand registerAPIFunctions(muxRouter, objLayer, apiFunctions...) return muxRouter } - registerAPIRouter(muxRouter, true) + registerAPIRouter(muxRouter, true, false) return muxRouter }