mirror of
https://github.com/versity/versitygw.git
synced 2026-08-27 19:36:57 +00:00
Merge pull request #545 from versity/aws-error-ref
AWS error refactoring
This commit is contained in:
+5
-4
@@ -17,6 +17,7 @@ package auth
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -292,13 +293,13 @@ func VerifyAccess(ctx context.Context, be backend.Backend, opts AccessOptions) e
|
||||
return nil
|
||||
}
|
||||
|
||||
policy, err := be.GetBucketPolicy(ctx, opts.Bucket)
|
||||
if err != nil {
|
||||
return err
|
||||
policy, policyErr := be.GetBucketPolicy(ctx, opts.Bucket)
|
||||
if policyErr != nil && !errors.Is(policyErr, s3err.GetAPIError(s3err.ErrNoSuchBucketPolicy)) {
|
||||
return policyErr
|
||||
}
|
||||
|
||||
// If bucket policy is not set and the ACL is default, only the owner has access
|
||||
if len(policy) == 0 && opts.Acl.ACL == "" && len(opts.Acl.Grantees) == 0 {
|
||||
if errors.Is(policyErr, s3err.GetAPIError(s3err.ErrNoSuchBucketPolicy)) && opts.Acl.ACL == "" && len(opts.Acl.Grantees) == 0 {
|
||||
return s3err.GetAPIError(s3err.ErrAccessDenied)
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ func ValidatePolicyDocument(policyBin []byte, bucket string, iam IAMService) err
|
||||
|
||||
func verifyBucketPolicy(policy []byte, access, bucket, object string, action Action) error {
|
||||
// If bucket policy is not set
|
||||
if len(policy) == 0 {
|
||||
if policy == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+3
-8
@@ -33,11 +33,6 @@ type BucketLockConfig struct {
|
||||
CreatedAt *time.Time
|
||||
}
|
||||
|
||||
type ObjectLockConfig struct {
|
||||
LegalHoldEnabled bool
|
||||
Retention *types.ObjectLockRetention
|
||||
}
|
||||
|
||||
func ParseBucketLockConfigurationInput(input []byte) ([]byte, error) {
|
||||
var lockConfig types.ObjectLockConfiguration
|
||||
if err := xml.Unmarshal(input, &lockConfig); err != nil {
|
||||
@@ -172,12 +167,12 @@ func CheckObjectAccess(ctx context.Context, bucket, userAccess string, objects [
|
||||
case types.ObjectLockRetentionModeGovernance:
|
||||
if !isAdminOrRoot {
|
||||
policy, err := be.GetBucketPolicy(ctx, bucket)
|
||||
if errors.Is(err, s3err.GetAPIError(s3err.ErrNoSuchBucketPolicy)) {
|
||||
return s3err.GetAPIError(s3err.ErrObjectLocked)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(policy) == 0 {
|
||||
return s3err.GetAPIError(s3err.ErrObjectLocked)
|
||||
}
|
||||
err = verifyBucketPolicy(policy, userAccess, bucket, obj, BypassGovernanceRetentionAction)
|
||||
if err != nil {
|
||||
return s3err.GetAPIError(s3err.ErrObjectLocked)
|
||||
|
||||
+39
-2
@@ -126,6 +126,21 @@ func (az *Azure) CreateBucket(ctx context.Context, input *s3.CreateBucketInput,
|
||||
meta := map[string]*string{
|
||||
string(keyAclCapital): backend.GetStringPtr(string(acl)),
|
||||
}
|
||||
|
||||
if input.ObjectLockEnabledForBucket != nil && *input.ObjectLockEnabledForBucket {
|
||||
now := time.Now()
|
||||
defaultLock := auth.BucketLockConfig{
|
||||
Enabled: true,
|
||||
CreatedAt: &now,
|
||||
}
|
||||
|
||||
defaultLockParsed, err := json.Marshal(defaultLock)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse default bucket lock state: %w", err)
|
||||
}
|
||||
|
||||
meta[string(keyBucketLock)] = backend.GetStringPtr(string(defaultLockParsed))
|
||||
}
|
||||
_, err := az.client.CreateContainer(ctx, *input.Bucket, &container.CreateOptions{Metadata: meta})
|
||||
return azureErrToS3Err(err)
|
||||
}
|
||||
@@ -189,6 +204,28 @@ func (az *Azure) PutObject(ctx context.Context, po *s3.PutObjectInput) (string,
|
||||
return "", azureErrToS3Err(err)
|
||||
}
|
||||
|
||||
// Set object legal hold
|
||||
if po.ObjectLockLegalHoldStatus == types.ObjectLockLegalHoldStatusOn {
|
||||
if err := az.PutObjectLegalHold(ctx, *po.Bucket, *po.Key, "", true); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Set object retention
|
||||
if po.ObjectLockMode != "" {
|
||||
retention := types.ObjectLockRetention{
|
||||
Mode: types.ObjectLockRetentionMode(po.ObjectLockMode),
|
||||
RetainUntilDate: po.ObjectLockRetainUntilDate,
|
||||
}
|
||||
retParsed, err := json.Marshal(retention)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parse object lock retention: %w", err)
|
||||
}
|
||||
if err := az.PutObjectRetention(ctx, *po.Bucket, *po.Key, "", retParsed); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return string(*uploadResp.ETag), nil
|
||||
}
|
||||
|
||||
@@ -235,7 +272,7 @@ func (az *Azure) GetBucketTagging(ctx context.Context, bucket string) (map[strin
|
||||
|
||||
tagsJson, ok := resp.Metadata[string(keyTags)]
|
||||
if !ok {
|
||||
return map[string]string{}, nil
|
||||
return nil, s3err.GetAPIError(s3err.ErrBucketTaggingNotFound)
|
||||
}
|
||||
|
||||
var tags map[string]string
|
||||
@@ -888,7 +925,7 @@ func (az *Azure) GetBucketPolicy(ctx context.Context, bucket string) ([]byte, er
|
||||
|
||||
policyPtr, ok := props.Metadata[string(keyPolicy)]
|
||||
if !ok {
|
||||
return []byte{}, nil
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchBucket)
|
||||
}
|
||||
|
||||
policy, err := base64.StdEncoding.DecodeString(*policyPtr)
|
||||
|
||||
+90
-20
@@ -30,6 +30,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||
@@ -229,6 +230,23 @@ func (p *Posix) CreateBucket(ctx context.Context, input *s3.CreateBucketInput, a
|
||||
return fmt.Errorf("set acl: %w", err)
|
||||
}
|
||||
|
||||
if input.ObjectLockEnabledForBucket != nil && *input.ObjectLockEnabledForBucket {
|
||||
now := time.Now()
|
||||
defaultLock := auth.BucketLockConfig{
|
||||
Enabled: true,
|
||||
CreatedAt: &now,
|
||||
}
|
||||
|
||||
defaultLockParsed, err := json.Marshal(defaultLock)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse default bucket lock state: %w", err)
|
||||
}
|
||||
|
||||
if err := p.meta.StoreAttribute(bucket, "", bucketLockKey, defaultLockParsed); err != nil {
|
||||
return fmt.Errorf("set default bucket lock: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1226,6 +1244,7 @@ func (p *Posix) PutObject(ctx context.Context, po *s3.PutObjectInput) (string, e
|
||||
}
|
||||
}
|
||||
|
||||
// Set object tagging
|
||||
if tagsStr != "" {
|
||||
err := p.PutObjectTagging(ctx, *po.Bucket, *po.Key, tags)
|
||||
if err != nil {
|
||||
@@ -1233,6 +1252,28 @@ func (p *Posix) PutObject(ctx context.Context, po *s3.PutObjectInput) (string, e
|
||||
}
|
||||
}
|
||||
|
||||
// Set object legal hold
|
||||
if po.ObjectLockLegalHoldStatus == types.ObjectLockLegalHoldStatusOn {
|
||||
if err := p.PutObjectLegalHold(ctx, *po.Bucket, *po.Key, "", true); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Set object retention
|
||||
if po.ObjectLockMode != "" {
|
||||
retention := types.ObjectLockRetention{
|
||||
Mode: types.ObjectLockRetentionMode(po.ObjectLockMode),
|
||||
RetainUntilDate: po.ObjectLockRetainUntilDate,
|
||||
}
|
||||
retParsed, err := json.Marshal(retention)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parse object lock retention: %w", err)
|
||||
}
|
||||
if err := p.PutObjectRetention(ctx, *po.Bucket, *po.Key, "", retParsed); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
dataSum := hash.Sum(nil)
|
||||
etag := hex.EncodeToString(dataSum[:])
|
||||
err = p.meta.StoreAttribute(*po.Bucket, *po.Key, etagkey, []byte(etag))
|
||||
@@ -1414,12 +1455,15 @@ func (p *Posix) GetObject(_ context.Context, input *s3.GetObjectInput, writer io
|
||||
etag = ""
|
||||
}
|
||||
|
||||
var tagCount *int32
|
||||
tags, err := p.getAttrTags(bucket, object)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get object tags: %w", err)
|
||||
if err != nil && !errors.Is(err, s3err.GetAPIError(s3err.ErrBucketTaggingNotFound)) {
|
||||
return nil, err
|
||||
}
|
||||
if tags != nil {
|
||||
tgCount := int32(len(tags))
|
||||
tagCount = &tgCount
|
||||
}
|
||||
|
||||
tagCount := int32(len(tags))
|
||||
|
||||
return &s3.GetObjectOutput{
|
||||
AcceptRanges: &acceptRange,
|
||||
@@ -1429,7 +1473,7 @@ func (p *Posix) GetObject(_ context.Context, input *s3.GetObjectInput, writer io
|
||||
ETag: &etag,
|
||||
LastModified: backend.GetTimePtr(fi.ModTime()),
|
||||
Metadata: userMetaData,
|
||||
TagCount: &tagCount,
|
||||
TagCount: tagCount,
|
||||
ContentRange: &contentRange,
|
||||
}, nil
|
||||
}
|
||||
@@ -1459,12 +1503,15 @@ func (p *Posix) GetObject(_ context.Context, input *s3.GetObjectInput, writer io
|
||||
etag = ""
|
||||
}
|
||||
|
||||
var tagCount *int32
|
||||
tags, err := p.getAttrTags(bucket, object)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get object tags: %w", err)
|
||||
if err != nil && !errors.Is(err, s3err.GetAPIError(s3err.ErrBucketTaggingNotFound)) {
|
||||
return nil, err
|
||||
}
|
||||
if tags != nil {
|
||||
tgCount := int32(len(tags))
|
||||
tagCount = &tgCount
|
||||
}
|
||||
|
||||
tagCount := int32(len(tags))
|
||||
|
||||
return &s3.GetObjectOutput{
|
||||
AcceptRanges: &acceptRange,
|
||||
@@ -1474,12 +1521,12 @@ func (p *Posix) GetObject(_ context.Context, input *s3.GetObjectInput, writer io
|
||||
ETag: &etag,
|
||||
LastModified: backend.GetTimePtr(fi.ModTime()),
|
||||
Metadata: userMetaData,
|
||||
TagCount: &tagCount,
|
||||
TagCount: tagCount,
|
||||
ContentRange: &contentRange,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Posix) HeadObject(_ context.Context, input *s3.HeadObjectInput) (*s3.HeadObjectOutput, error) {
|
||||
func (p *Posix) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3.HeadObjectOutput, error) {
|
||||
if input.Bucket == nil {
|
||||
return nil, s3err.GetAPIError(s3err.ErrInvalidBucketName)
|
||||
}
|
||||
@@ -1517,16 +1564,39 @@ func (p *Posix) HeadObject(_ context.Context, input *s3.HeadObjectInput) (*s3.He
|
||||
|
||||
size := fi.Size()
|
||||
|
||||
//TODO: Add object lock status properties
|
||||
var objectLockLegalHoldStatus types.ObjectLockLegalHoldStatus
|
||||
status, err := p.GetObjectLegalHold(ctx, bucket, object, "")
|
||||
if err == nil {
|
||||
if *status {
|
||||
objectLockLegalHoldStatus = types.ObjectLockLegalHoldStatusOn
|
||||
} else {
|
||||
objectLockLegalHoldStatus = types.ObjectLockLegalHoldStatusOff
|
||||
}
|
||||
}
|
||||
|
||||
var objectLockMode types.ObjectLockMode
|
||||
var objectLockRetainUntilDate *time.Time
|
||||
retention, err := p.GetObjectRetention(ctx, bucket, object, "")
|
||||
if err == nil {
|
||||
var config types.ObjectLockRetention
|
||||
if err := json.Unmarshal(retention, &config); err == nil {
|
||||
objectLockMode = types.ObjectLockMode(config.Mode)
|
||||
objectLockRetainUntilDate = config.RetainUntilDate
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: the method must handle multipart upload case
|
||||
|
||||
return &s3.HeadObjectOutput{
|
||||
ContentLength: &size,
|
||||
ContentType: &contentType,
|
||||
ContentEncoding: &contentEncoding,
|
||||
ETag: &etag,
|
||||
LastModified: backend.GetTimePtr(fi.ModTime()),
|
||||
Metadata: userMetaData,
|
||||
ContentLength: &size,
|
||||
ContentType: &contentType,
|
||||
ContentEncoding: &contentEncoding,
|
||||
ETag: &etag,
|
||||
LastModified: backend.GetTimePtr(fi.ModTime()),
|
||||
Metadata: userMetaData,
|
||||
ObjectLockLegalHoldStatus: objectLockLegalHoldStatus,
|
||||
ObjectLockMode: objectLockMode,
|
||||
ObjectLockRetainUntilDate: objectLockRetainUntilDate,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1974,7 +2044,7 @@ func (p *Posix) getAttrTags(bucket, object string) (map[string]string, error) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||
}
|
||||
if errors.Is(err, meta.ErrNoSuchKey) {
|
||||
return tags, nil
|
||||
return nil, s3err.GetAPIError(s3err.ErrBucketTaggingNotFound)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get tags: %w", err)
|
||||
@@ -2072,7 +2142,7 @@ func (p *Posix) GetBucketPolicy(ctx context.Context, bucket string) ([]byte, err
|
||||
|
||||
policy, err := p.meta.RetrieveAttribute(bucket, "", policykey)
|
||||
if errors.Is(err, meta.ErrNoSuchKey) {
|
||||
return []byte{}, nil
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchBucketPolicy)
|
||||
}
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchBucket)
|
||||
|
||||
+84
-10
@@ -1219,9 +1219,14 @@ func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
lockHeader := ctx.Get("X-Amz-Bucket-Object-Lock-Enabled")
|
||||
// CLI provides "True", SDK - "true"
|
||||
lockEnabled := lockHeader == "True" || lockHeader == "true"
|
||||
|
||||
err = c.be.CreateBucket(ctx.Context(), &s3.CreateBucketInput{
|
||||
Bucket: &bucket,
|
||||
ObjectOwnership: types.ObjectOwnership(acct.Access),
|
||||
Bucket: &bucket,
|
||||
ObjectOwnership: types.ObjectOwnership(acct.Access),
|
||||
ObjectLockEnabledForBucket: &lockEnabled,
|
||||
}, updAcl)
|
||||
return SendResponse(ctx, err,
|
||||
&MetaOpts{
|
||||
@@ -1780,6 +1785,52 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
legalHoldHdr := ctx.Get("X-Amz-Object-Lock-Legal-Hold")
|
||||
objLockModeHdr := ctx.Get("X-Amz-Object-Lock-Mode")
|
||||
objLockDate := ctx.Get("X-Amz-Object-Lock-Retain-Until-Date")
|
||||
|
||||
if (objLockDate != "" && objLockModeHdr == "") || (objLockDate == "" && objLockModeHdr != "") {
|
||||
return SendResponse(ctx, s3err.GetAPIError(s3err.ErrObjectLockInvalidHeaders),
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "PutObject",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
|
||||
var retainUntilDate *time.Time
|
||||
if objLockDate != "" {
|
||||
rDate, err := time.Parse(time.RFC3339, objLockDate)
|
||||
if err != nil {
|
||||
return SendResponse(ctx, s3err.GetAPIError(s3err.ErrInvalidRequest),
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "PutObject",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
if rDate.Before(time.Now()) {
|
||||
return SendResponse(ctx, s3err.GetAPIError(s3err.ErrPastObjectLockRetainDate),
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "PutObject",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
retainUntilDate = &rDate
|
||||
}
|
||||
|
||||
if objLockModeHdr != "" &&
|
||||
objLockModeHdr != string(types.ObjectLockModeCompliance) &&
|
||||
objLockModeHdr != string(types.ObjectLockModeGovernance) {
|
||||
return SendResponse(ctx, s3err.GetAPIError(s3err.ErrInvalidRequest),
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "PutObject",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
|
||||
var body io.Reader
|
||||
bodyi := ctx.Locals("body-reader")
|
||||
if bodyi != nil {
|
||||
@@ -1791,12 +1842,15 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
|
||||
ctx.Locals("logReqBody", false)
|
||||
etag, err := c.be.PutObject(ctx.Context(),
|
||||
&s3.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &keyStart,
|
||||
ContentLength: &contentLength,
|
||||
Metadata: metadata,
|
||||
Body: body,
|
||||
Tagging: &tagging,
|
||||
Bucket: &bucket,
|
||||
Key: &keyStart,
|
||||
ContentLength: &contentLength,
|
||||
Metadata: metadata,
|
||||
Body: body,
|
||||
Tagging: &tagging,
|
||||
ObjectLockRetainUntilDate: retainUntilDate,
|
||||
ObjectLockMode: types.ObjectLockMode(objLockModeHdr),
|
||||
ObjectLockLegalHoldStatus: types.ObjectLockLegalHoldStatus(legalHoldHdr),
|
||||
})
|
||||
ctx.Response().Header.Set("ETag", etag)
|
||||
return SendResponse(ctx, err,
|
||||
@@ -2212,7 +2266,7 @@ func (c S3ApiController) HeadObject(ctx *fiber.Ctx) error {
|
||||
if res.LastModified != nil {
|
||||
lastmod = res.LastModified.Format(timefmt)
|
||||
}
|
||||
utils.SetResponseHeaders(ctx, []utils.CustomHeader{
|
||||
headers := []utils.CustomHeader{
|
||||
{
|
||||
Key: "Content-Length",
|
||||
Value: fmt.Sprint(getint64(res.ContentLength)),
|
||||
@@ -2241,7 +2295,27 @@ func (c S3ApiController) HeadObject(ctx *fiber.Ctx) error {
|
||||
Key: "x-amz-restore",
|
||||
Value: getstring(res.Restore),
|
||||
},
|
||||
})
|
||||
}
|
||||
if res.ObjectLockMode != "" {
|
||||
headers = append(headers, utils.CustomHeader{
|
||||
Key: "x-amz-object-lock-mode",
|
||||
Value: string(res.ObjectLockMode),
|
||||
})
|
||||
}
|
||||
if res.ObjectLockLegalHoldStatus != "" {
|
||||
headers = append(headers, utils.CustomHeader{
|
||||
Key: "x-amz-object-lock-legal-hold",
|
||||
Value: string(res.ObjectLockLegalHoldStatus),
|
||||
})
|
||||
}
|
||||
if res.ObjectLockRetainUntilDate != nil {
|
||||
retainUntilDate := res.ObjectLockRetainUntilDate.Format(time.RFC3339)
|
||||
headers = append(headers, utils.CustomHeader{
|
||||
Key: "x-amz-object-lock-retain-until-date",
|
||||
Value: retainUntilDate,
|
||||
})
|
||||
}
|
||||
utils.SetResponseHeaders(ctx, headers)
|
||||
|
||||
return SendResponse(ctx, nil,
|
||||
&MetaOpts{
|
||||
|
||||
@@ -116,6 +116,9 @@ const (
|
||||
ErrInvalidBucketObjectLockConfiguration
|
||||
ErrObjectLocked
|
||||
ErrPastObjectLockRetainDate
|
||||
ErrNoSuchBucketPolicy
|
||||
ErrBucketTaggingNotFound
|
||||
ErrObjectLockInvalidHeaders
|
||||
ErrRequestTimeTooSkewed
|
||||
|
||||
// Non-AWS errors
|
||||
@@ -431,6 +434,21 @@ var errorCodeResponse = map[ErrorCode]APIError{
|
||||
Description: "the retain until date must be in the future",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrNoSuchBucketPolicy: {
|
||||
Code: "NoSuchBucketPolicy",
|
||||
Description: "The bucket policy does not exist",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrBucketTaggingNotFound: {
|
||||
Code: "NoSuchTagSet",
|
||||
Description: "The TagSet does not exist",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrObjectLockInvalidHeaders: {
|
||||
Code: "InvalidRequest",
|
||||
Description: "x-amz-object-lock-retain-until-date and x-amz-object-lock-mode must both be supplied",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrRequestTimeTooSkewed: {
|
||||
Code: "RequestTimeTooSkewed",
|
||||
Description: "The difference between the request time and the server's time is too large.",
|
||||
|
||||
@@ -5,17 +5,18 @@ get_bucket_policy() {
|
||||
echo "get bucket policy command requires command type, bucket"
|
||||
return 1
|
||||
fi
|
||||
local get_bucket_policy_result=0
|
||||
if [[ $1 == 'aws' ]]; then
|
||||
get_bucket_policy_aws "$2" || get_result=$?
|
||||
get_bucket_policy_aws "$2" || get_bucket_policy_result=$?
|
||||
elif [[ $1 == 's3cmd' ]]; then
|
||||
get_bucket_policy_s3cmd "$2" || get_result=$?
|
||||
get_bucket_policy_s3cmd "$2" || get_bucket_policy_result=$?
|
||||
elif [[ $1 == 'mc' ]]; then
|
||||
get_bucket_policy_mc "$2" || get_result=$?
|
||||
get_bucket_policy_mc "$2" || get_bucket_policy_result=$?
|
||||
else
|
||||
echo "command 'get bucket policy' not implemented for '$1'"
|
||||
return 1
|
||||
fi
|
||||
if [[ $get_result -ne 0 ]]; then
|
||||
if [[ $get_bucket_policy_result -ne 0 ]]; then
|
||||
echo "error getting policy: $bucket_policy"
|
||||
return 1
|
||||
fi
|
||||
@@ -28,12 +29,20 @@ get_bucket_policy_aws() {
|
||||
echo "aws 'get bucket policy' command requires bucket"
|
||||
return 1
|
||||
fi
|
||||
policy_json=$(aws --no-verify-ssl s3api get-bucket-policy --bucket "$1") || get_result=$?
|
||||
policy_json=$(aws --no-verify-ssl s3api get-bucket-policy --bucket "$1" 2>&1) || get_result=$?
|
||||
if [[ $policy_json == *"InsecureRequestWarning"* ]]; then
|
||||
policy_json=$(awk 'NR>2' <<< "$policy_json")
|
||||
fi
|
||||
if [[ $get_result -ne 0 ]]; then
|
||||
echo "error getting policy: $policy_json"
|
||||
return 1
|
||||
if [[ "$policy_json" == *"(NoSuchBucketPolicy)"* ]]; then
|
||||
bucket_policy=
|
||||
else
|
||||
echo "error getting policy: $policy_json"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
bucket_policy=$(echo "{$policy_json}" | jq -r '.Policy')
|
||||
fi
|
||||
bucket_policy=$(echo "$policy_json" | jq -r '.Policy')
|
||||
export bucket_policy
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ func TestCreateBucket(s *S3Conf) {
|
||||
CreateBucket_default_acl(s)
|
||||
CreateBucket_non_default_acl(s)
|
||||
CreateDeleteBucket_success(s)
|
||||
CreateBucket_default_object_lock(s)
|
||||
}
|
||||
|
||||
func TestHeadBucket(s *S3Conf) {
|
||||
@@ -81,6 +82,7 @@ func TestPutBucketTagging(s *S3Conf) {
|
||||
|
||||
func TestGetBucketTagging(s *S3Conf) {
|
||||
GetBucketTagging_non_existing_bucket(s)
|
||||
GetBucketTagging_unset_tags(s)
|
||||
GetBucketTagging_success(s)
|
||||
}
|
||||
|
||||
@@ -94,6 +96,8 @@ func TestPutObject(s *S3Conf) {
|
||||
PutObject_non_existing_bucket(s)
|
||||
PutObject_special_chars(s)
|
||||
PutObject_invalid_long_tags(s)
|
||||
PutObject_missing_object_lock_retention_config(s)
|
||||
PutObject_with_object_lock(s)
|
||||
PutObject_success(s)
|
||||
PutObject_invalid_credentials(s)
|
||||
}
|
||||
@@ -165,6 +169,7 @@ func TestPutObjectTagging(s *S3Conf) {
|
||||
|
||||
func TestGetObjectTagging(s *S3Conf) {
|
||||
GetObjectTagging_non_existing_object(s)
|
||||
GetObjectTagging_unset_tags(s)
|
||||
GetObjectTagging_success(s)
|
||||
}
|
||||
|
||||
@@ -275,7 +280,7 @@ func TestPutBucketPolicy(s *S3Conf) {
|
||||
|
||||
func TestGetBucketPolicy(s *S3Conf) {
|
||||
GetBucketPolicy_non_existing_bucket(s)
|
||||
GetBucketPolicy_default_empty_policy(s)
|
||||
GetBucketPolicy_not_set(s)
|
||||
GetBucketPolicy_success(s)
|
||||
}
|
||||
|
||||
@@ -449,6 +454,8 @@ func GetIntTests() IntTests {
|
||||
"PresignedAuth_expired_request": PresignedAuth_expired_request,
|
||||
"PresignedAuth_incorrect_secret_key": PresignedAuth_incorrect_secret_key,
|
||||
"PresignedAuth_PutObject_success": PresignedAuth_PutObject_success,
|
||||
"PutObject_missing_object_lock_retention_config": PutObject_missing_object_lock_retention_config,
|
||||
"PutObject_with_object_lock": PutObject_with_object_lock,
|
||||
"PresignedAuth_Put_GetObject_with_data": PresignedAuth_Put_GetObject_with_data,
|
||||
"PresignedAuth_Put_GetObject_with_UTF8_chars": PresignedAuth_Put_GetObject_with_UTF8_chars,
|
||||
"PresignedAuth_UploadPart": PresignedAuth_UploadPart,
|
||||
@@ -458,6 +465,7 @@ func GetIntTests() IntTests {
|
||||
"CreateDeleteBucket_success": CreateDeleteBucket_success,
|
||||
"CreateBucket_default_acl": CreateBucket_default_acl,
|
||||
"CreateBucket_non_default_acl": CreateBucket_non_default_acl,
|
||||
"CreateBucket_default_object_lock": CreateBucket_default_object_lock,
|
||||
"HeadBucket_non_existing_bucket": HeadBucket_non_existing_bucket,
|
||||
"HeadBucket_success": HeadBucket_success,
|
||||
"ListBuckets_as_user": ListBuckets_as_user,
|
||||
@@ -470,6 +478,7 @@ func GetIntTests() IntTests {
|
||||
"PutBucketTagging_long_tags": PutBucketTagging_long_tags,
|
||||
"PutBucketTagging_success": PutBucketTagging_success,
|
||||
"GetBucketTagging_non_existing_bucket": GetBucketTagging_non_existing_bucket,
|
||||
"GetBucketTagging_unset_tags": GetBucketTagging_unset_tags,
|
||||
"GetBucketTagging_success": GetBucketTagging_success,
|
||||
"DeleteBucketTagging_non_existing_object": DeleteBucketTagging_non_existing_object,
|
||||
"DeleteBucketTagging_success_status": DeleteBucketTagging_success_status,
|
||||
@@ -517,6 +526,7 @@ func GetIntTests() IntTests {
|
||||
"PutObjectTagging_long_tags": PutObjectTagging_long_tags,
|
||||
"PutObjectTagging_success": PutObjectTagging_success,
|
||||
"GetObjectTagging_non_existing_object": GetObjectTagging_non_existing_object,
|
||||
"GetObjectTagging_unset_tags": GetObjectTagging_unset_tags,
|
||||
"GetObjectTagging_success": GetObjectTagging_success,
|
||||
"DeleteObjectTagging_non_existing_object": DeleteObjectTagging_non_existing_object,
|
||||
"DeleteObjectTagging_success_status": DeleteObjectTagging_success_status,
|
||||
@@ -591,7 +601,7 @@ func GetIntTests() IntTests {
|
||||
"PutBucketPolicy_bucket_action_on_object_resource": PutBucketPolicy_bucket_action_on_object_resource,
|
||||
"PutBucketPolicy_success": PutBucketPolicy_success,
|
||||
"GetBucketPolicy_non_existing_bucket": GetBucketPolicy_non_existing_bucket,
|
||||
"GetBucketPolicy_default_empty_policy": GetBucketPolicy_default_empty_policy,
|
||||
"GetBucketPolicy_not_set": GetBucketPolicy_not_set,
|
||||
"GetBucketPolicy_success": GetBucketPolicy_success,
|
||||
"DeleteBucketPolicy_non_existing_bucket": DeleteBucketPolicy_non_existing_bucket,
|
||||
"DeleteBucketPolicy_remove_before_setting": DeleteBucketPolicy_remove_before_setting,
|
||||
|
||||
+193
-16
@@ -1784,6 +1784,51 @@ func CreateBucket_non_default_acl(s *S3Conf) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateBucket_default_object_lock(s *S3Conf) error {
|
||||
testName := "CreateBucket_default_object_lock"
|
||||
runF(testName)
|
||||
|
||||
bucket := getBucketName()
|
||||
lockEnabled := true
|
||||
|
||||
client := s3.NewFromConfig(s.Config())
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err := client.CreateBucket(ctx, &s3.CreateBucketInput{
|
||||
Bucket: &bucket,
|
||||
ObjectLockEnabledForBucket: &lockEnabled,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
failF("%v: %v", err)
|
||||
return fmt.Errorf("%v: %w", testName, err)
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||
resp, err := client.GetObjectLockConfiguration(ctx, &s3.GetObjectLockConfigurationInput{
|
||||
Bucket: &bucket,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
failF("%v: %v", err)
|
||||
return fmt.Errorf("%v: %w", testName, err)
|
||||
}
|
||||
|
||||
if resp.ObjectLockConfiguration.ObjectLockEnabled != types.ObjectLockEnabledEnabled {
|
||||
failF("%v: expected object lock to be enabled", testName)
|
||||
return fmt.Errorf("%v: expected object lock to be enabled", testName)
|
||||
}
|
||||
|
||||
err = teardown(s, bucket)
|
||||
if err != nil {
|
||||
failF("%v: %v", err)
|
||||
return fmt.Errorf("%v: %w", testName, err)
|
||||
}
|
||||
|
||||
passF(testName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func HeadBucket_non_existing_bucket(s *S3Conf) error {
|
||||
testName := "HeadBucket_non_existing_bucket"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
@@ -2183,6 +2228,21 @@ func GetBucketTagging_non_existing_bucket(s *S3Conf) error {
|
||||
})
|
||||
}
|
||||
|
||||
func GetBucketTagging_unset_tags(s *S3Conf) error {
|
||||
testName := "GetBucketTagging_unset_tags"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err := s3client.GetBucketTagging(ctx, &s3.GetBucketTaggingInput{
|
||||
Bucket: &bucket,
|
||||
})
|
||||
cancel()
|
||||
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrBucketTaggingNotFound)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func GetBucketTagging_success(s *S3Conf) error {
|
||||
testName := "GetBucketTagging_success"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
@@ -2371,6 +2431,107 @@ func PutObject_invalid_long_tags(s *S3Conf) error {
|
||||
})
|
||||
}
|
||||
|
||||
func PutObject_missing_object_lock_retention_config(s *S3Conf) error {
|
||||
testName := "PutObject_missing_object_lock_retention_config"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
key := "my-obj"
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err := s3client.PutObject(ctx, &s3.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &key,
|
||||
ObjectLockMode: types.ObjectLockModeCompliance,
|
||||
})
|
||||
cancel()
|
||||
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLockInvalidHeaders)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
retainDate := time.Now().Add(time.Hour * 48)
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = s3client.PutObject(ctx, &s3.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &key,
|
||||
ObjectLockRetainUntilDate: &retainDate,
|
||||
})
|
||||
cancel()
|
||||
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLockInvalidHeaders)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func PutObject_with_object_lock(s *S3Conf) error {
|
||||
testName := "PutObject_with_object_lock"
|
||||
runF(testName)
|
||||
bucket, obj, lockStatus := getBucketName(), "my-obj", true
|
||||
|
||||
client := s3.NewFromConfig(s.Config())
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err := client.CreateBucket(ctx, &s3.CreateBucketInput{
|
||||
Bucket: &bucket,
|
||||
ObjectLockEnabledForBucket: &lockStatus,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
failF("%v: %v", testName, err)
|
||||
return fmt.Errorf("%v: %w", testName, err)
|
||||
}
|
||||
|
||||
retainDate := time.Now().Add(time.Hour * 48)
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = client.PutObject(ctx, &s3.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
ObjectLockLegalHoldStatus: types.ObjectLockLegalHoldStatusOn,
|
||||
ObjectLockMode: types.ObjectLockModeCompliance,
|
||||
ObjectLockRetainUntilDate: &retainDate,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
failF("%v: %v", testName, err)
|
||||
return fmt.Errorf("%v: %w", testName, err)
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||
out, err := client.HeadObject(ctx, &s3.HeadObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
failF("%v: %v", testName, err)
|
||||
return fmt.Errorf("%v: %w", testName, err)
|
||||
}
|
||||
|
||||
if out.ObjectLockMode != types.ObjectLockModeCompliance {
|
||||
failF("%v: expected object lock mode to be %v, instead got %v", testName, types.ObjectLockModeCompliance, out.ObjectLockMode)
|
||||
return fmt.Errorf("%v: expected object lock mode to be %v, instead got %v", testName, types.ObjectLockModeCompliance, out.ObjectLockMode)
|
||||
}
|
||||
if out.ObjectLockLegalHoldStatus != types.ObjectLockLegalHoldStatusOn {
|
||||
failF("%v: expected object lock mode to be %v, instead got %v", testName, types.ObjectLockLegalHoldStatusOn, out.ObjectLockLegalHoldStatus)
|
||||
return fmt.Errorf("%v: expected object lock mode to be %v, instead got %v", testName, types.ObjectLockLegalHoldStatusOn, out.ObjectLockLegalHoldStatus)
|
||||
}
|
||||
|
||||
if err := changeBucketObjectLockStatus(client, bucket, false); err != nil {
|
||||
failF("%v: %v", err)
|
||||
return fmt.Errorf("%v: %w", testName, err)
|
||||
}
|
||||
|
||||
err = teardown(s, bucket)
|
||||
if err != nil {
|
||||
failF("%v: %v", err)
|
||||
return fmt.Errorf("%v: %w", testName, err)
|
||||
}
|
||||
|
||||
passF(testName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func PutObject_success(s *S3Conf) error {
|
||||
testName := "PutObject_success"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
@@ -2418,7 +2579,11 @@ func HeadObject_success(s *S3Conf) error {
|
||||
"key2": "val2",
|
||||
}
|
||||
|
||||
_, _, err := putObjectWithData(dataLen, &s3.PutObjectInput{Bucket: &bucket, Key: &obj, Metadata: meta}, s3client)
|
||||
_, _, err := putObjectWithData(dataLen, &s3.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
Metadata: meta,
|
||||
}, s3client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -3757,6 +3922,26 @@ func GetObjectTagging_non_existing_object(s *S3Conf) error {
|
||||
})
|
||||
}
|
||||
|
||||
func GetObjectTagging_unset_tags(s *S3Conf) error {
|
||||
testName := "GetObjectTagging_unset_tags"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "my-obj"
|
||||
if err := putObjects(s3client, []string{obj}, bucket); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err := s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
})
|
||||
cancel()
|
||||
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrBucketTaggingNotFound)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func GetObjectTagging_success(s *S3Conf) error {
|
||||
testName := "PutObjectTagging_success"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
@@ -5919,22 +6104,18 @@ func GetBucketPolicy_non_existing_bucket(s *S3Conf) error {
|
||||
})
|
||||
}
|
||||
|
||||
func GetBucketPolicy_default_empty_policy(s *S3Conf) error {
|
||||
testName := "GetBucketPolicy_default_empty_policy"
|
||||
func GetBucketPolicy_not_set(s *S3Conf) error {
|
||||
testName := "GetBucketPolicy_not_set"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
out, err := s3client.GetBucketPolicy(ctx, &s3.GetBucketPolicyInput{
|
||||
_, err := s3client.GetBucketPolicy(ctx, &s3.GetBucketPolicyInput{
|
||||
Bucket: &bucket,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucketPolicy)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if out.Policy != nil {
|
||||
return fmt.Errorf("expected policy to be nil, instead got %s", *out.Policy)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -6026,19 +6207,15 @@ func DeleteBucketPolicy_success(s *S3Conf) error {
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||
out, err := s3client.GetBucketPolicy(ctx, &s3.GetBucketPolicyInput{
|
||||
_, err = s3client.GetBucketPolicy(ctx, &s3.GetBucketPolicyInput{
|
||||
Bucket: &bucket,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucketPolicy)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if out.Policy != nil {
|
||||
return fmt.Errorf("expected policy to be nil, instead got %s", *out.Policy)
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -239,8 +239,8 @@ test_common_set_get_object_tags() {
|
||||
[[ $get_result -eq 0 ]] || fail "Error getting object tags"
|
||||
if [[ $1 == 'aws' ]]; then
|
||||
tag_set=$(echo "$tags" | jq '.TagSet')
|
||||
[[ $tag_set == "[]" ]] || fail "Error: tags not empty"
|
||||
elif [[ ! $tags == *"No tags found"* ]]; then
|
||||
[[ $tag_set == "[]" ]] || [[ $tag_set == "" ]] || fail "Error: tags not empty"
|
||||
elif [[ $tags != *"No tags found"* ]] && [[ $tags != "" ]]; then
|
||||
fail "no tags found (tags: $tags)"
|
||||
fi
|
||||
|
||||
@@ -397,7 +397,7 @@ EOF
|
||||
put_bucket_policy "$1" "$BUCKET_ONE_NAME" "$test_file_folder"/"$policy_file" || put_result=$?
|
||||
[[ $put_result -eq 0 ]] || fail "error putting bucket"
|
||||
|
||||
get_bucket_policy "$1" "$BUCKET_ONE_NAME" || get_result=$?
|
||||
get_bucket_policy "$1" "$BUCKET_ONE_NAME" || local get_result=$?
|
||||
[[ $get_result -eq 0 ]] || fail "error getting bucket policy after setting"
|
||||
|
||||
returned_effect=$(echo "$bucket_policy" | jq -r '.Statement[0].Effect')
|
||||
|
||||
+9
-4
@@ -710,11 +710,16 @@ get_object_tags() {
|
||||
return 1
|
||||
fi
|
||||
if [[ $result -ne 0 ]]; then
|
||||
echo "error getting object tags: $tags"
|
||||
return 1
|
||||
if [[ "$tags" == *"NoSuchTagSet"* ]] || [[ "$tags" == *"No tags found"* ]]; then
|
||||
tags=
|
||||
else
|
||||
echo "error getting object tags: $tags"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log 5 "$tags"
|
||||
tags=$(echo "$tags" | grep -v "InsecureRequestWarning")
|
||||
fi
|
||||
log 5 "$tags"
|
||||
tags=$(echo "$tags" | grep -v "InsecureRequestWarning")
|
||||
export tags
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ check_for_empty_policy() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
local get_result=0
|
||||
get_bucket_policy "$1" "$2" || get_result=$?
|
||||
if [[ $get_result -ne 0 ]]; then
|
||||
echo "error getting bucket policy"
|
||||
@@ -13,6 +14,10 @@ check_for_empty_policy() {
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2154
|
||||
if [[ $bucket_policy == "" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
policy=$(echo "$bucket_policy" | jq -r '.Policy')
|
||||
statement=$(echo "$policy" | jq -r '.Statement[0]')
|
||||
if [[ "" != "$statement" ]] && [[ "null" != "$statement" ]]; then
|
||||
|
||||
Reference in New Issue
Block a user