mirror of
https://github.com/versity/versitygw.git
synced 2026-01-03 10:35:15 +00:00
For s3 proxy backend, the ACLs are stored in bucket tags to not conflict with the underlying s3 object store ACLs. If the tag for the gatway ACLs does not exist for the bucket, then we were incorrectly returning the NoSuchTagSet from teh tag lookup. Instead, in this case we need to just return the default gateway bucket ACL, which is the root account ownership. Side note that there is currently a bug in the go sdk where the NoSuchTagSet is not defained as an error type, and a workaround for string matching is required until this is addressed. https://github.com/aws/aws-sdk-go-v2/issues/2878
879 lines
25 KiB
Go
879 lines
25 KiB
Go
// Copyright 2023 Versity Software
|
|
// This file is licensed under the Apache License, Version 2.0
|
|
// (the "License"); you may not use this file except in compliance
|
|
// with the License. You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package s3proxy
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/aws/aws-sdk-go-v2/aws"
|
|
v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
|
|
awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
|
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
|
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
|
"github.com/aws/smithy-go"
|
|
"github.com/versity/versitygw/auth"
|
|
"github.com/versity/versitygw/backend"
|
|
"github.com/versity/versitygw/s3err"
|
|
"github.com/versity/versitygw/s3response"
|
|
)
|
|
|
|
const aclKey string = "versitygwAcl"
|
|
|
|
type S3Proxy struct {
|
|
backend.BackendUnsupported
|
|
|
|
client *s3.Client
|
|
|
|
access string
|
|
secret string
|
|
endpoint string
|
|
awsRegion string
|
|
disableChecksum bool
|
|
sslSkipVerify bool
|
|
debug bool
|
|
}
|
|
|
|
var _ backend.Backend = &S3Proxy{}
|
|
|
|
func New(access, secret, endpoint, region string, disableChecksum, sslSkipVerify, debug bool) (*S3Proxy, error) {
|
|
s := &S3Proxy{
|
|
access: access,
|
|
secret: secret,
|
|
endpoint: endpoint,
|
|
awsRegion: region,
|
|
disableChecksum: disableChecksum,
|
|
sslSkipVerify: sslSkipVerify,
|
|
debug: debug,
|
|
}
|
|
client, err := s.getClientWithCtx(context.Background())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s.client = client
|
|
return s, nil
|
|
}
|
|
|
|
func (s *S3Proxy) ListBuckets(ctx context.Context, input s3response.ListBucketsInput) (s3response.ListAllMyBucketsResult, error) {
|
|
output, err := s.client.ListBuckets(ctx, &s3.ListBucketsInput{
|
|
ContinuationToken: &input.ContinuationToken,
|
|
MaxBuckets: &input.MaxBuckets,
|
|
Prefix: &input.Prefix,
|
|
})
|
|
if err != nil {
|
|
return s3response.ListAllMyBucketsResult{}, handleError(err)
|
|
}
|
|
|
|
var buckets []s3response.ListAllMyBucketsEntry
|
|
for _, b := range output.Buckets {
|
|
buckets = append(buckets, s3response.ListAllMyBucketsEntry{
|
|
Name: *b.Name,
|
|
CreationDate: *b.CreationDate,
|
|
})
|
|
}
|
|
|
|
return s3response.ListAllMyBucketsResult{
|
|
Owner: s3response.CanonicalUser{
|
|
ID: *output.Owner.ID,
|
|
},
|
|
Buckets: s3response.ListAllMyBucketsList{
|
|
Bucket: buckets,
|
|
},
|
|
ContinuationToken: backend.GetStringFromPtr(output.ContinuationToken),
|
|
Prefix: backend.GetStringFromPtr(output.Prefix),
|
|
}, nil
|
|
}
|
|
|
|
func (s *S3Proxy) HeadBucket(ctx context.Context, input *s3.HeadBucketInput) (*s3.HeadBucketOutput, error) {
|
|
out, err := s.client.HeadBucket(ctx, input)
|
|
return out, handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) CreateBucket(ctx context.Context, input *s3.CreateBucketInput, acl []byte) error {
|
|
_, err := s.client.CreateBucket(ctx, input)
|
|
if err != nil {
|
|
return handleError(err)
|
|
}
|
|
|
|
var tagSet []types.Tag
|
|
tagSet = append(tagSet, types.Tag{
|
|
Key: backend.GetPtrFromString(aclKey),
|
|
Value: backend.GetPtrFromString(base64Encode(acl)),
|
|
})
|
|
|
|
_, err = s.client.PutBucketTagging(ctx, &s3.PutBucketTaggingInput{
|
|
Bucket: input.Bucket,
|
|
Tagging: &types.Tagging{
|
|
TagSet: tagSet,
|
|
},
|
|
})
|
|
return handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) DeleteBucket(ctx context.Context, bucket string) error {
|
|
_, err := s.client.DeleteBucket(ctx, &s3.DeleteBucketInput{
|
|
Bucket: &bucket,
|
|
})
|
|
return handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) PutBucketOwnershipControls(ctx context.Context, bucket string, ownership types.ObjectOwnership) error {
|
|
_, err := s.client.PutBucketOwnershipControls(ctx, &s3.PutBucketOwnershipControlsInput{
|
|
Bucket: &bucket,
|
|
OwnershipControls: &types.OwnershipControls{
|
|
Rules: []types.OwnershipControlsRule{
|
|
{
|
|
ObjectOwnership: ownership,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
return handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) GetBucketOwnershipControls(ctx context.Context, bucket string) (types.ObjectOwnership, error) {
|
|
var ownship types.ObjectOwnership
|
|
resp, err := s.client.GetBucketOwnershipControls(ctx, &s3.GetBucketOwnershipControlsInput{
|
|
Bucket: &bucket,
|
|
})
|
|
if err != nil {
|
|
return ownship, handleError(err)
|
|
}
|
|
return resp.OwnershipControls.Rules[0].ObjectOwnership, nil
|
|
}
|
|
func (s *S3Proxy) DeleteBucketOwnershipControls(ctx context.Context, bucket string) error {
|
|
_, err := s.client.DeleteBucketOwnershipControls(ctx, &s3.DeleteBucketOwnershipControlsInput{
|
|
Bucket: &bucket,
|
|
})
|
|
return handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) PutBucketVersioning(ctx context.Context, bucket string, status types.BucketVersioningStatus) error {
|
|
_, err := s.client.PutBucketVersioning(ctx, &s3.PutBucketVersioningInput{
|
|
Bucket: &bucket,
|
|
VersioningConfiguration: &types.VersioningConfiguration{
|
|
Status: status,
|
|
},
|
|
})
|
|
|
|
return handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) GetBucketVersioning(ctx context.Context, bucket string) (s3response.GetBucketVersioningOutput, error) {
|
|
out, err := s.client.GetBucketVersioning(ctx, &s3.GetBucketVersioningInput{
|
|
Bucket: &bucket,
|
|
})
|
|
|
|
return s3response.GetBucketVersioningOutput{
|
|
Status: &out.Status,
|
|
MFADelete: &out.MFADelete,
|
|
}, handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) ListObjectVersions(ctx context.Context, input *s3.ListObjectVersionsInput) (s3response.ListVersionsResult, error) {
|
|
out, err := s.client.ListObjectVersions(ctx, input)
|
|
if err != nil {
|
|
return s3response.ListVersionsResult{}, handleError(err)
|
|
}
|
|
|
|
return s3response.ListVersionsResult{
|
|
CommonPrefixes: out.CommonPrefixes,
|
|
DeleteMarkers: out.DeleteMarkers,
|
|
Delimiter: out.Delimiter,
|
|
EncodingType: out.EncodingType,
|
|
IsTruncated: out.IsTruncated,
|
|
KeyMarker: out.KeyMarker,
|
|
MaxKeys: out.MaxKeys,
|
|
Name: out.Name,
|
|
NextKeyMarker: out.NextKeyMarker,
|
|
NextVersionIdMarker: out.NextVersionIdMarker,
|
|
Prefix: out.Prefix,
|
|
VersionIdMarker: input.VersionIdMarker,
|
|
Versions: out.Versions,
|
|
}, nil
|
|
}
|
|
|
|
func (s *S3Proxy) CreateMultipartUpload(ctx context.Context, input *s3.CreateMultipartUploadInput) (s3response.InitiateMultipartUploadResult, error) {
|
|
out, err := s.client.CreateMultipartUpload(ctx, input)
|
|
if err != nil {
|
|
return s3response.InitiateMultipartUploadResult{}, handleError(err)
|
|
}
|
|
|
|
return s3response.InitiateMultipartUploadResult{
|
|
Bucket: *out.Bucket,
|
|
Key: *out.Key,
|
|
UploadId: *out.UploadId,
|
|
}, nil
|
|
}
|
|
|
|
func (s *S3Proxy) CompleteMultipartUpload(ctx context.Context, input *s3.CompleteMultipartUploadInput) (*s3.CompleteMultipartUploadOutput, error) {
|
|
out, err := s.client.CompleteMultipartUpload(ctx, input)
|
|
return out, handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) AbortMultipartUpload(ctx context.Context, input *s3.AbortMultipartUploadInput) error {
|
|
_, err := s.client.AbortMultipartUpload(ctx, input)
|
|
return handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) ListMultipartUploads(ctx context.Context, input *s3.ListMultipartUploadsInput) (s3response.ListMultipartUploadsResult, error) {
|
|
output, err := s.client.ListMultipartUploads(ctx, input)
|
|
if err != nil {
|
|
return s3response.ListMultipartUploadsResult{}, handleError(err)
|
|
}
|
|
|
|
var uploads []s3response.Upload
|
|
for _, u := range output.Uploads {
|
|
uploads = append(uploads, s3response.Upload{
|
|
Key: *u.Key,
|
|
UploadID: *u.UploadId,
|
|
Initiator: s3response.Initiator{
|
|
ID: *u.Initiator.ID,
|
|
DisplayName: *u.Initiator.DisplayName,
|
|
},
|
|
Owner: s3response.Owner{
|
|
ID: *u.Owner.ID,
|
|
DisplayName: *u.Owner.DisplayName,
|
|
},
|
|
StorageClass: u.StorageClass,
|
|
Initiated: *u.Initiated,
|
|
ChecksumAlgorithm: u.ChecksumAlgorithm,
|
|
ChecksumType: u.ChecksumType,
|
|
})
|
|
}
|
|
|
|
var cps []s3response.CommonPrefix
|
|
for _, c := range output.CommonPrefixes {
|
|
cps = append(cps, s3response.CommonPrefix{
|
|
Prefix: *c.Prefix,
|
|
})
|
|
}
|
|
|
|
return s3response.ListMultipartUploadsResult{
|
|
Bucket: *output.Bucket,
|
|
KeyMarker: *output.KeyMarker,
|
|
UploadIDMarker: *output.UploadIdMarker,
|
|
NextKeyMarker: *output.NextKeyMarker,
|
|
NextUploadIDMarker: *output.NextUploadIdMarker,
|
|
Delimiter: *output.Delimiter,
|
|
Prefix: *output.Prefix,
|
|
EncodingType: string(output.EncodingType),
|
|
MaxUploads: int(*output.MaxUploads),
|
|
IsTruncated: *output.IsTruncated,
|
|
Uploads: uploads,
|
|
CommonPrefixes: cps,
|
|
}, nil
|
|
}
|
|
|
|
func (s *S3Proxy) ListParts(ctx context.Context, input *s3.ListPartsInput) (s3response.ListPartsResult, error) {
|
|
output, err := s.client.ListParts(ctx, input)
|
|
if err != nil {
|
|
return s3response.ListPartsResult{}, handleError(err)
|
|
}
|
|
|
|
var parts []s3response.Part
|
|
for _, p := range output.Parts {
|
|
parts = append(parts, s3response.Part{
|
|
PartNumber: int(*p.PartNumber),
|
|
LastModified: *p.LastModified,
|
|
ETag: *p.ETag,
|
|
Size: *p.Size,
|
|
ChecksumCRC32: p.ChecksumCRC32,
|
|
ChecksumCRC32C: p.ChecksumCRC32C,
|
|
ChecksumCRC64NVME: p.ChecksumCRC64NVME,
|
|
ChecksumSHA1: p.ChecksumSHA1,
|
|
ChecksumSHA256: p.ChecksumSHA256,
|
|
})
|
|
}
|
|
pnm, err := strconv.Atoi(*output.PartNumberMarker)
|
|
if err != nil {
|
|
return s3response.ListPartsResult{},
|
|
fmt.Errorf("parse part number marker: %w", err)
|
|
}
|
|
|
|
npmn, err := strconv.Atoi(*output.NextPartNumberMarker)
|
|
if err != nil {
|
|
return s3response.ListPartsResult{},
|
|
fmt.Errorf("parse next part number marker: %w", err)
|
|
}
|
|
|
|
return s3response.ListPartsResult{
|
|
Bucket: *output.Bucket,
|
|
Key: *output.Key,
|
|
UploadID: *output.UploadId,
|
|
Initiator: s3response.Initiator{
|
|
ID: *output.Initiator.ID,
|
|
DisplayName: *output.Initiator.DisplayName,
|
|
},
|
|
Owner: s3response.Owner{
|
|
ID: *output.Owner.ID,
|
|
DisplayName: *output.Owner.DisplayName,
|
|
},
|
|
StorageClass: output.StorageClass,
|
|
PartNumberMarker: pnm,
|
|
NextPartNumberMarker: npmn,
|
|
MaxParts: int(*output.MaxParts),
|
|
IsTruncated: *output.IsTruncated,
|
|
Parts: parts,
|
|
ChecksumAlgorithm: output.ChecksumAlgorithm,
|
|
ChecksumType: output.ChecksumType,
|
|
}, nil
|
|
}
|
|
|
|
func (s *S3Proxy) UploadPart(ctx context.Context, input *s3.UploadPartInput) (*s3.UploadPartOutput, error) {
|
|
// streaming backend is not seekable,
|
|
// use unsigned payload for streaming ops
|
|
output, err := s.client.UploadPart(ctx, input, s3.WithAPIOptions(
|
|
v4.SwapComputePayloadSHA256ForUnsignedPayloadMiddleware,
|
|
))
|
|
return output, handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) UploadPartCopy(ctx context.Context, input *s3.UploadPartCopyInput) (s3response.CopyPartResult, error) {
|
|
output, err := s.client.UploadPartCopy(ctx, input)
|
|
if err != nil {
|
|
return s3response.CopyPartResult{}, handleError(err)
|
|
}
|
|
|
|
return s3response.CopyPartResult{
|
|
LastModified: *output.CopyPartResult.LastModified,
|
|
ETag: output.CopyPartResult.ETag,
|
|
ChecksumCRC32: output.CopyPartResult.ChecksumCRC32,
|
|
ChecksumCRC32C: output.CopyPartResult.ChecksumCRC32C,
|
|
ChecksumCRC64NVME: output.CopyPartResult.ChecksumCRC64NVME,
|
|
ChecksumSHA1: output.CopyPartResult.ChecksumSHA1,
|
|
ChecksumSHA256: output.CopyPartResult.ChecksumSHA256,
|
|
}, nil
|
|
}
|
|
|
|
func (s *S3Proxy) PutObject(ctx context.Context, input *s3.PutObjectInput) (s3response.PutObjectOutput, error) {
|
|
// streaming backend is not seekable,
|
|
// use unsigned payload for streaming ops
|
|
output, err := s.client.PutObject(ctx, input, s3.WithAPIOptions(
|
|
v4.SwapComputePayloadSHA256ForUnsignedPayloadMiddleware,
|
|
))
|
|
if err != nil {
|
|
return s3response.PutObjectOutput{}, handleError(err)
|
|
}
|
|
|
|
var versionID string
|
|
if output.VersionId != nil {
|
|
versionID = *output.VersionId
|
|
}
|
|
|
|
return s3response.PutObjectOutput{
|
|
ETag: *output.ETag,
|
|
VersionID: versionID,
|
|
ChecksumCRC32: output.ChecksumCRC32,
|
|
ChecksumCRC32C: output.ChecksumCRC32C,
|
|
ChecksumCRC64NVME: output.ChecksumCRC64NVME,
|
|
ChecksumSHA1: output.ChecksumSHA1,
|
|
ChecksumSHA256: output.ChecksumSHA256,
|
|
}, nil
|
|
}
|
|
|
|
func (s *S3Proxy) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3.HeadObjectOutput, error) {
|
|
out, err := s.client.HeadObject(ctx, input)
|
|
return out, handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) GetObject(ctx context.Context, input *s3.GetObjectInput) (*s3.GetObjectOutput, error) {
|
|
output, err := s.client.GetObject(ctx, input)
|
|
if err != nil {
|
|
return nil, handleError(err)
|
|
}
|
|
|
|
return output, nil
|
|
}
|
|
|
|
func (s *S3Proxy) GetObjectAttributes(ctx context.Context, input *s3.GetObjectAttributesInput) (s3response.GetObjectAttributesResponse, error) {
|
|
out, err := s.client.GetObjectAttributes(ctx, input)
|
|
|
|
parts := s3response.ObjectParts{}
|
|
objParts := out.ObjectParts
|
|
if objParts != nil {
|
|
if objParts.PartNumberMarker != nil {
|
|
partNumberMarker, err := strconv.Atoi(*objParts.PartNumberMarker)
|
|
if err != nil {
|
|
parts.PartNumberMarker = partNumberMarker
|
|
}
|
|
if objParts.NextPartNumberMarker != nil {
|
|
nextPartNumberMarker, err := strconv.Atoi(*objParts.NextPartNumberMarker)
|
|
if err != nil {
|
|
parts.NextPartNumberMarker = nextPartNumberMarker
|
|
}
|
|
}
|
|
if objParts.IsTruncated != nil {
|
|
parts.IsTruncated = *objParts.IsTruncated
|
|
}
|
|
if objParts.MaxParts != nil {
|
|
parts.MaxParts = int(*objParts.MaxParts)
|
|
}
|
|
parts.Parts = objParts.Parts
|
|
}
|
|
}
|
|
|
|
return s3response.GetObjectAttributesResponse{
|
|
ETag: out.ETag,
|
|
LastModified: out.LastModified,
|
|
ObjectSize: out.ObjectSize,
|
|
StorageClass: out.StorageClass,
|
|
ObjectParts: &parts,
|
|
Checksum: out.Checksum,
|
|
}, handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) CopyObject(ctx context.Context, input *s3.CopyObjectInput) (*s3.CopyObjectOutput, error) {
|
|
out, err := s.client.CopyObject(ctx, input)
|
|
return out, handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) ListObjects(ctx context.Context, input *s3.ListObjectsInput) (s3response.ListObjectsResult, error) {
|
|
out, err := s.client.ListObjects(ctx, input)
|
|
if err != nil {
|
|
return s3response.ListObjectsResult{}, handleError(err)
|
|
}
|
|
|
|
contents := convertObjects(out.Contents)
|
|
|
|
return s3response.ListObjectsResult{
|
|
CommonPrefixes: out.CommonPrefixes,
|
|
Contents: contents,
|
|
Delimiter: out.Delimiter,
|
|
IsTruncated: out.IsTruncated,
|
|
Marker: out.Marker,
|
|
MaxKeys: out.MaxKeys,
|
|
Name: out.Name,
|
|
NextMarker: out.NextMarker,
|
|
Prefix: out.Prefix,
|
|
}, nil
|
|
}
|
|
|
|
func (s *S3Proxy) ListObjectsV2(ctx context.Context, input *s3.ListObjectsV2Input) (s3response.ListObjectsV2Result, error) {
|
|
out, err := s.client.ListObjectsV2(ctx, input)
|
|
if err != nil {
|
|
return s3response.ListObjectsV2Result{}, handleError(err)
|
|
}
|
|
|
|
contents := convertObjects(out.Contents)
|
|
|
|
return s3response.ListObjectsV2Result{
|
|
CommonPrefixes: out.CommonPrefixes,
|
|
Contents: contents,
|
|
Delimiter: out.Delimiter,
|
|
IsTruncated: out.IsTruncated,
|
|
ContinuationToken: out.ContinuationToken,
|
|
MaxKeys: out.MaxKeys,
|
|
Name: out.Name,
|
|
NextContinuationToken: out.NextContinuationToken,
|
|
Prefix: out.Prefix,
|
|
KeyCount: out.KeyCount,
|
|
}, nil
|
|
}
|
|
|
|
func (s *S3Proxy) DeleteObject(ctx context.Context, input *s3.DeleteObjectInput) (*s3.DeleteObjectOutput, error) {
|
|
res, err := s.client.DeleteObject(ctx, input)
|
|
return res, handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) DeleteObjects(ctx context.Context, input *s3.DeleteObjectsInput) (s3response.DeleteResult, error) {
|
|
if len(input.Delete.Objects) == 0 {
|
|
input.Delete.Objects = []types.ObjectIdentifier{}
|
|
}
|
|
|
|
output, err := s.client.DeleteObjects(ctx, input)
|
|
if err != nil {
|
|
return s3response.DeleteResult{}, handleError(err)
|
|
}
|
|
|
|
return s3response.DeleteResult{
|
|
Deleted: output.Deleted,
|
|
Error: output.Errors,
|
|
}, nil
|
|
}
|
|
|
|
func (s *S3Proxy) GetBucketAcl(ctx context.Context, input *s3.GetBucketAclInput) ([]byte, error) {
|
|
tagout, err := s.client.GetBucketTagging(ctx, &s3.GetBucketTaggingInput{
|
|
Bucket: input.Bucket,
|
|
})
|
|
if err != nil {
|
|
var ae smithy.APIError
|
|
if errors.As(err, &ae) {
|
|
// sdk issue workaround for missing NoSuchTagSet error type
|
|
// https://github.com/aws/aws-sdk-go-v2/issues/2878
|
|
if strings.Contains(ae.ErrorCode(), "NoSuchTagSet") {
|
|
return []byte{}, nil
|
|
}
|
|
}
|
|
return nil, handleError(err)
|
|
}
|
|
|
|
for _, tag := range tagout.TagSet {
|
|
if *tag.Key == aclKey {
|
|
acl, err := base64Decode(*tag.Value)
|
|
if err != nil {
|
|
return nil, handleError(err)
|
|
}
|
|
return acl, nil
|
|
}
|
|
}
|
|
|
|
return []byte{}, nil
|
|
}
|
|
|
|
func (s *S3Proxy) PutBucketAcl(ctx context.Context, bucket string, data []byte) error {
|
|
tagout, err := s.client.GetBucketTagging(ctx, &s3.GetBucketTaggingInput{
|
|
Bucket: &bucket,
|
|
})
|
|
if err != nil {
|
|
return handleError(err)
|
|
}
|
|
|
|
var found bool
|
|
for i, tag := range tagout.TagSet {
|
|
if *tag.Key == aclKey {
|
|
tagout.TagSet[i] = types.Tag{
|
|
Key: backend.GetPtrFromString(aclKey),
|
|
Value: backend.GetPtrFromString(base64Encode(data)),
|
|
}
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
tagout.TagSet = append(tagout.TagSet, types.Tag{
|
|
Key: backend.GetPtrFromString(aclKey),
|
|
Value: backend.GetPtrFromString(base64Encode(data)),
|
|
})
|
|
}
|
|
|
|
_, err = s.client.PutBucketTagging(ctx, &s3.PutBucketTaggingInput{
|
|
Bucket: &bucket,
|
|
Tagging: &types.Tagging{
|
|
TagSet: tagout.TagSet,
|
|
},
|
|
})
|
|
return handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) PutObjectTagging(ctx context.Context, bucket, object string, tags map[string]string) error {
|
|
tagging := &types.Tagging{
|
|
TagSet: []types.Tag{},
|
|
}
|
|
for key, val := range tags {
|
|
tagging.TagSet = append(tagging.TagSet, types.Tag{
|
|
Key: &key,
|
|
Value: &val,
|
|
})
|
|
}
|
|
|
|
_, err := s.client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{
|
|
Bucket: &bucket,
|
|
Key: &object,
|
|
Tagging: tagging,
|
|
})
|
|
return handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) GetObjectTagging(ctx context.Context, bucket, object string) (map[string]string, error) {
|
|
output, err := s.client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{
|
|
Bucket: &bucket,
|
|
Key: &object,
|
|
})
|
|
if err != nil {
|
|
return nil, handleError(err)
|
|
}
|
|
|
|
tags := make(map[string]string)
|
|
for _, el := range output.TagSet {
|
|
tags[*el.Key] = *el.Value
|
|
}
|
|
|
|
return tags, nil
|
|
}
|
|
|
|
func (s *S3Proxy) DeleteObjectTagging(ctx context.Context, bucket, object string) error {
|
|
_, err := s.client.DeleteObjectTagging(ctx, &s3.DeleteObjectTaggingInput{
|
|
Bucket: &bucket,
|
|
Key: &object,
|
|
})
|
|
return handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) PutBucketPolicy(ctx context.Context, bucket string, policy []byte) error {
|
|
_, err := s.client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{
|
|
Bucket: &bucket,
|
|
Policy: backend.GetPtrFromString(string(policy)),
|
|
})
|
|
return handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) GetBucketPolicy(ctx context.Context, bucket string) ([]byte, error) {
|
|
policy, err := s.client.GetBucketPolicy(ctx, &s3.GetBucketPolicyInput{
|
|
Bucket: &bucket,
|
|
})
|
|
if err != nil {
|
|
return nil, handleError(err)
|
|
}
|
|
|
|
result := []byte{}
|
|
if policy.Policy != nil {
|
|
result = []byte(*policy.Policy)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (s *S3Proxy) DeleteBucketPolicy(ctx context.Context, bucket string) error {
|
|
_, err := s.client.DeleteBucketPolicy(ctx, &s3.DeleteBucketPolicyInput{
|
|
Bucket: &bucket,
|
|
})
|
|
return handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) PutObjectLockConfiguration(ctx context.Context, bucket string, config []byte) error {
|
|
cfg, err := auth.ParseBucketLockConfigurationOutput(config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = s.client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{
|
|
Bucket: &bucket,
|
|
ObjectLockConfiguration: cfg,
|
|
})
|
|
|
|
return handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) GetObjectLockConfiguration(ctx context.Context, bucket string) ([]byte, error) {
|
|
resp, err := s.client.GetObjectLockConfiguration(ctx, &s3.GetObjectLockConfigurationInput{
|
|
Bucket: &bucket,
|
|
})
|
|
if err != nil {
|
|
return nil, handleError(err)
|
|
}
|
|
|
|
config := auth.BucketLockConfig{
|
|
Enabled: resp.ObjectLockConfiguration.ObjectLockEnabled == types.ObjectLockEnabledEnabled,
|
|
DefaultRetention: resp.ObjectLockConfiguration.Rule.DefaultRetention,
|
|
}
|
|
|
|
return json.Marshal(config)
|
|
}
|
|
|
|
func (s *S3Proxy) PutObjectRetention(ctx context.Context, bucket, object, versionId string, bypass bool, retention []byte) error {
|
|
ret, err := auth.ParseObjectLockRetentionOutput(retention)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = s.client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{
|
|
Bucket: &bucket,
|
|
Key: &object,
|
|
VersionId: &versionId,
|
|
Retention: ret,
|
|
BypassGovernanceRetention: &bypass,
|
|
})
|
|
return handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) GetObjectRetention(ctx context.Context, bucket, object, versionId string) ([]byte, error) {
|
|
resp, err := s.client.GetObjectRetention(ctx, &s3.GetObjectRetentionInput{
|
|
Bucket: &bucket,
|
|
Key: &object,
|
|
VersionId: &versionId,
|
|
})
|
|
if err != nil {
|
|
return nil, handleError(err)
|
|
}
|
|
|
|
return json.Marshal(resp.Retention)
|
|
}
|
|
|
|
func (s *S3Proxy) PutObjectLegalHold(ctx context.Context, bucket, object, versionId string, status bool) error {
|
|
var st types.ObjectLockLegalHoldStatus
|
|
if status {
|
|
st = types.ObjectLockLegalHoldStatusOn
|
|
} else {
|
|
st = types.ObjectLockLegalHoldStatusOff
|
|
}
|
|
|
|
_, err := s.client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{
|
|
Bucket: &bucket,
|
|
Key: &object,
|
|
VersionId: &versionId,
|
|
LegalHold: &types.ObjectLockLegalHold{
|
|
Status: st,
|
|
},
|
|
})
|
|
return handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) GetObjectLegalHold(ctx context.Context, bucket, object, versionId string) (*bool, error) {
|
|
resp, err := s.client.GetObjectLegalHold(ctx, &s3.GetObjectLegalHoldInput{
|
|
Bucket: &bucket,
|
|
Key: &object,
|
|
VersionId: &versionId,
|
|
})
|
|
if err != nil {
|
|
return nil, handleError(err)
|
|
}
|
|
|
|
status := resp.LegalHold.Status == types.ObjectLockLegalHoldStatusOn
|
|
return &status, nil
|
|
}
|
|
|
|
func (s *S3Proxy) ChangeBucketOwner(ctx context.Context, bucket string, acl []byte) error {
|
|
var acll auth.ACL
|
|
if err := json.Unmarshal(acl, &acll); err != nil {
|
|
return fmt.Errorf("unmarshal acl: %w", err)
|
|
}
|
|
req, err := http.NewRequest(http.MethodPatch, fmt.Sprintf("%v/change-bucket-owner/?bucket=%v&owner=%v", s.endpoint, bucket, acll.Owner), nil)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to send the request: %w", err)
|
|
}
|
|
|
|
signer := v4.NewSigner()
|
|
|
|
hashedPayload := sha256.Sum256([]byte{})
|
|
hexPayload := hex.EncodeToString(hashedPayload[:])
|
|
|
|
req.Header.Set("X-Amz-Content-Sha256", hexPayload)
|
|
|
|
signErr := signer.SignHTTP(req.Context(), aws.Credentials{AccessKeyID: s.access, SecretAccessKey: s.secret}, req, hexPayload, "s3", s.awsRegion, time.Now())
|
|
if signErr != nil {
|
|
return fmt.Errorf("failed to sign the request: %w", err)
|
|
}
|
|
|
|
client := http.Client{}
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to send the request: %w", err)
|
|
}
|
|
|
|
if resp.StatusCode > 300 {
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
return fmt.Errorf("%v", string(body))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *S3Proxy) ListBucketsAndOwners(ctx context.Context) ([]s3response.Bucket, error) {
|
|
req, err := http.NewRequest(http.MethodPatch, fmt.Sprintf("%v/list-buckets", s.endpoint), nil)
|
|
if err != nil {
|
|
return []s3response.Bucket{}, fmt.Errorf("failed to send the request: %w", err)
|
|
}
|
|
|
|
signer := v4.NewSigner()
|
|
|
|
hashedPayload := sha256.Sum256([]byte{})
|
|
hexPayload := hex.EncodeToString(hashedPayload[:])
|
|
|
|
req.Header.Set("X-Amz-Content-Sha256", hexPayload)
|
|
|
|
signErr := signer.SignHTTP(req.Context(), aws.Credentials{AccessKeyID: s.access, SecretAccessKey: s.secret}, req, hexPayload, "s3", s.awsRegion, time.Now())
|
|
if signErr != nil {
|
|
return []s3response.Bucket{}, fmt.Errorf("failed to sign the request: %w", err)
|
|
}
|
|
|
|
client := http.Client{}
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return []s3response.Bucket{}, fmt.Errorf("failed to send the request: %w", err)
|
|
}
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return []s3response.Bucket{}, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var buckets []s3response.Bucket
|
|
if err := json.Unmarshal(body, &buckets); err != nil {
|
|
return []s3response.Bucket{}, err
|
|
}
|
|
|
|
return buckets, nil
|
|
}
|
|
|
|
func handleError(err error) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
var ae smithy.APIError
|
|
if errors.As(err, &ae) {
|
|
apiErr := s3err.APIError{
|
|
Code: ae.ErrorCode(),
|
|
Description: ae.ErrorMessage(),
|
|
}
|
|
var re *awshttp.ResponseError
|
|
if errors.As(err, &re) {
|
|
apiErr.HTTPStatusCode = re.Response.StatusCode
|
|
}
|
|
return apiErr
|
|
}
|
|
return err
|
|
}
|
|
|
|
func base64Encode(input []byte) string {
|
|
return base64.StdEncoding.EncodeToString(input)
|
|
}
|
|
|
|
func base64Decode(encoded string) ([]byte, error) {
|
|
decoded, err := base64.StdEncoding.DecodeString(encoded)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return decoded, nil
|
|
}
|
|
|
|
func convertObjects(objs []types.Object) []s3response.Object {
|
|
result := make([]s3response.Object, len(objs))
|
|
|
|
for _, obj := range objs {
|
|
result = append(result, s3response.Object{
|
|
ETag: obj.ETag,
|
|
Key: obj.Key,
|
|
LastModified: obj.LastModified,
|
|
Owner: obj.Owner,
|
|
Size: obj.Size,
|
|
RestoreStatus: obj.RestoreStatus,
|
|
StorageClass: obj.StorageClass,
|
|
ChecksumAlgorithm: obj.ChecksumAlgorithm,
|
|
ChecksumType: obj.ChecksumType,
|
|
})
|
|
}
|
|
|
|
return result
|
|
}
|