interface_test.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. package nebula
  2. import (
  3. "net/netip"
  4. "testing"
  5. "github.com/gaissmai/bart"
  6. "github.com/slackhq/nebula/cert"
  7. "github.com/slackhq/nebula/config"
  8. "github.com/slackhq/nebula/overlay/overlaytest"
  9. "github.com/slackhq/nebula/routing"
  10. "github.com/slackhq/nebula/test"
  11. "github.com/stretchr/testify/assert"
  12. "github.com/stretchr/testify/require"
  13. )
  14. // TestReloadFirewall_CertUnsafeNetworksChanged verifies that reloadFirewall
  15. // rebuilds the firewall when only the certificate's UnsafeNetworks have changed,
  16. // even if the firewall section of the YAML has not.
  17. func TestReloadFirewall_CertUnsafeNetworksChanged(t *testing.T) {
  18. l := test.NewLogger()
  19. vpnNet := netip.MustParsePrefix("10.0.0.1/24")
  20. initialUnsafe := []netip.Prefix{netip.MustParsePrefix("198.51.100.0/24")}
  21. // dummyCert avoids dragging the real signing pipeline into a unit test.
  22. c1 := &dummyCert{
  23. version: cert.Version2,
  24. networks: []netip.Prefix{vpnNet},
  25. unsafeNetworks: initialUnsafe,
  26. }
  27. pki := &PKI{}
  28. pki.cs.Store(&CertState{v2Cert: c1, initiatingVersion: cert.Version2})
  29. rawYAML := `firewall:
  30. outbound:
  31. - port: any
  32. proto: any
  33. host: any
  34. inbound:
  35. - port: any
  36. proto: any
  37. host: any
  38. `
  39. cfg := config.NewC(l)
  40. require.NoError(t, cfg.LoadString(rawYAML))
  41. fw, err := NewFirewallFromConfig(l, pki.getCertState(), cfg)
  42. require.NoError(t, err)
  43. require.Equal(t, initialUnsafe, fw.unsafeNetworks)
  44. f := &Interface{
  45. pki: pki,
  46. firewall: fw,
  47. l: l,
  48. }
  49. // Swap the cert with a different UnsafeNetworks set.
  50. newUnsafe := []netip.Prefix{
  51. netip.MustParsePrefix("198.51.100.0/24"),
  52. netip.MustParsePrefix("203.0.113.0/24"),
  53. }
  54. c2 := &dummyCert{
  55. version: cert.Version2,
  56. networks: []netip.Prefix{vpnNet},
  57. unsafeNetworks: newUnsafe,
  58. }
  59. pki.cs.Store(&CertState{v2Cert: c2, initiatingVersion: cert.Version2})
  60. // Reload with the same YAML so HasChanged("firewall") reports false.
  61. require.NoError(t, cfg.ReloadConfigString(rawYAML).Err)
  62. require.False(t, cfg.HasChanged("firewall"))
  63. f.reloadFirewall(cfg, nil)
  64. assert.NotSame(t, fw, f.firewall, "firewall pointer should have been replaced")
  65. assert.Equal(t, newUnsafe, f.firewall.unsafeNetworks)
  66. assert.True(t, f.firewall.routableNetworks.Contains(netip.MustParseAddr("203.0.113.5")))
  67. }
  68. // TestReloadFirewall_NoChange verifies that reloadFirewall is a no-op when
  69. // neither the firewall config nor the cert's UnsafeNetworks have changed.
  70. func TestReloadFirewall_NoChange(t *testing.T) {
  71. l := test.NewLogger()
  72. vpnNet := netip.MustParsePrefix("10.0.0.1/24")
  73. unsafe := []netip.Prefix{netip.MustParsePrefix("198.51.100.0/24")}
  74. c1 := &dummyCert{
  75. version: cert.Version2,
  76. networks: []netip.Prefix{vpnNet},
  77. unsafeNetworks: unsafe,
  78. }
  79. pki := &PKI{}
  80. pki.cs.Store(&CertState{v2Cert: c1, initiatingVersion: cert.Version2})
  81. rawYAML := `firewall:
  82. outbound:
  83. - port: any
  84. proto: any
  85. host: any
  86. inbound:
  87. - port: any
  88. proto: any
  89. host: any
  90. `
  91. cfg := config.NewC(l)
  92. require.NoError(t, cfg.LoadString(rawYAML))
  93. fw, err := NewFirewallFromConfig(l, pki.getCertState(), cfg)
  94. require.NoError(t, err)
  95. f := &Interface{
  96. pki: pki,
  97. firewall: fw,
  98. l: l,
  99. }
  100. require.NoError(t, cfg.ReloadConfigString(rawYAML).Err)
  101. f.reloadFirewall(cfg, nil)
  102. assert.Same(t, fw, f.firewall, "firewall should not have been replaced")
  103. }
  104. // Rules that don't parse keep the old firewall, and the reload says it could not apply them
  105. func TestReloadFirewall_BadRulesFailTheReload(t *testing.T) {
  106. l := test.NewLogger()
  107. pki := &PKI{}
  108. pki.cs.Store(&CertState{v2Cert: &dummyCert{version: cert.Version2, networks: []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")}}, initiatingVersion: cert.Version2})
  109. rules := func(proto string) string {
  110. return "firewall:\n outbound:\n - port: any\n proto: any\n host: any\n inbound:\n - port: any\n proto: " + proto + "\n host: any\n"
  111. }
  112. cfg := config.NewC(l)
  113. require.NoError(t, cfg.LoadString(rules("any")))
  114. fw, err := NewFirewallFromConfig(l, pki.getCertState(), cfg)
  115. require.NoError(t, err)
  116. f := &Interface{pki: pki, firewall: fw, l: l}
  117. cfg.RegisterReloadCallback(f.reloadFirewall)
  118. res := cfg.ReloadConfigString(rules("nope"))
  119. require.NoError(t, res.Err)
  120. assert.True(t, res.Failed)
  121. assert.Same(t, fw, f.firewall)
  122. }
  123. // routesTun is a NoopTun with a route table
  124. type routesTun struct {
  125. overlaytest.NoopTun
  126. routes *bart.Table[routing.Gateways]
  127. }
  128. func (t routesTun) RoutesFor(ip netip.Addr) routing.Gateways {
  129. r, _ := t.routes.Lookup(ip)
  130. return r
  131. }
  132. func TestInterface_SiblingPeers(t *testing.T) {
  133. addr := netip.MustParseAddr
  134. routes := new(bart.Table[routing.Gateways])
  135. // a and b carry 198.51.100.0/24, the route names b by its v6 address
  136. routes.Insert(netip.MustParsePrefix("198.51.100.0/24"), routing.Gateways{
  137. routing.NewGateway(addr("192.0.2.2"), 1),
  138. routing.NewGateway(addr("fd00::3"), 1),
  139. routing.NewGateway(addr("192.0.2.5"), 1), // no tunnel to this one
  140. })
  141. routes.Insert(netip.MustParsePrefix("203.0.113.0/24"), routing.Gateways{routing.NewGateway(addr("192.0.2.2"), 1)})
  142. hm := newHostMap(test.NewLogger())
  143. a := &HostInfo{vpnAddrs: []netip.Addr{addr("192.0.2.2"), addr("fd00::2")}, localIndexId: 1}
  144. b := &HostInfo{vpnAddrs: []netip.Addr{addr("192.0.2.3"), addr("fd00::3")}, localIndexId: 2}
  145. c := &HostInfo{vpnAddrs: []netip.Addr{addr("192.0.2.4")}, localIndexId: 3}
  146. for _, h := range []*HostInfo{a, b, c} {
  147. hm.unlockedAddHostInfo(h, &Interface{})
  148. }
  149. f := &Interface{inside: routesTun{routes: routes}, hostMap: hm}
  150. // Siblings come back as their first vpn address, the one conntrack keys on, whichever address the route names
  151. assert.Equal(t, []netip.Addr{addr("192.0.2.3")}, f.siblingPeers(addr("198.51.100.5"), a, nil))
  152. assert.Equal(t, []netip.Addr{addr("192.0.2.2")}, f.siblingPeers(addr("198.51.100.5"), b, nil))
  153. assert.Empty(t, f.siblingPeers(addr("198.51.100.5"), c, nil), "not one of our gateways for the network")
  154. assert.Empty(t, f.siblingPeers(addr("203.0.113.5"), a, nil), "a single gateway route has no siblings")
  155. assert.Empty(t, f.siblingPeers(addr("192.0.2.3"), a, nil), "no route")
  156. }