interface_test.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. package nebula
  2. import (
  3. "net/netip"
  4. "testing"
  5. "github.com/slackhq/nebula/cert"
  6. "github.com/slackhq/nebula/config"
  7. "github.com/slackhq/nebula/test"
  8. "github.com/stretchr/testify/assert"
  9. "github.com/stretchr/testify/require"
  10. )
  11. // TestReloadFirewall_CertUnsafeNetworksChanged verifies that reloadFirewall
  12. // rebuilds the firewall when only the certificate's UnsafeNetworks have changed,
  13. // even if the firewall section of the YAML has not.
  14. func TestReloadFirewall_CertUnsafeNetworksChanged(t *testing.T) {
  15. l := test.NewLogger()
  16. vpnNet := netip.MustParsePrefix("10.0.0.1/24")
  17. initialUnsafe := []netip.Prefix{netip.MustParsePrefix("198.51.100.0/24")}
  18. // dummyCert avoids dragging the real signing pipeline into a unit test.
  19. c1 := &dummyCert{
  20. version: cert.Version2,
  21. networks: []netip.Prefix{vpnNet},
  22. unsafeNetworks: initialUnsafe,
  23. }
  24. pki := &PKI{}
  25. pki.cs.Store(&CertState{v2Cert: c1, initiatingVersion: cert.Version2})
  26. rawYAML := `firewall:
  27. outbound:
  28. - port: any
  29. proto: any
  30. host: any
  31. inbound:
  32. - port: any
  33. proto: any
  34. host: any
  35. `
  36. cfg := config.NewC(l)
  37. require.NoError(t, cfg.LoadString(rawYAML))
  38. fw, err := NewFirewallFromConfig(l, pki.getCertState(), cfg)
  39. require.NoError(t, err)
  40. require.Equal(t, initialUnsafe, fw.unsafeNetworks)
  41. f := &Interface{
  42. pki: pki,
  43. firewall: fw,
  44. l: l,
  45. }
  46. // Swap the cert with a different UnsafeNetworks set.
  47. newUnsafe := []netip.Prefix{
  48. netip.MustParsePrefix("198.51.100.0/24"),
  49. netip.MustParsePrefix("203.0.113.0/24"),
  50. }
  51. c2 := &dummyCert{
  52. version: cert.Version2,
  53. networks: []netip.Prefix{vpnNet},
  54. unsafeNetworks: newUnsafe,
  55. }
  56. pki.cs.Store(&CertState{v2Cert: c2, initiatingVersion: cert.Version2})
  57. // Reload with the same YAML so HasChanged("firewall") reports false.
  58. require.NoError(t, cfg.ReloadConfigString(rawYAML).Err)
  59. require.False(t, cfg.HasChanged("firewall"))
  60. f.reloadFirewall(cfg, nil)
  61. assert.NotSame(t, fw, f.firewall, "firewall pointer should have been replaced")
  62. assert.Equal(t, newUnsafe, f.firewall.unsafeNetworks)
  63. assert.True(t, f.firewall.routableNetworks.Contains(netip.MustParseAddr("203.0.113.5")))
  64. }
  65. // TestReloadFirewall_NoChange verifies that reloadFirewall is a no-op when
  66. // neither the firewall config nor the cert's UnsafeNetworks have changed.
  67. func TestReloadFirewall_NoChange(t *testing.T) {
  68. l := test.NewLogger()
  69. vpnNet := netip.MustParsePrefix("10.0.0.1/24")
  70. unsafe := []netip.Prefix{netip.MustParsePrefix("198.51.100.0/24")}
  71. c1 := &dummyCert{
  72. version: cert.Version2,
  73. networks: []netip.Prefix{vpnNet},
  74. unsafeNetworks: unsafe,
  75. }
  76. pki := &PKI{}
  77. pki.cs.Store(&CertState{v2Cert: c1, initiatingVersion: cert.Version2})
  78. rawYAML := `firewall:
  79. outbound:
  80. - port: any
  81. proto: any
  82. host: any
  83. inbound:
  84. - port: any
  85. proto: any
  86. host: any
  87. `
  88. cfg := config.NewC(l)
  89. require.NoError(t, cfg.LoadString(rawYAML))
  90. fw, err := NewFirewallFromConfig(l, pki.getCertState(), cfg)
  91. require.NoError(t, err)
  92. f := &Interface{
  93. pki: pki,
  94. firewall: fw,
  95. l: l,
  96. }
  97. require.NoError(t, cfg.ReloadConfigString(rawYAML).Err)
  98. f.reloadFirewall(cfg, nil)
  99. assert.Same(t, fw, f.firewall, "firewall should not have been replaced")
  100. }
  101. // Rules that don't parse keep the old firewall, and the reload says it could not apply them
  102. func TestReloadFirewall_BadRulesFailTheReload(t *testing.T) {
  103. l := test.NewLogger()
  104. pki := &PKI{}
  105. pki.cs.Store(&CertState{v2Cert: &dummyCert{version: cert.Version2, networks: []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")}}, initiatingVersion: cert.Version2})
  106. rules := func(proto string) string {
  107. 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"
  108. }
  109. cfg := config.NewC(l)
  110. require.NoError(t, cfg.LoadString(rules("any")))
  111. fw, err := NewFirewallFromConfig(l, pki.getCertState(), cfg)
  112. require.NoError(t, err)
  113. f := &Interface{pki: pki, firewall: fw, l: l}
  114. cfg.RegisterReloadCallback(f.reloadFirewall)
  115. // The running firewall still isn't built from what the config asks for, so the same reload fails again
  116. for range 2 {
  117. res := cfg.ReloadConfigString(rules("nope"))
  118. require.NoError(t, res.Err)
  119. assert.True(t, res.Failed)
  120. assert.Same(t, fw, f.firewall)
  121. }
  122. res := cfg.ReloadConfigString(rules("tcp"))
  123. require.NoError(t, res.Err)
  124. assert.False(t, res.Failed)
  125. assert.NotSame(t, fw, f.firewall)
  126. }
  127. // Every input the firewall is built from rebuilds it when it changes, and nothing else does
  128. func TestReloadFirewall_RebuildsOnAnyInput(t *testing.T) {
  129. l := test.NewLogger()
  130. networks := []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")}
  131. base := "firewall:\n inbound:\n - port: any\n proto: any\n host: any\n"
  132. setup := func() (*config.C, *Interface, *PKI) {
  133. pki := &PKI{}
  134. pki.cs.Store(&CertState{v2Cert: &dummyCert{version: cert.Version2, networks: networks}, initiatingVersion: cert.Version2})
  135. cfg := config.NewC(l)
  136. require.NoError(t, cfg.LoadString(base))
  137. fw, err := NewFirewallFromConfig(l, pki.getCertState(), cfg)
  138. require.NoError(t, err)
  139. f := &Interface{pki: pki, firewall: fw, l: l}
  140. cfg.RegisterReloadCallback(f.reloadFirewall)
  141. return cfg, f, pki
  142. }
  143. setCert := func(pki *PKI, networks, unsafe []netip.Prefix) {
  144. pki.cs.Store(&CertState{v2Cert: &dummyCert{version: cert.Version2, networks: networks, unsafeNetworks: unsafe}, initiatingVersion: cert.Version2})
  145. }
  146. cases := map[string]struct {
  147. cfg string
  148. cert func(*PKI)
  149. want bool
  150. }{
  151. "nothing": {base + "punchy:\n punch: true\n", nil, false},
  152. "rules": {base + " outbound:\n - port: any\n proto: any\n host: any\n", nil, true},
  153. "inbound_action": {base + " inbound_action: reject\n", nil, true},
  154. "conntrack": {base + " conntrack:\n tcp_timeout: 1m\n", nil, true},
  155. "networks": {base, func(p *PKI) { setCert(p, []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")}, nil) }, true},
  156. "unsafe_networks": {base, func(p *PKI) { setCert(p, networks, []netip.Prefix{netip.MustParsePrefix("192.168.0.0/24")}) }, true},
  157. }
  158. for name, tc := range cases {
  159. t.Run(name, func(t *testing.T) {
  160. cfg, f, pki := setup()
  161. old := f.firewall
  162. if tc.cert != nil {
  163. tc.cert(pki)
  164. }
  165. res := cfg.ReloadConfigString(tc.cfg)
  166. require.NoError(t, res.Err)
  167. assert.False(t, res.Failed)
  168. assert.Equal(t, tc.want, f.firewall != old)
  169. })
  170. }
  171. }