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!
|
||||
#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:
|
||||
tcp_timeout: 12m
|
||||
udp_timeout: 3m
|
||||
|
|
|
|||
13
firewall.go
13
firewall.go
|
|
@ -63,6 +63,7 @@ type Firewall struct {
|
|||
rulesVersion uint16
|
||||
|
||||
defaultLocalCIDRAny bool
|
||||
unsafePeerRouting bool
|
||||
incomingMetrics 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.unsafePeerRouting = c.GetBool("firewall.unsafe_peer_routing", false)
|
||||
|
||||
inboundAction := c.GetString("firewall.inbound_action", "drop")
|
||||
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
|
||||
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)
|
||||
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
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
ml := func(m map[string]struct{}, a [][]string) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue