github.com/cilium/cilium@v1.16.2/api/v1/health/server/server.go (about)

     1  // Code generated by go-swagger; DO NOT EDIT.
     2  
     3  // Copyright Authors of Cilium
     4  // SPDX-License-Identifier: Apache-2.0
     5  
     6  package server
     7  
     8  import (
     9  	"context"
    10  	"crypto/tls"
    11  	"crypto/x509"
    12  	"errors"
    13  	"fmt"
    14  	"log"
    15  	"net"
    16  	"net/http"
    17  	"os"
    18  	"strconv"
    19  	"sync"
    20  	"time"
    21  
    22  	"github.com/go-openapi/loads"
    23  	"github.com/go-openapi/runtime/middleware"
    24  	"github.com/go-openapi/swag"
    25  	"github.com/sirupsen/logrus"
    26  	"github.com/spf13/pflag"
    27  	"golang.org/x/net/netutil"
    28  
    29  	"github.com/cilium/cilium/api/v1/health/server/restapi"
    30  	"github.com/cilium/cilium/api/v1/health/server/restapi/connectivity"
    31  	"github.com/cilium/hive/cell"
    32  
    33  	"github.com/cilium/cilium/pkg/api"
    34  	"github.com/cilium/cilium/pkg/hive"
    35  )
    36  
    37  // Cell implements the cilium health API REST API server when provided
    38  // the required request handlers.
    39  var Cell = cell.Module(
    40  	"cilium-health-api-server",
    41  	"cilium health API server",
    42  
    43  	cell.Provide(newForCell),
    44  	APICell,
    45  )
    46  
    47  // APICell provides the restapi.CiliumHealthAPIAPI type, populated
    48  // with the request handlers. This cell is an alternative to 'Cell' when only
    49  // the API type is required and not the full server implementation.
    50  var APICell = cell.Provide(newAPI)
    51  
    52  type apiParams struct {
    53  	cell.In
    54  
    55  	Spec *Spec
    56  
    57  	Middleware middleware.Builder `name:"cilium-health-api-middleware" optional:"true"`
    58  
    59  	GetHealthzHandler                 restapi.GetHealthzHandler
    60  	ConnectivityGetStatusHandler      connectivity.GetStatusHandler
    61  	ConnectivityPutStatusProbeHandler connectivity.PutStatusProbeHandler
    62  }
    63  
    64  func newAPI(p apiParams) *restapi.CiliumHealthAPIAPI {
    65  	api := restapi.NewCiliumHealthAPIAPI(p.Spec.Document)
    66  
    67  	// Construct the API from the provided handlers
    68  
    69  	api.GetHealthzHandler = p.GetHealthzHandler
    70  	api.ConnectivityGetStatusHandler = p.ConnectivityGetStatusHandler
    71  	api.ConnectivityPutStatusProbeHandler = p.ConnectivityPutStatusProbeHandler
    72  
    73  	// Inject custom middleware if provided by Hive
    74  	if p.Middleware != nil {
    75  		api.Middleware = func(builder middleware.Builder) http.Handler {
    76  			return p.Middleware(api.Context().APIHandler(builder))
    77  		}
    78  	}
    79  
    80  	return api
    81  }
    82  
    83  type serverParams struct {
    84  	cell.In
    85  
    86  	Lifecycle  cell.Lifecycle
    87  	Shutdowner hive.Shutdowner
    88  	Logger     logrus.FieldLogger
    89  	Spec       *Spec
    90  	API        *restapi.CiliumHealthAPIAPI
    91  }
    92  
    93  func newForCell(p serverParams) (*Server, error) {
    94  	s := NewServer(p.API)
    95  	s.shutdowner = p.Shutdowner
    96  	s.logger = p.Logger
    97  	p.Lifecycle.Append(s)
    98  	return s, nil
    99  }
   100  
   101  const (
   102  	schemeHTTP  = "http"
   103  	schemeHTTPS = "https"
   104  	schemeUnix  = "unix"
   105  )
   106  
   107  var defaultSchemes []string
   108  
   109  func init() {
   110  	defaultSchemes = []string{
   111  		schemeUnix,
   112  	}
   113  }
   114  
   115  var (
   116  	enabledListeners []string
   117  	gracefulTimeout  time.Duration
   118  	maxHeaderSize    int
   119  
   120  	socketPath string
   121  
   122  	host         string
   123  	port         int
   124  	listenLimit  int
   125  	keepAlive    time.Duration
   126  	readTimeout  time.Duration
   127  	writeTimeout time.Duration
   128  
   129  	tlsHost           string
   130  	tlsPort           int
   131  	tlsListenLimit    int
   132  	tlsKeepAlive      time.Duration
   133  	tlsReadTimeout    time.Duration
   134  	tlsWriteTimeout   time.Duration
   135  	tlsCertificate    string
   136  	tlsCertificateKey string
   137  	tlsCACertificate  string
   138  )
   139  
   140  type ServerConfig struct {
   141  	EnableCiliumHealthAPIServerAccess []string
   142  }
   143  
   144  var (
   145  	defaultServerConfig = ServerConfig{
   146  		EnableCiliumHealthAPIServerAccess: []string{"*"},
   147  	}
   148  	AdminEnableFlag = "enable-cilium-health-api-server-access"
   149  )
   150  
   151  func (cfg ServerConfig) Flags(flags *pflag.FlagSet) {
   152  	flags.StringSlice(AdminEnableFlag, cfg.EnableCiliumHealthAPIServerAccess,
   153  		"List of cilium health API APIs which are administratively enabled. Supports '*'.")
   154  }
   155  
   156  var SpecCell = cell.Module(
   157  	"cilium-health-api-spec",
   158  	"cilium health API Specification",
   159  
   160  	cell.Config(defaultServerConfig),
   161  	cell.Provide(newSpec),
   162  )
   163  
   164  type Spec struct {
   165  	*loads.Document
   166  
   167  	// DeniedAPIs is a set of APIs that are administratively disabled.
   168  	DeniedAPIs api.PathSet
   169  }
   170  
   171  func newSpec(cfg ServerConfig) (*Spec, error) {
   172  	swaggerSpec, err := loads.Analyzed(SwaggerJSON, "")
   173  	if err != nil {
   174  		return nil, fmt.Errorf("failed to load swagger spec: %w", err)
   175  	}
   176  
   177  	deniedAPIs, err := api.AllowedFlagsToDeniedPaths(swaggerSpec, cfg.EnableCiliumHealthAPIServerAccess)
   178  	if err != nil {
   179  		return nil, fmt.Errorf("failed to parse %q flag: %w",
   180  			AdminEnableFlag, err)
   181  	}
   182  
   183  	return &Spec{
   184  		Document:   swaggerSpec,
   185  		DeniedAPIs: deniedAPIs,
   186  	}, nil
   187  }
   188  
   189  // NewServer creates a new api cilium health API server but does not configure it
   190  func NewServer(api *restapi.CiliumHealthAPIAPI) *Server {
   191  	s := new(Server)
   192  	s.api = api
   193  	return s
   194  }
   195  
   196  // ConfigureAPI configures the API and handlers.
   197  func (s *Server) ConfigureAPI() {
   198  	if s.api != nil {
   199  		s.handler = configureAPI(s.api)
   200  	}
   201  }
   202  
   203  // ConfigureFlags configures the additional flags defined by the handlers. Needs to be called before the parser.Parse
   204  func (s *Server) ConfigureFlags() {
   205  	if s.api != nil {
   206  		configureFlags(s.api)
   207  	}
   208  }
   209  
   210  // Server for the cilium health API API
   211  type Server struct {
   212  	EnabledListeners []string
   213  	CleanupTimeout   time.Duration
   214  	GracefulTimeout  time.Duration
   215  	MaxHeaderSize    int
   216  
   217  	SocketPath    string
   218  	domainSocketL *net.UnixListener
   219  
   220  	Host         string
   221  	Port         int
   222  	ListenLimit  int
   223  	KeepAlive    time.Duration
   224  	ReadTimeout  time.Duration
   225  	WriteTimeout time.Duration
   226  	httpServerL  net.Listener
   227  
   228  	TLSHost           string
   229  	TLSPort           int
   230  	TLSCertificate    string
   231  	TLSCertificateKey string
   232  	TLSCACertificate  string
   233  	TLSListenLimit    int
   234  	TLSKeepAlive      time.Duration
   235  	TLSReadTimeout    time.Duration
   236  	TLSWriteTimeout   time.Duration
   237  	httpsServerL      net.Listener
   238  
   239  	api          *restapi.CiliumHealthAPIAPI
   240  	handler      http.Handler
   241  	hasListeners bool
   242  	servers      []*http.Server
   243  
   244  	wg         sync.WaitGroup
   245  	shutdowner hive.Shutdowner
   246  	logger     logrus.FieldLogger
   247  }
   248  
   249  // Logf logs message either via defined user logger or via system one if no user logger is defined.
   250  func (s *Server) Logf(f string, args ...interface{}) {
   251  	if s.logger != nil {
   252  		s.logger.Infof(f, args...)
   253  	} else if s.api != nil && s.api.Logger != nil {
   254  		s.api.Logger(f, args...)
   255  	} else {
   256  		log.Printf(f, args...)
   257  	}
   258  }
   259  
   260  // Fatalf logs message either via defined user logger or via system one if no user logger is defined.
   261  // Exits with non-zero status after printing
   262  func (s *Server) Fatalf(f string, args ...interface{}) {
   263  	if s.shutdowner != nil {
   264  		s.shutdowner.Shutdown(hive.ShutdownWithError(fmt.Errorf(f, args...)))
   265  	} else if s.api != nil && s.api.Logger != nil {
   266  		s.api.Logger(f, args...)
   267  		os.Exit(1)
   268  	} else {
   269  		log.Fatalf(f, args...)
   270  	}
   271  }
   272  
   273  // SetAPI configures the server with the specified API. Needs to be called before Serve
   274  func (s *Server) SetAPI(api *restapi.CiliumHealthAPIAPI) {
   275  	if api == nil {
   276  		s.api = nil
   277  		s.handler = nil
   278  		return
   279  	}
   280  
   281  	s.api = api
   282  	s.handler = configureAPI(api)
   283  }
   284  
   285  // GetAPI returns the configured API. Modifications on the API must be performed
   286  // before server is started.
   287  func (s *Server) GetAPI() *restapi.CiliumHealthAPIAPI {
   288  	return s.api
   289  }
   290  
   291  func (s *Server) hasScheme(scheme string) bool {
   292  	schemes := s.EnabledListeners
   293  	if len(schemes) == 0 {
   294  		schemes = defaultSchemes
   295  	}
   296  
   297  	for _, v := range schemes {
   298  		if v == scheme {
   299  			return true
   300  		}
   301  	}
   302  	return false
   303  }
   304  
   305  func (s *Server) Serve() error {
   306  	// TODO remove when this is not needed for compatibility anymore
   307  	if err := s.Start(context.TODO()); err != nil {
   308  		return err
   309  	}
   310  	s.wg.Wait()
   311  	return nil
   312  }
   313  
   314  // Start the server
   315  func (s *Server) Start(cell.HookContext) (err error) {
   316  	if !s.hasListeners {
   317  		if err = s.Listen(); err != nil {
   318  			return err
   319  		}
   320  	}
   321  
   322  	if len(s.servers) != 0 {
   323  		return errors.New("already started")
   324  	}
   325  
   326  	// set default handler, if none is set
   327  	if s.handler == nil {
   328  		if s.api == nil {
   329  			return errors.New("can't create the default handler, as no api is set")
   330  		}
   331  
   332  		s.ConfigureAPI()
   333  		s.SetHandler(s.api.Serve(nil))
   334  	}
   335  
   336  	if s.hasScheme(schemeUnix) {
   337  		domainSocket := new(http.Server)
   338  		domainSocket.MaxHeaderBytes = s.MaxHeaderSize
   339  		domainSocket.Handler = s.handler
   340  		if int64(s.CleanupTimeout) > 0 {
   341  			domainSocket.IdleTimeout = s.CleanupTimeout
   342  		}
   343  
   344  		configureServer(domainSocket, "unix", s.SocketPath)
   345  
   346  		if os.Getuid() == 0 {
   347  			err := api.SetDefaultPermissions(s.SocketPath)
   348  			if err != nil {
   349  				return err
   350  			}
   351  		}
   352  		s.servers = append(s.servers, domainSocket)
   353  		s.wg.Add(1)
   354  		s.Logf("Serving cilium health API at unix://%s", s.SocketPath)
   355  		go func(l net.Listener) {
   356  			defer s.wg.Done()
   357  			if err := domainSocket.Serve(l); err != nil && err != http.ErrServerClosed {
   358  				s.Fatalf("%v", err)
   359  			}
   360  			s.Logf("Stopped serving cilium health API at unix://%s", s.SocketPath)
   361  		}(s.domainSocketL)
   362  	}
   363  
   364  	if s.hasScheme(schemeHTTP) {
   365  		httpServer := new(http.Server)
   366  		httpServer.MaxHeaderBytes = s.MaxHeaderSize
   367  		httpServer.ReadTimeout = s.ReadTimeout
   368  		httpServer.WriteTimeout = s.WriteTimeout
   369  		httpServer.SetKeepAlivesEnabled(int64(s.KeepAlive) > 0)
   370  		if s.ListenLimit > 0 {
   371  			s.httpServerL = netutil.LimitListener(s.httpServerL, s.ListenLimit)
   372  		}
   373  
   374  		if int64(s.CleanupTimeout) > 0 {
   375  			httpServer.IdleTimeout = s.CleanupTimeout
   376  		}
   377  
   378  		httpServer.Handler = s.handler
   379  
   380  		configureServer(httpServer, "http", s.httpServerL.Addr().String())
   381  
   382  		s.servers = append(s.servers, httpServer)
   383  		s.wg.Add(1)
   384  		s.Logf("Serving cilium health API at http://%s", s.httpServerL.Addr())
   385  		go func(l net.Listener) {
   386  			defer s.wg.Done()
   387  			if err := httpServer.Serve(l); err != nil && err != http.ErrServerClosed {
   388  				s.Fatalf("%v", err)
   389  			}
   390  			s.Logf("Stopped serving cilium health API at http://%s", l.Addr())
   391  		}(s.httpServerL)
   392  	}
   393  
   394  	if s.hasScheme(schemeHTTPS) {
   395  		httpsServer := new(http.Server)
   396  		httpsServer.MaxHeaderBytes = s.MaxHeaderSize
   397  		httpsServer.ReadTimeout = s.TLSReadTimeout
   398  		httpsServer.WriteTimeout = s.TLSWriteTimeout
   399  		httpsServer.SetKeepAlivesEnabled(int64(s.TLSKeepAlive) > 0)
   400  		if s.TLSListenLimit > 0 {
   401  			s.httpsServerL = netutil.LimitListener(s.httpsServerL, s.TLSListenLimit)
   402  		}
   403  		if int64(s.CleanupTimeout) > 0 {
   404  			httpsServer.IdleTimeout = s.CleanupTimeout
   405  		}
   406  		httpsServer.Handler = s.handler
   407  
   408  		// Inspired by https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go
   409  		httpsServer.TLSConfig = &tls.Config{
   410  			// Causes servers to use Go's default ciphersuite preferences,
   411  			// which are tuned to avoid attacks. Does nothing on clients.
   412  			PreferServerCipherSuites: true,
   413  			// Only use curves which have assembly implementations
   414  			// https://github.com/golang/go/tree/master/src/crypto/elliptic
   415  			CurvePreferences: []tls.CurveID{tls.CurveP256},
   416  			// Use modern tls mode https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
   417  			NextProtos: []string{"h2", "http/1.1"},
   418  			// https://www.owasp.org/index.php/Transport_Layer_Protection_Cheat_Sheet#Rule_-_Only_Support_Strong_Protocols
   419  			MinVersion: tls.VersionTLS12,
   420  			// These ciphersuites support Forward Secrecy: https://en.wikipedia.org/wiki/Forward_secrecy
   421  			CipherSuites: []uint16{
   422  				tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
   423  				tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
   424  				tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
   425  				tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
   426  				tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
   427  				tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
   428  			},
   429  		}
   430  
   431  		// build standard config from server options
   432  		if s.TLSCertificate != "" && s.TLSCertificateKey != "" {
   433  			httpsServer.TLSConfig.Certificates = make([]tls.Certificate, 1)
   434  			httpsServer.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(s.TLSCertificate, s.TLSCertificateKey)
   435  			if err != nil {
   436  				return err
   437  			}
   438  		}
   439  
   440  		if s.TLSCACertificate != "" {
   441  			// include specified CA certificate
   442  			caCert, caCertErr := os.ReadFile(s.TLSCACertificate)
   443  			if caCertErr != nil {
   444  				return caCertErr
   445  			}
   446  			caCertPool := x509.NewCertPool()
   447  			ok := caCertPool.AppendCertsFromPEM(caCert)
   448  			if !ok {
   449  				return fmt.Errorf("cannot parse CA certificate")
   450  			}
   451  			httpsServer.TLSConfig.ClientCAs = caCertPool
   452  			httpsServer.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
   453  		}
   454  
   455  		// call custom TLS configurator
   456  		configureTLS(httpsServer.TLSConfig)
   457  
   458  		if len(httpsServer.TLSConfig.Certificates) == 0 && httpsServer.TLSConfig.GetCertificate == nil {
   459  			// after standard and custom config are passed, this ends up with no certificate
   460  			if s.TLSCertificate == "" {
   461  				if s.TLSCertificateKey == "" {
   462  					s.Fatalf("the required flags `--tls-certificate` and `--tls-key` were not specified")
   463  				}
   464  				s.Fatalf("the required flag `--tls-certificate` was not specified")
   465  			}
   466  			if s.TLSCertificateKey == "" {
   467  				s.Fatalf("the required flag `--tls-key` was not specified")
   468  			}
   469  			// this happens with a wrong custom TLS configurator
   470  			s.Fatalf("no certificate was configured for TLS")
   471  		}
   472  
   473  		// must have at least one certificate or panics
   474  		httpsServer.TLSConfig.BuildNameToCertificate()
   475  
   476  		configureServer(httpsServer, "https", s.httpsServerL.Addr().String())
   477  
   478  		s.servers = append(s.servers, httpsServer)
   479  		s.wg.Add(1)
   480  		s.Logf("Serving cilium health API at https://%s", s.httpsServerL.Addr())
   481  		go func(l net.Listener) {
   482  			defer s.wg.Done()
   483  			if err := httpsServer.Serve(l); err != nil && err != http.ErrServerClosed {
   484  				s.Fatalf("%v", err)
   485  			}
   486  			s.Logf("Stopped serving cilium health API at https://%s", l.Addr())
   487  		}(tls.NewListener(s.httpsServerL, httpsServer.TLSConfig))
   488  	}
   489  
   490  	return nil
   491  }
   492  
   493  // Listen creates the listeners for the server
   494  func (s *Server) Listen() error {
   495  	if s.hasListeners { // already done this
   496  		return nil
   497  	}
   498  
   499  	if s.hasScheme(schemeHTTPS) {
   500  		// Use http host if https host wasn't defined
   501  		if s.TLSHost == "" {
   502  			s.TLSHost = s.Host
   503  		}
   504  		// Use http listen limit if https listen limit wasn't defined
   505  		if s.TLSListenLimit == 0 {
   506  			s.TLSListenLimit = s.ListenLimit
   507  		}
   508  		// Use http tcp keep alive if https tcp keep alive wasn't defined
   509  		if int64(s.TLSKeepAlive) == 0 {
   510  			s.TLSKeepAlive = s.KeepAlive
   511  		}
   512  		// Use http read timeout if https read timeout wasn't defined
   513  		if int64(s.TLSReadTimeout) == 0 {
   514  			s.TLSReadTimeout = s.ReadTimeout
   515  		}
   516  		// Use http write timeout if https write timeout wasn't defined
   517  		if int64(s.TLSWriteTimeout) == 0 {
   518  			s.TLSWriteTimeout = s.WriteTimeout
   519  		}
   520  	}
   521  
   522  	if s.hasScheme(schemeUnix) {
   523  		addr, err := net.ResolveUnixAddr("unix", s.SocketPath)
   524  		if err != nil {
   525  			return err
   526  		}
   527  		domSockListener, err := net.ListenUnix("unix", addr)
   528  		if err != nil {
   529  			return err
   530  		}
   531  		s.domainSocketL = domSockListener
   532  	}
   533  
   534  	if s.hasScheme(schemeHTTP) {
   535  		listener, err := net.Listen("tcp", net.JoinHostPort(s.Host, strconv.Itoa(s.Port)))
   536  		if err != nil {
   537  			return err
   538  		}
   539  
   540  		h, p, err := swag.SplitHostPort(listener.Addr().String())
   541  		if err != nil {
   542  			return err
   543  		}
   544  		s.Host = h
   545  		s.Port = p
   546  		s.httpServerL = listener
   547  	}
   548  
   549  	if s.hasScheme(schemeHTTPS) {
   550  		tlsListener, err := net.Listen("tcp", net.JoinHostPort(s.TLSHost, strconv.Itoa(s.TLSPort)))
   551  		if err != nil {
   552  			return err
   553  		}
   554  
   555  		sh, sp, err := swag.SplitHostPort(tlsListener.Addr().String())
   556  		if err != nil {
   557  			return err
   558  		}
   559  		s.TLSHost = sh
   560  		s.TLSPort = sp
   561  		s.httpsServerL = tlsListener
   562  	}
   563  
   564  	s.hasListeners = true
   565  	return nil
   566  }
   567  
   568  // Shutdown server and clean up resources
   569  func (s *Server) Shutdown() error {
   570  	ctx, cancel := context.WithTimeout(context.TODO(), s.GracefulTimeout)
   571  	defer cancel()
   572  	return s.Stop(ctx)
   573  }
   574  
   575  func (s *Server) Stop(ctx cell.HookContext) error {
   576  	// first execute the pre-shutdown hook
   577  	s.api.PreServerShutdown()
   578  
   579  	shutdownChan := make(chan bool)
   580  	for i := range s.servers {
   581  		server := s.servers[i]
   582  		go func() {
   583  			var success bool
   584  			defer func() {
   585  				shutdownChan <- success
   586  			}()
   587  			if err := server.Shutdown(ctx); err != nil {
   588  				s.Logf("HTTP server Shutdown: %v", err)
   589  
   590  				// Forcefully close open connections.
   591  				server.Close()
   592  			} else {
   593  				success = true
   594  			}
   595  		}()
   596  	}
   597  
   598  	// Wait until all listeners have successfully shut down before calling ServerShutdown
   599  	success := true
   600  	for range s.servers {
   601  		success = success && <-shutdownChan
   602  	}
   603  	if success {
   604  		s.api.ServerShutdown()
   605  	}
   606  
   607  	s.wg.Wait()
   608  	s.servers = nil
   609  
   610  	return nil
   611  }
   612  
   613  // GetHandler returns a handler useful for testing
   614  func (s *Server) GetHandler() http.Handler {
   615  	return s.handler
   616  }
   617  
   618  // SetHandler allows for setting a http handler on this server
   619  func (s *Server) SetHandler(handler http.Handler) {
   620  	s.handler = handler
   621  }
   622  
   623  // UnixListener returns the domain socket listener
   624  func (s *Server) UnixListener() (*net.UnixListener, error) {
   625  	if !s.hasListeners {
   626  		if err := s.Listen(); err != nil {
   627  			return nil, err
   628  		}
   629  	}
   630  	return s.domainSocketL, nil
   631  }
   632  
   633  // HTTPListener returns the http listener
   634  func (s *Server) HTTPListener() (net.Listener, error) {
   635  	if !s.hasListeners {
   636  		if err := s.Listen(); err != nil {
   637  			return nil, err
   638  		}
   639  	}
   640  	return s.httpServerL, nil
   641  }
   642  
   643  // TLSListener returns the https listener
   644  func (s *Server) TLSListener() (net.Listener, error) {
   645  	if !s.hasListeners {
   646  		if err := s.Listen(); err != nil {
   647  			return nil, err
   648  		}
   649  	}
   650  	return s.httpsServerL, nil
   651  }