github.com/minio/console@v1.4.1/api/server.go (about)

     1  //go:build exclude
     2  
     3  // This file is generated by go-swagger but it will be
     4  // replaced by a proper server to have more customization
     5  // Currently, this file is replaced by custom-server.go
     6  
     7  // Code generated by go-swagger; DO NOT EDIT.
     8  
     9  // This file is part of MinIO Console Server
    10  // Copyright (c) 2023 MinIO, Inc.
    11  //
    12  // This program is free software: you can redistribute it and/or modify
    13  // it under the terms of the GNU Affero General Public License as published by
    14  // the Free Software Foundation, either version 3 of the License, or
    15  // (at your option) any later version.
    16  //
    17  // This program is distributed in the hope that it will be useful,
    18  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    19  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    20  // GNU Affero General Public License for more details.
    21  //
    22  // You should have received a copy of the GNU Affero General Public License
    23  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    24  //
    25  
    26  package api
    27  
    28  import (
    29  	"context"
    30  	"crypto/tls"
    31  	"crypto/x509"
    32  	"errors"
    33  	"fmt"
    34  	"log"
    35  	"net"
    36  	"net/http"
    37  	"os"
    38  	"os/signal"
    39  	"strconv"
    40  	"sync"
    41  	"sync/atomic"
    42  	"syscall"
    43  	"time"
    44  
    45  	"github.com/go-openapi/runtime/flagext"
    46  	"github.com/go-openapi/swag"
    47  	flags "github.com/jessevdk/go-flags"
    48  	"golang.org/x/net/netutil"
    49  
    50  	"github.com/minio/console/api/operations"
    51  )
    52  
    53  const (
    54  	schemeHTTP  = "http"
    55  	schemeHTTPS = "https"
    56  	schemeUnix  = "unix"
    57  )
    58  
    59  var defaultSchemes []string
    60  
    61  func init() {
    62  	defaultSchemes = []string{
    63  		schemeHTTP,
    64  	}
    65  }
    66  
    67  // NewServer creates a new api console server but does not configure it
    68  func NewServer(api *operations.ConsoleAPI) *Server {
    69  	s := new(Server)
    70  
    71  	s.shutdown = make(chan struct{})
    72  	s.api = api
    73  	s.interrupt = make(chan os.Signal, 1)
    74  	return s
    75  }
    76  
    77  // ConfigureAPI configures the API and handlers.
    78  func (s *Server) ConfigureAPI() {
    79  	if s.api != nil {
    80  		s.handler = configureAPI(s.api)
    81  	}
    82  }
    83  
    84  // ConfigureFlags configures the additional flags defined by the handlers. Needs to be called before the parser.Parse
    85  func (s *Server) ConfigureFlags() {
    86  	if s.api != nil {
    87  		configureFlags(s.api)
    88  	}
    89  }
    90  
    91  // Server for the console API
    92  type Server struct {
    93  	EnabledListeners []string         `long:"scheme" description:"the listeners to enable, this can be repeated and defaults to the schemes in the swagger spec"`
    94  	CleanupTimeout   time.Duration    `long:"cleanup-timeout" description:"grace period for which to wait before killing idle connections" default:"10s"`
    95  	GracefulTimeout  time.Duration    `long:"graceful-timeout" description:"grace period for which to wait before shutting down the server" default:"15s"`
    96  	MaxHeaderSize    flagext.ByteSize `long:"max-header-size" description:"controls the maximum number of bytes the server will read parsing the request header's keys and values, including the request line. It does not limit the size of the request body." default:"1MiB"`
    97  
    98  	SocketPath    flags.Filename `long:"socket-path" description:"the unix socket to listen on" default:"/var/run/console.sock"`
    99  	domainSocketL net.Listener
   100  
   101  	Host         string        `long:"host" description:"the IP to listen on" default:"localhost" env:"HOST"`
   102  	Port         int           `long:"port" description:"the port to listen on for insecure connections, defaults to a random value" env:"PORT"`
   103  	ListenLimit  int           `long:"listen-limit" description:"limit the number of outstanding requests"`
   104  	KeepAlive    time.Duration `long:"keep-alive" description:"sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)" default:"3m"`
   105  	ReadTimeout  time.Duration `long:"read-timeout" description:"maximum duration before timing out read of the request" default:"30s"`
   106  	WriteTimeout time.Duration `long:"write-timeout" description:"maximum duration before timing out write of the response" default:"60s"`
   107  	httpServerL  net.Listener
   108  
   109  	TLSHost           string         `long:"tls-host" description:"the IP to listen on for tls, when not specified it's the same as --host" env:"TLS_HOST"`
   110  	TLSPort           int            `long:"tls-port" description:"the port to listen on for secure connections, defaults to a random value" env:"TLS_PORT"`
   111  	TLSCertificate    flags.Filename `long:"tls-certificate" description:"the certificate to use for secure connections" env:"TLS_CERTIFICATE"`
   112  	TLSCertificateKey flags.Filename `long:"tls-key" description:"the private key to use for secure connections" env:"TLS_PRIVATE_KEY"`
   113  	TLSCACertificate  flags.Filename `long:"tls-ca" description:"the certificate authority file to be used with mutual tls auth" env:"TLS_CA_CERTIFICATE"`
   114  	TLSListenLimit    int            `long:"tls-listen-limit" description:"limit the number of outstanding requests"`
   115  	TLSKeepAlive      time.Duration  `long:"tls-keep-alive" description:"sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)"`
   116  	TLSReadTimeout    time.Duration  `long:"tls-read-timeout" description:"maximum duration before timing out read of the request"`
   117  	TLSWriteTimeout   time.Duration  `long:"tls-write-timeout" description:"maximum duration before timing out write of the response"`
   118  	httpsServerL      net.Listener
   119  
   120  	api          *operations.ConsoleAPI
   121  	handler      http.Handler
   122  	hasListeners bool
   123  	shutdown     chan struct{}
   124  	shuttingDown int32
   125  	interrupted  bool
   126  	interrupt    chan os.Signal
   127  }
   128  
   129  // Logf logs message either via defined user logger or via system one if no user logger is defined.
   130  func (s *Server) Logf(f string, args ...interface{}) {
   131  	if s.api != nil && s.api.Logger != nil {
   132  		s.api.Logger(f, args...)
   133  	} else {
   134  		log.Printf(f, args...)
   135  	}
   136  }
   137  
   138  // Fatalf logs message either via defined user logger or via system one if no user logger is defined.
   139  // Exits with non-zero status after printing
   140  func (s *Server) Fatalf(f string, args ...interface{}) {
   141  	if s.api != nil && s.api.Logger != nil {
   142  		s.api.Logger(f, args...)
   143  		os.Exit(1)
   144  	} else {
   145  		log.Fatalf(f, args...)
   146  	}
   147  }
   148  
   149  // SetAPI configures the server with the specified API. Needs to be called before Serve
   150  func (s *Server) SetAPI(api *operations.ConsoleAPI) {
   151  	if api == nil {
   152  		s.api = nil
   153  		s.handler = nil
   154  		return
   155  	}
   156  
   157  	s.api = api
   158  	s.handler = configureAPI(api)
   159  }
   160  
   161  func (s *Server) hasScheme(scheme string) bool {
   162  	schemes := s.EnabledListeners
   163  	if len(schemes) == 0 {
   164  		schemes = defaultSchemes
   165  	}
   166  
   167  	for _, v := range schemes {
   168  		if v == scheme {
   169  			return true
   170  		}
   171  	}
   172  	return false
   173  }
   174  
   175  // Serve the api
   176  func (s *Server) Serve() (err error) {
   177  	if !s.hasListeners {
   178  		if err = s.Listen(); err != nil {
   179  			return err
   180  		}
   181  	}
   182  
   183  	// set default handler, if none is set
   184  	if s.handler == nil {
   185  		if s.api == nil {
   186  			return errors.New("can't create the default handler, as no api is set")
   187  		}
   188  
   189  		s.SetHandler(s.api.Serve(nil))
   190  	}
   191  
   192  	wg := new(sync.WaitGroup)
   193  	once := new(sync.Once)
   194  	signalNotify(s.interrupt)
   195  	go handleInterrupt(once, s)
   196  
   197  	servers := []*http.Server{}
   198  
   199  	if s.hasScheme(schemeUnix) {
   200  		domainSocket := new(http.Server)
   201  		domainSocket.MaxHeaderBytes = int(s.MaxHeaderSize)
   202  		domainSocket.Handler = s.handler
   203  		if int64(s.CleanupTimeout) > 0 {
   204  			domainSocket.IdleTimeout = s.CleanupTimeout
   205  		}
   206  
   207  		configureServer(domainSocket, "unix", string(s.SocketPath))
   208  
   209  		servers = append(servers, domainSocket)
   210  		wg.Add(1)
   211  		s.Logf("Serving console at unix://%s", s.SocketPath)
   212  		go func(l net.Listener) {
   213  			defer wg.Done()
   214  			if err := domainSocket.Serve(l); err != nil && err != http.ErrServerClosed {
   215  				s.Fatalf("%v", err)
   216  			}
   217  			s.Logf("Stopped serving console at unix://%s", s.SocketPath)
   218  		}(s.domainSocketL)
   219  	}
   220  
   221  	if s.hasScheme(schemeHTTP) {
   222  		httpServer := new(http.Server)
   223  		httpServer.MaxHeaderBytes = int(s.MaxHeaderSize)
   224  		httpServer.ReadTimeout = s.ReadTimeout
   225  		httpServer.WriteTimeout = s.WriteTimeout
   226  		httpServer.SetKeepAlivesEnabled(int64(s.KeepAlive) > 0)
   227  		if s.ListenLimit > 0 {
   228  			s.httpServerL = netutil.LimitListener(s.httpServerL, s.ListenLimit)
   229  		}
   230  
   231  		if int64(s.CleanupTimeout) > 0 {
   232  			httpServer.IdleTimeout = s.CleanupTimeout
   233  		}
   234  
   235  		httpServer.Handler = s.handler
   236  
   237  		configureServer(httpServer, "http", s.httpServerL.Addr().String())
   238  
   239  		servers = append(servers, httpServer)
   240  		wg.Add(1)
   241  		s.Logf("Serving console at http://%s", s.httpServerL.Addr())
   242  		go func(l net.Listener) {
   243  			defer wg.Done()
   244  			if err := httpServer.Serve(l); err != nil && err != http.ErrServerClosed {
   245  				s.Fatalf("%v", err)
   246  			}
   247  			s.Logf("Stopped serving console at http://%s", l.Addr())
   248  		}(s.httpServerL)
   249  	}
   250  
   251  	if s.hasScheme(schemeHTTPS) {
   252  		httpsServer := new(http.Server)
   253  		httpsServer.MaxHeaderBytes = int(s.MaxHeaderSize)
   254  		httpsServer.ReadTimeout = s.TLSReadTimeout
   255  		httpsServer.WriteTimeout = s.TLSWriteTimeout
   256  		httpsServer.SetKeepAlivesEnabled(int64(s.TLSKeepAlive) > 0)
   257  		if s.TLSListenLimit > 0 {
   258  			s.httpsServerL = netutil.LimitListener(s.httpsServerL, s.TLSListenLimit)
   259  		}
   260  		if int64(s.CleanupTimeout) > 0 {
   261  			httpsServer.IdleTimeout = s.CleanupTimeout
   262  		}
   263  		httpsServer.Handler = s.handler
   264  
   265  		// Inspired by https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go
   266  		httpsServer.TLSConfig = &tls.Config{
   267  			// Causes servers to use Go's default ciphersuite preferences,
   268  			// which are tuned to avoid attacks. Does nothing on clients.
   269  			PreferServerCipherSuites: true,
   270  			// Only use curves which have assembly implementations
   271  			// https://github.com/golang/go/tree/master/src/crypto/elliptic
   272  			CurvePreferences: []tls.CurveID{tls.CurveP256},
   273  			// Use modern tls mode https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
   274  			NextProtos: []string{"h2", "http/1.1"},
   275  			// https://www.owasp.org/index.php/Transport_Layer_Protection_Cheat_Sheet#Rule_-_Only_Support_Strong_Protocols
   276  			MinVersion: tls.VersionTLS12,
   277  			// These ciphersuites support Forward Secrecy: https://en.wikipedia.org/wiki/Forward_secrecy
   278  			CipherSuites: []uint16{
   279  				tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
   280  				tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
   281  				tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
   282  				tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
   283  				tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
   284  				tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
   285  			},
   286  		}
   287  
   288  		// build standard config from server options
   289  		if s.TLSCertificate != "" && s.TLSCertificateKey != "" {
   290  			httpsServer.TLSConfig.Certificates = make([]tls.Certificate, 1)
   291  			httpsServer.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(string(s.TLSCertificate), string(s.TLSCertificateKey))
   292  			if err != nil {
   293  				return err
   294  			}
   295  		}
   296  
   297  		if s.TLSCACertificate != "" {
   298  			// include specified CA certificate
   299  			caCert, caCertErr := os.ReadFile(string(s.TLSCACertificate))
   300  			if caCertErr != nil {
   301  				return caCertErr
   302  			}
   303  			caCertPool := x509.NewCertPool()
   304  			ok := caCertPool.AppendCertsFromPEM(caCert)
   305  			if !ok {
   306  				return fmt.Errorf("cannot parse CA certificate")
   307  			}
   308  			httpsServer.TLSConfig.ClientCAs = caCertPool
   309  			httpsServer.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
   310  		}
   311  
   312  		// call custom TLS configurator
   313  		configureTLS(httpsServer.TLSConfig)
   314  
   315  		if len(httpsServer.TLSConfig.Certificates) == 0 && httpsServer.TLSConfig.GetCertificate == nil {
   316  			// after standard and custom config are passed, this ends up with no certificate
   317  			if s.TLSCertificate == "" {
   318  				if s.TLSCertificateKey == "" {
   319  					s.Fatalf("the required flags `--tls-certificate` and `--tls-key` were not specified")
   320  				}
   321  				s.Fatalf("the required flag `--tls-certificate` was not specified")
   322  			}
   323  			if s.TLSCertificateKey == "" {
   324  				s.Fatalf("the required flag `--tls-key` was not specified")
   325  			}
   326  			// this happens with a wrong custom TLS configurator
   327  			s.Fatalf("no certificate was configured for TLS")
   328  		}
   329  
   330  		configureServer(httpsServer, "https", s.httpsServerL.Addr().String())
   331  
   332  		servers = append(servers, httpsServer)
   333  		wg.Add(1)
   334  		s.Logf("Serving console at https://%s", s.httpsServerL.Addr())
   335  		go func(l net.Listener) {
   336  			defer wg.Done()
   337  			if err := httpsServer.Serve(l); err != nil && err != http.ErrServerClosed {
   338  				s.Fatalf("%v", err)
   339  			}
   340  			s.Logf("Stopped serving console at https://%s", l.Addr())
   341  		}(tls.NewListener(s.httpsServerL, httpsServer.TLSConfig))
   342  	}
   343  
   344  	wg.Add(1)
   345  	go s.handleShutdown(wg, &servers)
   346  
   347  	wg.Wait()
   348  	return nil
   349  }
   350  
   351  // Listen creates the listeners for the server
   352  func (s *Server) Listen() error {
   353  	if s.hasListeners { // already done this
   354  		return nil
   355  	}
   356  
   357  	if s.hasScheme(schemeHTTPS) {
   358  		// Use http host if https host wasn't defined
   359  		if s.TLSHost == "" {
   360  			s.TLSHost = s.Host
   361  		}
   362  		// Use http listen limit if https listen limit wasn't defined
   363  		if s.TLSListenLimit == 0 {
   364  			s.TLSListenLimit = s.ListenLimit
   365  		}
   366  		// Use http tcp keep alive if https tcp keep alive wasn't defined
   367  		if int64(s.TLSKeepAlive) == 0 {
   368  			s.TLSKeepAlive = s.KeepAlive
   369  		}
   370  		// Use http read timeout if https read timeout wasn't defined
   371  		if int64(s.TLSReadTimeout) == 0 {
   372  			s.TLSReadTimeout = s.ReadTimeout
   373  		}
   374  		// Use http write timeout if https write timeout wasn't defined
   375  		if int64(s.TLSWriteTimeout) == 0 {
   376  			s.TLSWriteTimeout = s.WriteTimeout
   377  		}
   378  	}
   379  
   380  	if s.hasScheme(schemeUnix) {
   381  		domSockListener, err := net.Listen("unix", string(s.SocketPath))
   382  		if err != nil {
   383  			return err
   384  		}
   385  		s.domainSocketL = domSockListener
   386  	}
   387  
   388  	if s.hasScheme(schemeHTTP) {
   389  		listener, err := net.Listen("tcp", net.JoinHostPort(s.Host, strconv.Itoa(s.Port)))
   390  		if err != nil {
   391  			return err
   392  		}
   393  
   394  		h, p, err := swag.SplitHostPort(listener.Addr().String())
   395  		if err != nil {
   396  			return err
   397  		}
   398  		s.Host = h
   399  		s.Port = p
   400  		s.httpServerL = listener
   401  	}
   402  
   403  	if s.hasScheme(schemeHTTPS) {
   404  		tlsListener, err := net.Listen("tcp", net.JoinHostPort(s.TLSHost, strconv.Itoa(s.TLSPort)))
   405  		if err != nil {
   406  			return err
   407  		}
   408  
   409  		sh, sp, err := swag.SplitHostPort(tlsListener.Addr().String())
   410  		if err != nil {
   411  			return err
   412  		}
   413  		s.TLSHost = sh
   414  		s.TLSPort = sp
   415  		s.httpsServerL = tlsListener
   416  	}
   417  
   418  	s.hasListeners = true
   419  	return nil
   420  }
   421  
   422  // Shutdown server and clean up resources
   423  func (s *Server) Shutdown() error {
   424  	if atomic.CompareAndSwapInt32(&s.shuttingDown, 0, 1) {
   425  		close(s.shutdown)
   426  	}
   427  	return nil
   428  }
   429  
   430  func (s *Server) handleShutdown(wg *sync.WaitGroup, serversPtr *[]*http.Server) {
   431  	// wg.Done must occur last, after s.api.ServerShutdown()
   432  	// (to preserve old behaviour)
   433  	defer wg.Done()
   434  
   435  	<-s.shutdown
   436  
   437  	servers := *serversPtr
   438  
   439  	ctx, cancel := context.WithTimeout(context.TODO(), s.GracefulTimeout)
   440  	defer cancel()
   441  
   442  	// first execute the pre-shutdown hook
   443  	s.api.PreServerShutdown()
   444  
   445  	shutdownChan := make(chan bool)
   446  	for i := range servers {
   447  		server := servers[i]
   448  		go func() {
   449  			var success bool
   450  			defer func() {
   451  				shutdownChan <- success
   452  			}()
   453  			if err := server.Shutdown(ctx); err != nil {
   454  				// Error from closing listeners, or context timeout:
   455  				s.Logf("HTTP server Shutdown: %v", err)
   456  			} else {
   457  				success = true
   458  			}
   459  		}()
   460  	}
   461  
   462  	// Wait until all listeners have successfully shut down before calling ServerShutdown
   463  	success := true
   464  	for range servers {
   465  		success = success && <-shutdownChan
   466  	}
   467  	if success {
   468  		s.api.ServerShutdown()
   469  	}
   470  }
   471  
   472  // GetHandler returns a handler useful for testing
   473  func (s *Server) GetHandler() http.Handler {
   474  	return s.handler
   475  }
   476  
   477  // SetHandler allows for setting a http handler on this server
   478  func (s *Server) SetHandler(handler http.Handler) {
   479  	s.handler = handler
   480  }
   481  
   482  // UnixListener returns the domain socket listener
   483  func (s *Server) UnixListener() (net.Listener, error) {
   484  	if !s.hasListeners {
   485  		if err := s.Listen(); err != nil {
   486  			return nil, err
   487  		}
   488  	}
   489  	return s.domainSocketL, nil
   490  }
   491  
   492  // HTTPListener returns the http listener
   493  func (s *Server) HTTPListener() (net.Listener, error) {
   494  	if !s.hasListeners {
   495  		if err := s.Listen(); err != nil {
   496  			return nil, err
   497  		}
   498  	}
   499  	return s.httpServerL, nil
   500  }
   501  
   502  // TLSListener returns the https listener
   503  func (s *Server) TLSListener() (net.Listener, error) {
   504  	if !s.hasListeners {
   505  		if err := s.Listen(); err != nil {
   506  			return nil, err
   507  		}
   508  	}
   509  	return s.httpsServerL, nil
   510  }
   511  
   512  func handleInterrupt(once *sync.Once, s *Server) {
   513  	once.Do(func() {
   514  		for range s.interrupt {
   515  			if s.interrupted {
   516  				s.Logf("Server already shutting down")
   517  				continue
   518  			}
   519  			s.interrupted = true
   520  			s.Logf("Shutting down... ")
   521  			if err := s.Shutdown(); err != nil {
   522  				s.Logf("HTTP server Shutdown: %v", err)
   523  			}
   524  		}
   525  	})
   526  }
   527  
   528  func signalNotify(interrupt chan<- os.Signal) {
   529  	signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)
   530  }