pkg/: FEATURE: support allowed IPs outside a cluster

Users can specify IPs with the annotation "allowed-location-ips".
It makes no difference which node of a location is annotated.
The IP should be routable from the particular location, e.g. a printer in
the same LAN.
This way these IPs become routable from other location.

Signed-off-by: leonnicolas <leonloechner@gmx.de>

Co-authored-by: Lucas Servén Marín <lserven@gmail.com>
This commit is contained in:
leonnicolas
2021-05-27 09:01:22 +02:00
parent 6542c2ee94
commit 31ffaa0e71
9 changed files with 368 additions and 26 deletions

View File

@@ -19,6 +19,7 @@ import (
"strings"
"testing"
"github.com/go-kit/kit/log"
"github.com/kylelemons/godebug/pretty"
"github.com/squat/kilo/pkg/wireguard"
@@ -28,6 +29,15 @@ func allowedIPs(ips ...string) string {
return strings.Join(ips, ", ")
}
func mustParseCIDR(s string) (r *net.IPNet) {
if _, ip, err := net.ParseCIDR(s); err != nil {
panic("failed to parse CIDR")
} else {
r = ip
}
return
}
func setup(t *testing.T) (map[string]*Node, map[string]*Peer, []byte, uint32) {
key := []byte("private")
e1 := &net.IPNet{IP: net.ParseIP("10.1.0.1").To4(), Mask: net.CIDRMask(16, 32)}
@@ -36,6 +46,7 @@ func setup(t *testing.T) (map[string]*Node, map[string]*Peer, []byte, uint32) {
e4 := &net.IPNet{IP: net.ParseIP("10.1.0.4").To4(), Mask: net.CIDRMask(16, 32)}
i1 := &net.IPNet{IP: net.ParseIP("192.168.0.1").To4(), Mask: net.CIDRMask(32, 32)}
i2 := &net.IPNet{IP: net.ParseIP("192.168.0.2").To4(), Mask: net.CIDRMask(32, 32)}
i3 := &net.IPNet{IP: net.ParseIP("192.168.178.3").To4(), Mask: net.CIDRMask(32, 32)}
nodes := map[string]*Node{
"a": {
Name: "a",
@@ -47,12 +58,13 @@ func setup(t *testing.T) (map[string]*Node, map[string]*Peer, []byte, uint32) {
PersistentKeepalive: 25,
},
"b": {
Name: "b",
Endpoint: &wireguard.Endpoint{DNSOrIP: wireguard.DNSOrIP{IP: e2.IP}, Port: DefaultKiloPort},
InternalIP: i1,
Location: "2",
Subnet: &net.IPNet{IP: net.ParseIP("10.2.2.0"), Mask: net.CIDRMask(24, 32)},
Key: []byte("key2"),
Name: "b",
Endpoint: &wireguard.Endpoint{DNSOrIP: wireguard.DNSOrIP{IP: e2.IP}, Port: DefaultKiloPort},
InternalIP: i1,
Location: "2",
Subnet: &net.IPNet{IP: net.ParseIP("10.2.2.0"), Mask: net.CIDRMask(24, 32)},
Key: []byte("key2"),
AllowedLocationIPs: []*net.IPNet{i3},
},
"c": {
Name: "c",
@@ -146,6 +158,7 @@ func TestNewTopology(t *testing.T) {
hostnames: []string{"b", "c"},
privateIPs: []net.IP{nodes["b"].InternalIP.IP, nodes["c"].InternalIP.IP},
wireGuardIP: w2,
allowedLocationIPs: nodes["b"].AllowedLocationIPs,
},
{
allowedIPs: []*net.IPNet{nodes["d"].Subnet, {IP: w3, Mask: net.CIDRMask(32, 32)}},
@@ -159,7 +172,8 @@ func TestNewTopology(t *testing.T) {
wireGuardIP: w3,
},
},
peers: []*Peer{peers["a"], peers["b"]},
peers: []*Peer{peers["a"], peers["b"]},
logger: log.NewNopLogger(),
},
},
{
@@ -195,6 +209,7 @@ func TestNewTopology(t *testing.T) {
hostnames: []string{"b", "c"},
privateIPs: []net.IP{nodes["b"].InternalIP.IP, nodes["c"].InternalIP.IP},
wireGuardIP: w2,
allowedLocationIPs: nodes["b"].AllowedLocationIPs,
},
{
allowedIPs: []*net.IPNet{nodes["d"].Subnet, {IP: w3, Mask: net.CIDRMask(32, 32)}},
@@ -208,7 +223,8 @@ func TestNewTopology(t *testing.T) {
wireGuardIP: w3,
},
},
peers: []*Peer{peers["a"], peers["b"]},
peers: []*Peer{peers["a"], peers["b"]},
logger: log.NewNopLogger(),
},
},
{
@@ -244,6 +260,7 @@ func TestNewTopology(t *testing.T) {
hostnames: []string{"b", "c"},
privateIPs: []net.IP{nodes["b"].InternalIP.IP, nodes["c"].InternalIP.IP},
wireGuardIP: w2,
allowedLocationIPs: nodes["b"].AllowedLocationIPs,
},
{
allowedIPs: []*net.IPNet{nodes["d"].Subnet, {IP: w3, Mask: net.CIDRMask(32, 32)}},
@@ -257,7 +274,8 @@ func TestNewTopology(t *testing.T) {
wireGuardIP: w3,
},
},
peers: []*Peer{peers["a"], peers["b"]},
peers: []*Peer{peers["a"], peers["b"]},
logger: log.NewNopLogger(),
},
},
{
@@ -293,6 +311,7 @@ func TestNewTopology(t *testing.T) {
hostnames: []string{"b"},
privateIPs: []net.IP{nodes["b"].InternalIP.IP},
wireGuardIP: w2,
allowedLocationIPs: nodes["b"].AllowedLocationIPs,
},
{
allowedIPs: []*net.IPNet{nodes["c"].Subnet, nodes["c"].InternalIP, {IP: w3, Mask: net.CIDRMask(32, 32)}},
@@ -317,7 +336,8 @@ func TestNewTopology(t *testing.T) {
wireGuardIP: w4,
},
},
peers: []*Peer{peers["a"], peers["b"]},
peers: []*Peer{peers["a"], peers["b"]},
logger: log.NewNopLogger(),
},
},
{
@@ -353,6 +373,7 @@ func TestNewTopology(t *testing.T) {
hostnames: []string{"b"},
privateIPs: []net.IP{nodes["b"].InternalIP.IP},
wireGuardIP: w2,
allowedLocationIPs: nodes["b"].AllowedLocationIPs,
},
{
allowedIPs: []*net.IPNet{nodes["c"].Subnet, nodes["c"].InternalIP, {IP: w3, Mask: net.CIDRMask(32, 32)}},
@@ -377,7 +398,8 @@ func TestNewTopology(t *testing.T) {
wireGuardIP: w4,
},
},
peers: []*Peer{peers["a"], peers["b"]},
peers: []*Peer{peers["a"], peers["b"]},
logger: log.NewNopLogger(),
},
},
{
@@ -413,6 +435,7 @@ func TestNewTopology(t *testing.T) {
hostnames: []string{"b"},
privateIPs: []net.IP{nodes["b"].InternalIP.IP},
wireGuardIP: w2,
allowedLocationIPs: nodes["b"].AllowedLocationIPs,
},
{
allowedIPs: []*net.IPNet{nodes["c"].Subnet, nodes["c"].InternalIP, {IP: w3, Mask: net.CIDRMask(32, 32)}},
@@ -437,7 +460,8 @@ func TestNewTopology(t *testing.T) {
wireGuardIP: w4,
},
},
peers: []*Peer{peers["a"], peers["b"]},
peers: []*Peer{peers["a"], peers["b"]},
logger: log.NewNopLogger(),
},
},
{
@@ -473,6 +497,7 @@ func TestNewTopology(t *testing.T) {
hostnames: []string{"b"},
privateIPs: []net.IP{nodes["b"].InternalIP.IP},
wireGuardIP: w2,
allowedLocationIPs: nodes["b"].AllowedLocationIPs,
},
{
allowedIPs: []*net.IPNet{nodes["c"].Subnet, nodes["c"].InternalIP, {IP: w3, Mask: net.CIDRMask(32, 32)}},
@@ -497,13 +522,14 @@ func TestNewTopology(t *testing.T) {
wireGuardIP: w4,
},
},
peers: []*Peer{peers["a"], peers["b"]},
peers: []*Peer{peers["a"], peers["b"]},
logger: log.NewNopLogger(),
},
},
} {
tc.result.key = key
tc.result.port = port
topo, err := NewTopology(nodes, peers, tc.granularity, tc.hostname, port, key, DefaultKiloSubnet, 0)
topo, err := NewTopology(nodes, peers, tc.granularity, tc.hostname, port, key, DefaultKiloSubnet, 0, nil)
if err != nil {
t.Errorf("test case %q: failed to generate Topology: %v", tc.name, err)
}
@@ -514,7 +540,7 @@ func TestNewTopology(t *testing.T) {
}
func mustTopo(t *testing.T, nodes map[string]*Node, peers map[string]*Peer, granularity Granularity, hostname string, port uint32, key []byte, subnet *net.IPNet, persistentKeepalive int) *Topology {
topo, err := NewTopology(nodes, peers, granularity, hostname, port, key, subnet, persistentKeepalive)
topo, err := NewTopology(nodes, peers, granularity, hostname, port, key, subnet, persistentKeepalive, nil)
if err != nil {
t.Errorf("failed to generate Topology: %v", err)
}
@@ -538,7 +564,7 @@ ListenPort = 51820
[Peer]
PublicKey = key2
Endpoint = 10.1.0.2:51820
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32, 192.168.178.3/32
PersistentKeepalive = 25
[Peer]
@@ -623,7 +649,7 @@ PersistentKeepalive = 25
[Peer]
PublicKey = key2
Endpoint = 10.1.0.2:51820
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.4.0.2/32
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.4.0.2/32, 192.168.178.3/32
PersistentKeepalive = 25
[Peer]
@@ -697,7 +723,7 @@ PersistentKeepalive = 25
[Peer]
PublicKey = key2
Endpoint = 10.1.0.2:51820
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.4.0.2/32
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.4.0.2/32, 192.168.178.3/32
[Peer]
PublicKey = key4
@@ -953,3 +979,133 @@ func TestDeduplicatePeerIPs(t *testing.T) {
}
}
}
func TestFilterAllowedIPs(t *testing.T) {
nodes, peers, key, port := setup(t)
topo := mustTopo(t, nodes, peers, LogicalGranularity, nodes["a"].Name, port, key, DefaultKiloSubnet, nodes["a"].PersistentKeepalive)
for _, tc := range []struct {
name string
allowedLocationIPs map[int][]*net.IPNet
result map[int][]*net.IPNet
}{
{
name: "nothing to filter",
allowedLocationIPs: map[int][]*net.IPNet{
0: {
mustParseCIDR("192.168.178.4/32"),
},
1: {
mustParseCIDR("192.168.178.5/32"),
},
2: {
mustParseCIDR("192.168.178.6/32"),
mustParseCIDR("192.168.178.7/32"),
},
},
result: map[int][]*net.IPNet{
0: {
mustParseCIDR("192.168.178.4/32"),
},
1: {
mustParseCIDR("192.168.178.5/32"),
},
2: {
mustParseCIDR("192.168.178.6/32"),
mustParseCIDR("192.168.178.7/32"),
},
},
},
{
name: "intersections between segments",
allowedLocationIPs: map[int][]*net.IPNet{
0: {
mustParseCIDR("192.168.178.4/32"),
mustParseCIDR("192.168.178.8/32"),
},
1: {
mustParseCIDR("192.168.178.5/32"),
},
2: {
mustParseCIDR("192.168.178.6/32"),
mustParseCIDR("192.168.178.7/32"),
mustParseCIDR("192.168.178.4/32"),
},
},
result: map[int][]*net.IPNet{
0: {
mustParseCIDR("192.168.178.8/32"),
},
1: {
mustParseCIDR("192.168.178.5/32"),
},
2: {
mustParseCIDR("192.168.178.6/32"),
mustParseCIDR("192.168.178.7/32"),
mustParseCIDR("192.168.178.4/32"),
},
},
},
{
name: "intersections with wireGuardCIDR",
allowedLocationIPs: map[int][]*net.IPNet{
0: {
mustParseCIDR("10.4.0.1/32"),
mustParseCIDR("192.168.178.8/32"),
},
1: {
mustParseCIDR("192.168.178.5/32"),
},
2: {
mustParseCIDR("192.168.178.6/32"),
mustParseCIDR("192.168.178.7/32"),
},
},
result: map[int][]*net.IPNet{
0: {
mustParseCIDR("192.168.178.8/32"),
},
1: {
mustParseCIDR("192.168.178.5/32"),
},
2: {
mustParseCIDR("192.168.178.6/32"),
mustParseCIDR("192.168.178.7/32"),
},
},
},
{
name: "intersections with more than one allowedLocationIPs",
allowedLocationIPs: map[int][]*net.IPNet{
0: {
mustParseCIDR("192.168.178.8/32"),
},
1: {
mustParseCIDR("192.168.178.5/32"),
},
2: {
mustParseCIDR("192.168.178.7/24"),
},
},
result: map[int][]*net.IPNet{
0: {},
1: {},
2: {
mustParseCIDR("192.168.178.7/24"),
},
},
},
} {
for k, v := range tc.allowedLocationIPs {
topo.segments[k].allowedLocationIPs = v
}
for k, v := range topo.segments {
f := topo.filterAllowedLocationIPs(v.allowedLocationIPs, v.location)
// Overwrite the allowedLocationIPs to mimic the actual usage of the filterAllowedLocationIPs function.
topo.segments[k].allowedLocationIPs = f
if !ipNetSlicesEqual(f, tc.result[k]) {
t.Errorf("test case %q:\n\texpected:\n\t%q\n\tgot:\n\t%q\n", tc.name, tc.result[k], f)
}
}
}
}