mirror of
https://github.com/versity/versitygw.git
synced 2026-09-22 16:04:15 +00:00
Add a SessionsSnapshot view over the new C ABI entry point and serve it from the admin server as GET /rc-sessions. The admin server gains a WithAdminRoute option so an embedding binary can register extra admin routes that run with the same signature verification and admin checks as the built-in endpoints; vgwrdma registers the snapshot there when the RC feature is enabled. The route replies with the usual XML error surface so unsigned or non-admin requests get a 403 rather than a generic 500. Stub builds return a not supported error, keeping the build matrix unchanged.
215 lines
6.3 KiB
Go
215 lines
6.3 KiB
Go
// Copyright 2023 Versity Software
|
|
// This file is licensed under the Apache License, Version 2.0
|
|
// (the "License"); you may not use this file except in compliance
|
|
// with the License. You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package s3api
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
|
|
"github.com/gofiber/fiber/v3"
|
|
"github.com/gofiber/fiber/v3/middleware/logger"
|
|
"github.com/gofiber/fiber/v3/middleware/recover"
|
|
"github.com/versity/versitygw/auth"
|
|
"github.com/versity/versitygw/backend"
|
|
"github.com/versity/versitygw/debuglogger"
|
|
"github.com/versity/versitygw/internal/netutil"
|
|
"github.com/versity/versitygw/s3api/controllers"
|
|
"github.com/versity/versitygw/s3api/middlewares"
|
|
"github.com/versity/versitygw/s3log"
|
|
)
|
|
|
|
type S3AdminServer struct {
|
|
app *fiber.App
|
|
backend backend.Backend
|
|
router *S3AdminRouter
|
|
CertStorage *netutil.CertStorage
|
|
quiet bool
|
|
debug bool
|
|
corsAllowOrigin string
|
|
maxConnections int
|
|
maxRequests int
|
|
socketPerm os.FileMode
|
|
extraRoutes []adminRouteMount
|
|
}
|
|
|
|
// adminRouteMount is a route registered on the admin app after the
|
|
// built-in admin router ran.
|
|
type adminRouteMount struct {
|
|
method string
|
|
path string
|
|
handlers []fiber.Handler
|
|
}
|
|
|
|
func NewAdminServer(be backend.Backend, root middlewares.RootUserConfig, region string, iam auth.IAMService, l s3log.AuditLogger, ctrl controllers.S3ApiController, opts ...AdminOpt) *S3AdminServer {
|
|
server := &S3AdminServer{
|
|
backend: be,
|
|
router: &S3AdminRouter{
|
|
s3api: ctrl,
|
|
},
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(server)
|
|
}
|
|
|
|
app := fiber.New(fiber.Config{
|
|
AppName: "versitygw",
|
|
ServerHeader: "VERSITYGW",
|
|
ErrorHandler: globalErrorHandler,
|
|
Concurrency: server.maxConnections,
|
|
})
|
|
|
|
server.app = app
|
|
|
|
app.Use("*", recover.New(
|
|
recover.Config{
|
|
EnableStackTrace: true,
|
|
StackTraceHandler: stackTraceHandler,
|
|
}))
|
|
|
|
// Logging middlewares
|
|
if !server.quiet {
|
|
app.Use("*", logger.New(logger.Config{
|
|
Format: "${time} | adm | ${status} | ${latency} | ${ip} | ${method} | ${path} | ${error} | ${queryParams}\n",
|
|
CustomTags: map[string]logger.LogFunc{
|
|
logger.TagQueryStringParams: debuglogger.RedactedQueryParamsTag,
|
|
},
|
|
}))
|
|
}
|
|
// initialize requestId middleware
|
|
app.Use("*", middlewares.RequestIDs())
|
|
|
|
// initialize total requests cap limiter middleware
|
|
app.Use("*", middlewares.RateLimiter(server.maxRequests, nil, l))
|
|
|
|
app.Use("*", controllers.WrapMiddleware(middlewares.DecodeURL, l, nil))
|
|
|
|
// initialize the debug logger in debug mode
|
|
if debuglogger.IsDebugEnabled() {
|
|
app.Use("*", middlewares.DebugLogger())
|
|
}
|
|
|
|
server.router.Init(app, be, iam, l, root, region, server.debug, server.corsAllowOrigin)
|
|
|
|
for _, r := range server.extraRoutes {
|
|
args := make([]any, 0, len(r.handlers))
|
|
for _, h := range r.handlers {
|
|
args = append(args, h)
|
|
}
|
|
app.Add([]string{r.method}, r.path, args[0], args[1:]...)
|
|
}
|
|
|
|
return server
|
|
}
|
|
|
|
type AdminOpt func(s *S3AdminServer)
|
|
|
|
func WithAdminSrvTLS(cs *netutil.CertStorage) AdminOpt {
|
|
return func(s *S3AdminServer) { s.CertStorage = cs }
|
|
}
|
|
|
|
// WithQuiet silences default logging output
|
|
func WithAdminQuiet() AdminOpt {
|
|
return func(s *S3AdminServer) { s.quiet = true }
|
|
}
|
|
|
|
// WithAdminDebug enables the debug logging
|
|
func WithAdminDebug() AdminOpt {
|
|
return func(s *S3AdminServer) { s.debug = true }
|
|
}
|
|
|
|
// 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 }
|
|
}
|
|
|
|
// WithAdminConcurrencyLimiter sets the admin standalone server's maximum
|
|
// connection limit and the hard limit for in-flight requests.
|
|
func WithAdminConcurrencyLimiter(maxConnections, maxRequests int) AdminOpt {
|
|
return func(s *S3AdminServer) {
|
|
s.maxConnections = maxConnections
|
|
s.maxRequests = maxRequests
|
|
}
|
|
}
|
|
|
|
// WithAdminSocketPerm sets the file-mode permissions applied to file-backed
|
|
// UNIX domain sockets after binding. It has no effect on TCP/IP or abstract
|
|
// namespace sockets.
|
|
func WithAdminSocketPerm(perm os.FileMode) AdminOpt {
|
|
return func(s *S3AdminServer) { s.socketPerm = perm }
|
|
}
|
|
|
|
// WithAdminRoute registers a route on the standalone admin server,
|
|
// after the built-in admin routes and their middleware chain. Use it
|
|
// for admin-surface endpoints that do not fit the S3 admin controller
|
|
// shape.
|
|
func WithAdminRoute(method, path string, handlers ...fiber.Handler) AdminOpt {
|
|
return func(s *S3AdminServer) {
|
|
copied := append([]fiber.Handler(nil), handlers...)
|
|
s.extraRoutes = append(s.extraRoutes, adminRouteMount{
|
|
method: method,
|
|
path: path,
|
|
handlers: copied,
|
|
})
|
|
}
|
|
}
|
|
|
|
// ServeMultiPort creates listeners for multiple port specifications and serves
|
|
// on all of them simultaneously. This supports listening on multiple ports and/or
|
|
// addresses (e.g., [":8080", "localhost:8081"]).
|
|
func (sa *S3AdminServer) ServeMultiPort(ports []string) error {
|
|
if len(ports) == 0 {
|
|
return fmt.Errorf("no ports specified")
|
|
}
|
|
|
|
// Multiple ports - create listeners for each
|
|
var listeners []net.Listener
|
|
|
|
for _, portSpec := range ports {
|
|
var ln net.Listener
|
|
var err error
|
|
|
|
if sa.CertStorage != nil {
|
|
ln, err = netutil.NewMultiAddrTLSListener(fiber.NetworkTCP, portSpec, sa.CertStorage.GetCertificate, netutil.ListenerOptions{SocketPerm: sa.socketPerm})
|
|
} else {
|
|
ln, err = netutil.NewMultiAddrListener(fiber.NetworkTCP, portSpec, netutil.ListenerOptions{SocketPerm: sa.socketPerm})
|
|
}
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to bind admin listener %s: %w", portSpec, err)
|
|
}
|
|
|
|
listeners = append(listeners, ln)
|
|
}
|
|
|
|
if len(listeners) == 0 {
|
|
return fmt.Errorf("failed to create any admin listeners")
|
|
}
|
|
|
|
// Combine all listeners
|
|
finalListener := netutil.NewMultiListener(listeners...)
|
|
|
|
return sa.app.Listener(finalListener, fiber.ListenConfig{
|
|
DisableStartupMessage: true,
|
|
})
|
|
}
|
|
|
|
// ShutDown gracefully shuts down the server with a context timeout
|
|
func (sa S3AdminServer) Shutdown() error {
|
|
return sa.app.ShutdownWithTimeout(shutDownDuration)
|
|
}
|