Add support for SSE-S3 server side encryption with vault (#6192)

Add support for sse-s3 encryption with vault as KMS.

Also refactoring code to make use of headers and functions defined in
crypto package and clean up duplicated code.
This commit is contained in:
poornas
2018-08-17 12:52:14 -07:00
committed by kannappanr
parent 3d197c1449
commit e71ef905f9
236 changed files with 23463 additions and 608 deletions

View File

@@ -34,6 +34,7 @@ import (
"github.com/gorilla/mux"
miniogo "github.com/minio/minio-go"
"github.com/minio/minio/cmd/crypto"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/dns"
"github.com/minio/minio/pkg/event"
@@ -120,7 +121,6 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r
}
var selectReq ObjectSelectRequest
if err := xmlDecoder(r.Body, &selectReq, r.ContentLength); err != nil {
fmt.Println(err)
writeErrorResponse(w, ErrMalformedXML, r.URL)
return
}
@@ -177,7 +177,7 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r
}
getObject := objectAPI.GetObject
if api.CacheAPI() != nil && !hasSSECustomerHeader(r.Header) {
if api.CacheAPI() != nil && !crypto.SSEC.IsRequested(r.Header) {
getObject = api.CacheAPI().GetObject
}
@@ -190,7 +190,7 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r
var writer io.Writer
writer = pipewriter
if objectAPI.IsEncryptionSupported() {
if hasSSECustomerHeader(r.Header) {
if crypto.SSEC.IsRequested(r.Header) {
// Response writer should be limited early on for decryption upto required length,
// additionally also skipping mod(offset)64KiB boundaries.
writer = ioutil.LimitedWriter(writer, startOffset%(64*1024), length)
@@ -342,7 +342,8 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req
var writer io.Writer
writer = w
if objectAPI.IsEncryptionSupported() {
if hasSSECustomerHeader(r.Header) {
s3Encrypted := crypto.S3.IsEncrypted(objInfo.UserDefined)
if crypto.SSEC.IsRequested(r.Header) || s3Encrypted {
// Response writer should be limited early on for decryption upto required length,
// additionally also skipping mod(offset)64KiB boundaries.
writer = ioutil.LimitedWriter(writer, startOffset%(64*1024), length)
@@ -352,9 +353,12 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return
}
w.Header().Set(SSECustomerAlgorithm, r.Header.Get(SSECustomerAlgorithm))
w.Header().Set(SSECustomerKeyMD5, r.Header.Get(SSECustomerKeyMD5))
if s3Encrypted {
w.Header().Set(crypto.SSEHeader, crypto.SSEAlgorithmAES256)
} else {
w.Header().Set(crypto.SSECAlgorithm, r.Header.Get(crypto.SSECAlgorithm))
w.Header().Set(crypto.SSECKeyMD5, r.Header.Get(crypto.SSECKeyMD5))
}
}
}
@@ -362,7 +366,7 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req
setHeadGetRespHeaders(w, r.URL.Query())
getObject := objectAPI.GetObject
if api.CacheAPI() != nil && !hasSSECustomerHeader(r.Header) {
if api.CacheAPI() != nil && !crypto.SSEC.IsRequested(r.Header) && !crypto.S3.IsEncrypted(objInfo.UserDefined) {
getObject = api.CacheAPI().GetObject
}
@@ -463,12 +467,17 @@ func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Re
writeErrorResponse(w, apiErr, r.URL)
return
} else if encrypted {
s3Encrypted := crypto.S3.IsEncrypted(objInfo.UserDefined)
if _, err = DecryptRequest(w, r, bucket, object, objInfo.UserDefined); err != nil {
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return
}
w.Header().Set(SSECustomerAlgorithm, r.Header.Get(SSECustomerAlgorithm))
w.Header().Set(SSECustomerKeyMD5, r.Header.Get(SSECustomerKeyMD5))
if s3Encrypted {
w.Header().Set(crypto.SSEHeader, crypto.SSEAlgorithmAES256)
} else {
w.Header().Set(crypto.SSECAlgorithm, r.Header.Get(crypto.SSECAlgorithm))
w.Header().Set(crypto.SSECKeyMD5, r.Header.Get(crypto.SSECKeyMD5))
}
}
}
@@ -632,10 +641,14 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
var encMetadata = make(map[string]string)
if objectAPI.IsEncryptionSupported() {
var oldKey, newKey []byte
sseCopyC := hasSSECopyCustomerHeader(r.Header)
sseC := hasSSECustomerHeader(r.Header)
if sseC {
newKey, err = ParseSSECustomerRequest(r)
sseCopyS3 := crypto.S3.IsEncrypted(srcInfo.UserDefined)
sseCopyC := crypto.SSECopy.IsRequested(r.Header)
sseC := crypto.SSEC.IsRequested(r.Header)
sseS3 := crypto.S3.IsRequested(r.Header)
if sseC || sseS3 {
if sseC {
newKey, err = ParseSSECustomerRequest(r)
}
if err != nil {
pipeWriter.CloseWithError(err)
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
@@ -647,7 +660,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
// otherwise we proceed to encrypt/decrypt.
if sseCopyC && sseC && cpSrcDstSame {
// Get the old key which needs to be rotated.
oldKey, err = ParseSSECopyCustomerRequest(r)
oldKey, err = ParseSSECopyCustomerRequest(r, srcInfo.UserDefined)
if err != nil {
pipeWriter.CloseWithError(err)
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
@@ -665,7 +678,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
// Since we are rotating the keys, make sure to update the metadata.
srcInfo.metadataOnly = true
} else {
if sseCopyC {
if sseCopyC || sseCopyS3 {
// Source is encrypted make sure to save the encrypted size.
writer = ioutil.LimitedWriter(writer, 0, srcInfo.Size)
writer, srcInfo.Size, err = DecryptAllBlocksCopyRequest(writer, r, srcBucket, srcObject, srcInfo)
@@ -678,12 +691,12 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
// we are creating a new object at this point, even
// if source and destination are same objects.
srcInfo.metadataOnly = false
if sseC {
if sseC || sseS3 {
size = srcInfo.Size
}
}
if sseC {
reader, err = newEncryptReader(reader, newKey, dstBucket, dstObject, encMetadata)
if sseC || sseS3 {
reader, err = newEncryptReader(reader, newKey, dstBucket, dstObject, encMetadata, sseS3)
if err != nil {
pipeWriter.CloseWithError(err)
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
@@ -693,10 +706,11 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
// we are creating a new object at this point, even
// if source and destination are same objects.
srcInfo.metadataOnly = false
if !sseCopyC {
if !sseCopyC && !sseCopyS3 {
size = srcInfo.EncryptedSize()
}
}
srcInfo.Reader, err = hash.NewReader(reader, size, "", "") // do not try to verify encrypted content
if err != nil {
pipeWriter.CloseWithError(err)
@@ -706,7 +720,6 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
}
}
srcInfo.Writer = writer
srcInfo.UserDefined, err = getCpObjMetadataFromHeader(ctx, r, srcInfo.UserDefined)
if err != nil {
pipeWriter.CloseWithError(err)
@@ -725,7 +738,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
// metadataOnly is true indicating that we are not overwriting the object.
// if encryption is enabled we do not need explicit "REPLACE" metadata to
// be enabled as well - this is to allow for key-rotation.
if !isMetadataReplace(r.Header) && srcInfo.metadataOnly && !srcInfo.IsEncrypted() {
if !isMetadataReplace(r.Header) && srcInfo.metadataOnly && !crypto.SSEC.IsEncrypted(srcInfo.UserDefined) {
pipeWriter.CloseWithError(fmt.Errorf("invalid copy dest"))
// If x-amz-metadata-directive is not set to REPLACE then we need
// to error out if source and destination are same.
@@ -981,7 +994,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
}
if objectAPI.IsEncryptionSupported() {
if hasSSECustomerHeader(r.Header) && !hasSuffix(object, slashSeparator) { // handle SSE-C requests
if hasServerSideEncryptionHeader(r.Header) && !hasSuffix(object, slashSeparator) { // handle SSE requests
reader, err = EncryptRequest(hashReader, r, bucket, object, metadata)
if err != nil {
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
@@ -996,7 +1009,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
}
}
if api.CacheAPI() != nil && !hasSSECustomerHeader(r.Header) {
if api.CacheAPI() != nil && !hasServerSideEncryptionHeader(r.Header) {
putObject = api.CacheAPI().PutObject
}
@@ -1009,9 +1022,12 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
w.Header().Set("ETag", "\""+objInfo.ETag+"\"")
if objectAPI.IsEncryptionSupported() {
if hasSSECustomerHeader(r.Header) {
w.Header().Set(SSECustomerAlgorithm, r.Header.Get(SSECustomerAlgorithm))
w.Header().Set(SSECustomerKeyMD5, r.Header.Get(SSECustomerKeyMD5))
if crypto.S3.IsEncrypted(objInfo.UserDefined) {
w.Header().Set(crypto.SSEHeader, crypto.SSEAlgorithmAES256)
}
if crypto.SSEC.IsRequested(r.Header) {
w.Header().Set(crypto.SSECAlgorithm, r.Header.Get(crypto.SSECAlgorithm))
w.Header().Set(crypto.SSECKeyMD5, r.Header.Get(crypto.SSECKeyMD5))
}
}
@@ -1076,18 +1092,11 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
var encMetadata = map[string]string{}
if objectAPI.IsEncryptionSupported() {
if hasSSECustomerHeader(r.Header) {
key, err := ParseSSECustomerRequest(r)
if err != nil {
if hasServerSideEncryptionHeader(r.Header) {
if err := setEncryptionMetadata(r, bucket, object, encMetadata); err != nil {
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return
}
_, err = newEncryptMetadata(key, bucket, object, encMetadata)
if err != nil {
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return
}
// Set this for multipart only operations, we need to differentiate during
// decryption if the file was actually multipart or not.
encMetadata[ReservedMetadataPrefix+"Encrypted-Multipart"] = ""
@@ -1108,7 +1117,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
}
newMultipartUpload := objectAPI.NewMultipartUpload
if api.CacheAPI() != nil {
if api.CacheAPI() != nil && !hasServerSideEncryptionHeader(r.Header) {
newMultipartUpload = api.CacheAPI().NewMultipartUpload
}
uploadID, err := newMultipartUpload(ctx, bucket, object, metadata)
@@ -1246,8 +1255,9 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return
}
sseCopyC := hasSSECopyCustomerHeader(r.Header)
if sseCopyC {
sseCopyC := crypto.SSECopy.IsRequested(r.Header)
sseCopyS3 := crypto.S3.IsEncrypted(srcInfo.UserDefined)
if sseCopyC || sseCopyS3 {
// Response writer should be limited early on for decryption upto required length,
// additionally also skipping mod(offset)64KiB boundaries.
writer = ioutil.LimitedWriter(writer, startOffset%(64*1024), length)
@@ -1258,19 +1268,20 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
return
}
}
if li.IsEncrypted() {
if !hasSSECustomerHeader(r.Header) {
if crypto.IsEncrypted(li.UserDefined) {
if !hasServerSideEncryptionHeader(r.Header) {
writeErrorResponse(w, ErrSSEMultipartEncrypted, r.URL)
return
}
var key []byte
key, err = ParseSSECustomerRequest(r)
if err != nil {
pipeWriter.CloseWithError(err)
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return
if crypto.SSEC.IsRequested(r.Header) {
key, err = ParseSSECustomerRequest(r)
if err != nil {
pipeWriter.CloseWithError(err)
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return
}
}
var objectEncryptionKey []byte
objectEncryptionKey, err = decryptObjectInfo(key, dstBucket, dstObject, li.UserDefined)
if err != nil {
@@ -1463,16 +1474,18 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return
}
if li.IsEncrypted() {
if !hasSSECustomerHeader(r.Header) {
if crypto.IsEncrypted(li.UserDefined) {
if !hasServerSideEncryptionHeader(r.Header) {
writeErrorResponse(w, ErrSSEMultipartEncrypted, r.URL)
return
}
var key []byte
key, err = ParseSSECustomerRequest(r)
if err != nil {
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return
if crypto.SSEC.IsRequested(r.Header) {
key, err = ParseSSECustomerRequest(r)
if err != nil {
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return
}
}
// Calculating object encryption key
@@ -1506,7 +1519,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
}
putObjectPart := objectAPI.PutObjectPart
if api.CacheAPI() != nil {
if api.CacheAPI() != nil && !hasServerSideEncryptionHeader(r.Header) {
putObjectPart = api.CacheAPI().PutObjectPart
}
partInfo, err := putObjectPart(ctx, bucket, object, uploadID, partID, hashReader)