storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/object-api-options.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2017-2020 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 cmd
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"net/http"
    23  	"strconv"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/google/uuid"
    28  	"github.com/minio/minio-go/v7/pkg/encrypt"
    29  
    30  	"storj.io/minio/cmd/crypto"
    31  	xhttp "storj.io/minio/cmd/http"
    32  	"storj.io/minio/cmd/logger"
    33  )
    34  
    35  // set encryption options for pass through to backend in the case of gateway and UserDefined metadata
    36  func getDefaultOpts(header http.Header, copySource bool, metadata map[string]string) (opts ObjectOptions, err error) {
    37  	var clientKey [32]byte
    38  	var sse encrypt.ServerSide
    39  
    40  	opts = ObjectOptions{UserDefined: metadata}
    41  	if copySource {
    42  		if crypto.SSECopy.IsRequested(header) {
    43  			clientKey, err = crypto.SSECopy.ParseHTTP(header)
    44  			if err != nil {
    45  				return
    46  			}
    47  			if sse, err = encrypt.NewSSEC(clientKey[:]); err != nil {
    48  				return
    49  			}
    50  			opts.ServerSideEncryption = encrypt.SSECopy(sse)
    51  			return
    52  		}
    53  		return
    54  	}
    55  
    56  	if crypto.SSEC.IsRequested(header) {
    57  		clientKey, err = crypto.SSEC.ParseHTTP(header)
    58  		if err != nil {
    59  			return
    60  		}
    61  		if sse, err = encrypt.NewSSEC(clientKey[:]); err != nil {
    62  			return
    63  		}
    64  		opts.ServerSideEncryption = sse
    65  		return
    66  	}
    67  	if crypto.S3.IsRequested(header) || (metadata != nil && crypto.S3.IsEncrypted(metadata)) {
    68  		opts.ServerSideEncryption = encrypt.NewSSE()
    69  	}
    70  	if v, ok := header[xhttp.MinIOSourceProxyRequest]; ok {
    71  		opts.ProxyHeaderSet = true
    72  		opts.ProxyRequest = strings.Join(v, "") == "true"
    73  	}
    74  	return
    75  }
    76  
    77  // get ObjectOptions for GET calls from encryption headers
    78  func getOpts(ctx context.Context, r *http.Request, bucket, object string) (ObjectOptions, error) {
    79  	var (
    80  		encryption encrypt.ServerSide
    81  		opts       ObjectOptions
    82  	)
    83  
    84  	var partNumber int
    85  	var err error
    86  	if pn := r.URL.Query().Get(xhttp.PartNumber); pn != "" {
    87  		partNumber, err = strconv.Atoi(pn)
    88  		if err != nil {
    89  			return opts, err
    90  		}
    91  		if partNumber <= 0 {
    92  			return opts, errInvalidArgument
    93  		}
    94  	}
    95  
    96  	vid := strings.TrimSpace(r.URL.Query().Get(xhttp.VersionID))
    97  	if vid != "" && vid != nullVersionID {
    98  		_, err := uuid.Parse(vid)
    99  		if err != nil {
   100  			logger.LogIf(ctx, err)
   101  			return opts, InvalidVersionID{
   102  				Bucket:    bucket,
   103  				Object:    object,
   104  				VersionID: vid,
   105  			}
   106  		}
   107  	}
   108  
   109  	if GlobalGatewaySSE.SSEC() && crypto.SSEC.IsRequested(r.Header) {
   110  		key, err := crypto.SSEC.ParseHTTP(r.Header)
   111  		if err != nil {
   112  			return opts, err
   113  		}
   114  		derivedKey := deriveClientKey(key, bucket, object)
   115  		encryption, err = encrypt.NewSSEC(derivedKey[:])
   116  		logger.CriticalIf(ctx, err)
   117  		return ObjectOptions{
   118  			ServerSideEncryption: encryption,
   119  			VersionID:            vid,
   120  			PartNumber:           partNumber,
   121  		}, nil
   122  	}
   123  
   124  	// default case of passing encryption headers to backend
   125  	opts, err = getDefaultOpts(r.Header, false, nil)
   126  	if err != nil {
   127  		return opts, err
   128  	}
   129  	opts.PartNumber = partNumber
   130  	opts.VersionID = vid
   131  	delMarker := strings.TrimSpace(r.Header.Get(xhttp.MinIOSourceDeleteMarker))
   132  	if delMarker != "" {
   133  		switch delMarker {
   134  		case "true":
   135  			opts.DeleteMarker = true
   136  		case "false":
   137  		default:
   138  			err = fmt.Errorf("Unable to parse %s, failed with %w", xhttp.MinIOSourceDeleteMarker, fmt.Errorf("DeleteMarker should be true or false"))
   139  			logger.LogIf(ctx, err)
   140  			return opts, InvalidArgument{
   141  				Bucket: bucket,
   142  				Object: object,
   143  				Err:    err,
   144  			}
   145  		}
   146  
   147  	}
   148  	return opts, nil
   149  }
   150  
   151  func delOpts(ctx context.Context, r *http.Request, bucket, object string) (opts ObjectOptions, err error) {
   152  	versioned := globalBucketVersioningSys.Enabled(bucket)
   153  	opts, err = getOpts(ctx, r, bucket, object)
   154  	if err != nil {
   155  		return opts, err
   156  	}
   157  	opts.Versioned = versioned
   158  	opts.VersionSuspended = globalBucketVersioningSys.Suspended(bucket)
   159  	delMarker := strings.TrimSpace(r.Header.Get(xhttp.MinIOSourceDeleteMarker))
   160  	if delMarker != "" {
   161  		switch delMarker {
   162  		case "true":
   163  			opts.DeleteMarker = true
   164  		case "false":
   165  		default:
   166  			err = fmt.Errorf("Unable to parse %s, failed with %w", xhttp.MinIOSourceDeleteMarker, fmt.Errorf("DeleteMarker should be true or false"))
   167  			logger.LogIf(ctx, err)
   168  			return opts, InvalidArgument{
   169  				Bucket: bucket,
   170  				Object: object,
   171  				Err:    err,
   172  			}
   173  		}
   174  	}
   175  
   176  	purgeVersion := strings.TrimSpace(r.Header.Get(xhttp.MinIOSourceDeleteMarkerDelete))
   177  	if purgeVersion != "" {
   178  		switch purgeVersion {
   179  		case "true":
   180  			opts.VersionPurgeStatus = Complete
   181  		case "false":
   182  		default:
   183  			err = fmt.Errorf("Unable to parse %s, failed with %w", xhttp.MinIOSourceDeleteMarkerDelete, fmt.Errorf("DeleteMarkerPurge should be true or false"))
   184  			logger.LogIf(ctx, err)
   185  			return opts, InvalidArgument{
   186  				Bucket: bucket,
   187  				Object: object,
   188  				Err:    err,
   189  			}
   190  		}
   191  	}
   192  
   193  	mtime := strings.TrimSpace(r.Header.Get(xhttp.MinIOSourceMTime))
   194  	if mtime != "" {
   195  		opts.MTime, err = time.Parse(time.RFC3339Nano, mtime)
   196  		if err != nil {
   197  			return opts, InvalidArgument{
   198  				Bucket: bucket,
   199  				Object: object,
   200  				Err:    fmt.Errorf("Unable to parse %s, failed with %w", xhttp.MinIOSourceMTime, err),
   201  			}
   202  		}
   203  	} else {
   204  		opts.MTime = UTCNow()
   205  	}
   206  	return opts, nil
   207  }
   208  
   209  // get ObjectOptions for PUT calls from encryption headers and metadata
   210  func putOpts(ctx context.Context, r *http.Request, bucket, object string, metadata map[string]string) (opts ObjectOptions, err error) {
   211  	versioned := globalBucketVersioningSys.Enabled(bucket)
   212  	vid := strings.TrimSpace(r.URL.Query().Get(xhttp.VersionID))
   213  	if vid != "" && vid != nullVersionID {
   214  		_, err := uuid.Parse(vid)
   215  		if err != nil {
   216  			logger.LogIf(ctx, err)
   217  			return opts, InvalidVersionID{
   218  				Bucket:    bucket,
   219  				Object:    object,
   220  				VersionID: vid,
   221  			}
   222  		}
   223  		if !versioned {
   224  			return opts, InvalidArgument{
   225  				Bucket: bucket,
   226  				Object: object,
   227  				Err:    fmt.Errorf("VersionID specified %s, but versioning not enabled on  %s", opts.VersionID, bucket),
   228  			}
   229  		}
   230  	}
   231  	mtimeStr := strings.TrimSpace(r.Header.Get(xhttp.MinIOSourceMTime))
   232  	mtime := UTCNow()
   233  	if mtimeStr != "" {
   234  		mtime, err = time.Parse(time.RFC3339, mtimeStr)
   235  		if err != nil {
   236  			return opts, InvalidArgument{
   237  				Bucket: bucket,
   238  				Object: object,
   239  				Err:    fmt.Errorf("Unable to parse %s, failed with %w", xhttp.MinIOSourceMTime, err),
   240  			}
   241  		}
   242  	}
   243  	etag := strings.TrimSpace(r.Header.Get(xhttp.MinIOSourceETag))
   244  	if etag != "" {
   245  		if metadata == nil {
   246  			metadata = make(map[string]string, 1)
   247  		}
   248  		metadata["etag"] = etag
   249  	}
   250  
   251  	// In the case of multipart custom format, the metadata needs to be checked in addition to header to see if it
   252  	// is SSE-S3 encrypted, primarily because S3 protocol does not require SSE-S3 headers in PutObjectPart calls
   253  	if GlobalGatewaySSE.SSES3() && (crypto.S3.IsRequested(r.Header) || crypto.S3.IsEncrypted(metadata)) {
   254  		return ObjectOptions{
   255  			ServerSideEncryption: encrypt.NewSSE(),
   256  			UserDefined:          metadata,
   257  			VersionID:            vid,
   258  			Versioned:            versioned,
   259  			MTime:                mtime,
   260  		}, nil
   261  	}
   262  	if GlobalGatewaySSE.SSEC() && crypto.SSEC.IsRequested(r.Header) {
   263  		opts, err = getOpts(ctx, r, bucket, object)
   264  		opts.VersionID = vid
   265  		opts.Versioned = versioned
   266  		opts.UserDefined = metadata
   267  		return
   268  	}
   269  	if crypto.S3KMS.IsRequested(r.Header) {
   270  		keyID, context, err := crypto.S3KMS.ParseHTTP(r.Header)
   271  		if err != nil {
   272  			return ObjectOptions{}, err
   273  		}
   274  		sseKms, err := encrypt.NewSSEKMS(keyID, context)
   275  		if err != nil {
   276  			return ObjectOptions{}, err
   277  		}
   278  		return ObjectOptions{
   279  			ServerSideEncryption: sseKms,
   280  			UserDefined:          metadata,
   281  			VersionID:            vid,
   282  			Versioned:            versioned,
   283  			MTime:                mtime,
   284  		}, nil
   285  	}
   286  	// default case of passing encryption headers and UserDefined metadata to backend
   287  	opts, err = getDefaultOpts(r.Header, false, metadata)
   288  	if err != nil {
   289  		return opts, err
   290  	}
   291  	opts.VersionID = vid
   292  	opts.Versioned = versioned
   293  	opts.MTime = mtime
   294  	return opts, nil
   295  }
   296  
   297  // get ObjectOptions for Copy calls with encryption headers provided on the target side and source side metadata
   298  func copyDstOpts(ctx context.Context, r *http.Request, bucket, object string, metadata map[string]string) (opts ObjectOptions, err error) {
   299  	return putOpts(ctx, r, bucket, object, metadata)
   300  }
   301  
   302  // get ObjectOptions for Copy calls with encryption headers provided on the source side
   303  func copySrcOpts(ctx context.Context, r *http.Request, bucket, object string) (ObjectOptions, error) {
   304  	var (
   305  		ssec encrypt.ServerSide
   306  		opts ObjectOptions
   307  	)
   308  
   309  	if GlobalGatewaySSE.SSEC() && crypto.SSECopy.IsRequested(r.Header) {
   310  		key, err := crypto.SSECopy.ParseHTTP(r.Header)
   311  		if err != nil {
   312  			return opts, err
   313  		}
   314  		derivedKey := deriveClientKey(key, bucket, object)
   315  		ssec, err = encrypt.NewSSEC(derivedKey[:])
   316  		if err != nil {
   317  			return opts, err
   318  		}
   319  		return ObjectOptions{ServerSideEncryption: encrypt.SSECopy(ssec)}, nil
   320  	}
   321  
   322  	// default case of passing encryption headers to backend
   323  	opts, err := getDefaultOpts(r.Header, false, nil)
   324  	if err != nil {
   325  		return opts, err
   326  	}
   327  	return opts, nil
   328  }