diff --git a/go.mod b/go.mod index 5b8a4cc3..d8b4887a 100644 --- a/go.mod +++ b/go.mod @@ -9,3 +9,5 @@ require ( golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e ) + +go 1.13 diff --git a/server/leafnode.go b/server/leafnode.go index 9a91739a..69176563 100644 --- a/server/leafnode.go +++ b/server/leafnode.go @@ -652,7 +652,7 @@ func (s *Server) createLeafNode(conn net.Conn, remote *leafNodeCfg) *client { url := c.leaf.remote.getCurrentURL() host, _, _ := net.SplitHostPort(url.Host) // We need to check if this host is an IP. If so, we probably - // had this advertised to us an should use the configured host + // had this advertised to us and should use the configured host // name for the TLS server name. if net.ParseIP(host) != nil { if c.leaf.remote.tlsName != "" { @@ -679,9 +679,18 @@ func (s *Server) createLeafNode(conn net.Conn, remote *leafNodeCfg) *client { // Force handshake c.mu.Unlock() - if err := conn.Handshake(); err != nil { + if err = conn.Handshake(); err != nil { c.Errorf("TLS handshake error: %v", err) c.closeConnection(TLSHandshakeError) + // If we overrode and used the saved tlsName but that failed + // we will clear that here. This is for the case that another server + // does not have the same tlsName, maybe only IPs. + // https://github.com/nats-io/nats-server/issues/1256 + c.mu.Lock() + if host == c.leaf.remote.tlsName { + c.leaf.remote.tlsName = "" + } + c.mu.Unlock() return nil } // Reset the read deadline diff --git a/test/configs/certs/server-iponly.pem b/test/configs/certs/server-iponly.pem new file mode 100644 index 00000000..3f4ab15a --- /dev/null +++ b/test/configs/certs/server-iponly.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDgTCCAmmgAwIBAgIJAJT2U0V//QjOMA0GCSqGSIb3DQEBBQUAMHAxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDQTEQMA4GA1UECgwHU3luYWRpYTEQMA4GA1UECwwH +bmF0cy5pbzESMBAGA1UEAwwJbG9jYWxob3N0MRwwGgYJKoZIhvcNAQkBFg1kZXJl +a0BuYXRzLmlvMB4XDTIwMDEyMjE1MDQ1M1oXDTMwMDExOTE1MDQ1M1owXDELMAkG +A1UEBhMCVVMxCzAJBgNVBAgMAkNBMRAwDgYDVQQKDAdTeW5hZGlhMRAwDgYDVQQL +DAdOQVRTLmlvMRwwGgYJKoZIhvcNAQkBFg1kZXJla0BuYXRzLmlvMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvZjcFEmSQxpzNnVtwTNzs+f2iuID9yST +6zbaj2TGVhm3ktLx39NPfnjO29xu1FxYpKd5a0n/MDfZcZIh15FyjRohdQ+AI/Av +DmoaYpvrHnTf32GKBagxt9SX7sZgG2+xhZusPc2hnqi1VoTOn4xkpVdBdYHGAMjY +Pe+FnHgESVqUKa8nCr5WYGugUgZT9c/OR5zPUIodkjoh0STtgeiFl4xBRpbaAte3 +9auSTKsK4hmfuCEHXPfRfm5JcKnERcOyEKvKUOPJ6m+1GWQSMg6TWfiO/KK8M/Ml +5FmYbQ6XdrbanlDPTaFrYs5HhB33gora0JDnph9DJWFXwxV+4LhcFwIDAQABozIw +MDAPBgNVHREECDAGhwR/AAABMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcD +ATANBgkqhkiG9w0BAQUFAAOCAQEAlfWreTwqvMk3EXSW6ii781l7ypIRzaExxqYu +aBWtE7Q5UlISNFAsRu9TPqMHCtot52B/E3cPANcMRAQmzSlGhrqCVSpd6L7QTk+2 +b7UZHlFnxRHh8Pja25Z/R5vozV/3I1lDtRKGImSWa8GUHqNOkqz2sIWCUm2xWGL6 +Xiyr3LFzrelB6dMAlCiuHC5LVvjaEq/x3SKZ87XxAfbfBWLbbfDA95ZmWuskOSGv +8ntY0Y6fdXwAIzAldeFctNhmLy2cn2THQj3Qy1BiHO9NI+sxpt8ocRQRcR/eJvKs +Jd0TryU7HLFN/fk9V57q3GQ6gxD4ky+gUOKRylFitr2xlPfuRw== +-----END CERTIFICATE----- diff --git a/test/configs/certs/server-key-iponly.pem b/test/configs/certs/server-key-iponly.pem new file mode 100644 index 00000000..96748e68 --- /dev/null +++ b/test/configs/certs/server-key-iponly.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC9mNwUSZJDGnM2 +dW3BM3Oz5/aK4gP3JJPrNtqPZMZWGbeS0vHf009+eM7b3G7UXFikp3lrSf8wN9lx +kiHXkXKNGiF1D4Aj8C8Oahpim+sedN/fYYoFqDG31JfuxmAbb7GFm6w9zaGeqLVW +hM6fjGSlV0F1gcYAyNg974WceARJWpQprycKvlZga6BSBlP1z85HnM9Qih2SOiHR +JO2B6IWXjEFGltoC17f1q5JMqwriGZ+4IQdc99F+bklwqcRFw7IQq8pQ48nqb7UZ +ZBIyDpNZ+I78orwz8yXkWZhtDpd2ttqeUM9NoWtizkeEHfeCitrQkOemH0MlYVfD +FX7guFwXAgMBAAECggEAE6qdeYVAJLHDrax0nAvIPqsbCcD0BFjI9ycTeLhNUnUM +c7Bp4nu6zTWez3OIE4MYtsugbp6YV9oTNhKgbAnsRfKl8cyP0CqD1wzue7gMpXYe +Gr+1X2zY62aj8+Kj6XSmh2NkdGy2DQ0W8kiIXkhj0DrC0XuKnF45AAOualKQr0MG +JRFXi/AEEDkyWwfmqteD60HBzdweBqzYvsIyA8cQAgI4LguRKHKmLexqNDL0h0K+ +nPbQl5pg7es3kOaQ7DMICqd+E11btSXBJop95MLVhPXkzAyktIdfc1M7LsBNb7Dw +ja5G+wmmGG0KxCbqMVKCACxs63lWakIxFlrsxi+Q4QKBgQDwrM4f/j289+PBKlED +UCXHsGBgE1vNZMAinfAazWAImTAwWoY0FDrMBBPJGx+Azke5teI82o+Sq4jTL44Y +qvmqblHb9XEQLnRxSdl5WP794o1K9pP7nEvIpL+RngRIC1qt9BEGd5Pmn8FcF4BT +Zo9sdP8Hh6EJLGb33iYxUrTNTwKBgQDJq3GclCnVukbaGbCU2w54EDTJoghIY+W4 +kXeGL1VbSXD/NnyuhH/mua+UwBM7XSFJXKV2vfIhI1Qe9VW+7p4vyiLFp3atSb86 +AirajUPrCJNX1X/sm7fIss9U1ko/JruXSh1DxzEZvBQa52SwYH5bM6LbE6NJRuhS +xMY2ipyiuQKBgQCm0gCl6GH+w4wYbi5tL3agbT7AGWr+eSE8XWD6EvTHwPbH7Vcs +bgE7PHBCawxxCYppzQqdx5jQvxk92K6Tpp8bZRBUeFIAN1L624dkNy236PqqxTNZ +qcJVtuwaEP9CuKwH+y553xSjPISYQqnuJR6wvH+xRm92nlJY6KBse7lavQKBgCEV +NeMIz0AXec4HjtcshFgf2HkHUrKFaMb5XhEuLKN4DchgKN38MHsqFOqjA8SmR3Kg +dyheipzzDbayammS/XI7h67DBQ3yXiNm/Z6ys+SXmIw9IuoutVyAMNDrAm0PrpBo +ARsAT0a4etfbA8KHYdMWSm4D77JypmQFkbqazI1JAoGBAKZTn6KQaBcZ5JrSya17 +sClZPlSsYhIW8EbxqDgNwDippcNrLuJCsWiwMNW+glQoDIcGAo557PwAXrridH89 +5mjFCVggHeso3T6/nsBB86Q8B1hq5E8AszhEKH6IeKd5tFNZkK83eWjAlbIUrEBH +xXZFMGnYPPS7korkEzzuGJ+7 +-----END PRIVATE KEY----- diff --git a/test/leafnode_test.go b/test/leafnode_test.go index 5513dcf3..7d138a3b 100644 --- a/test/leafnode_test.go +++ b/test/leafnode_test.go @@ -15,8 +15,10 @@ package test import ( "crypto/tls" + "crypto/x509" "encoding/json" "fmt" + "io/ioutil" "math/rand" "net" "net/url" @@ -3016,3 +3018,81 @@ func TestLeafNodeDaisyChain(t *testing.T) { t.Fatalf("Expected a response") } } + +// This will test failover to a server with a cert with only an IP after successfully connecting +// to a server with a cert with both. +func TestClusterTLSMixedIPAndDNS(t *testing.T) { + confA := createConfFile(t, []byte(` + listen: 127.0.0.1:-1 + leafnodes { + listen: "127.0.0.1:-1" + tls { + cert_file: "./configs/certs/server-iponly.pem" + key_file: "./configs/certs/server-key-iponly.pem" + ca_file: "./configs/certs/ca.pem" + timeout: 2 + } + } + cluster { + listen: "127.0.0.1:-1" + } + `)) + srvA, optsA := RunServerWithConfig(confA) + defer srvA.Shutdown() + + bConfigTemplate := ` + listen: 127.0.0.1:-1 + leafnodes { + listen: "localhost:-1" + tls { + cert_file: "./configs/certs/server-cert.pem" + key_file: "./configs/certs/server-key.pem" + ca_file: "./configs/certs/ca.pem" + timeout: 2 + } + } + cluster { + listen: "127.0.0.1:-1" + routes [ + "nats://%s:%d" + ] + } + ` + confB := createConfFile(t, []byte(fmt.Sprintf(bConfigTemplate, + optsA.Cluster.Host, optsA.Cluster.Port))) + srvB, optsB := RunServerWithConfig(confB) + defer srvB.Shutdown() + + checkClusterFormed(t, srvA, srvB) + + // Solicit a leafnode server here. Don't use the helper since we need verification etc. + o := DefaultTestOptions + o.Port = -1 + rurl, _ := url.Parse(fmt.Sprintf("nats-leaf://%s:%d", optsB.LeafNode.Host, optsB.LeafNode.Port)) + o.LeafNode.ReconnectInterval = 10 * time.Millisecond + remote := &server.RemoteLeafOpts{URLs: []*url.URL{rurl}} + remote.TLSConfig = &tls.Config{MinVersion: tls.VersionTLS12} + pool := x509.NewCertPool() + rootPEM, err := ioutil.ReadFile("./configs/certs/ca.pem") + if err != nil || rootPEM == nil { + t.Fatalf("Error loading or parsing rootCA file: %v", err) + } + ok := pool.AppendCertsFromPEM(rootPEM) + if !ok { + t.Fatalf("Failed to parse root certificate from %q", "./configs/certs/ca.pem") + } + remote.TLSConfig.RootCAs = pool + host, _, _ := net.SplitHostPort(optsB.LeafNode.Host) + remote.TLSConfig.ServerName = host + o.LeafNode.Remotes = []*server.RemoteLeafOpts{remote} + sl, _ := RunServer(&o), &o + defer sl.Shutdown() + + checkLeafNodeConnected(t, srvB) + + // Now kill off srvB and force client to connect to srvA. + srvB.Shutdown() + + // Make sure this works. + checkLeafNodeConnected(t, srvA) +}