diff --git a/cmd/versitygw/main.go b/cmd/versitygw/main.go index ab0cb0a..3f7b6b4 100644 --- a/cmd/versitygw/main.go +++ b/cmd/versitygw/main.go @@ -23,6 +23,7 @@ import ( "github.com/gofiber/fiber/v2" "github.com/urfave/cli/v2" "github.com/versity/versitygw/backend" + "github.com/versity/versitygw/backend/auth" "github.com/versity/versitygw/s3api" "github.com/versity/versitygw/s3api/middlewares" ) @@ -144,7 +145,12 @@ func runGateway(be backend.Backend) error { opts = append(opts, s3api.WithTLS(cert)) } - srv, err := s3api.New(app, be, port, middlewares.AdminConfig{AdminAccess: adminAccess, AdminSecret: adminSecret, Region: region}, opts...) + srv, err := s3api.New(app, be, port, + middlewares.AdminConfig{ + AdminAccess: adminAccess, + AdminSecret: adminSecret, + Region: region, + }, auth.IAMServiceUnsupported{}, opts...) if err != nil { return fmt.Errorf("init gateway: %v", err) } diff --git a/s3api/middlewares/authentication.go b/s3api/middlewares/authentication.go index fe52e39..d7d78b1 100644 --- a/s3api/middlewares/authentication.go +++ b/s3api/middlewares/authentication.go @@ -23,6 +23,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" "github.com/gofiber/fiber/v2" + "github.com/versity/versitygw/backend/auth" "github.com/versity/versitygw/s3api/controllers" "github.com/versity/versitygw/s3api/utils" "github.com/versity/versitygw/s3err" @@ -38,7 +39,12 @@ type AdminConfig struct { Region string } -func VerifyV4Signature(config AdminConfig) fiber.Handler { +func VerifyV4Signature(config AdminConfig, iam auth.IAMService) fiber.Handler { + acct := accounts{ + admin: config, + iam: iam, + } + return func(ctx *fiber.Ctx) error { authorization := ctx.Get("Authorization") if authorization == "" { @@ -47,11 +53,22 @@ func VerifyV4Signature(config AdminConfig) fiber.Handler { // Check the signature version authParts := strings.Split(authorization, " ") + if len(authParts) < 4 { + return controllers.Responce[any](ctx, nil, s3err.GetAPIError(s3err.ErrMissingFields)) + } if authParts[0] != "AWS4-HMAC-SHA256" { return controllers.Responce[any](ctx, nil, s3err.GetAPIError(s3err.ErrSignatureVersionNotSupported)) } creds := strings.Split(strings.Split(authParts[1], "=")[1], "/") + if len(creds) < 4 { + return controllers.Responce[any](ctx, nil, s3err.GetAPIError(s3err.ErrCredMalformed)) + } + + secret, ok := acct.getAcctSecret(creds[0]) + if !ok { + return controllers.Responce[any](ctx, nil, s3err.GetAPIError(s3err.ErrInvalidAccessKeyID)) + } // Check X-Amz-Date header date := ctx.Get("X-Amz-Date") @@ -79,20 +96,23 @@ func VerifyV4Signature(config AdminConfig) fiber.Handler { // Create a new http request instance from fasthttp request req, err := utils.CreateHttpRequestFromCtx(ctx) if err != nil { - return controllers.Responce[any](ctx, nil, s3err.GetAPIError(s3err.ErrAccessDenied)) + return controllers.Responce[any](ctx, nil, s3err.GetAPIError(s3err.ErrInternalError)) } signer := v4.NewSigner() signErr := signer.SignHTTP(req.Context(), aws.Credentials{ - AccessKeyID: config.AdminAccess, - SecretAccessKey: config.AdminSecret, + AccessKeyID: creds[0], + SecretAccessKey: secret, }, req, hexPayload, creds[3], config.Region, tdate) if signErr != nil { - return controllers.Responce[any](ctx, nil, s3err.GetAPIError(s3err.ErrAccessDenied)) + return controllers.Responce[any](ctx, nil, s3err.GetAPIError(s3err.ErrInternalError)) } parts := strings.Split(req.Header.Get("Authorization"), " ") + if len(parts) < 4 { + return controllers.Responce[any](ctx, nil, s3err.GetAPIError(s3err.ErrMissingFields)) + } calculatedSign := strings.Split(parts[3], "=")[1] expectedSign := strings.Split(authParts[3], "=")[1] @@ -103,3 +123,22 @@ func VerifyV4Signature(config AdminConfig) fiber.Handler { return ctx.Next() } } + +type accounts struct { + admin AdminConfig + iam auth.IAMService +} + +func (a accounts) getAcctSecret(access string) (string, bool) { + if a.admin.AdminAccess == access { + return a.admin.AdminSecret, true + } + + conf, err := a.iam.GetIAMConfig() + if err != nil { + return "", false + } + + secret, ok := conf.AccessAccounts[access] + return secret, ok +} diff --git a/s3api/server.go b/s3api/server.go index 51c28b0..75a278b 100644 --- a/s3api/server.go +++ b/s3api/server.go @@ -20,6 +20,7 @@ import ( "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/logger" "github.com/versity/versitygw/backend" + "github.com/versity/versitygw/backend/auth" "github.com/versity/versitygw/s3api/middlewares" ) @@ -31,7 +32,7 @@ type S3ApiServer struct { cert *tls.Certificate } -func New(app *fiber.App, be backend.Backend, port string, adminUser middlewares.AdminConfig, opts ...Option) (*S3ApiServer, error) { +func New(app *fiber.App, be backend.Backend, port string, adminUser middlewares.AdminConfig, iam auth.IAMService, opts ...Option) (*S3ApiServer, error) { server := &S3ApiServer{ app: app, backend: be, @@ -43,7 +44,7 @@ func New(app *fiber.App, be backend.Backend, port string, adminUser middlewares. opt(server) } - app.Use(middlewares.VerifyV4Signature(adminUser)) + app.Use(middlewares.VerifyV4Signature(adminUser, iam)) app.Use(logger.New()) server.router.Init(app, be) return server, nil diff --git a/s3api/server_test.go b/s3api/server_test.go index f34b978..b1686fc 100644 --- a/s3api/server_test.go +++ b/s3api/server_test.go @@ -20,6 +20,7 @@ import ( "github.com/gofiber/fiber/v2" "github.com/versity/versitygw/backend" + "github.com/versity/versitygw/backend/auth" "github.com/versity/versitygw/s3api/middlewares" ) @@ -61,7 +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) + gotS3ApiServer, err := New(tt.args.app, tt.args.be, + tt.args.port, tt.args.adminUser, auth.IAMServiceUnsupported{}) if (err != nil) != tt.wantErr { t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/s3api/utils/utils.go b/s3api/utils/utils.go index f218edc..05013fa 100644 --- a/s3api/utils/utils.go +++ b/s3api/utils/utils.go @@ -48,16 +48,105 @@ func CreateHttpRequestFromCtx(ctx *fiber.Ctx) (*http.Request, error) { // Set the request headers req.Header.VisitAll(func(key, value []byte) { keyStr := string(key) - if keyStr == "X-Amz-Date" || keyStr == "X-Amz-Content-Sha256" || keyStr == "Host" { + if includeHeader(keyStr) { httpReq.Header.Add(keyStr, string(value)) } }) - // Set the Content-Length header - httpReq.ContentLength = int64(len(req.Body())) + // Content-Length header ignored for signing + httpReq.ContentLength = 0 // Set the Host header httpReq.Host = string(req.Header.Host()) return httpReq, nil } + +func includeHeader(hdr string) bool { + switch { + case strings.EqualFold(hdr, "Cache-Control"): + return true + case strings.EqualFold(hdr, "Content-Disposition"): + return true + case strings.EqualFold(hdr, "Content-Encoding"): + return true + case strings.EqualFold(hdr, "Content-Language"): + return true + case strings.EqualFold(hdr, "Content-Md5"): + return true + case strings.EqualFold(hdr, "Content-Type"): + return true + case strings.EqualFold(hdr, "Expires"): + return true + case strings.EqualFold(hdr, "If-Match"): + return true + case strings.EqualFold(hdr, "If-Modified-Since"): + return true + case strings.EqualFold(hdr, "If-None-Match"): + return true + case strings.EqualFold(hdr, "If-Unmodified-Since"): + return true + case strings.EqualFold(hdr, "Range"): + return true + case strings.EqualFold(hdr, "X-Amz-Acl"): + return true + case strings.EqualFold(hdr, "X-Amz-Copy-Source"): + return true + case strings.EqualFold(hdr, "X-Amz-Copy-Source-If-Match"): + return true + case strings.EqualFold(hdr, "X-Amz-Copy-Source-If-Modified-Since"): + return true + case strings.EqualFold(hdr, "X-Amz-Copy-Source-If-None-Match"): + return true + case strings.EqualFold(hdr, "X-Amz-Copy-Source-If-Unmodified-Since"): + return true + case strings.EqualFold(hdr, "X-Amz-Copy-Source-Range"): + return true + case strings.EqualFold(hdr, "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm"): + return true + case strings.EqualFold(hdr, "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key"): + return true + case strings.EqualFold(hdr, "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-Md5"): + return true + case strings.EqualFold(hdr, "X-Amz-Grant-Full-control"): + return true + case strings.EqualFold(hdr, "X-Amz-Grant-Read"): + return true + case strings.EqualFold(hdr, "X-Amz-Grant-Read-Acp"): + return true + case strings.EqualFold(hdr, "X-Amz-Grant-Write"): + return true + case strings.EqualFold(hdr, "X-Amz-Grant-Write-Acp"): + return true + case strings.EqualFold(hdr, "X-Amz-Metadata-Directive"): + return true + case strings.EqualFold(hdr, "X-Amz-Mfa"): + return true + case strings.EqualFold(hdr, "X-Amz-Request-Payer"): + return true + case strings.EqualFold(hdr, "X-Amz-Server-Side-Encryption"): + return true + case strings.EqualFold(hdr, "X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id"): + return true + case strings.EqualFold(hdr, "X-Amz-Server-Side-Encryption-Customer-Algorithm"): + return true + case strings.EqualFold(hdr, "X-Amz-Server-Side-Encryption-Customer-Key"): + return true + case strings.EqualFold(hdr, "X-Amz-Server-Side-Encryption-Customer-Key-Md5"): + return true + case strings.EqualFold(hdr, "X-Amz-Storage-Class"): + return true + case strings.EqualFold(hdr, "X-Amz-Website-Redirect-Location"): + return true + case strings.EqualFold(hdr, "X-Amz-Content-Sha256"): + return true + case strings.EqualFold(hdr, "X-Amz-Tagging"): + return true + case strings.HasPrefix(hdr, "X-Amz-Object-Lock-"): + return true + case strings.HasPrefix(hdr, "X-Amz-Meta-"): + return true + default: + return false + } +}