From 12c7d5d1019723726653f48630078d9697b704b0 Mon Sep 17 00:00:00 2001 From: Michal Pristas Date: Thu, 11 Nov 2021 12:30:57 +0100 Subject: [PATCH] Allow multiple endpoints where server is exposed at --- cmd/fleet/server.go | 118 ++++++++++++++++++----------------- internal/pkg/config/input.go | 38 +++++++++-- main.go | 2 +- 3 files changed, 95 insertions(+), 63 deletions(-) diff --git a/cmd/fleet/server.go b/cmd/fleet/server.go index 98ad61630..305ed87db 100644 --- a/cmd/fleet/server.go +++ b/cmd/fleet/server.go @@ -40,8 +40,8 @@ func diagConn(c net.Conn, s http.ConnState) { } func runServer(ctx context.Context, router *httprouter.Router, cfg *config.Server) error { + listeners := cfg.BindEndpoints() - addr := cfg.BindAddress() rdto := cfg.Timeouts.Read wrto := cfg.Timeouts.Write idle := cfg.Timeouts.Idle @@ -49,75 +49,77 @@ func runServer(ctx context.Context, router *httprouter.Router, cfg *config.Serve mhbz := cfg.Limits.MaxHeaderByteSize bctx := func(net.Listener) context.Context { return ctx } - log.Info(). - Str("bind", addr). - Dur("rdTimeout", rdto). - Dur("wrTimeout", wrto). - Msg("server listening") - - server := http.Server{ - Addr: addr, - ReadTimeout: rdto, - WriteTimeout: wrto, - IdleTimeout: idle, - ReadHeaderTimeout: rdhr, - Handler: router, - BaseContext: bctx, - ConnState: diagConn, - MaxHeaderBytes: mhbz, - ErrorLog: errLogger(), - } - - forceCh := make(chan struct{}) - defer close(forceCh) - - // handler to close server - go func() { - select { - case <-ctx.Done(): - log.Debug().Msg("force server close on ctx.Done()") - server.Close() - case <-forceCh: - log.Debug().Msg("go routine forced closed on exit") + for _, addr := range listeners { + log.Info(). + Str("bind", addr). + Dur("rdTimeout", rdto). + Dur("wrTimeout", wrto). + Msg("server listening") + + server := http.Server{ + Addr: addr, + ReadTimeout: rdto, + WriteTimeout: wrto, + IdleTimeout: idle, + ReadHeaderTimeout: rdhr, + Handler: router, + BaseContext: bctx, + ConnState: diagConn, + MaxHeaderBytes: mhbz, + ErrorLog: errLogger(), } - }() - var listenCfg net.ListenConfig + forceCh := make(chan struct{}) + defer close(forceCh) - ln, err := listenCfg.Listen(ctx, "tcp", addr) - if err != nil { - return err - } + // handler to close server + go func() { + select { + case <-ctx.Done(): + log.Debug().Msg("force server close on ctx.Done()") + server.Close() + case <-forceCh: + log.Debug().Msg("go routine forced closed on exit") + } + }() - // Bind the deferred Close() to the stack variable to handle case where 'ln' is wrapped - defer func() { ln.Close() }() + var listenCfg net.ListenConfig - // Conn Limiter must be before the TLS handshake in the stack; - // The server should not eat the cost of the handshake if there - // is no capacity to service the connection. - // Also, it appears the HTTP2 implementation depends on the tls.Listener - // being at the top of the stack. - ln = wrapConnLimitter(ctx, ln, cfg) - - if cfg.TLS != nil && cfg.TLS.IsEnabled() { - commonTlsCfg, err := tlscommon.LoadTLSServerConfig(cfg.TLS) + ln, err := listenCfg.Listen(ctx, "tcp", addr) if err != nil { return err } - server.TLSConfig = commonTlsCfg.ToConfig() - // Must enable http/2 in the configuration explicitly. - // (see https://golang.org/pkg/net/http/#Server.Serve) - server.TLSConfig.NextProtos = []string{"h2", "http/1.1"} + // Bind the deferred Close() to the stack variable to handle case where 'ln' is wrapped + defer func() { ln.Close() }() - ln = tls.NewListener(ln, server.TLSConfig) + // Conn Limiter must be before the TLS handshake in the stack; + // The server should not eat the cost of the handshake if there + // is no capacity to service the connection. + // Also, it appears the HTTP2 implementation depends on the tls.Listener + // being at the top of the stack. + ln = wrapConnLimitter(ctx, ln, cfg) - } else { - log.Warn().Msg("exposed over insecure HTTP; enablement of TLS is strongly recommended") - } + if cfg.TLS != nil && cfg.TLS.IsEnabled() { + commonTlsCfg, err := tlscommon.LoadTLSServerConfig(cfg.TLS) + if err != nil { + return err + } + server.TLSConfig = commonTlsCfg.ToConfig() - if err := server.Serve(ln); err != nil && err != http.ErrServerClosed { - return err + // Must enable http/2 in the configuration explicitly. + // (see https://golang.org/pkg/net/http/#Server.Serve) + server.TLSConfig.NextProtos = []string{"h2", "http/1.1"} + + ln = tls.NewListener(ln, server.TLSConfig) + + } else { + log.Warn().Msg("exposed over insecure HTTP; enablement of TLS is strongly recommended") + } + + if err := server.Serve(ln); err != nil && err != http.ErrServerClosed { + return err + } } return nil diff --git a/internal/pkg/config/input.go b/internal/pkg/config/input.go index b4238fb3e..0a38769ed 100644 --- a/internal/pkg/config/input.go +++ b/internal/pkg/config/input.go @@ -53,10 +53,16 @@ func (c *ServerBulk) InitDefaults() { c.FlushMaxPending = 8 } +type Endpoint struct { + Host string `config:"host"` + Port uint16 `config:"port"` +} + // Server is the configuration for the server type Server struct { - Host string `config:"host"` - Port uint16 `config:"port"` + Host string `config:"host"` // TODO: deprecated use Endpoints + Port uint16 `config:"port"` // TODO: deprecated use Endpoints + Endpoints []Endpoint `config:"endpoints"` TLS *tlscommon.ServerConfig `config:"ssl"` Timeouts ServerTimeouts `config:"timeouts"` Profiler ServerProfiler `config:"profiler"` @@ -80,13 +86,37 @@ func (c *Server) InitDefaults() { c.Bulk.InitDefaults() } +// BindEndpoints returns the binding address for the all HTTP server listeners. +func (c *Server) BindEndpoints() []string { + endpoints := map[string]bool{ + c.BindAddress(): true, + } + + // add each of the endpoints to collection, + for _, ep := range c.Endpoints { + e := bindAddress(ep.Host, ep.Port) + endpoints[e] = true + } + + // we need to get rid of duplicates so we dont have port collision + uniqueEndpoints := make([]string, 0, len(endpoints)) + for ep := range endpoints { + uniqueEndpoints = append(uniqueEndpoints, ep) + } + + return uniqueEndpoints +} + // BindAddress returns the binding address for the HTTP server. func (c *Server) BindAddress() string { - host := c.Host + return bindAddress(c.Host, c.Port) +} + +func bindAddress(host string, port uint16) string { if strings.Count(host, ":") > 1 && strings.Count(host, "]") == 0 { host = "[" + host + "]" } - return fmt.Sprintf("%s:%d", host, c.Port) + return fmt.Sprintf("%s:%d", host, port) } // Input is the input defined by Agent to run Fleet Server. diff --git a/main.go b/main.go index c72cf78b2..27330d702 100644 --- a/main.go +++ b/main.go @@ -17,7 +17,7 @@ import ( "github.com/elastic/fleet-server/v7/internal/pkg/build" ) -const defaultVersion = "8.1.0" +const defaultVersion = "7.16.0" var ( Version string = defaultVersion