feat: Added admin CLI, created api endpoint for creating new user, cr… (#68)

* feat: Added admin CLI, created api endpoint for creating new user, created action for admin CLI to create a new user, changed the authentication middleware to verify the users from db

* feat: Added both single and multi user support, added caching layer for getting IAM users

* fix: Added all the files
This commit is contained in:
Jon Austin
2023-06-09 10:30:20 -07:00
committed by GitHub
parent 0350215e2e
commit 160a99cbbd
10 changed files with 363 additions and 62 deletions
+49
View File
@@ -0,0 +1,49 @@
// 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 (
"fmt"
"github.com/gofiber/fiber/v2"
"github.com/versity/versitygw/backend/auth"
)
type AdminController struct {
IAMService auth.IAMService
}
func NewAdminController() AdminController {
return AdminController{IAMService: auth.New()}
}
func (c AdminController) CreateUser(ctx *fiber.Ctx) error {
access, secret, role, region := ctx.Query("access"), ctx.Query("secret"), ctx.Query("role"), ctx.Query("region")
requesterRole := ctx.Locals("role")
if requesterRole != "admin" {
return fmt.Errorf("access denied: only admin users have access to this resource")
}
user := auth.Account{Secret: secret, Role: role, Region: region}
err := c.IAMService.CreateAccount(access, &user)
if err != nil {
return fmt.Errorf("failed to create a user: %w", err)
}
ctx.SendString("The user has been created successfully")
return nil
}
+25 -26
View File
@@ -35,17 +35,14 @@ const (
iso8601Format = "20060102T150405Z"
)
type AdminConfig struct {
AdminAccess string
AdminSecret string
Region string
type RootUserConfig struct {
Access string
Secret string
Region string
}
func VerifyV4Signature(config AdminConfig, iam auth.IAMService, debug bool) fiber.Handler {
acct := accounts{
admin: config,
iam: iam,
}
func VerifyV4Signature(root RootUserConfig, iam auth.IAMService, debug bool) fiber.Handler {
acct := accounts{root: root, iam: iam}
return func(ctx *fiber.Ctx) error {
authorization := ctx.Get("Authorization")
@@ -77,8 +74,8 @@ func VerifyV4Signature(config AdminConfig, iam auth.IAMService, debug bool) fibe
}
signedHdrs := strings.Split(signHdrKv[1], ";")
secret, ok := acct.getAcctSecret(creds[0])
if !ok {
account := acct.getAccount(creds[0])
if account == nil {
return controllers.Responce[any](ctx, nil, s3err.GetAPIError(s3err.ErrInvalidAccessKeyID))
}
@@ -115,8 +112,8 @@ func VerifyV4Signature(config AdminConfig, iam auth.IAMService, debug bool) fibe
signErr := signer.SignHTTP(req.Context(), aws.Credentials{
AccessKeyID: creds[0],
SecretAccessKey: secret,
}, req, hexPayload, creds[3], config.Region, tdate, func(options *v4.SignerOptions) {
SecretAccessKey: account.Secret,
}, req, hexPayload, creds[3], account.Region, tdate, func(options *v4.SignerOptions) {
if debug {
options.LogSigning = true
options.Logger = logging.NewStandardLogger(os.Stderr)
@@ -137,25 +134,27 @@ func VerifyV4Signature(config AdminConfig, iam auth.IAMService, debug bool) fibe
return controllers.Responce[any](ctx, nil, s3err.GetAPIError(s3err.ErrSignatureDoesNotMatch))
}
ctx.Locals("role", account.Role)
return ctx.Next()
}
}
type accounts struct {
admin AdminConfig
iam auth.IAMService
root RootUserConfig
iam auth.IAMService
}
func (a accounts) getAcctSecret(access string) (string, bool) {
if a.admin.AdminAccess == access {
return a.admin.AdminSecret, true
func (a accounts) getAccount(access string) *auth.Account {
var account *auth.Account
if access == a.root.Access {
account = &auth.Account{
Secret: a.root.Secret,
Role: "admin",
Region: a.root.Region,
}
} else {
account = a.iam.GetUserAccount(access)
}
conf, err := a.iam.GetIAMConfig()
if err != nil {
return "", false
}
secret, ok := conf.AccessAccounts[access]
return secret, ok
return account
}
+6 -1
View File
@@ -17,13 +17,18 @@ package s3api
import (
"github.com/gofiber/fiber/v2"
"github.com/versity/versitygw/backend"
"github.com/versity/versitygw/backend/auth"
"github.com/versity/versitygw/s3api/controllers"
)
type S3ApiRouter struct{}
func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend) {
func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMService) {
s3ApiController := controllers.New(be)
adminController := controllers.AdminController{IAMService: iam}
// TODO: think of better routing system
app.Post("/create-user", adminController.CreateUser)
// ListBuckets action
app.Get("/", s3ApiController.ListBuckets)
+4 -1
View File
@@ -19,12 +19,14 @@ import (
"github.com/gofiber/fiber/v2"
"github.com/versity/versitygw/backend"
"github.com/versity/versitygw/backend/auth"
)
func TestS3ApiRouter_Init(t *testing.T) {
type args struct {
app *fiber.App
be backend.Backend
iam auth.IAMService
}
tests := []struct {
name string
@@ -37,12 +39,13 @@ func TestS3ApiRouter_Init(t *testing.T) {
args: args{
app: fiber.New(),
be: backend.BackendUnsupported{},
iam: auth.IAMServiceUnsupported{},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.sa.Init(tt.args.app, tt.args.be)
tt.sa.Init(tt.args.app, tt.args.be, tt.args.iam)
})
}
}
+3 -3
View File
@@ -33,7 +33,7 @@ type S3ApiServer struct {
debug bool
}
func New(app *fiber.App, be backend.Backend, port string, adminUser middlewares.AdminConfig, iam auth.IAMService, opts ...Option) (*S3ApiServer, error) {
func New(app *fiber.App, be backend.Backend, root middlewares.RootUserConfig, port string, iam auth.IAMService, opts ...Option) (*S3ApiServer, error) {
server := &S3ApiServer{
app: app,
backend: be,
@@ -45,10 +45,10 @@ func New(app *fiber.App, be backend.Backend, port string, adminUser middlewares.
opt(server)
}
app.Use(middlewares.VerifyV4Signature(adminUser, iam, server.debug))
app.Use(middlewares.VerifyV4Signature(root, iam, server.debug))
app.Use(logger.New())
app.Use(middlewares.VerifyMD5Body())
server.router.Init(app, be)
server.router.Init(app, be, iam)
return server, nil
}
+10 -10
View File
@@ -26,10 +26,10 @@ import (
func TestNew(t *testing.T) {
type args struct {
app *fiber.App
be backend.Backend
port string
adminUser middlewares.AdminConfig
app *fiber.App
be backend.Backend
port string
root middlewares.RootUserConfig
}
app := fiber.New()
@@ -46,10 +46,10 @@ func TestNew(t *testing.T) {
{
name: "Create S3 api server",
args: args{
app: app,
be: be,
port: port,
adminUser: middlewares.AdminConfig{},
app: app,
be: be,
port: port,
root: middlewares.RootUserConfig{},
},
wantS3ApiServer: &S3ApiServer{
app: app,
@@ -62,8 +62,8 @@ func TestNew(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotS3ApiServer, err := New(tt.args.app, tt.args.be,
tt.args.port, tt.args.adminUser, auth.IAMServiceUnsupported{})
gotS3ApiServer, err := New(tt.args.app, tt.args.be, tt.args.root,
tt.args.port, auth.IAMServiceUnsupported{})
if (err != nil) != tt.wantErr {
t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr)
return
-3
View File
@@ -2,7 +2,6 @@ package utils
import (
"bytes"
"fmt"
"net/http"
"reflect"
"testing"
@@ -62,8 +61,6 @@ func TestCreateHttpRequestFromCtx(t *testing.T) {
return
}
fmt.Println(got.Header, tt.want.Header)
if !reflect.DeepEqual(got.Header, tt.want.Header) {
t.Errorf("CreateHttpRequestFromCtx() got = %v, want %v", got, tt.want)
}