| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197 |
- package nebula
- import (
- "net/netip"
- "testing"
- "github.com/slackhq/nebula/cert"
- "github.com/slackhq/nebula/config"
- "github.com/slackhq/nebula/test"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- // TestReloadFirewall_CertUnsafeNetworksChanged verifies that reloadFirewall
- // rebuilds the firewall when only the certificate's UnsafeNetworks have changed,
- // even if the firewall section of the YAML has not.
- func TestReloadFirewall_CertUnsafeNetworksChanged(t *testing.T) {
- l := test.NewLogger()
- vpnNet := netip.MustParsePrefix("10.0.0.1/24")
- initialUnsafe := []netip.Prefix{netip.MustParsePrefix("198.51.100.0/24")}
- // dummyCert avoids dragging the real signing pipeline into a unit test.
- c1 := &dummyCert{
- version: cert.Version2,
- networks: []netip.Prefix{vpnNet},
- unsafeNetworks: initialUnsafe,
- }
- pki := &PKI{}
- pki.cs.Store(&CertState{v2Cert: c1, initiatingVersion: cert.Version2})
- rawYAML := `firewall:
- outbound:
- - port: any
- proto: any
- host: any
- inbound:
- - port: any
- proto: any
- host: any
- `
- cfg := config.NewC(l)
- require.NoError(t, cfg.LoadString(rawYAML))
- fw, err := NewFirewallFromConfig(l, pki.getCertState(), cfg)
- require.NoError(t, err)
- require.Equal(t, initialUnsafe, fw.unsafeNetworks)
- f := &Interface{
- pki: pki,
- firewall: fw,
- l: l,
- }
- // Swap the cert with a different UnsafeNetworks set.
- newUnsafe := []netip.Prefix{
- netip.MustParsePrefix("198.51.100.0/24"),
- netip.MustParsePrefix("203.0.113.0/24"),
- }
- c2 := &dummyCert{
- version: cert.Version2,
- networks: []netip.Prefix{vpnNet},
- unsafeNetworks: newUnsafe,
- }
- pki.cs.Store(&CertState{v2Cert: c2, initiatingVersion: cert.Version2})
- // Reload with the same YAML so HasChanged("firewall") reports false.
- require.NoError(t, cfg.ReloadConfigString(rawYAML).Err)
- require.False(t, cfg.HasChanged("firewall"))
- f.reloadFirewall(cfg, nil)
- assert.NotSame(t, fw, f.firewall, "firewall pointer should have been replaced")
- assert.Equal(t, newUnsafe, f.firewall.unsafeNetworks)
- assert.True(t, f.firewall.routableNetworks.Contains(netip.MustParseAddr("203.0.113.5")))
- }
- // TestReloadFirewall_NoChange verifies that reloadFirewall is a no-op when
- // neither the firewall config nor the cert's UnsafeNetworks have changed.
- func TestReloadFirewall_NoChange(t *testing.T) {
- l := test.NewLogger()
- vpnNet := netip.MustParsePrefix("10.0.0.1/24")
- unsafe := []netip.Prefix{netip.MustParsePrefix("198.51.100.0/24")}
- c1 := &dummyCert{
- version: cert.Version2,
- networks: []netip.Prefix{vpnNet},
- unsafeNetworks: unsafe,
- }
- pki := &PKI{}
- pki.cs.Store(&CertState{v2Cert: c1, initiatingVersion: cert.Version2})
- rawYAML := `firewall:
- outbound:
- - port: any
- proto: any
- host: any
- inbound:
- - port: any
- proto: any
- host: any
- `
- cfg := config.NewC(l)
- require.NoError(t, cfg.LoadString(rawYAML))
- fw, err := NewFirewallFromConfig(l, pki.getCertState(), cfg)
- require.NoError(t, err)
- f := &Interface{
- pki: pki,
- firewall: fw,
- l: l,
- }
- require.NoError(t, cfg.ReloadConfigString(rawYAML).Err)
- f.reloadFirewall(cfg, nil)
- assert.Same(t, fw, f.firewall, "firewall should not have been replaced")
- }
- // Rules that don't parse keep the old firewall, and the reload says it could not apply them
- func TestReloadFirewall_BadRulesFailTheReload(t *testing.T) {
- l := test.NewLogger()
- pki := &PKI{}
- pki.cs.Store(&CertState{v2Cert: &dummyCert{version: cert.Version2, networks: []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")}}, initiatingVersion: cert.Version2})
- rules := func(proto string) string {
- 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"
- }
- cfg := config.NewC(l)
- require.NoError(t, cfg.LoadString(rules("any")))
- fw, err := NewFirewallFromConfig(l, pki.getCertState(), cfg)
- require.NoError(t, err)
- f := &Interface{pki: pki, firewall: fw, l: l}
- cfg.RegisterReloadCallback(f.reloadFirewall)
- // The running firewall still isn't built from what the config asks for, so the same reload fails again
- for range 2 {
- res := cfg.ReloadConfigString(rules("nope"))
- require.NoError(t, res.Err)
- assert.True(t, res.Failed)
- assert.Same(t, fw, f.firewall)
- }
- res := cfg.ReloadConfigString(rules("tcp"))
- require.NoError(t, res.Err)
- assert.False(t, res.Failed)
- assert.NotSame(t, fw, f.firewall)
- }
- // Every input the firewall is built from rebuilds it when it changes, and nothing else does
- func TestReloadFirewall_RebuildsOnAnyInput(t *testing.T) {
- l := test.NewLogger()
- networks := []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")}
- base := "firewall:\n inbound:\n - port: any\n proto: any\n host: any\n"
- setup := func() (*config.C, *Interface, *PKI) {
- pki := &PKI{}
- pki.cs.Store(&CertState{v2Cert: &dummyCert{version: cert.Version2, networks: networks}, initiatingVersion: cert.Version2})
- cfg := config.NewC(l)
- require.NoError(t, cfg.LoadString(base))
- fw, err := NewFirewallFromConfig(l, pki.getCertState(), cfg)
- require.NoError(t, err)
- f := &Interface{pki: pki, firewall: fw, l: l}
- cfg.RegisterReloadCallback(f.reloadFirewall)
- return cfg, f, pki
- }
- setCert := func(pki *PKI, networks, unsafe []netip.Prefix) {
- pki.cs.Store(&CertState{v2Cert: &dummyCert{version: cert.Version2, networks: networks, unsafeNetworks: unsafe}, initiatingVersion: cert.Version2})
- }
- cases := map[string]struct {
- cfg string
- cert func(*PKI)
- want bool
- }{
- "nothing": {base + "punchy:\n punch: true\n", nil, false},
- "rules": {base + " outbound:\n - port: any\n proto: any\n host: any\n", nil, true},
- "inbound_action": {base + " inbound_action: reject\n", nil, true},
- "conntrack": {base + " conntrack:\n tcp_timeout: 1m\n", nil, true},
- "networks": {base, func(p *PKI) { setCert(p, []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")}, nil) }, true},
- "unsafe_networks": {base, func(p *PKI) { setCert(p, networks, []netip.Prefix{netip.MustParsePrefix("192.168.0.0/24")}) }, true},
- }
- for name, tc := range cases {
- t.Run(name, func(t *testing.T) {
- cfg, f, pki := setup()
- old := f.firewall
- if tc.cert != nil {
- tc.cert(pki)
- }
- res := cfg.ReloadConfigString(tc.cfg)
- require.NoError(t, res.Err)
- assert.False(t, res.Failed)
- assert.Equal(t, tc.want, f.firewall != old)
- })
- }
- }
|