github.com/Bio-core/jtree@v0.0.0-20190705165106-1d7a7e7d6272/restapi/server.go (about)

     1  package restapi
     2  
     3  import (
     4  	"crypto/tls"
     5  	"crypto/x509"
     6  	"errors"
     7  	"io/ioutil"
     8  	"log"
     9  	"net"
    10  	"net/http"
    11  	"os"
    12  	"strconv"
    13  	"sync"
    14  	"sync/atomic"
    15  	"time"
    16  
    17  	"github.com/go-openapi/runtime/flagext"
    18  	"github.com/go-openapi/swag"
    19  	flags "github.com/jessevdk/go-flags"
    20  	graceful "github.com/tylerb/graceful"
    21  
    22  	"github.com/Bio-core/jtree/restapi/operations"
    23  )
    24  
    25  const (
    26  	schemeHTTP  = "http"
    27  	schemeHTTPS = "https"
    28  	schemeUnix  = "unix"
    29  )
    30  
    31  var defaultSchemes []string
    32  
    33  func init() {
    34  	defaultSchemes = []string{
    35  		schemeHTTP,
    36  	}
    37  }
    38  
    39  // NewServer creates a new api jtree metadata server but does not configure it
    40  func NewServer(api *operations.JtreeMetadataAPI) *Server {
    41  	s := new(Server)
    42  
    43  	s.shutdown = make(chan struct{})
    44  	s.api = api
    45  	return s
    46  }
    47  
    48  // ConfigureAPI configures the API and handlers.
    49  func (s *Server) ConfigureAPI() {
    50  	if s.api != nil {
    51  		s.handler = configureAPI(s.api)
    52  	}
    53  	if s.Port == 0 {
    54  		s.Port = c.App.Port
    55  	}
    56  	s.Host = ""
    57  	// c.App.Host
    58  
    59  }
    60  
    61  // ConfigureFlags configures the additional flags defined by the handlers. Needs to be called before the parser.Parse
    62  func (s *Server) ConfigureFlags() {
    63  	if s.api != nil {
    64  		configureFlags(s.api)
    65  	}
    66  }
    67  
    68  // Server for the jtree metadata API
    69  type Server struct {
    70  	EnabledListeners []string         `long:"scheme" description:"the listeners to enable, this can be repeated and defaults to the schemes in the swagger spec"`
    71  	CleanupTimeout   time.Duration    `long:"cleanup-timeout" description:"grace period for which to wait before shutting down the server" default:"10s"`
    72  	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"`
    73  
    74  	SocketPath    flags.Filename `long:"socket-path" description:"the unix socket to listen on" default:"/var/run/jtree-metadata.sock"`
    75  	domainSocketL net.Listener
    76  
    77  	Host         string        `long:"host" description:"the IP to listen on" default:"localhost" env:"HOST"`
    78  	Port         int           `long:"port" description:"the port to listen on for insecure connections, defaults to a random value" env:"PORT"`
    79  	ListenLimit  int           `long:"listen-limit" description:"limit the number of outstanding requests"`
    80  	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"`
    81  	ReadTimeout  time.Duration `long:"read-timeout" description:"maximum duration before timing out read of the request" default:"30s"`
    82  	WriteTimeout time.Duration `long:"write-timeout" description:"maximum duration before timing out write of the response" default:"60s"`
    83  	httpServerL  net.Listener
    84  
    85  	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"`
    86  	TLSPort           int            `long:"tls-port" description:"the port to listen on for secure connections, defaults to a random value" env:"TLS_PORT"`
    87  	TLSCertificate    flags.Filename `long:"tls-certificate" description:"the certificate to use for secure connections" env:"TLS_CERTIFICATE"`
    88  	TLSCertificateKey flags.Filename `long:"tls-key" description:"the private key to use for secure conections" env:"TLS_PRIVATE_KEY"`
    89  	TLSCACertificate  flags.Filename `long:"tls-ca" description:"the certificate authority file to be used with mutual tls auth" env:"TLS_CA_CERTIFICATE"`
    90  	TLSListenLimit    int            `long:"tls-listen-limit" description:"limit the number of outstanding requests"`
    91  	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)"`
    92  	TLSReadTimeout    time.Duration  `long:"tls-read-timeout" description:"maximum duration before timing out read of the request"`
    93  	TLSWriteTimeout   time.Duration  `long:"tls-write-timeout" description:"maximum duration before timing out write of the response"`
    94  	httpsServerL      net.Listener
    95  
    96  	api          *operations.JtreeMetadataAPI
    97  	handler      http.Handler
    98  	hasListeners bool
    99  	shutdown     chan struct{}
   100  	shuttingDown int32
   101  }
   102  
   103  // Logf logs message either via defined user logger or via system one if no user logger is defined.
   104  func (s *Server) Logf(f string, args ...interface{}) {
   105  	if s.api != nil && s.api.Logger != nil {
   106  		s.api.Logger(f, args...)
   107  	} else {
   108  		log.Printf(f, args...)
   109  	}
   110  }
   111  
   112  // Fatalf logs message either via defined user logger or via system one if no user logger is defined.
   113  // Exits with non-zero status after printing
   114  func (s *Server) Fatalf(f string, args ...interface{}) {
   115  	if s.api != nil && s.api.Logger != nil {
   116  		s.api.Logger(f, args...)
   117  		os.Exit(1)
   118  	} else {
   119  		log.Fatalf(f, args...)
   120  	}
   121  }
   122  
   123  // SetAPI configures the server with the specified API. Needs to be called before Serve
   124  func (s *Server) SetAPI(api *operations.JtreeMetadataAPI) {
   125  	if api == nil {
   126  		s.api = nil
   127  		s.handler = nil
   128  		return
   129  	}
   130  
   131  	s.api = api
   132  	s.api.Logger = log.Printf
   133  	s.handler = configureAPI(api)
   134  }
   135  
   136  func (s *Server) hasScheme(scheme string) bool {
   137  	schemes := s.EnabledListeners
   138  	if len(schemes) == 0 {
   139  		schemes = defaultSchemes
   140  	}
   141  
   142  	for _, v := range schemes {
   143  		if v == scheme {
   144  			return true
   145  		}
   146  	}
   147  	return false
   148  }
   149  
   150  // Serve the api
   151  func (s *Server) Serve() (err error) {
   152  	if !s.hasListeners {
   153  		if err = s.Listen(); err != nil {
   154  			return err
   155  		}
   156  	}
   157  
   158  	// set default handler, if none is set
   159  	if s.handler == nil {
   160  		if s.api == nil {
   161  			return errors.New("can't create the default handler, as no api is set")
   162  		}
   163  
   164  		s.SetHandler(s.api.Serve(nil))
   165  	}
   166  
   167  	var wg sync.WaitGroup
   168  
   169  	if s.hasScheme(schemeUnix) {
   170  		domainSocket := &graceful.Server{Server: new(http.Server)}
   171  		domainSocket.MaxHeaderBytes = int(s.MaxHeaderSize)
   172  		domainSocket.Handler = s.handler
   173  		domainSocket.LogFunc = s.Logf
   174  		if int64(s.CleanupTimeout) > 0 {
   175  			domainSocket.Timeout = s.CleanupTimeout
   176  		}
   177  
   178  		configureServer(domainSocket, "unix", string(s.SocketPath))
   179  
   180  		wg.Add(2)
   181  		s.Logf("Serving jtree metadata at unix://%s", s.SocketPath)
   182  		go func(l net.Listener) {
   183  			defer wg.Done()
   184  			if err := domainSocket.Serve(l); err != nil {
   185  				s.Fatalf("%v", err)
   186  			}
   187  			s.Logf("Stopped serving jtree metadata at unix://%s", s.SocketPath)
   188  		}(s.domainSocketL)
   189  		go s.handleShutdown(&wg, domainSocket)
   190  	}
   191  
   192  	if s.hasScheme(schemeHTTP) {
   193  		httpServer := &graceful.Server{Server: new(http.Server)}
   194  		httpServer.MaxHeaderBytes = int(s.MaxHeaderSize)
   195  		httpServer.ReadTimeout = s.ReadTimeout
   196  		httpServer.WriteTimeout = s.WriteTimeout
   197  		httpServer.SetKeepAlivesEnabled(int64(s.KeepAlive) > 0)
   198  		httpServer.TCPKeepAlive = s.KeepAlive
   199  		if s.ListenLimit > 0 {
   200  			httpServer.ListenLimit = s.ListenLimit
   201  		}
   202  
   203  		if int64(s.CleanupTimeout) > 0 {
   204  			httpServer.Timeout = s.CleanupTimeout
   205  		}
   206  
   207  		httpServer.Handler = s.handler
   208  		httpServer.LogFunc = s.Logf
   209  
   210  		configureServer(httpServer, "http", s.httpServerL.Addr().String())
   211  
   212  		wg.Add(2)
   213  		s.Logf("Serving jtree metadata at http://%s", s.httpServerL.Addr())
   214  		go func(l net.Listener) {
   215  			defer wg.Done()
   216  			if err := httpServer.Serve(l); err != nil {
   217  				s.Fatalf("%v", err)
   218  			}
   219  			s.Logf("Stopped serving jtree metadata at http://%s", l.Addr())
   220  		}(s.httpServerL)
   221  		go s.handleShutdown(&wg, httpServer)
   222  	}
   223  
   224  	if s.hasScheme(schemeHTTPS) {
   225  		httpsServer := &graceful.Server{Server: new(http.Server)}
   226  		httpsServer.MaxHeaderBytes = int(s.MaxHeaderSize)
   227  		httpsServer.ReadTimeout = s.TLSReadTimeout
   228  		httpsServer.WriteTimeout = s.TLSWriteTimeout
   229  		httpsServer.SetKeepAlivesEnabled(int64(s.TLSKeepAlive) > 0)
   230  		httpsServer.TCPKeepAlive = s.TLSKeepAlive
   231  		if s.TLSListenLimit > 0 {
   232  			httpsServer.ListenLimit = s.TLSListenLimit
   233  		}
   234  		if int64(s.CleanupTimeout) > 0 {
   235  			httpsServer.Timeout = s.CleanupTimeout
   236  		}
   237  		httpsServer.Handler = s.handler
   238  		httpsServer.LogFunc = s.Logf
   239  
   240  		// Inspired by https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go
   241  		httpsServer.TLSConfig = &tls.Config{
   242  			// Causes servers to use Go's default ciphersuite preferences,
   243  			// which are tuned to avoid attacks. Does nothing on clients.
   244  			PreferServerCipherSuites: true,
   245  			// Only use curves which have assembly implementations
   246  			// https://github.com/golang/go/tree/master/src/crypto/elliptic
   247  			CurvePreferences: []tls.CurveID{tls.CurveP256},
   248  			// Use modern tls mode https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
   249  			NextProtos: []string{"http/1.1", "h2"},
   250  			// https://www.owasp.org/index.php/Transport_Layer_Protection_Cheat_Sheet#Rule_-_Only_Support_Strong_Protocols
   251  			MinVersion: tls.VersionTLS12,
   252  			// These ciphersuites support Forward Secrecy: https://en.wikipedia.org/wiki/Forward_secrecy
   253  			CipherSuites: []uint16{
   254  				tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
   255  				tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
   256  				tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
   257  				tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
   258  			},
   259  		}
   260  
   261  		if s.TLSCertificate != "" && s.TLSCertificateKey != "" {
   262  			httpsServer.TLSConfig.Certificates = make([]tls.Certificate, 1)
   263  			httpsServer.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(string(s.TLSCertificate), string(s.TLSCertificateKey))
   264  		}
   265  
   266  		if s.TLSCACertificate != "" {
   267  			caCert, caCertErr := ioutil.ReadFile(string(s.TLSCACertificate))
   268  			if caCertErr != nil {
   269  				log.Fatal(caCertErr)
   270  			}
   271  			caCertPool := x509.NewCertPool()
   272  			caCertPool.AppendCertsFromPEM(caCert)
   273  			httpsServer.TLSConfig.ClientCAs = caCertPool
   274  			httpsServer.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
   275  		}
   276  
   277  		configureTLS(httpsServer.TLSConfig)
   278  		httpsServer.TLSConfig.BuildNameToCertificate()
   279  
   280  		if err != nil {
   281  			return err
   282  		}
   283  
   284  		if len(httpsServer.TLSConfig.Certificates) == 0 {
   285  			if s.TLSCertificate == "" {
   286  				if s.TLSCertificateKey == "" {
   287  					s.Fatalf("the required flags `--tls-certificate` and `--tls-key` were not specified")
   288  				}
   289  				s.Fatalf("the required flag `--tls-certificate` was not specified")
   290  			}
   291  			if s.TLSCertificateKey == "" {
   292  				s.Fatalf("the required flag `--tls-key` was not specified")
   293  			}
   294  		}
   295  
   296  		configureServer(httpsServer, "https", s.httpsServerL.Addr().String())
   297  
   298  		wg.Add(2)
   299  		s.Logf("Serving jtree metadata at https://%s", s.httpsServerL.Addr())
   300  		go func(l net.Listener) {
   301  			defer wg.Done()
   302  			if err := httpsServer.Serve(l); err != nil {
   303  				s.Fatalf("%v", err)
   304  			}
   305  			s.Logf("Stopped serving jtree metadata at https://%s", l.Addr())
   306  		}(tls.NewListener(s.httpsServerL, httpsServer.TLSConfig))
   307  		go s.handleShutdown(&wg, httpsServer)
   308  	}
   309  
   310  	wg.Wait()
   311  	return nil
   312  }
   313  
   314  // Listen creates the listeners for the server
   315  func (s *Server) Listen() error {
   316  	if s.hasListeners { // already done this
   317  		return nil
   318  	}
   319  
   320  	if s.hasScheme(schemeHTTPS) {
   321  		// Use http host if https host wasn't defined
   322  		if s.TLSHost == "" {
   323  			s.TLSHost = s.Host
   324  		}
   325  		// Use http listen limit if https listen limit wasn't defined
   326  		if s.TLSListenLimit == 0 {
   327  			s.TLSListenLimit = s.ListenLimit
   328  		}
   329  		// Use http tcp keep alive if https tcp keep alive wasn't defined
   330  		if int64(s.TLSKeepAlive) == 0 {
   331  			s.TLSKeepAlive = s.KeepAlive
   332  		}
   333  		// Use http read timeout if https read timeout wasn't defined
   334  		if int64(s.TLSReadTimeout) == 0 {
   335  			s.TLSReadTimeout = s.ReadTimeout
   336  		}
   337  		// Use http write timeout if https write timeout wasn't defined
   338  		if int64(s.TLSWriteTimeout) == 0 {
   339  			s.TLSWriteTimeout = s.WriteTimeout
   340  		}
   341  	}
   342  
   343  	if s.hasScheme(schemeUnix) {
   344  		domSockListener, err := net.Listen("unix", string(s.SocketPath))
   345  		if err != nil {
   346  			return err
   347  		}
   348  		s.domainSocketL = domSockListener
   349  	}
   350  
   351  	if s.hasScheme(schemeHTTP) {
   352  		listener, err := net.Listen("tcp", net.JoinHostPort(s.Host, strconv.Itoa(s.Port)))
   353  		if err != nil {
   354  			return err
   355  		}
   356  
   357  		h, p, err := swag.SplitHostPort(listener.Addr().String())
   358  		if err != nil {
   359  			return err
   360  		}
   361  		s.Host = h
   362  		s.Port = p
   363  		s.httpServerL = listener
   364  	}
   365  
   366  	if s.hasScheme(schemeHTTPS) {
   367  		tlsListener, err := net.Listen("tcp", net.JoinHostPort(s.TLSHost, strconv.Itoa(s.TLSPort)))
   368  		if err != nil {
   369  			return err
   370  		}
   371  
   372  		sh, sp, err := swag.SplitHostPort(tlsListener.Addr().String())
   373  		if err != nil {
   374  			return err
   375  		}
   376  		s.TLSHost = sh
   377  		s.TLSPort = sp
   378  		s.httpsServerL = tlsListener
   379  	}
   380  
   381  	s.hasListeners = true
   382  	return nil
   383  }
   384  
   385  // Shutdown server and clean up resources
   386  func (s *Server) Shutdown() error {
   387  	if atomic.LoadInt32(&s.shuttingDown) != 0 {
   388  		s.Logf("already shutting down")
   389  		return nil
   390  	}
   391  	s.shutdown <- struct{}{}
   392  	return nil
   393  }
   394  
   395  func (s *Server) handleShutdown(wg *sync.WaitGroup, server *graceful.Server) {
   396  	defer wg.Done()
   397  	for {
   398  		select {
   399  		case <-s.shutdown:
   400  			atomic.AddInt32(&s.shuttingDown, 1)
   401  			server.Stop(s.CleanupTimeout)
   402  			<-server.StopChan()
   403  			s.api.ServerShutdown()
   404  			return
   405  		case <-server.StopChan():
   406  			atomic.AddInt32(&s.shuttingDown, 1)
   407  			s.api.ServerShutdown()
   408  			return
   409  		}
   410  	}
   411  }
   412  
   413  // GetHandler returns a handler useful for testing
   414  func (s *Server) GetHandler() http.Handler {
   415  	return s.handler
   416  }
   417  
   418  // SetHandler allows for setting a http handler on this server
   419  func (s *Server) SetHandler(handler http.Handler) {
   420  	s.handler = handler
   421  }
   422  
   423  // UnixListener returns the domain socket listener
   424  func (s *Server) UnixListener() (net.Listener, error) {
   425  	if !s.hasListeners {
   426  		if err := s.Listen(); err != nil {
   427  			return nil, err
   428  		}
   429  	}
   430  	return s.domainSocketL, nil
   431  }
   432  
   433  // HTTPListener returns the http listener
   434  func (s *Server) HTTPListener() (net.Listener, error) {
   435  	if !s.hasListeners {
   436  		if err := s.Listen(); err != nil {
   437  			return nil, err
   438  		}
   439  	}
   440  	return s.httpServerL, nil
   441  }
   442  
   443  // TLSListener returns the https listener
   444  func (s *Server) TLSListener() (net.Listener, error) {
   445  	if !s.hasListeners {
   446  		if err := s.Listen(); err != nil {
   447  			return nil, err
   448  		}
   449  	}
   450  	return s.httpsServerL, nil
   451  }