|
|
- package capstan
-
- import (
- "context"
- "crypto/tls"
- "crypto/x509"
- "fmt"
- "net"
- "net/http"
- "strings"
- "time"
-
- "git.destrealm.org/go/capstan/internal/sys"
- )
-
- // TODO: Consider refactoring named listeners to store their network address
- // instead.
-
- // Apply http.Server instances to all stored listeners. Errors will be returned
- // via the error channel errc.
- //
- // This increments the Router waitgroup once for each configured listener.
- func (r *Router) listen(errc chan error) {
- for _, listener := range r.listeners {
- r.AttachListener(listener)
- }
- }
-
- // Setup listeners to all configured addresses. Currently this only sets up 3
- // listeners for each of the following, if configured: ListenAddress,
- // ListenAddressTLS, and ListenSocket.
- //
- // If you wish to attach custom listeners, you must call AddListener (below) and
- // it should implement both net.Listener and syscall.Conn. While simply
- // implementing net.Listener should be sufficient, it may fail for high
- // availability configurations that expect graceful restart capabilities.
- func (r *Router) setupListeners() error {
- var requiresPriv bool
- config := r.app.Config().Server
-
- r.proxy.Switch(r.mux)
-
- if len(r.listeners) == 0 {
- requiresPriv = isPrivPort(r.app.Config().Server.ListenAddress)
- if listener, err := r.ListenHTTP(r.app.Config().Server.ListenAddress); err != nil {
- return err
- } else {
- r.namedListeners.tcp = listener
- r.AddListener(listener)
- }
-
- requiresPriv = isPrivPort(r.app.Config().Server.ListenAddressTLS)
- if listener, err := r.ListenTLS(r.app.Config().Server.ListenAddressTLS); err != nil {
- return err
- } else {
- r.namedListeners.tls = listener
- r.AddListener(listener)
- }
-
- if listener, err := r.ListenSocket(r.app.Config().Server.ListenSocket); err != nil {
- return err
- } else {
- r.namedListeners.sock = listener
- r.AddListener(listener)
- }
- }
-
- if requiresPriv && sys.HasSuperuser() {
- if config.RunAs == "" {
- return fmt.Errorf("application is running as superuser with no RunAs configured")
- }
- err := sys.SwitchUser(config.RunAs)
- if err != nil {
- return fmt.Errorf("unable to drop privileges: %v", err)
- }
- }
-
- return nil
- }
-
- // AddListener accepts a listener against which the Chi handler will be attached
- // and will be used as the base listener for an HTTP or HTTPS service.
- //
- // This listener will be mapped to the configured network management as defined
- // in network.go and netlisten_*.go. If the specified listener does not
- // implement both net.Listen and syscall.Conn it will not be used for high
- // availability graceful restart. If you depend on a custom listener, you may
- // need to implement graceful restart yourself or implement syscall.Conn.
- func (r *Router) AddListener(listener net.Listener) {
- if listener != nil {
- r.listeners = append(r.listeners, listener)
- }
- }
-
- // AttachListener to the current route handler.
- //
- // This may be used by external code to enable listeners.
- func (r *Router) AttachListener(listener net.Listener) {
- var server HTTPServer
- server.Handler = r.proxy
- server.ReadHeaderTimeout = time.Second * time.Duration(r.app.Config().Server.HeaderTimeout)
- server.IdleTimeout = time.Second * time.Duration(r.app.Config().Server.IdleConnectionTimeout)
- server.MaxHeaderBytes = r.app.Config().Server.MaxHeaderLength
- r.servers = append(r.servers, &server)
- r.wg.Add(1)
- go func(l net.Listener) {
- if err := server.Serve(l); err != http.ErrServerClosed {
- r.Close()
- }
- r.wg.Done()
- }(listener)
- }
-
- func (r *Router) AttachNamedListener(name string, listener net.Listener) {
- switch name {
- case "tls":
- r.namedListeners.tls = listener
- case "tcp":
- r.namedListeners.tcp = listener
- case "sock":
- fallthrough
- case "socket":
- fallthrough
- case "file":
- r.namedListeners.sock = listener
- }
- r.AttachListener(listener)
- }
-
- // Close all configured listeners gracefully. This calls Shutdown on each
- // http.Server and waits until all connections have closed before terminating.
- func (r *Router) Close() {
- r.app.Logger().Noticef("Shutting down (waiting max %d second(s) for connections to close)...", r.app.Config().Server.ShutdownTimeout)
- for _, server := range r.servers {
- ctx, cancel := context.WithTimeout(r.shutdownCtx,
- time.Second*time.Duration(r.app.Config().Server.ShutdownTimeout))
- defer cancel()
-
- r.app.Logger().Debugf("closing listener: (%s) %s", server.Listener().Addr().Network(), server.Listener().Addr().String())
- err := server.Shutdown(ctx)
- if err != nil {
- r.app.Logger().Warning("Error shutting down server:", err)
- }
- }
-
- for _, listener := range r.listeners {
- listener.Close()
- }
- }
-
- // CloseNamed listener.
- //
- // This will gracefully shut down the named listener, if available. The special
- // names "tls," "tcp," and "file" are used to close reserved listeners for TLS,
- // TCP, and Unix domain socket traffic.
- //
- // This will also remove the listener from the router's listeners store.
- func (r *Router) CloseNamed(name string) (err error) {
- var listener net.Listener
-
- switch name {
- case "tls":
- listener = r.namedListeners.tls
- case "tcp":
- listener = r.namedListeners.tcp
- case "sock":
- fallthrough
- case "socket":
- fallthrough
- case "file":
- listener = r.namedListeners.sock
- }
-
- if listener == nil {
- return
- }
-
- l := make([]net.Listener, 0)
- for _, mapped := range r.listeners {
- if mapped != listener {
- l = append(l, mapped)
- }
- }
- r.listeners = l
-
- r.app.Logger().Error("Shutting down listener (waiting max 10 seconds for connections to close)...")
- ctx, _ := context.WithTimeout(r.shutdownCtx, time.Second*10)
- for _, server := range r.servers {
- if server.Listener() == listener {
- if err = server.Shutdown(ctx); err != nil {
- r.app.Logger().Warning("error shutting down listener:", err)
- return
- } else {
- StopListening(listener)
- }
- }
- }
-
- switch name {
- case "tls":
- r.namedListeners.tls = nil
- case "tcp":
- r.namedListeners.tcp = nil
- case "sock":
- fallthrough
- case "socket":
- fallthrough
- case "file":
- r.namedListeners.sock = nil
- }
-
- return
- }
-
- // Listen on all configured listeners and wait until they close.
- //
- // If an error is received by this function it will call Router.Close itself and
- // begin the shutdown process.
- func (r *Router) Listen() error {
- var err error
- errc := make(chan error)
-
- if err := r.setupListeners(); err != nil {
- return err
- }
-
- r.listen(errc)
- go func() {
- err = <-errc
- if err != nil {
- r.Close()
- r.wg.Done()
- return
- }
- }()
-
- r.wg.Wait()
-
- return err
- }
-
- // ListenHTTP allows client code to configure a new port to listen on. This
- // should not be called if the server is already listening on an HTTP port. This
- // is called internally if ListenAddress is set.
- func (r *Router) ListenHTTP(addr string) (net.Listener, error) {
- // An HTTP port most liekly wasn't specified.
- if addr == "" {
- return nil, nil
- }
-
- return Listen("tcp", addr)
- }
-
- // ListenSocket allows client code to configure a new UNIX domain socket to
- // listen on. This should not be called if the server is already listening on
- // the same socket. This is called internally if ListenSocket is set.
- func (r *Router) ListenSocket(sock string) (net.Listener, error) {
- if sock == "" {
- return nil, nil
- }
-
- return Listen("unix", sock)
- }
-
- // ListenTLS allows client code to configure a new TLS port to listen on. This
- // should not be called if the server is already listening on a TLS port. This
- // is called internally if ListenAddressTLS is set.
- func (r *Router) ListenTLS(addr string) (net.Listener, error) {
- // TLS most likely wasn't specified.
- if addr == "" {
- return nil, nil
- }
-
- var err error
- var tc *tls.Config
- var pool *x509.CertPool
- ciphers := []uint16{
- tls.TLS_AES_256_GCM_SHA384,
- tls.TLS_AES_128_GCM_SHA256,
- tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
- tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
- tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
- tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- tls.TLS_CHACHA20_POLY1305_SHA256,
- tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
- tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
- tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
-
- // No forward secrecy but disabling these may reduce client
- // compatibility.
- tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
- tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
- }
-
- config := r.app.Config().Server
- if config.TLSConfig != nil {
- tc = config.TLSConfig
- } else {
- host := config.CalculatedHostname()
- if strings.Contains(host, ":") {
- host, _, err = net.SplitHostPort(host)
- if err != nil && host == "" {
- host = "localhost"
- } else if err != nil {
- return nil, err
- }
- }
-
- if config.MinVersion == 0 {
- config.MinVersion = tls.VersionTLS11
- }
- if config.MaxVersion == 0 {
- config.MaxVersion = tls.VersionTLS13
- }
-
- pool = x509.NewCertPool()
-
- tc = &tls.Config{
- Certificates: config.Certificates,
- RootCAs: pool,
- ServerName: host,
- MinVersion: config.MinVersion,
- MaxVersion: config.MaxVersion,
- CipherSuites: ciphers,
- NextProtos: []string{"h2"},
- // https://blog.cloudflare.com/exposing-go-on-the-internet/
- PreferServerCipherSuites: true,
- CurvePreferences: []tls.CurveID{
- tls.CurveP256,
- tls.X25519,
- },
- }
- }
-
- listener, err := Listen("tcp", addr)
- if err != nil {
- return nil, err
- }
-
- return tls.NewListener(listener, tc), nil
- }
-
- // HasSocket returns true if Capstan was configured to listen on a domain
- // socket.
- //
- // As with other Has* functions for listeners, this does not indicate a status
- // for manually-configured listeners and applies only to those that were started
- // via the Listen* methods (either automatically, via configuration, or
- // manually).
- func (r *Router) HasSocket() bool {
- return r.namedListeners.sock != nil
- }
-
- // HasTCP returns true if Capstan was configured to listen on a TCP socket.
- //
- // As with other Has* functions for listeners, this does not indicate a status
- // for manually-configured listeners and applies only to those that were started
- // via the Listen* methods (either automatically, via configuration, or
- // manually).
- func (r *Router) HasTCP() bool {
- return r.namedListeners.tcp != nil
- }
-
- // HasTLS returns true if Capstan was configured to listen on a TLS socket.
- //
- // As with other Has* functions for listeners, this does not indicate a status
- // for manually-configured listeners and applies only to those that were started
- // via the Listen* methods (either automatically, via configuration, or
- // manually).
- func (r *Router) HasTLS() bool {
- return r.namedListeners.tls != nil
- }
|