storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/bucket-object-lock.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2019-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 "math" 22 "net/http" 23 24 xhttp "storj.io/minio/cmd/http" 25 "storj.io/minio/cmd/logger" 26 "storj.io/minio/pkg/auth" 27 objectlock "storj.io/minio/pkg/bucket/object/lock" 28 "storj.io/minio/pkg/bucket/policy" 29 "storj.io/minio/pkg/bucket/replication" 30 ) 31 32 // BucketObjectLockSys - map of bucket and retention configuration. 33 type BucketObjectLockSys struct{} 34 35 // Get - Get retention configuration. 36 func (sys *BucketObjectLockSys) Get(bucketName string) (r objectlock.Retention, err error) { 37 if GlobalIsGateway { 38 objAPI := newObjectLayerFn() 39 if objAPI == nil { 40 return r, errServerNotInitialized 41 } 42 43 return r, nil 44 } 45 46 config, err := globalBucketMetadataSys.GetObjectLockConfig(bucketName) 47 if err != nil { 48 if _, ok := err.(BucketObjectLockConfigNotFound); ok { 49 return r, nil 50 } 51 return r, err 52 53 } 54 return config.ToRetention(), nil 55 } 56 57 // enforceRetentionForDeletion checks if it is appropriate to remove an 58 // object according to locking configuration when this is lifecycle/ bucket quota asking. 59 func enforceRetentionForDeletion(ctx context.Context, objInfo ObjectInfo) (locked bool) { 60 lhold := objectlock.GetObjectLegalHoldMeta(objInfo.UserDefined) 61 if lhold.Status.Valid() && lhold.Status == objectlock.LegalHoldOn { 62 return true 63 } 64 65 ret := objectlock.GetObjectRetentionMeta(objInfo.UserDefined) 66 if ret.Mode.Valid() && (ret.Mode == objectlock.RetCompliance || ret.Mode == objectlock.RetGovernance) { 67 t, err := objectlock.UTCNowNTP() 68 if err != nil { 69 logger.LogIf(ctx, err) 70 return true 71 } 72 if ret.RetainUntilDate.After(t) { 73 return true 74 } 75 } 76 return false 77 } 78 79 // enforceRetentionBypassForDelete enforces whether an existing object under governance can be deleted 80 // with governance bypass headers set in the request. 81 // Objects under site wide WORM can never be overwritten. 82 // For objects in "Governance" mode, overwrite is allowed if a) object retention date is past OR 83 // governance bypass headers are set and user has governance bypass permissions. 84 // Objects in "Compliance" mode can be overwritten only if retention date is past. 85 func enforceRetentionBypassForDelete(ctx context.Context, r *http.Request, bucket string, object ObjectToDelete, oi ObjectInfo, gerr error) APIErrorCode { 86 opts, err := getOpts(ctx, r, bucket, object.ObjectName) 87 if err != nil { 88 return toAPIErrorCode(ctx, err) 89 } 90 91 opts.VersionID = object.VersionID 92 if gerr != nil { // error from GetObjectInfo 93 switch gerr.(type) { 94 case MethodNotAllowed: // This happens usually for a delete marker 95 if oi.DeleteMarker { 96 // Delete marker should be present and valid. 97 return ErrNone 98 } 99 } 100 if isErrObjectNotFound(gerr) || isErrVersionNotFound(gerr) { 101 return ErrNone 102 } 103 return toAPIErrorCode(ctx, gerr) 104 } 105 106 lhold := objectlock.GetObjectLegalHoldMeta(oi.UserDefined) 107 if lhold.Status.Valid() && lhold.Status == objectlock.LegalHoldOn { 108 return ErrObjectLocked 109 } 110 111 ret := objectlock.GetObjectRetentionMeta(oi.UserDefined) 112 if ret.Mode.Valid() { 113 switch ret.Mode { 114 case objectlock.RetCompliance: 115 // In compliance mode, a protected object version can't be overwritten 116 // or deleted by any user, including the root user in your AWS account. 117 // When an object is locked in compliance mode, its retention mode can't 118 // be changed, and its retention period can't be shortened. Compliance mode 119 // ensures that an object version can't be overwritten or deleted for the 120 // duration of the retention period. 121 t, err := objectlock.UTCNowNTP() 122 if err != nil { 123 logger.LogIf(ctx, err) 124 return ErrObjectLocked 125 } 126 127 if !ret.RetainUntilDate.Before(t) { 128 return ErrObjectLocked 129 } 130 return ErrNone 131 case objectlock.RetGovernance: 132 // In governance mode, users can't overwrite or delete an object 133 // version or alter its lock settings unless they have special 134 // permissions. With governance mode, you protect objects against 135 // being deleted by most users, but you can still grant some users 136 // permission to alter the retention settings or delete the object 137 // if necessary. You can also use governance mode to test retention-period 138 // settings before creating a compliance-mode retention period. 139 // To override or remove governance-mode retention settings, a 140 // user must have the s3:BypassGovernanceRetention permission 141 // and must explicitly include x-amz-bypass-governance-retention:true 142 // as a request header with any request that requires overriding 143 // governance mode. 144 // 145 byPassSet := objectlock.IsObjectLockGovernanceBypassSet(r.Header) 146 if !byPassSet { 147 t, err := objectlock.UTCNowNTP() 148 if err != nil { 149 logger.LogIf(ctx, err) 150 return ErrObjectLocked 151 } 152 153 if !ret.RetainUntilDate.Before(t) { 154 return ErrObjectLocked 155 } 156 return ErrNone 157 } 158 // https://docs.aws.amazon.com/AmazonS3/latest/dev/object-lock-overview.html#object-lock-retention-modes 159 // If you try to delete objects protected by governance mode and have s3:BypassGovernanceRetention 160 // or s3:GetBucketObjectLockConfiguration permissions, the operation will succeed. 161 govBypassPerms1 := checkRequestAuthType(ctx, r, policy.BypassGovernanceRetentionAction, bucket, object.ObjectName) 162 govBypassPerms2 := checkRequestAuthType(ctx, r, policy.GetBucketObjectLockConfigurationAction, bucket, object.ObjectName) 163 if govBypassPerms1 != ErrNone && govBypassPerms2 != ErrNone { 164 return ErrAccessDenied 165 } 166 } 167 } 168 return ErrNone 169 } 170 171 // enforceRetentionBypassForPut enforces whether an existing object under governance can be overwritten 172 // with governance bypass headers set in the request. 173 // Objects under site wide WORM cannot be overwritten. 174 // For objects in "Governance" mode, overwrite is allowed if a) object retention date is past OR 175 // governance bypass headers are set and user has governance bypass permissions. 176 // Objects in compliance mode can be overwritten only if retention date is being extended. No mode change is permitted. 177 func enforceRetentionBypassForPut(ctx context.Context, r *http.Request, bucket, object string, getObjectInfoFn GetObjectInfoFn, objRetention *objectlock.ObjectRetention, cred auth.Credentials, owner bool, claims map[string]interface{}) (ObjectInfo, APIErrorCode) { 178 byPassSet := objectlock.IsObjectLockGovernanceBypassSet(r.Header) 179 opts, err := getOpts(ctx, r, bucket, object) 180 if err != nil { 181 return ObjectInfo{}, toAPIErrorCode(ctx, err) 182 } 183 184 oi, err := getObjectInfoFn(ctx, bucket, object, opts) 185 if err != nil { 186 return oi, toAPIErrorCode(ctx, err) 187 } 188 189 t, err := objectlock.UTCNowNTP() 190 if err != nil { 191 logger.LogIf(ctx, err) 192 return oi, ErrObjectLocked 193 } 194 195 // Pass in relative days from current time, to additionally to verify "object-lock-remaining-retention-days" policy if any. 196 days := int(math.Ceil(math.Abs(objRetention.RetainUntilDate.Sub(t).Hours()) / 24)) 197 198 ret := objectlock.GetObjectRetentionMeta(oi.UserDefined) 199 if ret.Mode.Valid() { 200 // Retention has expired you may change whatever you like. 201 if ret.RetainUntilDate.Before(t) { 202 perm := isPutRetentionAllowed(bucket, object, 203 days, objRetention.RetainUntilDate.Time, 204 objRetention.Mode, byPassSet, r, cred, 205 owner, claims) 206 return oi, perm 207 } 208 209 switch ret.Mode { 210 case objectlock.RetGovernance: 211 govPerm := isPutRetentionAllowed(bucket, object, days, 212 objRetention.RetainUntilDate.Time, objRetention.Mode, 213 byPassSet, r, cred, owner, claims) 214 // Governance mode retention period cannot be shortened, if x-amz-bypass-governance is not set. 215 if !byPassSet { 216 if objRetention.Mode != objectlock.RetGovernance || objRetention.RetainUntilDate.Before((ret.RetainUntilDate.Time)) { 217 return oi, ErrObjectLocked 218 } 219 } 220 return oi, govPerm 221 case objectlock.RetCompliance: 222 // Compliance retention mode cannot be changed or shortened. 223 // https://docs.aws.amazon.com/AmazonS3/latest/dev/object-lock-overview.html#object-lock-retention-modes 224 if objRetention.Mode != objectlock.RetCompliance || objRetention.RetainUntilDate.Before((ret.RetainUntilDate.Time)) { 225 return oi, ErrObjectLocked 226 } 227 compliancePerm := isPutRetentionAllowed(bucket, object, 228 days, objRetention.RetainUntilDate.Time, objRetention.Mode, 229 false, r, cred, owner, claims) 230 return oi, compliancePerm 231 } 232 return oi, ErrNone 233 } // No pre-existing retention metadata present. 234 235 perm := isPutRetentionAllowed(bucket, object, 236 days, objRetention.RetainUntilDate.Time, 237 objRetention.Mode, byPassSet, r, cred, owner, claims) 238 return oi, perm 239 } 240 241 // checkPutObjectLockAllowed enforces object retention policy and legal hold policy 242 // for requests with WORM headers 243 // See https://docs.aws.amazon.com/AmazonS3/latest/dev/object-lock-managing.html for the spec. 244 // For non-existing objects with object retention headers set, this method returns ErrNone if bucket has 245 // locking enabled and user has requisite permissions (s3:PutObjectRetention) 246 // If object exists on object store and site wide WORM enabled - this method 247 // returns an error. For objects in "Governance" mode, overwrite is allowed if the retention date has expired. 248 // For objects in "Compliance" mode, retention date cannot be shortened, and mode cannot be altered. 249 // For objects with legal hold header set, the s3:PutObjectLegalHold permission is expected to be set 250 // Both legal hold and retention can be applied independently on an object 251 func checkPutObjectLockAllowed(ctx context.Context, rq *http.Request, bucket, object string, getObjectInfoFn GetObjectInfoFn, retentionPermErr, legalHoldPermErr APIErrorCode) (objectlock.RetMode, objectlock.RetentionDate, objectlock.ObjectLegalHold, APIErrorCode) { 252 var mode objectlock.RetMode 253 var retainDate objectlock.RetentionDate 254 var legalHold objectlock.ObjectLegalHold 255 256 retentionRequested := objectlock.IsObjectLockRetentionRequested(rq.Header) 257 legalHoldRequested := objectlock.IsObjectLockLegalHoldRequested(rq.Header) 258 259 retentionCfg, err := globalBucketObjectLockSys.Get(bucket) 260 if err != nil { 261 return mode, retainDate, legalHold, ErrInvalidBucketObjectLockConfiguration 262 } 263 264 if !retentionCfg.LockEnabled { 265 if legalHoldRequested || retentionRequested { 266 return mode, retainDate, legalHold, ErrInvalidBucketObjectLockConfiguration 267 } 268 269 // If this not a WORM enabled bucket, we should return right here. 270 return mode, retainDate, legalHold, ErrNone 271 } 272 273 opts, err := getOpts(ctx, rq, bucket, object) 274 if err != nil { 275 return mode, retainDate, legalHold, toAPIErrorCode(ctx, err) 276 } 277 278 replica := rq.Header.Get(xhttp.AmzBucketReplicationStatus) == replication.Replica.String() 279 280 if opts.VersionID != "" && !replica { 281 if objInfo, err := getObjectInfoFn(ctx, bucket, object, opts); err == nil { 282 r := objectlock.GetObjectRetentionMeta(objInfo.UserDefined) 283 t, err := objectlock.UTCNowNTP() 284 if err != nil { 285 logger.LogIf(ctx, err) 286 return mode, retainDate, legalHold, ErrObjectLocked 287 } 288 if r.Mode == objectlock.RetCompliance && r.RetainUntilDate.After(t) { 289 return mode, retainDate, legalHold, ErrObjectLocked 290 } 291 mode = r.Mode 292 retainDate = r.RetainUntilDate 293 legalHold = objectlock.GetObjectLegalHoldMeta(objInfo.UserDefined) 294 // Disallow overwriting an object on legal hold 295 if legalHold.Status == objectlock.LegalHoldOn { 296 return mode, retainDate, legalHold, ErrObjectLocked 297 } 298 } 299 } 300 301 if legalHoldRequested { 302 var lerr error 303 if legalHold, lerr = objectlock.ParseObjectLockLegalHoldHeaders(rq.Header); lerr != nil { 304 return mode, retainDate, legalHold, toAPIErrorCode(ctx, err) 305 } 306 } 307 308 if retentionRequested { 309 legalHold, err := objectlock.ParseObjectLockLegalHoldHeaders(rq.Header) 310 if err != nil { 311 return mode, retainDate, legalHold, toAPIErrorCode(ctx, err) 312 } 313 rMode, rDate, err := objectlock.ParseObjectLockRetentionHeaders(rq.Header) 314 if err != nil { 315 return mode, retainDate, legalHold, toAPIErrorCode(ctx, err) 316 } 317 if retentionPermErr != ErrNone { 318 return mode, retainDate, legalHold, retentionPermErr 319 } 320 return rMode, rDate, legalHold, ErrNone 321 } 322 if replica { // replica inherits retention metadata only from source 323 return "", objectlock.RetentionDate{}, legalHold, ErrNone 324 } 325 if !retentionRequested && retentionCfg.Validity > 0 { 326 if retentionPermErr != ErrNone { 327 return mode, retainDate, legalHold, retentionPermErr 328 } 329 330 t, err := objectlock.UTCNowNTP() 331 if err != nil { 332 logger.LogIf(ctx, err) 333 return mode, retainDate, legalHold, ErrObjectLocked 334 } 335 336 if !legalHoldRequested && retentionCfg.LockEnabled { 337 // inherit retention from bucket configuration 338 return retentionCfg.Mode, objectlock.RetentionDate{Time: t.Add(retentionCfg.Validity)}, legalHold, ErrNone 339 } 340 return "", objectlock.RetentionDate{}, legalHold, ErrNone 341 } 342 return mode, retainDate, legalHold, ErrNone 343 } 344 345 // NewBucketObjectLockSys returns initialized BucketObjectLockSys 346 func NewBucketObjectLockSys() *BucketObjectLockSys { 347 return &BucketObjectLockSys{} 348 }