storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/gateway/s3/gateway-s3-sse.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2018 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package s3
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"io"
    23  	"net/http"
    24  	"path"
    25  	"strconv"
    26  	"strings"
    27  	"time"
    28  
    29  	"github.com/minio/minio-go/v7/pkg/encrypt"
    30  
    31  	minio "storj.io/minio/cmd"
    32  
    33  	"storj.io/minio/cmd/logger"
    34  )
    35  
    36  const (
    37  	// name of custom multipart metadata file for s3 backend.
    38  	gwdareMetaJSON string = "dare.meta"
    39  
    40  	// name of temporary per part metadata file
    41  	gwpartMetaJSON string = "part.meta"
    42  	// custom multipart files are stored under the defaultMinioGWPrefix
    43  	defaultMinioGWPrefix     = ".minio"
    44  	defaultGWContentFileName = "data"
    45  )
    46  
    47  // s3EncObjects is a wrapper around s3Objects and implements gateway calls for
    48  // custom large objects encrypted at the gateway
    49  type s3EncObjects struct {
    50  	s3Objects
    51  }
    52  
    53  /*
    54   NOTE:
    55   Custom gateway encrypted objects are stored on backend as follows:
    56  	 obj/.minio/data   <= encrypted content
    57  	 obj/.minio/dare.meta  <= metadata
    58  
    59   When a multipart upload operation is in progress, the metadata set during
    60   NewMultipartUpload is stored in obj/.minio/uploadID/dare.meta and each
    61   UploadPart operation saves additional state of the part's encrypted ETag and
    62   encrypted size in obj/.minio/uploadID/part1/part.meta
    63  
    64   All the part metadata and temp dare.meta are cleaned up when upload completes
    65  */
    66  
    67  // ListObjects lists all blobs in S3 bucket filtered by prefix
    68  func (l *s3EncObjects) ListObjects(ctx context.Context, bucket string, prefix string, marker string, delimiter string, maxKeys int) (loi minio.ListObjectsInfo, e error) {
    69  	var startAfter string
    70  	res, err := l.ListObjectsV2(ctx, bucket, prefix, marker, delimiter, maxKeys, false, startAfter)
    71  	if err != nil {
    72  		return loi, err
    73  	}
    74  	loi.IsTruncated = res.IsTruncated
    75  	loi.NextMarker = res.NextContinuationToken
    76  	loi.Objects = res.Objects
    77  	loi.Prefixes = res.Prefixes
    78  	return loi, nil
    79  
    80  }
    81  
    82  // ListObjectsV2 lists all blobs in S3 bucket filtered by prefix
    83  func (l *s3EncObjects) ListObjectsV2(ctx context.Context, bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (loi minio.ListObjectsV2Info, e error) {
    84  
    85  	var objects []minio.ObjectInfo
    86  	var prefixes []string
    87  	var isTruncated bool
    88  
    89  	// filter out objects that contain a .minio prefix, but is not a dare.meta metadata file.
    90  	for {
    91  		loi, e = l.s3Objects.ListObjectsV2(ctx, bucket, prefix, continuationToken, delimiter, 1000, fetchOwner, startAfter)
    92  		if e != nil {
    93  			return loi, minio.ErrorRespToObjectError(e, bucket)
    94  		}
    95  
    96  		continuationToken = loi.NextContinuationToken
    97  		isTruncated = loi.IsTruncated
    98  
    99  		for _, obj := range loi.Objects {
   100  			startAfter = obj.Name
   101  
   102  			if !isGWObject(obj.Name) {
   103  				continue
   104  			}
   105  			// get objectname and ObjectInfo from the custom metadata file
   106  			if strings.HasSuffix(obj.Name, gwdareMetaJSON) {
   107  				objSlice := strings.Split(obj.Name, minio.SlashSeparator+defaultMinioGWPrefix)
   108  				gwMeta, e := l.getGWMetadata(ctx, bucket, getDareMetaPath(objSlice[0]))
   109  				if e != nil {
   110  					continue
   111  				}
   112  				oInfo := gwMeta.ToObjectInfo(bucket, objSlice[0])
   113  				objects = append(objects, oInfo)
   114  			} else {
   115  				objects = append(objects, obj)
   116  			}
   117  			if len(objects) > maxKeys {
   118  				break
   119  			}
   120  		}
   121  		for _, p := range loi.Prefixes {
   122  			objName := strings.TrimSuffix(p, minio.SlashSeparator)
   123  			gm, err := l.getGWMetadata(ctx, bucket, getDareMetaPath(objName))
   124  			// if prefix is actually a custom multi-part object, append it to objects
   125  			if err == nil {
   126  				objects = append(objects, gm.ToObjectInfo(bucket, objName))
   127  				continue
   128  			}
   129  			isPrefix := l.isPrefix(ctx, bucket, p, fetchOwner, startAfter)
   130  			if isPrefix {
   131  				prefixes = append(prefixes, p)
   132  			}
   133  		}
   134  		if (len(objects) > maxKeys) || !loi.IsTruncated {
   135  			break
   136  		}
   137  	}
   138  
   139  	loi.IsTruncated = isTruncated
   140  	loi.ContinuationToken = continuationToken
   141  	loi.Objects = make([]minio.ObjectInfo, 0)
   142  	loi.Prefixes = make([]string, 0)
   143  	loi.Objects = append(loi.Objects, objects...)
   144  
   145  	for _, pfx := range prefixes {
   146  		if pfx != prefix {
   147  			loi.Prefixes = append(loi.Prefixes, pfx)
   148  		}
   149  	}
   150  	// Set continuation token if s3 returned truncated list
   151  	if isTruncated {
   152  		if len(objects) > 0 {
   153  			loi.NextContinuationToken = objects[len(objects)-1].Name
   154  		}
   155  	}
   156  	return loi, nil
   157  }
   158  
   159  // isGWObject returns true if it is a custom object
   160  func isGWObject(objName string) bool {
   161  	isEncrypted := strings.Contains(objName, defaultMinioGWPrefix)
   162  	if !isEncrypted {
   163  		return true
   164  	}
   165  	// ignore temp part.meta files
   166  	if strings.Contains(objName, gwpartMetaJSON) {
   167  		return false
   168  	}
   169  
   170  	pfxSlice := strings.Split(objName, minio.SlashSeparator)
   171  	var i1, i2 int
   172  	for i := len(pfxSlice) - 1; i >= 0; i-- {
   173  		p := pfxSlice[i]
   174  		if p == defaultMinioGWPrefix {
   175  			i1 = i
   176  		}
   177  		if p == gwdareMetaJSON {
   178  			i2 = i
   179  		}
   180  		if i1 > 0 && i2 > 0 {
   181  			break
   182  		}
   183  	}
   184  	// incomplete uploads would have a uploadID between defaultMinioGWPrefix and gwdareMetaJSON
   185  	return i2 > 0 && i1 > 0 && i2-i1 == 1
   186  }
   187  
   188  // isPrefix returns true if prefix exists and is not an incomplete multipart upload entry
   189  func (l *s3EncObjects) isPrefix(ctx context.Context, bucket, prefix string, fetchOwner bool, startAfter string) bool {
   190  	var continuationToken, delimiter string
   191  
   192  	for {
   193  		loi, e := l.s3Objects.ListObjectsV2(ctx, bucket, prefix, continuationToken, delimiter, 1000, fetchOwner, startAfter)
   194  		if e != nil {
   195  			return false
   196  		}
   197  		for _, obj := range loi.Objects {
   198  			if isGWObject(obj.Name) {
   199  				return true
   200  			}
   201  		}
   202  
   203  		continuationToken = loi.NextContinuationToken
   204  		if !loi.IsTruncated {
   205  			break
   206  		}
   207  	}
   208  	return false
   209  }
   210  
   211  // GetObject reads an object from S3. Supports additional
   212  // parameters like offset and length which are synonymous with
   213  // HTTP Range requests.
   214  func (l *s3EncObjects) GetObject(ctx context.Context, bucket string, key string, startOffset int64, length int64, writer io.Writer, etag string, opts minio.ObjectOptions) error {
   215  	return l.getObject(ctx, bucket, key, startOffset, length, writer, etag, opts)
   216  }
   217  
   218  func (l *s3EncObjects) isGWEncrypted(ctx context.Context, bucket, object string) bool {
   219  	_, err := l.s3Objects.GetObjectInfo(ctx, bucket, getDareMetaPath(object), minio.ObjectOptions{})
   220  	return err == nil
   221  }
   222  
   223  // getDaremetadata fetches dare.meta from s3 backend and marshals into a structured format.
   224  func (l *s3EncObjects) getGWMetadata(ctx context.Context, bucket, metaFileName string) (m gwMetaV1, err error) {
   225  	oi, err1 := l.s3Objects.GetObjectInfo(ctx, bucket, metaFileName, minio.ObjectOptions{})
   226  	if err1 != nil {
   227  		return m, err1
   228  	}
   229  	var buffer bytes.Buffer
   230  	err = l.s3Objects.getObject(ctx, bucket, metaFileName, 0, oi.Size, &buffer, oi.ETag, minio.ObjectOptions{})
   231  	if err != nil {
   232  		return m, err
   233  	}
   234  	return readGWMetadata(ctx, buffer)
   235  }
   236  
   237  // writes dare metadata to the s3 backend
   238  func (l *s3EncObjects) writeGWMetadata(ctx context.Context, bucket, metaFileName string, m gwMetaV1, o minio.ObjectOptions) error {
   239  	reader, err := getGWMetadata(ctx, bucket, metaFileName, m)
   240  	if err != nil {
   241  		logger.LogIf(ctx, err)
   242  		return err
   243  	}
   244  	_, err = l.s3Objects.PutObject(ctx, bucket, metaFileName, reader, o)
   245  	return err
   246  }
   247  
   248  // returns path of temporary metadata json file for the upload
   249  func getTmpDareMetaPath(object, uploadID string) string {
   250  	return path.Join(getGWMetaPath(object), uploadID, gwdareMetaJSON)
   251  }
   252  
   253  // returns path of metadata json file for encrypted objects
   254  func getDareMetaPath(object string) string {
   255  	return path.Join(getGWMetaPath(object), gwdareMetaJSON)
   256  }
   257  
   258  // returns path of temporary part metadata file for multipart uploads
   259  func getPartMetaPath(object, uploadID string, partID int) string {
   260  	return path.Join(object, defaultMinioGWPrefix, uploadID, strconv.Itoa(partID), gwpartMetaJSON)
   261  }
   262  
   263  // deletes the custom dare metadata file saved at the backend
   264  func (l *s3EncObjects) deleteGWMetadata(ctx context.Context, bucket, metaFileName string) (minio.ObjectInfo, error) {
   265  	return l.s3Objects.DeleteObject(ctx, bucket, metaFileName, minio.ObjectOptions{})
   266  }
   267  
   268  func (l *s3EncObjects) getObject(ctx context.Context, bucket string, key string, startOffset int64, length int64, writer io.Writer, etag string, opts minio.ObjectOptions) error {
   269  	var o minio.ObjectOptions
   270  	if minio.GlobalGatewaySSE.SSEC() {
   271  		o = opts
   272  	}
   273  	dmeta, err := l.getGWMetadata(ctx, bucket, getDareMetaPath(key))
   274  	if err != nil {
   275  		// unencrypted content
   276  		return l.s3Objects.getObject(ctx, bucket, key, startOffset, length, writer, etag, o)
   277  	}
   278  	if startOffset < 0 {
   279  		logger.LogIf(ctx, minio.InvalidRange{})
   280  	}
   281  
   282  	// For negative length read everything.
   283  	if length < 0 {
   284  		length = dmeta.Stat.Size - startOffset
   285  	}
   286  	// Reply back invalid range if the input offset and length fall out of range.
   287  	if startOffset > dmeta.Stat.Size || startOffset+length > dmeta.Stat.Size {
   288  		logger.LogIf(ctx, minio.InvalidRange{OffsetBegin: startOffset, OffsetEnd: length, ResourceSize: dmeta.Stat.Size})
   289  		return minio.InvalidRange{OffsetBegin: startOffset, OffsetEnd: length, ResourceSize: dmeta.Stat.Size}
   290  	}
   291  	// Get start part index and offset.
   292  	_, partOffset, err := dmeta.ObjectToPartOffset(ctx, startOffset)
   293  	if err != nil {
   294  		return minio.InvalidRange{OffsetBegin: startOffset, OffsetEnd: length, ResourceSize: dmeta.Stat.Size}
   295  	}
   296  
   297  	// Calculate endOffset according to length
   298  	endOffset := startOffset
   299  	if length > 0 {
   300  		endOffset += length - 1
   301  	}
   302  
   303  	// Get last part index to read given length.
   304  	if _, _, err := dmeta.ObjectToPartOffset(ctx, endOffset); err != nil {
   305  		return minio.InvalidRange{OffsetBegin: startOffset, OffsetEnd: length, ResourceSize: dmeta.Stat.Size}
   306  	}
   307  	return l.s3Objects.getObject(ctx, bucket, key, partOffset, endOffset, writer, dmeta.ETag, o)
   308  }
   309  
   310  // GetObjectNInfo - returns object info and locked object ReadCloser
   311  func (l *s3EncObjects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *minio.HTTPRangeSpec, h http.Header, lockType minio.LockType, o minio.ObjectOptions) (gr *minio.GetObjectReader, err error) {
   312  	var opts minio.ObjectOptions
   313  	if minio.GlobalGatewaySSE.SSEC() {
   314  		opts = o
   315  	}
   316  	objInfo, err := l.GetObjectInfo(ctx, bucket, object, opts)
   317  	if err != nil {
   318  		return l.s3Objects.GetObjectNInfo(ctx, bucket, object, rs, h, lockType, opts)
   319  	}
   320  	fn, off, length, err := minio.NewGetObjectReader(rs, objInfo, opts)
   321  	if err != nil {
   322  		return nil, minio.ErrorRespToObjectError(err, bucket, object)
   323  	}
   324  	if l.isGWEncrypted(ctx, bucket, object) {
   325  		object = getGWContentPath(object)
   326  	}
   327  	pr, pw := io.Pipe()
   328  	go func() {
   329  		// Do not set an `If-Match` header for the ETag when
   330  		// the ETag is encrypted. The ETag at the backend never
   331  		// matches an encrypted ETag and there is in any case
   332  		// no way to make two consecutive S3 calls safe for concurrent
   333  		// access.
   334  		// However,  the encrypted object changes concurrently then the
   335  		// gateway will not be able to decrypt it since the key (obtained
   336  		// from dare.meta) will not work for any new created object. Therefore,
   337  		// we will in any case not return invalid data to the client.
   338  		etag := objInfo.ETag
   339  		if len(etag) > 32 && strings.Count(etag, "-") == 0 {
   340  			etag = ""
   341  		}
   342  		err := l.getObject(ctx, bucket, object, off, length, pw, etag, opts)
   343  		pw.CloseWithError(err)
   344  	}()
   345  
   346  	// Setup cleanup function to cause the above go-routine to
   347  	// exit in case of partial read
   348  	pipeCloser := func() { pr.Close() }
   349  	return fn(pr, h, o.CheckPrecondFn, pipeCloser)
   350  }
   351  
   352  // GetObjectInfo reads object info and replies back ObjectInfo
   353  // For custom gateway encrypted large objects, the ObjectInfo is retrieved from the dare.meta file.
   354  func (l *s3EncObjects) GetObjectInfo(ctx context.Context, bucket string, object string, o minio.ObjectOptions) (objInfo minio.ObjectInfo, err error) {
   355  	var opts minio.ObjectOptions
   356  	if minio.GlobalGatewaySSE.SSEC() {
   357  		opts = o
   358  	}
   359  
   360  	gwMeta, err := l.getGWMetadata(ctx, bucket, getDareMetaPath(object))
   361  	if err != nil {
   362  		return l.s3Objects.GetObjectInfo(ctx, bucket, object, opts)
   363  	}
   364  	return gwMeta.ToObjectInfo(bucket, object), nil
   365  }
   366  
   367  // CopyObject copies an object from source bucket to a destination bucket.
   368  func (l *s3EncObjects) CopyObject(ctx context.Context, srcBucket string, srcObject string, dstBucket string, dstObject string, srcInfo minio.ObjectInfo, s, d minio.ObjectOptions) (objInfo minio.ObjectInfo, err error) {
   369  	cpSrcDstSame := path.Join(srcBucket, srcObject) == path.Join(dstBucket, dstObject)
   370  	if cpSrcDstSame {
   371  		var gwMeta gwMetaV1
   372  		if s.ServerSideEncryption != nil && d.ServerSideEncryption != nil &&
   373  			((s.ServerSideEncryption.Type() == encrypt.SSEC && d.ServerSideEncryption.Type() == encrypt.SSEC) ||
   374  				(s.ServerSideEncryption.Type() == encrypt.S3 && d.ServerSideEncryption.Type() == encrypt.S3)) {
   375  			gwMeta, err = l.getGWMetadata(ctx, srcBucket, getDareMetaPath(srcObject))
   376  			if err != nil {
   377  				return
   378  			}
   379  			header := make(http.Header)
   380  			if d.ServerSideEncryption != nil {
   381  				d.ServerSideEncryption.Marshal(header)
   382  			}
   383  			for k, v := range header {
   384  				srcInfo.UserDefined[k] = v[0]
   385  			}
   386  			gwMeta.Meta = srcInfo.UserDefined
   387  			if err = l.writeGWMetadata(ctx, dstBucket, getDareMetaPath(dstObject), gwMeta, minio.ObjectOptions{}); err != nil {
   388  				return objInfo, minio.ErrorRespToObjectError(err)
   389  			}
   390  			return gwMeta.ToObjectInfo(dstBucket, dstObject), nil
   391  		}
   392  	}
   393  	dstOpts := minio.ObjectOptions{ServerSideEncryption: d.ServerSideEncryption, UserDefined: srcInfo.UserDefined}
   394  	return l.PutObject(ctx, dstBucket, dstObject, srcInfo.PutObjReader, dstOpts)
   395  }
   396  
   397  // DeleteObject deletes a blob in bucket
   398  // For custom gateway encrypted large objects, cleans up encrypted content and metadata files
   399  // from the backend.
   400  func (l *s3EncObjects) DeleteObject(ctx context.Context, bucket string, object string, opts minio.ObjectOptions) (minio.ObjectInfo, error) {
   401  	// Get dare meta json
   402  	if _, err := l.getGWMetadata(ctx, bucket, getDareMetaPath(object)); err != nil {
   403  		logger.LogIf(minio.GlobalContext, err)
   404  		return l.s3Objects.DeleteObject(ctx, bucket, object, opts)
   405  	}
   406  	// delete encrypted object
   407  	l.s3Objects.DeleteObject(ctx, bucket, getGWContentPath(object), opts)
   408  	return l.deleteGWMetadata(ctx, bucket, getDareMetaPath(object))
   409  }
   410  
   411  func (l *s3EncObjects) DeleteObjects(ctx context.Context, bucket string, objects []minio.ObjectToDelete, opts minio.ObjectOptions) ([]minio.DeletedObject, []error) {
   412  	errs := make([]error, len(objects))
   413  	dobjects := make([]minio.DeletedObject, len(objects))
   414  	for idx, object := range objects {
   415  		_, errs[idx] = l.DeleteObject(ctx, bucket, object.ObjectName, opts)
   416  		if errs[idx] == nil {
   417  			dobjects[idx] = minio.DeletedObject{
   418  				ObjectName: object.ObjectName,
   419  			}
   420  		}
   421  	}
   422  	return dobjects, errs
   423  }
   424  
   425  // ListMultipartUploads lists all multipart uploads.
   426  func (l *s3EncObjects) ListMultipartUploads(ctx context.Context, bucket string, prefix string, keyMarker string, uploadIDMarker string, delimiter string, maxUploads int) (lmi minio.ListMultipartsInfo, e error) {
   427  
   428  	lmi, e = l.s3Objects.ListMultipartUploads(ctx, bucket, prefix, keyMarker, uploadIDMarker, delimiter, maxUploads)
   429  	if e != nil {
   430  		return
   431  	}
   432  	lmi.KeyMarker = strings.TrimSuffix(lmi.KeyMarker, getGWContentPath(minio.SlashSeparator))
   433  	lmi.NextKeyMarker = strings.TrimSuffix(lmi.NextKeyMarker, getGWContentPath(minio.SlashSeparator))
   434  	for i := range lmi.Uploads {
   435  		lmi.Uploads[i].Object = strings.TrimSuffix(lmi.Uploads[i].Object, getGWContentPath(minio.SlashSeparator))
   436  	}
   437  	return
   438  }
   439  
   440  // NewMultipartUpload uploads object in multiple parts
   441  func (l *s3EncObjects) NewMultipartUpload(ctx context.Context, bucket string, object string, o minio.ObjectOptions) (uploadID string, err error) {
   442  	var sseOpts encrypt.ServerSide
   443  	if o.ServerSideEncryption == nil {
   444  		return l.s3Objects.NewMultipartUpload(ctx, bucket, object, minio.ObjectOptions{UserDefined: o.UserDefined})
   445  	}
   446  	// Decide if sse options needed to be passed to backend
   447  	if (minio.GlobalGatewaySSE.SSEC() && o.ServerSideEncryption.Type() == encrypt.SSEC) ||
   448  		(minio.GlobalGatewaySSE.SSES3() && o.ServerSideEncryption.Type() == encrypt.S3) {
   449  		sseOpts = o.ServerSideEncryption
   450  	}
   451  
   452  	uploadID, err = l.s3Objects.NewMultipartUpload(ctx, bucket, getGWContentPath(object), minio.ObjectOptions{ServerSideEncryption: sseOpts})
   453  	if err != nil {
   454  		return
   455  	}
   456  	// Create uploadID and write a temporary dare.meta object under object/uploadID prefix
   457  	gwmeta := newGWMetaV1()
   458  	gwmeta.Meta = o.UserDefined
   459  	gwmeta.Stat.ModTime = time.Now().UTC()
   460  	err = l.writeGWMetadata(ctx, bucket, getTmpDareMetaPath(object, uploadID), gwmeta, minio.ObjectOptions{})
   461  	if err != nil {
   462  		return uploadID, minio.ErrorRespToObjectError(err)
   463  	}
   464  	return uploadID, nil
   465  }
   466  
   467  // PutObject creates a new object with the incoming data,
   468  func (l *s3EncObjects) PutObject(ctx context.Context, bucket string, object string, data *minio.PutObjReader, opts minio.ObjectOptions) (objInfo minio.ObjectInfo, err error) {
   469  	var sseOpts encrypt.ServerSide
   470  	// Decide if sse options needed to be passed to backend
   471  	if opts.ServerSideEncryption != nil &&
   472  		((minio.GlobalGatewaySSE.SSEC() && opts.ServerSideEncryption.Type() == encrypt.SSEC) ||
   473  			(minio.GlobalGatewaySSE.SSES3() && opts.ServerSideEncryption.Type() == encrypt.S3) ||
   474  			opts.ServerSideEncryption.Type() == encrypt.KMS) {
   475  		sseOpts = opts.ServerSideEncryption
   476  	}
   477  	if opts.ServerSideEncryption == nil {
   478  		defer l.deleteGWMetadata(ctx, bucket, getDareMetaPath(object))
   479  		defer l.DeleteObject(ctx, bucket, getGWContentPath(object), opts)
   480  		return l.s3Objects.PutObject(ctx, bucket, object, data, minio.ObjectOptions{UserDefined: opts.UserDefined})
   481  	}
   482  
   483  	oi, err := l.s3Objects.PutObject(ctx, bucket, getGWContentPath(object), data, minio.ObjectOptions{ServerSideEncryption: sseOpts})
   484  	if err != nil {
   485  		return objInfo, minio.ErrorRespToObjectError(err)
   486  	}
   487  
   488  	gwMeta := newGWMetaV1()
   489  	gwMeta.Meta = make(map[string]string)
   490  	for k, v := range opts.UserDefined {
   491  		gwMeta.Meta[k] = v
   492  	}
   493  	encMD5 := data.MD5CurrentHexString()
   494  
   495  	gwMeta.ETag = encMD5
   496  	gwMeta.Stat.Size = oi.Size
   497  	gwMeta.Stat.ModTime = time.Now().UTC()
   498  	if err = l.writeGWMetadata(ctx, bucket, getDareMetaPath(object), gwMeta, minio.ObjectOptions{}); err != nil {
   499  		return objInfo, minio.ErrorRespToObjectError(err)
   500  	}
   501  	objInfo = gwMeta.ToObjectInfo(bucket, object)
   502  	// delete any unencrypted content of the same name created previously
   503  	l.s3Objects.DeleteObject(ctx, bucket, object, opts)
   504  	return objInfo, nil
   505  }
   506  
   507  // PutObjectPart puts a part of object in bucket
   508  func (l *s3EncObjects) PutObjectPart(ctx context.Context, bucket string, object string, uploadID string, partID int, data *minio.PutObjReader, opts minio.ObjectOptions) (pi minio.PartInfo, e error) {
   509  
   510  	if opts.ServerSideEncryption == nil {
   511  		return l.s3Objects.PutObjectPart(ctx, bucket, object, uploadID, partID, data, opts)
   512  	}
   513  
   514  	var s3Opts minio.ObjectOptions
   515  	// for sse-s3 encryption options should not be passed to backend
   516  	if opts.ServerSideEncryption != nil && opts.ServerSideEncryption.Type() == encrypt.SSEC && minio.GlobalGatewaySSE.SSEC() {
   517  		s3Opts = opts
   518  	}
   519  
   520  	uploadPath := getTmpGWMetaPath(object, uploadID)
   521  	tmpDareMeta := path.Join(uploadPath, gwdareMetaJSON)
   522  	_, err := l.s3Objects.GetObjectInfo(ctx, bucket, tmpDareMeta, minio.ObjectOptions{})
   523  	if err != nil {
   524  		return pi, minio.InvalidUploadID{UploadID: uploadID}
   525  	}
   526  
   527  	pi, e = l.s3Objects.PutObjectPart(ctx, bucket, getGWContentPath(object), uploadID, partID, data, s3Opts)
   528  	if e != nil {
   529  		return
   530  	}
   531  	gwMeta := newGWMetaV1()
   532  	gwMeta.Parts = make([]minio.ObjectPartInfo, 1)
   533  	// Add incoming part.
   534  	gwMeta.Parts[0] = minio.ObjectPartInfo{
   535  		Number: partID,
   536  		ETag:   pi.ETag,
   537  		Size:   pi.Size,
   538  	}
   539  	gwMeta.ETag = data.MD5CurrentHexString() // encrypted ETag
   540  	gwMeta.Stat.Size = pi.Size
   541  	gwMeta.Stat.ModTime = pi.LastModified
   542  
   543  	if err = l.writeGWMetadata(ctx, bucket, getPartMetaPath(object, uploadID, partID), gwMeta, minio.ObjectOptions{}); err != nil {
   544  		return pi, minio.ErrorRespToObjectError(err)
   545  	}
   546  	return minio.PartInfo{
   547  		Size:         gwMeta.Stat.Size,
   548  		ETag:         minio.CanonicalizeETag(gwMeta.ETag),
   549  		LastModified: gwMeta.Stat.ModTime,
   550  		PartNumber:   partID,
   551  	}, nil
   552  }
   553  
   554  // CopyObjectPart creates a part in a multipart upload by copying
   555  // existing object or a part of it.
   556  func (l *s3EncObjects) CopyObjectPart(ctx context.Context, srcBucket, srcObject, destBucket, destObject, uploadID string,
   557  	partID int, startOffset, length int64, srcInfo minio.ObjectInfo, srcOpts, dstOpts minio.ObjectOptions) (p minio.PartInfo, err error) {
   558  	return l.PutObjectPart(ctx, destBucket, destObject, uploadID, partID, srcInfo.PutObjReader, dstOpts)
   559  }
   560  
   561  // GetMultipartInfo returns multipart info of the uploadId of the object
   562  func (l *s3EncObjects) GetMultipartInfo(ctx context.Context, bucket, object, uploadID string, opts minio.ObjectOptions) (result minio.MultipartInfo, err error) {
   563  	result.Bucket = bucket
   564  	result.Object = object
   565  	result.UploadID = uploadID
   566  	// We do not store parts uploaded so far in the dare.meta. Only CompleteMultipartUpload finalizes the parts under upload prefix.Otherwise,
   567  	// there could be situations of dare.meta getting corrupted by competing upload parts.
   568  	dm, err := l.getGWMetadata(ctx, bucket, getTmpDareMetaPath(object, uploadID))
   569  	if err != nil {
   570  		return l.s3Objects.GetMultipartInfo(ctx, bucket, object, uploadID, opts)
   571  	}
   572  	result.UserDefined = dm.ToObjectInfo(bucket, object).UserDefined
   573  	return result, nil
   574  }
   575  
   576  // ListObjectParts returns all object parts for specified object in specified bucket
   577  func (l *s3EncObjects) ListObjectParts(ctx context.Context, bucket string, object string, uploadID string, partNumberMarker int, maxParts int, opts minio.ObjectOptions) (lpi minio.ListPartsInfo, e error) {
   578  	// We do not store parts uploaded so far in the dare.meta. Only CompleteMultipartUpload finalizes the parts under upload prefix.Otherwise,
   579  	// there could be situations of dare.meta getting corrupted by competing upload parts.
   580  	dm, err := l.getGWMetadata(ctx, bucket, getTmpDareMetaPath(object, uploadID))
   581  	if err != nil {
   582  		return l.s3Objects.ListObjectParts(ctx, bucket, object, uploadID, partNumberMarker, maxParts, opts)
   583  	}
   584  
   585  	lpi, err = l.s3Objects.ListObjectParts(ctx, bucket, getGWContentPath(object), uploadID, partNumberMarker, maxParts, opts)
   586  	if err != nil {
   587  		return lpi, err
   588  	}
   589  	for i, part := range lpi.Parts {
   590  		partMeta, err := l.getGWMetadata(ctx, bucket, getPartMetaPath(object, uploadID, part.PartNumber))
   591  		if err != nil || len(partMeta.Parts) == 0 {
   592  			return lpi, minio.InvalidPart{}
   593  		}
   594  		lpi.Parts[i].ETag = partMeta.ETag
   595  	}
   596  	lpi.UserDefined = dm.ToObjectInfo(bucket, object).UserDefined
   597  	lpi.Object = object
   598  	return lpi, nil
   599  }
   600  
   601  // AbortMultipartUpload aborts a ongoing multipart upload
   602  func (l *s3EncObjects) AbortMultipartUpload(ctx context.Context, bucket string, object string, uploadID string, opts minio.ObjectOptions) error {
   603  	if _, err := l.getGWMetadata(ctx, bucket, getTmpDareMetaPath(object, uploadID)); err != nil {
   604  		return l.s3Objects.AbortMultipartUpload(ctx, bucket, object, uploadID, opts)
   605  	}
   606  
   607  	if err := l.s3Objects.AbortMultipartUpload(ctx, bucket, getGWContentPath(object), uploadID, opts); err != nil {
   608  		return err
   609  	}
   610  
   611  	uploadPrefix := getTmpGWMetaPath(object, uploadID)
   612  	var continuationToken, startAfter, delimiter string
   613  	for {
   614  		loi, err := l.s3Objects.ListObjectsV2(ctx, bucket, uploadPrefix, continuationToken, delimiter, 1000, false, startAfter)
   615  		if err != nil {
   616  			return minio.InvalidUploadID{UploadID: uploadID}
   617  		}
   618  		for _, obj := range loi.Objects {
   619  			if _, err := l.s3Objects.DeleteObject(ctx, bucket, obj.Name, minio.ObjectOptions{}); err != nil {
   620  				return minio.ErrorRespToObjectError(err)
   621  			}
   622  			startAfter = obj.Name
   623  		}
   624  		continuationToken = loi.NextContinuationToken
   625  		if !loi.IsTruncated {
   626  			break
   627  		}
   628  	}
   629  	return nil
   630  }
   631  
   632  // CompleteMultipartUpload completes ongoing multipart upload and finalizes object
   633  func (l *s3EncObjects) CompleteMultipartUpload(ctx context.Context, bucket, object, uploadID string, uploadedParts []minio.CompletePart, opts minio.ObjectOptions) (oi minio.ObjectInfo, e error) {
   634  
   635  	tmpMeta, err := l.getGWMetadata(ctx, bucket, getTmpDareMetaPath(object, uploadID))
   636  	if err != nil {
   637  		oi, e = l.s3Objects.CompleteMultipartUpload(ctx, bucket, object, uploadID, uploadedParts, opts)
   638  		if e == nil {
   639  			// delete any encrypted version of object that might exist
   640  			defer l.deleteGWMetadata(ctx, bucket, getDareMetaPath(object))
   641  			defer l.DeleteObject(ctx, bucket, getGWContentPath(object), opts)
   642  		}
   643  		return oi, e
   644  	}
   645  	gwMeta := newGWMetaV1()
   646  	gwMeta.Meta = make(map[string]string)
   647  	for k, v := range tmpMeta.Meta {
   648  		gwMeta.Meta[k] = v
   649  	}
   650  	// Allocate parts similar to incoming slice.
   651  	gwMeta.Parts = make([]minio.ObjectPartInfo, len(uploadedParts))
   652  
   653  	bkUploadedParts := make([]minio.CompletePart, len(uploadedParts))
   654  	// Calculate full object size.
   655  	var objectSize int64
   656  
   657  	// Validate each part and then commit to disk.
   658  	for i, part := range uploadedParts {
   659  		partMeta, err := l.getGWMetadata(ctx, bucket, getPartMetaPath(object, uploadID, part.PartNumber))
   660  		if err != nil || len(partMeta.Parts) == 0 {
   661  			return oi, minio.InvalidPart{}
   662  		}
   663  		bkUploadedParts[i] = minio.CompletePart{PartNumber: part.PartNumber, ETag: partMeta.Parts[0].ETag}
   664  		gwMeta.Parts[i] = partMeta.Parts[0]
   665  		objectSize += partMeta.Parts[0].Size
   666  	}
   667  	oi, e = l.s3Objects.CompleteMultipartUpload(ctx, bucket, getGWContentPath(object), uploadID, bkUploadedParts, opts)
   668  	if e != nil {
   669  		return oi, e
   670  	}
   671  
   672  	//delete any unencrypted version of object that might be on the backend
   673  	defer l.s3Objects.DeleteObject(ctx, bucket, object, opts)
   674  
   675  	// Save the final object size and modtime.
   676  	gwMeta.Stat.Size = objectSize
   677  	gwMeta.Stat.ModTime = time.Now().UTC()
   678  	gwMeta.ETag = oi.ETag
   679  
   680  	if err = l.writeGWMetadata(ctx, bucket, getDareMetaPath(object), gwMeta, minio.ObjectOptions{}); err != nil {
   681  		return oi, minio.ErrorRespToObjectError(err)
   682  	}
   683  	// Clean up any uploaded parts that are not being committed by this CompleteMultipart operation
   684  	var continuationToken, startAfter, delimiter string
   685  	uploadPrefix := getTmpGWMetaPath(object, uploadID)
   686  	done := false
   687  	for {
   688  		loi, lerr := l.s3Objects.ListObjectsV2(ctx, bucket, uploadPrefix, continuationToken, delimiter, 1000, false, startAfter)
   689  		if lerr != nil {
   690  			break
   691  		}
   692  		for _, obj := range loi.Objects {
   693  			if !strings.HasPrefix(obj.Name, uploadPrefix) {
   694  				done = true
   695  				break
   696  			}
   697  			startAfter = obj.Name
   698  			l.s3Objects.DeleteObject(ctx, bucket, obj.Name, opts)
   699  		}
   700  		continuationToken = loi.NextContinuationToken
   701  		if !loi.IsTruncated || done {
   702  			break
   703  		}
   704  	}
   705  
   706  	return gwMeta.ToObjectInfo(bucket, object), nil
   707  }
   708  
   709  // getTmpGWMetaPath returns the prefix under which uploads in progress are stored on backend
   710  func getTmpGWMetaPath(object, uploadID string) string {
   711  	return path.Join(object, defaultMinioGWPrefix, uploadID)
   712  }
   713  
   714  // getGWMetaPath returns the prefix under which custom object metadata and object are stored on backend after upload completes
   715  func getGWMetaPath(object string) string {
   716  	return path.Join(object, defaultMinioGWPrefix)
   717  }
   718  
   719  // getGWContentPath returns the prefix under which custom object is stored on backend after upload completes
   720  func getGWContentPath(object string) string {
   721  	return path.Join(object, defaultMinioGWPrefix, defaultGWContentFileName)
   722  }
   723  
   724  // Clean-up the stale incomplete encrypted multipart uploads. Should be run in a Go routine.
   725  func (l *s3EncObjects) cleanupStaleEncMultipartUploads(ctx context.Context, cleanupInterval, expiry time.Duration) {
   726  	ticker := time.NewTicker(cleanupInterval)
   727  	defer ticker.Stop()
   728  
   729  	for {
   730  		select {
   731  		case <-ctx.Done():
   732  			return
   733  		case <-ticker.C:
   734  			l.cleanupStaleUploads(ctx, expiry)
   735  		}
   736  	}
   737  }
   738  
   739  // cleanupStaleUploads removes old custom encryption multipart uploads on backend
   740  func (l *s3EncObjects) cleanupStaleUploads(ctx context.Context, expiry time.Duration) {
   741  	buckets, err := l.s3Objects.ListBuckets(ctx)
   742  	if err != nil {
   743  		logger.LogIf(ctx, err)
   744  		return
   745  	}
   746  	for _, b := range buckets {
   747  		expParts := l.getStalePartsForBucket(ctx, b.Name, expiry)
   748  		for k := range expParts {
   749  			l.s3Objects.DeleteObject(ctx, b.Name, k, minio.ObjectOptions{})
   750  		}
   751  	}
   752  }
   753  
   754  func (l *s3EncObjects) getStalePartsForBucket(ctx context.Context, bucket string, expiry time.Duration) (expParts map[string]string) {
   755  	var prefix, continuationToken, delimiter, startAfter string
   756  	expParts = make(map[string]string)
   757  	now := time.Now()
   758  	for {
   759  		loi, err := l.s3Objects.ListObjectsV2(ctx, bucket, prefix, continuationToken, delimiter, 1000, false, startAfter)
   760  		if err != nil {
   761  			logger.LogIf(ctx, err)
   762  			break
   763  		}
   764  		for _, obj := range loi.Objects {
   765  			startAfter = obj.Name
   766  			if !strings.Contains(obj.Name, defaultMinioGWPrefix) {
   767  				continue
   768  			}
   769  
   770  			if isGWObject(obj.Name) {
   771  				continue
   772  			}
   773  
   774  			// delete temporary part.meta or dare.meta files for incomplete uploads that are past expiry
   775  			if (strings.HasSuffix(obj.Name, gwpartMetaJSON) || strings.HasSuffix(obj.Name, gwdareMetaJSON)) &&
   776  				now.Sub(obj.ModTime) > expiry {
   777  				expParts[obj.Name] = ""
   778  			}
   779  		}
   780  		continuationToken = loi.NextContinuationToken
   781  		if !loi.IsTruncated {
   782  			break
   783  		}
   784  	}
   785  	return
   786  }
   787  
   788  func (l *s3EncObjects) DeleteBucket(ctx context.Context, bucket string, forceDelete bool) error {
   789  	var prefix, continuationToken, delimiter, startAfter string
   790  	expParts := make(map[string]string)
   791  
   792  	for {
   793  		loi, err := l.s3Objects.ListObjectsV2(ctx, bucket, prefix, continuationToken, delimiter, 1000, false, startAfter)
   794  		if err != nil {
   795  			break
   796  		}
   797  		for _, obj := range loi.Objects {
   798  			startAfter = obj.Name
   799  			if !strings.Contains(obj.Name, defaultMinioGWPrefix) {
   800  				return minio.BucketNotEmpty{}
   801  			}
   802  			if isGWObject(obj.Name) {
   803  				return minio.BucketNotEmpty{}
   804  			}
   805  			// delete temporary part.meta or dare.meta files for incomplete uploads
   806  			if strings.HasSuffix(obj.Name, gwpartMetaJSON) || strings.HasSuffix(obj.Name, gwdareMetaJSON) {
   807  				expParts[obj.Name] = ""
   808  			}
   809  		}
   810  		continuationToken = loi.NextContinuationToken
   811  		if !loi.IsTruncated {
   812  			break
   813  		}
   814  	}
   815  	for k := range expParts {
   816  		l.s3Objects.DeleteObject(ctx, bucket, k, minio.ObjectOptions{})
   817  	}
   818  	err := l.Client.RemoveBucket(ctx, bucket)
   819  	if err != nil {
   820  		return minio.ErrorRespToObjectError(err, bucket)
   821  	}
   822  	return nil
   823  }