mirror of
https://github.com/versity/versitygw.git
synced 2026-04-22 13:40:29 +00:00
feat: add --socket-perm option for UNIX socket file permissions
Add a --socket-perm flag (VGW_SOCKET_PERM env var) to control the file-mode permissions on file-backed UNIX domain sockets. This allows operators to limit access permission without relying on process umask. The option applies to S3, admin, and WebUI sockets and has no effect on TCP/IP addresses or Linux abstract namespace sockets. Fixes #2010
This commit is contained in:
@@ -105,6 +105,7 @@ var (
|
||||
disableACLs bool
|
||||
mpMaxParts int
|
||||
copyObjectThreshold int64
|
||||
socketPerm string
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -772,6 +773,12 @@ func initFlags() []cli.Flag {
|
||||
Value: 5 * 1024 * 1024 * 1024,
|
||||
Destination: ©ObjectThreshold,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "socket-perm",
|
||||
Usage: "file permissions for file-backed UNIX domain sockets (octal, e.g. '0660'); ignored for TCP/IP and abstract namespace sockets",
|
||||
EnvVars: []string{"VGW_SOCKET_PERM"},
|
||||
Destination: &socketPerm,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -855,6 +862,15 @@ func runGateway(ctx context.Context, be backend.Backend) error {
|
||||
|
||||
utils.SetBucketNameValidationStrict(!disableStrictBucketNames)
|
||||
|
||||
var parsedSocketPerm os.FileMode
|
||||
if socketPerm != "" {
|
||||
perm, err := strconv.ParseUint(socketPerm, 8, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid --socket-perm value %q: must be an octal integer (e.g. '0660'): %w", socketPerm, err)
|
||||
}
|
||||
parsedSocketPerm = os.FileMode(perm)
|
||||
}
|
||||
|
||||
if pprof != "" {
|
||||
// listen on specified port for pprof debug
|
||||
// point browser to http://<ip:port>/debug/pprof/
|
||||
@@ -867,6 +883,9 @@ func runGateway(ctx context.Context, be backend.Backend) error {
|
||||
s3api.WithConcurrencyLimiter(maxConnections, maxRequests),
|
||||
s3api.WithMpMaxParts(mpMaxParts),
|
||||
}
|
||||
if socketPerm != "" {
|
||||
opts = append(opts, s3api.WithSocketPerm(parsedSocketPerm))
|
||||
}
|
||||
if corsAllowOrigin != "" {
|
||||
opts = append(opts, s3api.WithCORSAllowOrigin(corsAllowOrigin))
|
||||
}
|
||||
@@ -1103,6 +1122,9 @@ func runGateway(ctx context.Context, be backend.Backend) error {
|
||||
if debug {
|
||||
opts = append(opts, s3api.WithAdminDebug())
|
||||
}
|
||||
if socketPerm != "" {
|
||||
opts = append(opts, s3api.WithAdminSocketPerm(parsedSocketPerm))
|
||||
}
|
||||
|
||||
admSrv = s3api.NewAdminServer(be, middlewares.RootUserConfig{Access: rootUserAccess, Secret: rootUserSecret}, region, iam, loggers.AdminLogger, srv.Router.Ctrl, opts...)
|
||||
}
|
||||
@@ -1215,6 +1237,9 @@ func runGateway(ctx context.Context, be backend.Backend) error {
|
||||
if webuiPathPrefix != "" {
|
||||
webOpts = append(webOpts, webui.WithPathPrefix(webuiPathPrefix))
|
||||
}
|
||||
if socketPerm != "" {
|
||||
webOpts = append(webOpts, webui.WithSocketPerm(parsedSocketPerm))
|
||||
}
|
||||
|
||||
webSrv = webui.NewServer(&webui.ServerConfig{
|
||||
Gateways: gateways,
|
||||
|
||||
@@ -102,6 +102,14 @@ ROOT_SECRET_ACCESS_KEY=
|
||||
#VGW_ADMIN_CERT=
|
||||
#VGW_ADMIN_CERT_KEY=
|
||||
|
||||
# The VGW_SOCKET_PERM option sets the file-mode permissions for file-backed
|
||||
# UNIX domain sockets created by the S3, admin, and WebUI servers. The value
|
||||
# must be an octal integer (e.g. 0660 to allow owner and group read/write
|
||||
# access). This option has no effect on TCP/IP addresses or Linux abstract
|
||||
# namespace sockets (those prefixed with "@"). When not set, the socket file
|
||||
# permissions are determined by the process umask.
|
||||
#VGW_SOCKET_PERM=
|
||||
|
||||
# The VGW_QUIET option when set will supress the S3 server request summary
|
||||
# logging to stdout.
|
||||
#VGW_QUIET=false
|
||||
|
||||
@@ -17,6 +17,7 @@ package s3api
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||
@@ -40,6 +41,7 @@ type S3AdminServer struct {
|
||||
corsAllowOrigin string
|
||||
maxConnections int
|
||||
maxRequests int
|
||||
socketPerm os.FileMode
|
||||
}
|
||||
|
||||
func NewAdminServer(be backend.Backend, root middlewares.RootUserConfig, region string, iam auth.IAMService, l s3log.AuditLogger, ctrl controllers.S3ApiController, opts ...AdminOpt) *S3AdminServer {
|
||||
@@ -124,6 +126,13 @@ func WithAdminConcurrencyLimiter(maxConnections, maxRequests int) AdminOpt {
|
||||
}
|
||||
}
|
||||
|
||||
// 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 }
|
||||
}
|
||||
|
||||
// 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"]).
|
||||
@@ -140,9 +149,9 @@ func (sa *S3AdminServer) ServeMultiPort(ports []string) error {
|
||||
var err error
|
||||
|
||||
if sa.CertStorage != nil {
|
||||
ln, err = utils.NewMultiAddrTLSListener(sa.app.Config().Network, portSpec, sa.CertStorage.GetCertificate)
|
||||
ln, err = utils.NewMultiAddrTLSListener(sa.app.Config().Network, portSpec, sa.CertStorage.GetCertificate, utils.ListenerOptions{SocketPerm: sa.socketPerm})
|
||||
} else {
|
||||
ln, err = utils.NewMultiAddrListener(sa.app.Config().Network, portSpec)
|
||||
ln, err = utils.NewMultiAddrListener(sa.app.Config().Network, portSpec, utils.ListenerOptions{SocketPerm: sa.socketPerm})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -54,6 +55,7 @@ type S3ApiServer struct {
|
||||
maxRequests int
|
||||
webuiMountPrefix string
|
||||
webuiSrvCfg *webui.ServerConfig
|
||||
socketPerm os.FileMode
|
||||
}
|
||||
|
||||
func New(
|
||||
@@ -217,6 +219,13 @@ func WithConcurrencyLimiter(maxConnections, maxRequests int) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithSocketPerm 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 WithSocketPerm(perm os.FileMode) Option {
|
||||
return func(s *S3ApiServer) { s.socketPerm = perm }
|
||||
}
|
||||
|
||||
// WithDisableACL disables the s3 api server ACLs, by ignoring all
|
||||
// bucket/object ACL headers
|
||||
func WithDisableACL() Option {
|
||||
@@ -239,9 +248,9 @@ func (sa *S3ApiServer) ServeMultiPort(ports []string) error {
|
||||
var err error
|
||||
|
||||
if sa.CertStorage != nil {
|
||||
ln, err = utils.NewMultiAddrTLSListener(sa.app.Config().Network, portSpec, sa.CertStorage.GetCertificate)
|
||||
ln, err = utils.NewMultiAddrTLSListener(sa.app.Config().Network, portSpec, sa.CertStorage.GetCertificate, utils.ListenerOptions{SocketPerm: sa.socketPerm})
|
||||
} else {
|
||||
ln, err = utils.NewMultiAddrListener(sa.app.Config().Network, portSpec)
|
||||
ln, err = utils.NewMultiAddrListener(sa.app.Config().Network, portSpec, utils.ListenerOptions{SocketPerm: sa.socketPerm})
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to bind s3 listener %s: %w", portSpec, err)
|
||||
|
||||
@@ -272,6 +272,15 @@ func resolveHostnameAddrs(address string) ([]string, error) {
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
// ListenerOptions configures optional behaviour for NewMultiAddrListener and
|
||||
// NewMultiAddrTLSListener.
|
||||
type ListenerOptions struct {
|
||||
// SocketPerm, when non-zero, sets the file-mode permissions on file-backed
|
||||
// UNIX sockets after binding. It is ignored for TCP/IP addresses and
|
||||
// abstract namespace sockets.
|
||||
SocketPerm os.FileMode
|
||||
}
|
||||
|
||||
// NewMultiAddrListener creates listeners for all IP addresses that the hostname
|
||||
// in the address resolves to. If the address is already an IP, it creates a
|
||||
// single listener. Returns a MultiListener if multiple addresses are resolved,
|
||||
@@ -281,7 +290,10 @@ func resolveHostnameAddrs(address string) ([]string, error) {
|
||||
// - "/path/to/socket" or "./rel/socket" — file-backed socket; any stale
|
||||
// socket file is removed before binding.
|
||||
// - "@name" — Linux abstract namespace socket; no file is created or removed.
|
||||
func NewMultiAddrListener(network, address string) (net.Listener, error) {
|
||||
//
|
||||
// opts.SocketPerm, when non-zero, sets the file-mode permissions on file-backed
|
||||
// sockets after binding. It is ignored for TCP/IP addresses and abstract sockets.
|
||||
func NewMultiAddrListener(network, address string, opts ListenerOptions) (net.Listener, error) {
|
||||
if IsUnixSocketPath(address) {
|
||||
// For file-backed sockets, remove any stale socket file so re-binding works cleanly.
|
||||
// Abstract sockets (@name) have no filesystem entry; skip removal for them.
|
||||
@@ -294,6 +306,12 @@ func NewMultiAddrListener(network, address string) (net.Listener, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to bind unix socket listener %s: %w", address, err)
|
||||
}
|
||||
if opts.SocketPerm != 0 && !isAbstractSocket(address) {
|
||||
if err := os.Chmod(address, opts.SocketPerm); err != nil {
|
||||
ln.Close()
|
||||
return nil, fmt.Errorf("failed to set permissions on socket %s: %w", address, err)
|
||||
}
|
||||
}
|
||||
return NewMultiListener(ln), nil
|
||||
}
|
||||
|
||||
@@ -328,7 +346,10 @@ func NewMultiAddrListener(network, address string) (net.Listener, error) {
|
||||
// - "/path/to/socket" or "./rel/socket" — file-backed socket; any stale
|
||||
// socket file is removed before binding.
|
||||
// - "@name" — Linux abstract namespace socket; no file is created or removed.
|
||||
func NewMultiAddrTLSListener(network, address string, getCertificateFunc func(*tls.ClientHelloInfo) (*tls.Certificate, error)) (net.Listener, error) {
|
||||
//
|
||||
// opts.SocketPerm, when non-zero, sets the file-mode permissions on file-backed
|
||||
// sockets after binding. It is ignored for TCP/IP addresses and abstract sockets.
|
||||
func NewMultiAddrTLSListener(network, address string, getCertificateFunc func(*tls.ClientHelloInfo) (*tls.Certificate, error), opts ListenerOptions) (net.Listener, error) {
|
||||
config := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
GetCertificate: getCertificateFunc,
|
||||
@@ -344,6 +365,12 @@ func NewMultiAddrTLSListener(network, address string, getCertificateFunc func(*t
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to bind unix TLS socket listener %s: %w", address, err)
|
||||
}
|
||||
if opts.SocketPerm != 0 && !isAbstractSocket(address) {
|
||||
if err := os.Chmod(address, opts.SocketPerm); err != nil {
|
||||
ln.Close()
|
||||
return nil, fmt.Errorf("failed to set permissions on socket %s: %w", address, err)
|
||||
}
|
||||
}
|
||||
return NewMultiListener(tls.NewListener(ln, config)), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -301,7 +301,7 @@ func TestNewMultiAddrListener(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ln, err := NewMultiAddrListener("tcp", tt.address)
|
||||
ln, err := NewMultiAddrListener("tcp", tt.address, ListenerOptions{})
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("NewMultiAddrListener() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
@@ -353,7 +353,7 @@ func TestNewMultiAddrTLSListener(t *testing.T) {
|
||||
return &cert, err
|
||||
}
|
||||
|
||||
ln, err := NewMultiAddrTLSListener("tcp", "127.0.0.1:0", getCertFunc)
|
||||
ln, err := NewMultiAddrTLSListener("tcp", "127.0.0.1:0", getCertFunc, ListenerOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("NewMultiAddrTLSListener() error = %v", err)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
@@ -43,6 +44,7 @@ type Server struct {
|
||||
config *ServerConfig
|
||||
pathPrefix string
|
||||
quiet bool
|
||||
socketPerm os.FileMode
|
||||
}
|
||||
|
||||
// Option sets various options for NewServer()
|
||||
@@ -63,6 +65,13 @@ func WithPathPrefix(prefix string) Option {
|
||||
return func(s *Server) { s.pathPrefix = prefix }
|
||||
}
|
||||
|
||||
// WithSocketPerm 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 WithSocketPerm(perm os.FileMode) Option {
|
||||
return func(s *Server) { s.socketPerm = perm }
|
||||
}
|
||||
|
||||
// NewServer creates a new GUI server instance
|
||||
func NewServer(cfg *ServerConfig, opts ...Option) *Server {
|
||||
app := fiber.New(fiber.Config{
|
||||
@@ -175,9 +184,9 @@ func (s *Server) ServeMultiPort(ports []string) error {
|
||||
var err error
|
||||
|
||||
if s.CertStorage != nil {
|
||||
ln, err = utils.NewMultiAddrTLSListener(s.app.Config().Network, addrSpec, s.CertStorage.GetCertificate)
|
||||
ln, err = utils.NewMultiAddrTLSListener(s.app.Config().Network, addrSpec, s.CertStorage.GetCertificate, utils.ListenerOptions{SocketPerm: s.socketPerm})
|
||||
} else {
|
||||
ln, err = utils.NewMultiAddrListener(s.app.Config().Network, addrSpec)
|
||||
ln, err = utils.NewMultiAddrListener(s.app.Config().Network, addrSpec, utils.ListenerOptions{SocketPerm: s.socketPerm})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user