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 }