2018-07-18 17:37:09 +08:00
|
|
|
package s3api
|
|
|
|
|
|
|
|
import (
|
2024-04-27 22:39:22 +08:00
|
|
|
"bytes"
|
2018-07-18 17:37:09 +08:00
|
|
|
"context"
|
2019-02-27 15:23:04 +08:00
|
|
|
"encoding/xml"
|
2022-03-31 11:44:48 +08:00
|
|
|
"errors"
|
2018-07-18 17:37:09 +08:00
|
|
|
"fmt"
|
2019-01-03 03:36:29 +08:00
|
|
|
"math"
|
2018-07-22 08:39:10 +08:00
|
|
|
"net/http"
|
2024-04-27 22:39:22 +08:00
|
|
|
"strings"
|
2018-07-22 08:39:10 +08:00
|
|
|
"time"
|
2018-07-24 16:38:08 +08:00
|
|
|
|
2024-05-17 19:54:09 +08:00
|
|
|
"github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3bucket"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
|
|
|
|
2022-07-29 15:17:28 +08:00
|
|
|
"github.com/seaweedfs/seaweedfs/weed/filer"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/needle"
|
2022-02-03 22:17:05 +08:00
|
|
|
|
2022-07-29 15:17:28 +08:00
|
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
|
2020-11-11 16:20:59 +08:00
|
|
|
|
2019-01-03 03:36:29 +08:00
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
|
|
"github.com/aws/aws-sdk-go/service/s3"
|
2020-02-26 03:13:06 +08:00
|
|
|
|
2022-07-29 15:17:28 +08:00
|
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
2018-07-19 16:21:44 +08:00
|
|
|
)
|
|
|
|
|
2019-01-03 03:36:29 +08:00
|
|
|
type ListAllMyBucketsResult struct {
|
2019-03-16 06:55:34 +08:00
|
|
|
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListAllMyBucketsResult"`
|
2019-01-03 03:36:29 +08:00
|
|
|
Owner *s3.Owner
|
2019-02-27 15:23:04 +08:00
|
|
|
Buckets []*s3.Bucket `xml:"Buckets>Bucket"`
|
2019-01-03 03:36:29 +08:00
|
|
|
}
|
|
|
|
|
2018-07-18 17:37:09 +08:00
|
|
|
func (s3a *S3ApiServer) ListBucketsHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
2021-09-19 15:18:59 +08:00
|
|
|
glog.V(3).Infof("ListBucketsHandler")
|
|
|
|
|
2020-12-25 16:38:56 +08:00
|
|
|
var identity *Identity
|
|
|
|
var s3Err s3err.ErrorCode
|
|
|
|
if s3a.iam.isEnabled() {
|
|
|
|
identity, s3Err = s3a.iam.authUser(r)
|
|
|
|
if s3Err != s3err.ErrNone {
|
2021-11-01 09:05:34 +08:00
|
|
|
s3err.WriteErrorResponse(w, r, s3Err)
|
2020-12-25 16:38:56 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-03 03:36:29 +08:00
|
|
|
var response ListAllMyBucketsResult
|
2018-07-18 17:37:09 +08:00
|
|
|
|
2020-09-12 05:53:50 +08:00
|
|
|
entries, _, err := s3a.list(s3a.option.BucketsPath, "", "", false, math.MaxInt32)
|
2018-07-18 17:37:09 +08:00
|
|
|
|
2018-09-04 04:03:16 +08:00
|
|
|
if err != nil {
|
2021-11-01 09:05:34 +08:00
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
2018-09-04 04:03:16 +08:00
|
|
|
return
|
|
|
|
}
|
2018-07-18 17:37:09 +08:00
|
|
|
|
2022-05-31 13:57:41 +08:00
|
|
|
identityId := r.Header.Get(s3_constants.AmzIdentityId)
|
2020-11-11 16:20:59 +08:00
|
|
|
|
2019-01-03 03:36:29 +08:00
|
|
|
var buckets []*s3.Bucket
|
2018-09-04 04:03:16 +08:00
|
|
|
for _, entry := range entries {
|
|
|
|
if entry.IsDirectory {
|
2022-01-04 07:39:36 +08:00
|
|
|
if identity != nil && !identity.canDo(s3_constants.ACTION_LIST, entry.Name, "") {
|
2020-11-13 05:57:54 +08:00
|
|
|
continue
|
2020-11-11 16:20:59 +08:00
|
|
|
}
|
2019-01-03 03:36:29 +08:00
|
|
|
buckets = append(buckets, &s3.Bucket{
|
|
|
|
Name: aws.String(entry.Name),
|
2020-04-30 12:35:24 +08:00
|
|
|
CreationDate: aws.Time(time.Unix(entry.Attributes.Crtime, 0).UTC()),
|
2018-09-04 04:03:16 +08:00
|
|
|
})
|
2018-07-18 17:37:09 +08:00
|
|
|
}
|
2018-09-04 04:03:16 +08:00
|
|
|
}
|
2018-07-18 17:37:09 +08:00
|
|
|
|
2019-01-03 03:36:29 +08:00
|
|
|
response = ListAllMyBucketsResult{
|
|
|
|
Owner: &s3.Owner{
|
2020-11-11 16:20:59 +08:00
|
|
|
ID: aws.String(identityId),
|
|
|
|
DisplayName: aws.String(identityId),
|
2018-09-04 04:03:16 +08:00
|
|
|
},
|
2019-01-03 03:36:29 +08:00
|
|
|
Buckets: buckets,
|
2018-07-18 17:37:09 +08:00
|
|
|
}
|
|
|
|
|
2021-11-01 09:02:08 +08:00
|
|
|
writeSuccessResponseXML(w, r, response)
|
2018-07-18 17:37:09 +08:00
|
|
|
}
|
2018-07-19 16:21:44 +08:00
|
|
|
|
|
|
|
func (s3a *S3ApiServer) PutBucketHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
2022-05-31 13:57:41 +08:00
|
|
|
bucket, _ := s3_constants.GetBucketAndObject(r)
|
2021-09-19 15:18:59 +08:00
|
|
|
glog.V(3).Infof("PutBucketHandler %s", bucket)
|
2018-07-19 16:21:44 +08:00
|
|
|
|
2023-02-08 06:57:29 +08:00
|
|
|
// validate the bucket name
|
|
|
|
err := s3bucket.VerifyS3BucketName(bucket)
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("put invalid bucket name: %v %v", bucket, err)
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInvalidBucketName)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-10-16 01:52:17 +08:00
|
|
|
// avoid duplicated buckets
|
|
|
|
errCode := s3err.ErrNone
|
2021-12-26 16:15:03 +08:00
|
|
|
if err := s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
2020-10-16 01:52:17 +08:00
|
|
|
if resp, err := client.CollectionList(context.Background(), &filer_pb.CollectionListRequest{
|
|
|
|
IncludeEcVolumes: true,
|
|
|
|
IncludeNormalVolumes: true,
|
|
|
|
}); err != nil {
|
|
|
|
glog.Errorf("list collection: %v", err)
|
|
|
|
return fmt.Errorf("list collections: %v", err)
|
2020-10-25 11:12:04 +08:00
|
|
|
} else {
|
2020-10-16 01:52:17 +08:00
|
|
|
for _, c := range resp.Collections {
|
2023-05-17 00:39:43 +08:00
|
|
|
if s3a.getCollectionName(bucket) == c.Name {
|
2020-10-16 01:52:17 +08:00
|
|
|
errCode = s3err.ErrBucketAlreadyExists
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}); err != nil {
|
2021-11-01 09:05:34 +08:00
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
2020-10-16 01:52:17 +08:00
|
|
|
return
|
|
|
|
}
|
2020-11-12 14:11:03 +08:00
|
|
|
if exist, err := s3a.exists(s3a.option.BucketsPath, bucket, true); err == nil && exist {
|
|
|
|
errCode = s3err.ErrBucketAlreadyExists
|
|
|
|
}
|
2020-10-16 01:52:17 +08:00
|
|
|
if errCode != s3err.ErrNone {
|
2021-11-01 09:05:34 +08:00
|
|
|
s3err.WriteErrorResponse(w, r, errCode)
|
2020-10-16 01:52:17 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-11-11 16:20:59 +08:00
|
|
|
fn := func(entry *filer_pb.Entry) {
|
2022-05-31 13:57:41 +08:00
|
|
|
if identityId := r.Header.Get(s3_constants.AmzIdentityId); identityId != "" {
|
2020-11-12 11:50:19 +08:00
|
|
|
if entry.Extended == nil {
|
|
|
|
entry.Extended = make(map[string][]byte)
|
|
|
|
}
|
2022-05-31 13:57:41 +08:00
|
|
|
entry.Extended[s3_constants.AmzIdentityId] = []byte(identityId)
|
2020-11-11 16:20:59 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-04 04:03:16 +08:00
|
|
|
// create the folder for bucket, but lazily create actual collection
|
2020-11-11 16:20:59 +08:00
|
|
|
if err := s3a.mkdir(s3a.option.BucketsPath, bucket, fn); err != nil {
|
2020-10-16 01:52:17 +08:00
|
|
|
glog.Errorf("PutBucketHandler mkdir: %v", err)
|
2021-11-01 09:05:34 +08:00
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
2018-07-19 16:21:44 +08:00
|
|
|
return
|
|
|
|
}
|
2022-05-02 14:16:29 +08:00
|
|
|
w.Header().Set("Location", "/"+bucket)
|
2021-11-01 09:02:08 +08:00
|
|
|
writeSuccessResponseEmpty(w, r)
|
2018-07-19 16:21:44 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s3a *S3ApiServer) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
2022-05-31 13:57:41 +08:00
|
|
|
bucket, _ := s3_constants.GetBucketAndObject(r)
|
2021-09-19 15:18:59 +08:00
|
|
|
glog.V(3).Infof("DeleteBucketHandler %s", bucket)
|
2018-07-19 16:21:44 +08:00
|
|
|
|
2020-11-13 17:13:20 +08:00
|
|
|
if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
|
2021-11-01 09:05:34 +08:00
|
|
|
s3err.WriteErrorResponse(w, r, err)
|
2020-11-12 16:44:16 +08:00
|
|
|
return
|
|
|
|
}
|
2020-11-12 17:59:31 +08:00
|
|
|
|
2021-12-26 16:15:03 +08:00
|
|
|
err := s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
2022-03-31 01:46:13 +08:00
|
|
|
if !s3a.option.AllowDeleteBucketNotEmpty {
|
2022-06-29 19:21:16 +08:00
|
|
|
entries, _, err := s3a.list(s3a.option.BucketsPath+"/"+bucket, "", "", false, 2)
|
2022-03-31 01:46:13 +08:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to list bucket %s: %v", bucket, err)
|
|
|
|
}
|
2022-06-29 19:21:16 +08:00
|
|
|
for _, entry := range entries {
|
|
|
|
if entry.Name != s3_constants.MultipartUploadsFolder {
|
|
|
|
return errors.New(s3err.GetAPIError(s3err.ErrBucketNotEmpty).Code)
|
|
|
|
}
|
2022-03-31 01:46:13 +08:00
|
|
|
}
|
2021-05-24 22:13:04 +08:00
|
|
|
}
|
2018-07-20 15:10:01 +08:00
|
|
|
|
|
|
|
// delete collection
|
|
|
|
deleteCollectionRequest := &filer_pb.DeleteCollectionRequest{
|
2023-05-17 00:39:43 +08:00
|
|
|
Collection: s3a.getCollectionName(bucket),
|
2018-07-20 15:10:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
glog.V(1).Infof("delete collection: %v", deleteCollectionRequest)
|
2020-02-26 14:23:59 +08:00
|
|
|
if _, err := client.DeleteCollection(context.Background(), deleteCollectionRequest); err != nil {
|
2018-07-20 15:58:10 +08:00
|
|
|
return fmt.Errorf("delete collection %s: %v", bucket, err)
|
2018-07-20 15:10:01 +08:00
|
|
|
}
|
|
|
|
|
2018-07-19 16:21:44 +08:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
2021-05-24 22:13:04 +08:00
|
|
|
if err != nil {
|
|
|
|
s3ErrorCode := s3err.ErrInternalError
|
2022-03-31 11:44:48 +08:00
|
|
|
if err.Error() == s3err.GetAPIError(s3err.ErrBucketNotEmpty).Code {
|
2021-05-24 22:13:04 +08:00
|
|
|
s3ErrorCode = s3err.ErrBucketNotEmpty
|
|
|
|
}
|
2022-03-31 01:46:13 +08:00
|
|
|
s3err.WriteErrorResponse(w, r, s3ErrorCode)
|
2021-05-24 22:13:04 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-03-21 05:17:31 +08:00
|
|
|
err = s3a.rm(s3a.option.BucketsPath, bucket, false, true)
|
2018-09-04 04:16:26 +08:00
|
|
|
|
2018-07-19 16:21:44 +08:00
|
|
|
if err != nil {
|
2021-11-01 09:05:34 +08:00
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
2018-07-19 16:21:44 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-11-01 09:02:08 +08:00
|
|
|
s3err.WriteEmptyResponse(w, r, http.StatusNoContent)
|
2018-07-19 16:21:44 +08:00
|
|
|
}
|
2018-07-19 16:43:27 +08:00
|
|
|
|
|
|
|
func (s3a *S3ApiServer) HeadBucketHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
2022-05-31 13:57:41 +08:00
|
|
|
bucket, _ := s3_constants.GetBucketAndObject(r)
|
2021-09-19 15:18:59 +08:00
|
|
|
glog.V(3).Infof("HeadBucketHandler %s", bucket)
|
2018-07-19 16:43:27 +08:00
|
|
|
|
2021-11-17 17:42:24 +08:00
|
|
|
if entry, err := s3a.getEntry(s3a.option.BucketsPath, bucket); entry == nil || err == filer_pb.ErrNotFound {
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket)
|
2020-11-13 17:13:20 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-11-01 09:02:08 +08:00
|
|
|
writeSuccessResponseEmpty(w, r)
|
2020-11-13 17:13:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s3a *S3ApiServer) checkBucket(r *http.Request, bucket string) s3err.ErrorCode {
|
2020-11-13 05:30:08 +08:00
|
|
|
entry, err := s3a.getEntry(s3a.option.BucketsPath, bucket)
|
2020-11-13 05:30:46 +08:00
|
|
|
if entry == nil || err == filer_pb.ErrNotFound {
|
2020-11-13 17:13:20 +08:00
|
|
|
return s3err.ErrNoSuchBucket
|
2018-07-19 16:43:27 +08:00
|
|
|
}
|
2020-11-12 17:59:31 +08:00
|
|
|
|
2024-05-17 19:54:09 +08:00
|
|
|
//if iam is enabled, the access was already checked before
|
|
|
|
if s3a.iam.isEnabled() {
|
|
|
|
return s3err.ErrNone
|
|
|
|
}
|
2020-11-13 05:57:54 +08:00
|
|
|
if !s3a.hasAccess(r, entry) {
|
2020-11-13 17:13:20 +08:00
|
|
|
return s3err.ErrAccessDenied
|
2020-11-12 16:15:59 +08:00
|
|
|
}
|
2020-11-13 17:13:20 +08:00
|
|
|
return s3err.ErrNone
|
2018-07-19 16:43:27 +08:00
|
|
|
}
|
2020-11-13 05:57:54 +08:00
|
|
|
|
|
|
|
func (s3a *S3ApiServer) hasAccess(r *http.Request, entry *filer_pb.Entry) bool {
|
2022-05-31 13:57:41 +08:00
|
|
|
isAdmin := r.Header.Get(s3_constants.AmzIsAdmin) != ""
|
2020-11-13 05:57:54 +08:00
|
|
|
if isAdmin {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if entry.Extended == nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2022-05-31 13:57:41 +08:00
|
|
|
identityId := r.Header.Get(s3_constants.AmzIdentityId)
|
|
|
|
if id, ok := entry.Extended[s3_constants.AmzIdentityId]; ok {
|
2020-11-13 05:57:54 +08:00
|
|
|
if identityId != string(id) {
|
2024-05-17 19:54:09 +08:00
|
|
|
glog.V(3).Infof("hasAccess: %s != %s (entry.Extended = %v)", identityId, id, entry.Extended)
|
2020-11-13 05:57:54 +08:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
2020-11-13 17:13:20 +08:00
|
|
|
}
|
2021-10-11 18:03:56 +08:00
|
|
|
|
|
|
|
// GetBucketAclHandler Get Bucket ACL
|
|
|
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketAcl.html
|
|
|
|
func (s3a *S3ApiServer) GetBucketAclHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
// collect parameters
|
2022-05-31 13:57:41 +08:00
|
|
|
bucket, _ := s3_constants.GetBucketAndObject(r)
|
2021-10-11 18:03:56 +08:00
|
|
|
glog.V(3).Infof("GetBucketAclHandler %s", bucket)
|
|
|
|
|
|
|
|
if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
|
2021-11-01 09:05:34 +08:00
|
|
|
s3err.WriteErrorResponse(w, r, err)
|
2021-10-11 18:03:56 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-09-25 23:34:12 +08:00
|
|
|
amzAccountId := r.Header.Get(s3_constants.AmzAccountId)
|
|
|
|
amzDisplayName := s3a.iam.GetAccountNameById(amzAccountId)
|
2023-09-21 23:20:05 +08:00
|
|
|
response := AccessControlPolicy{
|
|
|
|
Owner: CanonicalUser{
|
2023-09-25 23:34:12 +08:00
|
|
|
ID: amzAccountId,
|
|
|
|
DisplayName: amzDisplayName,
|
2023-09-21 23:20:05 +08:00
|
|
|
},
|
|
|
|
}
|
|
|
|
response.AccessControlList.Grant = append(response.AccessControlList.Grant, Grant{
|
|
|
|
Grantee: Grantee{
|
2023-09-25 23:34:12 +08:00
|
|
|
ID: amzAccountId,
|
|
|
|
DisplayName: amzDisplayName,
|
2023-09-21 23:20:05 +08:00
|
|
|
Type: "CanonicalUser",
|
|
|
|
XMLXSI: "CanonicalUser",
|
|
|
|
XMLNS: "http://www.w3.org/2001/XMLSchema-instance"},
|
|
|
|
Permission: s3.PermissionFullControl,
|
|
|
|
})
|
|
|
|
writeSuccessResponseXML(w, r, response)
|
|
|
|
}
|
|
|
|
|
|
|
|
// PutBucketAclHandler Put bucket ACL only responds success if the ACL is private.
|
|
|
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketAcl.html //
|
|
|
|
func (s3a *S3ApiServer) PutBucketAclHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
// collect parameters
|
|
|
|
bucket, _ := s3_constants.GetBucketAndObject(r)
|
|
|
|
glog.V(3).Infof("PutBucketAclHandler %s", bucket)
|
|
|
|
|
|
|
|
if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
|
|
|
|
s3err.WriteErrorResponse(w, r, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
cannedAcl := r.Header.Get(s3_constants.AmzCannedAcl)
|
|
|
|
switch {
|
|
|
|
case cannedAcl == "":
|
|
|
|
acl := &s3.AccessControlPolicy{}
|
|
|
|
if err := xmlDecoder(r.Body, acl, r.ContentLength); err != nil {
|
|
|
|
glog.Errorf("PutBucketAclHandler: %s", err)
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
|
|
|
|
return
|
2021-10-11 18:03:56 +08:00
|
|
|
}
|
2023-09-21 23:20:05 +08:00
|
|
|
if len(acl.Grants) == 1 && acl.Grants[0].Permission != nil && *acl.Grants[0].Permission == s3_constants.PermissionFullControl {
|
|
|
|
writeSuccessResponseEmpty(w, r)
|
|
|
|
return
|
2021-10-11 18:03:56 +08:00
|
|
|
}
|
2023-09-21 23:20:05 +08:00
|
|
|
case cannedAcl == s3_constants.CannedAclPrivate:
|
|
|
|
writeSuccessResponseEmpty(w, r)
|
|
|
|
return
|
2021-10-11 18:03:56 +08:00
|
|
|
}
|
2023-09-21 23:20:05 +08:00
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrNotImplemented)
|
2021-10-11 18:03:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetBucketLifecycleConfigurationHandler Get Bucket Lifecycle configuration
|
|
|
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLifecycleConfiguration.html
|
|
|
|
func (s3a *S3ApiServer) GetBucketLifecycleConfigurationHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
// collect parameters
|
2022-05-31 13:57:41 +08:00
|
|
|
bucket, _ := s3_constants.GetBucketAndObject(r)
|
2022-02-25 13:41:20 +08:00
|
|
|
glog.V(3).Infof("GetBucketLifecycleConfigurationHandler %s", bucket)
|
2021-10-11 18:03:56 +08:00
|
|
|
|
|
|
|
if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
|
2021-11-01 09:05:34 +08:00
|
|
|
s3err.WriteErrorResponse(w, r, err)
|
2021-10-11 18:03:56 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
fc, err := filer.ReadFilerConf(s3a.option.Filer, s3a.option.GrpcDialOption, nil)
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("GetBucketLifecycleConfigurationHandler: %s", err)
|
2021-11-01 09:05:34 +08:00
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
2021-10-11 18:03:56 +08:00
|
|
|
return
|
|
|
|
}
|
2023-05-17 00:39:43 +08:00
|
|
|
ttls := fc.GetCollectionTtls(s3a.getCollectionName(bucket))
|
2021-10-14 04:35:33 +08:00
|
|
|
if len(ttls) == 0 {
|
2021-11-01 09:05:34 +08:00
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchLifecycleConfiguration)
|
2021-10-28 22:34:37 +08:00
|
|
|
return
|
2021-10-14 04:35:33 +08:00
|
|
|
}
|
2024-04-27 22:39:22 +08:00
|
|
|
|
2021-10-14 04:35:33 +08:00
|
|
|
response := Lifecycle{}
|
2024-04-27 22:39:22 +08:00
|
|
|
for locationPrefix, internalTtl := range ttls {
|
2021-10-14 04:35:33 +08:00
|
|
|
ttl, _ := needle.ReadTTL(internalTtl)
|
|
|
|
days := int(ttl.Minutes() / 60 / 24)
|
|
|
|
if days == 0 {
|
|
|
|
continue
|
|
|
|
}
|
2024-04-27 22:39:22 +08:00
|
|
|
prefix, found := strings.CutPrefix(locationPrefix, fmt.Sprintf("%s/%s/", s3a.option.BucketsPath, bucket))
|
|
|
|
if !found {
|
|
|
|
continue
|
|
|
|
}
|
2021-10-14 04:35:33 +08:00
|
|
|
response.Rules = append(response.Rules, Rule{
|
2024-04-27 22:39:22 +08:00
|
|
|
ID: prefix,
|
|
|
|
Status: Enabled,
|
|
|
|
Prefix: Prefix{val: prefix, set: true},
|
2021-10-14 04:35:33 +08:00
|
|
|
Expiration: Expiration{Days: days, set: true},
|
|
|
|
})
|
|
|
|
}
|
2024-04-27 22:39:22 +08:00
|
|
|
|
2021-11-01 09:02:08 +08:00
|
|
|
writeSuccessResponseXML(w, r, response)
|
2021-10-11 18:03:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// PutBucketLifecycleConfigurationHandler Put Bucket Lifecycle configuration
|
|
|
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketLifecycleConfiguration.html
|
|
|
|
func (s3a *S3ApiServer) PutBucketLifecycleConfigurationHandler(w http.ResponseWriter, r *http.Request) {
|
2024-04-27 22:39:22 +08:00
|
|
|
// collect parameters
|
|
|
|
bucket, _ := s3_constants.GetBucketAndObject(r)
|
|
|
|
glog.V(3).Infof("PutBucketLifecycleConfigurationHandler %s", bucket)
|
2021-10-11 18:03:56 +08:00
|
|
|
|
2024-04-27 22:39:22 +08:00
|
|
|
if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
|
|
|
|
s3err.WriteErrorResponse(w, r, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
lifeCycleConfig := Lifecycle{}
|
|
|
|
if err := xmlDecoder(r.Body, &lifeCycleConfig, r.ContentLength); err != nil {
|
|
|
|
glog.Warningf("PutBucketLifecycleConfigurationHandler xml decode: %s", err)
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrMalformedXML)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
fc, err := filer.ReadFilerConf(s3a.option.Filer, s3a.option.GrpcDialOption, nil)
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("PutBucketLifecycleConfigurationHandler read filer config: %s", err)
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
collectionName := s3a.getCollectionName(bucket)
|
|
|
|
collectionTtls := fc.GetCollectionTtls(collectionName)
|
|
|
|
changed := false
|
|
|
|
|
|
|
|
for _, rule := range lifeCycleConfig.Rules {
|
|
|
|
if rule.Status != Enabled {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
var rulePrefix string
|
|
|
|
switch {
|
|
|
|
case rule.Filter.Prefix.set:
|
|
|
|
rulePrefix = rule.Filter.Prefix.val
|
|
|
|
case rule.Prefix.set:
|
|
|
|
rulePrefix = rule.Prefix.val
|
|
|
|
case !rule.Expiration.Date.IsZero() || rule.Transition.Days > 0 || !rule.Transition.Date.IsZero():
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrNotImplemented)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if rule.Expiration.Days == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
locConf := &filer_pb.FilerConf_PathConf{
|
|
|
|
LocationPrefix: fmt.Sprintf("%s/%s/%s", s3a.option.BucketsPath, bucket, rulePrefix),
|
|
|
|
Collection: collectionName,
|
|
|
|
Ttl: fmt.Sprintf("%dd", rule.Expiration.Days),
|
|
|
|
}
|
|
|
|
if ttl, ok := collectionTtls[locConf.LocationPrefix]; ok && ttl == locConf.Ttl {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if err := fc.AddLocationConf(locConf); err != nil {
|
|
|
|
glog.Errorf("PutBucketLifecycleConfigurationHandler add location config: %s", err)
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
changed = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if changed {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
if err := fc.ToText(&buf); err != nil {
|
|
|
|
glog.Errorf("PutBucketLifecycleConfigurationHandler save config to text: %s", err)
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
|
|
|
}
|
|
|
|
if err := s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
|
|
|
return filer.SaveInsideFiler(client, filer.DirectoryEtcSeaweedFS, filer.FilerConfName, buf.Bytes())
|
|
|
|
}); err != nil {
|
|
|
|
glog.Errorf("PutBucketLifecycleConfigurationHandler save config inside filer: %s", err)
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2021-10-11 18:03:56 +08:00
|
|
|
|
2024-04-27 22:39:22 +08:00
|
|
|
writeSuccessResponseEmpty(w, r)
|
2021-10-11 18:03:56 +08:00
|
|
|
}
|
|
|
|
|
2024-04-08 22:19:02 +08:00
|
|
|
// DeleteBucketLifecycleHandler Delete Bucket Lifecycle
|
2021-10-11 18:03:56 +08:00
|
|
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketLifecycle.html
|
|
|
|
func (s3a *S3ApiServer) DeleteBucketLifecycleHandler(w http.ResponseWriter, r *http.Request) {
|
2024-04-27 22:39:22 +08:00
|
|
|
// collect parameters
|
|
|
|
bucket, _ := s3_constants.GetBucketAndObject(r)
|
|
|
|
glog.V(3).Infof("DeleteBucketLifecycleHandler %s", bucket)
|
2021-10-11 18:03:56 +08:00
|
|
|
|
2024-04-27 22:39:22 +08:00
|
|
|
if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
|
|
|
|
s3err.WriteErrorResponse(w, r, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
fc, err := filer.ReadFilerConf(s3a.option.Filer, s3a.option.GrpcDialOption, nil)
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("DeleteBucketLifecycleHandler read filer config: %s", err)
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
collectionTtls := fc.GetCollectionTtls(s3a.getCollectionName(bucket))
|
|
|
|
changed := false
|
|
|
|
for prefix, ttl := range collectionTtls {
|
|
|
|
bucketPrefix := fmt.Sprintf("%s/%s/", s3a.option.BucketsPath, bucket)
|
|
|
|
if strings.HasPrefix(prefix, bucketPrefix) && strings.HasSuffix(ttl, "d") {
|
|
|
|
fc.DeleteLocationConf(prefix)
|
|
|
|
changed = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if changed {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
if err := fc.ToText(&buf); err != nil {
|
|
|
|
glog.Errorf("DeleteBucketLifecycleHandler save config to text: %s", err)
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
|
|
|
}
|
|
|
|
if err := s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
|
|
|
return filer.SaveInsideFiler(client, filer.DirectoryEtcSeaweedFS, filer.FilerConfName, buf.Bytes())
|
|
|
|
}); err != nil {
|
|
|
|
glog.Errorf("DeleteBucketLifecycleHandler save config inside filer: %s", err)
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2021-10-11 18:03:56 +08:00
|
|
|
|
2024-04-27 22:39:22 +08:00
|
|
|
s3err.WriteEmptyResponse(w, r, http.StatusNoContent)
|
2021-10-11 18:03:56 +08:00
|
|
|
}
|
2022-02-03 22:17:05 +08:00
|
|
|
|
|
|
|
// GetBucketLocationHandler Get bucket location
|
|
|
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLocation.html
|
|
|
|
func (s3a *S3ApiServer) GetBucketLocationHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
writeSuccessResponseXML(w, r, LocationConstraint{})
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetBucketRequestPaymentHandler Get bucket location
|
|
|
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketRequestPayment.html
|
|
|
|
func (s3a *S3ApiServer) GetBucketRequestPaymentHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
writeSuccessResponseXML(w, r, RequestPaymentConfiguration{Payer: "BucketOwner"})
|
|
|
|
}
|
2022-10-02 10:18:00 +08:00
|
|
|
|
|
|
|
// PutBucketOwnershipControls https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketOwnershipControls.html
|
|
|
|
func (s3a *S3ApiServer) PutBucketOwnershipControls(w http.ResponseWriter, r *http.Request) {
|
|
|
|
bucket, _ := s3_constants.GetBucketAndObject(r)
|
|
|
|
glog.V(3).Infof("PutBucketOwnershipControls %s", bucket)
|
|
|
|
|
|
|
|
errCode := s3a.checkAccessByOwnership(r, bucket)
|
|
|
|
if errCode != s3err.ErrNone {
|
|
|
|
s3err.WriteErrorResponse(w, r, errCode)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.Body == nil || r.Body == http.NoBody {
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var v s3.OwnershipControls
|
|
|
|
defer util.CloseRequest(r)
|
|
|
|
|
|
|
|
err := xmlutil.UnmarshalXML(&v, xml.NewDecoder(r.Body), "")
|
|
|
|
if err != nil {
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(v.Rules) != 1 {
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
printOwnership := true
|
|
|
|
ownership := *v.Rules[0].ObjectOwnership
|
|
|
|
switch ownership {
|
|
|
|
case s3_constants.OwnershipObjectWriter:
|
|
|
|
case s3_constants.OwnershipBucketOwnerPreferred:
|
|
|
|
case s3_constants.OwnershipBucketOwnerEnforced:
|
|
|
|
printOwnership = false
|
|
|
|
default:
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
bucketEntry, err := s3a.getEntry(s3a.option.BucketsPath, bucket)
|
|
|
|
if err != nil {
|
|
|
|
if err == filer_pb.ErrNotFound {
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
oldOwnership, ok := bucketEntry.Extended[s3_constants.ExtOwnershipKey]
|
|
|
|
if !ok || string(oldOwnership) != ownership {
|
|
|
|
if bucketEntry.Extended == nil {
|
|
|
|
bucketEntry.Extended = make(map[string][]byte)
|
|
|
|
}
|
|
|
|
bucketEntry.Extended[s3_constants.ExtOwnershipKey] = []byte(ownership)
|
|
|
|
err = s3a.updateEntry(s3a.option.BucketsPath, bucketEntry)
|
|
|
|
if err != nil {
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if printOwnership {
|
|
|
|
result := &s3.PutBucketOwnershipControlsInput{
|
|
|
|
OwnershipControls: &v,
|
|
|
|
}
|
|
|
|
s3err.WriteAwsXMLResponse(w, r, http.StatusOK, result)
|
|
|
|
} else {
|
|
|
|
writeSuccessResponseEmpty(w, r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetBucketOwnershipControls https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketOwnershipControls.html
|
|
|
|
func (s3a *S3ApiServer) GetBucketOwnershipControls(w http.ResponseWriter, r *http.Request) {
|
|
|
|
bucket, _ := s3_constants.GetBucketAndObject(r)
|
|
|
|
glog.V(3).Infof("GetBucketOwnershipControls %s", bucket)
|
|
|
|
|
|
|
|
errCode := s3a.checkAccessByOwnership(r, bucket)
|
|
|
|
if errCode != s3err.ErrNone {
|
|
|
|
s3err.WriteErrorResponse(w, r, errCode)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
bucketEntry, err := s3a.getEntry(s3a.option.BucketsPath, bucket)
|
|
|
|
if err != nil {
|
|
|
|
if err == filer_pb.ErrNotFound {
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
v, ok := bucketEntry.Extended[s3_constants.ExtOwnershipKey]
|
|
|
|
if !ok {
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.OwnershipControlsNotFoundError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ownership := string(v)
|
|
|
|
|
|
|
|
result := &s3.PutBucketOwnershipControlsInput{
|
|
|
|
OwnershipControls: &s3.OwnershipControls{
|
|
|
|
Rules: []*s3.OwnershipControlsRule{
|
|
|
|
{
|
|
|
|
ObjectOwnership: &ownership,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
s3err.WriteAwsXMLResponse(w, r, http.StatusOK, result)
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteBucketOwnershipControls https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketOwnershipControls.html
|
|
|
|
func (s3a *S3ApiServer) DeleteBucketOwnershipControls(w http.ResponseWriter, r *http.Request) {
|
|
|
|
bucket, _ := s3_constants.GetBucketAndObject(r)
|
|
|
|
glog.V(3).Infof("PutBucketOwnershipControls %s", bucket)
|
|
|
|
|
|
|
|
errCode := s3a.checkAccessByOwnership(r, bucket)
|
|
|
|
if errCode != s3err.ErrNone {
|
|
|
|
s3err.WriteErrorResponse(w, r, errCode)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
bucketEntry, err := s3a.getEntry(s3a.option.BucketsPath, bucket)
|
|
|
|
if err != nil {
|
|
|
|
if err == filer_pb.ErrNotFound {
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
_, ok := bucketEntry.Extended[s3_constants.ExtOwnershipKey]
|
|
|
|
if !ok {
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.OwnershipControlsNotFoundError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
delete(bucketEntry.Extended, s3_constants.ExtOwnershipKey)
|
|
|
|
err = s3a.updateEntry(s3a.option.BucketsPath, bucketEntry)
|
|
|
|
if err != nil {
|
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
emptyOwnershipControls := &s3.OwnershipControls{
|
|
|
|
Rules: []*s3.OwnershipControlsRule{},
|
|
|
|
}
|
|
|
|
s3err.WriteAwsXMLResponse(w, r, http.StatusOK, emptyOwnershipControls)
|
|
|
|
}
|
2023-11-13 18:25:17 +08:00
|
|
|
|
|
|
|
// GetBucketVersioningHandler Get Bucket Versioning status
|
|
|
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketVersioning.html
|
|
|
|
func (s3a *S3ApiServer) GetBucketVersioningHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
bucket, _ := s3_constants.GetBucketAndObject(r)
|
|
|
|
glog.V(3).Infof("GetBucketVersioning %s", bucket)
|
|
|
|
|
|
|
|
if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
|
|
|
|
s3err.WriteErrorResponse(w, r, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-01-04 04:27:13 +08:00
|
|
|
s3err.WriteAwsXMLResponse(w, r, http.StatusOK, &s3.PutBucketVersioningInput{
|
|
|
|
VersioningConfiguration: &s3.VersioningConfiguration{
|
|
|
|
Status: aws.String(s3.BucketVersioningStatusSuspended),
|
|
|
|
},
|
|
|
|
})
|
2023-11-13 18:25:17 +08:00
|
|
|
}
|