mirror of
https://github.com/minio/minio.git
synced 2025-05-21 17:43:48 -04:00
fix: support object-remaining-retention-days policy condition (#9259)
This PR also tries to simplify the approach taken in object-locking implementation by preferential treatment given towards full validation. This in-turn has fixed couple of bugs related to how policy should have been honored when ByPassGovernance is provided. Simplifies code a bit, but also duplicates code intentionally for clarity due to complex nature of object locking implementation.
This commit is contained in:
parent
b9b1bfefe7
commit
43a3778b45
@ -46,7 +46,10 @@ function main()
|
|||||||
gw_pid="$(start_minio_gateway_s3)"
|
gw_pid="$(start_minio_gateway_s3)"
|
||||||
|
|
||||||
SERVER_ENDPOINT=127.0.0.1:24240 ENABLE_HTTPS=0 ACCESS_KEY=minio \
|
SERVER_ENDPOINT=127.0.0.1:24240 ENABLE_HTTPS=0 ACCESS_KEY=minio \
|
||||||
SECRET_KEY=minio123 MINT_MODE="full" /mint/entrypoint.sh
|
SECRET_KEY=minio123 MINT_MODE="full" /mint/entrypoint.sh \
|
||||||
|
aws-sdk-go aws-sdk-java aws-sdk-php aws-sdk-ruby awscli \
|
||||||
|
healthcheck mc minio-dotnet minio-java minio-js \
|
||||||
|
minio-py s3cmd s3select security
|
||||||
rv=$?
|
rv=$?
|
||||||
|
|
||||||
kill "$sr_pid"
|
kill "$sr_pid"
|
||||||
|
@ -26,12 +26,15 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
xhttp "github.com/minio/minio/cmd/http"
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
xjwt "github.com/minio/minio/cmd/jwt"
|
xjwt "github.com/minio/minio/cmd/jwt"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
|
objectlock "github.com/minio/minio/pkg/bucket/object/lock"
|
||||||
"github.com/minio/minio/pkg/bucket/policy"
|
"github.com/minio/minio/pkg/bucket/policy"
|
||||||
"github.com/minio/minio/pkg/hash"
|
"github.com/minio/minio/pkg/hash"
|
||||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||||
@ -464,6 +467,106 @@ func (a authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
writeErrorResponse(r.Context(), w, errorCodes.ToAPIErr(ErrSignatureVersionNotSupported), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(r.Context(), w, errorCodes.ToAPIErr(ErrSignatureVersionNotSupported), r.URL, guessIsBrowserReq(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateSignature(atype authType, r *http.Request) (auth.Credentials, bool, map[string]interface{}, APIErrorCode) {
|
||||||
|
var cred auth.Credentials
|
||||||
|
var owner bool
|
||||||
|
var s3Err APIErrorCode
|
||||||
|
switch atype {
|
||||||
|
case authTypeUnknown, authTypeStreamingSigned:
|
||||||
|
return cred, owner, nil, ErrSignatureVersionNotSupported
|
||||||
|
case authTypeSignedV2, authTypePresignedV2:
|
||||||
|
if s3Err = isReqAuthenticatedV2(r); s3Err != ErrNone {
|
||||||
|
return cred, owner, nil, s3Err
|
||||||
|
}
|
||||||
|
cred, owner, s3Err = getReqAccessKeyV2(r)
|
||||||
|
case authTypePresigned, authTypeSigned:
|
||||||
|
region := globalServerRegion
|
||||||
|
if s3Err = isReqAuthenticated(GlobalContext, r, region, serviceS3); s3Err != ErrNone {
|
||||||
|
return cred, owner, nil, s3Err
|
||||||
|
}
|
||||||
|
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
|
||||||
|
}
|
||||||
|
if s3Err != ErrNone {
|
||||||
|
return cred, owner, nil, s3Err
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, s3Err := checkClaimsFromToken(r, cred)
|
||||||
|
if s3Err != ErrNone {
|
||||||
|
return cred, owner, nil, s3Err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cred, owner, claims, ErrNone
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate time.Time, retMode objectlock.RetMode, byPassSet bool, r *http.Request, cred auth.Credentials, owner bool, claims map[string]interface{}) (s3Err APIErrorCode) {
|
||||||
|
var retSet bool
|
||||||
|
if cred.AccessKey == "" {
|
||||||
|
conditions := getConditionValues(r, "", "", nil)
|
||||||
|
conditions["object-lock-mode"] = []string{string(retMode)}
|
||||||
|
conditions["object-lock-retain-until-date"] = []string{retDate.Format(time.RFC3339)}
|
||||||
|
if retDays > 0 {
|
||||||
|
conditions["object-lock-remaining-retention-days"] = []string{strconv.Itoa(retDays)}
|
||||||
|
}
|
||||||
|
if retMode == objectlock.RetGovernance && byPassSet {
|
||||||
|
byPassSet = globalPolicySys.IsAllowed(policy.Args{
|
||||||
|
AccountName: cred.AccessKey,
|
||||||
|
Action: policy.Action(policy.BypassGovernanceRetentionAction),
|
||||||
|
BucketName: bucketName,
|
||||||
|
ConditionValues: conditions,
|
||||||
|
IsOwner: false,
|
||||||
|
ObjectName: objectName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if globalPolicySys.IsAllowed(policy.Args{
|
||||||
|
AccountName: cred.AccessKey,
|
||||||
|
Action: policy.Action(policy.PutObjectRetentionAction),
|
||||||
|
BucketName: bucketName,
|
||||||
|
ConditionValues: conditions,
|
||||||
|
IsOwner: false,
|
||||||
|
ObjectName: objectName,
|
||||||
|
}) {
|
||||||
|
retSet = true
|
||||||
|
}
|
||||||
|
if byPassSet || retSet {
|
||||||
|
return ErrNone
|
||||||
|
}
|
||||||
|
return ErrAccessDenied
|
||||||
|
}
|
||||||
|
|
||||||
|
conditions := getConditionValues(r, "", cred.AccessKey, claims)
|
||||||
|
conditions["object-lock-mode"] = []string{string(retMode)}
|
||||||
|
conditions["object-lock-retain-until-date"] = []string{retDate.Format(time.RFC3339)}
|
||||||
|
if retDays > 0 {
|
||||||
|
conditions["object-lock-remaining-retention-days"] = []string{strconv.Itoa(retDays)}
|
||||||
|
}
|
||||||
|
if retMode == objectlock.RetGovernance && byPassSet {
|
||||||
|
byPassSet = globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
|
AccountName: cred.AccessKey,
|
||||||
|
Action: policy.BypassGovernanceRetentionAction,
|
||||||
|
BucketName: bucketName,
|
||||||
|
ObjectName: objectName,
|
||||||
|
ConditionValues: conditions,
|
||||||
|
IsOwner: owner,
|
||||||
|
Claims: claims,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
|
AccountName: cred.AccessKey,
|
||||||
|
Action: policy.PutObjectRetentionAction,
|
||||||
|
BucketName: bucketName,
|
||||||
|
ConditionValues: conditions,
|
||||||
|
ObjectName: objectName,
|
||||||
|
IsOwner: owner,
|
||||||
|
Claims: claims,
|
||||||
|
}) {
|
||||||
|
retSet = true
|
||||||
|
}
|
||||||
|
if byPassSet || retSet {
|
||||||
|
return ErrNone
|
||||||
|
}
|
||||||
|
return ErrAccessDenied
|
||||||
|
}
|
||||||
|
|
||||||
// isPutActionAllowed - check if PUT operation is allowed on the resource, this
|
// isPutActionAllowed - check if PUT operation is allowed on the resource, this
|
||||||
// call verifies bucket policies and IAM policies, supports multi user
|
// call verifies bucket policies and IAM policies, supports multi user
|
||||||
// checks etc.
|
// checks etc.
|
||||||
|
@ -399,11 +399,14 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
govBypassPerms := checkRequestAuthType(ctx, r, policy.BypassGovernanceRetentionAction, bucket, object.ObjectName)
|
|
||||||
if _, err := enforceRetentionBypassForDelete(ctx, r, bucket, object.ObjectName, getObjectInfoFn, govBypassPerms); err != ErrNone {
|
if _, ok := globalBucketObjectLockConfig.Get(bucket); ok {
|
||||||
dErrs[index] = err
|
if err := enforceRetentionBypassForDelete(ctx, r, bucket, object.ObjectName, getObjectInfoFn); err != ErrNone {
|
||||||
continue
|
dErrs[index] = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid duplicate objects, we use map to filter them out.
|
// Avoid duplicate objects, we use map to filter them out.
|
||||||
if _, ok := objectsToDelete[object.ObjectName]; !ok {
|
if _, ok := objectsToDelete[object.ObjectName]; !ok {
|
||||||
objectsToDelete[object.ObjectName] = index
|
objectsToDelete[object.ObjectName] = index
|
||||||
|
@ -239,7 +239,7 @@ func (c *cacheObjects) GetObjectNInfo(ctx context.Context, bucket, object string
|
|||||||
// skip cache for objects with locks
|
// skip cache for objects with locks
|
||||||
objRetention := objectlock.GetObjectRetentionMeta(objInfo.UserDefined)
|
objRetention := objectlock.GetObjectRetentionMeta(objInfo.UserDefined)
|
||||||
legalHold := objectlock.GetObjectLegalHoldMeta(objInfo.UserDefined)
|
legalHold := objectlock.GetObjectLegalHoldMeta(objInfo.UserDefined)
|
||||||
if objRetention.Mode != objectlock.Invalid || legalHold.Status != "" {
|
if objRetention.Mode.Valid() || legalHold.Status.Valid() {
|
||||||
c.cacheStats.incMiss()
|
c.cacheStats.incMiss()
|
||||||
return c.GetObjectNInfoFn(ctx, bucket, object, rs, h, lockType, opts)
|
return c.GetObjectNInfoFn(ctx, bucket, object, rs, h, lockType, opts)
|
||||||
}
|
}
|
||||||
@ -614,7 +614,7 @@ func (c *cacheObjects) PutObject(ctx context.Context, bucket, object string, r *
|
|||||||
// skip cache for objects with locks
|
// skip cache for objects with locks
|
||||||
objRetention := objectlock.GetObjectRetentionMeta(opts.UserDefined)
|
objRetention := objectlock.GetObjectRetentionMeta(opts.UserDefined)
|
||||||
legalHold := objectlock.GetObjectLegalHoldMeta(opts.UserDefined)
|
legalHold := objectlock.GetObjectLegalHoldMeta(opts.UserDefined)
|
||||||
if objRetention.Mode != objectlock.Invalid || legalHold.Status != "" {
|
if objRetention.Mode.Valid() || legalHold.Status.Valid() {
|
||||||
dcache.Delete(ctx, bucket, object)
|
dcache.Delete(ctx, bucket, object)
|
||||||
return putObjectFn(ctx, bucket, object, r, opts)
|
return putObjectFn(ctx, bucket, object, r, opts)
|
||||||
}
|
}
|
||||||
|
@ -1017,27 +1017,29 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
|||||||
srcInfo.UserDefined[xhttp.AmzObjectTagging] = tags
|
srcInfo.UserDefined[xhttp.AmzObjectTagging] = tags
|
||||||
}
|
}
|
||||||
|
|
||||||
getObjectInfo := objectAPI.GetObjectInfo
|
|
||||||
if api.CacheAPI() != nil {
|
|
||||||
getObjectInfo = api.CacheAPI().GetObjectInfo
|
|
||||||
}
|
|
||||||
srcInfo.UserDefined = objectlock.FilterObjectLockMetadata(srcInfo.UserDefined, true, true)
|
srcInfo.UserDefined = objectlock.FilterObjectLockMetadata(srcInfo.UserDefined, true, true)
|
||||||
retPerms := isPutActionAllowed(getRequestAuthType(r), dstBucket, dstObject, r, iampolicy.PutObjectRetentionAction)
|
retPerms := isPutActionAllowed(getRequestAuthType(r), dstBucket, dstObject, r, iampolicy.PutObjectRetentionAction)
|
||||||
holdPerms := isPutActionAllowed(getRequestAuthType(r), dstBucket, dstObject, r, iampolicy.PutObjectLegalHoldAction)
|
holdPerms := isPutActionAllowed(getRequestAuthType(r), dstBucket, dstObject, r, iampolicy.PutObjectLegalHoldAction)
|
||||||
|
|
||||||
|
getObjectInfo := objectAPI.GetObjectInfo
|
||||||
|
if api.CacheAPI() != nil {
|
||||||
|
getObjectInfo = api.CacheAPI().GetObjectInfo
|
||||||
|
}
|
||||||
|
|
||||||
// apply default bucket configuration/governance headers for dest side.
|
// apply default bucket configuration/governance headers for dest side.
|
||||||
retentionMode, retentionDate, legalHold, s3Err := checkPutObjectLockAllowed(ctx, r, dstBucket, dstObject, getObjectInfo, retPerms, holdPerms)
|
retentionMode, retentionDate, legalHold, s3Err := checkPutObjectLockAllowed(ctx, r, dstBucket, dstObject, getObjectInfo, retPerms, holdPerms)
|
||||||
if s3Err == ErrNone && retentionMode != "" {
|
if s3Err == ErrNone && retentionMode.Valid() {
|
||||||
srcInfo.UserDefined[xhttp.AmzObjectLockMode] = string(retentionMode)
|
srcInfo.UserDefined[xhttp.AmzObjectLockMode] = string(retentionMode)
|
||||||
srcInfo.UserDefined[xhttp.AmzObjectLockRetainUntilDate] = retentionDate.UTC().Format(time.RFC3339)
|
srcInfo.UserDefined[xhttp.AmzObjectLockRetainUntilDate] = retentionDate.UTC().Format(time.RFC3339)
|
||||||
}
|
}
|
||||||
if s3Err == ErrNone && legalHold.Status != "" {
|
if s3Err == ErrNone && legalHold.Status.Valid() {
|
||||||
srcInfo.UserDefined[xhttp.AmzObjectLockLegalHold] = string(legalHold.Status)
|
srcInfo.UserDefined[xhttp.AmzObjectLockLegalHold] = string(legalHold.Status)
|
||||||
}
|
}
|
||||||
if s3Err != ErrNone {
|
if s3Err != ErrNone {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the preserved compression metadata.
|
// Store the preserved compression metadata.
|
||||||
for k, v := range compressMetadata {
|
for k, v := range compressMetadata {
|
||||||
srcInfo.UserDefined[k] = v
|
srcInfo.UserDefined[k] = v
|
||||||
@ -1327,20 +1329,25 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
|||||||
writeErrorResponseHeadersOnly(w, toAPIError(ctx, err))
|
writeErrorResponseHeadersOnly(w, toAPIError(ctx, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if api.CacheAPI() != nil {
|
||||||
|
putObject = api.CacheAPI().PutObject
|
||||||
|
}
|
||||||
|
|
||||||
|
retPerms := isPutActionAllowed(getRequestAuthType(r), bucket, object, r, iampolicy.PutObjectRetentionAction)
|
||||||
|
holdPerms := isPutActionAllowed(getRequestAuthType(r), bucket, object, r, iampolicy.PutObjectLegalHoldAction)
|
||||||
|
|
||||||
getObjectInfo := objectAPI.GetObjectInfo
|
getObjectInfo := objectAPI.GetObjectInfo
|
||||||
if api.CacheAPI() != nil {
|
if api.CacheAPI() != nil {
|
||||||
getObjectInfo = api.CacheAPI().GetObjectInfo
|
getObjectInfo = api.CacheAPI().GetObjectInfo
|
||||||
putObject = api.CacheAPI().PutObject
|
|
||||||
}
|
}
|
||||||
retPerms := isPutActionAllowed(rAuthType, bucket, object, r, iampolicy.PutObjectRetentionAction)
|
|
||||||
holdPerms := isPutActionAllowed(getRequestAuthType(r), bucket, object, r, iampolicy.PutObjectLegalHoldAction)
|
|
||||||
|
|
||||||
retentionMode, retentionDate, legalHold, s3Err := checkPutObjectLockAllowed(ctx, r, bucket, object, getObjectInfo, retPerms, holdPerms)
|
retentionMode, retentionDate, legalHold, s3Err := checkPutObjectLockAllowed(ctx, r, bucket, object, getObjectInfo, retPerms, holdPerms)
|
||||||
if s3Err == ErrNone && retentionMode != "" {
|
if s3Err == ErrNone && retentionMode.Valid() {
|
||||||
metadata[strings.ToLower(xhttp.AmzObjectLockMode)] = string(retentionMode)
|
metadata[strings.ToLower(xhttp.AmzObjectLockMode)] = string(retentionMode)
|
||||||
metadata[strings.ToLower(xhttp.AmzObjectLockRetainUntilDate)] = retentionDate.UTC().Format(time.RFC3339)
|
metadata[strings.ToLower(xhttp.AmzObjectLockRetainUntilDate)] = retentionDate.UTC().Format(time.RFC3339)
|
||||||
}
|
}
|
||||||
if s3Err == ErrNone && legalHold.Status != "" {
|
if s3Err == ErrNone && legalHold.Status.Valid() {
|
||||||
metadata[strings.ToLower(xhttp.AmzObjectLockLegalHold)] = string(legalHold.Status)
|
metadata[strings.ToLower(xhttp.AmzObjectLockLegalHold)] = string(legalHold.Status)
|
||||||
}
|
}
|
||||||
if s3Err != ErrNone {
|
if s3Err != ErrNone {
|
||||||
@ -1471,6 +1478,14 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deny if WORM is enabled
|
||||||
|
if globalWORMEnabled {
|
||||||
|
if _, err := objectAPI.GetObjectInfo(ctx, bucket, object, opts); err == nil {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Validate storage class metadata if present
|
// Validate storage class metadata if present
|
||||||
if sc := r.Header.Get(xhttp.AmzStorageClass); sc != "" {
|
if sc := r.Header.Get(xhttp.AmzStorageClass); sc != "" {
|
||||||
if !storageclass.IsValid(sc) {
|
if !storageclass.IsValid(sc) {
|
||||||
@ -1499,21 +1514,28 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
|
|||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
retPerms := isPutActionAllowed(getRequestAuthType(r), bucket, object, r, iampolicy.PutObjectRetentionAction)
|
retPerms := isPutActionAllowed(getRequestAuthType(r), bucket, object, r, iampolicy.PutObjectRetentionAction)
|
||||||
holdPerms := isPutActionAllowed(getRequestAuthType(r), bucket, object, r, iampolicy.PutObjectLegalHoldAction)
|
holdPerms := isPutActionAllowed(getRequestAuthType(r), bucket, object, r, iampolicy.PutObjectLegalHoldAction)
|
||||||
|
|
||||||
retentionMode, retentionDate, legalHold, s3Err := checkPutObjectLockAllowed(ctx, r, bucket, object, objectAPI.GetObjectInfo, retPerms, holdPerms)
|
getObjectInfo := objectAPI.GetObjectInfo
|
||||||
if s3Err == ErrNone && retentionMode != "" {
|
if api.CacheAPI() != nil {
|
||||||
|
getObjectInfo = api.CacheAPI().GetObjectInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
retentionMode, retentionDate, legalHold, s3Err := checkPutObjectLockAllowed(ctx, r, bucket, object, getObjectInfo, retPerms, holdPerms)
|
||||||
|
if s3Err == ErrNone && retentionMode.Valid() {
|
||||||
metadata[strings.ToLower(xhttp.AmzObjectLockMode)] = string(retentionMode)
|
metadata[strings.ToLower(xhttp.AmzObjectLockMode)] = string(retentionMode)
|
||||||
metadata[strings.ToLower(xhttp.AmzObjectLockRetainUntilDate)] = retentionDate.UTC().Format(time.RFC3339)
|
metadata[strings.ToLower(xhttp.AmzObjectLockRetainUntilDate)] = retentionDate.UTC().Format(time.RFC3339)
|
||||||
}
|
}
|
||||||
if s3Err == ErrNone && legalHold.Status != "" {
|
if s3Err == ErrNone && legalHold.Status.Valid() {
|
||||||
metadata[strings.ToLower(xhttp.AmzObjectLockLegalHold)] = string(legalHold.Status)
|
metadata[strings.ToLower(xhttp.AmzObjectLockLegalHold)] = string(legalHold.Status)
|
||||||
}
|
}
|
||||||
if s3Err != ErrNone {
|
if s3Err != ErrNone {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to preserve the encryption headers set in EncryptRequest,
|
// We need to preserve the encryption headers set in EncryptRequest,
|
||||||
// so we do not want to override them, copy them instead.
|
// so we do not want to override them, copy them instead.
|
||||||
for k, v := range encMetadata {
|
for k, v := range encMetadata {
|
||||||
@ -2339,22 +2361,6 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reject retention or governance headers if set, CompleteMultipartUpload spec
|
|
||||||
// does not use these headers, and should not be passed down to checkPutObjectLockAllowed
|
|
||||||
if objectlock.IsObjectLockRequested(r.Header) || objectlock.IsObjectLockGovernanceBypassSet(r.Header) {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL, guessIsBrowserReq(r))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enforce object lock governance in case a competing upload finalized first.
|
|
||||||
retPerms := isPutActionAllowed(getRequestAuthType(r), bucket, object, r, iampolicy.PutObjectRetentionAction)
|
|
||||||
holdPerms := isPutActionAllowed(getRequestAuthType(r), bucket, object, r, iampolicy.PutObjectLegalHoldAction)
|
|
||||||
|
|
||||||
if _, _, _, s3Err := checkPutObjectLockAllowed(ctx, r, bucket, object, objectAPI.GetObjectInfo, retPerms, holdPerms); s3Err != ErrNone {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL, guessIsBrowserReq(r))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get upload id.
|
// Get upload id.
|
||||||
uploadID, _, _, _, s3Error := getObjectResources(r.URL.Query())
|
uploadID, _, _, _, s3Error := getObjectResources(r.URL.Query())
|
||||||
if s3Error != ErrNone {
|
if s3Error != ErrNone {
|
||||||
@ -2375,6 +2381,36 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
|
|||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidPartOrder), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidPartOrder), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reject retention or governance headers if set, CompleteMultipartUpload spec
|
||||||
|
// does not use these headers, and should not be passed down to checkPutObjectLockAllowed
|
||||||
|
if objectlock.IsObjectLockRequested(r.Header) || objectlock.IsObjectLockGovernanceBypassSet(r.Header) {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deny if global WORM is enabled
|
||||||
|
if globalWORMEnabled {
|
||||||
|
opts, err := getOpts(ctx, r, bucket, object)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := objectAPI.GetObjectInfo(ctx, bucket, object, opts); err == nil {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enforce object lock governance in case a competing upload finalized first.
|
||||||
|
retPerms := isPutActionAllowed(getRequestAuthType(r), bucket, object, r, iampolicy.PutObjectRetentionAction)
|
||||||
|
holdPerms := isPutActionAllowed(getRequestAuthType(r), bucket, object, r, iampolicy.PutObjectLegalHoldAction)
|
||||||
|
|
||||||
|
if _, _, _, s3Err := checkPutObjectLockAllowed(ctx, r, bucket, object, objectAPI.GetObjectInfo, retPerms, holdPerms); s3Err != ErrNone {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var objectEncryptionKey []byte
|
var objectEncryptionKey []byte
|
||||||
var opts ObjectOptions
|
var opts ObjectOptions
|
||||||
var isEncrypted, ssec bool
|
var isEncrypted, ssec bool
|
||||||
@ -2546,12 +2582,6 @@ func (api objectAPIHandlers) DeleteObjectHandler(w http.ResponseWriter, r *http.
|
|||||||
getObjectInfo = api.CacheAPI().GetObjectInfo
|
getObjectInfo = api.CacheAPI().GetObjectInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
govBypassPerms := checkRequestAuthType(ctx, r, policy.BypassGovernanceRetentionAction, bucket, object)
|
|
||||||
if _, err := enforceRetentionBypassForDelete(ctx, r, bucket, object, getObjectInfo, govBypassPerms); err != ErrNone {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(err), r.URL, guessIsBrowserReq(r))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if globalDNSConfig != nil {
|
if globalDNSConfig != nil {
|
||||||
_, err := globalDNSConfig.Get(bucket)
|
_, err := globalDNSConfig.Get(bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -2560,16 +2590,41 @@ func (api objectAPIHandlers) DeleteObjectHandler(w http.ResponseWriter, r *http.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectDELETE.html
|
// Deny if global WORM is enabled
|
||||||
if err := deleteObject(ctx, objectAPI, api.CacheAPI(), bucket, object, r); err != nil {
|
if globalWORMEnabled {
|
||||||
switch err.(type) {
|
opts, err := getOpts(ctx, r, bucket, object)
|
||||||
case BucketNotFound:
|
if err != nil {
|
||||||
// When bucket doesn't exist specially handle it.
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Ignore delete object errors while replying to client, since we are suppposed to reply only 204.
|
if _, err := objectAPI.GetObjectInfo(ctx, bucket, object, opts); err == nil {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apiErr := ErrNone
|
||||||
|
if _, ok := globalBucketObjectLockConfig.Get(bucket); ok {
|
||||||
|
apiErr = enforceRetentionBypassForDelete(ctx, r, bucket, object, getObjectInfo)
|
||||||
|
if apiErr != ErrNone && apiErr != ErrNoSuchKey {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(apiErr), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if apiErr == ErrNone {
|
||||||
|
// http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectDELETE.html
|
||||||
|
if err := deleteObject(ctx, objectAPI, api.CacheAPI(), bucket, object, r); err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case BucketNotFound:
|
||||||
|
// When bucket doesn't exist specially handle it.
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Ignore delete object errors while replying to client, since we are suppposed to reply only 204.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
writeSuccessNoContent(w)
|
writeSuccessNoContent(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2695,6 +2750,11 @@ func (api objectAPIHandlers) GetObjectLegalHoldHandler(w http.ResponseWriter, r
|
|||||||
getObjectInfo = api.CacheAPI().GetObjectInfo
|
getObjectInfo = api.CacheAPI().GetObjectInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, ok := globalBucketObjectLockConfig.Get(bucket); !ok {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidBucketObjectLockConfiguration), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
opts, err := getOpts(ctx, r, bucket, object)
|
opts, err := getOpts(ctx, r, bucket, object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
@ -2707,15 +2767,12 @@ func (api objectAPIHandlers) GetObjectLegalHoldHandler(w http.ResponseWriter, r
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := globalBucketObjectLockConfig.Get(bucket); !ok {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidBucketObjectLockConfiguration), r.URL, guessIsBrowserReq(r))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
legalHold := objectlock.GetObjectLegalHoldMeta(objInfo.UserDefined)
|
legalHold := objectlock.GetObjectLegalHoldMeta(objInfo.UserDefined)
|
||||||
if legalHold.IsEmpty() {
|
if legalHold.IsEmpty() {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNoSuchObjectLockConfiguration), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNoSuchObjectLockConfiguration), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writeSuccessResponseXML(w, encodeResponse(legalHold))
|
writeSuccessResponseXML(w, encodeResponse(legalHold))
|
||||||
// Notify object legal hold accessed via a GET request.
|
// Notify object legal hold accessed via a GET request.
|
||||||
sendEvent(eventArgs{
|
sendEvent(eventArgs{
|
||||||
@ -2753,8 +2810,9 @@ func (api objectAPIHandlers) PutObjectRetentionHandler(w http.ResponseWriter, r
|
|||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Check permissions to perform this governance operation
|
|
||||||
if s3Err := checkRequestAuthType(ctx, r, policy.PutObjectRetentionAction, bucket, object); s3Err != ErrNone {
|
cred, owner, claims, s3Err := validateSignature(getRequestAuthType(r), r)
|
||||||
|
if s3Err != ErrNone {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -2763,11 +2821,17 @@ func (api objectAPIHandlers) PutObjectRetentionHandler(w http.ResponseWriter, r
|
|||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasContentMD5(r.Header) {
|
if !hasContentMD5(r.Header) {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingContentMD5), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingContentMD5), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if globalWORMEnabled {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrObjectLocked), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if _, ok := globalBucketObjectLockConfig.Get(bucket); !ok {
|
if _, ok := globalBucketObjectLockConfig.Get(bucket); !ok {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidBucketObjectLockConfiguration), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidBucketObjectLockConfiguration), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
@ -2780,13 +2844,13 @@ func (api objectAPIHandlers) PutObjectRetentionHandler(w http.ResponseWriter, r
|
|||||||
writeErrorResponse(ctx, w, apiErr, r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, apiErr, r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
getObjectInfo := objectAPI.GetObjectInfo
|
getObjectInfo := objectAPI.GetObjectInfo
|
||||||
if api.CacheAPI() != nil {
|
if api.CacheAPI() != nil {
|
||||||
getObjectInfo = api.CacheAPI().GetObjectInfo
|
getObjectInfo = api.CacheAPI().GetObjectInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
govBypassPerms := isPutActionAllowed(getRequestAuthType(r), bucket, object, r, policy.BypassGovernanceRetentionAction)
|
objInfo, s3Err := enforceRetentionBypassForPut(ctx, r, bucket, object, getObjectInfo, objRetention, cred, owner, claims)
|
||||||
objInfo, s3Err := enforceRetentionBypassForPut(ctx, r, bucket, object, getObjectInfo, govBypassPerms, objRetention)
|
|
||||||
if s3Err != ErrNone {
|
if s3Err != ErrNone {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
@ -2794,7 +2858,7 @@ func (api objectAPIHandlers) PutObjectRetentionHandler(w http.ResponseWriter, r
|
|||||||
|
|
||||||
objInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockMode)] = string(objRetention.Mode)
|
objInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockMode)] = string(objRetention.Mode)
|
||||||
objInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockRetainUntilDate)] = objRetention.RetainUntilDate.UTC().Format(time.RFC3339)
|
objInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockRetainUntilDate)] = objRetention.RetainUntilDate.UTC().Format(time.RFC3339)
|
||||||
objInfo.metadataOnly = true
|
objInfo.metadataOnly = true // Perform only metadata updates.
|
||||||
if _, err = objectAPI.CopyObject(ctx, bucket, object, bucket, object, objInfo, ObjectOptions{}, ObjectOptions{}); err != nil {
|
if _, err = objectAPI.CopyObject(ctx, bucket, object, bucket, object, objInfo, ObjectOptions{}, ObjectOptions{}); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
|
@ -20,62 +20,164 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
|
"github.com/minio/minio/pkg/auth"
|
||||||
objectlock "github.com/minio/minio/pkg/bucket/object/lock"
|
objectlock "github.com/minio/minio/pkg/bucket/object/lock"
|
||||||
|
"github.com/minio/minio/pkg/bucket/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Similar to enforceRetentionBypassForDelete but for WebUI
|
||||||
|
func enforceRetentionBypassForDeleteWeb(ctx context.Context, r *http.Request, bucket, object string, getObjectInfoFn GetObjectInfoFn) APIErrorCode {
|
||||||
|
opts, err := getOpts(ctx, r, bucket, object)
|
||||||
|
if err != nil {
|
||||||
|
return toAPIErrorCode(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
oi, err := getObjectInfoFn(ctx, bucket, object, opts)
|
||||||
|
if err != nil {
|
||||||
|
return toAPIErrorCode(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
lhold := objectlock.GetObjectLegalHoldMeta(oi.UserDefined)
|
||||||
|
if lhold.Status.Valid() && lhold.Status == objectlock.LegalHoldOn {
|
||||||
|
return ErrObjectLocked
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := objectlock.GetObjectRetentionMeta(oi.UserDefined)
|
||||||
|
if ret.Mode.Valid() {
|
||||||
|
switch ret.Mode {
|
||||||
|
case objectlock.RetCompliance:
|
||||||
|
// In compliance mode, a protected object version can't be overwritten
|
||||||
|
// or deleted by any user, including the root user in your AWS account.
|
||||||
|
// When an object is locked in compliance mode, its retention mode can't
|
||||||
|
// be changed, and its retention period can't be shortened. Compliance mode
|
||||||
|
// ensures that an object version can't be overwritten or deleted for the
|
||||||
|
// duration of the retention period.
|
||||||
|
t, err := objectlock.UTCNowNTP()
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
return ErrObjectLocked
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ret.RetainUntilDate.Before(t) {
|
||||||
|
return ErrObjectLocked
|
||||||
|
}
|
||||||
|
return ErrNone
|
||||||
|
case objectlock.RetGovernance:
|
||||||
|
// In governance mode, users can't overwrite or delete an object
|
||||||
|
// version or alter its lock settings unless they have special
|
||||||
|
// permissions. With governance mode, you protect objects against
|
||||||
|
// being deleted by most users, but you can still grant some users
|
||||||
|
// permission to alter the retention settings or delete the object
|
||||||
|
// if necessary. You can also use governance mode to test retention-period
|
||||||
|
// settings before creating a compliance-mode retention period.
|
||||||
|
// To override or remove governance-mode retention settings, a
|
||||||
|
// user must have the s3:BypassGovernanceRetention permission
|
||||||
|
// and must explicitly include x-amz-bypass-governance-retention:true
|
||||||
|
// as a request header with any request that requires overriding
|
||||||
|
// governance mode.
|
||||||
|
byPassSet := objectlock.IsObjectLockGovernanceBypassSet(r.Header)
|
||||||
|
if !byPassSet {
|
||||||
|
t, err := objectlock.UTCNowNTP()
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
return ErrObjectLocked
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ret.RetainUntilDate.Before(t) {
|
||||||
|
return ErrObjectLocked
|
||||||
|
}
|
||||||
|
return ErrNone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ErrNone
|
||||||
|
}
|
||||||
|
|
||||||
// enforceRetentionBypassForDelete enforces whether an existing object under governance can be deleted
|
// enforceRetentionBypassForDelete enforces whether an existing object under governance can be deleted
|
||||||
// with governance bypass headers set in the request.
|
// with governance bypass headers set in the request.
|
||||||
// Objects under site wide WORM can never be overwritten.
|
// Objects under site wide WORM can never be overwritten.
|
||||||
// For objects in "Governance" mode, overwrite is allowed if a) object retention date is past OR
|
// For objects in "Governance" mode, overwrite is allowed if a) object retention date is past OR
|
||||||
// governance bypass headers are set and user has governance bypass permissions.
|
// governance bypass headers are set and user has governance bypass permissions.
|
||||||
// Objects in "Compliance" mode can be overwritten only if retention date is past.
|
// Objects in "Compliance" mode can be overwritten only if retention date is past.
|
||||||
func enforceRetentionBypassForDelete(ctx context.Context, r *http.Request, bucket, object string, getObjectInfoFn GetObjectInfoFn, govBypassPerm APIErrorCode) (oi ObjectInfo, s3Err APIErrorCode) {
|
func enforceRetentionBypassForDelete(ctx context.Context, r *http.Request, bucket, object string, getObjectInfoFn GetObjectInfoFn) APIErrorCode {
|
||||||
if globalWORMEnabled {
|
opts, err := getOpts(ctx, r, bucket, object)
|
||||||
return oi, ErrObjectLocked
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
var opts ObjectOptions
|
|
||||||
opts, err = getOpts(ctx, r, bucket, object)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return oi, toAPIErrorCode(ctx, err)
|
return toAPIErrorCode(ctx, err)
|
||||||
}
|
}
|
||||||
oi, err = getObjectInfoFn(ctx, bucket, object, opts)
|
|
||||||
|
oi, err := getObjectInfoFn(ctx, bucket, object, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// ignore case where object no longer exists
|
return toAPIErrorCode(ctx, err)
|
||||||
if toAPIError(ctx, err).Code == "NoSuchKey" {
|
|
||||||
oi.UserDefined = map[string]string{}
|
|
||||||
return oi, ErrNone
|
|
||||||
}
|
|
||||||
return oi, toAPIErrorCode(ctx, err)
|
|
||||||
}
|
}
|
||||||
ret := objectlock.GetObjectRetentionMeta(oi.UserDefined)
|
|
||||||
lhold := objectlock.GetObjectLegalHoldMeta(oi.UserDefined)
|
lhold := objectlock.GetObjectLegalHoldMeta(oi.UserDefined)
|
||||||
if lhold.Status == objectlock.ON {
|
if lhold.Status.Valid() && lhold.Status == objectlock.LegalHoldOn {
|
||||||
return oi, ErrObjectLocked
|
return ErrObjectLocked
|
||||||
}
|
}
|
||||||
// Here bucket does not support object lock
|
|
||||||
if ret.Mode == objectlock.Invalid {
|
ret := objectlock.GetObjectRetentionMeta(oi.UserDefined)
|
||||||
return oi, ErrNone
|
if ret.Mode.Valid() {
|
||||||
|
switch ret.Mode {
|
||||||
|
case objectlock.RetCompliance:
|
||||||
|
// In compliance mode, a protected object version can't be overwritten
|
||||||
|
// or deleted by any user, including the root user in your AWS account.
|
||||||
|
// When an object is locked in compliance mode, its retention mode can't
|
||||||
|
// be changed, and its retention period can't be shortened. Compliance mode
|
||||||
|
// ensures that an object version can't be overwritten or deleted for the
|
||||||
|
// duration of the retention period.
|
||||||
|
t, err := objectlock.UTCNowNTP()
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
return ErrObjectLocked
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ret.RetainUntilDate.Before(t) {
|
||||||
|
return ErrObjectLocked
|
||||||
|
}
|
||||||
|
return ErrNone
|
||||||
|
case objectlock.RetGovernance:
|
||||||
|
// In governance mode, users can't overwrite or delete an object
|
||||||
|
// version or alter its lock settings unless they have special
|
||||||
|
// permissions. With governance mode, you protect objects against
|
||||||
|
// being deleted by most users, but you can still grant some users
|
||||||
|
// permission to alter the retention settings or delete the object
|
||||||
|
// if necessary. You can also use governance mode to test retention-period
|
||||||
|
// settings before creating a compliance-mode retention period.
|
||||||
|
// To override or remove governance-mode retention settings, a
|
||||||
|
// user must have the s3:BypassGovernanceRetention permission
|
||||||
|
// and must explicitly include x-amz-bypass-governance-retention:true
|
||||||
|
// as a request header with any request that requires overriding
|
||||||
|
// governance mode.
|
||||||
|
//
|
||||||
|
byPassSet := objectlock.IsObjectLockGovernanceBypassSet(r.Header)
|
||||||
|
if !byPassSet {
|
||||||
|
t, err := objectlock.UTCNowNTP()
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
return ErrObjectLocked
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ret.RetainUntilDate.Before(t) {
|
||||||
|
return ErrObjectLocked
|
||||||
|
}
|
||||||
|
return ErrNone
|
||||||
|
}
|
||||||
|
// https://docs.aws.amazon.com/AmazonS3/latest/dev/object-lock-overview.html#object-lock-retention-modes
|
||||||
|
// If you try to delete objects protected by governance mode and have s3:BypassGovernanceRetention
|
||||||
|
// or s3:GetBucketObjectLockConfiguration permissions, the operation will succeed.
|
||||||
|
govBypassPerms1 := checkRequestAuthType(ctx, r, policy.BypassGovernanceRetentionAction, bucket, object)
|
||||||
|
govBypassPerms2 := checkRequestAuthType(ctx, r, policy.GetBucketObjectLockConfigurationAction, bucket, object)
|
||||||
|
if govBypassPerms1 != ErrNone && govBypassPerms2 != ErrNone {
|
||||||
|
return ErrAccessDenied
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ret.Mode != objectlock.Compliance && ret.Mode != objectlock.Governance {
|
return ErrNone
|
||||||
return oi, ErrUnknownWORMModeDirective
|
|
||||||
}
|
|
||||||
t, err := objectlock.UTCNowNTP()
|
|
||||||
if err != nil {
|
|
||||||
logger.LogIf(ctx, err)
|
|
||||||
return oi, ErrObjectLocked
|
|
||||||
}
|
|
||||||
if ret.RetainUntilDate.Before(t) {
|
|
||||||
return oi, ErrNone
|
|
||||||
}
|
|
||||||
if objectlock.IsObjectLockGovernanceBypassSet(r.Header) && ret.Mode == objectlock.Governance && govBypassPerm == ErrNone {
|
|
||||||
return oi, ErrNone
|
|
||||||
}
|
|
||||||
return oi, ErrObjectLocked
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// enforceRetentionBypassForPut enforces whether an existing object under governance can be overwritten
|
// enforceRetentionBypassForPut enforces whether an existing object under governance can be overwritten
|
||||||
@ -84,66 +186,68 @@ func enforceRetentionBypassForDelete(ctx context.Context, r *http.Request, bucke
|
|||||||
// For objects in "Governance" mode, overwrite is allowed if a) object retention date is past OR
|
// For objects in "Governance" mode, overwrite is allowed if a) object retention date is past OR
|
||||||
// governance bypass headers are set and user has governance bypass permissions.
|
// governance bypass headers are set and user has governance bypass permissions.
|
||||||
// Objects in compliance mode can be overwritten only if retention date is being extended. No mode change is permitted.
|
// Objects in compliance mode can be overwritten only if retention date is being extended. No mode change is permitted.
|
||||||
func enforceRetentionBypassForPut(ctx context.Context, r *http.Request, bucket, object string, getObjectInfoFn GetObjectInfoFn, govBypassPerm APIErrorCode, objRetention *objectlock.ObjectRetention) (oi ObjectInfo, s3Err APIErrorCode) {
|
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) {
|
||||||
if globalWORMEnabled {
|
byPassSet := objectlock.IsObjectLockGovernanceBypassSet(r.Header)
|
||||||
return oi, ErrObjectLocked
|
opts, err := getOpts(ctx, r, bucket, object)
|
||||||
|
if err != nil {
|
||||||
|
return ObjectInfo{}, toAPIErrorCode(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
oi, err := getObjectInfoFn(ctx, bucket, object, opts)
|
||||||
var opts ObjectOptions
|
|
||||||
opts, err = getOpts(ctx, r, bucket, object)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return oi, toAPIErrorCode(ctx, err)
|
return oi, toAPIErrorCode(ctx, err)
|
||||||
}
|
}
|
||||||
oi, err = getObjectInfoFn(ctx, bucket, object, opts)
|
|
||||||
if err != nil {
|
|
||||||
// ignore case where object no longer exists
|
|
||||||
if toAPIError(ctx, err).Code == "NoSuchKey" {
|
|
||||||
oi.UserDefined = map[string]string{}
|
|
||||||
return oi, ErrNone
|
|
||||||
}
|
|
||||||
return oi, toAPIErrorCode(ctx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ret := objectlock.GetObjectRetentionMeta(oi.UserDefined)
|
|
||||||
// no retention metadata on object
|
|
||||||
if ret.Mode == objectlock.Invalid {
|
|
||||||
if _, isWORMBucket := globalBucketObjectLockConfig.Get(bucket); !isWORMBucket {
|
|
||||||
return oi, ErrInvalidBucketObjectLockConfiguration
|
|
||||||
}
|
|
||||||
return oi, ErrNone
|
|
||||||
}
|
|
||||||
t, err := objectlock.UTCNowNTP()
|
t, err := objectlock.UTCNowNTP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return oi, ErrObjectLocked
|
return oi, ErrObjectLocked
|
||||||
}
|
}
|
||||||
|
|
||||||
if ret.Mode == objectlock.Compliance {
|
// Pass in relative days from current time, to additionally to verify "object-lock-remaining-retention-days" policy if any.
|
||||||
// Compliance retention mode cannot be changed and retention period cannot be shortened as per
|
days := int(math.Ceil(math.Abs(objRetention.RetainUntilDate.Sub(t).Hours()) / 24))
|
||||||
// https://docs.aws.amazon.com/AmazonS3/latest/dev/object-lock-overview.html#object-lock-retention-modes
|
|
||||||
if objRetention.Mode != objectlock.Compliance || objRetention.RetainUntilDate.Before(ret.RetainUntilDate.Time) {
|
|
||||||
return oi, ErrObjectLocked
|
|
||||||
}
|
|
||||||
if objRetention.RetainUntilDate.Before(t) {
|
|
||||||
return oi, ErrInvalidRetentionDate
|
|
||||||
}
|
|
||||||
return oi, ErrNone
|
|
||||||
}
|
|
||||||
|
|
||||||
if ret.Mode == objectlock.Governance {
|
ret := objectlock.GetObjectRetentionMeta(oi.UserDefined)
|
||||||
if !objectlock.IsObjectLockGovernanceBypassSet(r.Header) {
|
if ret.Mode.Valid() {
|
||||||
if objRetention.RetainUntilDate.Before(t) {
|
// Retention has expired you may change whatever you like.
|
||||||
return oi, ErrInvalidRetentionDate
|
if ret.RetainUntilDate.Before(t) {
|
||||||
|
perm := isPutRetentionAllowed(bucket, object,
|
||||||
|
days, objRetention.RetainUntilDate.Time,
|
||||||
|
objRetention.Mode, byPassSet, r, cred,
|
||||||
|
owner, claims)
|
||||||
|
return oi, perm
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ret.Mode {
|
||||||
|
case objectlock.RetGovernance:
|
||||||
|
govPerm := isPutRetentionAllowed(bucket, object, days,
|
||||||
|
objRetention.RetainUntilDate.Time, objRetention.Mode,
|
||||||
|
byPassSet, r, cred, owner, claims)
|
||||||
|
// Governance mode retention period cannot be shortened, if x-amz-bypass-governance is not set.
|
||||||
|
if !byPassSet {
|
||||||
|
if objRetention.Mode != objectlock.RetGovernance || objRetention.RetainUntilDate.Before((ret.RetainUntilDate.Time)) {
|
||||||
|
return oi, ErrObjectLocked
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if objRetention.RetainUntilDate.Before((ret.RetainUntilDate.Time)) {
|
return oi, govPerm
|
||||||
|
case objectlock.RetCompliance:
|
||||||
|
// Compliance retention mode cannot be changed or shortened.
|
||||||
|
// https://docs.aws.amazon.com/AmazonS3/latest/dev/object-lock-overview.html#object-lock-retention-modes
|
||||||
|
if objRetention.Mode != objectlock.RetCompliance || objRetention.RetainUntilDate.Before((ret.RetainUntilDate.Time)) {
|
||||||
return oi, ErrObjectLocked
|
return oi, ErrObjectLocked
|
||||||
}
|
}
|
||||||
return oi, ErrNone
|
compliancePerm := isPutRetentionAllowed(bucket, object,
|
||||||
|
days, objRetention.RetainUntilDate.Time, objRetention.Mode,
|
||||||
|
false, r, cred, owner, claims)
|
||||||
|
return oi, compliancePerm
|
||||||
}
|
}
|
||||||
return oi, govBypassPerm
|
return oi, ErrNone
|
||||||
}
|
} // No pre-existing retention metadata present.
|
||||||
return oi, ErrNone
|
|
||||||
|
perm := isPutRetentionAllowed(bucket, object,
|
||||||
|
days, objRetention.RetainUntilDate.Time,
|
||||||
|
objRetention.Mode, byPassSet, r, cred, owner, claims)
|
||||||
|
return oi, perm
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkPutObjectLockAllowed enforces object retention policy and legal hold policy
|
// checkPutObjectLockAllowed enforces object retention policy and legal hold policy
|
||||||
@ -156,16 +260,23 @@ func enforceRetentionBypassForPut(ctx context.Context, r *http.Request, bucket,
|
|||||||
// For objects in "Compliance" mode, retention date cannot be shortened, and mode cannot be altered.
|
// For objects in "Compliance" mode, retention date cannot be shortened, and mode cannot be altered.
|
||||||
// For objects with legal hold header set, the s3:PutObjectLegalHold permission is expected to be set
|
// For objects with legal hold header set, the s3:PutObjectLegalHold permission is expected to be set
|
||||||
// Both legal hold and retention can be applied independently on an object
|
// Both legal hold and retention can be applied independently on an object
|
||||||
func checkPutObjectLockAllowed(ctx context.Context, r *http.Request, bucket, object string, getObjectInfoFn GetObjectInfoFn, retentionPermErr, legalHoldPermErr APIErrorCode) (objectlock.Mode, objectlock.RetentionDate, objectlock.ObjectLegalHold, APIErrorCode) {
|
func checkPutObjectLockAllowed(ctx context.Context, r *http.Request, bucket, object string, getObjectInfoFn GetObjectInfoFn, retentionPermErr, legalHoldPermErr APIErrorCode) (objectlock.RetMode, objectlock.RetentionDate, objectlock.ObjectLegalHold, APIErrorCode) {
|
||||||
var mode objectlock.Mode
|
var mode objectlock.RetMode
|
||||||
var retainDate objectlock.RetentionDate
|
var retainDate objectlock.RetentionDate
|
||||||
var legalHold objectlock.ObjectLegalHold
|
var legalHold objectlock.ObjectLegalHold
|
||||||
|
|
||||||
retentionCfg, isWORMBucket := globalBucketObjectLockConfig.Get(bucket)
|
|
||||||
|
|
||||||
retentionRequested := objectlock.IsObjectLockRetentionRequested(r.Header)
|
retentionRequested := objectlock.IsObjectLockRetentionRequested(r.Header)
|
||||||
legalHoldRequested := objectlock.IsObjectLockLegalHoldRequested(r.Header)
|
legalHoldRequested := objectlock.IsObjectLockLegalHoldRequested(r.Header)
|
||||||
|
|
||||||
|
retentionCfg, isWORMBucket := globalBucketObjectLockConfig.Get(bucket)
|
||||||
|
if !isWORMBucket {
|
||||||
|
if legalHoldRequested || retentionRequested {
|
||||||
|
return mode, retainDate, legalHold, ErrInvalidBucketObjectLockConfiguration
|
||||||
|
}
|
||||||
|
// If this not a WORM enabled bucket, we should return right here.
|
||||||
|
return mode, retainDate, legalHold, ErrNone
|
||||||
|
}
|
||||||
|
|
||||||
var objExists bool
|
var objExists bool
|
||||||
opts, err := getOpts(ctx, r, bucket, object)
|
opts, err := getOpts(ctx, r, bucket, object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -177,33 +288,30 @@ func checkPutObjectLockAllowed(ctx context.Context, r *http.Request, bucket, obj
|
|||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return mode, retainDate, legalHold, ErrObjectLocked
|
return mode, retainDate, legalHold, ErrObjectLocked
|
||||||
}
|
}
|
||||||
|
|
||||||
if objInfo, err := getObjectInfoFn(ctx, bucket, object, opts); err == nil {
|
if objInfo, err := getObjectInfoFn(ctx, bucket, object, opts); err == nil {
|
||||||
objExists = true
|
objExists = true
|
||||||
r := objectlock.GetObjectRetentionMeta(objInfo.UserDefined)
|
r := objectlock.GetObjectRetentionMeta(objInfo.UserDefined)
|
||||||
if globalWORMEnabled || ((r.Mode == objectlock.Compliance) && r.RetainUntilDate.After(t)) {
|
if globalWORMEnabled || ((r.Mode == objectlock.RetCompliance) && r.RetainUntilDate.After(t)) {
|
||||||
return mode, retainDate, legalHold, ErrObjectLocked
|
return mode, retainDate, legalHold, ErrObjectLocked
|
||||||
}
|
}
|
||||||
mode = r.Mode
|
mode = r.Mode
|
||||||
retainDate = r.RetainUntilDate
|
retainDate = r.RetainUntilDate
|
||||||
legalHold = objectlock.GetObjectLegalHoldMeta(objInfo.UserDefined)
|
legalHold = objectlock.GetObjectLegalHoldMeta(objInfo.UserDefined)
|
||||||
// Disallow overwriting an object on legal hold
|
// Disallow overwriting an object on legal hold
|
||||||
if legalHold.Status == "ON" {
|
if legalHold.Status == objectlock.LegalHoldOn {
|
||||||
return mode, retainDate, legalHold, ErrObjectLocked
|
return mode, retainDate, legalHold, ErrObjectLocked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if legalHoldRequested {
|
if legalHoldRequested {
|
||||||
if !isWORMBucket {
|
|
||||||
return mode, retainDate, legalHold, ErrInvalidBucketObjectLockConfiguration
|
|
||||||
}
|
|
||||||
var lerr error
|
var lerr error
|
||||||
if legalHold, lerr = objectlock.ParseObjectLockLegalHoldHeaders(r.Header); lerr != nil {
|
if legalHold, lerr = objectlock.ParseObjectLockLegalHoldHeaders(r.Header); lerr != nil {
|
||||||
return mode, retainDate, legalHold, toAPIErrorCode(ctx, err)
|
return mode, retainDate, legalHold, toAPIErrorCode(ctx, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if retentionRequested {
|
if retentionRequested {
|
||||||
if !isWORMBucket {
|
|
||||||
return mode, retainDate, legalHold, ErrInvalidBucketObjectLockConfiguration
|
|
||||||
}
|
|
||||||
legalHold, err := objectlock.ParseObjectLockLegalHoldHeaders(r.Header)
|
legalHold, err := objectlock.ParseObjectLockLegalHoldHeaders(r.Header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mode, retainDate, legalHold, toAPIErrorCode(ctx, err)
|
return mode, retainDate, legalHold, toAPIErrorCode(ctx, err)
|
||||||
@ -215,9 +323,6 @@ func checkPutObjectLockAllowed(ctx context.Context, r *http.Request, bucket, obj
|
|||||||
if objExists && retainDate.After(t) {
|
if objExists && retainDate.After(t) {
|
||||||
return mode, retainDate, legalHold, ErrObjectLocked
|
return mode, retainDate, legalHold, ErrObjectLocked
|
||||||
}
|
}
|
||||||
if rMode == objectlock.Invalid {
|
|
||||||
return mode, retainDate, legalHold, toAPIErrorCode(ctx, objectlock.ErrObjectLockInvalidHeaders)
|
|
||||||
}
|
|
||||||
if retentionPermErr != ErrNone {
|
if retentionPermErr != ErrNone {
|
||||||
return mode, retainDate, legalHold, retentionPermErr
|
return mode, retainDate, legalHold, retentionPermErr
|
||||||
}
|
}
|
||||||
@ -241,7 +346,7 @@ func checkPutObjectLockAllowed(ctx context.Context, r *http.Request, bucket, obj
|
|||||||
// inherit retention from bucket configuration
|
// inherit retention from bucket configuration
|
||||||
return retentionCfg.Mode, objectlock.RetentionDate{Time: t.Add(retentionCfg.Validity)}, legalHold, ErrNone
|
return retentionCfg.Mode, objectlock.RetentionDate{Time: t.Add(retentionCfg.Validity)}, legalHold, ErrNone
|
||||||
}
|
}
|
||||||
return objectlock.Mode(""), objectlock.RetentionDate{}, legalHold, ErrNone
|
return "", objectlock.RetentionDate{}, legalHold, ErrNone
|
||||||
}
|
}
|
||||||
return mode, retainDate, legalHold, ErrNone
|
return mode, retainDate, legalHold, ErrNone
|
||||||
}
|
}
|
||||||
|
@ -20,10 +20,10 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -146,7 +146,7 @@ func NewPolicySys() *PolicySys {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getConditionValues(request *http.Request, locationConstraint string, username string, claims map[string]interface{}) map[string][]string {
|
func getConditionValues(r *http.Request, lc string, username string, claims map[string]interface{}) map[string][]string {
|
||||||
currTime := UTCNow()
|
currTime := UTCNow()
|
||||||
|
|
||||||
principalType := "Anonymous"
|
principalType := "Anonymous"
|
||||||
@ -156,22 +156,21 @@ func getConditionValues(request *http.Request, locationConstraint string, userna
|
|||||||
|
|
||||||
args := map[string][]string{
|
args := map[string][]string{
|
||||||
"CurrentTime": {currTime.Format(time.RFC3339)},
|
"CurrentTime": {currTime.Format(time.RFC3339)},
|
||||||
"EpochTime": {fmt.Sprintf("%d", currTime.Unix())},
|
"EpochTime": {strconv.FormatInt(currTime.Unix(), 10)},
|
||||||
|
"SecureTransport": {strconv.FormatBool(r.TLS != nil)},
|
||||||
|
"SourceIp": {handlers.GetSourceIP(r)},
|
||||||
|
"UserAgent": {r.UserAgent()},
|
||||||
|
"Referer": {r.Referer()},
|
||||||
"principaltype": {principalType},
|
"principaltype": {principalType},
|
||||||
"SecureTransport": {fmt.Sprintf("%t", request.TLS != nil)},
|
|
||||||
"SourceIp": {handlers.GetSourceIP(request)},
|
|
||||||
"UserAgent": {request.UserAgent()},
|
|
||||||
"Referer": {request.Referer()},
|
|
||||||
"userid": {username},
|
"userid": {username},
|
||||||
"username": {username},
|
"username": {username},
|
||||||
}
|
}
|
||||||
|
|
||||||
if locationConstraint != "" {
|
if lc != "" {
|
||||||
args["LocationConstraint"] = []string{locationConstraint}
|
args["LocationConstraint"] = []string{lc}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: support object-lock-remaining-retention-days
|
cloneHeader := r.Header.Clone()
|
||||||
cloneHeader := request.Header.Clone()
|
|
||||||
|
|
||||||
for _, objLock := range []string{
|
for _, objLock := range []string{
|
||||||
xhttp.AmzObjectLockMode,
|
xhttp.AmzObjectLockMode,
|
||||||
@ -193,7 +192,7 @@ func getConditionValues(request *http.Request, locationConstraint string, userna
|
|||||||
}
|
}
|
||||||
|
|
||||||
var cloneURLValues = url.Values{}
|
var cloneURLValues = url.Values{}
|
||||||
for k, v := range request.URL.Query() {
|
for k, v := range r.URL.Query() {
|
||||||
cloneURLValues[k] = v
|
cloneURLValues[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -588,11 +588,17 @@ func (web *webAPIHandlers) RemoveObject(r *http.Request, args *RemoveObjectArgs,
|
|||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return toJSONError(ctx, errServerNotInitialized)
|
return toJSONError(ctx, errServerNotInitialized)
|
||||||
}
|
}
|
||||||
listObjects := objectAPI.ListObjects
|
|
||||||
getObjectInfo := objectAPI.GetObjectInfo
|
getObjectInfo := objectAPI.GetObjectInfo
|
||||||
if web.CacheAPI() != nil {
|
if web.CacheAPI() != nil {
|
||||||
getObjectInfo = web.CacheAPI().GetObjectInfo
|
getObjectInfo = web.CacheAPI().GetObjectInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteObjects := objectAPI.DeleteObjects
|
||||||
|
if web.CacheAPI() != nil {
|
||||||
|
deleteObjects = web.CacheAPI().DeleteObjects
|
||||||
|
}
|
||||||
|
|
||||||
claims, owner, authErr := webRequestAuthenticate(r)
|
claims, owner, authErr := webRequestAuthenticate(r)
|
||||||
if authErr != nil {
|
if authErr != nil {
|
||||||
if authErr == errNoAuthToken {
|
if authErr == errNoAuthToken {
|
||||||
@ -688,6 +694,17 @@ next:
|
|||||||
}) {
|
}) {
|
||||||
govBypassPerms = ErrNone
|
govBypassPerms = ErrNone
|
||||||
}
|
}
|
||||||
|
if globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
|
AccountName: claims.AccessKey,
|
||||||
|
Action: iampolicy.GetBucketObjectLockConfigurationAction,
|
||||||
|
BucketName: args.BucketName,
|
||||||
|
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
|
||||||
|
IsOwner: owner,
|
||||||
|
ObjectName: objectName,
|
||||||
|
Claims: claims.Map(),
|
||||||
|
}) {
|
||||||
|
govBypassPerms = ErrNone
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if authErr == errNoAuthToken {
|
if authErr == errNoAuthToken {
|
||||||
// Check if object is allowed to be deleted anonymously
|
// Check if object is allowed to be deleted anonymously
|
||||||
@ -711,12 +728,44 @@ next:
|
|||||||
}) {
|
}) {
|
||||||
govBypassPerms = ErrNone
|
govBypassPerms = ErrNone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if object is allowed to be deleted anonymously
|
||||||
|
if globalPolicySys.IsAllowed(policy.Args{
|
||||||
|
Action: policy.GetBucketObjectLockConfigurationAction,
|
||||||
|
BucketName: args.BucketName,
|
||||||
|
ConditionValues: getConditionValues(r, "", "", nil),
|
||||||
|
IsOwner: false,
|
||||||
|
ObjectName: objectName,
|
||||||
|
}) {
|
||||||
|
govBypassPerms = ErrNone
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if _, err := enforceRetentionBypassForDelete(ctx, r, args.BucketName, objectName, getObjectInfo, govBypassPerms); err != ErrNone {
|
if govBypassPerms != ErrNone {
|
||||||
return toJSONError(ctx, errAccessDenied)
|
return toJSONError(ctx, errAccessDenied)
|
||||||
}
|
}
|
||||||
if err = deleteObject(ctx, objectAPI, web.CacheAPI(), args.BucketName, objectName, r); err != nil {
|
|
||||||
break next
|
apiErr := ErrNone
|
||||||
|
// Deny if global WORM is enabled
|
||||||
|
if globalWORMEnabled {
|
||||||
|
opts, err := getOpts(ctx, r, args.BucketName, objectName)
|
||||||
|
if err != nil {
|
||||||
|
apiErr = toAPIErrorCode(ctx, err)
|
||||||
|
} else {
|
||||||
|
if _, err := getObjectInfo(ctx, args.BucketName, objectName, opts); err == nil {
|
||||||
|
apiErr = ErrMethodNotAllowed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := globalBucketObjectLockConfig.Get(args.BucketName); ok && (apiErr == ErrNone) {
|
||||||
|
apiErr = enforceRetentionBypassForDeleteWeb(ctx, r, args.BucketName, objectName, getObjectInfo)
|
||||||
|
if apiErr != ErrNone && apiErr != ErrNoSuchKey {
|
||||||
|
return toJSONError(ctx, errAccessDenied)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if apiErr == ErrNone {
|
||||||
|
if err = deleteObject(ctx, objectAPI, web.CacheAPI(), args.BucketName, objectName, r); err != nil {
|
||||||
|
break next
|
||||||
|
}
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -746,23 +795,34 @@ next:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For directories, list the contents recursively and remove.
|
// Allocate new results channel to receive ObjectInfo.
|
||||||
marker := ""
|
objInfoCh := make(chan ObjectInfo)
|
||||||
|
|
||||||
|
// Walk through all objects
|
||||||
|
if err = objectAPI.Walk(ctx, args.BucketName, objectName, objInfoCh); err != nil {
|
||||||
|
break next
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
var lo ListObjectsInfo
|
var objects []string
|
||||||
lo, err = listObjects(ctx, args.BucketName, objectName, marker, "", maxObjectList)
|
for obj := range objInfoCh {
|
||||||
if err != nil {
|
if len(objects) == maxObjectList {
|
||||||
|
// Reached maximum delete requests, attempt a delete for now.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
objects = append(objects, obj.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing to do.
|
||||||
|
if len(objects) == 0 {
|
||||||
break next
|
break next
|
||||||
}
|
}
|
||||||
marker = lo.NextMarker
|
|
||||||
for _, obj := range lo.Objects {
|
// Deletes a list of objects.
|
||||||
err = deleteObject(ctx, objectAPI, web.CacheAPI(), args.BucketName, obj.Name, r)
|
_, err = deleteObjects(ctx, args.BucketName, objects)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break next
|
logger.LogIf(ctx, err)
|
||||||
}
|
break next
|
||||||
}
|
|
||||||
if !lo.IsTruncated {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1097,27 +1157,30 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Ensure that metadata does not contain sensitive information
|
// Ensure that metadata does not contain sensitive information
|
||||||
crypto.RemoveSensitiveEntries(metadata)
|
crypto.RemoveSensitiveEntries(metadata)
|
||||||
|
|
||||||
getObjectInfo := objectAPI.GetObjectInfo
|
retentionRequested := objectlock.IsObjectLockRetentionRequested(r.Header)
|
||||||
if web.CacheAPI() != nil {
|
legalHoldRequested := objectlock.IsObjectLockLegalHoldRequested(r.Header)
|
||||||
getObjectInfo = web.CacheAPI().GetObjectInfo
|
|
||||||
}
|
|
||||||
// enforce object retention rules
|
|
||||||
retentionMode, retentionDate, legalHold, s3Err := checkPutObjectLockAllowed(ctx, r, bucket, object, getObjectInfo, retPerms, holdPerms)
|
|
||||||
if s3Err == ErrNone && retentionMode != "" {
|
|
||||||
opts.UserDefined[xhttp.AmzObjectLockMode] = string(retentionMode)
|
|
||||||
opts.UserDefined[xhttp.AmzObjectLockRetainUntilDate] = retentionDate.UTC().Format(time.RFC3339)
|
|
||||||
}
|
|
||||||
if s3Err == ErrNone && legalHold.Status != "" {
|
|
||||||
opts.UserDefined[xhttp.AmzObjectLockLegalHold] = string(legalHold.Status)
|
|
||||||
}
|
|
||||||
if s3Err != ErrNone {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL, guessIsBrowserReq(r))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
putObject := objectAPI.PutObject
|
putObject := objectAPI.PutObject
|
||||||
|
getObjectInfo := objectAPI.GetObjectInfo
|
||||||
if web.CacheAPI() != nil {
|
if web.CacheAPI() != nil {
|
||||||
putObject = web.CacheAPI().PutObject
|
putObject = web.CacheAPI().PutObject
|
||||||
|
getObjectInfo = web.CacheAPI().GetObjectInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
if retentionRequested || legalHoldRequested {
|
||||||
|
// enforce object retention rules
|
||||||
|
retentionMode, retentionDate, legalHold, s3Err := checkPutObjectLockAllowed(ctx, r, bucket, object, getObjectInfo, retPerms, holdPerms)
|
||||||
|
if s3Err == ErrNone && retentionMode != "" {
|
||||||
|
opts.UserDefined[xhttp.AmzObjectLockMode] = string(retentionMode)
|
||||||
|
opts.UserDefined[xhttp.AmzObjectLockRetainUntilDate] = retentionDate.UTC().Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
if s3Err == ErrNone && legalHold.Status != "" {
|
||||||
|
opts.UserDefined[xhttp.AmzObjectLockLegalHold] = string(legalHold.Status)
|
||||||
|
}
|
||||||
|
if s3Err != ErrNone {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL, guessIsBrowserReq(r))
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
objInfo, err := putObject(context.Background(), bucket, object, pReader, opts)
|
objInfo, err := putObject(context.Background(), bucket, object, pReader, opts)
|
||||||
@ -1462,13 +1525,12 @@ func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) {
|
|||||||
writeWebErrorResponse(w, errInvalidBucketName)
|
writeWebErrorResponse(w, errInvalidBucketName)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
getObjectNInfo := objectAPI.GetObjectNInfo
|
getObjectNInfo := objectAPI.GetObjectNInfo
|
||||||
if web.CacheAPI() != nil {
|
if web.CacheAPI() != nil {
|
||||||
getObjectNInfo = web.CacheAPI().GetObjectNInfo
|
getObjectNInfo = web.CacheAPI().GetObjectNInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
listObjects := objectAPI.ListObjects
|
|
||||||
|
|
||||||
archive := zip.NewWriter(w)
|
archive := zip.NewWriter(w)
|
||||||
defer archive.Close()
|
defer archive.Close()
|
||||||
|
|
||||||
@ -1541,29 +1603,24 @@ func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) {
|
|||||||
// If not a directory, compress the file and write it to response.
|
// If not a directory, compress the file and write it to response.
|
||||||
err := zipit(pathJoin(args.Prefix, object))
|
err := zipit(pathJoin(args.Prefix, object))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// For directories, list the contents recursively and write the objects as compressed
|
objInfoCh := make(chan ObjectInfo)
|
||||||
// date to the response writer.
|
|
||||||
marker := ""
|
// Walk through all objects
|
||||||
for {
|
if err := objectAPI.Walk(ctx, args.BucketName, pathJoin(args.Prefix, object), objInfoCh); err != nil {
|
||||||
lo, err := listObjects(ctx, args.BucketName, pathJoin(args.Prefix, object), marker, "",
|
logger.LogIf(ctx, err)
|
||||||
maxObjectList)
|
continue
|
||||||
if err != nil {
|
}
|
||||||
return
|
|
||||||
}
|
for obj := range objInfoCh {
|
||||||
marker = lo.NextMarker
|
if err := zipit(obj.Name); err != nil {
|
||||||
for _, obj := range lo.Objects {
|
logger.LogIf(ctx, err)
|
||||||
err = zipit(obj.Name)
|
continue
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !lo.IsTruncated {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,33 +28,36 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/beevik/ntp"
|
"github.com/beevik/ntp"
|
||||||
xhttp "github.com/minio/minio/cmd/http"
|
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/env"
|
"github.com/minio/minio/pkg/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mode - object retention mode.
|
// RetMode - object retention mode.
|
||||||
type Mode string
|
type RetMode string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Governance - governance mode.
|
// RetGovernance - governance mode.
|
||||||
Governance Mode = "GOVERNANCE"
|
RetGovernance RetMode = "GOVERNANCE"
|
||||||
|
|
||||||
// Compliance - compliance mode.
|
// RetCompliance - compliance mode.
|
||||||
Compliance Mode = "COMPLIANCE"
|
RetCompliance RetMode = "COMPLIANCE"
|
||||||
|
|
||||||
// Invalid - invalid retention mode.
|
|
||||||
Invalid Mode = ""
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseMode(modeStr string) (mode Mode) {
|
// Valid - returns if retention mode is valid
|
||||||
|
func (r RetMode) Valid() bool {
|
||||||
|
switch r {
|
||||||
|
case RetGovernance, RetCompliance:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRetMode(modeStr string) (mode RetMode) {
|
||||||
switch strings.ToUpper(modeStr) {
|
switch strings.ToUpper(modeStr) {
|
||||||
case "GOVERNANCE":
|
case "GOVERNANCE":
|
||||||
mode = Governance
|
mode = RetGovernance
|
||||||
case "COMPLIANCE":
|
case "COMPLIANCE":
|
||||||
mode = Compliance
|
mode = RetCompliance
|
||||||
default:
|
|
||||||
mode = Invalid
|
|
||||||
}
|
}
|
||||||
return mode
|
return mode
|
||||||
}
|
}
|
||||||
@ -63,23 +66,40 @@ func parseMode(modeStr string) (mode Mode) {
|
|||||||
type LegalHoldStatus string
|
type LegalHoldStatus string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// ON -legal hold is on.
|
// LegalHoldOn - legal hold is on.
|
||||||
ON LegalHoldStatus = "ON"
|
LegalHoldOn LegalHoldStatus = "ON"
|
||||||
|
|
||||||
// OFF -legal hold is off.
|
// LegalHoldOff - legal hold is off.
|
||||||
OFF LegalHoldStatus = "OFF"
|
LegalHoldOff LegalHoldStatus = "OFF"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseLegalHoldStatus(holdStr string) LegalHoldStatus {
|
// Valid - returns true if legal hold status has valid values
|
||||||
|
func (l LegalHoldStatus) Valid() bool {
|
||||||
|
switch l {
|
||||||
|
case LegalHoldOn, LegalHoldOff:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseLegalHoldStatus(holdStr string) (st LegalHoldStatus) {
|
||||||
switch strings.ToUpper(holdStr) {
|
switch strings.ToUpper(holdStr) {
|
||||||
case "ON":
|
case "ON":
|
||||||
return ON
|
st = LegalHoldOn
|
||||||
case "OFF":
|
case "OFF":
|
||||||
return OFF
|
st = LegalHoldOff
|
||||||
}
|
}
|
||||||
return LegalHoldStatus("")
|
return st
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bypass retention governance header.
|
||||||
|
const (
|
||||||
|
AmzObjectLockBypassRetGovernance = "X-Amz-Bypass-Governance-Retention"
|
||||||
|
AmzObjectLockRetainUntilDate = "X-Amz-Object-Lock-Retain-Until-Date"
|
||||||
|
AmzObjectLockMode = "X-Amz-Object-Lock-Mode"
|
||||||
|
AmzObjectLockLegalHold = "X-Amz-Object-Lock-Legal-Hold"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrMalformedBucketObjectConfig -indicates that the bucket object lock config is malformed
|
// ErrMalformedBucketObjectConfig -indicates that the bucket object lock config is malformed
|
||||||
ErrMalformedBucketObjectConfig = errors.New("invalid bucket object lock config")
|
ErrMalformedBucketObjectConfig = errors.New("invalid bucket object lock config")
|
||||||
@ -118,13 +138,13 @@ func UTCNowNTP() (time.Time, error) {
|
|||||||
|
|
||||||
// Retention - bucket level retention configuration.
|
// Retention - bucket level retention configuration.
|
||||||
type Retention struct {
|
type Retention struct {
|
||||||
Mode Mode
|
Mode RetMode
|
||||||
Validity time.Duration
|
Validity time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsEmpty - returns whether retention is empty or not.
|
// IsEmpty - returns whether retention is empty or not.
|
||||||
func (r Retention) IsEmpty() bool {
|
func (r Retention) IsEmpty() bool {
|
||||||
return r.Mode == "" || r.Validity == 0
|
return !r.Mode.Valid() || r.Validity == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retain - check whether given date is retainable by validity time.
|
// Retain - check whether given date is retainable by validity time.
|
||||||
@ -176,7 +196,7 @@ func NewBucketObjectLockConfig() *BucketObjectLockConfig {
|
|||||||
// DefaultRetention - default retention configuration.
|
// DefaultRetention - default retention configuration.
|
||||||
type DefaultRetention struct {
|
type DefaultRetention struct {
|
||||||
XMLName xml.Name `xml:"DefaultRetention"`
|
XMLName xml.Name `xml:"DefaultRetention"`
|
||||||
Mode Mode `xml:"Mode"`
|
Mode RetMode `xml:"Mode"`
|
||||||
Days *uint64 `xml:"Days"`
|
Days *uint64 `xml:"Days"`
|
||||||
Years *uint64 `xml:"Years"`
|
Years *uint64 `xml:"Years"`
|
||||||
}
|
}
|
||||||
@ -198,8 +218,8 @@ func (dr *DefaultRetention) UnmarshalXML(d *xml.Decoder, start xml.StartElement)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch string(retention.Mode) {
|
switch retention.Mode {
|
||||||
case "GOVERNANCE", "COMPLIANCE":
|
case RetGovernance, RetCompliance:
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown retention mode %v", retention.Mode)
|
return fmt.Errorf("unknown retention mode %v", retention.Mode)
|
||||||
}
|
}
|
||||||
@ -282,10 +302,13 @@ func (config *Config) ToRetention() (r Retention) {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Maximum 4KiB size per object lock config.
|
||||||
|
const maxObjectLockConfigSize = 1 << 12
|
||||||
|
|
||||||
// ParseObjectLockConfig parses ObjectLockConfig from xml
|
// ParseObjectLockConfig parses ObjectLockConfig from xml
|
||||||
func ParseObjectLockConfig(reader io.Reader) (*Config, error) {
|
func ParseObjectLockConfig(reader io.Reader) (*Config, error) {
|
||||||
config := Config{}
|
config := Config{}
|
||||||
if err := xml.NewDecoder(reader).Decode(&config); err != nil {
|
if err := xml.NewDecoder(io.LimitReader(reader, maxObjectLockConfigSize)).Decode(&config); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -338,17 +361,20 @@ func (rDate *RetentionDate) MarshalXML(e *xml.Encoder, startElement xml.StartEle
|
|||||||
type ObjectRetention struct {
|
type ObjectRetention struct {
|
||||||
XMLNS string `xml:"xmlns,attr,omitempty"`
|
XMLNS string `xml:"xmlns,attr,omitempty"`
|
||||||
XMLName xml.Name `xml:"Retention"`
|
XMLName xml.Name `xml:"Retention"`
|
||||||
Mode Mode `xml:"Mode,omitempty"`
|
Mode RetMode `xml:"Mode,omitempty"`
|
||||||
RetainUntilDate RetentionDate `xml:"RetainUntilDate,omitempty"`
|
RetainUntilDate RetentionDate `xml:"RetainUntilDate,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Maximum 4KiB size per object retention config.
|
||||||
|
const maxObjectRetentionSize = 1 << 12
|
||||||
|
|
||||||
// ParseObjectRetention constructs ObjectRetention struct from xml input
|
// ParseObjectRetention constructs ObjectRetention struct from xml input
|
||||||
func ParseObjectRetention(reader io.Reader) (*ObjectRetention, error) {
|
func ParseObjectRetention(reader io.Reader) (*ObjectRetention, error) {
|
||||||
ret := ObjectRetention{}
|
ret := ObjectRetention{}
|
||||||
if err := xml.NewDecoder(reader).Decode(&ret); err != nil {
|
if err := xml.NewDecoder(io.LimitReader(reader, maxObjectRetentionSize)).Decode(&ret); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if ret.Mode != Compliance && ret.Mode != Governance {
|
if !ret.Mode.Valid() {
|
||||||
return &ret, ErrUnknownWORMModeDirective
|
return &ret, ErrUnknownWORMModeDirective
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -367,10 +393,10 @@ func ParseObjectRetention(reader io.Reader) (*ObjectRetention, error) {
|
|||||||
|
|
||||||
// IsObjectLockRetentionRequested returns true if object lock retention headers are set.
|
// IsObjectLockRetentionRequested returns true if object lock retention headers are set.
|
||||||
func IsObjectLockRetentionRequested(h http.Header) bool {
|
func IsObjectLockRetentionRequested(h http.Header) bool {
|
||||||
if _, ok := h[xhttp.AmzObjectLockMode]; ok {
|
if _, ok := h[AmzObjectLockMode]; ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if _, ok := h[xhttp.AmzObjectLockRetainUntilDate]; ok {
|
if _, ok := h[AmzObjectLockRetainUntilDate]; ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@ -378,13 +404,13 @@ func IsObjectLockRetentionRequested(h http.Header) bool {
|
|||||||
|
|
||||||
// IsObjectLockLegalHoldRequested returns true if object lock legal hold header is set.
|
// IsObjectLockLegalHoldRequested returns true if object lock legal hold header is set.
|
||||||
func IsObjectLockLegalHoldRequested(h http.Header) bool {
|
func IsObjectLockLegalHoldRequested(h http.Header) bool {
|
||||||
_, ok := h[xhttp.AmzObjectLockLegalHold]
|
_, ok := h[AmzObjectLockLegalHold]
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsObjectLockGovernanceBypassSet returns true if object lock governance bypass header is set.
|
// IsObjectLockGovernanceBypassSet returns true if object lock governance bypass header is set.
|
||||||
func IsObjectLockGovernanceBypassSet(h http.Header) bool {
|
func IsObjectLockGovernanceBypassSet(h http.Header) bool {
|
||||||
return strings.ToLower(h.Get(xhttp.AmzObjectLockBypassGovernance)) == "true"
|
return strings.ToLower(h.Get(AmzObjectLockBypassRetGovernance)) == "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsObjectLockRequested returns true if legal hold or object lock retention headers are requested.
|
// IsObjectLockRequested returns true if legal hold or object lock retention headers are requested.
|
||||||
@ -393,14 +419,15 @@ func IsObjectLockRequested(h http.Header) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ParseObjectLockRetentionHeaders parses http headers to extract retention mode and retention date
|
// ParseObjectLockRetentionHeaders parses http headers to extract retention mode and retention date
|
||||||
func ParseObjectLockRetentionHeaders(h http.Header) (rmode Mode, r RetentionDate, err error) {
|
func ParseObjectLockRetentionHeaders(h http.Header) (rmode RetMode, r RetentionDate, err error) {
|
||||||
retMode := h.Get(xhttp.AmzObjectLockMode)
|
retMode := h.Get(AmzObjectLockMode)
|
||||||
dateStr := h.Get(xhttp.AmzObjectLockRetainUntilDate)
|
dateStr := h.Get(AmzObjectLockRetainUntilDate)
|
||||||
if len(retMode) == 0 || len(dateStr) == 0 {
|
if len(retMode) == 0 || len(dateStr) == 0 {
|
||||||
return rmode, r, ErrObjectLockInvalidHeaders
|
return rmode, r, ErrObjectLockInvalidHeaders
|
||||||
}
|
}
|
||||||
rmode = parseMode(retMode)
|
|
||||||
if rmode == Invalid {
|
rmode = parseRetMode(retMode)
|
||||||
|
if !rmode.Valid() {
|
||||||
return rmode, r, ErrUnknownWORMModeDirective
|
return rmode, r, ErrUnknownWORMModeDirective
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -429,13 +456,13 @@ func ParseObjectLockRetentionHeaders(h http.Header) (rmode Mode, r RetentionDate
|
|||||||
|
|
||||||
// GetObjectRetentionMeta constructs ObjectRetention from metadata
|
// GetObjectRetentionMeta constructs ObjectRetention from metadata
|
||||||
func GetObjectRetentionMeta(meta map[string]string) ObjectRetention {
|
func GetObjectRetentionMeta(meta map[string]string) ObjectRetention {
|
||||||
var mode Mode
|
var mode RetMode
|
||||||
var retainTill RetentionDate
|
var retainTill RetentionDate
|
||||||
|
|
||||||
if modeStr, ok := meta[strings.ToLower(xhttp.AmzObjectLockMode)]; ok {
|
if modeStr, ok := meta[strings.ToLower(AmzObjectLockMode)]; ok {
|
||||||
mode = parseMode(modeStr)
|
mode = parseRetMode(modeStr)
|
||||||
}
|
}
|
||||||
if tillStr, ok := meta[strings.ToLower(xhttp.AmzObjectLockRetainUntilDate)]; ok {
|
if tillStr, ok := meta[strings.ToLower(AmzObjectLockRetainUntilDate)]; ok {
|
||||||
if t, e := time.Parse(time.RFC3339, tillStr); e == nil {
|
if t, e := time.Parse(time.RFC3339, tillStr); e == nil {
|
||||||
retainTill = RetentionDate{t.UTC()}
|
retainTill = RetentionDate{t.UTC()}
|
||||||
}
|
}
|
||||||
@ -445,8 +472,7 @@ func GetObjectRetentionMeta(meta map[string]string) ObjectRetention {
|
|||||||
|
|
||||||
// GetObjectLegalHoldMeta constructs ObjectLegalHold from metadata
|
// GetObjectLegalHoldMeta constructs ObjectLegalHold from metadata
|
||||||
func GetObjectLegalHoldMeta(meta map[string]string) ObjectLegalHold {
|
func GetObjectLegalHoldMeta(meta map[string]string) ObjectLegalHold {
|
||||||
|
holdStr, ok := meta[strings.ToLower(AmzObjectLockLegalHold)]
|
||||||
holdStr, ok := meta[strings.ToLower(xhttp.AmzObjectLockLegalHold)]
|
|
||||||
if ok {
|
if ok {
|
||||||
return ObjectLegalHold{XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/", Status: parseLegalHoldStatus(holdStr)}
|
return ObjectLegalHold{XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/", Status: parseLegalHoldStatus(holdStr)}
|
||||||
}
|
}
|
||||||
@ -455,13 +481,13 @@ func GetObjectLegalHoldMeta(meta map[string]string) ObjectLegalHold {
|
|||||||
|
|
||||||
// ParseObjectLockLegalHoldHeaders parses request headers to construct ObjectLegalHold
|
// ParseObjectLockLegalHoldHeaders parses request headers to construct ObjectLegalHold
|
||||||
func ParseObjectLockLegalHoldHeaders(h http.Header) (lhold ObjectLegalHold, err error) {
|
func ParseObjectLockLegalHoldHeaders(h http.Header) (lhold ObjectLegalHold, err error) {
|
||||||
holdStatus, ok := h[xhttp.AmzObjectLockLegalHold]
|
holdStatus, ok := h[AmzObjectLockLegalHold]
|
||||||
if ok {
|
if ok {
|
||||||
lh := parseLegalHoldStatus(strings.Join(holdStatus, ""))
|
lh := parseLegalHoldStatus(holdStatus[0])
|
||||||
if lh != ON && lh != OFF {
|
if !lh.Valid() {
|
||||||
return lhold, ErrUnknownWORMModeDirective
|
return lhold, ErrUnknownWORMModeDirective
|
||||||
}
|
}
|
||||||
lhold = ObjectLegalHold{Status: lh}
|
lhold = ObjectLegalHold{XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/", Status: lh}
|
||||||
}
|
}
|
||||||
return lhold, nil
|
return lhold, nil
|
||||||
|
|
||||||
@ -477,16 +503,17 @@ type ObjectLegalHold struct {
|
|||||||
|
|
||||||
// IsEmpty returns true if struct is empty
|
// IsEmpty returns true if struct is empty
|
||||||
func (l *ObjectLegalHold) IsEmpty() bool {
|
func (l *ObjectLegalHold) IsEmpty() bool {
|
||||||
return l.Status != ON && l.Status != OFF
|
return !l.Status.Valid()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseObjectLegalHold decodes the XML into ObjectLegalHold
|
// ParseObjectLegalHold decodes the XML into ObjectLegalHold
|
||||||
func ParseObjectLegalHold(reader io.Reader) (hold *ObjectLegalHold, err error) {
|
func ParseObjectLegalHold(reader io.Reader) (hold *ObjectLegalHold, err error) {
|
||||||
if err = xml.NewDecoder(reader).Decode(&hold); err != nil {
|
hold = &ObjectLegalHold{}
|
||||||
|
if err = xml.NewDecoder(reader).Decode(hold); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if hold.Status != ON && hold.Status != OFF {
|
if !hold.Status.Valid() {
|
||||||
return nil, ErrMalformedXML
|
return nil, ErrMalformedXML
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -511,15 +538,14 @@ func FilterObjectLockMetadata(metadata map[string]string, filterRetention, filte
|
|||||||
delete(dst, key)
|
delete(dst, key)
|
||||||
}
|
}
|
||||||
legalHold := GetObjectLegalHoldMeta(metadata)
|
legalHold := GetObjectLegalHoldMeta(metadata)
|
||||||
if legalHold.Status == "" || filterLegalHold {
|
if !legalHold.Status.Valid() || filterLegalHold {
|
||||||
delKey(xhttp.AmzObjectLockLegalHold)
|
delKey(AmzObjectLockLegalHold)
|
||||||
}
|
}
|
||||||
|
|
||||||
ret := GetObjectRetentionMeta(metadata)
|
ret := GetObjectRetentionMeta(metadata)
|
||||||
|
if !ret.Mode.Valid() || filterRetention {
|
||||||
if ret.Mode == Invalid || filterRetention {
|
delKey(AmzObjectLockMode)
|
||||||
delKey(xhttp.AmzObjectLockMode)
|
delKey(AmzObjectLockRetainUntilDate)
|
||||||
delKey(xhttp.AmzObjectLockRetainUntilDate)
|
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
return dst
|
return dst
|
||||||
|
@ -31,25 +31,25 @@ import (
|
|||||||
func TestParseMode(t *testing.T) {
|
func TestParseMode(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
value string
|
value string
|
||||||
expectedMode Mode
|
expectedMode RetMode
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
value: "governance",
|
value: "governance",
|
||||||
expectedMode: Governance,
|
expectedMode: RetGovernance,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "complIAnce",
|
value: "complIAnce",
|
||||||
expectedMode: Compliance,
|
expectedMode: RetCompliance,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "gce",
|
value: "gce",
|
||||||
expectedMode: Invalid,
|
expectedMode: "",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
if parseMode(tc.value) != tc.expectedMode {
|
if parseRetMode(tc.value) != tc.expectedMode {
|
||||||
t.Errorf("Expected Mode %s, got %s", tc.expectedMode, parseMode(tc.value))
|
t.Errorf("Expected Mode %s, got %s", tc.expectedMode, parseRetMode(tc.value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,11 +60,11 @@ func TestParseLegalHoldStatus(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
value: "ON",
|
value: "ON",
|
||||||
expectedStatus: ON,
|
expectedStatus: LegalHoldOn,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "Off",
|
value: "Off",
|
||||||
expectedStatus: OFF,
|
expectedStatus: LegalHoldOff,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "x",
|
value: "x",
|
||||||
@ -98,32 +98,32 @@ func TestUnmarshalDefaultRetention(t *testing.T) {
|
|||||||
expectErr: true,
|
expectErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: DefaultRetention{Mode: "GOVERNANCE"},
|
value: DefaultRetention{Mode: RetGovernance},
|
||||||
expectedErr: fmt.Errorf("either Days or Years must be specified"),
|
expectedErr: fmt.Errorf("either Days or Years must be specified"),
|
||||||
expectErr: true,
|
expectErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: DefaultRetention{Mode: "GOVERNANCE", Days: &days},
|
value: DefaultRetention{Mode: RetGovernance, Days: &days},
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
expectErr: false,
|
expectErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: DefaultRetention{Mode: "GOVERNANCE", Years: &years},
|
value: DefaultRetention{Mode: RetGovernance, Years: &years},
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
expectErr: false,
|
expectErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: DefaultRetention{Mode: "GOVERNANCE", Days: &days, Years: &years},
|
value: DefaultRetention{Mode: RetGovernance, Days: &days, Years: &years},
|
||||||
expectedErr: fmt.Errorf("either Days or Years must be specified, not both"),
|
expectedErr: fmt.Errorf("either Days or Years must be specified, not both"),
|
||||||
expectErr: true,
|
expectErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: DefaultRetention{Mode: "GOVERNANCE", Days: &zerodays},
|
value: DefaultRetention{Mode: RetGovernance, Days: &zerodays},
|
||||||
expectedErr: fmt.Errorf("Default retention period must be a positive integer value for 'Days'"),
|
expectedErr: fmt.Errorf("Default retention period must be a positive integer value for 'Days'"),
|
||||||
expectErr: true,
|
expectErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: DefaultRetention{Mode: "GOVERNANCE", Days: &invalidDays},
|
value: DefaultRetention{Mode: RetGovernance, Days: &invalidDays},
|
||||||
expectedErr: fmt.Errorf("Default retention period too large for 'Days' %d", invalidDays),
|
expectedErr: fmt.Errorf("Default retention period too large for 'Days' %d", invalidDays),
|
||||||
expectErr: true,
|
expectErr: true,
|
||||||
},
|
},
|
||||||
@ -234,20 +234,20 @@ func TestIsObjectLockRequested(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: http.Header{
|
header: http.Header{
|
||||||
xhttp.AmzObjectLockLegalHold: []string{""},
|
AmzObjectLockLegalHold: []string{""},
|
||||||
},
|
},
|
||||||
expectedVal: true,
|
expectedVal: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: http.Header{
|
header: http.Header{
|
||||||
xhttp.AmzObjectLockRetainUntilDate: []string{""},
|
AmzObjectLockRetainUntilDate: []string{""},
|
||||||
xhttp.AmzObjectLockMode: []string{""},
|
AmzObjectLockMode: []string{""},
|
||||||
},
|
},
|
||||||
expectedVal: true,
|
expectedVal: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: http.Header{
|
header: http.Header{
|
||||||
xhttp.AmzObjectLockBypassGovernance: []string{""},
|
AmzObjectLockBypassRetGovernance: []string{""},
|
||||||
},
|
},
|
||||||
expectedVal: false,
|
expectedVal: false,
|
||||||
},
|
},
|
||||||
@ -275,26 +275,26 @@ func TestIsObjectLockGovernanceBypassSet(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: http.Header{
|
header: http.Header{
|
||||||
xhttp.AmzObjectLockLegalHold: []string{""},
|
AmzObjectLockLegalHold: []string{""},
|
||||||
},
|
},
|
||||||
expectedVal: false,
|
expectedVal: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: http.Header{
|
header: http.Header{
|
||||||
xhttp.AmzObjectLockRetainUntilDate: []string{""},
|
AmzObjectLockRetainUntilDate: []string{""},
|
||||||
xhttp.AmzObjectLockMode: []string{""},
|
AmzObjectLockMode: []string{""},
|
||||||
},
|
},
|
||||||
expectedVal: false,
|
expectedVal: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: http.Header{
|
header: http.Header{
|
||||||
xhttp.AmzObjectLockBypassGovernance: []string{""},
|
AmzObjectLockBypassRetGovernance: []string{""},
|
||||||
},
|
},
|
||||||
expectedVal: false,
|
expectedVal: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: http.Header{
|
header: http.Header{
|
||||||
xhttp.AmzObjectLockBypassGovernance: []string{"true"},
|
AmzObjectLockBypassRetGovernance: []string{"true"},
|
||||||
},
|
},
|
||||||
expectedVal: true,
|
expectedVal: true,
|
||||||
},
|
},
|
||||||
@ -394,7 +394,7 @@ func TestGetObjectRetentionMeta(t *testing.T) {
|
|||||||
metadata: map[string]string{
|
metadata: map[string]string{
|
||||||
"x-amz-object-lock-mode": "governance",
|
"x-amz-object-lock-mode": "governance",
|
||||||
},
|
},
|
||||||
expected: ObjectRetention{Mode: Governance},
|
expected: ObjectRetention{Mode: RetGovernance},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
metadata: map[string]string{
|
metadata: map[string]string{
|
||||||
@ -427,13 +427,13 @@ func TestGetObjectLegalHoldMeta(t *testing.T) {
|
|||||||
metadata: map[string]string{
|
metadata: map[string]string{
|
||||||
"x-amz-object-lock-legal-hold": "on",
|
"x-amz-object-lock-legal-hold": "on",
|
||||||
},
|
},
|
||||||
expected: ObjectLegalHold{Status: ON},
|
expected: ObjectLegalHold{Status: LegalHoldOn},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
metadata: map[string]string{
|
metadata: map[string]string{
|
||||||
"x-amz-object-lock-legal-hold": "off",
|
"x-amz-object-lock-legal-hold": "off",
|
||||||
},
|
},
|
||||||
expected: ObjectLegalHold{Status: OFF},
|
expected: ObjectLegalHold{Status: LegalHoldOff},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
metadata: map[string]string{
|
metadata: map[string]string{
|
||||||
|
@ -263,12 +263,14 @@ var actionConditionKeyMap = map[Action]condition.KeySet{
|
|||||||
condition.S3ObjectLockMode,
|
condition.S3ObjectLockMode,
|
||||||
condition.S3ObjectLockLegalHold,
|
condition.S3ObjectLockLegalHold,
|
||||||
}, condition.CommonKeys...)...),
|
}, condition.CommonKeys...)...),
|
||||||
|
|
||||||
|
// https://docs.aws.amazon.com/AmazonS3/latest/dev/list_amazons3.html
|
||||||
|
// LockLegalHold is not supported with PutObjectRetentionAction
|
||||||
PutObjectRetentionAction: condition.NewKeySet(
|
PutObjectRetentionAction: condition.NewKeySet(
|
||||||
append([]condition.Key{
|
append([]condition.Key{
|
||||||
condition.S3ObjectLockRemainingRetentionDays,
|
condition.S3ObjectLockRemainingRetentionDays,
|
||||||
condition.S3ObjectLockRetainUntilDate,
|
condition.S3ObjectLockRetainUntilDate,
|
||||||
condition.S3ObjectLockMode,
|
condition.S3ObjectLockMode,
|
||||||
condition.S3ObjectLockLegalHold,
|
|
||||||
}, condition.CommonKeys...)...),
|
}, condition.CommonKeys...)...),
|
||||||
|
|
||||||
GetObjectRetentionAction: condition.NewKeySet(condition.CommonKeys...),
|
GetObjectRetentionAction: condition.NewKeySet(condition.CommonKeys...),
|
||||||
@ -277,6 +279,8 @@ var actionConditionKeyMap = map[Action]condition.KeySet{
|
|||||||
condition.S3ObjectLockLegalHold,
|
condition.S3ObjectLockLegalHold,
|
||||||
}, condition.CommonKeys...)...),
|
}, condition.CommonKeys...)...),
|
||||||
GetObjectLegalHoldAction: condition.NewKeySet(condition.CommonKeys...),
|
GetObjectLegalHoldAction: condition.NewKeySet(condition.CommonKeys...),
|
||||||
|
|
||||||
|
// https://docs.aws.amazon.com/AmazonS3/latest/dev/list_amazons3.html
|
||||||
BypassGovernanceRetentionAction: condition.NewKeySet(
|
BypassGovernanceRetentionAction: condition.NewKeySet(
|
||||||
append([]condition.Key{
|
append([]condition.Key{
|
||||||
condition.S3ObjectLockRemainingRetentionDays,
|
condition.S3ObjectLockRemainingRetentionDays,
|
||||||
@ -284,6 +288,7 @@ var actionConditionKeyMap = map[Action]condition.KeySet{
|
|||||||
condition.S3ObjectLockMode,
|
condition.S3ObjectLockMode,
|
||||||
condition.S3ObjectLockLegalHold,
|
condition.S3ObjectLockLegalHold,
|
||||||
}, condition.CommonKeys...)...),
|
}, condition.CommonKeys...)...),
|
||||||
|
|
||||||
GetBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...),
|
GetBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...),
|
||||||
PutBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...),
|
PutBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...),
|
||||||
PutObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),
|
PutObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),
|
||||||
|
@ -112,7 +112,6 @@ func valuesToStringSlice(n name, values ValueSet) ([]string, error) {
|
|||||||
valueStrings := []string{}
|
valueStrings := []string{}
|
||||||
|
|
||||||
for value := range values {
|
for value := range values {
|
||||||
// FIXME: if AWS supports non-string values, we would need to support it.
|
|
||||||
s, err := value.GetString()
|
s, err := value.GetString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("value must be a string for %v condition", n)
|
return nil, fmt.Errorf("value must be a string for %v condition", n)
|
||||||
|
@ -301,12 +301,13 @@ var actionConditionKeyMap = map[Action]condition.KeySet{
|
|||||||
condition.S3ObjectLockLegalHold,
|
condition.S3ObjectLockLegalHold,
|
||||||
}, condition.CommonKeys...)...),
|
}, condition.CommonKeys...)...),
|
||||||
|
|
||||||
|
// https://docs.aws.amazon.com/AmazonS3/latest/dev/list_amazons3.html
|
||||||
|
// LockLegalHold is not supported with PutObjectRetentionAction
|
||||||
PutObjectRetentionAction: condition.NewKeySet(
|
PutObjectRetentionAction: condition.NewKeySet(
|
||||||
append([]condition.Key{
|
append([]condition.Key{
|
||||||
condition.S3ObjectLockRemainingRetentionDays,
|
condition.S3ObjectLockRemainingRetentionDays,
|
||||||
condition.S3ObjectLockRetainUntilDate,
|
condition.S3ObjectLockRetainUntilDate,
|
||||||
condition.S3ObjectLockMode,
|
condition.S3ObjectLockMode,
|
||||||
condition.S3ObjectLockLegalHold,
|
|
||||||
}, condition.CommonKeys...)...),
|
}, condition.CommonKeys...)...),
|
||||||
|
|
||||||
GetObjectRetentionAction: condition.NewKeySet(condition.CommonKeys...),
|
GetObjectRetentionAction: condition.NewKeySet(condition.CommonKeys...),
|
||||||
@ -315,6 +316,8 @@ var actionConditionKeyMap = map[Action]condition.KeySet{
|
|||||||
condition.S3ObjectLockLegalHold,
|
condition.S3ObjectLockLegalHold,
|
||||||
}, condition.CommonKeys...)...),
|
}, condition.CommonKeys...)...),
|
||||||
GetObjectLegalHoldAction: condition.NewKeySet(condition.CommonKeys...),
|
GetObjectLegalHoldAction: condition.NewKeySet(condition.CommonKeys...),
|
||||||
|
|
||||||
|
// https://docs.aws.amazon.com/AmazonS3/latest/dev/list_amazons3.html
|
||||||
BypassGovernanceRetentionAction: condition.NewKeySet(
|
BypassGovernanceRetentionAction: condition.NewKeySet(
|
||||||
append([]condition.Key{
|
append([]condition.Key{
|
||||||
condition.S3ObjectLockRemainingRetentionDays,
|
condition.S3ObjectLockRemainingRetentionDays,
|
||||||
@ -322,6 +325,7 @@ var actionConditionKeyMap = map[Action]condition.KeySet{
|
|||||||
condition.S3ObjectLockMode,
|
condition.S3ObjectLockMode,
|
||||||
condition.S3ObjectLockLegalHold,
|
condition.S3ObjectLockLegalHold,
|
||||||
}, condition.CommonKeys...)...),
|
}, condition.CommonKeys...)...),
|
||||||
|
|
||||||
GetBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...),
|
GetBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...),
|
||||||
PutBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...),
|
PutBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...),
|
||||||
PutObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),
|
PutObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user