[FIXED] Routing: TLS connections to discovered server may fail

The server was not setting "server name" in the TLS configuration
for route connections, which may lead to failed (re)connect if
the certificate does not allow for the IP and the URL did not
have the hostname, which would happen with gossip protocol.

Signed-off-by: Ivan Kozlovic <ivan@synadia.com>
This commit is contained in:
Ivan Kozlovic
2022-11-07 17:26:17 -07:00
parent d8a4b56b8b
commit 2d181e1c27
4 changed files with 84 additions and 1 deletions

View File

@@ -413,7 +413,9 @@ func (r *routesOption) Apply(server *Server) {
}
// Add routes.
server.mu.Lock()
server.solicitRoutes(r.add)
server.mu.Unlock()
server.Noticef("Reloaded: cluster routes")
}

View File

@@ -1305,6 +1305,7 @@ func (s *Server) createRoute(conn net.Conn, rURL *url.URL) *client {
authRequired := s.routeInfo.AuthRequired
tlsRequired := s.routeInfo.TLSRequired
clusterName := s.info.Cluster
tlsName := s.routeTLSName
s.mu.Unlock()
// Grab lock
@@ -1329,8 +1330,13 @@ func (s *Server) createRoute(conn net.Conn, rURL *url.URL) *client {
tlsConfig = tlsConfig.Clone()
}
// Perform (server or client side) TLS handshake.
if _, err := c.doTLSHandshake("route", didSolicit, rURL, tlsConfig, _EMPTY_, opts.Cluster.TLSTimeout, opts.Cluster.TLSPinnedCerts); err != nil {
if resetTLSName, err := c.doTLSHandshake("route", didSolicit, rURL, tlsConfig, tlsName, opts.Cluster.TLSTimeout, opts.Cluster.TLSPinnedCerts); err != nil {
c.mu.Unlock()
if resetTLSName {
s.mu.Lock()
s.routeTLSName = _EMPTY_
s.mu.Unlock()
}
return nil
}
}
@@ -1897,7 +1903,24 @@ func (c *client) isSolicitedRoute() bool {
return c.kind == ROUTER && c.route != nil && c.route.didSolicit
}
// Save the first hostname found in route URLs. This will be used in gossip mode
// when trying to create a TLS connection by setting the tlsConfig.ServerName.
// Lock is held on entry
func (s *Server) saveRouteTLSName() {
var tlsName string
for _, u := range s.getOpts().Routes {
if tlsName == _EMPTY_ && net.ParseIP(u.Hostname()) == nil {
tlsName = u.Hostname()
}
}
s.routeTLSName = tlsName
}
// Start connection process to provided routes. Each route connection will
// be started in a dedicated go routine.
// Lock is held on entry
func (s *Server) solicitRoutes(routes []*url.URL) {
s.saveRouteTLSName()
for _, r := range routes {
route := r
s.startGoRoutine(func() { s.connectToRoute(route, true, true) })

View File

@@ -1670,3 +1670,60 @@ func TestRouteSolicitedReconnectsEvenIfImplicit(t *testing.T) {
// OK
}
}
func TestRouteSaveTLSName(t *testing.T) {
c1Conf := createConfFile(t, []byte(`
port: -1
cluster {
name: "abc"
port: -1
tls {
cert_file: '../test/configs/certs/server-noip.pem'
key_file: '../test/configs/certs/server-key-noip.pem'
ca_file: '../test/configs/certs/ca.pem'
}
}
`))
defer removeFile(t, c1Conf)
s1, o1 := RunServerWithConfig(c1Conf)
defer s1.Shutdown()
c2And3Conf := createConfFile(t, []byte(fmt.Sprintf(`
port: -1
cluster {
name: "abc"
port: -1
routes: ["nats://localhost:%d"]
tls {
cert_file: '../test/configs/certs/server-noip.pem'
key_file: '../test/configs/certs/server-key-noip.pem'
ca_file: '../test/configs/certs/ca.pem'
}
}
`, o1.Cluster.Port)))
defer removeFile(t, c2And3Conf)
s2, _ := RunServerWithConfig(c2And3Conf)
defer s2.Shutdown()
checkClusterFormed(t, s1, s2)
s3, _ := RunServerWithConfig(c2And3Conf)
defer s3.Shutdown()
checkClusterFormed(t, s1, s2, s3)
// Do a reload of s2 and close the route connections and make sure it
// reconnects properly.
err := s2.Reload()
require_NoError(t, err)
s2.mu.RLock()
for _, r := range s2.routes {
r.mu.Lock()
r.nc.Close()
r.mu.Unlock()
}
s2.mu.RUnlock()
checkClusterFormed(t, s1, s2, s3)
}

View File

@@ -152,6 +152,7 @@ type Server struct {
routeInfoJSON []byte
routeResolver netResolver
routesToSelf map[string]struct{}
routeTLSName string
leafNodeListener net.Listener
leafNodeListenerErr error
leafNodeInfo Info