mirror of
https://github.com/versity/versitygw.git
synced 2026-08-17 12:46:23 +00:00
Reload TLS certificates on SIGHUP
* Add utils.CertStorage for holding cert data that can be updated at runtime. * Add utils.NewTLSListener() to have a central place to control e.g. TLS MinVersion across different servers. * Add WithTLS() to webserver code so it looks more like the other servers. Fixes #1299
This commit is contained in:
+41
-10
@@ -16,7 +16,6 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
@@ -733,11 +732,12 @@ func runGateway(ctx context.Context, be backend.Backend) error {
|
||||
return fmt.Errorf("TLS cert specified without key file")
|
||||
}
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
cs := utils.NewCertStorage()
|
||||
err := cs.SetCertificate(certFile, keyFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("tls: load certs: %v", err)
|
||||
}
|
||||
opts = append(opts, s3api.WithTLS(cert))
|
||||
opts = append(opts, s3api.WithTLS(cs))
|
||||
}
|
||||
if admPort == "" {
|
||||
opts = append(opts, s3api.WithAdminServer())
|
||||
@@ -873,11 +873,12 @@ func runGateway(ctx context.Context, be backend.Backend) error {
|
||||
return fmt.Errorf("TLS cert specified without key file")
|
||||
}
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(admCertFile, admKeyFile)
|
||||
cs := utils.NewCertStorage()
|
||||
err = cs.SetCertificate(admCertFile, admKeyFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("tls: load certs: %v", err)
|
||||
}
|
||||
opts = append(opts, s3api.WithAdminSrvTLS(cert))
|
||||
opts = append(opts, s3api.WithAdminSrvTLS(cs))
|
||||
}
|
||||
if quiet {
|
||||
opts = append(opts, s3api.WithAdminQuiet())
|
||||
@@ -891,6 +892,8 @@ func runGateway(ctx context.Context, be backend.Backend) error {
|
||||
|
||||
var webSrv *webui.Server
|
||||
webuiSSLEnabled := false
|
||||
webTLSCert := ""
|
||||
webTLSKey := ""
|
||||
if webuiAddr != "" {
|
||||
_, webPrt, err := net.SplitHostPort(webuiAddr)
|
||||
if err != nil {
|
||||
@@ -904,8 +907,7 @@ func runGateway(ctx context.Context, be backend.Backend) error {
|
||||
return fmt.Errorf("webui port must be between 0 and 65535")
|
||||
}
|
||||
|
||||
webTLSCert := ""
|
||||
webTLSKey := ""
|
||||
var webOpts []webui.Option
|
||||
if !webuiNoTLS {
|
||||
// WebUI can either use explicitly provided TLS files or reuse the
|
||||
// gateway's TLS files by default.
|
||||
@@ -923,6 +925,14 @@ func runGateway(ctx context.Context, be backend.Backend) error {
|
||||
return fmt.Errorf("webui TLS cert specified without key file")
|
||||
}
|
||||
webuiSSLEnabled = true
|
||||
|
||||
cs := utils.NewCertStorage()
|
||||
err := cs.SetCertificate(webTLSCert, webTLSKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("tls: load certs: %v", err)
|
||||
}
|
||||
|
||||
webOpts = append(webOpts, webui.WithTLS(cs))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -945,7 +955,6 @@ func runGateway(ctx context.Context, be backend.Backend) error {
|
||||
}
|
||||
}
|
||||
|
||||
var webOpts []webui.Option
|
||||
if quiet {
|
||||
webOpts = append(webOpts, webui.WithQuiet())
|
||||
}
|
||||
@@ -955,8 +964,6 @@ func runGateway(ctx context.Context, be backend.Backend) error {
|
||||
Gateways: gateways,
|
||||
AdminGateways: adminGateways,
|
||||
Region: region,
|
||||
TLSCert: webTLSCert,
|
||||
TLSKey: webTLSKey,
|
||||
}, webOpts...)
|
||||
}
|
||||
|
||||
@@ -1003,6 +1010,30 @@ Loop:
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
if certFile != "" && keyFile != "" {
|
||||
err = srv.CertStorage.SetCertificate(certFile, keyFile)
|
||||
if err != nil {
|
||||
debuglogger.InernalError(fmt.Errorf("srv cert reload failed: %w", err))
|
||||
} else {
|
||||
fmt.Printf("srv cert reloaded (cert: %s, key: %s)\n", certFile, keyFile)
|
||||
}
|
||||
}
|
||||
if admPort != "" && admCertFile != "" && admKeyFile != "" {
|
||||
err = admSrv.CertStorage.SetCertificate(admCertFile, admKeyFile)
|
||||
if err != nil {
|
||||
debuglogger.InernalError(fmt.Errorf("admSrv cert reload failed: %w", err))
|
||||
} else {
|
||||
fmt.Printf("admSrv cert reloaded (cert: %s, key: %s)\n", admCertFile, admKeyFile)
|
||||
}
|
||||
}
|
||||
if webSrv != nil && webTLSCert != "" && webTLSKey != "" {
|
||||
err := webSrv.CertStorage.SetCertificate(webTLSCert, webTLSKey)
|
||||
if err != nil {
|
||||
debuglogger.InernalError(fmt.Errorf("webSrv cert reload failed: %w", err))
|
||||
} else {
|
||||
fmt.Printf("webSrv cert reloaded (cert: %s, key: %s)\n", webTLSCert, webTLSKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
saveErr := err
|
||||
|
||||
+11
-7
@@ -15,8 +15,6 @@
|
||||
package s3api
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||
"github.com/gofiber/fiber/v2/middleware/recover"
|
||||
@@ -25,6 +23,7 @@ import (
|
||||
"github.com/versity/versitygw/debuglogger"
|
||||
"github.com/versity/versitygw/s3api/controllers"
|
||||
"github.com/versity/versitygw/s3api/middlewares"
|
||||
"github.com/versity/versitygw/s3api/utils"
|
||||
"github.com/versity/versitygw/s3log"
|
||||
)
|
||||
|
||||
@@ -33,7 +32,7 @@ type S3AdminServer struct {
|
||||
backend backend.Backend
|
||||
router *S3AdminRouter
|
||||
port string
|
||||
cert *tls.Certificate
|
||||
CertStorage *utils.CertStorage
|
||||
quiet bool
|
||||
debug bool
|
||||
corsAllowOrigin string
|
||||
@@ -88,8 +87,8 @@ func NewAdminServer(be backend.Backend, root middlewares.RootUserConfig, port, r
|
||||
|
||||
type AdminOpt func(s *S3AdminServer)
|
||||
|
||||
func WithAdminSrvTLS(cert tls.Certificate) AdminOpt {
|
||||
return func(s *S3AdminServer) { s.cert = &cert }
|
||||
func WithAdminSrvTLS(cs *utils.CertStorage) AdminOpt {
|
||||
return func(s *S3AdminServer) { s.CertStorage = cs }
|
||||
}
|
||||
|
||||
// WithQuiet silences default logging output
|
||||
@@ -109,8 +108,13 @@ func WithAdminCORSAllowOrigin(origin string) AdminOpt {
|
||||
}
|
||||
|
||||
func (sa *S3AdminServer) Serve() (err error) {
|
||||
if sa.cert != nil {
|
||||
return sa.app.ListenTLSWithCertificate(sa.port, *sa.cert)
|
||||
if sa.CertStorage != nil {
|
||||
ln, err := utils.NewTLSListener(sa.app.Config().Network, sa.port, sa.CertStorage.GetCertificate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sa.app.Listener(ln)
|
||||
}
|
||||
return sa.app.Listen(sa.port)
|
||||
}
|
||||
|
||||
+10
-6
@@ -15,7 +15,6 @@
|
||||
package s3api
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -45,7 +44,7 @@ type S3ApiServer struct {
|
||||
app *fiber.App
|
||||
backend backend.Backend
|
||||
port string
|
||||
cert *tls.Certificate
|
||||
CertStorage *utils.CertStorage
|
||||
quiet bool
|
||||
readonly bool
|
||||
keepAlive bool
|
||||
@@ -133,8 +132,8 @@ func New(
|
||||
type Option func(*S3ApiServer)
|
||||
|
||||
// WithTLS sets TLS Credentials
|
||||
func WithTLS(cert tls.Certificate) Option {
|
||||
return func(s *S3ApiServer) { s.cert = &cert }
|
||||
func WithTLS(cs *utils.CertStorage) Option {
|
||||
return func(s *S3ApiServer) { s.CertStorage = cs }
|
||||
}
|
||||
|
||||
// WithAdminServer runs admin endpoints with the gateway in the same network
|
||||
@@ -173,8 +172,13 @@ func WithCORSAllowOrigin(origin string) Option {
|
||||
}
|
||||
|
||||
func (sa *S3ApiServer) Serve() (err error) {
|
||||
if sa.cert != nil {
|
||||
return sa.app.ListenTLSWithCertificate(sa.port, *sa.cert)
|
||||
if sa.CertStorage != nil {
|
||||
ln, err := utils.NewTLSListener(sa.app.Config().Network, sa.port, sa.CertStorage.GetCertificate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sa.app.Listener(ln)
|
||||
}
|
||||
return sa.app.Listen(sa.port)
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
package s3api
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"testing"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/versity/versitygw/backend"
|
||||
"github.com/versity/versitygw/s3api/utils"
|
||||
)
|
||||
|
||||
func TestS3ApiServer_Serve(t *testing.T) {
|
||||
@@ -42,11 +42,11 @@ func TestS3ApiServer_Serve(t *testing.T) {
|
||||
name: "Serve-invalid-address-with-certificate",
|
||||
wantErr: true,
|
||||
sa: &S3ApiServer{
|
||||
app: fiber.New(),
|
||||
backend: backend.BackendUnsupported{},
|
||||
port: "Invalid address",
|
||||
Router: &S3ApiRouter{},
|
||||
cert: &tls.Certificate{},
|
||||
app: fiber.New(),
|
||||
backend: backend.BackendUnsupported{},
|
||||
port: "Invalid address",
|
||||
Router: &S3ApiRouter{},
|
||||
CertStorage: &utils.CertStorage{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -16,11 +16,13 @@ package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
@@ -910,3 +912,39 @@ func GenerateObjectLocation(ctx *fiber.Ctx, virtualDomain, bucket, object string
|
||||
obj,
|
||||
)
|
||||
}
|
||||
|
||||
type CertStorage struct {
|
||||
cert atomic.Pointer[tls.Certificate]
|
||||
}
|
||||
|
||||
func NewCertStorage() *CertStorage {
|
||||
return &CertStorage{}
|
||||
}
|
||||
|
||||
func (cs *CertStorage) GetCertificate(_ *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
return cs.cert.Load(), nil
|
||||
}
|
||||
|
||||
func (cs *CertStorage) SetCertificate(certFile string, keyFile string) error {
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to set certificate: %w", err)
|
||||
}
|
||||
|
||||
cs.cert.Store(&cert)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewTLSListener(network string, address string, getCertificateFunc func(*tls.ClientHelloInfo) (*tls.Certificate, error)) (net.Listener, error) {
|
||||
config := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
GetCertificate: getCertificateFunc,
|
||||
}
|
||||
|
||||
ln, err := net.Listen(network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tls.NewListener(ln, config), nil
|
||||
}
|
||||
|
||||
+17
-7
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/gofiber/fiber/v2/middleware/filesystem"
|
||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||
"github.com/gofiber/fiber/v2/middleware/recover"
|
||||
"github.com/versity/versitygw/s3api/utils"
|
||||
)
|
||||
|
||||
// ServerConfig holds the server configuration
|
||||
@@ -31,16 +32,15 @@ type ServerConfig struct {
|
||||
Gateways []string // S3 API gateways
|
||||
AdminGateways []string // Admin API gateways (defaults to Gateways if empty)
|
||||
Region string
|
||||
TLSCert string
|
||||
TLSKey string
|
||||
CORSOrigin string
|
||||
}
|
||||
|
||||
// Server is the main GUI server
|
||||
type Server struct {
|
||||
app *fiber.App
|
||||
config *ServerConfig
|
||||
quiet bool
|
||||
app *fiber.App
|
||||
CertStorage *utils.CertStorage
|
||||
config *ServerConfig
|
||||
quiet bool
|
||||
}
|
||||
|
||||
// Option sets various options for NewServer()
|
||||
@@ -51,6 +51,11 @@ func WithQuiet() Option {
|
||||
return func(s *Server) { s.quiet = true }
|
||||
}
|
||||
|
||||
// WithTLS sets TLS Credentials
|
||||
func WithTLS(cs *utils.CertStorage) Option {
|
||||
return func(s *Server) { s.CertStorage = cs }
|
||||
}
|
||||
|
||||
// NewServer creates a new GUI server instance
|
||||
func NewServer(cfg *ServerConfig, opts ...Option) *Server {
|
||||
app := fiber.New(fiber.Config{
|
||||
@@ -127,8 +132,13 @@ func (s *Server) Serve() error {
|
||||
}
|
||||
|
||||
// Check if TLS is configured
|
||||
if s.config.TLSCert != "" && s.config.TLSKey != "" {
|
||||
return s.app.ListenTLS(addr, s.config.TLSCert, s.config.TLSKey)
|
||||
if s.CertStorage != nil {
|
||||
ln, err := utils.NewTLSListener(s.app.Config().Network, addr, s.CertStorage.GetCertificate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.app.Listener(ln)
|
||||
}
|
||||
|
||||
return s.app.Listen(addr)
|
||||
|
||||
Reference in New Issue
Block a user