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 }