handshake_manager_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. package nebula
  2. import (
  3. "log/slog"
  4. "net/netip"
  5. "testing"
  6. "time"
  7. "github.com/flynn/noise"
  8. "github.com/gaissmai/bart"
  9. "github.com/rcrowley/go-metrics"
  10. "github.com/slackhq/nebula/cert"
  11. ct "github.com/slackhq/nebula/cert_test"
  12. "github.com/slackhq/nebula/handshake"
  13. "github.com/slackhq/nebula/header"
  14. "github.com/slackhq/nebula/test"
  15. "github.com/slackhq/nebula/udp"
  16. "github.com/stretchr/testify/assert"
  17. "github.com/stretchr/testify/require"
  18. )
  19. func Test_NewHandshakeManagerVpnIp(t *testing.T) {
  20. l := test.NewLogger()
  21. localrange := netip.MustParsePrefix("10.1.1.1/24")
  22. ip := netip.MustParseAddr("172.1.1.2")
  23. preferredRanges := []netip.Prefix{localrange}
  24. mainHM := newHostMap(l)
  25. mainHM.preferredRanges.Store(&preferredRanges)
  26. lh := newTestLighthouse()
  27. cs := &CertState{
  28. initiatingVersion: cert.Version1,
  29. privateKey: []byte{},
  30. v1Cert: &dummyCert{version: cert.Version1},
  31. v1Credential: nil,
  32. }
  33. blah := NewHandshakeManager(l, mainHM, lh, &udp.NoopConn{}, defaultHandshakeConfig)
  34. blah.f = &Interface{handshakeManager: blah, pki: &PKI{}, l: l}
  35. blah.f.pki.cs.Store(cs)
  36. now := time.Now()
  37. blah.NextOutboundHandshakeTimerTick(now)
  38. i := blah.StartHandshake(ip, nil)
  39. i2 := blah.StartHandshake(ip, nil)
  40. assert.Same(t, i, i2)
  41. // Adding something to pending should not affect the main hostmap
  42. assert.Empty(t, mainHM.Hosts)
  43. // Confirm they are in the pending index list
  44. assert.Contains(t, blah.vpnIps, ip)
  45. // Jump ahead `HandshakeRetries` ticks, offset by one to get the sleep logic right
  46. for i := 1; i <= DefaultHandshakeRetries+1; i++ {
  47. now = now.Add(time.Duration(i) * DefaultHandshakeTryInterval)
  48. blah.NextOutboundHandshakeTimerTick(now)
  49. }
  50. // Confirm they are still in the pending index list
  51. assert.Contains(t, blah.vpnIps, ip)
  52. // Tick 1 more time, a minute will certainly flush it out
  53. blah.NextOutboundHandshakeTimerTick(now.Add(time.Minute))
  54. // Confirm they have been removed
  55. assert.NotContains(t, blah.vpnIps, ip)
  56. }
  57. func testCountTimerWheelEntries(tw *LockingTimerWheel[netip.Addr]) (c int) {
  58. for _, i := range tw.t.wheel {
  59. n := i.Head
  60. for n != nil {
  61. c++
  62. n = n.Next
  63. }
  64. }
  65. return c
  66. }
  67. type mockEncWriter struct {
  68. }
  69. func (mw *mockEncWriter) SendMessageToVpnAddr(_ header.MessageType, _ header.MessageSubType, _ netip.Addr, _, _, _ []byte) {
  70. return
  71. }
  72. func (mw *mockEncWriter) SendVia(via *HostInfo, relay *Relay, ad, nb, out []byte, nocopy bool, q int) {
  73. return
  74. }
  75. func (mw *mockEncWriter) SendMessageToHostInfo(_ header.MessageType, _ header.MessageSubType, _ *HostInfo, _, _, _ []byte) {
  76. return
  77. }
  78. func (mw *mockEncWriter) Handshake(_ netip.Addr) {}
  79. func (mw *mockEncWriter) GetHostInfo(_ netip.Addr) *HostInfo {
  80. return nil
  81. }
  82. func (mw *mockEncWriter) GetCertState() *CertState {
  83. return &CertState{initiatingVersion: cert.Version2}
  84. }
  85. func TestValidatePeerCert(t *testing.T) {
  86. l := test.NewLogger()
  87. myNetwork := netip.MustParsePrefix("10.0.0.1/24")
  88. myAddrTable := new(bart.Lite)
  89. myAddrTable.Insert(netip.PrefixFrom(myNetwork.Addr(), myNetwork.Addr().BitLen()))
  90. myNetTable := new(bart.Lite)
  91. myNetTable.Insert(myNetwork.Masked())
  92. newHM := func() *HandshakeManager {
  93. hm := NewHandshakeManager(l, newHostMap(l), newTestLighthouse(), &udp.NoopConn{}, defaultHandshakeConfig)
  94. hm.f = &Interface{
  95. handshakeManager: hm,
  96. pki: &PKI{},
  97. l: l,
  98. myVpnAddrsTable: myAddrTable,
  99. myVpnNetworksTable: myNetTable,
  100. lightHouse: hm.lightHouse,
  101. }
  102. return hm
  103. }
  104. cached := func(networks ...netip.Prefix) *cert.CachedCertificate {
  105. return &cert.CachedCertificate{
  106. Certificate: &dummyCert{name: "peer", networks: networks},
  107. }
  108. }
  109. via := ViaSender{
  110. UdpAddr: netip.MustParseAddrPort("198.51.100.7:4242"),
  111. IsRelayed: true, // skip the remote allow list (covered separately)
  112. }
  113. t.Run("addr inside our networks sets anyVpnAddrsInCommon", func(t *testing.T) {
  114. hm := newHM()
  115. // 10.0.0.2 falls inside our 10.0.0.0/24
  116. addrs, common, ok := hm.validatePeerCert(via, cached(netip.MustParsePrefix("10.0.0.2/24")))
  117. assert.True(t, ok)
  118. assert.True(t, common)
  119. assert.Equal(t, []netip.Addr{netip.MustParseAddr("10.0.0.2")}, addrs)
  120. })
  121. t.Run("addr outside our networks leaves anyVpnAddrsInCommon false", func(t *testing.T) {
  122. hm := newHM()
  123. addrs, common, ok := hm.validatePeerCert(via, cached(netip.MustParsePrefix("192.168.1.5/24")))
  124. assert.True(t, ok)
  125. assert.False(t, common)
  126. assert.Equal(t, []netip.Addr{netip.MustParseAddr("192.168.1.5")}, addrs)
  127. })
  128. t.Run("any matching network is enough", func(t *testing.T) {
  129. hm := newHM()
  130. addrs, common, ok := hm.validatePeerCert(via, cached(
  131. netip.MustParsePrefix("192.168.1.5/24"),
  132. netip.MustParsePrefix("10.0.0.42/24"),
  133. ))
  134. assert.True(t, ok)
  135. assert.True(t, common)
  136. assert.Len(t, addrs, 2)
  137. })
  138. t.Run("self-handshake is rejected", func(t *testing.T) {
  139. hm := newHM()
  140. // 10.0.0.1 is in myVpnAddrsTable
  141. addrs, common, ok := hm.validatePeerCert(via, cached(netip.MustParsePrefix("10.0.0.1/24")))
  142. assert.False(t, ok)
  143. assert.False(t, common)
  144. assert.Nil(t, addrs)
  145. })
  146. t.Run("cert with no networks is rejected", func(t *testing.T) {
  147. hm := newHM()
  148. addrs, common, ok := hm.validatePeerCert(via, cached())
  149. assert.False(t, ok)
  150. assert.False(t, common)
  151. assert.Nil(t, addrs)
  152. })
  153. }
  154. func TestHandleIncomingDispatch(t *testing.T) {
  155. l := test.NewLogger()
  156. newHM := func() *HandshakeManager {
  157. hm := NewHandshakeManager(l, newHostMap(l), newTestLighthouse(), &udp.NoopConn{}, defaultHandshakeConfig)
  158. hm.f = &Interface{
  159. handshakeManager: hm,
  160. pki: &PKI{},
  161. l: l,
  162. }
  163. return hm
  164. }
  165. via := ViaSender{
  166. UdpAddr: netip.MustParseAddrPort("198.51.100.7:4242"),
  167. IsRelayed: true, // bypass remote allow list
  168. }
  169. // A packet body of zero length is fine for these tests: dispatch is
  170. // gated on header fields, and we assert that we never reach noise/cert
  171. // processing for any of the malformed shapes here.
  172. pkt := make([]byte, header.Len)
  173. t.Run("unsupported subtype dropped", func(t *testing.T) {
  174. hm := newHM()
  175. h := &header.H{Type: header.Handshake, Subtype: header.MessageSubType(99), MessageCounter: 1}
  176. hm.HandleIncoming(via, pkt, h)
  177. assert.Empty(t, hm.indexes, "no pending handshake should be created")
  178. })
  179. t.Run("stage-1 with non-zero RemoteIndex dropped", func(t *testing.T) {
  180. hm := newHM()
  181. h := &header.H{
  182. Type: header.Handshake,
  183. Subtype: header.HandshakeIXPSK0,
  184. RemoteIndex: 0xdeadbeef,
  185. MessageCounter: 1,
  186. }
  187. hm.HandleIncoming(via, pkt, h)
  188. assert.Empty(t, hm.indexes, "spoofed stage-1 must not create a pending machine")
  189. })
  190. t.Run("continuation with no matching pending index dropped", func(t *testing.T) {
  191. hm := newHM()
  192. h := &header.H{
  193. Type: header.Handshake,
  194. Subtype: header.HandshakeIXPSK0,
  195. RemoteIndex: 0xcafef00d,
  196. MessageCounter: 2,
  197. }
  198. hm.HandleIncoming(via, pkt, h)
  199. assert.Empty(t, hm.indexes, "orphan stage-2 must not create state")
  200. })
  201. }
  202. func TestBlocklistedHandshakeLogLevelAndCounter(t *testing.T) {
  203. ca, _, caKey, _ := ct.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil)
  204. pool := ct.NewTestCAPool(ca)
  205. newCredential := func(name, network string) (cert.Certificate, *handshake.Credential) {
  206. c, _, rawKey, _ := ct.NewTestCert(cert.Version2, cert.Curve_CURVE25519, ca, caKey,
  207. name, ca.NotBefore(), ca.NotAfter(), []netip.Prefix{netip.MustParsePrefix(network)}, nil, nil)
  208. privateKey, _, _, err := cert.UnmarshalPrivateKeyFromPEM(rawKey)
  209. require.NoError(t, err)
  210. certBytes, err := c.MarshalForHandshakes()
  211. require.NoError(t, err)
  212. cipherSuite := noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashSHA256)
  213. return c, handshake.NewCredential(c, certBytes, privateKey, cipherSuite)
  214. }
  215. initiatorCert, initiatorCredential := newCredential("initiator", "10.0.0.1/24")
  216. responderCert, responderCredential := newCredential("responder", "10.0.0.2/24")
  217. fingerprint, err := initiatorCert.Fingerprint()
  218. require.NoError(t, err)
  219. pool.BlocklistFingerprint(fingerprint)
  220. initiator, err := handshake.NewMachine(cert.Version2,
  221. func(cert.Version) *handshake.Credential { return initiatorCredential },
  222. func(c cert.Certificate) (*cert.CachedCertificate, error) {
  223. return pool.VerifyCertificate(time.Now(), c)
  224. },
  225. func() (uint32, error) { return 1, nil }, true, header.HandshakeIXPSK0)
  226. require.NoError(t, err)
  227. packet, err := initiator.Initiate(nil)
  228. require.NoError(t, err)
  229. handler := &capturingHandler{}
  230. logger := slog.New(handler)
  231. pki := &PKI{}
  232. pki.cs.Store(&CertState{initiatingVersion: cert.Version2, v2Credential: responderCredential})
  233. pki.caPool.Store(pool)
  234. hm := NewHandshakeManager(logger, nil, nil, nil, defaultHandshakeConfig)
  235. hm.metricRejectedBlocklisted = metrics.NewCounter()
  236. hm.f = &Interface{pki: pki, l: logger}
  237. via := ViaSender{UdpAddr: netip.MustParseAddrPort("198.51.100.7:4242")}
  238. hm.beginHandshake(via, packet, nil)
  239. entry := findEntry(t, handler.entries, "Failed to process handshake packet")
  240. assert.Equal(t, slog.LevelDebug, entry.Level)
  241. require.ErrorIs(t, entry.Attrs["error"].(error), cert.ErrBlockListed)
  242. assert.Equal(t, int64(1), hm.metricRejectedBlocklisted.Count())
  243. handler.entries = nil
  244. hm.beginHandshake(via, nil, nil)
  245. entry = findEntry(t, handler.entries, "Failed to process handshake packet")
  246. assert.Equal(t, slog.LevelError, entry.Level)
  247. assert.Equal(t, int64(1), hm.metricRejectedBlocklisted.Count())
  248. // The initiator sees the same rejection in continueHandshake, after a
  249. // responder has accepted its first packet and sent a valid reply.
  250. cleanPool := ct.NewTestCAPool(ca)
  251. responder, err := handshake.NewMachine(cert.Version2,
  252. func(cert.Version) *handshake.Credential { return responderCredential },
  253. func(c cert.Certificate) (*cert.CachedCertificate, error) {
  254. return cleanPool.VerifyCertificate(time.Now(), c)
  255. },
  256. func() (uint32, error) { return 2, nil }, false, header.HandshakeIXPSK0)
  257. require.NoError(t, err)
  258. response, _, err := responder.ProcessPacket(nil, packet)
  259. require.NoError(t, err)
  260. responderFingerprint, err := responderCert.Fingerprint()
  261. require.NoError(t, err)
  262. pool.BlocklistFingerprint(responderFingerprint)
  263. initiatingHM := NewHandshakeManager(logger, nil, nil, nil, defaultHandshakeConfig)
  264. initiatingHM.metricRejectedBlocklisted = metrics.NewCounter()
  265. initiatingHM.f = &Interface{l: logger}
  266. hh := &HandshakeHostInfo{
  267. machine: initiator,
  268. hostinfo: &HostInfo{localIndexId: 1, vpnAddrs: []netip.Addr{netip.MustParseAddr("10.0.0.2")}},
  269. }
  270. initiatingHM.indexes[1] = hh
  271. via.IsRelayed = true // skip the remote allow list before certificate validation
  272. initiatingHM.continueHandshake(via, hh, response)
  273. assert.Equal(t, int64(1), initiatingHM.metricRejectedBlocklisted.Count())
  274. }
  275. // Test_PendingHandshakeHasRemotes covers create-tunnel -address: a pending hostinfo must already
  276. // carry its RemoteList when StartHandshake runs its callback, so SetRemote there, before the
  277. // handshake loop has run, is safe and the address becomes one the handshake is sent to.
  278. func Test_PendingHandshakeHasRemotes(t *testing.T) {
  279. l := test.NewLogger()
  280. mainHM := newHostMap(l)
  281. preferredRanges := []netip.Prefix{}
  282. mainHM.preferredRanges.Store(&preferredRanges)
  283. hm := NewHandshakeManager(l, mainHM, newTestLighthouse(), &udp.NoopConn{}, defaultHandshakeConfig)
  284. vpnIp := netip.MustParseAddr("172.1.1.2")
  285. remote := netip.MustParseAddrPort("192.168.1.9:4242")
  286. hi := hm.StartHandshake(vpnIp, func(hh *HandshakeHostInfo) {
  287. require.NotNil(t, hh.hostinfo.remotes, "a pending handshake must never be without its RemoteList")
  288. hh.hostinfo.SetRemote(remote)
  289. })
  290. assert.Contains(t, hi.remotes.CopyAddrs(preferredRanges), remote, "the handshake must be sent to the supplied remote")
  291. assert.Equal(t, remote, hi.GetRemote())
  292. // A RemoteList keeps one learned address per host, so a second remote replaces the
  293. // first — the same behaviour change-remote has on an established tunnel.
  294. other := netip.MustParseAddrPort("192.168.1.10:4242")
  295. hi.SetRemote(other)
  296. assert.Equal(t, []netip.AddrPort{other}, hi.remotes.CopyAddrs(preferredRanges))
  297. assert.Equal(t, other, hi.GetRemote())
  298. }
  299. // newRemotesRaceFixture builds a handshake manager with an established tunnel to vpnAddr whose
  300. // RemoteList is the lighthouse's cached list, the state every teardown-during-handshake race starts from.
  301. func newRemotesRaceFixture(t *testing.T, vpnAddr netip.Addr) (*HandshakeManager, *Interface, *HostInfo) {
  302. t.Helper()
  303. l := test.NewLogger()
  304. mainHM := newHostMap(l)
  305. preferredRanges := []netip.Prefix{}
  306. mainHM.preferredRanges.Store(&preferredRanges)
  307. lh := newTestLighthouse()
  308. hm := NewHandshakeManager(l, mainHM, lh, &udp.NoopConn{}, defaultHandshakeConfig)
  309. f := &Interface{hostMap: mainHM, lightHouse: lh, handshakeManager: hm, l: l}
  310. hm.f = f
  311. established := &HostInfo{
  312. vpnAddrs: []netip.Addr{vpnAddr},
  313. localIndexId: 1,
  314. remoteIndexId: 101,
  315. remotes: lh.QueryCache([]netip.Addr{vpnAddr}),
  316. HandshakePacket: map[uint8][]byte{},
  317. }
  318. mainHM.Lock()
  319. mainHM.unlockedAddHostInfo(established, f)
  320. mainHM.Unlock()
  321. return hm, f, established
  322. }
  323. // With no handshake pending, tearing down the last tunnel still evicts the list.
  324. func Test_CloseTunnelEvictsWithoutPendingHandshake(t *testing.T) {
  325. vpnAddr := netip.MustParseAddr("172.1.1.2")
  326. _, f, established := newRemotesRaceFixture(t, vpnAddr)
  327. f.closeTunnel(established)
  328. assert.NotContains(t, f.hostMap.Hosts, vpnAddr)
  329. assert.NotContains(t, f.lightHouse.addrMap, vpnAddr)
  330. }