mirror of
https://github.com/slackhq/nebula.git
synced 2025-12-06 02:30:57 -08:00
Merge f8a9286d08 into 0f305d5397
This commit is contained in:
commit
d86d4010b3
3 changed files with 153 additions and 2 deletions
|
|
@ -369,6 +369,13 @@ firewall:
|
||||||
# is explicitly defined. This is usually not the desired behavior and should be avoided!
|
# is explicitly defined. This is usually not the desired behavior and should be avoided!
|
||||||
#default_local_cidr_any: false
|
#default_local_cidr_any: false
|
||||||
|
|
||||||
|
# Allow one Nebula peer to route packets to another peer if they can both handle the destination address or the
|
||||||
|
# source address. By default, Nebula only allows a packet through a firewall if the sending peer can handle the
|
||||||
|
# source address and the receiving peer can handle the destination address, but in case where you'd want multi-hop
|
||||||
|
# routing, you can enable this option to relax the rule slightly.
|
||||||
|
# This option is needed for each peer that wants to route traffic in this way, but it's not needed for other hosts.
|
||||||
|
#unsafe_peer_routing: false
|
||||||
|
|
||||||
conntrack:
|
conntrack:
|
||||||
tcp_timeout: 12m
|
tcp_timeout: 12m
|
||||||
udp_timeout: 3m
|
udp_timeout: 3m
|
||||||
|
|
|
||||||
13
firewall.go
13
firewall.go
|
|
@ -63,6 +63,7 @@ type Firewall struct {
|
||||||
rulesVersion uint16
|
rulesVersion uint16
|
||||||
|
|
||||||
defaultLocalCIDRAny bool
|
defaultLocalCIDRAny bool
|
||||||
|
unsafePeerRouting bool
|
||||||
incomingMetrics firewallMetrics
|
incomingMetrics firewallMetrics
|
||||||
outgoingMetrics firewallMetrics
|
outgoingMetrics firewallMetrics
|
||||||
|
|
||||||
|
|
@ -210,6 +211,7 @@ func NewFirewallFromConfig(l *logrus.Logger, cs *CertState, c *config.C) (*Firew
|
||||||
)
|
)
|
||||||
|
|
||||||
fw.defaultLocalCIDRAny = c.GetBool("firewall.default_local_cidr_any", false)
|
fw.defaultLocalCIDRAny = c.GetBool("firewall.default_local_cidr_any", false)
|
||||||
|
fw.unsafePeerRouting = c.GetBool("firewall.unsafe_peer_routing", false)
|
||||||
|
|
||||||
inboundAction := c.GetString("firewall.inbound_action", "drop")
|
inboundAction := c.GetString("firewall.inbound_action", "drop")
|
||||||
switch inboundAction {
|
switch inboundAction {
|
||||||
|
|
@ -431,7 +433,10 @@ func (f *Firewall) Drop(fp firewall.Packet, incoming bool, h *HostInfo, caPool *
|
||||||
|
|
||||||
// Make sure remote address matches nebula certificate
|
// Make sure remote address matches nebula certificate
|
||||||
if h.networks != nil {
|
if h.networks != nil {
|
||||||
if !h.networks.Contains(fp.RemoteAddr) {
|
// In case where `unsafe-peer-routing` is enabled, we also accept the packet if we only can handle LocalAddr.
|
||||||
|
// This is to support multi-hop routing via Nebula, e.g. a peer is fowarding an ingress traffic to us.
|
||||||
|
if !(h.networks.Contains(fp.RemoteAddr) ||
|
||||||
|
f.unsafePeerRouting && h.networks.Contains(fp.LocalAddr)) {
|
||||||
f.metrics(incoming).droppedRemoteAddr.Inc(1)
|
f.metrics(incoming).droppedRemoteAddr.Inc(1)
|
||||||
return ErrInvalidRemoteIP
|
return ErrInvalidRemoteIP
|
||||||
}
|
}
|
||||||
|
|
@ -444,7 +449,11 @@ func (f *Firewall) Drop(fp firewall.Packet, incoming bool, h *HostInfo, caPool *
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure we are supposed to be handling this local ip address
|
// Make sure we are supposed to be handling this local ip address
|
||||||
if !f.routableNetworks.Contains(fp.LocalAddr) {
|
//
|
||||||
|
// In case where `unsafe-peer-routing` is enabled, we also accept the packet if we can only handle RemoteAddr.
|
||||||
|
// This is to support multi-hop routing via Nebula, e.g. we are forwarding an ingress traffic to a peer.
|
||||||
|
if !(f.routableNetworks.Contains(fp.LocalAddr) ||
|
||||||
|
f.unsafePeerRouting && f.routableNetworks.Contains(fp.RemoteAddr)) {
|
||||||
f.metrics(incoming).droppedLocalAddr.Inc(1)
|
f.metrics(incoming).droppedLocalAddr.Inc(1)
|
||||||
return ErrInvalidLocalIP
|
return ErrInvalidLocalIP
|
||||||
}
|
}
|
||||||
|
|
|
||||||
135
firewall_test.go
135
firewall_test.go
|
|
@ -736,6 +736,141 @@ func TestFirewall_DropIPSpoofing(t *testing.T) {
|
||||||
assert.Equal(t, fw.Drop(p, true, &h1, cp, nil), ErrInvalidRemoteIP)
|
assert.Equal(t, fw.Drop(p, true, &h1, cp, nil), ErrInvalidRemoteIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFirewall_DropUnsafePeerRouting(t *testing.T) {
|
||||||
|
l := test.NewLogger()
|
||||||
|
ob := &bytes.Buffer{}
|
||||||
|
l.SetOutput(ob)
|
||||||
|
|
||||||
|
anyNetwork := netip.MustParsePrefix("0.0.0.0/0")
|
||||||
|
unsafeNetwork := netip.MustParsePrefix("198.51.100.0/24")
|
||||||
|
|
||||||
|
c := cert.CachedCertificate{
|
||||||
|
Certificate: &dummyCert{
|
||||||
|
name: "host-owner",
|
||||||
|
networks: []netip.Prefix{netip.MustParsePrefix("192.0.2.1/24")},
|
||||||
|
unsafeNetworks: []netip.Prefix{unsafeNetwork},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a peer that we want to route to/from.
|
||||||
|
c1 := cert.CachedCertificate{
|
||||||
|
Certificate: &dummyCert{
|
||||||
|
name: "peer",
|
||||||
|
networks: []netip.Prefix{netip.MustParsePrefix("192.0.2.2/24")},
|
||||||
|
unsafeNetworks: []netip.Prefix{unsafeNetwork},
|
||||||
|
issuer: "signer-sha",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
h1 := HostInfo{
|
||||||
|
ConnectionState: &ConnectionState{
|
||||||
|
peerCert: &c1,
|
||||||
|
},
|
||||||
|
vpnAddrs: []netip.Addr{c1.Certificate.Networks()[0].Addr()},
|
||||||
|
}
|
||||||
|
h1.buildNetworks(c1.Certificate.Networks(), c1.Certificate.UnsafeNetworks())
|
||||||
|
|
||||||
|
// This is another host in the Nebula network.
|
||||||
|
c2 := cert.CachedCertificate{
|
||||||
|
Certificate: &dummyCert{
|
||||||
|
name: "host",
|
||||||
|
networks: []netip.Prefix{netip.MustParsePrefix("192.0.2.3/24")},
|
||||||
|
unsafeNetworks: []netip.Prefix{},
|
||||||
|
issuer: "signer-sha",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
h2 := HostInfo{
|
||||||
|
ConnectionState: &ConnectionState{
|
||||||
|
peerCert: &c2,
|
||||||
|
},
|
||||||
|
vpnAddrs: []netip.Addr{c2.Certificate.Networks()[0].Addr()},
|
||||||
|
}
|
||||||
|
h2.buildNetworks(c2.Certificate.Networks(), c2.Certificate.UnsafeNetworks())
|
||||||
|
|
||||||
|
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
||||||
|
fw2 := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
||||||
|
fw2.unsafePeerRouting = true
|
||||||
|
|
||||||
|
// Add firewall rules. Due to `default_local_cidr_any` we need to explicitly add CIDR for unsafe network.
|
||||||
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "", netip.Prefix{}, anyNetwork, "", ""))
|
||||||
|
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, "", netip.Prefix{}, anyNetwork, "", ""))
|
||||||
|
require.NoError(t, fw2.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "", netip.Prefix{}, anyNetwork, "", ""))
|
||||||
|
require.NoError(t, fw2.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, "", netip.Prefix{}, anyNetwork, "", ""))
|
||||||
|
cp := cert.NewCAPool()
|
||||||
|
|
||||||
|
// Packet initially send from `host` to us should pass both config.
|
||||||
|
p := firewall.Packet{
|
||||||
|
LocalAddr: netip.MustParseAddr("198.51.100.42"),
|
||||||
|
RemoteAddr: netip.MustParseAddr("192.0.2.3"),
|
||||||
|
LocalPort: 1,
|
||||||
|
RemotePort: 1,
|
||||||
|
Protocol: firewall.ProtoUDP,
|
||||||
|
Fragment: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, fw.Drop(p, true, &h2, cp, nil))
|
||||||
|
require.NoError(t, fw2.Drop(p, true, &h2, cp, nil))
|
||||||
|
|
||||||
|
pRev := firewall.Packet{
|
||||||
|
LocalAddr: netip.MustParseAddr("192.0.2.3"),
|
||||||
|
RemoteAddr: netip.MustParseAddr("198.51.100.42"),
|
||||||
|
LocalPort: 1,
|
||||||
|
RemotePort: 1,
|
||||||
|
Protocol: firewall.ProtoUDP,
|
||||||
|
Fragment: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forward to `peer`, should only pass `fw2`.
|
||||||
|
assert.Equal(t, fw.Drop(pRev, false, &h1, cp, nil), ErrInvalidLocalIP)
|
||||||
|
require.NoError(t, fw2.Drop(pRev, false, &h1, cp, nil))
|
||||||
|
|
||||||
|
// Now let's test receiving end, check when the packet is received via `peer`.
|
||||||
|
resetConntrack(fw)
|
||||||
|
resetConntrack(fw2)
|
||||||
|
assert.Equal(t, fw.Drop(p, true, &h1, cp, nil), ErrInvalidRemoteIP)
|
||||||
|
require.NoError(t, fw2.Drop(p, true, &h1, cp, nil))
|
||||||
|
|
||||||
|
// The reverse direction, forward traffic to `peer` for it to reach `host`.
|
||||||
|
assert.Equal(t, fw.Drop(p, false, &h1, cp, nil), ErrInvalidRemoteIP)
|
||||||
|
require.NoError(t, fw2.Drop(p, false, &h1, cp, nil))
|
||||||
|
|
||||||
|
// Now let's test the receving end of the reverse direction, check when he packet is received via `peer`.
|
||||||
|
resetConntrack(fw)
|
||||||
|
resetConntrack(fw2)
|
||||||
|
assert.Equal(t, fw.Drop(pRev, true, &h1, cp, nil), ErrInvalidLocalIP)
|
||||||
|
require.NoError(t, fw2.Drop(pRev, true, &h1, cp, nil))
|
||||||
|
|
||||||
|
// Final reply to `host`. This time it's allowed regardless the config.
|
||||||
|
require.NoError(t, fw.Drop(p, false, &h2, cp, nil))
|
||||||
|
require.NoError(t, fw2.Drop(p, false, &h2, cp, nil))
|
||||||
|
|
||||||
|
// Try some cases where the address is *not* covered by certificate and ensure they're rejected.
|
||||||
|
p = firewall.Packet{
|
||||||
|
LocalAddr: netip.MustParseAddr("203.0.113.42"),
|
||||||
|
RemoteAddr: netip.MustParseAddr("192.0.2.3"),
|
||||||
|
LocalPort: 1,
|
||||||
|
RemotePort: 1,
|
||||||
|
Protocol: firewall.ProtoUDP,
|
||||||
|
Fragment: false,
|
||||||
|
}
|
||||||
|
pRev = firewall.Packet{
|
||||||
|
LocalAddr: netip.MustParseAddr("192.0.2.3"),
|
||||||
|
RemoteAddr: netip.MustParseAddr("203.0.113.42"),
|
||||||
|
LocalPort: 1,
|
||||||
|
RemotePort: 1,
|
||||||
|
Protocol: firewall.ProtoUDP,
|
||||||
|
Fragment: false,
|
||||||
|
}
|
||||||
|
resetConntrack(fw2)
|
||||||
|
assert.Equal(t, fw2.Drop(p, true, &h2, cp, nil), ErrInvalidLocalIP)
|
||||||
|
assert.Equal(t, fw2.Drop(pRev, false, &h1, cp, nil), ErrInvalidRemoteIP)
|
||||||
|
resetConntrack(fw2)
|
||||||
|
assert.Equal(t, fw2.Drop(p, true, &h1, cp, nil), ErrInvalidRemoteIP)
|
||||||
|
assert.Equal(t, fw2.Drop(p, false, &h1, cp, nil), ErrInvalidRemoteIP)
|
||||||
|
resetConntrack(fw2)
|
||||||
|
assert.Equal(t, fw.Drop(pRev, true, &h1, cp, nil), ErrInvalidRemoteIP)
|
||||||
|
assert.Equal(t, fw.Drop(p, false, &h2, cp, nil), ErrInvalidLocalIP)
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkLookup(b *testing.B) {
|
func BenchmarkLookup(b *testing.B) {
|
||||||
ml := func(m map[string]struct{}, a [][]string) {
|
ml := func(m map[string]struct{}, a [][]string) {
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue