mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-02 11:48:43 -07:00
[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:
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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) })
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -152,6 +152,7 @@ type Server struct {
|
||||
routeInfoJSON []byte
|
||||
routeResolver netResolver
|
||||
routesToSelf map[string]struct{}
|
||||
routeTLSName string
|
||||
leafNodeListener net.Listener
|
||||
leafNodeListenerErr error
|
||||
leafNodeInfo Info
|
||||
|
||||
Reference in New Issue
Block a user