mirror of
https://github.com/slackhq/nebula.git
synced 2026-11-15 06:06:52 +01:00
198 lines
6.3 KiB
Go
198 lines
6.3 KiB
Go
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)
|
|
})
|
|
}
|
|
}
|