mirror of
https://github.com/versity/versitygw.git
synced 2026-01-25 04:22:02 +00:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e46e4e941b | ||
|
|
c9653cff71 | ||
|
|
48798c9e39 | ||
|
|
42c4ad3b9e | ||
|
|
1874d3c329 | ||
|
|
9f0c9badba | ||
|
|
cb6b60324c | ||
|
|
c53707d4ae | ||
|
|
ee1ab5bdcc | ||
|
|
7a0c4423e4 | ||
|
|
8382911ab6 | ||
|
|
4e7615b4fd | ||
|
|
8951cce6d0 | ||
|
|
363c82971a | ||
|
|
cf1c44969b | ||
|
|
37b5429468 | ||
|
|
c9475adb04 | ||
|
|
b00819ff31 | ||
|
|
5ab38e3dab | ||
|
|
c04f6d7f00 | ||
|
|
6ac69b3198 | ||
|
|
c72686f7fa | ||
|
|
145c2dd4e3 | ||
|
|
f90562fea2 | ||
|
|
b0b22467cc | ||
|
|
b2247e20ee | ||
|
|
8017b0cff0 | ||
|
|
c1b105d928 | ||
|
|
57c7518864 | ||
|
|
5a94a70212 | ||
|
|
064230108f | ||
|
|
51680d445c | ||
|
|
732e92a72f | ||
|
|
36aea696c6 | ||
|
|
46c0762133 | ||
|
|
8d6b5c387f | ||
|
|
a241e6a7e6 | ||
|
|
c690b01a90 | ||
|
|
0d044c2303 | ||
|
|
42270fbe1c | ||
|
|
35fe6d8dee | ||
|
|
23b5eb30ed | ||
|
|
24309ae25a | ||
|
|
f74179d01c | ||
|
|
1959fac8a0 | ||
|
|
eb05f5a93e | ||
|
|
23c26d802c |
10
.github/dependabot.yml
vendored
Normal file
10
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
groups:
|
||||
dev-dependencies:
|
||||
patterns:
|
||||
- "*"
|
||||
@@ -45,6 +45,7 @@ changelog:
|
||||
exclude:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
- '^Merge '
|
||||
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
|
||||
|
||||
@@ -105,7 +105,6 @@ func UpdateACL(input *s3.PutBucketAclInput, acl ACL, iam IAMService) ([]byte, er
|
||||
|
||||
if *input.GrantFullControl != "" {
|
||||
fullControlList = splitUnique(*input.GrantFullControl, ",")
|
||||
fmt.Println(fullControlList)
|
||||
for _, str := range fullControlList {
|
||||
grantees = append(grantees, Grantee{Access: str, Permission: "FULL_CONTROL"})
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ type Backend interface {
|
||||
|
||||
// non AWS actions
|
||||
ChangeBucketOwner(_ context.Context, bucket, newOwner string) error
|
||||
ListBucketsAndOwners(context.Context) ([]s3response.Bucket, error)
|
||||
}
|
||||
|
||||
type BackendUnsupported struct{}
|
||||
@@ -178,3 +179,6 @@ func (BackendUnsupported) RemoveTags(_ context.Context, bucket, object string) e
|
||||
func (BackendUnsupported) ChangeBucketOwner(_ context.Context, bucket, newOwner string) error {
|
||||
return s3err.GetAPIError(s3err.ErrNotImplemented)
|
||||
}
|
||||
func (BackendUnsupported) ListBucketsAndOwners(context.Context) ([]s3response.Bucket, error) {
|
||||
return []s3response.Bucket{}, s3err.GetAPIError(s3err.ErrNotImplemented)
|
||||
}
|
||||
|
||||
@@ -413,7 +413,6 @@ func loadUserMetaData(path string, m map[string]string) (contentType, contentEnc
|
||||
if err != nil || len(ents) == 0 {
|
||||
return
|
||||
}
|
||||
fmt.Println(ents)
|
||||
for _, e := range ents {
|
||||
if !isValidMeta(e) {
|
||||
continue
|
||||
@@ -426,7 +425,6 @@ func loadUserMetaData(path string, m map[string]string) (contentType, contentEnc
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
fmt.Println(b)
|
||||
m[strings.TrimPrefix(e, fmt.Sprintf("user.%v.", metaHdr))] = string(b)
|
||||
}
|
||||
|
||||
@@ -884,7 +882,7 @@ func (p *Posix) UploadPartCopy(_ context.Context, upi *s3.UploadPartCopyInput) (
|
||||
}
|
||||
|
||||
if startOffset+length > fi.Size()+1 {
|
||||
return s3response.CopyObjectResult{}, s3err.GetAPIError(s3err.ErrInvalidRequest)
|
||||
return s3response.CopyObjectResult{}, s3err.GetAPIError(s3err.ErrInvalidRange)
|
||||
}
|
||||
|
||||
f, err := openTmpFile(filepath.Join(*upi.Bucket, objdir),
|
||||
@@ -1168,7 +1166,6 @@ func (p *Posix) GetObject(_ context.Context, input *s3.GetObjectInput, writer io
|
||||
userMetaData := make(map[string]string)
|
||||
|
||||
contentType, contentEncoding := loadUserMetaData(objPath, userMetaData)
|
||||
fmt.Println(userMetaData)
|
||||
|
||||
b, err := xattr.Get(objPath, etagkey)
|
||||
etag := string(b)
|
||||
@@ -1241,6 +1238,11 @@ func (p *Posix) CopyObject(ctx context.Context, input *s3.CopyObjectInput) (*s3.
|
||||
}
|
||||
dstBucket := *input.Bucket
|
||||
dstObject := *input.Key
|
||||
owner := *input.ExpectedBucketOwner
|
||||
|
||||
if fmt.Sprintf("%v/%v", srcBucket, srcObject) == fmt.Sprintf("%v/%v", dstBucket, dstObject) {
|
||||
return &s3.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrInvalidCopyDest)
|
||||
}
|
||||
|
||||
_, err := os.Stat(srcBucket)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
@@ -1258,6 +1260,22 @@ func (p *Posix) CopyObject(ctx context.Context, input *s3.CopyObjectInput) (*s3.
|
||||
return nil, fmt.Errorf("stat bucket: %w", err)
|
||||
}
|
||||
|
||||
dstBucketACLBytes, err := xattr.Get(dstBucket, aclkey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get dst bucket acl tag: %w", err)
|
||||
}
|
||||
|
||||
var dstBucketACL auth.ACL
|
||||
err = json.Unmarshal(dstBucketACLBytes, &dstBucketACL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse dst bucket acl: %w", err)
|
||||
}
|
||||
|
||||
err = auth.VerifyACL(dstBucketACL, dstBucket, owner, types.PermissionWrite, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objPath := filepath.Join(srcBucket, srcObject)
|
||||
f, err := os.Open(objPath)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
@@ -1566,6 +1584,46 @@ func (p *Posix) ChangeBucketOwner(ctx context.Context, bucket, newOwner string)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Posix) ListBucketsAndOwners(ctx context.Context) (buckets []s3response.Bucket, err error) {
|
||||
entries, err := os.ReadDir(".")
|
||||
if err != nil {
|
||||
return buckets, fmt.Errorf("readdir buckets: %w", err)
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
fi, err := entry.Info()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
aclTag, err := xattr.Get(entry.Name(), aclkey)
|
||||
if err != nil {
|
||||
return buckets, fmt.Errorf("get acl tag: %w", err)
|
||||
}
|
||||
|
||||
var acl auth.ACL
|
||||
err = json.Unmarshal(aclTag, &acl)
|
||||
if err != nil {
|
||||
return buckets, fmt.Errorf("parse acl tag: %w", err)
|
||||
}
|
||||
|
||||
buckets = append(buckets, s3response.Bucket{
|
||||
Name: fi.Name(),
|
||||
Owner: acl.Owner,
|
||||
})
|
||||
}
|
||||
|
||||
sort.SliceStable(buckets, func(i, j int) bool {
|
||||
return buckets[i].Name < buckets[j].Name
|
||||
})
|
||||
|
||||
return buckets, nil
|
||||
}
|
||||
|
||||
const (
|
||||
iamMode = 0600
|
||||
)
|
||||
|
||||
@@ -30,7 +30,6 @@ import (
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||
"github.com/pkg/xattr"
|
||||
"github.com/versity/scoutfs-go"
|
||||
"github.com/versity/versitygw/backend"
|
||||
"github.com/versity/versitygw/backend/posix"
|
||||
"github.com/versity/versitygw/s3err"
|
||||
@@ -188,7 +187,7 @@ func (s *ScoutFS) CompleteMultipartUpload(_ context.Context, input *s3.CompleteM
|
||||
// scoutfs move data is a metadata only operation that moves the data
|
||||
// extent references from the source, appeding to the destination.
|
||||
// this needs to be 4k aligned.
|
||||
err = scoutfs.MoveData(pf, f.f)
|
||||
err = moveData(pf, f.f)
|
||||
pf.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("move blocks part %v: %v", p.PartNumber, err)
|
||||
@@ -392,7 +391,7 @@ func (s *ScoutFS) HeadObject(_ context.Context, input *s3.HeadObjectInput) (*s3.
|
||||
|
||||
// Check if there are any offline exents associated with this file.
|
||||
// If so, we will set storage class to glacier.
|
||||
st, err := scoutfs.StatMore(objPath)
|
||||
st, err := statMore(objPath)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||
}
|
||||
@@ -466,7 +465,7 @@ func (s *ScoutFS) GetObject(_ context.Context, input *s3.GetObjectInput, writer
|
||||
if s.glaciermode {
|
||||
// Check if there are any offline exents associated with this file.
|
||||
// If so, we will return the InvalidObjectState error.
|
||||
st, err := scoutfs.StatMore(objPath)
|
||||
st, err := statMore(objPath)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||
}
|
||||
@@ -666,7 +665,7 @@ func (s *ScoutFS) fileToObj(bucket string) backend.GetObjFunc {
|
||||
if s.glaciermode {
|
||||
// Check if there are any offline exents associated with this file.
|
||||
// If so, we will return the InvalidObjectState error.
|
||||
st, err := scoutfs.StatMore(objPath)
|
||||
st, err := statMore(objPath)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return types.Object{}, backend.ErrSkipObj
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
//go:build linux && amd64
|
||||
|
||||
package scoutfs
|
||||
|
||||
import (
|
||||
@@ -26,6 +28,7 @@ import (
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/versity/scoutfs-go"
|
||||
"github.com/versity/versitygw/backend/posix"
|
||||
)
|
||||
|
||||
@@ -182,3 +185,25 @@ func (tmp *tmpfile) Write(b []byte) (int, error) {
|
||||
func (tmp *tmpfile) cleanup() {
|
||||
tmp.f.Close()
|
||||
}
|
||||
|
||||
func moveData(from *os.File, to *os.File) error {
|
||||
return scoutfs.MoveData(from, to)
|
||||
}
|
||||
|
||||
func statMore(path string) (stat, error) {
|
||||
st, err := scoutfs.StatMore(path)
|
||||
if err != nil {
|
||||
return stat{}, err
|
||||
}
|
||||
var s stat
|
||||
|
||||
s.Meta_seq = st.Meta_seq
|
||||
s.Data_seq = st.Data_seq
|
||||
s.Data_version = st.Data_version
|
||||
s.Online_blocks = st.Online_blocks
|
||||
s.Offline_blocks = st.Offline_blocks
|
||||
s.Crtime_sec = st.Crtime_sec
|
||||
s.Crtime_nsec = st.Crtime_nsec
|
||||
|
||||
return s, nil
|
||||
}
|
||||
@@ -12,6 +12,8 @@
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
//go:build !(linux && amd64)
|
||||
|
||||
package scoutfs
|
||||
|
||||
import (
|
||||
@@ -46,3 +48,11 @@ func (tmp *tmpfile) Write(b []byte) (int, error) {
|
||||
|
||||
func (tmp *tmpfile) cleanup() {
|
||||
}
|
||||
|
||||
func moveData(from *os.File, to *os.File) error {
|
||||
return errNotSupported
|
||||
}
|
||||
|
||||
func statMore(path string) (stat, error) {
|
||||
return stat{}, errNotSupported
|
||||
}
|
||||
@@ -14,35 +14,12 @@
|
||||
|
||||
package scoutfs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func New(rootdir string, opts ...Option) (*ScoutFS, error) {
|
||||
return nil, fmt.Errorf("scoutfs only available on linux")
|
||||
}
|
||||
|
||||
type tmpfile struct {
|
||||
f *os.File
|
||||
}
|
||||
|
||||
var (
|
||||
errNotSupported = errors.New("not supported")
|
||||
)
|
||||
|
||||
func openTmpFile(dir, bucket, obj string, size int64) (*tmpfile, error) {
|
||||
return nil, errNotSupported
|
||||
}
|
||||
|
||||
func (tmp *tmpfile) link() error {
|
||||
return errNotSupported
|
||||
}
|
||||
|
||||
func (tmp *tmpfile) Write(b []byte) (int, error) {
|
||||
return 0, errNotSupported
|
||||
}
|
||||
|
||||
func (tmp *tmpfile) cleanup() {
|
||||
type stat struct {
|
||||
Meta_seq uint64
|
||||
Data_seq uint64
|
||||
Data_version uint64
|
||||
Online_blocks uint64
|
||||
Offline_blocks uint64
|
||||
Crtime_sec uint64
|
||||
Crtime_nsec uint32
|
||||
}
|
||||
@@ -64,8 +64,10 @@ func Walk(fileSystem fs.FS, prefix, delimiter, marker string, max int32, getObj
|
||||
}
|
||||
|
||||
if pastMax {
|
||||
newMarker = path
|
||||
truncated = true
|
||||
if len(objects) != 0 {
|
||||
newMarker = *objects[len(objects)-1].Key
|
||||
truncated = true
|
||||
}
|
||||
return fs.SkipAll
|
||||
}
|
||||
|
||||
@@ -75,8 +77,8 @@ func Walk(fileSystem fs.FS, prefix, delimiter, marker string, max int32, getObj
|
||||
// match this prefix. Make sure to append the / at the end of
|
||||
// directories since this is implied as a directory path name.
|
||||
// If path is a prefix of prefix, then path could still be
|
||||
// building to match. So only skip if path isnt a prefix of prefix
|
||||
// and prefix isnt a prefix of path.
|
||||
// building to match. So only skip if path isn't a prefix of prefix
|
||||
// and prefix isn't a prefix of path.
|
||||
if prefix != "" &&
|
||||
!strings.HasPrefix(path+string(os.PathSeparator), prefix) &&
|
||||
!strings.HasPrefix(prefix, path+string(os.PathSeparator)) {
|
||||
@@ -104,10 +106,10 @@ func Walk(fileSystem fs.FS, prefix, delimiter, marker string, max int32, getObj
|
||||
}
|
||||
|
||||
if !pastMarker {
|
||||
if path != marker {
|
||||
return nil
|
||||
if path == marker {
|
||||
pastMarker = true
|
||||
}
|
||||
pastMarker = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// If object doesn't have prefix, don't include in results.
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/versity/versitygw/auth"
|
||||
"github.com/versity/versitygw/s3response"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -105,6 +106,11 @@ func adminCommand() *cli.Command {
|
||||
},
|
||||
Action: changeBucketOwner,
|
||||
},
|
||||
{
|
||||
Name: "list-buckets",
|
||||
Usage: "Lists all the gateway buckets and owners.",
|
||||
Action: listBuckets,
|
||||
},
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
// TODO: create a configuration file for this
|
||||
@@ -319,3 +325,60 @@ func changeBucketOwner(ctx *cli.Context) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func printBuckets(buckets []s3response.Bucket) {
|
||||
w := new(tabwriter.Writer)
|
||||
w.Init(os.Stdout, minwidth, tabwidth, padding, padchar, flags)
|
||||
fmt.Fprintln(w, "Bucket\tOwner")
|
||||
fmt.Fprintln(w, "-------\t----")
|
||||
for _, acc := range buckets {
|
||||
fmt.Fprintf(w, "%v\t%v\n", acc.Name, acc.Owner)
|
||||
}
|
||||
fmt.Fprintln(w)
|
||||
w.Flush()
|
||||
}
|
||||
|
||||
func listBuckets(ctx *cli.Context) error {
|
||||
req, err := http.NewRequest(http.MethodPatch, fmt.Sprintf("%v/list-buckets", adminEndpoint), 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: adminAccess, SecretAccessKey: adminSecret}, req, hexPayload, "s3", region, 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)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return fmt.Errorf(string(body))
|
||||
}
|
||||
|
||||
var buckets []s3response.Bucket
|
||||
if err := json.Unmarshal(body, &buckets); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printBuckets(buckets)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/urfave/cli/v2"
|
||||
@@ -32,10 +33,11 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
port string
|
||||
port, admPort string
|
||||
rootUserAccess string
|
||||
rootUserSecret string
|
||||
region string
|
||||
admCertFile, admKeyFile string
|
||||
certFile, keyFile string
|
||||
kafkaURL, kafkaTopic, kafkaKey string
|
||||
natsURL, natsTopic string
|
||||
@@ -142,6 +144,22 @@ func initFlags() []cli.Flag {
|
||||
Usage: "TLS key file",
|
||||
Destination: &keyFile,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "admin-port",
|
||||
Usage: "gateway admin server listen address <ip>:<port> or :<port>",
|
||||
Destination: &admPort,
|
||||
Aliases: []string{"ap"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "admin-cert",
|
||||
Usage: "TLS cert file for admin server",
|
||||
Destination: &admCertFile,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "admin-cert-key",
|
||||
Usage: "TLS key file for admin server",
|
||||
Destination: &admKeyFile,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "debug",
|
||||
Usage: "enable debug output",
|
||||
@@ -193,10 +211,17 @@ func initFlags() []cli.Flag {
|
||||
}
|
||||
|
||||
func runGateway(ctx *cli.Context, be backend.Backend, s auth.Storer) error {
|
||||
// int32 max for 32 bit arch
|
||||
blimit := int64(2*1024*1024*1024 - 1)
|
||||
if strconv.IntSize > 32 {
|
||||
// 5GB max for 64 bit arch
|
||||
blimit = int64(5 * 1024 * 1024 * 1024)
|
||||
}
|
||||
|
||||
app := fiber.New(fiber.Config{
|
||||
AppName: "versitygw",
|
||||
ServerHeader: "VERSITYGW",
|
||||
BodyLimit: 5 * 1024 * 1024 * 1024,
|
||||
BodyLimit: int(blimit),
|
||||
})
|
||||
|
||||
var opts []s3api.Option
|
||||
@@ -215,10 +240,34 @@ func runGateway(ctx *cli.Context, be backend.Backend, s auth.Storer) error {
|
||||
}
|
||||
opts = append(opts, s3api.WithTLS(cert))
|
||||
}
|
||||
|
||||
if debug {
|
||||
opts = append(opts, s3api.WithDebug())
|
||||
}
|
||||
if admPort == "" {
|
||||
opts = append(opts, s3api.WithAdminServer())
|
||||
}
|
||||
|
||||
admApp := fiber.New(fiber.Config{
|
||||
AppName: "versitygw",
|
||||
ServerHeader: "VERSITYGW",
|
||||
})
|
||||
|
||||
var admOpts []s3api.AdminOpt
|
||||
|
||||
if admCertFile != "" || admKeyFile != "" {
|
||||
if admCertFile == "" {
|
||||
return fmt.Errorf("TLS key specified without cert file")
|
||||
}
|
||||
if admKeyFile == "" {
|
||||
return fmt.Errorf("TLS cert specified without key file")
|
||||
}
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(admCertFile, admKeyFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("tls: load certs: %v", err)
|
||||
}
|
||||
admOpts = append(admOpts, s3api.WithAdminSrvTLS(cert))
|
||||
}
|
||||
|
||||
err := s.InitIAM()
|
||||
if err != nil {
|
||||
@@ -257,8 +306,13 @@ func runGateway(ctx *cli.Context, be backend.Backend, s auth.Storer) error {
|
||||
return fmt.Errorf("init gateway: %v", err)
|
||||
}
|
||||
|
||||
c := make(chan error, 1)
|
||||
admSrv := s3api.NewAdminServer(admApp, be, middlewares.RootUserConfig{Access: rootUserAccess, Secret: rootUserSecret}, admPort, region, iam, admOpts...)
|
||||
|
||||
c := make(chan error, 2)
|
||||
go func() { c <- srv.Serve() }()
|
||||
if admPort != "" {
|
||||
go func() { c <- admSrv.Serve() }()
|
||||
}
|
||||
|
||||
// for/select blocks until shutdown
|
||||
Loop:
|
||||
|
||||
48
go.mod
48
go.mod
@@ -3,26 +3,26 @@ module github.com/versity/versitygw
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go-v2 v1.20.0
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.38.1
|
||||
github.com/aws/smithy-go v1.14.0
|
||||
github.com/gofiber/fiber/v2 v2.48.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/nats-io/nats.go v1.28.0
|
||||
github.com/aws/aws-sdk-go-v2 v1.21.0
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.38.5
|
||||
github.com/aws/smithy-go v1.14.2
|
||||
github.com/gofiber/fiber/v2 v2.49.2
|
||||
github.com/google/uuid v1.3.1
|
||||
github.com/nats-io/nats.go v1.29.0
|
||||
github.com/pkg/xattr v0.4.9
|
||||
github.com/segmentio/kafka-go v0.4.42
|
||||
github.com/urfave/cli/v2 v2.25.7
|
||||
github.com/valyala/fasthttp v1.48.0
|
||||
github.com/valyala/fasthttp v1.50.0
|
||||
github.com/versity/scoutfs-go v0.0.0-20230606232754-0474b14343b9
|
||||
golang.org/x/sys v0.10.0
|
||||
golang.org/x/sys v0.12.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.38 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.13.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.21.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.42 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.14.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.16.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.22.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/nats-io/nats-server/v2 v2.9.20 // indirect
|
||||
@@ -36,17 +36,17 @@ require (
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.32
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.31
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.76
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.37 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.31 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.32 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.31 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.40
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.38
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.84
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.36 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.4 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/klauspost/compress v1.16.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
|
||||
96
go.sum
96
go.sum
@@ -1,58 +1,58 @@
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/aws/aws-sdk-go-v2 v1.20.0 h1:INUDpYLt4oiPOJl0XwZDK2OVAVf0Rzo+MGVTv9f+gy8=
|
||||
github.com/aws/aws-sdk-go-v2 v1.20.0/go.mod h1:uWOr0m0jDsiWw8nnXiqZ+YG6LdvAlGYDLLf2NmHZoy4=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.11 h1:/MS8AzqYNAhhRNalOmxUvYs8VEbNGifTnzhPFdcRQkQ=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.11/go.mod h1:va22++AdXht4ccO3kH2SHkHHYvZ2G9Utz+CXKmm2CaU=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.32 h1:tqEOvkbTxwEV7hToRcJ1xZRjcATqwDVsWbAscgRKyNI=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.32/go.mod h1:U3ZF0fQRRA4gnbn9GGvOWLoT2EzzZfAWeKwnVrm1rDc=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.31 h1:vJyON3lG7R8VOErpJJBclBADiWTwzcwdkQpTKx8D2sk=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.31/go.mod h1:T4sESjBtY2lNxLgkIASmeP57b5j7hTQqCbqG0tWnxC4=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.7 h1:X3H6+SU21x+76LRglk21dFRgMTJMa5QcpW+SqUf5BBg=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.7/go.mod h1:3we0V09SwcJBzNlnyovrR2wWJhWmVdqAsmVs4uronv8=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.76 h1:DJ1kHj0GI9BbX+XhF0kHxlzOVjcncmDUXmCvXdbfdAE=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.76/go.mod h1:/AZCdswMSgwpB2yMSFfY5H4pVeBLnCuPehdmO/r3xSM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.37 h1:zr/gxAZkMcvP71ZhQOcvdm8ReLjFgIXnIn0fw5AM7mo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.37/go.mod h1:Pdn4j43v49Kk6+82spO3Tu5gSeQXRsxo56ePPQAvFiA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.31 h1:0HCMIkAkVY9KMgueD8tf4bRTUanzEYvhw7KkPXIMpO0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.31/go.mod h1:fTJDMe8LOFYtqiFFFeHA+SVMAwqLhoq0kcInYoLa9Js=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.38 h1:+i1DOFrW3YZ3apE45tCal9+aDKK6kNEbW6Ib7e1nFxE=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.38/go.mod h1:1/jLp0OgOaWIetycOmycW+vYTYgTZFPttJQRgsI1PoU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.0 h1:U5yySdwt2HPo/pnQec04DImLzWORbeWML1fJiLkKruI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.0/go.mod h1:EhC/83j8/hL/UB1WmExo3gkElaja/KlmZM/gl1rTfjM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.12 h1:uAiiHnWihGP2rVp64fHwzLDrswGjEjsPszwRYMiYQPU=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.12/go.mod h1:fUTHpOXqRQpXvEpDPSa3zxCc2fnpW6YnBoba+eQr+Bg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.32 h1:kvN1jPHr9UffqqG3bSgZ8tx4+1zKVHz/Ktw/BwW6hX8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.32/go.mod h1:QmMEM7es84EUkbYWcpnkx8i5EW2uERPfrTFeOch128Y=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.31 h1:auGDJ0aLZahF5SPvkJ6WcUuX7iQ7kyl2MamV7Tm8QBk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.31/go.mod h1:3+lloe3sZuBQw1aBc5MyndvodzQlyqCZ7x1QPDHaWP4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.0 h1:Wgjft9X4W5pMeuqgPCHIQtbZ87wsgom7S5F8obreg+c=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.0/go.mod h1:FWNzS4+zcWAP05IF7TDYTY1ysZAzIvogxWaDT9p8fsA=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.38.1 h1:mTgFVlfQT8gikc5+/HwD8UL9jnUro5MGv8n/VEYF12I=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.38.1/go.mod h1:6SOWLiobcZZshbmECRTADIRYliPL0etqFSigauQEeT0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.13.1 h1:DSNpSbfEgFXRV+IfEcKE5kTbqxm+MeF5WgyeRlsLnHY=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.13.1/go.mod h1:TC9BubuFMVScIU+TLKamO6VZiYTkYoEHqlSQwAe2omw=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.1 h1:hd0SKLMdOL/Sl6Z0np1PX9LeH2gqNtBe0MhTedA8MGI=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.1/go.mod h1:XO/VcyoQ8nKyKfFW/3DMsRQXsfh/052tHTWmg3xBXRg=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.21.1 h1:pAOJj+80tC8sPVgSDHzMYD6KLWsaLQ1kZw31PTeORbs=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.21.1/go.mod h1:G8SbvL0rFk4WOJroU8tKBczhsbhj2p/YY7qeJezJ3CI=
|
||||
github.com/aws/smithy-go v1.14.0 h1:+X90sB94fizKjDmwb4vyl2cTTPXTE5E2G/1mjByb0io=
|
||||
github.com/aws/smithy-go v1.14.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/aws/aws-sdk-go-v2 v1.21.0 h1:gMT0IW+03wtYJhRqTVYn0wLzwdnK9sRMcxmtfGzRdJc=
|
||||
github.com/aws/aws-sdk-go-v2 v1.21.0/go.mod h1:/RfNgGmRxI+iFOB1OeJUyxiU+9s88k3pfHvDagGEp0M=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13 h1:OPLEkmhXf6xFPiz0bLeDArZIDx1NNS4oJyG4nv3Gct0=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13/go.mod h1:gpAbvyDGQFozTEmlTFO8XcQKHzubdq0LzRyJpG6MiXM=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.40 h1:dbu1llI/nTIL+r6sYHMeVLl99DM8J8/o1I4EPurnhLg=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.40/go.mod h1:JjrCZQwSPGCoZRQzKHyZNNueaKO+kFaEy2sR6mCzd90=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.38 h1:gDAuCdVlA4lmmgQhvpZlscwicloCqH44vkxLklGkQLA=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.38/go.mod h1:sD4G/Ybgp6s89mWIES3Xn97CsRLpxvz9uVSdv0UxY8I=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 h1:uDZJF1hu0EVT/4bogChk8DyjSF6fof6uL/0Y26Ma7Fg=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11/go.mod h1:TEPP4tENqBGO99KwVpV9MlOX4NSrSLP8u3KRy2CDwA8=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.84 h1:LENrVcqnWTyI8fbIUCvxAMe+fXbREIaXzcR8WPwco1U=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.84/go.mod h1:LHxCiYAStsgps4srke7HujyADd504MSkNXjLpOtICTc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 h1:22dGT7PneFMx4+b3pz7lMTRyN8ZKH7M2cW4GP9yUS2g=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41/go.mod h1:CrObHAuPneJBlfEJ5T3szXOUkLEThaGfvnhTf33buas=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 h1:SijA0mgjV8E+8G45ltVHs0fvKpTj8xmZJ3VwhGKtUSI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35/go.mod h1:SJC1nEVVva1g3pHAIdCp7QsRIkMmLAgoDquQ9Rr8kYw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.42 h1:GPUcE/Yq7Ur8YSUk6lVkoIMWnJNO0HT18GUzCWCgCI0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.42/go.mod h1:rzfdUlfA+jdgLDmPKjd3Chq9V7LVLYo1Nz++Wb91aRo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.4 h1:6lJvvkQ9HmbHZ4h/IEwclwv2mrTW8Uq1SOB/kXy0mfw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.4/go.mod h1:1PrKYwxTM+zjpw9Y41KFtoJCQrJ34Z47Y4VgVbfndjo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.14 h1:m0QTSI6pZYJTk5WSKx3fm5cNW/DCicVzULBgU/6IyD0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.14/go.mod h1:dDilntgHy9WnHXsh7dDtUPgHKEfTJIBUTHM8OWm0f/0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.36 h1:eev2yZX7esGRjqRbnVk1UxMLw4CyVZDpZXRCcy75oQk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.36/go.mod h1:lGnOkH9NJATw0XEPcAknFBj3zzNTEGRHtSw+CwC1YTg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 h1:CdzPW9kKitgIiLV1+MHobfR5Xg25iYnyzWZhyQuSlDI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35/go.mod h1:QGF2Rs33W5MaN9gYdEQOBBFPLwTZkEhRwI33f7KIG0o=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.4 h1:v0jkRigbSD6uOdwcaUQmgEwG1BkPfAPDqaeNt/29ghg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.4/go.mod h1:LhTyt8J04LL+9cIt7pYJ5lbS/U98ZmXovLOR/4LUsk8=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.38.5 h1:A42xdtStObqy7NGvzZKpnyNXvoOmm+FENobZ0/ssHWk=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.38.5/go.mod h1:rDGMZA7f4pbmTtPOk5v5UM2lmX6UAbRnMDJeDvnH7AM=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.14.0 h1:AR/hlTsCyk1CwlyKnPFvIMvnONydRjDDRT9OGb0i+/g=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.14.0/go.mod h1:fIAwKQKBFu90pBxx07BFOMJLpRUGu8VOzLJakeY+0K4=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.16.0 h1:vbgiXuhtn49+erlPrgIvQ+J32rg1HseaPf8lEpKbkxQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.16.0/go.mod h1:yygr8ACQRY2PrEcy3xsUI357stq2AxnFM6DIsR9lij4=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.22.0 h1:s4bioTgjSFRwOoyEFzAVCmFmoowBgjTR8gkrF/sQ4wk=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.22.0/go.mod h1:VC7JDqsqiwXukYEDjoHh9U0fOJtNWh04FPQz4ct4GGU=
|
||||
github.com/aws/smithy-go v1.14.2 h1:MJU9hqBGbvWZdApzpvoF2WAIJDbtjK2NDJSiJP7HblQ=
|
||||
github.com/aws/smithy-go v1.14.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gofiber/fiber/v2 v2.48.0 h1:cRVMCb9aUJDsyHxGFLwz/sGzDggdailZZyptU9F9cU0=
|
||||
github.com/gofiber/fiber/v2 v2.48.0/go.mod h1:xqJgfqrc23FJuqGOW6DVgi3HyZEm2Mn9pRqUb2kHSX8=
|
||||
github.com/gofiber/fiber/v2 v2.49.2 h1:ONEN3/Vc+dUCxxDgZZwpqvhISgHqb+bu+isBiEyKEQs=
|
||||
github.com/gofiber/fiber/v2 v2.49.2/go.mod h1:gNsKnyrmfEWFpJxQAV0qvW6l70K1dZGno12oLtukcts=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
@@ -71,8 +71,8 @@ github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA
|
||||
github.com/nats-io/jwt/v2 v2.4.1 h1:Y35W1dgbbz2SQUYDPCaclXcuqleVmpbRa7646Jf2EX4=
|
||||
github.com/nats-io/nats-server/v2 v2.9.20 h1:bt1dW6xsL1hWWwv7Hovm+EJt5L6iplyqlgEFkoEUk0k=
|
||||
github.com/nats-io/nats-server/v2 v2.9.20/go.mod h1:aTb/xtLCGKhfTFLxP591CMWfkdgBmcUUSkiSOe5A3gw=
|
||||
github.com/nats-io/nats.go v1.28.0 h1:Th4G6zdsz2d0OqXdfzKLClo6bOfoI/b1kInhRtFIy5c=
|
||||
github.com/nats-io/nats.go v1.28.0/go.mod h1:XpbWUlOElGwTYbMR7imivs7jJj9GtK7ypv321Wp6pjc=
|
||||
github.com/nats-io/nats.go v1.29.0 h1:dSXZ+SZeGyTdHVYeXimeq12FsIpb9dM8CJ2IZFiHcyE=
|
||||
github.com/nats-io/nats.go v1.29.0/go.mod h1:XpbWUlOElGwTYbMR7imivs7jJj9GtK7ypv321Wp6pjc=
|
||||
github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA=
|
||||
github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
@@ -102,8 +102,8 @@ github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
|
||||
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.48.0 h1:oJWvHb9BIZToTQS3MuQ2R3bJZiNSa2KiNdeI8A+79Tc=
|
||||
github.com/valyala/fasthttp v1.48.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
|
||||
github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M=
|
||||
github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
|
||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
github.com/versity/scoutfs-go v0.0.0-20230606232754-0474b14343b9 h1:ZfmQR01Kk6/kQh6+zlqfBYszVY02fzf9xYrchOY4NFM=
|
||||
@@ -138,8 +138,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
|
||||
@@ -25,6 +25,7 @@ func TestAuthentication(s *S3Conf) {
|
||||
func TestCreateBucket(s *S3Conf) {
|
||||
CreateBucket_invalid_bucket_name(s)
|
||||
CreateBucket_existing_bucket(s)
|
||||
CreateBucket_as_user(s)
|
||||
CreateDeleteBucket_success(s)
|
||||
}
|
||||
|
||||
@@ -71,6 +72,7 @@ func TestListObjects(s *S3Conf) {
|
||||
ListObject_truncated(s)
|
||||
ListObjects_invalid_max_keys(s)
|
||||
ListObjects_max_keys_0(s)
|
||||
ListObjects_delimiter(s)
|
||||
}
|
||||
|
||||
func TestDeleteObject(s *S3Conf) {
|
||||
@@ -86,6 +88,8 @@ func TestDeleteObjects(s *S3Conf) {
|
||||
|
||||
func TestCopyObject(s *S3Conf) {
|
||||
CopyObject_non_existing_dst_bucket(s)
|
||||
CopyObject_not_owned_source_bucket(s)
|
||||
CopyObject_copy_to_itself(s)
|
||||
CopyObject_success(s)
|
||||
}
|
||||
|
||||
@@ -127,6 +131,7 @@ func TestUploadPartCopy(s *S3Conf) {
|
||||
UploadPartCopy_non_existing_source_object_key(s)
|
||||
UploadPartCopy_success(s)
|
||||
UploadPartCopy_by_range_invalid_range(s)
|
||||
UploadPartCopy_greater_range_than_obj_size(s)
|
||||
UploadPartCopy_by_range_success(s)
|
||||
}
|
||||
|
||||
|
||||
@@ -656,6 +656,32 @@ func CreateBucket_invalid_bucket_name(s *S3Conf) {
|
||||
passF(testName)
|
||||
}
|
||||
|
||||
func CreateBucket_as_user(s *S3Conf) {
|
||||
testName := "CreateBucket_as_user"
|
||||
runF(testName)
|
||||
usr := user{
|
||||
access: "grt1",
|
||||
secret: "grt1secret",
|
||||
role: "user",
|
||||
}
|
||||
cfg := *s
|
||||
cfg.awsID = usr.access
|
||||
cfg.awsSecret = usr.secret
|
||||
err := createUsers(s, []user{usr})
|
||||
if err != nil {
|
||||
failF("%v: %v", testName, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = setup(&cfg, getBucketName())
|
||||
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil {
|
||||
failF("%v: %v", testName, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
passF(testName)
|
||||
}
|
||||
|
||||
func CreateBucket_existing_bucket(s *S3Conf) {
|
||||
testName := "CreateBucket_existing_bucket"
|
||||
runF(testName)
|
||||
@@ -1321,7 +1347,7 @@ func ListObject_truncated(s *S3Conf) {
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
out, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{
|
||||
out1, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{
|
||||
Bucket: &bucket,
|
||||
MaxKeys: maxKeys,
|
||||
})
|
||||
@@ -1330,35 +1356,41 @@ func ListObject_truncated(s *S3Conf) {
|
||||
return err
|
||||
}
|
||||
|
||||
if !out.IsTruncated {
|
||||
return fmt.Errorf("expected output to be truncated")
|
||||
if !out1.IsTruncated {
|
||||
return fmt.Errorf("expected out1put to be truncated")
|
||||
}
|
||||
|
||||
if out.MaxKeys != maxKeys {
|
||||
return fmt.Errorf("expected max-keys to be %v, instead got %v", maxKeys, out.MaxKeys)
|
||||
if out1.MaxKeys != maxKeys {
|
||||
return fmt.Errorf("expected max-keys to be %v, instead got %v", maxKeys, out1.MaxKeys)
|
||||
}
|
||||
|
||||
if !compareObjects([]string{"bar", "baz"}, out.Contents) {
|
||||
if *out1.NextMarker != "baz" {
|
||||
return fmt.Errorf("expected nex-marker to be baz, instead got %v", *out1.NextMarker)
|
||||
}
|
||||
|
||||
if !compareObjects([]string{"bar", "baz"}, out1.Contents) {
|
||||
return fmt.Errorf("unexpected output for list objects with max-keys")
|
||||
}
|
||||
|
||||
//TODO: Add next marker checker after bug-fixing
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||
out, err = s3client.ListObjects(ctx, &s3.ListObjectsInput{
|
||||
out2, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{
|
||||
Bucket: &bucket,
|
||||
Marker: out.NextMarker,
|
||||
Marker: out1.NextMarker,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if out.IsTruncated {
|
||||
if out2.IsTruncated {
|
||||
return fmt.Errorf("expected output not to be truncated")
|
||||
}
|
||||
|
||||
if !compareObjects([]string{"foo"}, out.Contents) {
|
||||
if *out2.Marker != *out1.NextMarker {
|
||||
return fmt.Errorf("expected marker to be %v, instead got %v", *out1.NextMarker, *out2.Marker)
|
||||
}
|
||||
|
||||
if !compareObjects([]string{"foo"}, out2.Contents) {
|
||||
return fmt.Errorf("unexpected output for list objects with max-keys")
|
||||
}
|
||||
return nil
|
||||
@@ -1408,7 +1440,38 @@ func ListObjects_max_keys_0(s *S3Conf) {
|
||||
})
|
||||
}
|
||||
|
||||
//TODO: Add a test case for delimiter after bug-fixing, as delimiter doesn't work as intended
|
||||
func ListObjects_delimiter(s *S3Conf) {
|
||||
testName := "ListObjects_delimiter"
|
||||
actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
err := putObjects(s3client, []string{"foo/bar/baz", "foo/bar/xyzzy", "quux/thud", "asdf"}, bucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
out, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{
|
||||
Bucket: &bucket,
|
||||
Delimiter: getPtr("/"),
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if *out.Delimiter != "/" {
|
||||
return fmt.Errorf("expected delimiter to be /, instead got %v", *out.Delimiter)
|
||||
}
|
||||
if len(out.Contents) != 1 || *out.Contents[0].Key != "asdf" {
|
||||
return fmt.Errorf("expected result [\"asdf\"], instead got %v", out.Contents)
|
||||
}
|
||||
|
||||
if !comparePrefixes([]string{"foo/", "quux/"}, out.CommonPrefixes) {
|
||||
return fmt.Errorf("expected common prefixes to be %v, instead got %v", []string{"foo/", "quux/"}, out.CommonPrefixes)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func DeleteObject_non_existing_object(s *S3Conf) {
|
||||
testName := "DeleteObject_non_existing_object"
|
||||
@@ -1613,6 +1676,72 @@ func CopyObject_non_existing_dst_bucket(s *S3Conf) {
|
||||
})
|
||||
}
|
||||
|
||||
func CopyObject_not_owned_source_bucket(s *S3Conf) {
|
||||
testName := "CopyObject_not_owned_source_bucket"
|
||||
actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
srcObj := "my-obj"
|
||||
err := putObjects(s3client, []string{srcObj}, bucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
usr := user{
|
||||
access: "admin1",
|
||||
secret: "admin1secret",
|
||||
role: "admin",
|
||||
}
|
||||
|
||||
cfg := *s
|
||||
cfg.awsID = usr.access
|
||||
cfg.awsSecret = usr.secret
|
||||
|
||||
err = createUsers(s, []user{usr})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dstBucket := getBucketName()
|
||||
err = setup(&cfg, dstBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{
|
||||
Bucket: &dstBucket,
|
||||
Key: getPtr("obj-1"),
|
||||
CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, srcObj)),
|
||||
})
|
||||
cancel()
|
||||
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func CopyObject_copy_to_itself(s *S3Conf) {
|
||||
testName := "CopyObject_copy_to_itself"
|
||||
actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "my-obj"
|
||||
err := putObjects(s3client, []string{obj}, bucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, obj)),
|
||||
})
|
||||
cancel()
|
||||
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidCopyDest)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func CopyObject_success(s *S3Conf) {
|
||||
testName := "CopyObject_success"
|
||||
actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
@@ -2297,6 +2426,46 @@ func UploadPartCopy_by_range_invalid_range(s *S3Conf) {
|
||||
})
|
||||
}
|
||||
|
||||
func UploadPartCopy_greater_range_than_obj_size(s *S3Conf) {
|
||||
testName := "UploadPartCopy_greater_range_than_obj_size"
|
||||
actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj, srcBucket, srcObj := "my-obj", getBucketName(), "src-obj"
|
||||
err := setup(s, srcBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
srcObjSize := 5 * 1024 * 1024
|
||||
_, _, err = putObjectWithData(int64(srcObjSize), &s3.PutObjectInput{
|
||||
Bucket: &srcBucket,
|
||||
Key: &srcObj,
|
||||
}, s3client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out, err := CreateMp(s3client, bucket, obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{
|
||||
Bucket: &bucket,
|
||||
CopySource: getPtr(srcBucket + "/" + srcObj),
|
||||
UploadId: out.UploadId,
|
||||
Key: &obj,
|
||||
CopySourceRange: getPtr(fmt.Sprintf("bytes=0-%v", srcObjSize+50)), // The specified range is greater than the actual object size
|
||||
PartNumber: 1,
|
||||
})
|
||||
cancel()
|
||||
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidRange)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func UploadPartCopy_by_range_success(s *S3Conf) {
|
||||
testName := "UploadPartCopy_by_range_success"
|
||||
actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
|
||||
@@ -401,6 +401,26 @@ func compareObjects(list1 []string, list2 []types.Object) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func comparePrefixes(list1 []string, list2 []types.CommonPrefix) bool {
|
||||
if len(list1) != len(list2) {
|
||||
return false
|
||||
}
|
||||
|
||||
elementMap := make(map[string]bool)
|
||||
|
||||
for _, elem := range list1 {
|
||||
elementMap[elem] = true
|
||||
}
|
||||
|
||||
for _, elem := range list2 {
|
||||
if _, found := elementMap[*elem.Prefix]; !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func compareDelObjects(list1 []string, list2 []types.DeletedObject) bool {
|
||||
if len(list1) != len(list2) {
|
||||
return false
|
||||
|
||||
43
s3api/admin-router.go
Normal file
43
s3api/admin-router.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// 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 s3api
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/versity/versitygw/auth"
|
||||
"github.com/versity/versitygw/backend"
|
||||
"github.com/versity/versitygw/s3api/controllers"
|
||||
)
|
||||
|
||||
type S3AdminRouter struct{}
|
||||
|
||||
func (ar *S3AdminRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMService) {
|
||||
controller := controllers.NewAdminController(iam, be)
|
||||
|
||||
// CreateUser admin api
|
||||
app.Patch("/create-user", controller.CreateUser)
|
||||
|
||||
// DeleteUsers admin api
|
||||
app.Patch("/delete-user", controller.DeleteUser)
|
||||
|
||||
// ListUsers admin api
|
||||
app.Patch("/list-users", controller.ListUsers)
|
||||
|
||||
// ChangeBucketOwner admin api
|
||||
app.Patch("/change-bucket-owner", controller.ChangeBucketOwner)
|
||||
|
||||
// ListBucketsAndOwners admin api
|
||||
app.Patch("/list-buckets", controller.ListBuckets)
|
||||
}
|
||||
72
s3api/admin-server.go
Normal file
72
s3api/admin-server.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// 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 s3api
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||
"github.com/versity/versitygw/auth"
|
||||
"github.com/versity/versitygw/backend"
|
||||
"github.com/versity/versitygw/s3api/middlewares"
|
||||
)
|
||||
|
||||
type S3AdminServer struct {
|
||||
app *fiber.App
|
||||
backend backend.Backend
|
||||
router *S3AdminRouter
|
||||
port string
|
||||
cert *tls.Certificate
|
||||
}
|
||||
|
||||
func NewAdminServer(app *fiber.App, be backend.Backend, root middlewares.RootUserConfig, port, region string, iam auth.IAMService, opts ...AdminOpt) *S3AdminServer {
|
||||
server := &S3AdminServer{
|
||||
app: app,
|
||||
backend: be,
|
||||
router: new(S3AdminRouter),
|
||||
port: port,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(server)
|
||||
}
|
||||
|
||||
// Logging middlewares
|
||||
app.Use(logger.New())
|
||||
app.Use(middlewares.DecodeURL(nil))
|
||||
|
||||
// Authentication middlewares
|
||||
app.Use(middlewares.VerifyV4Signature(root, iam, nil, region, false))
|
||||
app.Use(middlewares.VerifyMD5Body(nil))
|
||||
app.Use(middlewares.AclParser(be, nil))
|
||||
|
||||
server.router.Init(app, be, iam)
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
type AdminOpt func(s *S3AdminServer)
|
||||
|
||||
func WithAdminSrvTLS(cert tls.Certificate) AdminOpt {
|
||||
return func(s *S3AdminServer) { s.cert = &cert }
|
||||
}
|
||||
|
||||
func (sa *S3AdminServer) Serve() (err error) {
|
||||
if sa.cert != nil {
|
||||
return sa.app.ListenTLSWithCertificate(sa.port, *sa.cert)
|
||||
}
|
||||
return sa.app.Listen(sa.port)
|
||||
}
|
||||
@@ -103,3 +103,17 @@ func (c AdminController) ChangeBucketOwner(ctx *fiber.Ctx) error {
|
||||
|
||||
return ctx.Status(201).SendString("Bucket owner has been updated successfully")
|
||||
}
|
||||
|
||||
func (c AdminController) ListBuckets(ctx *fiber.Ctx) error {
|
||||
role := ctx.Locals("role").(string)
|
||||
if role != "admin" {
|
||||
return fmt.Errorf("access denied: only admin users have access to this resource")
|
||||
}
|
||||
|
||||
buckets, err := c.be.ListBucketsAndOwners(ctx.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctx.JSON(buckets)
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/versity/versitygw/auth"
|
||||
"github.com/versity/versitygw/s3response"
|
||||
)
|
||||
|
||||
func TestAdminController_CreateUser(t *testing.T) {
|
||||
@@ -395,3 +396,72 @@ func TestAdminController_ChangeBucketOwner(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminController_ListBuckets(t *testing.T) {
|
||||
type args struct {
|
||||
req *http.Request
|
||||
}
|
||||
adminController := AdminController{
|
||||
be: &BackendMock{
|
||||
ListBucketsAndOwnersFunc: func(contextMoqParam context.Context) ([]s3response.Bucket, error) {
|
||||
return []s3response.Bucket{}, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app := fiber.New()
|
||||
|
||||
app.Use(func(ctx *fiber.Ctx) error {
|
||||
ctx.Locals("role", "admin")
|
||||
return ctx.Next()
|
||||
})
|
||||
|
||||
app.Patch("/list-buckets", adminController.ListBuckets)
|
||||
|
||||
appRoleErr := fiber.New()
|
||||
|
||||
appRoleErr.Use(func(ctx *fiber.Ctx) error {
|
||||
ctx.Locals("role", "user")
|
||||
return ctx.Next()
|
||||
})
|
||||
|
||||
appRoleErr.Patch("/list-buckets", adminController.ListBuckets)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
app *fiber.App
|
||||
args args
|
||||
wantErr bool
|
||||
statusCode int
|
||||
}{
|
||||
{
|
||||
name: "List-buckets-incorrect-role",
|
||||
app: appRoleErr,
|
||||
args: args{
|
||||
req: httptest.NewRequest(http.MethodPatch, "/list-buckets", nil),
|
||||
},
|
||||
wantErr: false,
|
||||
statusCode: 500,
|
||||
},
|
||||
{
|
||||
name: "List-buckets-success",
|
||||
app: app,
|
||||
args: args{
|
||||
req: httptest.NewRequest(http.MethodPatch, "/list-buckets", nil),
|
||||
},
|
||||
wantErr: false,
|
||||
statusCode: 200,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
resp, err := tt.app.Test(tt.args.req)
|
||||
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("AdminController.ListBuckets() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
|
||||
if resp.StatusCode != tt.statusCode {
|
||||
t.Errorf("AdminController.ListBuckets() statusCode = %v, wantStatusCode = %v", resp.StatusCode, tt.statusCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,9 +70,12 @@ var _ backend.Backend = &BackendMock{}
|
||||
// HeadObjectFunc: func(contextMoqParam context.Context, headObjectInput *s3.HeadObjectInput) (*s3.HeadObjectOutput, error) {
|
||||
// panic("mock out the HeadObject method")
|
||||
// },
|
||||
// ListBucketsFunc: func(contextMoqParam context.Context, owner string, isRoot bool) (s3response.ListAllMyBucketsResult, error) {
|
||||
// ListBucketsFunc: func(contextMoqParam context.Context, owner string, isAdmin bool) (s3response.ListAllMyBucketsResult, error) {
|
||||
// panic("mock out the ListBuckets method")
|
||||
// },
|
||||
// ListBucketsAndOwnersFunc: func(contextMoqParam context.Context) ([]s3response.Bucket, error) {
|
||||
// panic("mock out the ListBucketsAndOwners method")
|
||||
// },
|
||||
// ListMultipartUploadsFunc: func(contextMoqParam context.Context, listMultipartUploadsInput *s3.ListMultipartUploadsInput) (s3response.ListMultipartUploadsResult, error) {
|
||||
// panic("mock out the ListMultipartUploads method")
|
||||
// },
|
||||
@@ -174,7 +177,10 @@ type BackendMock struct {
|
||||
HeadObjectFunc func(contextMoqParam context.Context, headObjectInput *s3.HeadObjectInput) (*s3.HeadObjectOutput, error)
|
||||
|
||||
// ListBucketsFunc mocks the ListBuckets method.
|
||||
ListBucketsFunc func(contextMoqParam context.Context, owner string, isRoot bool) (s3response.ListAllMyBucketsResult, error)
|
||||
ListBucketsFunc func(contextMoqParam context.Context, owner string, isAdmin bool) (s3response.ListAllMyBucketsResult, error)
|
||||
|
||||
// ListBucketsAndOwnersFunc mocks the ListBucketsAndOwners method.
|
||||
ListBucketsAndOwnersFunc func(contextMoqParam context.Context) ([]s3response.Bucket, error)
|
||||
|
||||
// ListMultipartUploadsFunc mocks the ListMultipartUploads method.
|
||||
ListMultipartUploadsFunc func(contextMoqParam context.Context, listMultipartUploadsInput *s3.ListMultipartUploadsInput) (s3response.ListMultipartUploadsResult, error)
|
||||
@@ -347,8 +353,13 @@ type BackendMock struct {
|
||||
ContextMoqParam context.Context
|
||||
// Owner is the owner argument value.
|
||||
Owner string
|
||||
// IsRoot is the isRoot argument value.
|
||||
IsRoot bool
|
||||
// IsAdmin is the isAdmin argument value.
|
||||
IsAdmin bool
|
||||
}
|
||||
// ListBucketsAndOwners holds details about calls to the ListBucketsAndOwners method.
|
||||
ListBucketsAndOwners []struct {
|
||||
// ContextMoqParam is the contextMoqParam argument value.
|
||||
ContextMoqParam context.Context
|
||||
}
|
||||
// ListMultipartUploads holds details about calls to the ListMultipartUploads method.
|
||||
ListMultipartUploads []struct {
|
||||
@@ -473,6 +484,7 @@ type BackendMock struct {
|
||||
lockHeadBucket sync.RWMutex
|
||||
lockHeadObject sync.RWMutex
|
||||
lockListBuckets sync.RWMutex
|
||||
lockListBucketsAndOwners sync.RWMutex
|
||||
lockListMultipartUploads sync.RWMutex
|
||||
lockListObjects sync.RWMutex
|
||||
lockListObjectsV2 sync.RWMutex
|
||||
@@ -1079,23 +1091,23 @@ func (mock *BackendMock) HeadObjectCalls() []struct {
|
||||
}
|
||||
|
||||
// ListBuckets calls ListBucketsFunc.
|
||||
func (mock *BackendMock) ListBuckets(contextMoqParam context.Context, owner string, isRoot bool) (s3response.ListAllMyBucketsResult, error) {
|
||||
func (mock *BackendMock) ListBuckets(contextMoqParam context.Context, owner string, isAdmin bool) (s3response.ListAllMyBucketsResult, error) {
|
||||
if mock.ListBucketsFunc == nil {
|
||||
panic("BackendMock.ListBucketsFunc: method is nil but Backend.ListBuckets was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
ContextMoqParam context.Context
|
||||
Owner string
|
||||
IsRoot bool
|
||||
IsAdmin bool
|
||||
}{
|
||||
ContextMoqParam: contextMoqParam,
|
||||
Owner: owner,
|
||||
IsRoot: isRoot,
|
||||
IsAdmin: isAdmin,
|
||||
}
|
||||
mock.lockListBuckets.Lock()
|
||||
mock.calls.ListBuckets = append(mock.calls.ListBuckets, callInfo)
|
||||
mock.lockListBuckets.Unlock()
|
||||
return mock.ListBucketsFunc(contextMoqParam, owner, isRoot)
|
||||
return mock.ListBucketsFunc(contextMoqParam, owner, isAdmin)
|
||||
}
|
||||
|
||||
// ListBucketsCalls gets all the calls that were made to ListBuckets.
|
||||
@@ -1105,12 +1117,12 @@ func (mock *BackendMock) ListBuckets(contextMoqParam context.Context, owner stri
|
||||
func (mock *BackendMock) ListBucketsCalls() []struct {
|
||||
ContextMoqParam context.Context
|
||||
Owner string
|
||||
IsRoot bool
|
||||
IsAdmin bool
|
||||
} {
|
||||
var calls []struct {
|
||||
ContextMoqParam context.Context
|
||||
Owner string
|
||||
IsRoot bool
|
||||
IsAdmin bool
|
||||
}
|
||||
mock.lockListBuckets.RLock()
|
||||
calls = mock.calls.ListBuckets
|
||||
@@ -1118,6 +1130,38 @@ func (mock *BackendMock) ListBucketsCalls() []struct {
|
||||
return calls
|
||||
}
|
||||
|
||||
// ListBucketsAndOwners calls ListBucketsAndOwnersFunc.
|
||||
func (mock *BackendMock) ListBucketsAndOwners(contextMoqParam context.Context) ([]s3response.Bucket, error) {
|
||||
if mock.ListBucketsAndOwnersFunc == nil {
|
||||
panic("BackendMock.ListBucketsAndOwnersFunc: method is nil but Backend.ListBucketsAndOwners was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
ContextMoqParam context.Context
|
||||
}{
|
||||
ContextMoqParam: contextMoqParam,
|
||||
}
|
||||
mock.lockListBucketsAndOwners.Lock()
|
||||
mock.calls.ListBucketsAndOwners = append(mock.calls.ListBucketsAndOwners, callInfo)
|
||||
mock.lockListBucketsAndOwners.Unlock()
|
||||
return mock.ListBucketsAndOwnersFunc(contextMoqParam)
|
||||
}
|
||||
|
||||
// ListBucketsAndOwnersCalls gets all the calls that were made to ListBucketsAndOwners.
|
||||
// Check the length with:
|
||||
//
|
||||
// len(mockedBackend.ListBucketsAndOwnersCalls())
|
||||
func (mock *BackendMock) ListBucketsAndOwnersCalls() []struct {
|
||||
ContextMoqParam context.Context
|
||||
} {
|
||||
var calls []struct {
|
||||
ContextMoqParam context.Context
|
||||
}
|
||||
mock.lockListBucketsAndOwners.RLock()
|
||||
calls = mock.calls.ListBucketsAndOwners
|
||||
mock.lockListBucketsAndOwners.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// ListMultipartUploads calls ListMultipartUploadsFunc.
|
||||
func (mock *BackendMock) ListMultipartUploads(contextMoqParam context.Context, listMultipartUploadsInput *s3.ListMultipartUploadsInput) (s3response.ListMultipartUploadsResult, error) {
|
||||
if mock.ListMultipartUploadsFunc == nil {
|
||||
|
||||
@@ -598,6 +598,7 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
|
||||
CopySourceIfNoneMatch: ©SrcIfNoneMatch,
|
||||
CopySourceIfModifiedSince: &mtime,
|
||||
CopySourceIfUnmodifiedSince: &umtime,
|
||||
ExpectedBucketOwner: &access,
|
||||
})
|
||||
if err == nil {
|
||||
return SendXMLResponse(ctx, res, err, &MetaOpts{
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/valyala/fasthttp"
|
||||
"github.com/versity/versitygw/auth"
|
||||
"github.com/versity/versitygw/backend"
|
||||
"github.com/versity/versitygw/s3err"
|
||||
@@ -1419,16 +1420,9 @@ func Test_XMLresponse(t *testing.T) {
|
||||
resp any
|
||||
err error
|
||||
}
|
||||
|
||||
app := fiber.New()
|
||||
|
||||
var ctx fiber.Ctx
|
||||
// Mocking the fiber ctx
|
||||
app.Get("/:bucket/:key", func(c *fiber.Ctx) error {
|
||||
ctx = *c
|
||||
return nil
|
||||
})
|
||||
|
||||
app.Test(httptest.NewRequest(http.MethodGet, "/my-bucket/my-key", nil))
|
||||
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -1439,7 +1433,7 @@ func Test_XMLresponse(t *testing.T) {
|
||||
{
|
||||
name: "Internal-server-error",
|
||||
args: args{
|
||||
ctx: &ctx,
|
||||
ctx: ctx,
|
||||
resp: nil,
|
||||
err: s3err.GetAPIError(s3err.ErrInternalError),
|
||||
},
|
||||
@@ -1449,7 +1443,7 @@ func Test_XMLresponse(t *testing.T) {
|
||||
{
|
||||
name: "Error-not-implemented",
|
||||
args: args{
|
||||
ctx: &ctx,
|
||||
ctx: ctx,
|
||||
resp: nil,
|
||||
err: s3err.GetAPIError(s3err.ErrNotImplemented),
|
||||
},
|
||||
@@ -1459,7 +1453,7 @@ func Test_XMLresponse(t *testing.T) {
|
||||
{
|
||||
name: "Invalid-request-body",
|
||||
args: args{
|
||||
ctx: &ctx,
|
||||
ctx: ctx,
|
||||
resp: make(chan int),
|
||||
err: nil,
|
||||
},
|
||||
@@ -1469,7 +1463,7 @@ func Test_XMLresponse(t *testing.T) {
|
||||
{
|
||||
name: "Successful-response",
|
||||
args: args{
|
||||
ctx: &ctx,
|
||||
ctx: ctx,
|
||||
resp: "Valid response",
|
||||
err: nil,
|
||||
},
|
||||
@@ -1500,15 +1494,9 @@ func Test_response(t *testing.T) {
|
||||
resp any
|
||||
err error
|
||||
}
|
||||
app := fiber.New()
|
||||
var ctx fiber.Ctx
|
||||
// Mocking the fiber ctx
|
||||
app.Get("/:bucket/:key", func(c *fiber.Ctx) error {
|
||||
ctx = *c
|
||||
return nil
|
||||
})
|
||||
|
||||
app.Test(httptest.NewRequest(http.MethodGet, "/my-bucket/my-key", nil))
|
||||
app := fiber.New()
|
||||
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -1519,7 +1507,7 @@ func Test_response(t *testing.T) {
|
||||
{
|
||||
name: "Internal-server-error",
|
||||
args: args{
|
||||
ctx: &ctx,
|
||||
ctx: ctx,
|
||||
resp: nil,
|
||||
err: s3err.GetAPIError(s3err.ErrInternalError),
|
||||
},
|
||||
@@ -1529,7 +1517,7 @@ func Test_response(t *testing.T) {
|
||||
{
|
||||
name: "Internal-server-error-not-api",
|
||||
args: args{
|
||||
ctx: &ctx,
|
||||
ctx: ctx,
|
||||
resp: nil,
|
||||
err: fmt.Errorf("custom error"),
|
||||
},
|
||||
@@ -1539,7 +1527,7 @@ func Test_response(t *testing.T) {
|
||||
{
|
||||
name: "Error-not-implemented",
|
||||
args: args{
|
||||
ctx: &ctx,
|
||||
ctx: ctx,
|
||||
resp: nil,
|
||||
err: s3err.GetAPIError(s3err.ErrNotImplemented),
|
||||
},
|
||||
@@ -1549,7 +1537,7 @@ func Test_response(t *testing.T) {
|
||||
{
|
||||
name: "Successful-response",
|
||||
args: args{
|
||||
ctx: &ctx,
|
||||
ctx: ctx,
|
||||
resp: "Valid response",
|
||||
err: nil,
|
||||
},
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -94,15 +95,14 @@ func VerifyV4Signature(root RootUserConfig, iam auth.IAMService, logger s3log.Au
|
||||
}, &controllers.MetaOpts{Logger: logger})
|
||||
}
|
||||
|
||||
// Validate the dates difference
|
||||
err := validateDate(creds[1])
|
||||
if err != nil {
|
||||
return controllers.SendResponse(ctx, err, &controllers.MetaOpts{Logger: logger})
|
||||
}
|
||||
|
||||
ctx.Locals("access", creds[0])
|
||||
ctx.Locals("isRoot", creds[0] == root.Access)
|
||||
|
||||
_, err := time.Parse(YYYYMMDD, creds[1])
|
||||
if err != nil {
|
||||
return controllers.SendResponse(ctx, s3err.GetAPIError(s3err.ErrSignatureDateDoesNotMatch), &controllers.MetaOpts{Logger: logger})
|
||||
}
|
||||
|
||||
signHdrKv := strings.Split(authParts[1], "=")
|
||||
if len(signHdrKv) != 2 {
|
||||
return controllers.SendResponse(ctx, s3err.GetAPIError(s3err.ErrCredMalformed), &controllers.MetaOpts{Logger: logger})
|
||||
@@ -134,6 +134,12 @@ func VerifyV4Signature(root RootUserConfig, iam auth.IAMService, logger s3log.Au
|
||||
return controllers.SendResponse(ctx, s3err.GetAPIError(s3err.ErrSignatureDateDoesNotMatch), &controllers.MetaOpts{Logger: logger})
|
||||
}
|
||||
|
||||
// Validate the dates difference
|
||||
err = validateDate(tdate)
|
||||
if err != nil {
|
||||
return controllers.SendResponse(ctx, err, &controllers.MetaOpts{Logger: logger})
|
||||
}
|
||||
|
||||
hashPayloadHeader := ctx.Get("X-Amz-Content-Sha256")
|
||||
ok := isSpecialPayload(hashPayloadHeader)
|
||||
|
||||
@@ -214,25 +220,24 @@ func isSpecialPayload(str string) bool {
|
||||
return specialValues[str]
|
||||
}
|
||||
|
||||
func validateDate(date string) error {
|
||||
credDate, err := time.Parse(YYYYMMDD, date)
|
||||
if err != nil {
|
||||
return s3err.GetAPIError(s3err.ErrSignatureDateDoesNotMatch)
|
||||
}
|
||||
func validateDate(date time.Time) error {
|
||||
now := time.Now().UTC()
|
||||
diff := date.Unix() - now.Unix()
|
||||
|
||||
today := time.Now()
|
||||
if credDate.Year() > today.Year() || (credDate.Year() == today.Year() && credDate.YearDay() > today.YearDay()) {
|
||||
return s3err.APIError{
|
||||
Code: "SignatureDoesNotMatch",
|
||||
Description: fmt.Sprintf("Signature not yet current: %s is still later than %s", credDate.Format(YYYYMMDD), today.Format(YYYYMMDD)),
|
||||
HTTPStatusCode: http.StatusForbidden,
|
||||
}
|
||||
}
|
||||
if credDate.Year() < today.Year() || (credDate.Year() == today.Year() && credDate.YearDay() < today.YearDay()) {
|
||||
return s3err.APIError{
|
||||
Code: "SignatureDoesNotMatch",
|
||||
Description: fmt.Sprintf("Signature expired: %s is now earlier than %s", credDate.Format(YYYYMMDD), today.Format(YYYYMMDD)),
|
||||
HTTPStatusCode: http.StatusForbidden,
|
||||
// Checks the dates difference to be less than a minute
|
||||
if math.Abs(float64(diff)) > 60 {
|
||||
if diff > 0 {
|
||||
return s3err.APIError{
|
||||
Code: "SignatureDoesNotMatch",
|
||||
Description: fmt.Sprintf("Signature not yet current: %s is still later than %s", date.Format(iso8601Format), now.Format(iso8601Format)),
|
||||
HTTPStatusCode: http.StatusForbidden,
|
||||
}
|
||||
} else {
|
||||
return s3err.APIError{
|
||||
Code: "SignatureDoesNotMatch",
|
||||
Description: fmt.Sprintf("Signature expired: %s is now earlier than %s", date.Format(iso8601Format), now.Format(iso8601Format)),
|
||||
HTTPStatusCode: http.StatusForbidden,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,28 +23,36 @@ import (
|
||||
"github.com/versity/versitygw/s3log"
|
||||
)
|
||||
|
||||
type S3ApiRouter struct{}
|
||||
type S3ApiRouter struct {
|
||||
WithAdmSrv bool
|
||||
}
|
||||
|
||||
func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMService, logger s3log.AuditLogger, evs s3event.S3EventSender) {
|
||||
s3ApiController := controllers.New(be, iam, logger, evs)
|
||||
adminController := controllers.NewAdminController(iam, be)
|
||||
|
||||
// CreateUser admin api
|
||||
app.Patch("/create-user", adminController.CreateUser)
|
||||
if sa.WithAdmSrv {
|
||||
adminController := controllers.NewAdminController(iam, be)
|
||||
|
||||
// DeleteUsers admin api
|
||||
app.Patch("/delete-user", adminController.DeleteUser)
|
||||
// CreateUser admin api
|
||||
app.Patch("/create-user", adminController.CreateUser)
|
||||
|
||||
// ListUsers admin api
|
||||
app.Patch("/list-users", adminController.ListUsers)
|
||||
// DeleteUsers admin api
|
||||
app.Patch("/delete-user", adminController.DeleteUser)
|
||||
|
||||
// ChangeBucketOwner
|
||||
app.Patch("/change-bucket-owner", adminController.ChangeBucketOwner)
|
||||
// ListUsers admin api
|
||||
app.Patch("/list-users", adminController.ListUsers)
|
||||
|
||||
// ChangeBucketOwner admin api
|
||||
app.Patch("/change-bucket-owner", adminController.ChangeBucketOwner)
|
||||
|
||||
// ListBucketsAndOwners admin api
|
||||
app.Patch("/list-buckets", adminController.ListBuckets)
|
||||
}
|
||||
|
||||
// ListBuckets action
|
||||
app.Get("/", s3ApiController.ListBuckets)
|
||||
|
||||
// PutBucket action
|
||||
// CreateBucket action
|
||||
// PutBucketAcl action
|
||||
app.Put("/:bucket", s3ApiController.PutBucketActions)
|
||||
|
||||
@@ -53,6 +61,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
|
||||
// HeadBucket
|
||||
app.Head("/:bucket", s3ApiController.HeadBucket)
|
||||
|
||||
// GetBucketAcl action
|
||||
// ListMultipartUploads action
|
||||
// ListObjects action
|
||||
@@ -61,22 +70,34 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
|
||||
// HeadObject action
|
||||
app.Head("/:bucket/:key/*", s3ApiController.HeadObject)
|
||||
|
||||
// GetObjectAcl action
|
||||
// GetObject action
|
||||
// ListObjectParts action
|
||||
// GetTags action
|
||||
// ListParts action
|
||||
// GetObjectAttributes action
|
||||
app.Get("/:bucket/:key/*", s3ApiController.GetActions)
|
||||
|
||||
// DeleteObject action
|
||||
// AbortMultipartUpload action
|
||||
// RemoveTags action
|
||||
app.Delete("/:bucket/:key/*", s3ApiController.DeleteActions)
|
||||
|
||||
// DeleteObjects action
|
||||
app.Post("/:bucket", s3ApiController.DeleteObjects)
|
||||
|
||||
// CompleteMultipartUpload action
|
||||
// CreateMultipartUpload
|
||||
// RestoreObject action
|
||||
// SelectObjectContent action
|
||||
app.Post("/:bucket/:key/*", s3ApiController.CreateActions)
|
||||
|
||||
// CopyObject action
|
||||
// PutObject action
|
||||
// UploadPart action
|
||||
// UploadPartCopy action
|
||||
// SetTags action
|
||||
// PutObjectAcl action
|
||||
app.Put("/:bucket/:key/*", s3ApiController.PutActions)
|
||||
}
|
||||
|
||||
@@ -70,6 +70,11 @@ func WithTLS(cert tls.Certificate) Option {
|
||||
return func(s *S3ApiServer) { s.cert = &cert }
|
||||
}
|
||||
|
||||
// WithAdminServer runs admin endpoints with the gateway in the same network
|
||||
func WithAdminServer() Option {
|
||||
return func(s *S3ApiServer) { s.router.WithAdmSrv = true }
|
||||
}
|
||||
|
||||
// WithDebug sets debug output
|
||||
func WithDebug() Option {
|
||||
return func(s *S3ApiServer) { s.debug = true }
|
||||
|
||||
@@ -134,3 +134,8 @@ type SelectObjectContentResult struct {
|
||||
Cont *string
|
||||
End *string
|
||||
}
|
||||
|
||||
type Bucket struct {
|
||||
Name string `json:"name"`
|
||||
Owner string `json:"owner"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user