Merge pull request #2068 from versity/ben/socket-permissions

This commit is contained in:
Ben McClelland
2026-04-21 06:15:58 -07:00
committed by GitHub
7 changed files with 97 additions and 10 deletions
+25
View File
@@ -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: &copyObjectThreshold,
},
&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,
+8
View File
@@ -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
+11 -2
View File
@@ -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 {
+11 -2
View File
@@ -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)
+29 -2
View File
@@ -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
}
+2 -2
View File
@@ -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)
}
+11 -2
View File
@@ -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 {