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

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