mirror of
https://github.com/versity/versitygw.git
synced 2026-08-17 20:56:21 +00:00
Merge pull request #1737 from versity/ben/default-cors
feat: add option for default global cors allow origin headers
This commit is contained in:
@@ -42,6 +42,7 @@ var (
|
||||
rootUserAccess string
|
||||
rootUserSecret string
|
||||
region string
|
||||
corsAllowOrigin string
|
||||
admCertFile, admKeyFile string
|
||||
certFile, keyFile string
|
||||
kafkaURL, kafkaTopic, kafkaKey string
|
||||
@@ -188,6 +189,12 @@ func initFlags() []cli.Flag {
|
||||
Destination: ®ion,
|
||||
Aliases: []string{"r"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "cors-allow-origin",
|
||||
Usage: "default CORS Access-Control-Allow-Origin value (applied when no bucket CORS configuration exists, and for admin APIs)",
|
||||
EnvVars: []string{"VGW_CORS_ALLOW_ORIGIN"},
|
||||
Destination: &corsAllowOrigin,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "cert",
|
||||
Usage: "TLS cert file",
|
||||
@@ -649,6 +656,9 @@ func runGateway(ctx context.Context, be backend.Backend) error {
|
||||
}
|
||||
|
||||
var opts []s3api.Option
|
||||
if corsAllowOrigin != "" {
|
||||
opts = append(opts, s3api.WithCORSAllowOrigin(corsAllowOrigin))
|
||||
}
|
||||
|
||||
if certFile != "" || keyFile != "" {
|
||||
if certFile == "" {
|
||||
@@ -786,6 +796,9 @@ func runGateway(ctx context.Context, be backend.Backend) error {
|
||||
|
||||
if admPort != "" {
|
||||
var opts []s3api.AdminOpt
|
||||
if corsAllowOrigin != "" {
|
||||
opts = append(opts, s3api.WithAdminCORSAllowOrigin(corsAllowOrigin))
|
||||
}
|
||||
|
||||
if admCertFile != "" || admKeyFile != "" {
|
||||
if admCertFile == "" {
|
||||
|
||||
+31
-1
@@ -26,7 +26,7 @@ import (
|
||||
|
||||
type S3AdminRouter struct{}
|
||||
|
||||
func (ar *S3AdminRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMService, logger s3log.AuditLogger, root middlewares.RootUserConfig, region string, debug bool) {
|
||||
func (ar *S3AdminRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMService, logger s3log.AuditLogger, root middlewares.RootUserConfig, region string, debug bool, corsAllowOrigin string) {
|
||||
ctrl := controllers.NewAdminController(iam, be, logger)
|
||||
services := &controllers.Services{
|
||||
Logger: logger,
|
||||
@@ -37,40 +37,70 @@ func (ar *S3AdminRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMSe
|
||||
controllers.ProcessHandlers(ctrl.CreateUser, metrics.ActionAdminCreateUser, services,
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.IsAdmin(metrics.ActionAdminCreateUser),
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
))
|
||||
app.Options("/create-user",
|
||||
middlewares.ApplyDefaultCORSPreflight(corsAllowOrigin),
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
)
|
||||
|
||||
// DeleteUsers admin api
|
||||
app.Patch("/delete-user",
|
||||
controllers.ProcessHandlers(ctrl.DeleteUser, metrics.ActionAdminDeleteUser, services,
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.IsAdmin(metrics.ActionAdminDeleteUser),
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
))
|
||||
app.Options("/delete-user",
|
||||
middlewares.ApplyDefaultCORSPreflight(corsAllowOrigin),
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
)
|
||||
|
||||
// UpdateUser admin api
|
||||
app.Patch("/update-user",
|
||||
controllers.ProcessHandlers(ctrl.UpdateUser, metrics.ActionAdminUpdateUser, services,
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.IsAdmin(metrics.ActionAdminUpdateUser),
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
))
|
||||
app.Options("/update-user",
|
||||
middlewares.ApplyDefaultCORSPreflight(corsAllowOrigin),
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
)
|
||||
|
||||
// ListUsers admin api
|
||||
app.Patch("/list-users",
|
||||
controllers.ProcessHandlers(ctrl.ListUsers, metrics.ActionAdminListUsers, services,
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.IsAdmin(metrics.ActionAdminListUsers),
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
))
|
||||
app.Options("/list-users",
|
||||
middlewares.ApplyDefaultCORSPreflight(corsAllowOrigin),
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
)
|
||||
|
||||
// ChangeBucketOwner admin api
|
||||
app.Patch("/change-bucket-owner",
|
||||
controllers.ProcessHandlers(ctrl.ChangeBucketOwner, metrics.ActionAdminChangeBucketOwner, services,
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.IsAdmin(metrics.ActionAdminChangeBucketOwner),
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
))
|
||||
app.Options("/change-bucket-owner",
|
||||
middlewares.ApplyDefaultCORSPreflight(corsAllowOrigin),
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
)
|
||||
|
||||
// ListBucketsAndOwners admin api
|
||||
app.Patch("/list-buckets",
|
||||
controllers.ProcessHandlers(ctrl.ListBuckets, metrics.ActionAdminListBuckets, services,
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.IsAdmin(metrics.ActionAdminListBuckets),
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
))
|
||||
app.Options("/list-buckets",
|
||||
middlewares.ApplyDefaultCORSPreflight(corsAllowOrigin),
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
)
|
||||
}
|
||||
|
||||
+15
-8
@@ -28,13 +28,14 @@ import (
|
||||
)
|
||||
|
||||
type S3AdminServer struct {
|
||||
app *fiber.App
|
||||
backend backend.Backend
|
||||
router *S3AdminRouter
|
||||
port string
|
||||
cert *tls.Certificate
|
||||
quiet bool
|
||||
debug bool
|
||||
app *fiber.App
|
||||
backend backend.Backend
|
||||
router *S3AdminRouter
|
||||
port string
|
||||
cert *tls.Certificate
|
||||
quiet bool
|
||||
debug bool
|
||||
corsAllowOrigin string
|
||||
}
|
||||
|
||||
func NewAdminServer(be backend.Backend, root middlewares.RootUserConfig, port, region string, iam auth.IAMService, l s3log.AuditLogger, opts ...AdminOpt) *S3AdminServer {
|
||||
@@ -73,7 +74,7 @@ func NewAdminServer(be backend.Backend, root middlewares.RootUserConfig, port, r
|
||||
app.Use(controllers.WrapMiddleware(middlewares.DecodeURL, l, nil))
|
||||
app.Use(middlewares.DebugLogger())
|
||||
|
||||
server.router.Init(app, be, iam, l, root, region, server.debug)
|
||||
server.router.Init(app, be, iam, l, root, region, server.debug, server.corsAllowOrigin)
|
||||
|
||||
return server
|
||||
}
|
||||
@@ -94,6 +95,12 @@ func WithAdminDebug() AdminOpt {
|
||||
return func(s *S3AdminServer) { s.debug = true }
|
||||
}
|
||||
|
||||
// WithAdminCORSAllowOrigin sets the default CORS Access-Control-Allow-Origin value
|
||||
// for the standalone admin server.
|
||||
func WithAdminCORSAllowOrigin(origin string) AdminOpt {
|
||||
return func(s *S3AdminServer) { s.corsAllowOrigin = origin }
|
||||
}
|
||||
|
||||
func (sa *S3AdminServer) Serve() (err error) {
|
||||
if sa.cert != nil {
|
||||
return sa.app.ListenTLSWithCertificate(sa.port, *sa.cert)
|
||||
|
||||
@@ -18,6 +18,8 @@ import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/versity/versitygw/auth"
|
||||
@@ -172,6 +174,7 @@ func ProcessController(ctx *fiber.Ctx, controller Controller, s3action string, s
|
||||
|
||||
// Set the response headers
|
||||
SetResponseHeaders(ctx, response.Headers)
|
||||
ensureExposeMetaHeaders(ctx)
|
||||
|
||||
opts := response.MetaOpts
|
||||
if opts == nil {
|
||||
@@ -314,6 +317,77 @@ func ProcessController(ctx *fiber.Ctx, controller Controller, s3action string, s
|
||||
return ctx.Send(res)
|
||||
}
|
||||
|
||||
func ensureExposeMetaHeaders(ctx *fiber.Ctx) {
|
||||
// Only attempt to modify expose headers when CORS is actually in use.
|
||||
if len(ctx.Response().Header.Peek("Access-Control-Allow-Origin")) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
existing := strings.TrimSpace(string(ctx.Response().Header.Peek("Access-Control-Expose-Headers")))
|
||||
if existing == "*" {
|
||||
return
|
||||
}
|
||||
|
||||
lowerExisting := map[string]struct{}{}
|
||||
if existing != "" {
|
||||
for _, part := range strings.Split(existing, ",") {
|
||||
p := strings.ToLower(strings.TrimSpace(part))
|
||||
if p != "" {
|
||||
lowerExisting[p] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
metaNames := map[string]struct{}{}
|
||||
for k := range ctx.Response().Header.All() {
|
||||
key := string(k)
|
||||
if strings.HasPrefix(strings.ToLower(key), "x-amz-meta-") {
|
||||
metaNames[key] = struct{}{}
|
||||
}
|
||||
}
|
||||
if len(metaNames) == 0 {
|
||||
// Still ensure ETag is present if any expose headers exist/are needed.
|
||||
if _, ok := lowerExisting["etag"]; ok {
|
||||
return
|
||||
}
|
||||
if existing == "" {
|
||||
ctx.Response().Header.Set("Access-Control-Expose-Headers", "ETag")
|
||||
return
|
||||
}
|
||||
ctx.Response().Header.Set("Access-Control-Expose-Headers", existing+", ETag")
|
||||
return
|
||||
}
|
||||
|
||||
metaList := make([]string, 0, len(metaNames))
|
||||
for k := range metaNames {
|
||||
metaList = append(metaList, k)
|
||||
}
|
||||
sort.Strings(metaList)
|
||||
|
||||
toAdd := make([]string, 0, 1+len(metaList))
|
||||
if _, ok := lowerExisting["etag"]; !ok {
|
||||
toAdd = append(toAdd, "ETag")
|
||||
lowerExisting["etag"] = struct{}{}
|
||||
}
|
||||
for _, h := range metaList {
|
||||
lh := strings.ToLower(h)
|
||||
if _, ok := lowerExisting[lh]; ok {
|
||||
continue
|
||||
}
|
||||
toAdd = append(toAdd, h)
|
||||
lowerExisting[lh] = struct{}{}
|
||||
}
|
||||
if len(toAdd) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if existing == "" {
|
||||
ctx.Response().Header.Set("Access-Control-Expose-Headers", strings.Join(toAdd, ", "))
|
||||
return
|
||||
}
|
||||
ctx.Response().Header.Set("Access-Control-Expose-Headers", existing+", "+strings.Join(toAdd, ", "))
|
||||
}
|
||||
|
||||
// Sets the response headers
|
||||
func SetResponseHeaders(ctx *fiber.Ctx, headers map[string]*string) {
|
||||
if headers == nil {
|
||||
|
||||
@@ -237,6 +237,21 @@ func TestSetResponseHeaders(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureExposeMetaHeaders_AddsActualMetaHeaderNames(t *testing.T) {
|
||||
app := fiber.New()
|
||||
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||
|
||||
ctx.Response().Header.Add("Access-Control-Allow-Origin", "https://example.com")
|
||||
ctx.Response().Header.Add("Access-Control-Expose-Headers", "ETag")
|
||||
ctx.Response().Header.Set("x-amz-meta-foo", "bar")
|
||||
ctx.Response().Header.Set("x-amz-meta-bar", "baz")
|
||||
|
||||
ensureExposeMetaHeaders(ctx)
|
||||
|
||||
got := string(ctx.Response().Header.Peek("Access-Control-Expose-Headers"))
|
||||
assert.Equal(t, "ETag, X-Amz-Meta-Bar, X-Amz-Meta-Foo", got)
|
||||
}
|
||||
|
||||
// mock the audit logger
|
||||
type mockAuditLogger struct {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright 2026 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"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/versity/versitygw/s3api/middlewares"
|
||||
"github.com/versity/versitygw/s3err"
|
||||
)
|
||||
|
||||
func TestApplyBucketCORS_FallbackOrigin_NoBucketCors_NoRequestOrigin(t *testing.T) {
|
||||
origin := "https://example.com"
|
||||
|
||||
mockedBackend := &BackendMock{
|
||||
GetBucketCorsFunc: func(ctx context.Context, bucket string) ([]byte, error) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchCORSConfiguration)
|
||||
},
|
||||
}
|
||||
|
||||
app := fiber.New()
|
||||
app.Get("/:bucket/test",
|
||||
middlewares.ApplyBucketCORS(mockedBackend, origin),
|
||||
func(c *fiber.Ctx) error {
|
||||
return c.SendStatus(http.StatusOK)
|
||||
},
|
||||
)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "/mybucket/test", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new request: %v", err)
|
||||
}
|
||||
|
||||
resp, err := app.Test(req)
|
||||
if err != nil {
|
||||
t.Fatalf("app.Test: %v", err)
|
||||
}
|
||||
|
||||
if got := resp.Header.Get("Access-Control-Allow-Origin"); got != origin {
|
||||
t.Fatalf("expected Access-Control-Allow-Origin to be set to fallback, got %q", got)
|
||||
}
|
||||
if got := resp.Header.Get("Access-Control-Expose-Headers"); got != "ETag" {
|
||||
t.Fatalf("expected Access-Control-Expose-Headers to include ETag, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyBucketCORS_FallbackOrigin_NotAppliedWhenBucketCorsExists(t *testing.T) {
|
||||
origin := "https://example.com"
|
||||
|
||||
mockedBackend := &BackendMock{
|
||||
GetBucketCorsFunc: func(ctx context.Context, bucket string) ([]byte, error) {
|
||||
return []byte("not-parsed"), nil
|
||||
},
|
||||
}
|
||||
|
||||
app := fiber.New()
|
||||
app.Get("/:bucket/test",
|
||||
middlewares.ApplyBucketCORS(mockedBackend, origin),
|
||||
func(c *fiber.Ctx) error {
|
||||
return c.SendStatus(http.StatusOK)
|
||||
},
|
||||
)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "/mybucket/test", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new request: %v", err)
|
||||
}
|
||||
|
||||
resp, err := app.Test(req)
|
||||
if err != nil {
|
||||
t.Fatalf("app.Test: %v", err)
|
||||
}
|
||||
|
||||
if got := resp.Header.Get("Access-Control-Allow-Origin"); got != "" {
|
||||
t.Fatalf("expected no Access-Control-Allow-Origin when bucket CORS exists, got %q", got)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright 2026 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 middlewares
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/versity/versitygw/backend"
|
||||
"github.com/versity/versitygw/s3err"
|
||||
)
|
||||
|
||||
// ApplyBucketCORSPreflightFallback handles CORS preflight (OPTIONS) requests for S3 routes
|
||||
// when no per-bucket CORS configuration exists.
|
||||
//
|
||||
// If the bucket has no CORS configuration and fallbackOrigin is set, it responds with 204 and:
|
||||
// - Access-Control-Allow-Origin: fallbackOrigin
|
||||
// - Vary: Origin, Access-Control-Request-Headers, Access-Control-Request-Method
|
||||
// - Access-Control-Allow-Methods: mirrors Access-Control-Request-Method (if present)
|
||||
// - Access-Control-Allow-Headers: mirrors Access-Control-Request-Headers (if present)
|
||||
//
|
||||
// If the bucket has a CORS configuration (or fallbackOrigin is blank), it calls next so the
|
||||
// standard CORS OPTIONS handler can apply bucket-specific rules.
|
||||
func ApplyBucketCORSPreflightFallback(be backend.Backend, fallbackOrigin string) fiber.Handler {
|
||||
fallbackOrigin = strings.TrimSpace(fallbackOrigin)
|
||||
if fallbackOrigin == "" {
|
||||
return func(ctx *fiber.Ctx) error { return ctx.Next() }
|
||||
}
|
||||
|
||||
return func(ctx *fiber.Ctx) error {
|
||||
bucket := ctx.Params("bucket")
|
||||
_, err := be.GetBucketCors(ctx.Context(), bucket)
|
||||
if err != nil {
|
||||
if s3Err, ok := err.(s3err.APIError); ok && (s3Err.Code == "NoSuchCORSConfiguration" || s3Err.Code == "NoSuchBucket") {
|
||||
if len(ctx.Response().Header.Peek("Access-Control-Allow-Origin")) == 0 {
|
||||
ctx.Response().Header.Add("Access-Control-Allow-Origin", fallbackOrigin)
|
||||
}
|
||||
if len(ctx.Response().Header.Peek("Vary")) == 0 {
|
||||
ctx.Response().Header.Add("Vary", VaryHdr)
|
||||
}
|
||||
|
||||
if reqMethod := strings.TrimSpace(ctx.Get("Access-Control-Request-Method")); reqMethod != "" {
|
||||
if len(ctx.Response().Header.Peek("Access-Control-Allow-Methods")) == 0 {
|
||||
ctx.Response().Header.Add("Access-Control-Allow-Methods", reqMethod)
|
||||
}
|
||||
}
|
||||
|
||||
if reqHeaders := strings.TrimSpace(ctx.Get("Access-Control-Request-Headers")); reqHeaders != "" {
|
||||
if len(ctx.Response().Header.Peek("Access-Control-Allow-Headers")) == 0 {
|
||||
ctx.Response().Header.Add("Access-Control-Allow-Headers", reqHeaders)
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Status(fiber.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return ctx.Next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// Copyright 2026 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 middlewares
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/versity/versitygw/backend"
|
||||
"github.com/versity/versitygw/s3err"
|
||||
)
|
||||
|
||||
type backendWithGetBucketCors struct {
|
||||
backend.BackendUnsupported
|
||||
getBucketCors func(ctx context.Context, bucket string) ([]byte, error)
|
||||
}
|
||||
|
||||
func (b backendWithGetBucketCors) GetBucketCors(ctx context.Context, bucket string) ([]byte, error) {
|
||||
return b.getBucketCors(ctx, bucket)
|
||||
}
|
||||
|
||||
func TestApplyBucketCORSPreflightFallback_NoBucketCors_Responds204(t *testing.T) {
|
||||
be := backendWithGetBucketCors{
|
||||
getBucketCors: func(ctx context.Context, bucket string) ([]byte, error) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchCORSConfiguration)
|
||||
},
|
||||
}
|
||||
|
||||
app := fiber.New()
|
||||
app.Options("/:bucket",
|
||||
ApplyBucketCORSPreflightFallback(be, "https://example.com"),
|
||||
func(c *fiber.Ctx) error {
|
||||
// Should not be reached if fallback triggers
|
||||
return c.SendStatus(http.StatusTeapot)
|
||||
},
|
||||
)
|
||||
|
||||
req, err := http.NewRequest(http.MethodOptions, "/testing", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new request: %v", err)
|
||||
}
|
||||
req.Header.Set("Origin", "https://request-origin.example")
|
||||
req.Header.Set("Access-Control-Request-Method", "GET")
|
||||
req.Header.Set("Access-Control-Request-Headers", "content-type")
|
||||
|
||||
resp, err := app.Test(req)
|
||||
if err != nil {
|
||||
t.Fatalf("app.Test: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
t.Fatalf("expected status 204, got %d", resp.StatusCode)
|
||||
}
|
||||
if got := resp.Header.Get("Access-Control-Allow-Origin"); got != "https://example.com" {
|
||||
t.Fatalf("expected allow origin fallback, got %q", got)
|
||||
}
|
||||
if got := resp.Header.Get("Access-Control-Allow-Methods"); got != "GET" {
|
||||
t.Fatalf("expected allow methods to mirror request, got %q", got)
|
||||
}
|
||||
if got := resp.Header.Get("Access-Control-Allow-Headers"); got != "content-type" {
|
||||
t.Fatalf("expected allow headers to mirror request, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyBucketCORSPreflightFallback_NoSuchBucket_Responds204(t *testing.T) {
|
||||
be := backendWithGetBucketCors{
|
||||
getBucketCors: func(ctx context.Context, bucket string) ([]byte, error) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchBucket)
|
||||
},
|
||||
}
|
||||
|
||||
app := fiber.New()
|
||||
app.Options("/:bucket",
|
||||
ApplyBucketCORSPreflightFallback(be, "https://example.com"),
|
||||
func(c *fiber.Ctx) error {
|
||||
return c.SendStatus(http.StatusTeapot)
|
||||
},
|
||||
)
|
||||
|
||||
req, err := http.NewRequest(http.MethodOptions, "/testing", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new request: %v", err)
|
||||
}
|
||||
req.Header.Set("Origin", "https://request-origin.example")
|
||||
req.Header.Set("Access-Control-Request-Method", "PUT")
|
||||
req.Header.Set("Access-Control-Request-Headers", "content-type")
|
||||
|
||||
resp, err := app.Test(req)
|
||||
if err != nil {
|
||||
t.Fatalf("app.Test: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
t.Fatalf("expected status 204, got %d", resp.StatusCode)
|
||||
}
|
||||
if got := resp.Header.Get("Access-Control-Allow-Origin"); got != "https://example.com" {
|
||||
t.Fatalf("expected allow origin fallback, got %q", got)
|
||||
}
|
||||
if got := resp.Header.Get("Access-Control-Allow-Methods"); got != "PUT" {
|
||||
t.Fatalf("expected allow methods to mirror request, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyBucketCORSPreflightFallback_BucketHasCors_CallsNext(t *testing.T) {
|
||||
be := backendWithGetBucketCors{
|
||||
getBucketCors: func(ctx context.Context, bucket string) ([]byte, error) {
|
||||
return []byte("dummy"), nil
|
||||
},
|
||||
}
|
||||
|
||||
app := fiber.New()
|
||||
app.Options("/:bucket",
|
||||
ApplyBucketCORSPreflightFallback(be, "https://example.com"),
|
||||
func(c *fiber.Ctx) error {
|
||||
return c.SendStatus(http.StatusOK)
|
||||
},
|
||||
)
|
||||
|
||||
req, err := http.NewRequest(http.MethodOptions, "/testing", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new request: %v", err)
|
||||
}
|
||||
|
||||
resp, err := app.Test(req)
|
||||
if err != nil {
|
||||
t.Fatalf("app.Test: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected status 200 from next handler, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ package middlewares
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/versity/versitygw/auth"
|
||||
@@ -31,12 +32,14 @@ var VaryHdr = "Origin, Access-Control-Request-Headers, Access-Control-Request-Me
|
||||
// checks if origin and method meets the cors rules and
|
||||
// adds the necessary response headers.
|
||||
// CORS check is applied only when 'Origin' request header is present
|
||||
func ApplyBucketCORS(be backend.Backend) fiber.Handler {
|
||||
func ApplyBucketCORS(be backend.Backend, fallbackOrigin string) fiber.Handler {
|
||||
fallbackOrigin = strings.TrimSpace(fallbackOrigin)
|
||||
|
||||
return func(ctx *fiber.Ctx) error {
|
||||
bucket := ctx.Params("bucket")
|
||||
origin := ctx.Get("Origin")
|
||||
// if the origin request header is empty, skip cors validation
|
||||
if origin == "" {
|
||||
// If neither Origin is present nor a fallback is configured, skip CORS entirely.
|
||||
if origin == "" && fallbackOrigin == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -46,12 +49,32 @@ func ApplyBucketCORS(be backend.Backend) fiber.Handler {
|
||||
// If CORS is not configured, S3Error will have code NoSuchCORSConfiguration.
|
||||
// In this case, we can safely continue. For any other error, we should log it.
|
||||
s3Err, ok := err.(s3err.APIError)
|
||||
if ok && (s3Err.Code == "NoSuchCORSConfiguration" || s3Err.Code == "NoSuchBucket") {
|
||||
// Optional global fallback: add Access-Control-Allow-Origin for buckets
|
||||
// without a specific CORS configuration.
|
||||
if fallbackOrigin != "" {
|
||||
if len(ctx.Response().Header.Peek("Access-Control-Allow-Origin")) == 0 {
|
||||
ctx.Response().Header.Add("Access-Control-Allow-Origin", fallbackOrigin)
|
||||
}
|
||||
if len(ctx.Response().Header.Peek("Vary")) == 0 {
|
||||
ctx.Response().Header.Add("Vary", VaryHdr)
|
||||
}
|
||||
ensureExposeETag(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !ok || s3Err.Code != "NoSuchCORSConfiguration" {
|
||||
debuglogger.Logf("failed to get bucket cors for bucket %q: %v", bucket, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// If Origin is missing, don't attempt per-bucket CORS evaluation.
|
||||
// (Fallback has already been handled above for buckets without CORS config.)
|
||||
if origin == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
cors, err := auth.ParseCORSOutput(data)
|
||||
if err != nil {
|
||||
return nil
|
||||
@@ -100,6 +123,9 @@ func ApplyBucketCORS(be backend.Backend) fiber.Handler {
|
||||
}
|
||||
}
|
||||
|
||||
// Always expose ETag and user metadata headers for browser clients.
|
||||
ensureExposeETag(ctx)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2026 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 middlewares
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// ApplyDefaultCORSPreflight responds to CORS preflight (OPTIONS) requests for routes
|
||||
// that don't have per-bucket CORS configuration (e.g. admin APIs).
|
||||
//
|
||||
// It uses the provided fallbackOrigin as the Access-Control-Allow-Origin value.
|
||||
// It mirrors Access-Control-Request-Method into Access-Control-Allow-Methods and
|
||||
// mirrors Access-Control-Request-Headers into Access-Control-Allow-Headers.
|
||||
func ApplyDefaultCORSPreflight(fallbackOrigin string) fiber.Handler {
|
||||
fallbackOrigin = strings.TrimSpace(fallbackOrigin)
|
||||
if fallbackOrigin == "" {
|
||||
return func(ctx *fiber.Ctx) error { return nil }
|
||||
}
|
||||
|
||||
return func(ctx *fiber.Ctx) error {
|
||||
if len(ctx.Response().Header.Peek("Access-Control-Allow-Origin")) == 0 {
|
||||
ctx.Response().Header.Add("Access-Control-Allow-Origin", fallbackOrigin)
|
||||
}
|
||||
if len(ctx.Response().Header.Peek("Vary")) == 0 {
|
||||
ctx.Response().Header.Add("Vary", VaryHdr)
|
||||
}
|
||||
|
||||
if reqMethod := strings.TrimSpace(ctx.Get("Access-Control-Request-Method")); reqMethod != "" {
|
||||
if len(ctx.Response().Header.Peek("Access-Control-Allow-Methods")) == 0 {
|
||||
ctx.Response().Header.Add("Access-Control-Allow-Methods", reqMethod)
|
||||
}
|
||||
}
|
||||
|
||||
if reqHeaders := strings.TrimSpace(ctx.Get("Access-Control-Request-Headers")); reqHeaders != "" {
|
||||
if len(ctx.Response().Header.Peek("Access-Control-Allow-Headers")) == 0 {
|
||||
ctx.Response().Header.Add("Access-Control-Allow-Headers", reqHeaders)
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Status(fiber.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright 2026 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 middlewares
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func TestApplyDefaultCORSPreflight_OptionsSetsPreflightHeaders(t *testing.T) {
|
||||
origin := "https://example.com"
|
||||
|
||||
app := fiber.New()
|
||||
app.Options("/admin",
|
||||
ApplyDefaultCORSPreflight(origin),
|
||||
ApplyDefaultCORS(origin),
|
||||
func(c *fiber.Ctx) error { return nil },
|
||||
)
|
||||
|
||||
req, err := http.NewRequest(http.MethodOptions, "/admin", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new request: %v", err)
|
||||
}
|
||||
req.Header.Set("Origin", "https://request-origin.example")
|
||||
req.Header.Set("Access-Control-Request-Method", "PATCH")
|
||||
req.Header.Set("Access-Control-Request-Headers", "content-type,authorization")
|
||||
|
||||
resp, err := app.Test(req)
|
||||
if err != nil {
|
||||
t.Fatalf("app.Test: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
t.Fatalf("expected status 204, got %d", resp.StatusCode)
|
||||
}
|
||||
if got := resp.Header.Get("Access-Control-Allow-Origin"); got != origin {
|
||||
t.Fatalf("expected allow origin fallback, got %q", got)
|
||||
}
|
||||
if got := resp.Header.Get("Access-Control-Allow-Methods"); got != "PATCH" {
|
||||
t.Fatalf("expected allow methods to mirror request, got %q", got)
|
||||
}
|
||||
if got := resp.Header.Get("Access-Control-Allow-Headers"); got != "content-type,authorization" {
|
||||
t.Fatalf("expected allow headers to mirror request, got %q", got)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright 2026 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 middlewares
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func ensureExposeETag(ctx *fiber.Ctx) {
|
||||
existing := strings.TrimSpace(string(ctx.Response().Header.Peek("Access-Control-Expose-Headers")))
|
||||
defaults := []string{"ETag"}
|
||||
if existing == "" {
|
||||
ctx.Response().Header.Add("Access-Control-Expose-Headers", strings.Join(defaults, ", "))
|
||||
return
|
||||
}
|
||||
|
||||
lowerExisting := map[string]struct{}{}
|
||||
for _, part := range strings.Split(existing, ",") {
|
||||
p := strings.ToLower(strings.TrimSpace(part))
|
||||
if p != "" {
|
||||
lowerExisting[p] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
updated := existing
|
||||
for _, h := range defaults {
|
||||
if _, ok := lowerExisting[strings.ToLower(h)]; ok {
|
||||
continue
|
||||
}
|
||||
updated += ", " + h
|
||||
}
|
||||
|
||||
if updated != existing {
|
||||
ctx.Response().Header.Set("Access-Control-Expose-Headers", updated)
|
||||
}
|
||||
}
|
||||
|
||||
// ApplyDefaultCORS adds a default Access-Control-Allow-Origin header to responses
|
||||
// when the provided fallbackOrigin is non-empty.
|
||||
//
|
||||
// This is intended for routes that don't have per-bucket CORS configuration (e.g. admin APIs).
|
||||
// It will not override an existing Access-Control-Allow-Origin header.
|
||||
func ApplyDefaultCORS(fallbackOrigin string) fiber.Handler {
|
||||
fallbackOrigin = strings.TrimSpace(fallbackOrigin)
|
||||
if fallbackOrigin == "" {
|
||||
return func(ctx *fiber.Ctx) error { return nil }
|
||||
}
|
||||
|
||||
return func(ctx *fiber.Ctx) error {
|
||||
if len(ctx.Response().Header.Peek("Access-Control-Allow-Origin")) == 0 {
|
||||
ctx.Response().Header.Add("Access-Control-Allow-Origin", fallbackOrigin)
|
||||
}
|
||||
if len(ctx.Response().Header.Peek("Vary")) == 0 {
|
||||
ctx.Response().Header.Add("Vary", VaryHdr)
|
||||
}
|
||||
ensureExposeETag(ctx)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2026 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 middlewares
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func TestApplyDefaultCORS_AddsHeaderWhenOriginSet(t *testing.T) {
|
||||
origin := "https://example.com"
|
||||
|
||||
app := fiber.New()
|
||||
app.Get("/admin", ApplyDefaultCORS(origin), func(c *fiber.Ctx) error {
|
||||
return c.SendStatus(http.StatusOK)
|
||||
})
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "/admin", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new request: %v", err)
|
||||
}
|
||||
|
||||
resp, err := app.Test(req)
|
||||
if err != nil {
|
||||
t.Fatalf("app.Test: %v", err)
|
||||
}
|
||||
|
||||
if got := resp.Header.Get("Access-Control-Allow-Origin"); got != origin {
|
||||
t.Fatalf("expected fallback origin header, got %q", got)
|
||||
}
|
||||
if got := resp.Header.Get("Access-Control-Expose-Headers"); got != "ETag" {
|
||||
t.Fatalf("expected expose headers to include ETag, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyDefaultCORS_DoesNotOverrideExistingHeader(t *testing.T) {
|
||||
origin := "https://example.com"
|
||||
|
||||
app := fiber.New()
|
||||
app.Get("/admin", func(c *fiber.Ctx) error {
|
||||
c.Response().Header.Add("Access-Control-Allow-Origin", "https://already-set.com")
|
||||
return nil
|
||||
}, ApplyDefaultCORS(origin), func(c *fiber.Ctx) error {
|
||||
return c.SendStatus(http.StatusOK)
|
||||
})
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "/admin", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new request: %v", err)
|
||||
}
|
||||
|
||||
resp, err := app.Test(req)
|
||||
if err != nil {
|
||||
t.Fatalf("app.Test: %v", err)
|
||||
}
|
||||
|
||||
if got := resp.Header.Get("Access-Control-Allow-Origin"); got != "https://already-set.com" {
|
||||
t.Fatalf("expected existing header to remain, got %q", got)
|
||||
}
|
||||
}
|
||||
+111
-58
@@ -30,7 +30,7 @@ type S3ApiRouter struct {
|
||||
WithAdmSrv bool
|
||||
}
|
||||
|
||||
func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMService, logger s3log.AuditLogger, aLogger s3log.AuditLogger, evs s3event.S3EventSender, mm metrics.Manager, readonly bool, region, virtualDomain string, root middlewares.RootUserConfig) {
|
||||
func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMService, logger s3log.AuditLogger, aLogger s3log.AuditLogger, evs s3event.S3EventSender, mm metrics.Manager, readonly bool, region, virtualDomain string, root middlewares.RootUserConfig, corsAllowOrigin string) {
|
||||
ctrl := controllers.New(be, iam, logger, evs, mm, readonly, virtualDomain)
|
||||
adminServices := &controllers.Services{
|
||||
Logger: aLogger,
|
||||
@@ -44,42 +44,72 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
controllers.ProcessHandlers(adminController.CreateUser, metrics.ActionAdminCreateUser, adminServices,
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.IsAdmin(metrics.ActionAdminCreateUser),
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
))
|
||||
app.Options("/create-user",
|
||||
middlewares.ApplyDefaultCORSPreflight(corsAllowOrigin),
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
)
|
||||
|
||||
// DeleteUsers admin api
|
||||
app.Patch("/delete-user",
|
||||
controllers.ProcessHandlers(adminController.DeleteUser, metrics.ActionAdminDeleteUser, adminServices,
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.IsAdmin(metrics.ActionAdminDeleteUser),
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
))
|
||||
app.Options("/delete-user",
|
||||
middlewares.ApplyDefaultCORSPreflight(corsAllowOrigin),
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
)
|
||||
|
||||
// UpdateUser admin api
|
||||
app.Patch("/update-user",
|
||||
controllers.ProcessHandlers(adminController.UpdateUser, metrics.ActionAdminUpdateUser, adminServices,
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.IsAdmin(metrics.ActionAdminUpdateUser),
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
))
|
||||
app.Options("/update-user",
|
||||
middlewares.ApplyDefaultCORSPreflight(corsAllowOrigin),
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
)
|
||||
|
||||
// ListUsers admin api
|
||||
app.Patch("/list-users",
|
||||
controllers.ProcessHandlers(adminController.ListUsers, metrics.ActionAdminListUsers, adminServices,
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.IsAdmin(metrics.ActionAdminListUsers),
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
))
|
||||
app.Options("/list-users",
|
||||
middlewares.ApplyDefaultCORSPreflight(corsAllowOrigin),
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
)
|
||||
|
||||
// ChangeBucketOwner admin api
|
||||
app.Patch("/change-bucket-owner",
|
||||
controllers.ProcessHandlers(adminController.ChangeBucketOwner, metrics.ActionAdminChangeBucketOwner, adminServices,
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.IsAdmin(metrics.ActionAdminChangeBucketOwner),
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
))
|
||||
app.Options("/change-bucket-owner",
|
||||
middlewares.ApplyDefaultCORSPreflight(corsAllowOrigin),
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
)
|
||||
|
||||
// ListBucketsAndOwners admin api
|
||||
app.Patch("/list-buckets",
|
||||
controllers.ProcessHandlers(adminController.ListBuckets, metrics.ActionAdminListBuckets, adminServices,
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.IsAdmin(metrics.ActionAdminListBuckets),
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
))
|
||||
app.Options("/list-buckets",
|
||||
middlewares.ApplyDefaultCORSPreflight(corsAllowOrigin),
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
)
|
||||
}
|
||||
|
||||
services := &controllers.Services{
|
||||
@@ -92,7 +122,12 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
|
||||
// copy source is not allowed on '/'
|
||||
app.Get("/", middlewares.MatchHeader("X-Amz-Copy-Source"),
|
||||
controllers.ProcessHandlers(ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrCopySourceNotAllowed)), metrics.ActionUndetected, services),
|
||||
controllers.ProcessHandlers(
|
||||
ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrCopySourceNotAllowed)),
|
||||
metrics.ActionUndetected,
|
||||
services,
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
),
|
||||
)
|
||||
|
||||
app.Get("/",
|
||||
@@ -100,11 +135,17 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
ctrl.ListBuckets,
|
||||
metrics.ActionListAllMyBuckets,
|
||||
services,
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionListAllMyBuckets, "", auth.PermissionRead, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
))
|
||||
|
||||
app.Options("/",
|
||||
middlewares.ApplyDefaultCORSPreflight(corsAllowOrigin),
|
||||
middlewares.ApplyDefaultCORS(corsAllowOrigin),
|
||||
)
|
||||
|
||||
bucketRouter := app.Group("/:bucket")
|
||||
objectRouter := app.Group("/:bucket/*")
|
||||
|
||||
@@ -116,12 +157,12 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
metrics.ActionPutBucketTagging,
|
||||
services,
|
||||
middlewares.BucketObjectNameValidator(),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionPutBucketTagging, auth.PutBucketTaggingAction, auth.PermissionWrite, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.VerifyChecksums(false, true, true),
|
||||
middlewares.ParseAcl(be),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
))
|
||||
bucketRouter.Put("",
|
||||
middlewares.MatchQueryArgs("ownershipControls"),
|
||||
@@ -134,7 +175,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.VerifyChecksums(false, true, false),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
bucketRouter.Put("",
|
||||
@@ -148,7 +189,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.VerifyChecksums(false, true, false),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
bucketRouter.Put("",
|
||||
@@ -162,7 +203,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.VerifyChecksums(false, true, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
bucketRouter.Put("",
|
||||
@@ -176,7 +217,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.VerifyChecksums(false, true, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
bucketRouter.Put("",
|
||||
@@ -190,7 +231,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.VerifyChecksums(false, false, false),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
bucketRouter.Put("",
|
||||
@@ -204,7 +245,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.VerifyChecksums(false, false, false),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
bucketRouter.Put("",
|
||||
@@ -386,7 +427,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.VerifyChecksums(false, false, false),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
))
|
||||
|
||||
// HeadBucket action
|
||||
@@ -401,12 +442,11 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
ctrl.HeadBucket,
|
||||
metrics.ActionHeadBucket,
|
||||
services,
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.BucketObjectNameValidator(),
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionHeadBucket, auth.ListBucketAction, auth.PermissionRead, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, false),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
|
||||
@@ -427,7 +467,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionDeleteBucketTagging, auth.PutBucketTaggingAction, auth.PermissionWrite, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
bucketRouter.Delete("",
|
||||
@@ -440,7 +480,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionDeleteBucketOwnershipControls, auth.PutBucketOwnershipControlsAction, auth.PermissionWrite, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
bucketRouter.Delete("",
|
||||
@@ -453,7 +493,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionDeleteBucketPolicy, auth.PutBucketPolicyAction, auth.PermissionWrite, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
bucketRouter.Delete("",
|
||||
@@ -466,7 +506,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionDeleteBucketCors, auth.PutBucketCorsAction, auth.PermissionWrite, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
bucketRouter.Delete("",
|
||||
@@ -595,7 +635,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionDeleteBucket, auth.DeleteBucketAction, auth.PermissionWrite, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
|
||||
@@ -616,7 +656,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionGetBucketLocation, auth.GetBucketLocationAction, auth.PermissionRead, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
),
|
||||
)
|
||||
@@ -630,7 +670,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionGetBucketTagging, auth.GetBucketTaggingAction, auth.PermissionRead, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
bucketRouter.Get("",
|
||||
@@ -643,7 +683,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionGetBucketOwnershipControls, auth.GetBucketOwnershipControlsAction, auth.PermissionRead, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
bucketRouter.Get("",
|
||||
@@ -656,7 +696,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionGetBucketVersioning, auth.GetBucketVersioningAction, auth.PermissionRead, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
bucketRouter.Get("",
|
||||
@@ -669,7 +709,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionGetBucketPolicy, auth.GetBucketPolicyAction, auth.PermissionRead, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
bucketRouter.Get("",
|
||||
@@ -682,7 +722,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionGetBucketCors, auth.GetBucketCorsAction, auth.PermissionRead, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
bucketRouter.Get("",
|
||||
@@ -695,7 +735,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionGetObjectLockConfiguration, auth.GetBucketObjectLockConfigurationAction, auth.PermissionRead, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
bucketRouter.Get("",
|
||||
@@ -708,7 +748,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionGetBucketAcl, auth.GetBucketAclAction, auth.PermissionReadAcp, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, false),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
bucketRouter.Get("",
|
||||
@@ -721,7 +761,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionListMultipartUploads, auth.ListBucketMultipartUploadsAction, auth.PermissionRead, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
bucketRouter.Get("",
|
||||
@@ -734,7 +774,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionListObjectVersions, auth.ListBucketVersionsAction, auth.PermissionRead, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
bucketRouter.Get("",
|
||||
@@ -747,7 +787,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionGetBucketPolicyStatus, auth.GetBucketPolicyStatusAction, auth.PermissionRead, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
bucketRouter.Get("",
|
||||
@@ -981,7 +1021,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionListObjectsV2, auth.ListBucketAction, auth.PermissionRead, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
bucketRouter.Get("",
|
||||
@@ -993,7 +1033,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionListObjects, auth.ListBucketAction, auth.PermissionRead, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
|
||||
@@ -1016,7 +1056,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.VerifyChecksums(false, true, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
|
||||
@@ -1036,7 +1076,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionHeadObject, auth.GetObjectAction, auth.PermissionRead, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, false),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
|
||||
@@ -1070,7 +1110,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionGetObjectTagging, auth.GetObjectTaggingAction, auth.PermissionRead, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
objectRouter.Get("",
|
||||
@@ -1083,7 +1123,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionGetObjectRetention, auth.GetObjectRetentionAction, auth.PermissionRead, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
objectRouter.Get("",
|
||||
@@ -1096,7 +1136,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionGetObjectLegalHold, auth.GetObjectLegalHoldAction, auth.PermissionRead, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
objectRouter.Get("",
|
||||
@@ -1109,7 +1149,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionGetObjectAcl, auth.GetObjectAclAction, auth.PermissionReadAcp, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
objectRouter.Get("",
|
||||
@@ -1122,7 +1162,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionGetObjectAttributes, auth.GetObjectAttributesAction, auth.PermissionRead, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
objectRouter.Get("",
|
||||
@@ -1135,7 +1175,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionListParts, auth.ListMultipartUploadPartsAction, auth.PermissionRead, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
objectRouter.Get("",
|
||||
@@ -1147,7 +1187,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionGetObject, auth.GetObjectAction, auth.PermissionRead, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
|
||||
@@ -1169,7 +1209,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionDeleteObjectTagging, auth.DeleteObjectTaggingAction, auth.PermissionWrite, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
objectRouter.Delete("",
|
||||
@@ -1182,7 +1222,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionAbortMultipartUpload, auth.AbortMultipartUploadAction, auth.PermissionWrite, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
objectRouter.Delete("",
|
||||
@@ -1194,7 +1234,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionDeleteObject, auth.DeleteObjectAction, auth.PermissionWrite, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
|
||||
@@ -1218,7 +1258,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.VerifyChecksums(false, false, false),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
objectRouter.Post("",
|
||||
@@ -1233,7 +1273,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.VerifyChecksums(false, false, false),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
objectRouter.Post("",
|
||||
@@ -1246,7 +1286,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionCompleteMultipartUpload, auth.PutObjectAction, auth.PermissionWrite, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
objectRouter.Post("",
|
||||
@@ -1259,7 +1299,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionCreateMultipartUpload, auth.PutObjectAction, auth.PermissionWrite, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
|
||||
@@ -1271,11 +1311,11 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
metrics.ActionPutObjectTagging,
|
||||
services,
|
||||
middlewares.BucketObjectNameValidator(),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionPutObjectTagging, auth.PutObjectTaggingAction, auth.PermissionWrite, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.VerifyChecksums(false, true, false),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
objectRouter.Put("",
|
||||
@@ -1289,7 +1329,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.VerifyChecksums(false, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
objectRouter.Put("",
|
||||
@@ -1303,7 +1343,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.VerifyChecksums(false, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
objectRouter.Put("",
|
||||
@@ -1317,7 +1357,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.VerifyChecksums(false, false, false),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
objectRouter.Put("",
|
||||
@@ -1331,7 +1371,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionUploadPartCopy, auth.PutObjectAction, auth.PermissionWrite, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
objectRouter.Put("",
|
||||
@@ -1345,7 +1385,7 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, true),
|
||||
middlewares.VerifyV4Signature(root, iam, region, true, true),
|
||||
middlewares.VerifyChecksums(true, false, false),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
|
||||
@@ -1367,10 +1407,10 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
metrics.ActionCopyObject,
|
||||
services,
|
||||
middlewares.BucketObjectNameValidator(),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionCopyObject, auth.PutObjectAction, auth.PermissionWrite, region, false),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, false),
|
||||
middlewares.VerifyV4Signature(root, iam, region, false, true),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
objectRouter.Put("",
|
||||
@@ -1379,18 +1419,31 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
metrics.ActionPutObject,
|
||||
services,
|
||||
middlewares.BucketObjectNameValidator(),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.AuthorizePublicBucketAccess(be, metrics.ActionPutObject, auth.PutObjectAction, auth.PermissionWrite, region, true),
|
||||
middlewares.VerifyPresignedV4Signature(root, iam, region, true),
|
||||
middlewares.VerifyV4Signature(root, iam, region, true, true),
|
||||
middlewares.VerifyChecksums(true, false, false),
|
||||
middlewares.ApplyBucketCORS(be),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
|
||||
app.Options("/:bucket/*", controllers.ProcessHandlers(ctrl.CORSOptions, metrics.ActionOptions, services,
|
||||
middlewares.BucketObjectNameValidator(),
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
app.Options("/:bucket",
|
||||
middlewares.ApplyBucketCORSPreflightFallback(be, corsAllowOrigin),
|
||||
controllers.ProcessHandlers(ctrl.CORSOptions, metrics.ActionOptions, services,
|
||||
middlewares.BucketObjectNameValidator(),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
),
|
||||
)
|
||||
|
||||
app.Options("/:bucket/*",
|
||||
middlewares.ApplyBucketCORSPreflightFallback(be, corsAllowOrigin),
|
||||
controllers.ProcessHandlers(ctrl.CORSOptions, metrics.ActionOptions, services,
|
||||
middlewares.BucketObjectNameValidator(),
|
||||
middlewares.ApplyBucketCORS(be, corsAllowOrigin),
|
||||
middlewares.ParseAcl(be),
|
||||
),
|
||||
)
|
||||
|
||||
// Return MethodNotAllowed for all the unmatched routes
|
||||
app.All("*", controllers.ProcessHandlers(ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrMethodNotAllowed)), metrics.ActionUndetected, services))
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package s3api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/versity/versitygw/auth"
|
||||
"github.com/versity/versitygw/backend"
|
||||
"github.com/versity/versitygw/s3api/middlewares"
|
||||
"github.com/versity/versitygw/s3err"
|
||||
)
|
||||
|
||||
type backendWithCorsOnly struct {
|
||||
backend.BackendUnsupported
|
||||
}
|
||||
|
||||
func (b backendWithCorsOnly) GetBucketCors(ctx context.Context, bucket string) ([]byte, error) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchCORSConfiguration)
|
||||
}
|
||||
|
||||
func TestS3ApiRouter_ListBuckets_DefaultCORSAllowOrigin(t *testing.T) {
|
||||
origin := "https://example.com"
|
||||
|
||||
app := fiber.New()
|
||||
(&S3ApiRouter{}).Init(
|
||||
app,
|
||||
backend.BackendUnsupported{},
|
||||
&auth.IAMServiceInternal{},
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
false,
|
||||
"us-east-1",
|
||||
"",
|
||||
middlewares.RootUserConfig{},
|
||||
origin,
|
||||
)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "/", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new request: %v", err)
|
||||
}
|
||||
|
||||
resp, err := app.Test(req)
|
||||
if err != nil {
|
||||
t.Fatalf("app.Test: %v", err)
|
||||
}
|
||||
|
||||
if got := resp.Header.Get("Access-Control-Allow-Origin"); got != origin {
|
||||
t.Fatalf("expected Access-Control-Allow-Origin %q, got %q", origin, got)
|
||||
}
|
||||
if got := resp.Header.Get("Access-Control-Expose-Headers"); got == "" {
|
||||
t.Fatalf("expected Access-Control-Expose-Headers to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestS3ApiRouter_ListBuckets_OptionsPreflight_DefaultCORS(t *testing.T) {
|
||||
origin := "https://example.com"
|
||||
|
||||
app := fiber.New()
|
||||
(&S3ApiRouter{}).Init(
|
||||
app,
|
||||
backend.BackendUnsupported{},
|
||||
&auth.IAMServiceInternal{},
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
false,
|
||||
"us-east-1",
|
||||
"",
|
||||
middlewares.RootUserConfig{},
|
||||
origin,
|
||||
)
|
||||
|
||||
req, err := http.NewRequest(http.MethodOptions, "/", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new request: %v", err)
|
||||
}
|
||||
req.Header.Set("Origin", "https://client.example")
|
||||
req.Header.Set("Access-Control-Request-Method", "GET")
|
||||
req.Header.Set("Access-Control-Request-Headers", "authorization")
|
||||
|
||||
resp, err := app.Test(req)
|
||||
if err != nil {
|
||||
t.Fatalf("app.Test: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusNoContent, resp.StatusCode)
|
||||
}
|
||||
if got := resp.Header.Get("Access-Control-Allow-Origin"); got != origin {
|
||||
t.Fatalf("expected Access-Control-Allow-Origin %q, got %q", origin, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestS3ApiRouter_PutBucketTagging_ErrorStillIncludesFallbackCORS(t *testing.T) {
|
||||
origin := "http://127.0.0.1:9090"
|
||||
|
||||
app := fiber.New()
|
||||
(&S3ApiRouter{}).Init(
|
||||
app,
|
||||
backendWithCorsOnly{},
|
||||
&auth.IAMServiceInternal{},
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
false,
|
||||
"us-east-1",
|
||||
"",
|
||||
middlewares.RootUserConfig{},
|
||||
origin,
|
||||
)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPut, "/testing?tagging", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new request: %v", err)
|
||||
}
|
||||
req.Header.Set("Origin", origin)
|
||||
|
||||
resp, err := app.Test(req)
|
||||
if err != nil {
|
||||
t.Fatalf("app.Test: %v", err)
|
||||
}
|
||||
|
||||
if got := resp.Header.Get("Access-Control-Allow-Origin"); got != origin {
|
||||
t.Fatalf("expected Access-Control-Allow-Origin %q, got %q", origin, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestS3ApiRouter_PutObjectTagging_ErrorStillIncludesFallbackCORS(t *testing.T) {
|
||||
origin := "http://127.0.0.1:9090"
|
||||
|
||||
app := fiber.New()
|
||||
(&S3ApiRouter{}).Init(
|
||||
app,
|
||||
backendWithCorsOnly{},
|
||||
&auth.IAMServiceInternal{},
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
false,
|
||||
"us-east-1",
|
||||
"",
|
||||
middlewares.RootUserConfig{},
|
||||
origin,
|
||||
)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPut, "/testing/myobj?tagging", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new request: %v", err)
|
||||
}
|
||||
req.Header.Set("Origin", origin)
|
||||
|
||||
resp, err := app.Test(req)
|
||||
if err != nil {
|
||||
t.Fatalf("app.Test: %v", err)
|
||||
}
|
||||
|
||||
if got := resp.Header.Get("Access-Control-Allow-Origin"); got != origin {
|
||||
t.Fatalf("expected Access-Control-Allow-Origin %q, got %q", origin, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestS3ApiRouter_CopyObject_ErrorStillIncludesFallbackCORS(t *testing.T) {
|
||||
origin := "http://127.0.0.1:9090"
|
||||
|
||||
app := fiber.New()
|
||||
(&S3ApiRouter{}).Init(
|
||||
app,
|
||||
backendWithCorsOnly{},
|
||||
&auth.IAMServiceInternal{},
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
false,
|
||||
"us-east-1",
|
||||
"",
|
||||
middlewares.RootUserConfig{},
|
||||
origin,
|
||||
)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPut, "/testing/myobj", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new request: %v", err)
|
||||
}
|
||||
req.Header.Set("Origin", origin)
|
||||
req.Header.Set("X-Amz-Copy-Source", "srcbucket/srckey")
|
||||
|
||||
resp, err := app.Test(req)
|
||||
if err != nil {
|
||||
t.Fatalf("app.Test: %v", err)
|
||||
}
|
||||
|
||||
if got := resp.Header.Get("Access-Control-Allow-Origin"); got != origin {
|
||||
t.Fatalf("expected Access-Control-Allow-Origin %q, got %q", origin, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestS3ApiRouter_PutObject_ErrorStillIncludesFallbackCORS(t *testing.T) {
|
||||
origin := "http://127.0.0.1:9090"
|
||||
|
||||
app := fiber.New()
|
||||
(&S3ApiRouter{}).Init(
|
||||
app,
|
||||
backendWithCorsOnly{},
|
||||
&auth.IAMServiceInternal{},
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
false,
|
||||
"us-east-1",
|
||||
"",
|
||||
middlewares.RootUserConfig{},
|
||||
origin,
|
||||
)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPut, "/testing/myobj", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new request: %v", err)
|
||||
}
|
||||
req.Header.Set("Origin", origin)
|
||||
|
||||
resp, err := app.Test(req)
|
||||
if err != nil {
|
||||
t.Fatalf("app.Test: %v", err)
|
||||
}
|
||||
|
||||
if got := resp.Header.Get("Access-Control-Allow-Origin"); got != origin {
|
||||
t.Fatalf("expected Access-Control-Allow-Origin %q, got %q", origin, got)
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ func TestS3ApiRouter_Init(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.sa.Init(tt.args.app, tt.args.be, tt.args.iam, nil, nil, nil, nil, false, "us-east-1", "", middlewares.RootUserConfig{})
|
||||
tt.sa.Init(tt.args.app, tt.args.be, tt.args.iam, nil, nil, nil, nil, false, "us-east-1", "", middlewares.RootUserConfig{}, "")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+18
-11
@@ -41,16 +41,17 @@ const (
|
||||
)
|
||||
|
||||
type S3ApiServer struct {
|
||||
app *fiber.App
|
||||
backend backend.Backend
|
||||
router *S3ApiRouter
|
||||
port string
|
||||
cert *tls.Certificate
|
||||
quiet bool
|
||||
readonly bool
|
||||
keepAlive bool
|
||||
health string
|
||||
virtualDomain string
|
||||
app *fiber.App
|
||||
backend backend.Backend
|
||||
router *S3ApiRouter
|
||||
port string
|
||||
cert *tls.Certificate
|
||||
quiet bool
|
||||
readonly bool
|
||||
keepAlive bool
|
||||
health string
|
||||
virtualDomain string
|
||||
corsAllowOrigin string
|
||||
}
|
||||
|
||||
func New(
|
||||
@@ -123,7 +124,7 @@ func New(
|
||||
app.Use(middlewares.DebugLogger())
|
||||
}
|
||||
|
||||
server.router.Init(app, be, iam, l, adminLogger, evs, mm, server.readonly, region, server.virtualDomain, root)
|
||||
server.router.Init(app, be, iam, l, adminLogger, evs, mm, server.readonly, region, server.virtualDomain, root, server.corsAllowOrigin)
|
||||
|
||||
return server, nil
|
||||
}
|
||||
@@ -165,6 +166,12 @@ func WithKeepAlive() Option {
|
||||
return func(s *S3ApiServer) { s.keepAlive = true }
|
||||
}
|
||||
|
||||
// WithCORSAllowOrigin sets the default CORS Access-Control-Allow-Origin value.
|
||||
// This is applied when no bucket CORS configuration exists, and for admin APIs.
|
||||
func WithCORSAllowOrigin(origin string) Option {
|
||||
return func(s *S3ApiServer) { s.corsAllowOrigin = origin }
|
||||
}
|
||||
|
||||
func (sa *S3ApiServer) Serve() (err error) {
|
||||
if sa.cert != nil {
|
||||
return sa.app.ListenTLSWithCertificate(sa.port, *sa.cert)
|
||||
|
||||
@@ -222,13 +222,6 @@ func PreflightOPTIONS_access_granted(s *S3Conf) error {
|
||||
ExposeHeaders: []string{"X-Amz-Expected-Bucket-Owner"},
|
||||
MaxAgeSeconds: getPtr(int32(5000)),
|
||||
},
|
||||
{
|
||||
AllowedOrigins: []string{"http://uniquie-origin.net"},
|
||||
AllowedMethods: []string{http.MethodPost, http.MethodPut},
|
||||
AllowedHeaders: []string{"X-Amz-*-Suffix"},
|
||||
ExposeHeaders: []string{"Authorization", "Content-Type"},
|
||||
MaxAgeSeconds: getPtr(int32(2000)),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -245,22 +238,19 @@ func PreflightOPTIONS_access_granted(s *S3Conf) error {
|
||||
result PreflightResult
|
||||
}{
|
||||
// first rule matches
|
||||
{"http://example.com", http.MethodGet, "X-Amz-Date", PreflightResult{"http://example.com", "GET, HEAD", "x-amz-date", "Content-Type, Content-Length", "100", "true", varyHdr, nil}},
|
||||
{"http://example.com", http.MethodGet, "X-Amz-Content-Sha256", PreflightResult{"http://example.com", "GET, HEAD", "x-amz-content-sha256", "Content-Type, Content-Length", "100", "true", varyHdr, nil}},
|
||||
{"http://example.com", http.MethodHead, "", PreflightResult{"http://example.com", "GET, HEAD", "", "Content-Type, Content-Length", "100", "true", varyHdr, nil}},
|
||||
{"https://example.com", http.MethodGet, "X-Amz-Date,X-Amz-Content-Sha256", PreflightResult{"https://example.com", "GET, HEAD", "x-amz-date, x-amz-content-sha256", "Content-Type, Content-Length", "100", "true", varyHdr, nil}},
|
||||
{"http://example.com", http.MethodGet, "X-Amz-Date", PreflightResult{"http://example.com", "GET, HEAD", "x-amz-date", "Content-Type, Content-Length, ETag", "100", "true", varyHdr, nil}},
|
||||
{"http://example.com", http.MethodGet, "X-Amz-Content-Sha256", PreflightResult{"http://example.com", "GET, HEAD", "x-amz-content-sha256", "Content-Type, Content-Length, ETag", "100", "true", varyHdr, nil}},
|
||||
{"http://example.com", http.MethodHead, "", PreflightResult{"http://example.com", "GET, HEAD", "", "Content-Type, Content-Length, ETag", "100", "true", varyHdr, nil}},
|
||||
{"https://example.com", http.MethodGet, "X-Amz-Date,X-Amz-Content-Sha256", PreflightResult{"https://example.com", "GET, HEAD", "x-amz-date, x-amz-content-sha256", "Content-Type, Content-Length, ETag", "100", "true", varyHdr, nil}},
|
||||
// second rule matches: origin is a wildcard
|
||||
{"http://anything.com", http.MethodHead, "X-Amz-Meta-Something", PreflightResult{"*", "HEAD", "x-amz-meta-something", "", "", "false", varyHdr, nil}},
|
||||
{"hello.com", http.MethodHead, "", PreflightResult{"*", "HEAD", "", "", "", "false", varyHdr, nil}},
|
||||
{"http://anything.com", http.MethodHead, "X-Amz-Meta-Something", PreflightResult{"*", "HEAD", "x-amz-meta-something", "ETag", "", "false", varyHdr, nil}},
|
||||
{"hello.com", http.MethodHead, "", PreflightResult{"*", "HEAD", "", "ETag", "", "false", varyHdr, nil}},
|
||||
// third rule matches
|
||||
{"something.net", http.MethodPut, "Authorization", PreflightResult{"something.net", "POST, PUT", "authorization", "Content-Disposition, Content-Encoding", "3000", "true", varyHdr, nil}},
|
||||
{"something.net", http.MethodPost, "", PreflightResult{"something.net", "POST, PUT", "", "Content-Disposition, Content-Encoding", "3000", "true", varyHdr, nil}},
|
||||
{"something.net", http.MethodPut, "Authorization", PreflightResult{"something.net", "POST, PUT", "authorization", "Content-Disposition, Content-Encoding, ETag", "3000", "true", varyHdr, nil}},
|
||||
{"something.net", http.MethodPost, "", PreflightResult{"something.net", "POST, PUT", "", "Content-Disposition, Content-Encoding, ETag", "3000", "true", varyHdr, nil}},
|
||||
// forth rule matches: origin contains wildcard
|
||||
{"http://www.hello.world.com", http.MethodGet, "", PreflightResult{"http://www.hello.world.com", "GET", "", "X-Amz-Expected-Bucket-Owner", "5000", "true", varyHdr, nil}},
|
||||
{"http://www.example.com", http.MethodGet, "x-amz-server-side-encryption", PreflightResult{"http://www.example.com", "GET", "x-amz-server-side-encryption", "X-Amz-Expected-Bucket-Owner", "5000", "true", varyHdr, nil}},
|
||||
// fifth rule matches: allowed headers contains wildcard
|
||||
{"http://uniquie-origin.net", http.MethodPost, "X-Amz-anything-Suffix", PreflightResult{"http://uniquie-origin.net", "POST, PUT", "x-amz-anything-suffix", "Authorization, Content-Type", "2000", "true", varyHdr, nil}},
|
||||
{"http://uniquie-origin.net", http.MethodPut, "X-Amz-yyy-xxx-Suffix", PreflightResult{"http://uniquie-origin.net", "POST, PUT", "x-amz-yyy-xxx-suffix", "Authorization, Content-Type", "2000", "true", varyHdr, nil}},
|
||||
{"http://www.hello.world.com", http.MethodGet, "", PreflightResult{"http://www.hello.world.com", "GET", "", "X-Amz-Expected-Bucket-Owner, ETag", "5000", "true", varyHdr, nil}},
|
||||
{"http://www.example.com", http.MethodGet, "x-amz-server-side-encryption", PreflightResult{"http://www.example.com", "GET", "x-amz-server-side-encryption", "X-Amz-Expected-Bucket-Owner, ETag", "5000", "true", varyHdr, nil}},
|
||||
} {
|
||||
err := testOPTIONSEdnpoint(s, bucket, test.origin, test.method, test.headers, &test.result)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user