diff --git a/cmd/bucket-replication.go b/cmd/bucket-replication.go index 445fca3f3..3baa91415 100644 --- a/cmd/bucket-replication.go +++ b/cmd/bucket-replication.go @@ -1418,7 +1418,8 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object } // Set the encrypted size for SSE-C objects - if crypto.SSEC.IsEncrypted(objInfo.UserDefined) { + isSSEC := crypto.SSEC.IsEncrypted(objInfo.UserDefined) + if isSSEC { size = objInfo.Size } @@ -1478,6 +1479,13 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object return } } else { + // SSEC objects will refuse HeadObject without the decryption key. + // Ignore the error, since we know the object exists and versioning prevents overwriting existing versions. + if isSSEC && strings.Contains(cerr.Error(), errorCodes[ErrSSEEncryptedObject].Description) { + rinfo.ReplicationStatus = replication.Completed + rinfo.ReplicationAction = replicateNone + goto applyAction + } // if target returns error other than NoSuchKey, defer replication attempt if minio.IsNetworkOrHostDown(cerr, true) && !globalBucketTargetSys.isOffline(tgt.EndpointURL()) { globalBucketTargetSys.markOffline(tgt.EndpointURL()) @@ -1505,6 +1513,7 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object return } } +applyAction: rinfo.ReplicationStatus = replication.Completed rinfo.Size = size rinfo.ReplicationAction = rAction @@ -1638,13 +1647,14 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob }() var ( - hr *hash.Reader - pInfo minio.ObjectPart + hr *hash.Reader + pInfo minio.ObjectPart + isSSEC = crypto.SSEC.IsEncrypted(objInfo.UserDefined) ) var objectSize int64 for _, partInfo := range objInfo.Parts { - if crypto.SSEC.IsEncrypted(objInfo.UserDefined) { + if isSSEC { hr, err = hash.NewReader(ctx, io.LimitReader(r, partInfo.Size), partInfo.Size, "", "", partInfo.ActualSize) } else { hr, err = hash.NewReader(ctx, io.LimitReader(r, partInfo.ActualSize), partInfo.ActualSize, "", "", partInfo.ActualSize) @@ -1655,16 +1665,18 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob cHeader := http.Header{} cHeader.Add(xhttp.MinIOSourceReplicationRequest, "true") - crc := getCRCMeta(objInfo, partInfo.Number, nil) // No SSE-C keys here. - for k, v := range crc { - cHeader.Add(k, v) + if !isSSEC { + crc := getCRCMeta(objInfo, partInfo.Number, nil) // No SSE-C keys here. + for k, v := range crc { + cHeader.Add(k, v) + } } popts := minio.PutObjectPartOptions{ SSE: opts.ServerSideEncryption, CustomHeader: cHeader, } - if crypto.SSEC.IsEncrypted(objInfo.UserDefined) { + if isSSEC { objectSize += partInfo.Size pInfo, err = c.PutObjectPart(ctx, bucket, object, uploadID, partInfo.Number, hr, partInfo.Size, popts) } else { @@ -1674,7 +1686,7 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob if err != nil { return err } - if !crypto.SSEC.IsEncrypted(objInfo.UserDefined) && pInfo.Size != partInfo.ActualSize { + if !isSSEC && pInfo.Size != partInfo.ActualSize { return fmt.Errorf("Part size mismatch: got %d, want %d", pInfo.Size, partInfo.ActualSize) } uploadedParts = append(uploadedParts, minio.CompletePart{ @@ -1686,12 +1698,18 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob ChecksumSHA256: pInfo.ChecksumSHA256, }) } + userMeta := map[string]string{ + validSSEReplicationHeaders[ReservedMetadataPrefix+"Actual-Object-Size"]: objInfo.UserDefined[ReservedMetadataPrefix+"actual-size"], + } + if isSSEC && objInfo.UserDefined[ReplicationSsecChecksumHeader] != "" { + userMeta[ReplicationSsecChecksumHeader] = objInfo.UserDefined[ReplicationSsecChecksumHeader] + } // really big value but its okay on heavily loaded systems. This is just tail end timeout. cctx, ccancel := context.WithTimeout(ctx, 10*time.Minute) defer ccancel() _, err = c.CompleteMultipartUpload(cctx, bucket, object, uploadID, uploadedParts, minio.PutObjectOptions{ - UserMetadata: map[string]string{validSSEReplicationHeaders[ReservedMetadataPrefix+"Actual-Object-Size"]: objInfo.UserDefined[ReservedMetadataPrefix+"actual-size"]}, + UserMetadata: userMeta, Internal: minio.AdvancedPutOptions{ SourceMTime: objInfo.ModTime, SourceETag: objInfo.ETag, diff --git a/cmd/encryption-v1.go b/cmd/encryption-v1.go index bc959274d..7bd46593d 100644 --- a/cmd/encryption-v1.go +++ b/cmd/encryption-v1.go @@ -1025,7 +1025,9 @@ func DecryptObjectInfo(info *ObjectInfo, r *http.Request) (encrypted bool, err e if encrypted { if crypto.SSEC.IsEncrypted(info.UserDefined) { if !(crypto.SSEC.IsRequested(headers) || crypto.SSECopy.IsRequested(headers)) { - return encrypted, errEncryptedObject + if r.Header.Get(xhttp.MinIOSourceReplicationRequest) != "true" { + return encrypted, errEncryptedObject + } } } @@ -1168,7 +1170,9 @@ func (o *ObjectInfo) decryptChecksums(part int, h http.Header) map[string]string if _, encrypted := crypto.IsEncrypted(o.UserDefined); encrypted { decrypted, err := o.metadataDecrypter(h)("object-checksum", data) if err != nil { - encLogIf(GlobalContext, err) + if err != crypto.ErrSecretKeyMismatch { + encLogIf(GlobalContext, err) + } return nil } data = decrypted diff --git a/cmd/erasure-multipart.go b/cmd/erasure-multipart.go index f8f93a38c..3b9182766 100644 --- a/cmd/erasure-multipart.go +++ b/cmd/erasure-multipart.go @@ -483,6 +483,11 @@ func (er erasureObjects) newMultipartUpload(ctx context.Context, bucket string, } fi.DataDir = mustGetUUID() + if userDefined[ReplicationSsecChecksumHeader] != "" { + fi.Checksum, _ = base64.StdEncoding.DecodeString(userDefined[ReplicationSsecChecksumHeader]) + delete(userDefined, ReplicationSsecChecksumHeader) + } + // Initialize erasure metadata. for index := range partsMetadata { partsMetadata[index] = fi @@ -1294,6 +1299,14 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str defer lk.Unlock(lkctx) } + // Accept encrypted checksum from incoming request. + if opts.UserDefined[ReplicationSsecChecksumHeader] != "" { + if v, err := base64.StdEncoding.DecodeString(opts.UserDefined[ReplicationSsecChecksumHeader]); err == nil { + fi.Checksum = v + } + delete(opts.UserDefined, ReplicationSsecChecksumHeader) + } + if checksumType.IsSet() { checksumType |= hash.ChecksumMultipart | hash.ChecksumIncludesMultipart var cs *hash.Checksum @@ -1303,13 +1316,7 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str fi.Checksum = opts.EncryptFn("object-checksum", fi.Checksum) } } - if fi.Metadata[ReplicationSsecChecksumHeader] != "" { - if v, err := base64.StdEncoding.DecodeString(fi.Metadata[ReplicationSsecChecksumHeader]); err == nil { - fi.Checksum = v - } - } - delete(fi.Metadata, ReplicationSsecChecksumHeader) // Transferred above. - delete(fi.Metadata, hash.MinIOMultipartChecksum) // Not needed in final object. + delete(fi.Metadata, hash.MinIOMultipartChecksum) // Not needed in final object. // Save the final object size and modtime. fi.Size = objectSize diff --git a/cmd/object-api-options.go b/cmd/object-api-options.go index 08c64bb81..f50bd71ab 100644 --- a/cmd/object-api-options.go +++ b/cmd/object-api-options.go @@ -37,6 +37,15 @@ func getDefaultOpts(header http.Header, copySource bool, metadata map[string]str var sse encrypt.ServerSide opts = ObjectOptions{UserDefined: metadata} + if v, ok := header[xhttp.MinIOSourceProxyRequest]; ok { + opts.ProxyHeaderSet = true + opts.ProxyRequest = strings.Join(v, "") == "true" + } + if _, ok := header[xhttp.MinIOSourceReplicationRequest]; ok { + opts.ReplicationRequest = true + } + opts.Speedtest = header.Get(globalObjectPerfUserMetadata) != "" + if copySource { if crypto.SSECopy.IsRequested(header) { clientKey, err = crypto.SSECopy.ParseHTTP(header) @@ -66,14 +75,7 @@ func getDefaultOpts(header http.Header, copySource bool, metadata map[string]str if crypto.S3.IsRequested(header) || (metadata != nil && crypto.S3.IsEncrypted(metadata)) { opts.ServerSideEncryption = encrypt.NewSSE() } - if v, ok := header[xhttp.MinIOSourceProxyRequest]; ok { - opts.ProxyHeaderSet = true - opts.ProxyRequest = strings.Join(v, "") == "true" - } - if _, ok := header[xhttp.MinIOSourceReplicationRequest]; ok { - opts.ReplicationRequest = true - } - opts.Speedtest = header.Get(globalObjectPerfUserMetadata) != "" + return } @@ -494,5 +496,8 @@ func completeMultipartOpts(ctx context.Context, r *http.Request, bucket, object if _, ok := r.Header[xhttp.MinIOSourceReplicationRequest]; ok { opts.ReplicationRequest = true } + if r.Header.Get(ReplicationSsecChecksumHeader) != "" { + opts.UserDefined[ReplicationSsecChecksumHeader] = r.Header.Get(ReplicationSsecChecksumHeader) + } return opts, nil } diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go index 99415107b..4f6b5143f 100644 --- a/cmd/object-handlers.go +++ b/cmd/object-handlers.go @@ -1482,9 +1482,10 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re // convert copy src encryption options for GET calls getOpts := ObjectOptions{ - VersionID: srcOpts.VersionID, - Versioned: srcOpts.Versioned, - VersionSuspended: srcOpts.VersionSuspended, + VersionID: srcOpts.VersionID, + Versioned: srcOpts.Versioned, + VersionSuspended: srcOpts.VersionSuspended, + ReplicationRequest: r.Header.Get(xhttp.MinIOSourceReplicationRequest) == "true", } getSSE := encrypt.SSE(srcOpts.ServerSideEncryption) if getSSE != srcOpts.ServerSideEncryption { @@ -1616,7 +1617,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re return } // Encryption parameters not present for this object. - if crypto.SSEC.IsEncrypted(srcInfo.UserDefined) && !crypto.SSECopy.IsRequested(r.Header) { + if crypto.SSEC.IsEncrypted(srcInfo.UserDefined) && !crypto.SSECopy.IsRequested(r.Header) && r.Header.Get(xhttp.MinIOSourceReplicationRequest) != "true" { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidSSECustomerAlgorithm), r.URL) return }