mirror of
https://github.com/minio/minio.git
synced 2025-11-07 12:52:58 -05:00
CopyObject must preserve checksums and encrypt them if required (#21399)
This commit is contained in:
@@ -1074,8 +1074,16 @@ func (o *ObjectInfo) metadataDecrypter(h http.Header) objectMetaDecryptFn {
|
||||
return input, nil
|
||||
}
|
||||
var key []byte
|
||||
if k, err := crypto.SSEC.ParseHTTP(h); err == nil {
|
||||
key = k[:]
|
||||
if crypto.SSECopy.IsRequested(h) {
|
||||
sseCopyKey, err := crypto.SSECopy.ParseHTTP(h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key = sseCopyKey[:]
|
||||
} else {
|
||||
if k, err := crypto.SSEC.ParseHTTP(h); err == nil {
|
||||
key = k[:]
|
||||
}
|
||||
}
|
||||
key, err := decryptObjectMeta(key, o.Bucket, o.Name, o.UserDefined)
|
||||
if err != nil {
|
||||
@@ -1087,7 +1095,8 @@ func (o *ObjectInfo) metadataDecrypter(h http.Header) objectMetaDecryptFn {
|
||||
}
|
||||
}
|
||||
|
||||
// decryptPartsChecksums will attempt to decode checksums and return it/them if set.
|
||||
// decryptPartsChecksums will attempt to decrypt and decode part checksums, and save
|
||||
// only the decrypted part checksum values on ObjectInfo directly.
|
||||
// if part > 0, and we have the checksum for the part that will be returned.
|
||||
func (o *ObjectInfo) decryptPartsChecksums(h http.Header) {
|
||||
data := o.Checksum
|
||||
@@ -1112,6 +1121,23 @@ func (o *ObjectInfo) decryptPartsChecksums(h http.Header) {
|
||||
}
|
||||
}
|
||||
|
||||
// decryptChecksum will attempt to decrypt the ObjectInfo.Checksum, returns the decrypted value
|
||||
// An error is only returned if it was encrypted and the decryption failed.
|
||||
func (o *ObjectInfo) decryptChecksum(h http.Header) ([]byte, error) {
|
||||
data := o.Checksum
|
||||
if len(data) == 0 {
|
||||
return data, nil
|
||||
}
|
||||
if _, encrypted := crypto.IsEncrypted(o.UserDefined); encrypted {
|
||||
decrypted, err := o.metadataDecrypter(h)("object-checksum", data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data = decrypted
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// metadataEncryptFn provides an encryption function for metadata.
|
||||
// Will return nil, nil if unencrypted.
|
||||
func (o *ObjectInfo) metadataEncryptFn(headers http.Header) (objectMetaEncryptFn, error) {
|
||||
|
||||
@@ -1470,7 +1470,17 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
|
||||
actualSize = n
|
||||
}
|
||||
}
|
||||
if fi.Checksum == nil {
|
||||
// If ServerSideChecksum is wanted for this object, it takes precedence
|
||||
// over opts.WantChecksum.
|
||||
if opts.WantServerSideChecksumType.IsSet() {
|
||||
serverSideChecksum := r.RawServerSideChecksumResult()
|
||||
if serverSideChecksum != nil {
|
||||
fi.Checksum = serverSideChecksum.AppendTo(nil, nil)
|
||||
if opts.EncryptFn != nil {
|
||||
fi.Checksum = opts.EncryptFn("object-checksum", fi.Checksum)
|
||||
}
|
||||
}
|
||||
} else if fi.Checksum == nil && opts.WantChecksum != nil {
|
||||
// Trailing headers checksums should now be filled.
|
||||
fi.Checksum = opts.WantChecksum.AppendTo(nil, nil)
|
||||
if opts.EncryptFn != nil {
|
||||
|
||||
@@ -1340,12 +1340,15 @@ func (z *erasureServerPools) CopyObject(ctx context.Context, srcBucket, srcObjec
|
||||
}
|
||||
|
||||
putOpts := ObjectOptions{
|
||||
ServerSideEncryption: dstOpts.ServerSideEncryption,
|
||||
UserDefined: srcInfo.UserDefined,
|
||||
Versioned: dstOpts.Versioned,
|
||||
VersionID: dstOpts.VersionID,
|
||||
MTime: dstOpts.MTime,
|
||||
NoLock: true,
|
||||
ServerSideEncryption: dstOpts.ServerSideEncryption,
|
||||
UserDefined: srcInfo.UserDefined,
|
||||
Versioned: dstOpts.Versioned,
|
||||
VersionID: dstOpts.VersionID,
|
||||
MTime: dstOpts.MTime,
|
||||
NoLock: true,
|
||||
EncryptFn: dstOpts.EncryptFn,
|
||||
WantChecksum: dstOpts.WantChecksum,
|
||||
WantServerSideChecksumType: dstOpts.WantServerSideChecksumType,
|
||||
}
|
||||
|
||||
return z.serverPools[poolIdx].PutObject(ctx, dstBucket, dstObject, srcInfo.PutObjReader, putOpts)
|
||||
|
||||
@@ -868,11 +868,14 @@ func (s *erasureSets) CopyObject(ctx context.Context, srcBucket, srcObject, dstB
|
||||
}
|
||||
|
||||
putOpts := ObjectOptions{
|
||||
ServerSideEncryption: dstOpts.ServerSideEncryption,
|
||||
UserDefined: srcInfo.UserDefined,
|
||||
Versioned: dstOpts.Versioned,
|
||||
VersionID: dstOpts.VersionID,
|
||||
MTime: dstOpts.MTime,
|
||||
ServerSideEncryption: dstOpts.ServerSideEncryption,
|
||||
UserDefined: srcInfo.UserDefined,
|
||||
Versioned: dstOpts.Versioned,
|
||||
VersionID: dstOpts.VersionID,
|
||||
MTime: dstOpts.MTime,
|
||||
EncryptFn: dstOpts.EncryptFn,
|
||||
WantChecksum: dstOpts.WantChecksum,
|
||||
WantServerSideChecksumType: dstOpts.WantServerSideChecksumType,
|
||||
}
|
||||
|
||||
return dstSet.putObject(ctx, dstBucket, dstObject, srcInfo.PutObjReader, putOpts)
|
||||
|
||||
@@ -152,6 +152,10 @@ func encLogIf(ctx context.Context, err error, errKind ...interface{}) {
|
||||
logger.LogIf(ctx, "encryption", err, errKind...)
|
||||
}
|
||||
|
||||
func encLogOnceIf(ctx context.Context, err error, id string, errKind ...interface{}) {
|
||||
logger.LogOnceIf(ctx, "encryption", err, id, errKind...)
|
||||
}
|
||||
|
||||
func storageLogIf(ctx context.Context, err error, errKind ...interface{}) {
|
||||
logger.LogIf(ctx, "storage", err, errKind...)
|
||||
}
|
||||
|
||||
@@ -654,6 +654,7 @@ type objectAttributesChecksum struct {
|
||||
ChecksumSHA1 string `xml:",omitempty"`
|
||||
ChecksumSHA256 string `xml:",omitempty"`
|
||||
ChecksumCRC64NVME string `xml:",omitempty"`
|
||||
ChecksumType string `xml:",omitempty"`
|
||||
}
|
||||
|
||||
type objectAttributesParts struct {
|
||||
|
||||
@@ -86,6 +86,8 @@ type ObjectOptions struct {
|
||||
|
||||
WantChecksum *hash.Checksum // x-amz-checksum-XXX checksum sent to PutObject/ CompleteMultipartUpload.
|
||||
|
||||
WantServerSideChecksumType hash.ChecksumType // if set, we compute a server-side checksum of this type
|
||||
|
||||
NoDecryption bool // indicates if the stream must be decrypted.
|
||||
PreserveETag string // preserves this etag during a PUT call.
|
||||
NoLock bool // indicates to lower layers if the caller is expecting to hold locks.
|
||||
|
||||
@@ -1096,6 +1096,16 @@ func NewPutObjReader(rawReader *hash.Reader) *PutObjReader {
|
||||
return &PutObjReader{Reader: rawReader, rawReader: rawReader}
|
||||
}
|
||||
|
||||
// RawServerSideChecksumResult returns the ServerSideChecksumResult from the
|
||||
// underlying rawReader, since the PutObjReader might be encrypted data and
|
||||
// thus any checksum from that would be incorrect.
|
||||
func (p *PutObjReader) RawServerSideChecksumResult() *hash.Checksum {
|
||||
if p.rawReader != nil {
|
||||
return p.rawReader.ServerSideChecksumResult
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sealETag(encKey crypto.ObjectKey, md5CurrSum []byte) []byte {
|
||||
var emptyKey [32]byte
|
||||
if bytes.Equal(encKey[:], emptyKey[:]) {
|
||||
|
||||
@@ -641,6 +641,7 @@ func (api objectAPIHandlers) getObjectAttributesHandler(ctx context.Context, obj
|
||||
ChecksumSHA1: strings.Split(chkSums["SHA1"], "-")[0],
|
||||
ChecksumSHA256: strings.Split(chkSums["SHA256"], "-")[0],
|
||||
ChecksumCRC64NVME: strings.Split(chkSums["CRC64NVME"], "-")[0],
|
||||
ChecksumType: chkSums[xhttp.AmzChecksumType],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1465,6 +1466,46 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
targetSize, _ = srcInfo.DecryptedSize()
|
||||
}
|
||||
|
||||
// Client can request that a different type of checksum is computed server-side for the
|
||||
// destination object using the x-amz-checksum-algorithm header.
|
||||
headerChecksumType := hash.NewChecksumHeader(r.Header)
|
||||
if headerChecksumType.IsSet() {
|
||||
dstOpts.WantServerSideChecksumType = headerChecksumType.Base()
|
||||
srcInfo.Reader.AddServerSideChecksumHasher(headerChecksumType)
|
||||
dstOpts.WantChecksum = nil
|
||||
} else {
|
||||
// Check the source object for checksum.
|
||||
// If Checksum is not encrypted, decryptChecksum will be a no-op and return
|
||||
// the already unencrypted value.
|
||||
srcChecksumDecrypted, err := srcInfo.decryptChecksum(r.Header)
|
||||
if err != nil {
|
||||
encLogOnceIf(GlobalContext,
|
||||
fmt.Errorf("Unable to decryptChecksum for object: %s/%s, error: %w", srcBucket, srcObject, err),
|
||||
"copy-object-decrypt-checksums-"+srcBucket+srcObject)
|
||||
}
|
||||
|
||||
// The source object has a checksum set, we need the destination to have one too.
|
||||
if srcChecksumDecrypted != nil {
|
||||
dstOpts.WantChecksum = hash.ChecksumFromBytes(srcChecksumDecrypted)
|
||||
|
||||
// When an object is being copied from a source that is multipart, the destination will
|
||||
// no longer be multipart, and thus the checksum becomes full-object instead. Since
|
||||
// the CopyObject API does not require that the caller send us this final checksum, we need
|
||||
// to compute it server-side, with the same type as the source object.
|
||||
if dstOpts.WantChecksum != nil && dstOpts.WantChecksum.Type.IsMultipartComposite() {
|
||||
dstOpts.WantServerSideChecksumType = dstOpts.WantChecksum.Type.Base()
|
||||
srcInfo.Reader.AddServerSideChecksumHasher(dstOpts.WantServerSideChecksumType)
|
||||
dstOpts.WantChecksum = nil
|
||||
}
|
||||
} else {
|
||||
// S3: All copied objects without checksums and specified destination checksum algorithms
|
||||
// automatically gain a CRC-64NVME checksum algorithm.
|
||||
dstOpts.WantServerSideChecksumType = hash.ChecksumCRC64NVME
|
||||
srcInfo.Reader.AddServerSideChecksumHasher(dstOpts.WantServerSideChecksumType)
|
||||
dstOpts.WantChecksum = nil
|
||||
}
|
||||
}
|
||||
|
||||
if isTargetEncrypted {
|
||||
var encReader io.Reader
|
||||
kind, _ := crypto.IsRequested(r.Header)
|
||||
@@ -1498,6 +1539,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
if dstOpts.IndexCB != nil {
|
||||
dstOpts.IndexCB = compressionIndexEncrypter(objEncKey, dstOpts.IndexCB)
|
||||
}
|
||||
dstOpts.EncryptFn = metadataEncrypter(objEncKey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1633,6 +1675,13 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
return
|
||||
}
|
||||
|
||||
// After we've checked for an invalid copy (above), if a server-side checksum type
|
||||
// is requested, we need to read the source to recompute the checksum.
|
||||
if dstOpts.WantServerSideChecksumType.IsSet() {
|
||||
srcInfo.metadataOnly = false
|
||||
}
|
||||
|
||||
// Federation only.
|
||||
remoteCallRequired := isRemoteCopyRequired(ctx, srcBucket, dstBucket, objectAPI)
|
||||
|
||||
var objInfo ObjectInfo
|
||||
|
||||
Reference in New Issue
Block a user