diff --git a/auth/acl.go b/auth/acl.go index 08c47db..936505d 100644 --- a/auth/acl.go +++ b/auth/acl.go @@ -17,7 +17,6 @@ package auth import ( "encoding/json" "fmt" - "os" "strings" "github.com/aws/aws-sdk-go-v2/service/s3" @@ -236,29 +235,14 @@ func VerifyACL(acl ACL, access string, permission types.Permission, isRoot bool) return s3err.GetAPIError(s3err.ErrAccessDenied) } -func IsAdmin(access string, isRoot bool) error { - var data IAMConfig - +func IsAdmin(acct Account, isRoot bool) error { if isRoot { return nil } - file, err := os.ReadFile("users.json") - if err != nil { - return fmt.Errorf("unable to read config file: %w", err) - } - - if err := json.Unmarshal(file, &data); err != nil { - return err - } - - acc, ok := data.AccessAccounts[access] - if !ok { - return fmt.Errorf("user does not exist") - } - - if acc.Role == "admin" { + if acct.Role == "admin" { return nil } + return s3err.GetAPIError(s3err.ErrAccessDenied) } diff --git a/auth/iam.go b/auth/iam.go index 99748da..535649c 100644 --- a/auth/iam.go +++ b/auth/iam.go @@ -29,7 +29,7 @@ type Account struct { // //go:generate moq -out ../s3api/controllers/iam_moq_test.go -pkg controllers . IAMService type IAMService interface { - CreateAccount(access string, account Account) error + CreateAccount(account Account) error GetUserAccount(access string) (Account, error) DeleteUserAccount(access string) error ListUserAccounts() ([]Account, error) diff --git a/auth/iam_internal.go b/auth/iam_internal.go index 2b4610d..32b8400 100644 --- a/auth/iam_internal.go +++ b/auth/iam_internal.go @@ -16,45 +16,59 @@ package auth import ( "encoding/json" + "errors" "fmt" "hash/crc32" + "io/fs" + "os" + "path/filepath" "sync" + "time" +) + +const ( + iamFile = "users.json" + iamBackupFile = "users.json.backup" +) + +var ( + cacheDuration = 5 * time.Minute ) // IAMServiceInternal manages the internal IAM service type IAMServiceInternal struct { - storer Storer + path string - mu sync.RWMutex - accts IAMConfig - serial uint32 + mu sync.RWMutex + accts iAMConfig + serial uint32 + iamcache []byte + iamvalid bool + iamexpire time.Time } // UpdateAcctFunc accepts the current data and returns the new data to be stored type UpdateAcctFunc func([]byte) ([]byte, error) -// Storer is the interface to manage the peristent IAM data for the internal -// IAM service -type Storer interface { - InitIAM() error - GetIAM() ([]byte, error) - StoreIAM(UpdateAcctFunc) error -} - -// IAMConfig stores all internal IAM accounts -type IAMConfig struct { +// iAMConfig stores all internal IAM accounts +type iAMConfig struct { AccessAccounts map[string]Account `json:"accessAccounts"` } var _ IAMService = &IAMServiceInternal{} // NewInternal creates a new instance for the Internal IAM service -func NewInternal(s Storer) (*IAMServiceInternal, error) { +func NewInternal(path string) (*IAMServiceInternal, error) { i := &IAMServiceInternal{ - storer: s, + path: path, } - err := i.updateCache() + err := i.initIAM() + if err != nil { + return nil, fmt.Errorf("init iam: %w", err) + } + + err = i.updateCache() if err != nil { return nil, fmt.Errorf("refresh iam cache: %w", err) } @@ -64,26 +78,26 @@ func NewInternal(s Storer) (*IAMServiceInternal, error) { // CreateAccount creates a new IAM account. Returns an error if the account // already exists. -func (s *IAMServiceInternal) CreateAccount(access string, account Account) error { +func (s *IAMServiceInternal) CreateAccount(account Account) error { s.mu.Lock() defer s.mu.Unlock() - return s.storer.StoreIAM(func(data []byte) ([]byte, error) { - var conf IAMConfig + return s.storeIAM(func(data []byte) ([]byte, error) { + var conf iAMConfig if len(data) > 0 { if err := json.Unmarshal(data, &conf); err != nil { return nil, fmt.Errorf("failed to parse iam: %w", err) } } else { - conf = IAMConfig{AccessAccounts: map[string]Account{}} + conf = iAMConfig{AccessAccounts: map[string]Account{}} } - _, ok := conf.AccessAccounts[access] + _, ok := conf.AccessAccounts[account.Access] if ok { return nil, fmt.Errorf("account already exists") } - conf.AccessAccounts[access] = account + conf.AccessAccounts[account.Access] = account b, err := json.Marshal(conf) if err != nil { @@ -101,7 +115,7 @@ func (s *IAMServiceInternal) GetUserAccount(access string) (Account, error) { s.mu.RLock() defer s.mu.RUnlock() - data, err := s.storer.GetIAM() + data, err := s.getIAM() if err != nil { return Account{}, fmt.Errorf("get iam data: %w", err) } @@ -129,7 +143,7 @@ func (s *IAMServiceInternal) updateCache() error { s.mu.Lock() defer s.mu.Unlock() - data, err := s.storer.GetIAM() + data, err := s.getIAM() if err != nil { return fmt.Errorf("get iam data: %w", err) } @@ -155,13 +169,13 @@ func (s *IAMServiceInternal) DeleteUserAccount(access string) error { s.mu.Lock() defer s.mu.Unlock() - return s.storer.StoreIAM(func(data []byte) ([]byte, error) { + return s.storeIAM(func(data []byte) ([]byte, error) { if len(data) == 0 { // empty config, do nothing return data, nil } - var conf IAMConfig + var conf iAMConfig if err := json.Unmarshal(data, &conf); err != nil { return nil, fmt.Errorf("failed to parse iam: %w", err) @@ -185,7 +199,7 @@ func (s *IAMServiceInternal) ListUserAccounts() (accs []Account, err error) { s.mu.RLock() defer s.mu.RUnlock() - data, err := s.storer.GetIAM() + data, err := s.getIAM() if err != nil { return []Account{}, fmt.Errorf("get iam data: %w", err) } @@ -210,3 +224,186 @@ func (s *IAMServiceInternal) ListUserAccounts() (accs []Account, err error) { return accs, nil } + +const ( + iamMode = 0600 +) + +func (s *IAMServiceInternal) initIAM() error { + fname := filepath.Join(s.path, iamFile) + + _, err := os.ReadFile(fname) + if errors.Is(err, fs.ErrNotExist) { + b, err := json.Marshal(iAMConfig{AccessAccounts: map[string]Account{}}) + if err != nil { + return fmt.Errorf("marshal default iam: %w", err) + } + err = os.WriteFile(fname, b, iamMode) + if err != nil { + return fmt.Errorf("write default iam: %w", err) + } + } + + return nil +} + +func (s *IAMServiceInternal) getIAM() ([]byte, error) { + if !s.iamvalid || !s.iamexpire.After(time.Now()) { + err := s.refreshIAM() + if err != nil { + return nil, err + } + } + + return s.iamcache, nil +} + +const ( + backoff = 100 * time.Millisecond + maxretry = 300 +) + +func (s *IAMServiceInternal) refreshIAM() error { + // We are going to be racing with other running gateways without any + // coordination. So we might find the file does not exist at times. + // For this case we need to retry for a while assuming the other gateway + // will eventually write the file. If it doesn't after the max retries, + // then we will return the error. + + retries := 0 + + for { + b, err := os.ReadFile(filepath.Join(s.path, iamFile)) + if errors.Is(err, fs.ErrNotExist) { + // racing with someone else updating + // keep retrying after backoff + retries++ + if retries < maxretry { + time.Sleep(backoff) + continue + } + return fmt.Errorf("read iam file: %w", err) + } + if err != nil { + return err + } + + s.iamcache = b + s.iamvalid = true + s.iamexpire = time.Now().Add(cacheDuration) + break + } + + return nil +} + +func (s *IAMServiceInternal) storeIAM(update UpdateAcctFunc) error { + // We are going to be racing with other running gateways without any + // coordination. So the strategy here is to read the current file data. + // If the file doesn't exist, then we assume someone else is currently + // updating the file. So we just need to keep retrying. We also need + // to make sure the data is consistent within a single update. So racing + // writes to a file would possibly leave this in some invalid state. + // We can get atomic updates with rename. If we read the data, update + // the data, write to a temp file, then rename the tempfile back to the + // data file. This should always result in a complete data image. + + // There is at least one unsolved failure mode here. + // If a gateway removes the data file and then crashes, all other + // gateways will retry forever thinking that the original will eventually + // write the file. + + retries := 0 + fname := filepath.Join(s.path, iamFile) + + for { + b, err := os.ReadFile(fname) + if errors.Is(err, fs.ErrNotExist) { + // racing with someone else updating + // keep retrying after backoff + retries++ + if retries < maxretry { + time.Sleep(backoff) + continue + } + + // we have been unsuccessful trying to read the iam file + // so this must be the case where something happened and + // the file did not get updated successfully, and probably + // isn't going to be. The recovery procedure would be to + // copy the backup file into place of the original. + return fmt.Errorf("no iam file, needs backup recovery") + } + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("read iam file: %w", err) + } + + // reset retries on successful read + retries = 0 + + err = os.Remove(iamFile) + if errors.Is(err, fs.ErrNotExist) { + // racing with someone else updating + // keep retrying after backoff + time.Sleep(backoff) + continue + } + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("remove old iam file: %w", err) + } + + // save copy of data + datacopy := make([]byte, len(b)) + copy(datacopy, b) + + // make a backup copy in case we crash before update + // this is after remove, so there is a small window something + // can go wrong, but the remove should barrier other gateways + // from trying to write backup at the same time. Only one + // gateway will successfully remove the file. + os.WriteFile(filepath.Join(s.path, iamBackupFile), b, iamMode) + + b, err = update(b) + if err != nil { + // update failed, try to write old data back out + os.WriteFile(fname, datacopy, iamMode) + return fmt.Errorf("update iam data: %w", err) + } + + err = s.writeTempFile(b) + if err != nil { + // update failed, try to write old data back out + os.WriteFile(fname, datacopy, iamMode) + return err + } + + s.iamcache = b + s.iamvalid = true + s.iamexpire = time.Now().Add(cacheDuration) + break + } + + return nil +} + +func (s *IAMServiceInternal) writeTempFile(b []byte) error { + fname := filepath.Join(s.path, iamFile) + + f, err := os.CreateTemp(s.path, iamFile) + if err != nil { + return fmt.Errorf("create temp file: %w", err) + } + defer os.Remove(f.Name()) + + _, err = f.Write(b) + if err != nil { + return fmt.Errorf("write temp file: %w", err) + } + + err = os.Rename(f.Name(), fname) + if err != nil { + return fmt.Errorf("rename temp file: %w", err) + } + + return nil +} diff --git a/auth/iam_single.go b/auth/iam_single.go new file mode 100644 index 0000000..7b03386 --- /dev/null +++ b/auth/iam_single.go @@ -0,0 +1,40 @@ +// 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 auth + +import "fmt" + +// IAMServiceSingle manages the single tenant (root-only) IAM service +type IAMServiceSingle struct{} + +// CreateAccount not valid in single tenant mode +func (IAMServiceSingle) CreateAccount(account Account) error { + return fmt.Errorf("create user not valid in single tenant mode") +} + +// GetUserAccount no accounts in single tenant mode +func (IAMServiceSingle) GetUserAccount(access string) (Account, error) { + return Account{}, ErrNoSuchUser +} + +// DeleteUserAccount no accounts in single tenant mode +func (IAMServiceSingle) DeleteUserAccount(access string) error { + return ErrNoSuchUser +} + +// ListUserAccounts no accounts in single tenant mode +func (IAMServiceSingle) ListUserAccounts() ([]Account, error) { + return []Account{}, nil +} diff --git a/backend/posix/posix.go b/backend/posix/posix.go index 729816c..897816a 100644 --- a/backend/posix/posix.go +++ b/backend/posix/posix.go @@ -29,9 +29,7 @@ import ( "sort" "strconv" "strings" - "sync" "syscall" - "time" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3/types" @@ -48,19 +46,10 @@ type Posix struct { rootfd *os.File rootdir string - - mu sync.RWMutex - iamcache []byte - iamvalid bool - iamexpire time.Time } var _ backend.Backend = &Posix{} -var ( - cacheDuration = 5 * time.Minute -) - const ( metaTmpDir = ".sgwtmp" metaTmpMultipartDir = metaTmpDir + "/multipart" @@ -70,8 +59,6 @@ const ( contentTypeHdr = "content-type" contentEncHdr = "content-encoding" emptyMD5 = "d41d8cd98f00b204e9800998ecf8427e" - iamFile = "users.json" - iamBackupFile = "users.json.backup" aclkey = "user.acl" etagkey = "user.etag" ) @@ -148,6 +135,8 @@ func (p *Posix) ListBuckets(_ context.Context, owner string, isAdmin bool) (s3re sort.Sort(backend.ByBucketName(buckets)) + fmt.Println("ListAllMyBucketsResult owner:", owner) + return s3response.ListAllMyBucketsResult{ Buckets: s3response.ListAllMyBucketsList{ Bucket: buckets, @@ -1654,198 +1643,6 @@ func (p *Posix) ListBucketsAndOwners(ctx context.Context) (buckets []s3response. return buckets, nil } -const ( - iamMode = 0600 -) - -func (p *Posix) InitIAM() error { - p.mu.RLock() - defer p.mu.RUnlock() - - _, err := os.ReadFile(iamFile) - if errors.Is(err, fs.ErrNotExist) { - b, err := json.Marshal(auth.IAMConfig{AccessAccounts: map[string]auth.Account{}}) - if err != nil { - return fmt.Errorf("marshal default iam: %w", err) - } - err = os.WriteFile(iamFile, b, iamMode) - if err != nil { - return fmt.Errorf("write default iam: %w", err) - } - } - - return nil -} - -func (p *Posix) GetIAM() ([]byte, error) { - p.mu.RLock() - defer p.mu.RUnlock() - - if !p.iamvalid || !p.iamexpire.After(time.Now()) { - p.mu.RUnlock() - err := p.refreshIAM() - p.mu.RLock() - if err != nil { - return nil, err - } - } - - return p.iamcache, nil -} - -const ( - backoff = 100 * time.Millisecond - maxretry = 300 -) - -func (p *Posix) refreshIAM() error { - p.mu.Lock() - defer p.mu.Unlock() - - // We are going to be racing with other running gateways without any - // coordination. So we might find the file does not exist at times. - // For this case we need to retry for a while assuming the other gateway - // will eventually write the file. If it doesn't after the max retries, - // then we will return the error. - - retries := 0 - - for { - b, err := os.ReadFile(iamFile) - if errors.Is(err, fs.ErrNotExist) { - // racing with someone else updating - // keep retrying after backoff - retries++ - if retries < maxretry { - time.Sleep(backoff) - continue - } - return fmt.Errorf("read iam file: %w", err) - } - if err != nil { - return err - } - - p.iamcache = b - p.iamvalid = true - p.iamexpire = time.Now().Add(cacheDuration) - break - } - - return nil -} - -func (p *Posix) StoreIAM(update auth.UpdateAcctFunc) error { - p.mu.Lock() - defer p.mu.Unlock() - - // We are going to be racing with other running gateways without any - // coordination. So the strategy here is to read the current file data. - // If the file doesn't exist, then we assume someone else is currently - // updating the file. So we just need to keep retrying. We also need - // to make sure the data is consistent within a single update. So racing - // writes to a file would possibly leave this in some invalid state. - // We can get atomic updates with rename. If we read the data, update - // the data, write to a temp file, then rename the tempfile back to the - // data file. This should always result in a complete data image. - - // There is at least one unsolved failure mode here. - // If a gateway removes the data file and then crashes, all other - // gateways will retry forever thinking that the original will eventually - // write the file. - - retries := 0 - - for { - b, err := os.ReadFile(iamFile) - if errors.Is(err, fs.ErrNotExist) { - // racing with someone else updating - // keep retrying after backoff - retries++ - if retries < maxretry { - time.Sleep(backoff) - continue - } - - // we have been unsuccessful trying to read the iam file - // so this must be the case where something happened and - // the file did not get updated successfully, and probably - // isn't going to be. The recovery procedure would be to - // copy the backup file into place of the original. - return fmt.Errorf("no iam file, needs backup recovery") - } - if err != nil && !errors.Is(err, fs.ErrNotExist) { - return fmt.Errorf("read iam file: %w", err) - } - - // reset retries on successful read - retries = 0 - - err = os.Remove(iamFile) - if errors.Is(err, fs.ErrNotExist) { - // racing with someone else updating - // keep retrying after backoff - time.Sleep(backoff) - continue - } - if err != nil && !errors.Is(err, fs.ErrNotExist) { - return fmt.Errorf("remove old iam file: %w", err) - } - - // save copy of data - datacopy := make([]byte, len(b)) - copy(datacopy, b) - - // make a backup copy in case we crash before update - // this is after remove, so there is a small window something - // can go wrong, but the remove should barrier other gateways - // from trying to write backup at the same time. Only one - // gateway will successfully remove the file. - os.WriteFile(iamBackupFile, b, iamMode) - - b, err = update(b) - if err != nil { - // update failed, try to write old data back out - os.WriteFile(iamFile, datacopy, iamMode) - return fmt.Errorf("update iam data: %w", err) - } - - err = writeTempFile(b) - if err != nil { - // update failed, try to write old data back out - os.WriteFile(iamFile, datacopy, iamMode) - return err - } - - p.iamcache = b - p.iamvalid = true - p.iamexpire = time.Now().Add(cacheDuration) - break - } - - return nil -} - -func writeTempFile(b []byte) error { - f, err := os.CreateTemp(".", iamFile) - if err != nil { - return fmt.Errorf("create temp file: %w", err) - } - defer os.Remove(f.Name()) - - _, err = f.Write(b) - if err != nil { - return fmt.Errorf("write temp file: %w", err) - } - - err = os.Rename(f.Name(), iamFile) - if err != nil { - return fmt.Errorf("rename temp file: %w", err) - } - - return nil -} - func isNoAttr(err error) bool { if err == nil { return false diff --git a/cmd/versitygw/main.go b/cmd/versitygw/main.go index 519381e..6cb6e2c 100644 --- a/cmd/versitygw/main.go +++ b/cmd/versitygw/main.go @@ -44,6 +44,7 @@ var ( logWebhookURL string accessLog string debug bool + iamDir string ) var ( @@ -207,10 +208,15 @@ func initFlags() []cli.Flag { Destination: &natsTopic, Aliases: []string{"ent"}, }, + &cli.StringFlag{ + Name: "iam-dir", + Usage: "if defined, run internal iam service within this directory", + Destination: &iamDir, + }, } } -func runGateway(ctx *cli.Context, be backend.Backend, s auth.Storer) error { +func runGateway(ctx *cli.Context, be backend.Backend) error { // int32 max for 32 bit arch blimit := int64(2*1024*1024*1024 - 1) if strconv.IntSize > 32 { @@ -269,14 +275,18 @@ func runGateway(ctx *cli.Context, be backend.Backend, s auth.Storer) error { admOpts = append(admOpts, s3api.WithAdminSrvTLS(cert)) } - err := s.InitIAM() - if err != nil { - return fmt.Errorf("init iam: %w", err) - } - - iam, err := auth.NewInternal(s) - if err != nil { - return fmt.Errorf("setup internal iam service: %w", err) + var iam auth.IAMService + switch { + case iamDir != "": + var err error + iam, err = auth.NewInternal(iamDir) + if err != nil { + return fmt.Errorf("setup internal iam service: %w", err) + } + default: + // default gateway to single user mode when + // no other iam service configured + iam = auth.IAMServiceSingle{} } logger, err := s3log.InitLogger(&s3log.LogConfig{ diff --git a/cmd/versitygw/posix.go b/cmd/versitygw/posix.go index 8928b8f..cb5219c 100644 --- a/cmd/versitygw/posix.go +++ b/cmd/versitygw/posix.go @@ -49,5 +49,5 @@ func runPosix(ctx *cli.Context) error { return fmt.Errorf("init posix: %v", err) } - return runGateway(ctx, be, be) + return runGateway(ctx, be) } diff --git a/cmd/versitygw/scoutfs.go b/cmd/versitygw/scoutfs.go index 7327d4c..2ad1d0c 100644 --- a/cmd/versitygw/scoutfs.go +++ b/cmd/versitygw/scoutfs.go @@ -69,5 +69,5 @@ func runScoutfs(ctx *cli.Context) error { return fmt.Errorf("init scoutfs: %v", err) } - return runGateway(ctx, be, be) + return runGateway(ctx, be) } diff --git a/runtests.sh b/runtests.sh index a186f36..178f223 100755 --- a/runtests.sh +++ b/runtests.sh @@ -6,7 +6,7 @@ rm -rf /tmp/covdata mkdir /tmp/covdata # run server in background -GOCOVERDIR=/tmp/covdata ./versitygw -a user -s pass posix /tmp/gw & +GOCOVERDIR=/tmp/covdata ./versitygw -a user -s pass --iam-dir /tmp/gw posix /tmp/gw & GW_PID=$! # wait a second for server to start up diff --git a/s3api/controllers/admin.go b/s3api/controllers/admin.go index 483f606..5f9446e 100644 --- a/s3api/controllers/admin.go +++ b/s3api/controllers/admin.go @@ -33,18 +33,18 @@ func NewAdminController(iam auth.IAMService, be backend.Backend) AdminController func (c AdminController) CreateUser(ctx *fiber.Ctx) error { access, secret, role := ctx.Query("access"), ctx.Query("secret"), ctx.Query("role") - requesterRole := ctx.Locals("role").(string) + acct := ctx.Locals("account").(auth.Account) - if requesterRole != "admin" { + if acct.Role != "admin" { return fmt.Errorf("access denied: only admin users have access to this resource") } if role != "user" && role != "admin" { return fmt.Errorf("invalid parameters: user role have to be one of the following: 'user', 'admin'") } - user := auth.Account{Secret: secret, Role: role} + user := auth.Account{Access: access, Secret: secret, Role: role} - err := c.iam.CreateAccount(access, user) + err := c.iam.CreateAccount(user) if err != nil { return fmt.Errorf("failed to create a user: %w", err) } @@ -54,8 +54,8 @@ func (c AdminController) CreateUser(ctx *fiber.Ctx) error { func (c AdminController) DeleteUser(ctx *fiber.Ctx) error { access := ctx.Query("access") - requesterRole := ctx.Locals("role").(string) - if requesterRole != "admin" { + acct := ctx.Locals("account").(auth.Account) + if acct.Role != "admin" { return fmt.Errorf("access denied: only admin users have access to this resource") } @@ -68,8 +68,8 @@ func (c AdminController) DeleteUser(ctx *fiber.Ctx) error { } func (c AdminController) ListUsers(ctx *fiber.Ctx) error { - role := ctx.Locals("role").(string) - if role != "admin" { + acct := ctx.Locals("account").(auth.Account) + if acct.Role != "admin" { return fmt.Errorf("access denied: only admin users have access to this resource") } accs, err := c.iam.ListUserAccounts() @@ -81,8 +81,8 @@ func (c AdminController) ListUsers(ctx *fiber.Ctx) error { } func (c AdminController) ChangeBucketOwner(ctx *fiber.Ctx) error { - role := ctx.Locals("role").(string) - if role != "admin" { + acct := ctx.Locals("account").(auth.Account) + if acct.Role != "admin" { return fmt.Errorf("access denied: only admin users have access to this resource") } owner := ctx.Query("owner") @@ -105,8 +105,8 @@ func (c AdminController) ChangeBucketOwner(ctx *fiber.Ctx) error { } func (c AdminController) ListBuckets(ctx *fiber.Ctx) error { - role := ctx.Locals("role").(string) - if role != "admin" { + acct := ctx.Locals("account").(auth.Account) + if acct.Role != "admin" { return fmt.Errorf("access denied: only admin users have access to this resource") } diff --git a/s3api/controllers/admin_test.go b/s3api/controllers/admin_test.go index fc918a8..09478b3 100644 --- a/s3api/controllers/admin_test.go +++ b/s3api/controllers/admin_test.go @@ -33,7 +33,7 @@ func TestAdminController_CreateUser(t *testing.T) { adminController := AdminController{ iam: &IAMServiceMock{ - CreateAccountFunc: func(access string, account auth.Account) error { + CreateAccountFunc: func(account auth.Account) error { return nil }, }, @@ -42,7 +42,7 @@ func TestAdminController_CreateUser(t *testing.T) { app := fiber.New() app.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("role", "admin") + ctx.Locals("account", auth.Account{Access: "admin1", Secret: "secret", Role: "admin"}) return ctx.Next() }) @@ -51,7 +51,7 @@ func TestAdminController_CreateUser(t *testing.T) { appErr := fiber.New() appErr.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("role", "user") + ctx.Locals("account", auth.Account{Access: "user1", Secret: "secret", Role: "user"}) return ctx.Next() }) @@ -121,7 +121,7 @@ func TestAdminController_DeleteUser(t *testing.T) { app := fiber.New() app.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("role", "admin") + ctx.Locals("account", auth.Account{Access: "admin1", Secret: "secret", Role: "admin"}) return ctx.Next() }) @@ -130,7 +130,7 @@ func TestAdminController_DeleteUser(t *testing.T) { appErr := fiber.New() appErr.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("role", "user") + ctx.Locals("account", auth.Account{Access: "user1", Secret: "secret", Role: "user"}) return ctx.Next() }) @@ -199,7 +199,7 @@ func TestAdminController_ListUsers(t *testing.T) { appErr := fiber.New() appErr.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("role", "admin") + ctx.Locals("account", auth.Account{Access: "admin1", Secret: "secret", Role: "admin"}) return ctx.Next() }) @@ -208,7 +208,7 @@ func TestAdminController_ListUsers(t *testing.T) { appRoleErr := fiber.New() appRoleErr.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("role", "user") + ctx.Locals("account", auth.Account{Access: "user1", Secret: "secret", Role: "user"}) return ctx.Next() }) @@ -217,7 +217,7 @@ func TestAdminController_ListUsers(t *testing.T) { appSucc := fiber.New() appSucc.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("role", "admin") + ctx.Locals("account", auth.Account{Access: "admin1", Secret: "secret", Role: "admin"}) return ctx.Next() }) @@ -307,7 +307,7 @@ func TestAdminController_ChangeBucketOwner(t *testing.T) { app := fiber.New() app.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("role", "admin") + ctx.Locals("account", auth.Account{Access: "admin1", Secret: "secret", Role: "admin"}) return ctx.Next() }) @@ -316,7 +316,7 @@ func TestAdminController_ChangeBucketOwner(t *testing.T) { appRoleErr := fiber.New() appRoleErr.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("role", "user") + ctx.Locals("account", auth.Account{Access: "user1", Secret: "secret", Role: "user"}) return ctx.Next() }) @@ -325,7 +325,7 @@ func TestAdminController_ChangeBucketOwner(t *testing.T) { appIamErr := fiber.New() appIamErr.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("role", "admin") + ctx.Locals("account", auth.Account{Access: "admin1", Secret: "secret", Role: "admin"}) return ctx.Next() }) @@ -334,7 +334,7 @@ func TestAdminController_ChangeBucketOwner(t *testing.T) { appIamNoSuchUser := fiber.New() appIamNoSuchUser.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("role", "admin") + ctx.Locals("account", auth.Account{Access: "admin1", Secret: "secret", Role: "admin"}) return ctx.Next() }) @@ -412,7 +412,7 @@ func TestAdminController_ListBuckets(t *testing.T) { app := fiber.New() app.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("role", "admin") + ctx.Locals("account", auth.Account{Access: "admin1", Secret: "secret", Role: "admin"}) return ctx.Next() }) @@ -421,7 +421,7 @@ func TestAdminController_ListBuckets(t *testing.T) { appRoleErr := fiber.New() appRoleErr.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("role", "user") + ctx.Locals("account", auth.Account{Access: "user1", Secret: "secret", Role: "user"}) return ctx.Next() }) diff --git a/s3api/controllers/base.go b/s3api/controllers/base.go index f64820b..5b3a308 100644 --- a/s3api/controllers/base.go +++ b/s3api/controllers/base.go @@ -53,8 +53,8 @@ func New(be backend.Backend, iam auth.IAMService, logger s3log.AuditLogger, evs } func (c S3ApiController) ListBuckets(ctx *fiber.Ctx) error { - access, role := ctx.Locals("access").(string), ctx.Locals("role").(string) - res, err := c.be.ListBuckets(ctx.Context(), access, role == "admin") + acct := ctx.Locals("account").(auth.Account) + res, err := c.be.ListBuckets(ctx.Context(), acct.Access, acct.Role == "admin") return SendXMLResponse(ctx, res, err, &MetaOpts{Logger: c.logger, Action: "ListBucket"}) } @@ -66,7 +66,7 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error { maxParts := ctx.QueryInt("max-parts", 0) partNumberMarker := ctx.Query("part-number-marker") acceptRange := ctx.Get("Range") - access := ctx.Locals("access").(string) + acct := ctx.Locals("account").(auth.Account) isRoot := ctx.Locals("isRoot").(bool) parsedAcl := ctx.Locals("parsedAcl").(auth.ACL) if keyEnd != "" { @@ -74,7 +74,7 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error { } if ctx.Request().URI().QueryArgs().Has("tagging") { - if err := auth.VerifyACL(parsedAcl, access, "READ", isRoot); err != nil { + if err := auth.VerifyACL(parsedAcl, acct.Access, "READ", isRoot); err != nil { return SendXMLResponse(ctx, nil, err, &MetaOpts{Logger: c.logger, Action: "GetObjectTagging", BucketOwner: parsedAcl.Owner}) } @@ -102,7 +102,7 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error { } } - if err := auth.VerifyACL(parsedAcl, access, "READ", isRoot); err != nil { + if err := auth.VerifyACL(parsedAcl, acct.Access, "READ", isRoot); err != nil { return SendXMLResponse(ctx, nil, err, &MetaOpts{Logger: c.logger, Action: "ListParts", BucketOwner: parsedAcl.Owner}) } @@ -117,7 +117,7 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error { } if ctx.Request().URI().QueryArgs().Has("acl") { - if err := auth.VerifyACL(parsedAcl, access, "READ_ACP", isRoot); err != nil { + if err := auth.VerifyACL(parsedAcl, acct.Access, "READ_ACP", isRoot); err != nil { return SendXMLResponse(ctx, nil, err, &MetaOpts{Logger: c.logger, Action: "GetObjectAcl", BucketOwner: parsedAcl.Owner}) } res, err := c.be.GetObjectAcl(ctx.Context(), &s3.GetObjectAclInput{ @@ -128,7 +128,7 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error { } if attrs := ctx.Get("X-Amz-Object-Attributes"); attrs != "" { - if err := auth.VerifyACL(parsedAcl, access, "READ", isRoot); err != nil { + if err := auth.VerifyACL(parsedAcl, acct.Access, "READ", isRoot); err != nil { return SendXMLResponse(ctx, nil, err, &MetaOpts{Logger: c.logger, Action: "GetObjectAttributes", BucketOwner: parsedAcl.Owner}) } var oattrs []types.ObjectAttributes @@ -143,7 +143,7 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error { return SendXMLResponse(ctx, res, err, &MetaOpts{Logger: c.logger, Action: "GetObjectAttributes", BucketOwner: parsedAcl.Owner}) } - if err := auth.VerifyACL(parsedAcl, access, "READ_ACP", isRoot); err != nil { + if err := auth.VerifyACL(parsedAcl, acct.Access, "READ_ACP", isRoot); err != nil { return SendResponse(ctx, err, &MetaOpts{Logger: c.logger, Action: "GetObject", BucketOwner: parsedAcl.Owner}) } @@ -224,12 +224,12 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error { keyMarker := ctx.Query("key-marker") maxUploadsStr := ctx.Query("max-uploads") uploadIdMarker := ctx.Query("upload-id-marker") - access := ctx.Locals("access").(string) + acct := ctx.Locals("account").(auth.Account) isRoot := ctx.Locals("isRoot").(bool) parsedAcl := ctx.Locals("parsedAcl").(auth.ACL) if ctx.Request().URI().QueryArgs().Has("acl") { - if err := auth.VerifyACL(parsedAcl, access, "READ_ACP", isRoot); err != nil { + if err := auth.VerifyACL(parsedAcl, acct.Access, "READ_ACP", isRoot); err != nil { return SendXMLResponse(ctx, nil, err, &MetaOpts{Logger: c.logger, Action: "GetBucketAcl", BucketOwner: parsedAcl.Owner}) } @@ -243,7 +243,7 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error { } if ctx.Request().URI().QueryArgs().Has("uploads") { - if err := auth.VerifyACL(parsedAcl, access, "READ", isRoot); err != nil { + if err := auth.VerifyACL(parsedAcl, acct.Access, "READ", isRoot); err != nil { return SendXMLResponse(ctx, nil, err, &MetaOpts{Logger: c.logger, Action: "ListMultipartUploads", BucketOwner: parsedAcl.Owner}) } maxUploads, err := utils.ParseUint(maxUploadsStr) @@ -266,7 +266,7 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error { } if ctx.QueryInt("list-type") == 2 { - if err := auth.VerifyACL(parsedAcl, access, "READ", isRoot); err != nil { + if err := auth.VerifyACL(parsedAcl, acct.Access, "READ", isRoot); err != nil { return SendXMLResponse(ctx, nil, err, &MetaOpts{Logger: c.logger, Action: "ListObjectsV2", BucketOwner: parsedAcl.Owner}) } maxkeys, err := utils.ParseUint(maxkeysStr) @@ -287,7 +287,7 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error { return SendXMLResponse(ctx, res, err, &MetaOpts{Logger: c.logger, Action: "ListObjectsV2", BucketOwner: parsedAcl.Owner}) } - if err := auth.VerifyACL(parsedAcl, access, "READ", isRoot); err != nil { + if err := auth.VerifyACL(parsedAcl, acct.Access, "READ", isRoot); err != nil { return SendXMLResponse(ctx, nil, err, &MetaOpts{Logger: c.logger, Action: "ListObjects", BucketOwner: parsedAcl.Owner}) } @@ -311,7 +311,7 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error { } func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error { - bucket, acl, grantFullControl, grantRead, grantReadACP, granWrite, grantWriteACP, access, isRoot := + bucket, acl, grantFullControl, grantRead, grantReadACP, granWrite, grantWriteACP, acct, isRoot := ctx.Params("bucket"), ctx.Get("X-Amz-Acl"), ctx.Get("X-Amz-Grant-Full-Control"), @@ -319,7 +319,7 @@ func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error { ctx.Get("X-Amz-Grant-Read-Acp"), ctx.Get("X-Amz-Grant-Write"), ctx.Get("X-Amz-Grant-Write-Acp"), - ctx.Locals("access").(string), + ctx.Locals("account").(auth.Account), ctx.Locals("isRoot").(bool) grants := grantFullControl + grantRead + grantReadACP + granWrite + grantWriteACP @@ -329,7 +329,7 @@ func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error { var accessControlPolicy auth.AccessControlPolicy parsedAcl := ctx.Locals("parsedAcl").(auth.ACL) - if err := auth.VerifyACL(parsedAcl, access, "WRITE_ACP", isRoot); err != nil { + if err := auth.VerifyACL(parsedAcl, acct.Access, "WRITE_ACP", isRoot); err != nil { return SendResponse(ctx, err, &MetaOpts{Logger: c.logger, Action: "PutBucketAcl", BucketOwner: parsedAcl.Owner}) } @@ -391,9 +391,9 @@ func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error { err := c.be.CreateBucket(ctx.Context(), &s3.CreateBucketInput{ Bucket: &bucket, - ObjectOwnership: types.ObjectOwnership(access), + ObjectOwnership: types.ObjectOwnership(acct.Access), }) - return SendResponse(ctx, err, &MetaOpts{Logger: c.logger, Action: "CreateBucket", BucketOwner: access}) + return SendResponse(ctx, err, &MetaOpts{Logger: c.logger, Action: "CreateBucket", BucketOwner: acct.Access}) } func (c S3ApiController) PutActions(ctx *fiber.Ctx) error { @@ -401,7 +401,7 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error { keyStart := ctx.Params("key") keyEnd := ctx.Params("*1") uploadId := ctx.Query("uploadId") - access := ctx.Locals("access").(string) + acct := ctx.Locals("account").(auth.Account) isRoot := ctx.Locals("isRoot").(bool) parsedAcl := ctx.Locals("parsedAcl").(auth.ACL) tagging := ctx.Get("x-amz-tagging") @@ -452,7 +452,7 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error { tags[tag.Key] = tag.Value } - if err := auth.VerifyACL(parsedAcl, access, "WRITE", isRoot); err != nil { + if err := auth.VerifyACL(parsedAcl, acct.Access, "WRITE", isRoot); err != nil { return SendResponse(ctx, err, &MetaOpts{Logger: c.logger, Action: "PutObjectTagging", BucketOwner: parsedAcl.Owner}) } @@ -490,7 +490,7 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error { return SendResponse(ctx, s3err.GetAPIError(s3err.ErrInvalidPart), &MetaOpts{Logger: c.logger, Action: "UploadPart", BucketOwner: parsedAcl.Owner}) } - if err := auth.VerifyACL(parsedAcl, access, "WRITE", isRoot); err != nil { + if err := auth.VerifyACL(parsedAcl, acct.Access, "WRITE", isRoot); err != nil { return SendResponse(ctx, err, &MetaOpts{Logger: c.logger, Action: "UploadPart", BucketOwner: parsedAcl.Owner}) } @@ -574,7 +574,7 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error { } if copySource != "" { - if err := auth.VerifyACL(parsedAcl, access, "WRITE", isRoot); err != nil { + if err := auth.VerifyACL(parsedAcl, acct.Access, "WRITE", isRoot); err != nil { return SendXMLResponse(ctx, nil, err, &MetaOpts{Logger: c.logger, Action: "CopyObject", BucketOwner: parsedAcl.Owner}) } @@ -604,7 +604,7 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error { CopySourceIfNoneMatch: ©SrcIfNoneMatch, CopySourceIfModifiedSince: &mtime, CopySourceIfUnmodifiedSince: &umtime, - ExpectedBucketOwner: &access, + ExpectedBucketOwner: &acct.Access, Metadata: metadata, }) if err == nil { @@ -628,7 +628,7 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error { metadata := utils.GetUserMetaData(&ctx.Request().Header) - if err := auth.VerifyACL(parsedAcl, access, "WRITE", isRoot); err != nil { + if err := auth.VerifyACL(parsedAcl, acct.Access, "WRITE", isRoot); err != nil { return SendResponse(ctx, err, &MetaOpts{Logger: c.logger, Action: "PutObject", BucketOwner: parsedAcl.Owner}) } @@ -659,9 +659,9 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error { } func (c S3ApiController) DeleteBucket(ctx *fiber.Ctx) error { - bucket, access, isRoot, parsedAcl := ctx.Params("bucket"), ctx.Locals("access").(string), ctx.Locals("isRoot").(bool), ctx.Locals("parsedAcl").(auth.ACL) + bucket, acct, isRoot, parsedAcl := ctx.Params("bucket"), ctx.Locals("account").(auth.Account), ctx.Locals("isRoot").(bool), ctx.Locals("parsedAcl").(auth.ACL) - if err := auth.VerifyACL(parsedAcl, access, "WRITE", isRoot); err != nil { + if err := auth.VerifyACL(parsedAcl, acct.Access, "WRITE", isRoot); err != nil { return SendResponse(ctx, err, &MetaOpts{Logger: c.logger, Action: "DeleteBucket", BucketOwner: parsedAcl.Owner}) } @@ -672,14 +672,14 @@ func (c S3ApiController) DeleteBucket(ctx *fiber.Ctx) error { } func (c S3ApiController) DeleteObjects(ctx *fiber.Ctx) error { - bucket, access, isRoot, parsedAcl := ctx.Params("bucket"), ctx.Locals("access").(string), ctx.Locals("isRoot").(bool), ctx.Locals("parsedAcl").(auth.ACL) + bucket, acct, isRoot, parsedAcl := ctx.Params("bucket"), ctx.Locals("account").(auth.Account), ctx.Locals("isRoot").(bool), ctx.Locals("parsedAcl").(auth.ACL) var dObj s3response.DeleteObjects if err := xml.Unmarshal(ctx.Body(), &dObj); err != nil { return SendResponse(ctx, s3err.GetAPIError(s3err.ErrInvalidRequest), &MetaOpts{Logger: c.logger, Action: "DeleteObjects", BucketOwner: parsedAcl.Owner}) } - if err := auth.VerifyACL(parsedAcl, access, "WRITE", isRoot); err != nil { + if err := auth.VerifyACL(parsedAcl, acct.Access, "WRITE", isRoot); err != nil { return SendResponse(ctx, err, &MetaOpts{Logger: c.logger, Action: "DeleteObjects", BucketOwner: parsedAcl.Owner}) } @@ -697,7 +697,7 @@ func (c S3ApiController) DeleteActions(ctx *fiber.Ctx) error { key := ctx.Params("key") keyEnd := ctx.Params("*1") uploadId := ctx.Query("uploadId") - access := ctx.Locals("access").(string) + acct := ctx.Locals("account").(auth.Account) isRoot := ctx.Locals("isRoot").(bool) parsedAcl := ctx.Locals("parsedAcl").(auth.ACL) @@ -706,7 +706,7 @@ func (c S3ApiController) DeleteActions(ctx *fiber.Ctx) error { } if ctx.Request().URI().QueryArgs().Has("tagging") { - if err := auth.VerifyACL(parsedAcl, access, "WRITE", isRoot); err != nil { + if err := auth.VerifyACL(parsedAcl, acct.Access, "WRITE", isRoot); err != nil { return SendResponse(ctx, err, &MetaOpts{Logger: c.logger, Action: "RemoveObjectTagging", BucketOwner: parsedAcl.Owner}) } @@ -724,7 +724,7 @@ func (c S3ApiController) DeleteActions(ctx *fiber.Ctx) error { if uploadId != "" { expectedBucketOwner, requestPayer := ctx.Get("X-Amz-Expected-Bucket-Owner"), ctx.Get("X-Amz-Request-Payer") - if err := auth.VerifyACL(parsedAcl, access, "WRITE", isRoot); err != nil { + if err := auth.VerifyACL(parsedAcl, acct.Access, "WRITE", isRoot); err != nil { return SendResponse(ctx, err, &MetaOpts{Logger: c.logger, Action: "AbortMultipartUpload", BucketOwner: parsedAcl.Owner}) } @@ -738,7 +738,7 @@ func (c S3ApiController) DeleteActions(ctx *fiber.Ctx) error { return SendResponse(ctx, err, &MetaOpts{Logger: c.logger, Action: "AbortMultipartUpload", BucketOwner: parsedAcl.Owner, Status: 204}) } - if err := auth.VerifyACL(parsedAcl, access, "WRITE", isRoot); err != nil { + if err := auth.VerifyACL(parsedAcl, acct.Access, "WRITE", isRoot); err != nil { return SendResponse(ctx, err, &MetaOpts{Logger: c.logger, Action: "DeleteObject", BucketOwner: parsedAcl.Owner}) } @@ -757,9 +757,9 @@ func (c S3ApiController) DeleteActions(ctx *fiber.Ctx) error { } func (c S3ApiController) HeadBucket(ctx *fiber.Ctx) error { - bucket, access, isRoot, parsedAcl := ctx.Params("bucket"), ctx.Locals("access").(string), ctx.Locals("isRoot").(bool), ctx.Locals("parsedAcl").(auth.ACL) + bucket, acct, isRoot, parsedAcl := ctx.Params("bucket"), ctx.Locals("account").(auth.Account), ctx.Locals("isRoot").(bool), ctx.Locals("parsedAcl").(auth.ACL) - if err := auth.VerifyACL(parsedAcl, access, "READ", isRoot); err != nil { + if err := auth.VerifyACL(parsedAcl, acct.Access, "READ", isRoot); err != nil { return SendResponse(ctx, err, &MetaOpts{Logger: c.logger, Action: "HeadBucket", BucketOwner: parsedAcl.Owner}) } @@ -775,14 +775,14 @@ const ( ) func (c S3ApiController) HeadObject(ctx *fiber.Ctx) error { - bucket, access, isRoot, parsedAcl := ctx.Params("bucket"), ctx.Locals("access").(string), ctx.Locals("isRoot").(bool), ctx.Locals("parsedAcl").(auth.ACL) + bucket, acct, isRoot, parsedAcl := ctx.Params("bucket"), ctx.Locals("account").(auth.Account), ctx.Locals("isRoot").(bool), ctx.Locals("parsedAcl").(auth.ACL) key := ctx.Params("key") keyEnd := ctx.Params("*1") if keyEnd != "" { key = strings.Join([]string{key, keyEnd}, "/") } - if err := auth.VerifyACL(parsedAcl, access, "READ", isRoot); err != nil { + if err := auth.VerifyACL(parsedAcl, acct.Access, "READ", isRoot); err != nil { return SendResponse(ctx, err, &MetaOpts{Logger: c.logger, Action: "HeadObject", BucketOwner: parsedAcl.Owner}) } @@ -841,7 +841,7 @@ func (c S3ApiController) CreateActions(ctx *fiber.Ctx) error { key := ctx.Params("key") keyEnd := ctx.Params("*1") uploadId := ctx.Query("uploadId") - access := ctx.Locals("access").(string) + acct := ctx.Locals("account").(auth.Account) isRoot := ctx.Locals("isRoot").(bool) parsedAcl := ctx.Locals("parsedAcl").(auth.ACL) @@ -856,7 +856,7 @@ func (c S3ApiController) CreateActions(ctx *fiber.Ctx) error { return SendResponse(ctx, err, &MetaOpts{Logger: c.logger, Action: "RestoreObject", BucketOwner: parsedAcl.Owner}) } - if err := auth.VerifyACL(parsedAcl, access, "WRITE", isRoot); err != nil { + if err := auth.VerifyACL(parsedAcl, acct.Access, "WRITE", isRoot); err != nil { return SendResponse(ctx, err, &MetaOpts{Logger: c.logger, Action: "RestoreObject", BucketOwner: parsedAcl.Owner}) } @@ -884,7 +884,7 @@ func (c S3ApiController) CreateActions(ctx *fiber.Ctx) error { }) } - if err := auth.VerifyACL(parsedAcl, access, "WRITE", isRoot); err != nil { + if err := auth.VerifyACL(parsedAcl, acct.Access, "WRITE", isRoot); err != nil { return SendXMLResponse(ctx, nil, err, &MetaOpts{Logger: c.logger, Action: "SelectObjectContent", BucketOwner: parsedAcl.Owner}) } @@ -914,7 +914,7 @@ func (c S3ApiController) CreateActions(ctx *fiber.Ctx) error { }) } - if err := auth.VerifyACL(parsedAcl, access, "WRITE", isRoot); err != nil { + if err := auth.VerifyACL(parsedAcl, acct.Access, "WRITE", isRoot); err != nil { return SendXMLResponse(ctx, nil, err, &MetaOpts{Logger: c.logger, Action: "CompleteMultipartUpload", BucketOwner: parsedAcl.Owner}) } @@ -945,7 +945,7 @@ func (c S3ApiController) CreateActions(ctx *fiber.Ctx) error { } } - if err := auth.VerifyACL(parsedAcl, access, "WRITE", isRoot); err != nil { + if err := auth.VerifyACL(parsedAcl, acct.Access, "WRITE", isRoot); err != nil { return SendXMLResponse(ctx, nil, err, &MetaOpts{Logger: c.logger, Action: "CreateMultipartUpload", BucketOwner: parsedAcl.Owner}) } diff --git a/s3api/controllers/base_test.go b/s3api/controllers/base_test.go index c410e85..11e7f92 100644 --- a/s3api/controllers/base_test.go +++ b/s3api/controllers/base_test.go @@ -98,8 +98,7 @@ func TestS3ApiController_ListBuckets(t *testing.T) { } app.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("access", "valid access") - ctx.Locals("role", "admin") + ctx.Locals("account", auth.Account{Access: "valid access", Role: "admin:"}) ctx.Locals("isDebug", false) return ctx.Next() }) @@ -116,8 +115,7 @@ func TestS3ApiController_ListBuckets(t *testing.T) { } appErr.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("access", "valid access") - ctx.Locals("role", "admin") + ctx.Locals("account", auth.Account{Access: "valid access", Role: "admin:"}) ctx.Locals("isDebug", false) return ctx.Next() }) @@ -207,7 +205,7 @@ func TestS3ApiController_GetActions(t *testing.T) { }, } app.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("access", "valid access") + ctx.Locals("account", auth.Account{Access: "valid access"}) ctx.Locals("isRoot", true) ctx.Locals("isDebug", false) ctx.Locals("parsedAcl", auth.ACL{}) @@ -347,7 +345,7 @@ func TestS3ApiController_ListActions(t *testing.T) { } app.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("access", "valid access") + ctx.Locals("account", auth.Account{Access: "valid access"}) ctx.Locals("isRoot", true) ctx.Locals("isDebug", false) ctx.Locals("parsedAcl", auth.ACL{}) @@ -369,7 +367,7 @@ func TestS3ApiController_ListActions(t *testing.T) { } appError := fiber.New() appError.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("access", "valid access") + ctx.Locals("account", auth.Account{Access: "valid access"}) ctx.Locals("isRoot", true) ctx.Locals("isDebug", false) ctx.Locals("parsedAcl", auth.ACL{}) @@ -507,7 +505,7 @@ func TestS3ApiController_PutBucketActions(t *testing.T) { } // Mock ctx.Locals app.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("access", "valid access") + ctx.Locals("account", auth.Account{Access: "valid access"}) ctx.Locals("isRoot", true) ctx.Locals("isDebug", false) ctx.Locals("parsedAcl", auth.ACL{Owner: "valid access"}) @@ -680,7 +678,7 @@ func TestS3ApiController_PutActions(t *testing.T) { }, } app.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("access", "valid access") + ctx.Locals("account", auth.Account{Access: "valid access"}) ctx.Locals("isRoot", true) ctx.Locals("isDebug", false) ctx.Locals("parsedAcl", auth.ACL{}) @@ -879,7 +877,7 @@ func TestS3ApiController_DeleteBucket(t *testing.T) { } app.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("access", "valid access") + ctx.Locals("account", auth.Account{Access: "valid access"}) ctx.Locals("isRoot", true) ctx.Locals("isDebug", false) ctx.Locals("parsedAcl", auth.ACL{}) @@ -936,7 +934,7 @@ func TestS3ApiController_DeleteObjects(t *testing.T) { } app.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("access", "valid access") + ctx.Locals("account", auth.Account{Access: "valid access"}) ctx.Locals("isRoot", true) ctx.Locals("isDebug", false) ctx.Locals("parsedAcl", auth.ACL{}) @@ -1013,7 +1011,7 @@ func TestS3ApiController_DeleteActions(t *testing.T) { } app.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("access", "valid access") + ctx.Locals("account", auth.Account{Access: "valid access"}) ctx.Locals("isRoot", true) ctx.Locals("isDebug", false) ctx.Locals("parsedAcl", auth.ACL{}) @@ -1034,7 +1032,7 @@ func TestS3ApiController_DeleteActions(t *testing.T) { }} appErr.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("access", "valid access") + ctx.Locals("account", auth.Account{Access: "valid access"}) ctx.Locals("isRoot", true) ctx.Locals("isDebug", false) ctx.Locals("parsedAcl", auth.ACL{}) @@ -1117,7 +1115,7 @@ func TestS3ApiController_HeadBucket(t *testing.T) { } app.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("access", "valid access") + ctx.Locals("account", auth.Account{Access: "valid access"}) ctx.Locals("isRoot", true) ctx.Locals("isDebug", false) ctx.Locals("parsedAcl", auth.ACL{}) @@ -1140,7 +1138,7 @@ func TestS3ApiController_HeadBucket(t *testing.T) { } appErr.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("access", "valid access") + ctx.Locals("account", auth.Account{Access: "valid access"}) ctx.Locals("isRoot", true) ctx.Locals("isDebug", false) ctx.Locals("parsedAcl", auth.ACL{}) @@ -1219,7 +1217,7 @@ func TestS3ApiController_HeadObject(t *testing.T) { } app.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("access", "valid access") + ctx.Locals("account", auth.Account{Access: "valid access"}) ctx.Locals("isRoot", true) ctx.Locals("isDebug", false) ctx.Locals("parsedAcl", auth.ACL{}) @@ -1242,7 +1240,7 @@ func TestS3ApiController_HeadObject(t *testing.T) { } appErr.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("access", "valid access") + ctx.Locals("account", auth.Account{Access: "valid access"}) ctx.Locals("isRoot", true) ctx.Locals("isDebug", false) ctx.Locals("parsedAcl", auth.ACL{}) @@ -1322,7 +1320,7 @@ func TestS3ApiController_CreateActions(t *testing.T) { ` app.Use(func(ctx *fiber.Ctx) error { - ctx.Locals("access", "valid access") + ctx.Locals("account", auth.Account{Access: "valid access"}) ctx.Locals("isRoot", true) ctx.Locals("isDebug", false) ctx.Locals("parsedAcl", auth.ACL{}) diff --git a/s3api/controllers/iam_moq_test.go b/s3api/controllers/iam_moq_test.go index f51493b..686bb88 100644 --- a/s3api/controllers/iam_moq_test.go +++ b/s3api/controllers/iam_moq_test.go @@ -18,7 +18,7 @@ var _ auth.IAMService = &IAMServiceMock{} // // // make and configure a mocked auth.IAMService // mockedIAMService := &IAMServiceMock{ -// CreateAccountFunc: func(access string, account auth.Account) error { +// CreateAccountFunc: func(account auth.Account) error { // panic("mock out the CreateAccount method") // }, // DeleteUserAccountFunc: func(access string) error { @@ -38,7 +38,7 @@ var _ auth.IAMService = &IAMServiceMock{} // } type IAMServiceMock struct { // CreateAccountFunc mocks the CreateAccount method. - CreateAccountFunc func(access string, account auth.Account) error + CreateAccountFunc func(account auth.Account) error // DeleteUserAccountFunc mocks the DeleteUserAccount method. DeleteUserAccountFunc func(access string) error @@ -53,8 +53,6 @@ type IAMServiceMock struct { calls struct { // CreateAccount holds details about calls to the CreateAccount method. CreateAccount []struct { - // Access is the access argument value. - Access string // Account is the account argument value. Account auth.Account } @@ -79,21 +77,19 @@ type IAMServiceMock struct { } // CreateAccount calls CreateAccountFunc. -func (mock *IAMServiceMock) CreateAccount(access string, account auth.Account) error { +func (mock *IAMServiceMock) CreateAccount(account auth.Account) error { if mock.CreateAccountFunc == nil { panic("IAMServiceMock.CreateAccountFunc: method is nil but IAMService.CreateAccount was just called") } callInfo := struct { - Access string Account auth.Account }{ - Access: access, Account: account, } mock.lockCreateAccount.Lock() mock.calls.CreateAccount = append(mock.calls.CreateAccount, callInfo) mock.lockCreateAccount.Unlock() - return mock.CreateAccountFunc(access, account) + return mock.CreateAccountFunc(account) } // CreateAccountCalls gets all the calls that were made to CreateAccount. @@ -101,11 +97,9 @@ func (mock *IAMServiceMock) CreateAccount(access string, account auth.Account) e // // len(mockedIAMService.CreateAccountCalls()) func (mock *IAMServiceMock) CreateAccountCalls() []struct { - Access string Account auth.Account } { var calls []struct { - Access string Account auth.Account } mock.lockCreateAccount.RLock() diff --git a/s3api/middlewares/acl-parser.go b/s3api/middlewares/acl-parser.go index db9b6ad..c6b58d0 100644 --- a/s3api/middlewares/acl-parser.go +++ b/s3api/middlewares/acl-parser.go @@ -28,7 +28,7 @@ import ( func AclParser(be backend.Backend, logger s3log.AuditLogger) fiber.Handler { return func(ctx *fiber.Ctx) error { - isRoot, access := ctx.Locals("isRoot").(bool), ctx.Locals("access").(string) + isRoot, acct := ctx.Locals("isRoot").(bool), ctx.Locals("account").(auth.Account) path := ctx.Path() pathParts := strings.Split(path, "/") bucket := pathParts[1] @@ -39,7 +39,7 @@ func AclParser(be backend.Backend, logger s3log.AuditLogger) fiber.Handler { return ctx.Next() } if len(pathParts) == 2 && pathParts[1] != "" && ctx.Method() == http.MethodPut && !ctx.Request().URI().QueryArgs().Has("acl") { - if err := auth.IsAdmin(access, isRoot); err != nil { + if err := auth.IsAdmin(acct, isRoot); err != nil { return controllers.SendXMLResponse(ctx, nil, err, &controllers.MetaOpts{Logger: logger, Action: "CreateBucket"}) } return ctx.Next() diff --git a/s3api/middlewares/authentication.go b/s3api/middlewares/authentication.go index 23c88c5..b485cd1 100644 --- a/s3api/middlewares/authentication.go +++ b/s3api/middlewares/authentication.go @@ -95,7 +95,6 @@ func VerifyV4Signature(root RootUserConfig, iam auth.IAMService, logger s3log.Au }, &controllers.MetaOpts{Logger: logger}) } - ctx.Locals("access", creds[0]) ctx.Locals("isRoot", creds[0] == root.Access) _, err := time.Parse(YYYYMMDD, creds[1]) @@ -116,7 +115,7 @@ func VerifyV4Signature(root RootUserConfig, iam auth.IAMService, logger s3log.Au if err != nil { return controllers.SendResponse(ctx, err, &controllers.MetaOpts{Logger: logger}) } - ctx.Locals("role", account.Role) + ctx.Locals("account", account) // Check X-Amz-Date header date := ctx.Get("X-Amz-Date") @@ -199,6 +198,7 @@ type accounts struct { func (a accounts) getAccount(access string) (auth.Account, error) { if access == a.root.Access { return auth.Account{ + Access: a.root.Access, Secret: a.root.Secret, Role: "admin", }, nil