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 }