forked from TrueCloudLab/frostfs-rest-gw
Alex Vanin
d2d88ba21b
Due to source code relocation from GitHub. Signed-off-by: Alex Vanin <a.vanin@yadro.com>
504 lines
13 KiB
Go
504 lines
13 KiB
Go
// Code generated by go-swagger; DO NOT EDIT.
|
|
|
|
package restapi
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"sync"
|
|
"sync/atomic"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/go-openapi/swag"
|
|
"golang.org/x/net/netutil"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-rest-gw/gen/restapi/operations"
|
|
)
|
|
|
|
const (
|
|
schemeHTTP = "http"
|
|
schemeHTTPS = "https"
|
|
)
|
|
|
|
var defaultSchemes []string
|
|
|
|
func init() {
|
|
defaultSchemes = []string{
|
|
schemeHTTP,
|
|
}
|
|
}
|
|
|
|
type ServerConfig struct {
|
|
EnabledListeners []string
|
|
CleanupTimeout time.Duration
|
|
GracefulTimeout time.Duration
|
|
MaxHeaderSize int
|
|
|
|
ListenAddress string
|
|
ListenLimit int
|
|
KeepAlive time.Duration
|
|
ReadTimeout time.Duration
|
|
WriteTimeout time.Duration
|
|
|
|
TLSListenAddress string
|
|
TLSListenLimit int
|
|
TLSKeepAlive time.Duration
|
|
TLSReadTimeout time.Duration
|
|
TLSWriteTimeout time.Duration
|
|
TLSCertificate string
|
|
TLSCertificateKey string
|
|
TLSCACertificate string
|
|
|
|
SuccessfulStartCallback func()
|
|
}
|
|
|
|
// NewServer creates a new api frostfs rest gw server but does not configure it
|
|
func NewServer(api *operations.FrostfsRestGwAPI, cfg *ServerConfig) *Server {
|
|
s := new(Server)
|
|
s.EnabledListeners = cfg.EnabledListeners
|
|
s.CleanupTimeout = cfg.CleanupTimeout
|
|
s.GracefulTimeout = cfg.GracefulTimeout
|
|
s.MaxHeaderSize = cfg.MaxHeaderSize
|
|
s.ListenAddress = cfg.ListenAddress
|
|
s.ListenLimit = cfg.ListenLimit
|
|
s.KeepAlive = cfg.KeepAlive
|
|
s.ReadTimeout = cfg.ReadTimeout
|
|
s.WriteTimeout = cfg.WriteTimeout
|
|
s.TLSListenAddress = cfg.TLSListenAddress
|
|
if len(s.TLSListenAddress) == 0 {
|
|
s.TLSListenAddress = s.ListenAddress
|
|
}
|
|
s.TLSCertificate = cfg.TLSCertificate
|
|
s.TLSCertificateKey = cfg.TLSCertificateKey
|
|
s.TLSCACertificate = cfg.TLSCACertificate
|
|
s.TLSListenLimit = cfg.TLSListenLimit
|
|
s.TLSKeepAlive = cfg.TLSKeepAlive
|
|
s.TLSReadTimeout = cfg.TLSReadTimeout
|
|
s.TLSWriteTimeout = cfg.TLSWriteTimeout
|
|
s.shutdown = make(chan struct{})
|
|
s.api = api
|
|
s.startCallback = cfg.SuccessfulStartCallback
|
|
s.interrupt = make(chan os.Signal, 1)
|
|
return s
|
|
}
|
|
|
|
// ConfigureAPI configures the API and handlers.
|
|
func (s *Server) ConfigureAPI(fn func(*operations.FrostfsRestGwAPI) http.Handler) {
|
|
if s.api != nil {
|
|
s.handler = fn(s.api)
|
|
}
|
|
}
|
|
|
|
// Server for the frostfs rest gw API
|
|
type Server struct {
|
|
EnabledListeners []string
|
|
CleanupTimeout time.Duration
|
|
GracefulTimeout time.Duration
|
|
MaxHeaderSize int
|
|
|
|
ListenAddress string
|
|
ListenLimit int
|
|
KeepAlive time.Duration
|
|
ReadTimeout time.Duration
|
|
WriteTimeout time.Duration
|
|
httpServerL net.Listener
|
|
|
|
TLSListenAddress string
|
|
TLSCertificate string
|
|
TLSCertificateKey string
|
|
TLSCACertificate string
|
|
TLSListenLimit int
|
|
TLSKeepAlive time.Duration
|
|
TLSReadTimeout time.Duration
|
|
TLSWriteTimeout time.Duration
|
|
httpsServerL net.Listener
|
|
|
|
startCallback func()
|
|
|
|
cfgTLSFn func(tlsConfig *tls.Config)
|
|
cfgServerFn func(s *http.Server, scheme, addr string)
|
|
|
|
api *operations.FrostfsRestGwAPI
|
|
handler http.Handler
|
|
hasListeners bool
|
|
shutdown chan struct{}
|
|
shuttingDown int32
|
|
interrupted bool
|
|
interrupt chan os.Signal
|
|
}
|
|
|
|
// Logf logs message either via defined user logger or via system one if no user logger is defined.
|
|
func (s *Server) Logf(f string, args ...interface{}) {
|
|
if s.api != nil && s.api.Logger != nil {
|
|
s.api.Logger(f, args...)
|
|
} else {
|
|
log.Printf(f, args...)
|
|
}
|
|
}
|
|
|
|
// Fatalf logs message either via defined user logger or via system one if no user logger is defined.
|
|
// Exits with non-zero status after printing
|
|
func (s *Server) Fatalf(f string, args ...interface{}) {
|
|
if s.api != nil && s.api.Logger != nil {
|
|
s.api.Logger(f, args...)
|
|
os.Exit(1)
|
|
} else {
|
|
log.Fatalf(f, args...)
|
|
}
|
|
}
|
|
|
|
func (s *Server) hasScheme(scheme string) bool {
|
|
schemes := s.EnabledListeners
|
|
if len(schemes) == 0 {
|
|
schemes = defaultSchemes
|
|
}
|
|
|
|
for _, v := range schemes {
|
|
if v == scheme {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Serve the api
|
|
func (s *Server) Serve() (err error) {
|
|
if !s.hasListeners {
|
|
if err = s.Listen(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// set default handler, if none is set
|
|
if s.handler == nil {
|
|
if s.api == nil {
|
|
return errors.New("can't create the default handler, as no api is set")
|
|
}
|
|
|
|
s.SetHandler(s.api.Serve(nil))
|
|
}
|
|
|
|
wg := new(sync.WaitGroup)
|
|
once := new(sync.Once)
|
|
signalNotify(s.interrupt)
|
|
go handleInterrupt(once, s)
|
|
|
|
servers := []*http.Server{}
|
|
|
|
if s.hasScheme(schemeHTTP) {
|
|
httpServer := new(http.Server)
|
|
httpServer.MaxHeaderBytes = s.MaxHeaderSize
|
|
httpServer.ReadTimeout = s.ReadTimeout
|
|
httpServer.WriteTimeout = s.WriteTimeout
|
|
httpServer.SetKeepAlivesEnabled(int64(s.KeepAlive) > 0)
|
|
if s.ListenLimit > 0 {
|
|
s.httpServerL = netutil.LimitListener(s.httpServerL, s.ListenLimit)
|
|
}
|
|
|
|
if int64(s.CleanupTimeout) > 0 {
|
|
httpServer.IdleTimeout = s.CleanupTimeout
|
|
}
|
|
|
|
httpServer.Handler = s.handler
|
|
|
|
if s.cfgServerFn != nil {
|
|
s.cfgServerFn(httpServer, "http", s.httpServerL.Addr().String())
|
|
}
|
|
|
|
servers = append(servers, httpServer)
|
|
wg.Add(1)
|
|
s.Logf("Serving frostfs rest gw at http://%s", s.httpServerL.Addr())
|
|
go func(l net.Listener) {
|
|
defer wg.Done()
|
|
if err := httpServer.Serve(l); err != nil && err != http.ErrServerClosed {
|
|
s.Fatalf("%v", err)
|
|
}
|
|
s.Logf("Stopped serving frostfs rest gw at http://%s", l.Addr())
|
|
}(s.httpServerL)
|
|
}
|
|
|
|
if s.hasScheme(schemeHTTPS) {
|
|
httpsServer := new(http.Server)
|
|
httpsServer.MaxHeaderBytes = s.MaxHeaderSize
|
|
httpsServer.ReadTimeout = s.TLSReadTimeout
|
|
httpsServer.WriteTimeout = s.TLSWriteTimeout
|
|
httpsServer.SetKeepAlivesEnabled(int64(s.TLSKeepAlive) > 0)
|
|
if s.TLSListenLimit > 0 {
|
|
s.httpsServerL = netutil.LimitListener(s.httpsServerL, s.TLSListenLimit)
|
|
}
|
|
if int64(s.CleanupTimeout) > 0 {
|
|
httpsServer.IdleTimeout = s.CleanupTimeout
|
|
}
|
|
httpsServer.Handler = s.handler
|
|
|
|
// Inspired by https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go
|
|
httpsServer.TLSConfig = &tls.Config{
|
|
// Causes servers to use Go's default ciphersuite preferences,
|
|
// which are tuned to avoid attacks. Does nothing on clients.
|
|
PreferServerCipherSuites: true,
|
|
// Only use curves which have assembly implementations
|
|
// https://github.com/golang/go/tree/master/src/crypto/elliptic
|
|
CurvePreferences: []tls.CurveID{tls.CurveP256},
|
|
// Use modern tls mode https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
|
|
NextProtos: []string{"h2", "http/1.1"},
|
|
// https://www.owasp.org/index.php/Transport_Layer_Protection_Cheat_Sheet#Rule_-_Only_Support_Strong_Protocols
|
|
MinVersion: tls.VersionTLS12,
|
|
// These ciphersuites support Forward Secrecy: https://en.wikipedia.org/wiki/Forward_secrecy
|
|
CipherSuites: []uint16{
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
|
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
|
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
|
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
|
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
|
},
|
|
}
|
|
|
|
// build standard config from server options
|
|
if s.TLSCertificate != "" && s.TLSCertificateKey != "" {
|
|
httpsServer.TLSConfig.Certificates = make([]tls.Certificate, 1)
|
|
httpsServer.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(s.TLSCertificate, s.TLSCertificateKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if s.TLSCACertificate != "" {
|
|
// include specified CA certificate
|
|
caCert, caCertErr := ioutil.ReadFile(s.TLSCACertificate)
|
|
if caCertErr != nil {
|
|
return caCertErr
|
|
}
|
|
caCertPool := x509.NewCertPool()
|
|
ok := caCertPool.AppendCertsFromPEM(caCert)
|
|
if !ok {
|
|
return fmt.Errorf("cannot parse CA certificate")
|
|
}
|
|
httpsServer.TLSConfig.ClientCAs = caCertPool
|
|
httpsServer.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
|
}
|
|
|
|
// call custom TLS configurator
|
|
if s.cfgTLSFn != nil {
|
|
s.cfgTLSFn(httpsServer.TLSConfig)
|
|
}
|
|
|
|
if len(httpsServer.TLSConfig.Certificates) == 0 && httpsServer.TLSConfig.GetCertificate == nil {
|
|
// after standard and custom config are passed, this ends up with no certificate
|
|
if s.TLSCertificate == "" {
|
|
if s.TLSCertificateKey == "" {
|
|
s.Fatalf("the required flags `--tls-certificate` and `--tls-key` were not specified")
|
|
}
|
|
s.Fatalf("the required flag `--tls-certificate` was not specified")
|
|
}
|
|
if s.TLSCertificateKey == "" {
|
|
s.Fatalf("the required flag `--tls-key` was not specified")
|
|
}
|
|
// this happens with a wrong custom TLS configurator
|
|
s.Fatalf("no certificate was configured for TLS")
|
|
}
|
|
|
|
if s.cfgServerFn != nil {
|
|
s.cfgServerFn(httpsServer, "https", s.httpsServerL.Addr().String())
|
|
}
|
|
|
|
servers = append(servers, httpsServer)
|
|
wg.Add(1)
|
|
s.Logf("Serving frostfs rest gw at https://%s", s.httpsServerL.Addr())
|
|
go func(l net.Listener) {
|
|
defer wg.Done()
|
|
if err := httpsServer.Serve(l); err != nil && err != http.ErrServerClosed {
|
|
s.Fatalf("%v", err)
|
|
}
|
|
s.Logf("Stopped serving frostfs rest gw at https://%s", l.Addr())
|
|
}(tls.NewListener(s.httpsServerL, httpsServer.TLSConfig))
|
|
}
|
|
|
|
wg.Add(1)
|
|
go s.handleShutdown(wg, &servers)
|
|
|
|
if s.startCallback != nil {
|
|
s.startCallback()
|
|
}
|
|
|
|
wg.Wait()
|
|
return nil
|
|
}
|
|
|
|
// The TLS configuration before HTTPS server starts.
|
|
func (s *Server) ConfigureTLS(cfgTLS func(tlsConfig *tls.Config)) {
|
|
s.cfgTLSFn = cfgTLS
|
|
}
|
|
|
|
// As soon as server is initialized but not run yet, this function will be called.
|
|
// If you need to modify a config, store server instance to stop it individually later, this is the place.
|
|
// This function can be called multiple times, depending on the number of serving schemes.
|
|
// scheme value will be set accordingly: "http", "https" or "unix".
|
|
func (s *Server) ConfigureServer(cfgServer func(s *http.Server, scheme, addr string)) {
|
|
s.cfgServerFn = cfgServer
|
|
}
|
|
|
|
// Listen creates the listeners for the server
|
|
func (s *Server) Listen() error {
|
|
if s.hasListeners { // already done this
|
|
return nil
|
|
}
|
|
|
|
if s.hasScheme(schemeHTTPS) {
|
|
// Use http listen limit if https listen limit wasn't defined
|
|
if s.TLSListenLimit == 0 {
|
|
s.TLSListenLimit = s.ListenLimit
|
|
}
|
|
// Use http tcp keep alive if https tcp keep alive wasn't defined
|
|
if int64(s.TLSKeepAlive) == 0 {
|
|
s.TLSKeepAlive = s.KeepAlive
|
|
}
|
|
// Use http read timeout if https read timeout wasn't defined
|
|
if int64(s.TLSReadTimeout) == 0 {
|
|
s.TLSReadTimeout = s.ReadTimeout
|
|
}
|
|
// Use http write timeout if https write timeout wasn't defined
|
|
if int64(s.TLSWriteTimeout) == 0 {
|
|
s.TLSWriteTimeout = s.WriteTimeout
|
|
}
|
|
}
|
|
|
|
if s.hasScheme(schemeHTTP) {
|
|
listener, err := net.Listen("tcp", s.ListenAddress)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, _, err = swag.SplitHostPort(listener.Addr().String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.httpServerL = listener
|
|
}
|
|
|
|
if s.hasScheme(schemeHTTPS) {
|
|
tlsListener, err := net.Listen("tcp", s.TLSListenAddress)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, _, err = swag.SplitHostPort(tlsListener.Addr().String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.httpsServerL = tlsListener
|
|
}
|
|
|
|
s.hasListeners = true
|
|
return nil
|
|
}
|
|
|
|
// Shutdown server and clean up resources
|
|
func (s *Server) Shutdown() error {
|
|
if atomic.CompareAndSwapInt32(&s.shuttingDown, 0, 1) {
|
|
close(s.shutdown)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) handleShutdown(wg *sync.WaitGroup, serversPtr *[]*http.Server) {
|
|
// wg.Done must occur last, after s.api.ServerShutdown()
|
|
// (to preserve old behaviour)
|
|
defer wg.Done()
|
|
|
|
<-s.shutdown
|
|
|
|
servers := *serversPtr
|
|
|
|
ctx, cancel := context.WithTimeout(context.TODO(), s.GracefulTimeout)
|
|
defer cancel()
|
|
|
|
// first execute the pre-shutdown hook
|
|
s.api.PreServerShutdown()
|
|
|
|
shutdownChan := make(chan bool)
|
|
for i := range servers {
|
|
server := servers[i]
|
|
go func() {
|
|
var success bool
|
|
defer func() {
|
|
shutdownChan <- success
|
|
}()
|
|
if err := server.Shutdown(ctx); err != nil {
|
|
// Error from closing listeners, or context timeout:
|
|
s.Logf("HTTP server Shutdown: %v", err)
|
|
} else {
|
|
success = true
|
|
}
|
|
}()
|
|
}
|
|
|
|
// Wait until all listeners have successfully shut down before calling ServerShutdown
|
|
success := true
|
|
for range servers {
|
|
success = success && <-shutdownChan
|
|
}
|
|
if success {
|
|
s.api.ServerShutdown()
|
|
}
|
|
}
|
|
|
|
// GetHandler returns a handler useful for testing
|
|
func (s *Server) GetHandler() http.Handler {
|
|
return s.handler
|
|
}
|
|
|
|
// SetHandler allows for setting a http handler on this server
|
|
func (s *Server) SetHandler(handler http.Handler) {
|
|
s.handler = handler
|
|
}
|
|
|
|
// HTTPListener returns the http listener
|
|
func (s *Server) HTTPListener() (net.Listener, error) {
|
|
if !s.hasListeners {
|
|
if err := s.Listen(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return s.httpServerL, nil
|
|
}
|
|
|
|
// TLSListener returns the https listener
|
|
func (s *Server) TLSListener() (net.Listener, error) {
|
|
if !s.hasListeners {
|
|
if err := s.Listen(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return s.httpsServerL, nil
|
|
}
|
|
|
|
func handleInterrupt(once *sync.Once, s *Server) {
|
|
once.Do(func() {
|
|
for range s.interrupt {
|
|
if s.interrupted {
|
|
s.Logf("Server already shutting down")
|
|
continue
|
|
}
|
|
s.interrupted = true
|
|
s.Logf("Shutting down... ")
|
|
if err := s.Shutdown(); err != nil {
|
|
s.Logf("HTTP server Shutdown: %v", err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func signalNotify(interrupt chan<- os.Signal) {
|
|
signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)
|
|
}
|