Compare commits

...

47 Commits
v0.6 ... v0.7

Author SHA1 Message Date
Ben McClelland
e46e4e941b Merge pull request #255 from versity/fix/issue-245-upload_part_copy-invalid-range
Issue 245, UploadPartCopy exceeding range error
2023-09-19 09:04:05 -07:00
jonaustin09
c9653cff71 fix: Fixes #245, Fixed exceeding range error for UploadPartCopy action 2023-09-19 11:53:49 -04:00
Ben McClelland
48798c9e39 Merge pull request #254 from versity/dependabot/go_modules/dev-dependencies-e5940eaa8e
chore(deps): bump the dev-dependencies group with 4 updates
2023-09-18 17:21:42 -07:00
dependabot[bot]
42c4ad3b9e chore(deps): bump the dev-dependencies group with 4 updates
Bumps the dev-dependencies group with 4 updates: [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go), [github.com/valyala/fasthttp](https://github.com/valyala/fasthttp), [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) and [github.com/aws/aws-sdk-go-v2/feature/s3/manager](https://github.com/aws/aws-sdk-go-v2).


Updates `github.com/nats-io/nats.go` from 1.28.0 to 1.29.0
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.28.0...v1.29.0)

Updates `github.com/valyala/fasthttp` from 1.49.0 to 1.50.0
- [Release notes](https://github.com/valyala/fasthttp/releases)
- [Commits](https://github.com/valyala/fasthttp/compare/v1.49.0...v1.50.0)

Updates `github.com/aws/aws-sdk-go-v2/config` from 1.18.39 to 1.18.40
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.18.39...config/v1.18.40)

Updates `github.com/aws/aws-sdk-go-v2/feature/s3/manager` from 1.11.83 to 1.11.84
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/feature/s3/manager/v1.11.83...feature/s3/manager/v1.11.84)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats.go
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: github.com/valyala/fasthttp
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: github.com/aws/aws-sdk-go-v2/config
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: github.com/aws/aws-sdk-go-v2/feature/s3/manager
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-18 21:59:05 +00:00
Jon Austin
1874d3c329 fix: Fixes #244, Added destincation bucket ACL check for CopyObject action (#253) 2023-09-18 11:49:37 -07:00
Ben McClelland
9f0c9badba Merge pull request #252 from versity/fix/issue-243-copy-object-same-dest
Issue 243 CopyObject error case to copy the object into itself
2023-09-18 09:34:04 -07:00
jonaustin09
cb6b60324c fix: Fixes #243, fixed the error case for CopyObject to copy the object into itself 2023-09-18 09:29:25 -07:00
Ben McClelland
c53707d4ae Merge pull request #251 from versity/fix/walk-last-elem
Walk function last object check panic fix
2023-09-18 08:29:08 -07:00
jonaustin09
ee1ab5bdcc fix: Changed Walk function last object check to avoid panic 2023-09-18 08:20:08 -04:00
Ben McClelland
7a0c4423e4 Merge pull request #242 from versity/dependabot/go_modules/github.com/gofiber/fiber/v2-2.49.2
chore(deps): bump github.com/gofiber/fiber/v2 from 2.49.1 to 2.49.2
2023-09-14 18:18:57 -07:00
Ben McClelland
8382911ab6 Merge pull request #241 from versity/fix/list-objects-bugs
Issue 179, 180
2023-09-14 18:18:18 -07:00
dependabot[bot]
4e7615b4fd chore(deps): bump github.com/gofiber/fiber/v2 from 2.49.1 to 2.49.2
Bumps [github.com/gofiber/fiber/v2](https://github.com/gofiber/fiber) from 2.49.1 to 2.49.2.
- [Release notes](https://github.com/gofiber/fiber/releases)
- [Commits](https://github.com/gofiber/fiber/compare/v2.49.1...v2.49.2)

---
updated-dependencies:
- dependency-name: github.com/gofiber/fiber/v2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-14 20:19:54 +00:00
jonaustin09
8951cce6d0 fix: Fixes #179, Fixes #180, Fixes ListObject marker bug 2023-09-14 16:17:29 -04:00
Ben McClelland
363c82971a Merge pull request #240 from versity/fix/issue-239
Issue 239, authentication time comparison with UTC
2023-09-13 12:35:07 -07:00
jonaustin09
cf1c44969b fix: Fixes #239, Change SigV4 date comparison with UTC 2023-09-13 15:16:50 -04:00
Ben McClelland
37b5429468 Merge pull request #235 from versity/ben/admin_server
fix: remove body limit for admin app
2023-09-13 07:16:50 -07:00
Ben McClelland
c9475adb04 fix: remove body limit for admin app
The BodyLimit is needed for large PUTs in the s3 api server, but
the admin server requests are not expected to exceed the default
4MB limit.
2023-09-12 16:16:47 -07:00
Ben McClelland
b00819ff31 Merge pull request #234 from versity/feat/admin-server
Issue 232, An option to run admin server as a separate one.
2023-09-12 16:12:20 -07:00
jonaustin09
5ab38e3dab feat: Closes #232, Added an option to run admin server in a different network, by specifying admin server address/ip 2023-09-12 16:04:31 -07:00
Ben McClelland
c04f6d7f00 Merge pull request #233 from versity/admin/list-buckets
Issue 217, Admin API and CLI action to list all the buckets and its owners.
2023-09-12 16:02:29 -07:00
jonaustin09
6ac69b3198 feat: Closes #217, Created an admin API and CLI action to list all the buckets and its owners as a table 2023-09-12 08:29:34 -04:00
Ben McClelland
c72686f7fa Merge pull request #223 from versity/ben/32bit
fix: builds for non 64 bit linux arch
2023-09-11 09:10:30 -07:00
Ben McClelland
145c2dd4e3 fix: builds for non 64 bit linux arch
The scoutfs backend is only supported on 64bit linux.  This corrects
the build constraints to only supported linux arch, and prevents the
incompatible import for unspported arch.

We also need to adjust the body limit setting on 32 bit since this
is an int, and our default limit will overfow on 32 bit.
2023-09-10 20:49:56 -07:00
Ben McClelland
f90562fea2 Merge pull request #231 from versity/dependabot/go_modules/dev-dependencies-8b25ff15ea
chore(deps): bump the dev-dependencies group with 1 update
2023-09-10 20:36:15 -07:00
dependabot[bot]
b0b22467cc chore(deps): bump the dev-dependencies group with 1 update
Bumps the dev-dependencies group with 1 update: [github.com/gofiber/fiber/v2](https://github.com/gofiber/fiber).

- [Release notes](https://github.com/gofiber/fiber/releases)
- [Commits](https://github.com/gofiber/fiber/compare/v2.48.0...v2.49.1)

---
updated-dependencies:
- dependency-name: github.com/gofiber/fiber/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-10 19:21:20 +00:00
Ben McClelland
b2247e20ee Merge pull request #230 from versity/ben/dependabot
chore: add grouping for dependabot PRs
2023-09-10 12:20:47 -07:00
Ben McClelland
8017b0cff0 Merge pull request #229 from versity/dependabot/go_modules/golang.org/x/sys-0.12.0
chore(deps): bump golang.org/x/sys from 0.10.0 to 0.12.0
2023-09-10 12:20:31 -07:00
dependabot[bot]
c1b105d928 chore(deps): bump golang.org/x/sys from 0.10.0 to 0.12.0
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.10.0 to 0.12.0.
- [Commits](https://github.com/golang/sys/compare/v0.10.0...v0.12.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-10 19:15:49 +00:00
Ben McClelland
57c7518864 Merge pull request #228 from versity/dependabot/go_modules/github.com/aws/aws-sdk-go-v2/feature/s3/manager-1.11.83
chore(deps): bump github.com/aws/aws-sdk-go-v2/feature/s3/manager from 1.11.76 to 1.11.83
2023-09-10 12:15:07 -07:00
dependabot[bot]
5a94a70212 chore(deps): bump github.com/aws/aws-sdk-go-v2/feature/s3/manager
Bumps [github.com/aws/aws-sdk-go-v2/feature/s3/manager](https://github.com/aws/aws-sdk-go-v2) from 1.11.76 to 1.11.83.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/feature/s3/manager/v1.11.76...feature/s3/manager/v1.11.83)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/feature/s3/manager
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-10 19:09:21 +00:00
Ben McClelland
064230108f Merge pull request #227 from versity/dependabot/go_modules/github.com/valyala/fasthttp-1.49.0
chore(deps): bump github.com/valyala/fasthttp from 1.48.0 to 1.49.0
2023-09-10 12:08:23 -07:00
Ben McClelland
51680d445c chore: add grouping for dependabot PRs 2023-09-10 12:07:09 -07:00
dependabot[bot]
732e92a72f chore(deps): bump github.com/valyala/fasthttp from 1.48.0 to 1.49.0
Bumps [github.com/valyala/fasthttp](https://github.com/valyala/fasthttp) from 1.48.0 to 1.49.0.
- [Release notes](https://github.com/valyala/fasthttp/releases)
- [Commits](https://github.com/valyala/fasthttp/compare/v1.48.0...v1.49.0)

---
updated-dependencies:
- dependency-name: github.com/valyala/fasthttp
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-10 18:59:04 +00:00
Ben McClelland
36aea696c6 Merge pull request #226 from versity/dependabot/go_modules/github.com/aws/aws-sdk-go-v2-1.21.0
chore(deps): bump github.com/aws/aws-sdk-go-v2 from 1.20.0 to 1.21.0
2023-09-10 11:58:45 -07:00
Ben McClelland
46c0762133 Merge pull request #225 from versity/dependabot/go_modules/github.com/google/uuid-1.3.1
chore(deps): bump github.com/google/uuid from 1.3.0 to 1.3.1
2023-09-10 11:58:19 -07:00
dependabot[bot]
8d6b5c387f chore(deps): bump github.com/aws/aws-sdk-go-v2 from 1.20.0 to 1.21.0
Bumps [github.com/aws/aws-sdk-go-v2](https://github.com/aws/aws-sdk-go-v2) from 1.20.0 to 1.21.0.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/v1.20.0...v1.21.0)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-10 18:52:04 +00:00
dependabot[bot]
a241e6a7e6 chore(deps): bump github.com/google/uuid from 1.3.0 to 1.3.1
Bumps [github.com/google/uuid](https://github.com/google/uuid) from 1.3.0 to 1.3.1.
- [Release notes](https://github.com/google/uuid/releases)
- [Changelog](https://github.com/google/uuid/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/uuid/compare/v1.3.0...v1.3.1)

---
updated-dependencies:
- dependency-name: github.com/google/uuid
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-10 18:51:27 +00:00
Ben McClelland
c690b01a90 Merge pull request #224 from versity/ben/dependabot
chore: add dependabot.yml configuration
2023-09-10 11:51:03 -07:00
Ben McClelland
0d044c2303 chore: add dependabot.yml configuration 2023-09-10 11:47:39 -07:00
Ben McClelland
42270fbe1c Merge pull request #222 from versity/minor_app_cleanup
App cleanup(minor changes)
2023-09-08 09:51:29 -07:00
jonaustin09
35fe6d8dee fix: some cleanup in posix, router and acl 2023-09-08 12:46:50 -04:00
Ben McClelland
23b5eb30ed Merge pull request #220 from versity/ben/goreleaser
fix: goreleaser remove merge commits from release changelog
2023-09-08 09:36:39 -07:00
Ben McClelland
24309ae25a Merge pull request #221 from versity/int_test_create_bucket
CreateBucket integration test
2023-09-08 09:36:18 -07:00
jonaustin09
f74179d01c feat: Added an integration test case for CreateBucket to create a bucket as user 2023-09-08 12:28:01 -04:00
Ben McClelland
1959fac8a0 fix: goreleaser remove merge commits from release changelog 2023-09-08 09:09:36 -07:00
Ben McClelland
eb05f5a93e Merge pull request #219 from versity/vet-warnings
Issue 206, vet warnings
2023-09-08 09:06:28 -07:00
jonaustin09
23c26d802c fix: Fixes #216, Fixed vet warnings, removed the code snippet which copied fiber.Ctx 2023-09-08 11:57:17 -04:00
28 changed files with 871 additions and 207 deletions

10
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "weekly"
groups:
dev-dependencies:
patterns:
- "*"

View File

@@ -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

View File

@@ -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"})
}

View File

@@ -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)
}

View File

@@ -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
)

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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.

View File

@@ -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
}

View File

@@ -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
View File

@@ -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
View File

@@ -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=

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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
View 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
View 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)
}

View File

@@ -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)
}

View File

@@ -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)
}
}
}

View File

@@ -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 {

View File

@@ -598,6 +598,7 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
CopySourceIfNoneMatch: &copySrcIfNoneMatch,
CopySourceIfModifiedSince: &mtime,
CopySourceIfUnmodifiedSince: &umtime,
ExpectedBucketOwner: &access,
})
if err == nil {
return SendXMLResponse(ctx, res, err, &MetaOpts{

View File

@@ -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,
},

View File

@@ -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,
}
}
}

View File

@@ -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)
}

View File

@@ -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 }

View File

@@ -134,3 +134,8 @@ type SelectObjectContentResult struct {
Cont *string
End *string
}
type Bucket struct {
Name string `json:"name"`
Owner string `json:"owner"`
}