feat: implements advanced routing for the admin apis. Adds the debug logging and quite mode for the separate admin server.

Adjusts the admin apis to the new advanced routing changes.
Enables debug logging for the separate admin server(when a separate server is run for the admin apis).
Adds the quiet mode for the separate admin server.
This commit is contained in:
niksis02
2025-06-20 23:23:53 +04:00
parent b7c758b065
commit abdf342ef7
8 changed files with 162 additions and 660 deletions

View File

@@ -621,30 +621,6 @@ func runGateway(ctx context.Context, be backend.Backend) error {
opts = append(opts, s3api.WithHostStyle(virtualDomain)) opts = append(opts, s3api.WithHostStyle(virtualDomain))
} }
admApp := fiber.New(fiber.Config{
AppName: "versitygw",
ServerHeader: "VERSITYGW",
Network: fiber.NetworkTCP,
DisableStartupMessage: true,
})
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))
}
iam, err := auth.New(&auth.Opts{ iam, err := auth.New(&auth.Opts{
RootAccount: auth.Account{ RootAccount: auth.Account{
Access: rootUserAccess, Access: rootUserAccess,
@@ -732,7 +708,41 @@ func runGateway(ctx context.Context, be backend.Backend) error {
return fmt.Errorf("init gateway: %v", err) return fmt.Errorf("init gateway: %v", err)
} }
admSrv := s3api.NewAdminServer(admApp, be, middlewares.RootUserConfig{Access: rootUserAccess, Secret: rootUserSecret}, admPort, region, iam, loggers.AdminLogger, admOpts...) var admSrv *s3api.S3AdminServer
if admPort != "" {
admApp := fiber.New(fiber.Config{
AppName: "versitygw",
ServerHeader: "VERSITYGW",
Network: fiber.NetworkTCP,
DisableStartupMessage: true,
})
var opts []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)
}
opts = append(opts, s3api.WithAdminSrvTLS(cert))
}
if quiet {
opts = append(opts, s3api.WithAdminQuiet())
}
if debug {
opts = append(opts, s3api.WithAdminDebug())
}
admSrv = s3api.NewAdminServer(admApp, be, middlewares.RootUserConfig{Access: rootUserAccess, Secret: rootUserSecret}, admPort, region, iam, loggers.AdminLogger, opts...)
}
if !quiet { if !quiet {
printBanner(port, admPort, certFile != "", admCertFile != "") printBanner(port, admPort, certFile != "", admCertFile != "")
@@ -977,10 +987,7 @@ func getMatchingIPs(spec string) ([]string, error) {
const columnWidth = 70 const columnWidth = 70
func centerText(text string) string { func centerText(text string) string {
padding := (columnWidth - 2 - len(text)) / 2 padding := max((columnWidth-2-len(text))/2, 0)
if padding < 0 {
padding = 0
}
return strings.Repeat(" ", padding) + text return strings.Repeat(" ", padding) + text
} }

View File

@@ -25,23 +25,23 @@ import (
type S3AdminRouter struct{} type S3AdminRouter struct{}
func (ar *S3AdminRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMService, logger s3log.AuditLogger) { func (ar *S3AdminRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMService, logger s3log.AuditLogger) {
controller := controllers.NewAdminController(iam, be, logger) ctrl := controllers.NewAdminController(iam, be, logger)
// CreateUser admin api // CreateUser admin api
app.Patch("/create-user", controller.CreateUser) app.Patch("/create-user", controllers.ProcessResponse(ctrl.CreateUser, logger, nil, nil))
// DeleteUsers admin api // DeleteUsers admin api
app.Patch("/delete-user", controller.DeleteUser) app.Patch("/delete-user", controllers.ProcessResponse(ctrl.DeleteUser, logger, nil, nil))
// UpdateUser admin api // UpdateUser admin api
app.Patch("/update-user", controller.UpdateUser) app.Patch("/update-user", controllers.ProcessResponse(ctrl.UpdateUser, logger, nil, nil))
// ListUsers admin api // ListUsers admin api
app.Patch("/list-users", controller.ListUsers) app.Patch("/list-users", controllers.ProcessResponse(ctrl.ListUsers, logger, nil, nil))
// ChangeBucketOwner admin api // ChangeBucketOwner admin api
app.Patch("/change-bucket-owner", controller.ChangeBucketOwner) app.Patch("/change-bucket-owner", controllers.ProcessResponse(ctrl.ChangeBucketOwner, logger, nil, nil))
// ListBucketsAndOwners admin api // ListBucketsAndOwners admin api
app.Patch("/list-buckets", controller.ListBuckets) app.Patch("/list-buckets", controllers.ProcessResponse(ctrl.ListBuckets, logger, nil, nil))
} }

View File

@@ -31,6 +31,8 @@ type S3AdminServer struct {
router *S3AdminRouter router *S3AdminRouter
port string port string
cert *tls.Certificate cert *tls.Certificate
quiet bool
debug bool
} }
func NewAdminServer(app *fiber.App, be backend.Backend, root middlewares.RootUserConfig, port, region string, iam auth.IAMService, l s3log.AuditLogger, opts ...AdminOpt) *S3AdminServer { func NewAdminServer(app *fiber.App, be backend.Backend, root middlewares.RootUserConfig, port, region string, iam auth.IAMService, l s3log.AuditLogger, opts ...AdminOpt) *S3AdminServer {
@@ -46,8 +48,13 @@ func NewAdminServer(app *fiber.App, be backend.Backend, root middlewares.RootUse
} }
// Logging middlewares // Logging middlewares
app.Use(logger.New()) if !server.quiet {
app.Use(logger.New(logger.Config{
Format: "${time} | ${status} | ${latency} | ${ip} | ${method} | ${path} | ${error} | ${queryParams}\n",
}))
}
app.Use(middlewares.DecodeURL(l, nil)) app.Use(middlewares.DecodeURL(l, nil))
app.Use(middlewares.DebugLogger())
// Authentication middlewares // Authentication middlewares
app.Use(middlewares.VerifyV4Signature(root, iam, l, nil, region, false)) app.Use(middlewares.VerifyV4Signature(root, iam, l, nil, region, false))
@@ -67,6 +74,16 @@ func WithAdminSrvTLS(cert tls.Certificate) AdminOpt {
return func(s *S3AdminServer) { s.cert = &cert } return func(s *S3AdminServer) { s.cert = &cert }
} }
// WithQuiet silences default logging output
func WithAdminQuiet() AdminOpt {
return func(s *S3AdminServer) { s.quiet = true }
}
// WithAdminDebug enables the debug logging
func WithAdminDebug() AdminOpt {
return func(s *S3AdminServer) { s.debug = true }
}
func (sa *S3AdminServer) Serve() (err error) { func (sa *S3AdminServer) Serve() (err error) {
if sa.cert != nil { if sa.cert != nil {
return sa.app.ListenTLSWithCertificate(sa.port, *sa.cert) return sa.app.ListenTLSWithCertificate(sa.port, *sa.cert)

View File

@@ -15,10 +15,13 @@
package controllers package controllers
import ( import (
"encoding/json"
"encoding/xml" "encoding/xml"
"fmt"
"net/http" "net/http"
"strings" "strings"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/versity/versitygw/auth" "github.com/versity/versitygw/auth"
"github.com/versity/versitygw/backend" "github.com/versity/versitygw/backend"
@@ -38,23 +41,23 @@ func NewAdminController(iam auth.IAMService, be backend.Backend, l s3log.AuditLo
return AdminController{iam: iam, be: be, l: l} return AdminController{iam: iam, be: be, l: l}
} }
func (c AdminController) CreateUser(ctx *fiber.Ctx) error { func (c AdminController) CreateUser(ctx *fiber.Ctx) (*Response, error) {
var usr auth.Account var usr auth.Account
err := xml.Unmarshal(ctx.Body(), &usr) err := xml.Unmarshal(ctx.Body(), &usr)
if err != nil { if err != nil {
return SendResponse(ctx, s3err.GetAPIError(s3err.ErrMalformedXML), return &Response{
&MetaOpts{ MetaOpts: &MetaOptions{
Logger: c.l,
Action: metrics.ActionAdminCreateUser, Action: metrics.ActionAdminCreateUser,
}) },
}, s3err.GetAPIError(s3err.ErrMalformedXML)
} }
if !usr.Role.IsValid() { if !usr.Role.IsValid() {
return SendResponse(ctx, s3err.GetAPIError(s3err.ErrAdminInvalidUserRole), return &Response{
&MetaOpts{ MetaOpts: &MetaOptions{
Logger: c.l,
Action: metrics.ActionAdminCreateUser, Action: metrics.ActionAdminCreateUser,
}) },
}, s3err.GetAPIError(s3err.ErrAdminInvalidUserRole)
} }
err = c.iam.CreateAccount(usr) err = c.iam.CreateAccount(usr)
@@ -63,47 +66,47 @@ func (c AdminController) CreateUser(ctx *fiber.Ctx) error {
err = s3err.GetAPIError(s3err.ErrAdminUserExists) err = s3err.GetAPIError(s3err.ErrAdminUserExists)
} }
return SendResponse(ctx, err, return &Response{
&MetaOpts{ MetaOpts: &MetaOptions{
Logger: c.l,
Action: metrics.ActionAdminCreateUser, Action: metrics.ActionAdminCreateUser,
}) },
}, err
} }
return SendResponse(ctx, nil, return &Response{
&MetaOpts{ MetaOpts: &MetaOptions{
Logger: c.l,
Action: metrics.ActionAdminCreateUser, Action: metrics.ActionAdminCreateUser,
Status: http.StatusCreated, Status: http.StatusCreated,
}) },
}, nil
} }
func (c AdminController) UpdateUser(ctx *fiber.Ctx) error { func (c AdminController) UpdateUser(ctx *fiber.Ctx) (*Response, error) {
access := ctx.Query("access") access := ctx.Query("access")
if access == "" { if access == "" {
return SendResponse(ctx, s3err.GetAPIError(s3err.ErrAdminMissingUserAcess), return &Response{
&MetaOpts{ MetaOpts: &MetaOptions{
Logger: c.l,
Action: metrics.ActionAdminUpdateUser, Action: metrics.ActionAdminUpdateUser,
}) },
}, s3err.GetAPIError(s3err.ErrAdminMissingUserAcess)
} }
var props auth.MutableProps var props auth.MutableProps
if err := xml.Unmarshal(ctx.Body(), &props); err != nil { if err := xml.Unmarshal(ctx.Body(), &props); err != nil {
return SendResponse(ctx, s3err.GetAPIError(s3err.ErrMalformedXML), return &Response{
&MetaOpts{ MetaOpts: &MetaOptions{
Logger: c.l,
Action: metrics.ActionAdminUpdateUser, Action: metrics.ActionAdminUpdateUser,
}) },
}, s3err.GetAPIError(s3err.ErrMalformedXML)
} }
err := props.Validate() err := props.Validate()
if err != nil { if err != nil {
return SendResponse(ctx, s3err.GetAPIError(s3err.ErrAdminInvalidUserRole), return &Response{
&MetaOpts{ MetaOpts: &MetaOptions{
Logger: c.l,
Action: metrics.ActionAdminUpdateUser, Action: metrics.ActionAdminUpdateUser,
}) },
}, s3err.GetAPIError(s3err.ErrAdminInvalidUserRole)
} }
err = c.iam.UpdateUserAccount(access, props) err = c.iam.UpdateUserAccount(access, props)
@@ -112,78 +115,97 @@ func (c AdminController) UpdateUser(ctx *fiber.Ctx) error {
err = s3err.GetAPIError(s3err.ErrAdminUserNotFound) err = s3err.GetAPIError(s3err.ErrAdminUserNotFound)
} }
return SendResponse(ctx, err, return &Response{
&MetaOpts{ MetaOpts: &MetaOptions{
Logger: c.l,
Action: metrics.ActionAdminUpdateUser, Action: metrics.ActionAdminUpdateUser,
}) },
}, err
} }
return SendResponse(ctx, nil, return &Response{
&MetaOpts{ MetaOpts: &MetaOptions{
Logger: c.l,
Action: metrics.ActionAdminUpdateUser, Action: metrics.ActionAdminUpdateUser,
}) },
}, nil
} }
func (c AdminController) DeleteUser(ctx *fiber.Ctx) error { func (c AdminController) DeleteUser(ctx *fiber.Ctx) (*Response, error) {
access := ctx.Query("access") access := ctx.Query("access")
err := c.iam.DeleteUserAccount(access) err := c.iam.DeleteUserAccount(access)
return SendResponse(ctx, err, return &Response{
&MetaOpts{ MetaOpts: &MetaOptions{
Logger: c.l,
Action: metrics.ActionAdminDeleteUser, Action: metrics.ActionAdminDeleteUser,
}) },
}, err
} }
func (c AdminController) ListUsers(ctx *fiber.Ctx) error { func (c AdminController) ListUsers(ctx *fiber.Ctx) (*Response, error) {
accs, err := c.iam.ListUserAccounts() accs, err := c.iam.ListUserAccounts()
return SendXMLResponse(ctx, return &Response{
auth.ListUserAccountsResult{ Data: auth.ListUserAccountsResult{Accounts: accs},
Accounts: accs, MetaOpts: &MetaOptions{
}, err,
&MetaOpts{
Logger: c.l,
Action: metrics.ActionAdminListUsers, Action: metrics.ActionAdminListUsers,
}) },
}, err
} }
func (c AdminController) ChangeBucketOwner(ctx *fiber.Ctx) error { func (c AdminController) ChangeBucketOwner(ctx *fiber.Ctx) (*Response, error) {
owner := ctx.Query("owner") owner := ctx.Query("owner")
bucket := ctx.Query("bucket") bucket := ctx.Query("bucket")
accs, err := auth.CheckIfAccountsExist([]string{owner}, c.iam) accs, err := auth.CheckIfAccountsExist([]string{owner}, c.iam)
if err != nil { if err != nil {
return SendResponse(ctx, err, return &Response{
&MetaOpts{ MetaOpts: &MetaOptions{
Logger: c.l,
Action: metrics.ActionAdminChangeBucketOwner, Action: metrics.ActionAdminChangeBucketOwner,
}) },
}, err
} }
if len(accs) > 0 { if len(accs) > 0 {
return SendResponse(ctx, s3err.GetAPIError(s3err.ErrAdminUserNotFound), return &Response{
&MetaOpts{ MetaOpts: &MetaOptions{
Logger: c.l,
Action: metrics.ActionAdminChangeBucketOwner, Action: metrics.ActionAdminChangeBucketOwner,
}) },
}, s3err.GetAPIError(s3err.ErrAdminUserNotFound)
} }
err = c.be.ChangeBucketOwner(ctx.Context(), bucket, owner) acl := auth.ACL{
return SendResponse(ctx, err, Owner: owner,
&MetaOpts{ Grantees: []auth.Grantee{
Logger: c.l, {
Permission: auth.PermissionFullControl,
Access: owner,
Type: types.TypeCanonicalUser,
},
},
}
aclParsed, err := json.Marshal(acl)
if err != nil {
return &Response{
MetaOpts: &MetaOptions{
Action: metrics.ActionAdminChangeBucketOwner,
},
}, fmt.Errorf("failed to marshal the bucket acl: %w", err)
}
err = c.be.ChangeBucketOwner(ctx.Context(), bucket, aclParsed)
return &Response{
MetaOpts: &MetaOptions{
Action: metrics.ActionAdminChangeBucketOwner, Action: metrics.ActionAdminChangeBucketOwner,
}) },
}, err
} }
func (c AdminController) ListBuckets(ctx *fiber.Ctx) error { func (c AdminController) ListBuckets(ctx *fiber.Ctx) (*Response, error) {
buckets, err := c.be.ListBucketsAndOwners(ctx.Context()) buckets, err := c.be.ListBucketsAndOwners(ctx.Context())
return SendXMLResponse(ctx, return &Response{
s3response.ListBucketsResult{ Data: s3response.ListBucketsResult{
Buckets: buckets, Buckets: buckets,
}, err, &MetaOpts{ },
Logger: c.l, MetaOpts: &MetaOptions{
Action: metrics.ActionAdminListBuckets, Action: metrics.ActionAdminListBuckets,
}) },
}, err
} }

View File

@@ -1,454 +0,0 @@
// 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 controllers
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gofiber/fiber/v2"
"github.com/versity/versitygw/auth"
"github.com/versity/versitygw/s3response"
)
func TestAdminController_CreateUser(t *testing.T) {
type args struct {
req *http.Request
}
adminController := AdminController{
iam: &IAMServiceMock{
CreateAccountFunc: func(account auth.Account) error {
return nil
},
},
}
app := fiber.New()
app.Patch("/create-user", adminController.CreateUser)
succUser := `
<Account>
<Access>access</Access>
<Secret>secret</Secret>
<Role>admin</Role>
<UserID>0</UserID>
<GroupID>0</GroupID>
</Account>
`
invuser := `
<Account>
<Access>access</Access>
<Secret>secret</Secret>
<Role>invalid_role</Role>
<UserID>0</UserID>
<GroupID>0</GroupID>
</Account>
`
tests := []struct {
name string
app *fiber.App
args args
wantErr bool
statusCode int
}{
{
name: "Admin-create-user-malformed-body",
app: app,
args: args{
req: httptest.NewRequest(http.MethodPatch, "/create-user", nil),
},
wantErr: false,
statusCode: 400,
},
{
name: "Admin-create-user-invalid-requester-role",
app: app,
args: args{
req: httptest.NewRequest(http.MethodPatch, "/create-user", strings.NewReader(invuser)),
},
wantErr: false,
statusCode: 400,
},
{
name: "Admin-create-user-success",
app: app,
args: args{
req: httptest.NewRequest(http.MethodPatch, "/create-user", strings.NewReader(succUser)),
},
wantErr: false,
statusCode: 201,
},
}
for _, tt := range tests {
resp, err := tt.app.Test(tt.args.req)
if (err != nil) != tt.wantErr {
t.Errorf("AdminController.CreateUser() error = %v, wantErr %v", err, tt.wantErr)
}
if resp.StatusCode != tt.statusCode {
t.Errorf("AdminController.CreateUser() statusCode = %v, wantStatusCode = %v", resp.StatusCode, tt.statusCode)
}
}
}
func TestAdminController_UpdateUser(t *testing.T) {
type args struct {
req *http.Request
}
adminController := AdminController{
iam: &IAMServiceMock{
UpdateUserAccountFunc: func(access string, props auth.MutableProps) error {
return nil
},
},
}
app := fiber.New()
app.Patch("/update-user", adminController.UpdateUser)
adminControllerErr := AdminController{
iam: &IAMServiceMock{
UpdateUserAccountFunc: func(access string, props auth.MutableProps) error {
return auth.ErrNoSuchUser
},
},
}
appNotFound := fiber.New()
appNotFound.Patch("/update-user", adminControllerErr.UpdateUser)
succUser := `
<Account>
<Secret>secret</Secret>
<UserID>0</UserID>
<GroupID>0</GroupID>
</Account>
`
tests := []struct {
name string
app *fiber.App
args args
wantErr bool
statusCode int
}{
{
name: "Admin-update-user-success",
app: app,
args: args{
req: httptest.NewRequest(http.MethodPatch, "/update-user?access=access", strings.NewReader(succUser)),
},
wantErr: false,
statusCode: 200,
},
{
name: "Admin-update-user-missing-access",
app: app,
args: args{
req: httptest.NewRequest(http.MethodPatch, "/update-user", strings.NewReader(succUser)),
},
wantErr: false,
statusCode: 404,
},
{
name: "Admin-update-user-invalid-request-body",
app: app,
args: args{
req: httptest.NewRequest(http.MethodPatch, "/update-user?access=access", nil),
},
wantErr: false,
statusCode: 400,
},
{
name: "Admin-update-user-not-found",
app: appNotFound,
args: args{
req: httptest.NewRequest(http.MethodPatch, "/update-user?access=access", strings.NewReader(succUser)),
},
wantErr: false,
statusCode: 404,
},
}
for _, tt := range tests {
resp, err := tt.app.Test(tt.args.req)
if (err != nil) != tt.wantErr {
t.Errorf("AdminController.UpdateUser() error = %v, wantErr %v", err, tt.wantErr)
}
if resp.StatusCode != tt.statusCode {
t.Errorf("AdminController.UpdateUser() statusCode = %v, wantStatusCode = %v", resp.StatusCode, tt.statusCode)
}
}
}
func TestAdminController_DeleteUser(t *testing.T) {
type args struct {
req *http.Request
}
adminController := AdminController{
iam: &IAMServiceMock{
DeleteUserAccountFunc: func(access string) error {
return nil
},
},
}
app := fiber.New()
app.Patch("/delete-user", adminController.DeleteUser)
tests := []struct {
name string
app *fiber.App
args args
wantErr bool
statusCode int
}{
{
name: "Admin-delete-user-success",
app: app,
args: args{
req: httptest.NewRequest(http.MethodPatch, "/delete-user?access=test", 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.DeleteUser() error = %v, wantErr %v", err, tt.wantErr)
}
if resp.StatusCode != tt.statusCode {
t.Errorf("AdminController.DeleteUser() statusCode = %v, wantStatusCode = %v", resp.StatusCode, tt.statusCode)
}
}
}
func TestAdminController_ListUsers(t *testing.T) {
type args struct {
req *http.Request
}
adminController := AdminController{
iam: &IAMServiceMock{
ListUserAccountsFunc: func() ([]auth.Account, error) {
return []auth.Account{}, nil
},
},
}
adminControllerErr := AdminController{
iam: &IAMServiceMock{
ListUserAccountsFunc: func() ([]auth.Account, error) {
return []auth.Account{}, fmt.Errorf("server error")
},
},
}
appErr := fiber.New()
appErr.Patch("/list-users", adminControllerErr.ListUsers)
appSucc := fiber.New()
appSucc.Patch("/list-users", adminController.ListUsers)
tests := []struct {
name string
app *fiber.App
args args
wantErr bool
statusCode int
}{
{
name: "Admin-list-users-iam-error",
app: appErr,
args: args{
req: httptest.NewRequest(http.MethodPatch, "/list-users", nil),
},
wantErr: false,
statusCode: 500,
},
{
name: "Admin-list-users-success",
app: appSucc,
args: args{
req: httptest.NewRequest(http.MethodPatch, "/list-users", 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.ListUsers() error = %v, wantErr %v", err, tt.wantErr)
}
if resp.StatusCode != tt.statusCode {
t.Errorf("AdminController.ListUsers() statusCode = %v, wantStatusCode = %v", resp.StatusCode, tt.statusCode)
}
}
}
func TestAdminController_ChangeBucketOwner(t *testing.T) {
type args struct {
req *http.Request
}
adminController := AdminController{
be: &BackendMock{
ChangeBucketOwnerFunc: func(contextMoqParam context.Context, bucket, owner string) error {
return nil
},
},
iam: &IAMServiceMock{
GetUserAccountFunc: func(access string) (auth.Account, error) {
return auth.Account{}, nil
},
},
}
adminControllerIamErr := AdminController{
iam: &IAMServiceMock{
GetUserAccountFunc: func(access string) (auth.Account, error) {
return auth.Account{}, fmt.Errorf("unknown server error")
},
},
}
adminControllerIamAccDoesNotExist := AdminController{
iam: &IAMServiceMock{
GetUserAccountFunc: func(access string) (auth.Account, error) {
return auth.Account{}, auth.ErrNoSuchUser
},
},
}
app := fiber.New()
app.Patch("/change-bucket-owner", adminController.ChangeBucketOwner)
appIamErr := fiber.New()
appIamErr.Patch("/change-bucket-owner", adminControllerIamErr.ChangeBucketOwner)
appIamNoSuchUser := fiber.New()
appIamNoSuchUser.Patch("/change-bucket-owner", adminControllerIamAccDoesNotExist.ChangeBucketOwner)
tests := []struct {
name string
app *fiber.App
args args
wantErr bool
statusCode int
}{
{
name: "Change-bucket-owner-check-account-server-error",
app: appIamErr,
args: args{
req: httptest.NewRequest(http.MethodPatch, "/change-bucket-owner", nil),
},
wantErr: false,
statusCode: 500,
},
{
name: "Change-bucket-owner-acc-does-not-exist",
app: appIamNoSuchUser,
args: args{
req: httptest.NewRequest(http.MethodPatch, "/change-bucket-owner", nil),
},
wantErr: false,
statusCode: 404,
},
{
name: "Change-bucket-owner-success",
app: app,
args: args{
req: httptest.NewRequest(http.MethodPatch, "/change-bucket-owner?bucket=bucket&owner=owner", 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.ChangeBucketOwner() error = %v, wantErr %v", err, tt.wantErr)
}
if resp.StatusCode != tt.statusCode {
t.Errorf("AdminController.ChangeBucketOwner() statusCode = %v, wantStatusCode = %v", resp.StatusCode, tt.statusCode)
}
}
}
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.Patch("/list-buckets", adminController.ListBuckets)
tests := []struct {
name string
app *fiber.App
args args
wantErr bool
statusCode int
}{
{
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

@@ -1991,93 +1991,3 @@ func SendResponse(ctx *fiber.Ctx, err error, l *MetaOpts) error {
ctx.Status(l.Status) ctx.Status(l.Status)
return nil return nil
} }
func SendXMLResponse(ctx *fiber.Ctx, resp any, err error, l *MetaOpts) error {
if l.MetricsMng != nil {
if l.ObjectCount > 0 {
l.MetricsMng.Send(ctx, err, l.Action, l.ObjectCount, l.Status)
} else {
l.MetricsMng.Send(ctx, err, l.Action, l.ContentLength, l.Status)
}
}
if err != nil {
if l.Logger != nil {
l.Logger.Log(ctx, err, nil, s3log.LogMeta{
Action: l.Action,
BucketOwner: l.BucketOwner,
ObjectSize: l.ObjectSize,
})
}
serr, ok := err.(s3err.APIError)
if ok {
ctx.Status(serr.HTTPStatusCode)
return ctx.Send(s3err.GetAPIErrorResponse(serr, "", "", ""))
}
fmt.Fprintf(os.Stderr, "Internal Error, %v\n", err)
ctx.Status(http.StatusInternalServerError)
return ctx.Send(s3err.GetAPIErrorResponse(
s3err.GetAPIError(s3err.ErrInternalError), "", "", ""))
}
var b []byte
// Handle already encoded responses(text, json...)
encodedResp, ok := resp.([]byte)
if ok {
b = encodedResp
}
if resp != nil && !ok {
if b, err = xml.Marshal(resp); err != nil {
return err
}
if len(b) > 0 {
ctx.Response().Header.Set("Content-Length", fmt.Sprint(len(b)))
ctx.Response().Header.SetContentType(fiber.MIMEApplicationXML)
}
}
if l.Logger != nil {
l.Logger.Log(ctx, nil, b, s3log.LogMeta{
Action: l.Action,
BucketOwner: l.BucketOwner,
ObjectSize: l.ObjectSize,
})
}
if l.EvSender != nil {
l.EvSender.SendEvent(ctx, s3event.EventMeta{
BucketOwner: l.BucketOwner,
ObjectSize: l.ObjectSize,
ObjectETag: l.ObjectETag,
VersionId: l.VersionId,
EventName: l.EventName,
})
}
if ok {
if len(b) > 0 {
ctx.Response().Header.Set("Content-Length", fmt.Sprint(len(b)))
}
return ctx.Send(b)
}
msglen := len(xmlhdr) + len(b)
if msglen > maxXMLBodyLen {
debuglogger.Logf("XML encoded body len %v exceeds max len %v",
msglen, maxXMLBodyLen)
ctx.Status(http.StatusInternalServerError)
return ctx.Send(s3err.GetAPIErrorResponse(
s3err.GetAPIError(s3err.ErrInternalError), "", "", ""))
}
res := make([]byte, 0, msglen)
res = append(res, xmlhdr...)
res = append(res, b...)
return ctx.Send(res)
}

View File

@@ -55,10 +55,10 @@ func AclParser(be backend.Backend, logger s3log.AuditLogger, readonly bool) fibe
!ctx.Request().URI().QueryArgs().Has("cors") { !ctx.Request().URI().QueryArgs().Has("cors") {
isRoot, acct := utils.ContextKeyIsRoot.Get(ctx).(bool), utils.ContextKeyAccount.Get(ctx).(auth.Account) isRoot, acct := utils.ContextKeyIsRoot.Get(ctx).(bool), utils.ContextKeyAccount.Get(ctx).(auth.Account)
if err := auth.MayCreateBucket(acct, isRoot); err != nil { if err := auth.MayCreateBucket(acct, isRoot); err != nil {
return controllers.SendXMLResponse(ctx, nil, err, &controllers.MetaOpts{Logger: logger, Action: "CreateBucket"}) return controllers.SendResponse(ctx, err, &controllers.MetaOpts{Logger: logger, Action: "CreateBucket"})
} }
if readonly { if readonly {
return controllers.SendXMLResponse(ctx, nil, s3err.GetAPIError(s3err.ErrAccessDenied), return controllers.SendResponse(ctx, s3err.GetAPIError(s3err.ErrAccessDenied),
&controllers.MetaOpts{ &controllers.MetaOpts{
Logger: logger, Logger: logger,
Action: "CreateBucket", Action: "CreateBucket",

View File

@@ -36,22 +36,22 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
adminController := controllers.NewAdminController(iam, be, aLogger) adminController := controllers.NewAdminController(iam, be, aLogger)
// CreateUser admin api // CreateUser admin api
app.Patch("/create-user", middlewares.IsAdmin(logger), adminController.CreateUser) app.Patch("/create-user", middlewares.IsAdmin(logger), controllers.ProcessResponse(adminController.CreateUser, aLogger, nil, nil))
// DeleteUsers admin api // DeleteUsers admin api
app.Patch("/delete-user", middlewares.IsAdmin(logger), adminController.DeleteUser) app.Patch("/delete-user", middlewares.IsAdmin(logger), controllers.ProcessResponse(adminController.DeleteUser, aLogger, nil, nil))
// UpdateUser admin api // UpdateUser admin api
app.Patch("/update-user", middlewares.IsAdmin(logger), adminController.UpdateUser) app.Patch("/update-user", middlewares.IsAdmin(logger), controllers.ProcessResponse(adminController.UpdateUser, aLogger, nil, nil))
// ListUsers admin api // ListUsers admin api
app.Patch("/list-users", middlewares.IsAdmin(logger), adminController.ListUsers) app.Patch("/list-users", middlewares.IsAdmin(logger), controllers.ProcessResponse(adminController.ListUsers, aLogger, nil, nil))
// ChangeBucketOwner admin api // ChangeBucketOwner admin api
app.Patch("/change-bucket-owner", middlewares.IsAdmin(logger), adminController.ChangeBucketOwner) app.Patch("/change-bucket-owner", middlewares.IsAdmin(logger), controllers.ProcessResponse(adminController.ChangeBucketOwner, aLogger, nil, nil))
// ListBucketsAndOwners admin api // ListBucketsAndOwners admin api
app.Patch("/list-buckets", middlewares.IsAdmin(logger), adminController.ListBuckets) app.Patch("/list-buckets", middlewares.IsAdmin(logger), controllers.ProcessResponse(adminController.ListBuckets, aLogger, nil, nil))
} }
// ListBuckets action // ListBuckets action