mirror of
https://github.com/juanfont/headscale.git
synced 2026-10-03 05:13:36 +09:00
@@ -8,30 +8,8 @@ import (
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/rs/zerolog/log"
|
||||
"tailscale.com/types/views"
|
||||
)
|
||||
|
||||
// ReduceNodes returns the list of peers authorized to be accessed from a given node.
|
||||
func ReduceNodes(
|
||||
node types.NodeView,
|
||||
nodes views.Slice[types.NodeView],
|
||||
matchers []matcher.Match,
|
||||
) views.Slice[types.NodeView] {
|
||||
var result []types.NodeView
|
||||
|
||||
for _, peer := range nodes.All() {
|
||||
if peer.ID() == node.ID() {
|
||||
continue
|
||||
}
|
||||
|
||||
if node.CanAccess(matchers, peer) || peer.CanAccess(matchers, node) {
|
||||
result = append(result, peer)
|
||||
}
|
||||
}
|
||||
|
||||
return views.SliceOf(result)
|
||||
}
|
||||
|
||||
// ReduceRoutes returns a reduced list of routes for a given node that it can access.
|
||||
func ReduceRoutes(
|
||||
node types.NodeView,
|
||||
|
||||
@@ -24,836 +24,7 @@ var p = func(prefStr string) netip.Prefix {
|
||||
return ip
|
||||
}
|
||||
|
||||
func TestReduceNodes(t *testing.T) {
|
||||
type args struct {
|
||||
nodes types.Nodes
|
||||
rules []tailcfg.FilterRule
|
||||
node *types.Node
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want types.Nodes
|
||||
}{
|
||||
{
|
||||
name: "all hosts can talk to each other",
|
||||
args: args{
|
||||
nodes: types.Nodes{ // list of all nodes in the database
|
||||
&types.Node{
|
||||
ID: 1,
|
||||
IPv4: ap("100.64.0.1"),
|
||||
User: &types.User{Name: "joe"},
|
||||
},
|
||||
&types.Node{
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
User: &types.User{Name: "marc"},
|
||||
},
|
||||
&types.Node{
|
||||
ID: 3,
|
||||
IPv4: ap("100.64.0.3"),
|
||||
User: &types.User{Name: "mickael"},
|
||||
},
|
||||
},
|
||||
rules: []tailcfg.FilterRule{
|
||||
{
|
||||
SrcIPs: []string{"100.64.0.1", "100.64.0.2", "100.64.0.3"},
|
||||
DstPorts: []tailcfg.NetPortRange{
|
||||
{IP: "*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
node: &types.Node{ // current nodes
|
||||
ID: 1,
|
||||
IPv4: ap("100.64.0.1"),
|
||||
User: &types.User{Name: "joe"},
|
||||
},
|
||||
},
|
||||
want: types.Nodes{
|
||||
&types.Node{
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
User: &types.User{Name: "marc"},
|
||||
},
|
||||
&types.Node{
|
||||
ID: 3,
|
||||
IPv4: ap("100.64.0.3"),
|
||||
User: &types.User{Name: "mickael"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "One host can talk to another, but not all hosts",
|
||||
args: args{
|
||||
nodes: types.Nodes{ // list of all nodes in the database
|
||||
&types.Node{
|
||||
ID: 1,
|
||||
IPv4: ap("100.64.0.1"),
|
||||
User: &types.User{Name: "joe"},
|
||||
},
|
||||
&types.Node{
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
User: &types.User{Name: "marc"},
|
||||
},
|
||||
&types.Node{
|
||||
ID: 3,
|
||||
IPv4: ap("100.64.0.3"),
|
||||
User: &types.User{Name: "mickael"},
|
||||
},
|
||||
},
|
||||
rules: []tailcfg.FilterRule{ // list of all ACLRules registered
|
||||
{
|
||||
SrcIPs: []string{"100.64.0.1", "100.64.0.2", "100.64.0.3"},
|
||||
DstPorts: []tailcfg.NetPortRange{
|
||||
{IP: "100.64.0.2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
node: &types.Node{ // current nodes
|
||||
ID: 1,
|
||||
IPv4: ap("100.64.0.1"),
|
||||
User: &types.User{Name: "joe"},
|
||||
},
|
||||
},
|
||||
want: types.Nodes{
|
||||
&types.Node{
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
User: &types.User{Name: "marc"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "host cannot directly talk to destination, but return path is authorized",
|
||||
args: args{
|
||||
nodes: types.Nodes{ // list of all nodes in the database
|
||||
&types.Node{
|
||||
ID: 1,
|
||||
IPv4: ap("100.64.0.1"),
|
||||
User: &types.User{Name: "joe"},
|
||||
},
|
||||
&types.Node{
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
User: &types.User{Name: "marc"},
|
||||
},
|
||||
&types.Node{
|
||||
ID: 3,
|
||||
IPv4: ap("100.64.0.3"),
|
||||
User: &types.User{Name: "mickael"},
|
||||
},
|
||||
},
|
||||
rules: []tailcfg.FilterRule{ // list of all ACLRules registered
|
||||
{
|
||||
SrcIPs: []string{"100.64.0.3"},
|
||||
DstPorts: []tailcfg.NetPortRange{
|
||||
{IP: "100.64.0.2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
node: &types.Node{ // current nodes
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
User: &types.User{Name: "marc"},
|
||||
},
|
||||
},
|
||||
want: types.Nodes{
|
||||
&types.Node{
|
||||
ID: 3,
|
||||
IPv4: ap("100.64.0.3"),
|
||||
User: &types.User{Name: "mickael"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "rules allows all hosts to reach one destination",
|
||||
args: args{
|
||||
nodes: types.Nodes{ // list of all nodes in the database
|
||||
&types.Node{
|
||||
ID: 1,
|
||||
IPv4: ap("100.64.0.1"),
|
||||
User: &types.User{Name: "joe"},
|
||||
},
|
||||
&types.Node{
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
User: &types.User{Name: "marc"},
|
||||
},
|
||||
&types.Node{
|
||||
ID: 3,
|
||||
IPv4: ap("100.64.0.3"),
|
||||
User: &types.User{Name: "mickael"},
|
||||
},
|
||||
},
|
||||
rules: []tailcfg.FilterRule{ // list of all ACLRules registered
|
||||
{
|
||||
SrcIPs: []string{"*"},
|
||||
DstPorts: []tailcfg.NetPortRange{
|
||||
{IP: "100.64.0.2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
node: &types.Node{ // current nodes
|
||||
ID: 1,
|
||||
IPv4: ap("100.64.0.1"),
|
||||
User: &types.User{Name: "joe"},
|
||||
},
|
||||
},
|
||||
want: types.Nodes{
|
||||
&types.Node{
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
User: &types.User{Name: "marc"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "rules allows all hosts to reach one destination, destination can reach all hosts",
|
||||
args: args{
|
||||
nodes: types.Nodes{ // list of all nodes in the database
|
||||
&types.Node{
|
||||
ID: 1,
|
||||
IPv4: ap("100.64.0.1"),
|
||||
User: &types.User{Name: "joe"},
|
||||
},
|
||||
&types.Node{
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
User: &types.User{Name: "marc"},
|
||||
},
|
||||
&types.Node{
|
||||
ID: 3,
|
||||
IPv4: ap("100.64.0.3"),
|
||||
User: &types.User{Name: "mickael"},
|
||||
},
|
||||
},
|
||||
rules: []tailcfg.FilterRule{ // list of all ACLRules registered
|
||||
{
|
||||
SrcIPs: []string{"*"},
|
||||
DstPorts: []tailcfg.NetPortRange{
|
||||
{IP: "100.64.0.2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
node: &types.Node{ // current nodes
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
User: &types.User{Name: "marc"},
|
||||
},
|
||||
},
|
||||
want: types.Nodes{
|
||||
&types.Node{
|
||||
ID: 1,
|
||||
IPv4: ap("100.64.0.1"),
|
||||
User: &types.User{Name: "joe"},
|
||||
},
|
||||
&types.Node{
|
||||
ID: 3,
|
||||
IPv4: ap("100.64.0.3"),
|
||||
User: &types.User{Name: "mickael"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "rule allows all hosts to reach all destinations",
|
||||
args: args{
|
||||
nodes: types.Nodes{ // list of all nodes in the database
|
||||
&types.Node{
|
||||
ID: 1,
|
||||
IPv4: ap("100.64.0.1"),
|
||||
User: &types.User{Name: "joe"},
|
||||
},
|
||||
&types.Node{
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
User: &types.User{Name: "marc"},
|
||||
},
|
||||
&types.Node{
|
||||
ID: 3,
|
||||
IPv4: ap("100.64.0.3"),
|
||||
User: &types.User{Name: "mickael"},
|
||||
},
|
||||
},
|
||||
rules: []tailcfg.FilterRule{ // list of all ACLRules registered
|
||||
{
|
||||
SrcIPs: []string{"*"},
|
||||
DstPorts: []tailcfg.NetPortRange{
|
||||
{IP: "*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
node: &types.Node{ // current nodes
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
User: &types.User{Name: "marc"},
|
||||
},
|
||||
},
|
||||
want: types.Nodes{
|
||||
&types.Node{
|
||||
ID: 1,
|
||||
IPv4: ap("100.64.0.1"),
|
||||
User: &types.User{Name: "joe"},
|
||||
},
|
||||
&types.Node{
|
||||
ID: 3,
|
||||
IPv4: ap("100.64.0.3"),
|
||||
User: &types.User{Name: "mickael"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "without rule all communications are forbidden",
|
||||
args: args{
|
||||
nodes: types.Nodes{ // list of all nodes in the database
|
||||
&types.Node{
|
||||
ID: 1,
|
||||
IPv4: ap("100.64.0.1"),
|
||||
User: &types.User{Name: "joe"},
|
||||
},
|
||||
&types.Node{
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
User: &types.User{Name: "marc"},
|
||||
},
|
||||
&types.Node{
|
||||
ID: 3,
|
||||
IPv4: ap("100.64.0.3"),
|
||||
User: &types.User{Name: "mickael"},
|
||||
},
|
||||
},
|
||||
rules: []tailcfg.FilterRule{ // list of all ACLRules registered
|
||||
},
|
||||
node: &types.Node{ // current nodes
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
User: &types.User{Name: "marc"},
|
||||
},
|
||||
},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
// Investigating 699
|
||||
// Found some nodes: [ts-head-8w6paa ts-unstable-lys2ib ts-head-upcrmb ts-unstable-rlwpvr] nodes=ts-head-8w6paa
|
||||
// ACL rules generated ACL=[{"DstPorts":[{"Bits":null,"IP":"*","Ports":{"First":0,"Last":65535}}],"SrcIPs":["fd7a:115c:a1e0::3","100.64.0.3","fd7a:115c:a1e0::4","100.64.0.4"]}]
|
||||
// ACL Cache Map={"100.64.0.3":{"*":{}},"100.64.0.4":{"*":{}},"fd7a:115c:a1e0::3":{"*":{}},"fd7a:115c:a1e0::4":{"*":{}}}
|
||||
name: "issue-699-broken-star",
|
||||
args: args{
|
||||
nodes: types.Nodes{ //
|
||||
&types.Node{
|
||||
ID: 1,
|
||||
Hostname: "ts-head-upcrmb",
|
||||
IPv4: ap("100.64.0.3"),
|
||||
IPv6: ap("fd7a:115c:a1e0::3"),
|
||||
User: &types.User{Name: "user1"},
|
||||
},
|
||||
&types.Node{
|
||||
ID: 2,
|
||||
Hostname: "ts-unstable-rlwpvr",
|
||||
IPv4: ap("100.64.0.4"),
|
||||
IPv6: ap("fd7a:115c:a1e0::4"),
|
||||
User: &types.User{Name: "user1"},
|
||||
},
|
||||
&types.Node{
|
||||
ID: 3,
|
||||
Hostname: "ts-head-8w6paa",
|
||||
IPv4: ap("100.64.0.1"),
|
||||
IPv6: ap("fd7a:115c:a1e0::1"),
|
||||
User: &types.User{Name: "user2"},
|
||||
},
|
||||
&types.Node{
|
||||
ID: 4,
|
||||
Hostname: "ts-unstable-lys2ib",
|
||||
IPv4: ap("100.64.0.2"),
|
||||
IPv6: ap("fd7a:115c:a1e0::2"),
|
||||
User: &types.User{Name: "user2"},
|
||||
},
|
||||
},
|
||||
rules: []tailcfg.FilterRule{ // list of all ACLRules registered
|
||||
{
|
||||
DstPorts: []tailcfg.NetPortRange{
|
||||
{
|
||||
IP: "*",
|
||||
Ports: tailcfg.PortRange{First: 0, Last: 65535},
|
||||
},
|
||||
},
|
||||
SrcIPs: []string{
|
||||
"fd7a:115c:a1e0::3", "100.64.0.3",
|
||||
"fd7a:115c:a1e0::4", "100.64.0.4",
|
||||
},
|
||||
},
|
||||
},
|
||||
node: &types.Node{ // current nodes
|
||||
ID: 3,
|
||||
Hostname: "ts-head-8w6paa",
|
||||
IPv4: ap("100.64.0.1"),
|
||||
IPv6: ap("fd7a:115c:a1e0::1"),
|
||||
User: &types.User{Name: "user2"},
|
||||
},
|
||||
},
|
||||
want: types.Nodes{
|
||||
&types.Node{
|
||||
ID: 1,
|
||||
Hostname: "ts-head-upcrmb",
|
||||
IPv4: ap("100.64.0.3"),
|
||||
IPv6: ap("fd7a:115c:a1e0::3"),
|
||||
User: &types.User{Name: "user1"},
|
||||
},
|
||||
&types.Node{
|
||||
ID: 2,
|
||||
Hostname: "ts-unstable-rlwpvr",
|
||||
IPv4: ap("100.64.0.4"),
|
||||
IPv6: ap("fd7a:115c:a1e0::4"),
|
||||
User: &types.User{Name: "user1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "failing-edge-case-during-p3-refactor",
|
||||
args: args{
|
||||
nodes: []*types.Node{
|
||||
{
|
||||
ID: 1,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
Hostname: "peer1",
|
||||
User: &types.User{Name: "mini"},
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.3"),
|
||||
Hostname: "peer2",
|
||||
User: &types.User{Name: "peer2"},
|
||||
},
|
||||
},
|
||||
rules: []tailcfg.FilterRule{
|
||||
{
|
||||
SrcIPs: []string{"100.64.0.1/32"},
|
||||
DstPorts: []tailcfg.NetPortRange{
|
||||
{IP: "100.64.0.3/32", Ports: tailcfg.PortRangeAny},
|
||||
{IP: "::/0", Ports: tailcfg.PortRangeAny},
|
||||
},
|
||||
},
|
||||
},
|
||||
node: &types.Node{
|
||||
ID: 0,
|
||||
IPv4: ap("100.64.0.1"),
|
||||
Hostname: "mini",
|
||||
User: &types.User{Name: "mini"},
|
||||
},
|
||||
},
|
||||
want: []*types.Node{
|
||||
{
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.3"),
|
||||
Hostname: "peer2",
|
||||
User: &types.User{Name: "peer2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "p4-host-in-netmap-user2-dest-bug",
|
||||
args: args{
|
||||
nodes: []*types.Node{
|
||||
{
|
||||
ID: 1,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
Hostname: "user1-2",
|
||||
User: &types.User{Name: "user1"},
|
||||
},
|
||||
{
|
||||
ID: 0,
|
||||
IPv4: ap("100.64.0.1"),
|
||||
Hostname: "user1-1",
|
||||
User: &types.User{Name: "user1"},
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
IPv4: ap("100.64.0.4"),
|
||||
Hostname: "user2-2",
|
||||
User: &types.User{Name: "user2"},
|
||||
},
|
||||
},
|
||||
rules: []tailcfg.FilterRule{
|
||||
{
|
||||
SrcIPs: []string{
|
||||
"100.64.0.3/32",
|
||||
"100.64.0.4/32",
|
||||
"fd7a:115c:a1e0::3/128",
|
||||
"fd7a:115c:a1e0::4/128",
|
||||
},
|
||||
DstPorts: []tailcfg.NetPortRange{
|
||||
{IP: "100.64.0.3/32", Ports: tailcfg.PortRangeAny},
|
||||
{IP: "100.64.0.4/32", Ports: tailcfg.PortRangeAny},
|
||||
{IP: "fd7a:115c:a1e0::3/128", Ports: tailcfg.PortRangeAny},
|
||||
{IP: "fd7a:115c:a1e0::4/128", Ports: tailcfg.PortRangeAny},
|
||||
},
|
||||
},
|
||||
{
|
||||
SrcIPs: []string{
|
||||
"100.64.0.1/32",
|
||||
"100.64.0.2/32",
|
||||
"fd7a:115c:a1e0::1/128",
|
||||
"fd7a:115c:a1e0::2/128",
|
||||
},
|
||||
DstPorts: []tailcfg.NetPortRange{
|
||||
{IP: "100.64.0.3/32", Ports: tailcfg.PortRangeAny},
|
||||
{IP: "100.64.0.4/32", Ports: tailcfg.PortRangeAny},
|
||||
{IP: "fd7a:115c:a1e0::3/128", Ports: tailcfg.PortRangeAny},
|
||||
{IP: "fd7a:115c:a1e0::4/128", Ports: tailcfg.PortRangeAny},
|
||||
},
|
||||
},
|
||||
},
|
||||
node: &types.Node{
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.3"),
|
||||
Hostname: "user-2-1",
|
||||
User: &types.User{Name: "user2"},
|
||||
},
|
||||
},
|
||||
want: []*types.Node{
|
||||
{
|
||||
ID: 1,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
Hostname: "user1-2",
|
||||
User: &types.User{Name: "user1"},
|
||||
},
|
||||
{
|
||||
ID: 0,
|
||||
IPv4: ap("100.64.0.1"),
|
||||
Hostname: "user1-1",
|
||||
User: &types.User{Name: "user1"},
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
IPv4: ap("100.64.0.4"),
|
||||
Hostname: "user2-2",
|
||||
User: &types.User{Name: "user2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "p4-host-in-netmap-user1-dest-bug",
|
||||
args: args{
|
||||
nodes: []*types.Node{
|
||||
{
|
||||
ID: 1,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
Hostname: "user1-2",
|
||||
User: &types.User{Name: "user1"},
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.3"),
|
||||
Hostname: "user-2-1",
|
||||
User: &types.User{Name: "user2"},
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
IPv4: ap("100.64.0.4"),
|
||||
Hostname: "user2-2",
|
||||
User: &types.User{Name: "user2"},
|
||||
},
|
||||
},
|
||||
rules: []tailcfg.FilterRule{
|
||||
{
|
||||
SrcIPs: []string{
|
||||
"100.64.0.1/32",
|
||||
"100.64.0.2/32",
|
||||
"fd7a:115c:a1e0::1/128",
|
||||
"fd7a:115c:a1e0::2/128",
|
||||
},
|
||||
DstPorts: []tailcfg.NetPortRange{
|
||||
{IP: "100.64.0.1/32", Ports: tailcfg.PortRangeAny},
|
||||
{IP: "100.64.0.2/32", Ports: tailcfg.PortRangeAny},
|
||||
{IP: "fd7a:115c:a1e0::1/128", Ports: tailcfg.PortRangeAny},
|
||||
{IP: "fd7a:115c:a1e0::2/128", Ports: tailcfg.PortRangeAny},
|
||||
},
|
||||
},
|
||||
{
|
||||
SrcIPs: []string{
|
||||
"100.64.0.1/32",
|
||||
"100.64.0.2/32",
|
||||
"fd7a:115c:a1e0::1/128",
|
||||
"fd7a:115c:a1e0::2/128",
|
||||
},
|
||||
DstPorts: []tailcfg.NetPortRange{
|
||||
{IP: "100.64.0.3/32", Ports: tailcfg.PortRangeAny},
|
||||
{IP: "100.64.0.4/32", Ports: tailcfg.PortRangeAny},
|
||||
{IP: "fd7a:115c:a1e0::3/128", Ports: tailcfg.PortRangeAny},
|
||||
{IP: "fd7a:115c:a1e0::4/128", Ports: tailcfg.PortRangeAny},
|
||||
},
|
||||
},
|
||||
},
|
||||
node: &types.Node{
|
||||
ID: 0,
|
||||
IPv4: ap("100.64.0.1"),
|
||||
Hostname: "user1-1",
|
||||
User: &types.User{Name: "user1"},
|
||||
},
|
||||
},
|
||||
want: []*types.Node{
|
||||
{
|
||||
ID: 1,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
Hostname: "user1-2",
|
||||
User: &types.User{Name: "user1"},
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.3"),
|
||||
Hostname: "user-2-1",
|
||||
User: &types.User{Name: "user2"},
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
IPv4: ap("100.64.0.4"),
|
||||
Hostname: "user2-2",
|
||||
User: &types.User{Name: "user2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "subnet-router-with-only-route",
|
||||
args: args{
|
||||
nodes: []*types.Node{
|
||||
{
|
||||
ID: 1,
|
||||
IPv4: ap("100.64.0.1"),
|
||||
Hostname: "user1",
|
||||
User: &types.User{Name: "user1"},
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
Hostname: "router",
|
||||
User: &types.User{Name: "router"},
|
||||
Hostinfo: &tailcfg.Hostinfo{
|
||||
RoutableIPs: []netip.Prefix{netip.MustParsePrefix("10.33.0.0/16")},
|
||||
},
|
||||
ApprovedRoutes: []netip.Prefix{netip.MustParsePrefix("10.33.0.0/16")},
|
||||
},
|
||||
},
|
||||
rules: []tailcfg.FilterRule{
|
||||
{
|
||||
SrcIPs: []string{
|
||||
"100.64.0.1/32",
|
||||
},
|
||||
DstPorts: []tailcfg.NetPortRange{
|
||||
{IP: "10.33.0.0/16", Ports: tailcfg.PortRangeAny},
|
||||
},
|
||||
},
|
||||
},
|
||||
node: &types.Node{
|
||||
ID: 1,
|
||||
IPv4: ap("100.64.0.1"),
|
||||
Hostname: "user1",
|
||||
User: &types.User{Name: "user1"},
|
||||
},
|
||||
},
|
||||
want: []*types.Node{
|
||||
{
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
Hostname: "router",
|
||||
User: &types.User{Name: "router"},
|
||||
Hostinfo: &tailcfg.Hostinfo{
|
||||
RoutableIPs: []netip.Prefix{netip.MustParsePrefix("10.33.0.0/16")},
|
||||
},
|
||||
ApprovedRoutes: []netip.Prefix{netip.MustParsePrefix("10.33.0.0/16")},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "subnet-router-with-only-route-smaller-mask-2181",
|
||||
args: args{
|
||||
nodes: []*types.Node{
|
||||
{
|
||||
ID: 1,
|
||||
IPv4: ap("100.64.0.1"),
|
||||
Hostname: "router",
|
||||
User: &types.User{Name: "router"},
|
||||
Hostinfo: &tailcfg.Hostinfo{
|
||||
RoutableIPs: []netip.Prefix{netip.MustParsePrefix("10.99.0.0/16")},
|
||||
},
|
||||
ApprovedRoutes: []netip.Prefix{netip.MustParsePrefix("10.99.0.0/16")},
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
Hostname: "node",
|
||||
User: &types.User{Name: "node"},
|
||||
},
|
||||
},
|
||||
rules: []tailcfg.FilterRule{
|
||||
{
|
||||
SrcIPs: []string{
|
||||
"100.64.0.2/32",
|
||||
},
|
||||
DstPorts: []tailcfg.NetPortRange{
|
||||
{IP: "10.99.0.2/32", Ports: tailcfg.PortRangeAny},
|
||||
},
|
||||
},
|
||||
},
|
||||
node: &types.Node{
|
||||
ID: 1,
|
||||
IPv4: ap("100.64.0.1"),
|
||||
Hostname: "router",
|
||||
User: &types.User{Name: "router"},
|
||||
Hostinfo: &tailcfg.Hostinfo{
|
||||
RoutableIPs: []netip.Prefix{netip.MustParsePrefix("10.99.0.0/16")},
|
||||
},
|
||||
ApprovedRoutes: []netip.Prefix{netip.MustParsePrefix("10.99.0.0/16")},
|
||||
},
|
||||
},
|
||||
want: []*types.Node{
|
||||
{
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
Hostname: "node",
|
||||
User: &types.User{Name: "node"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "node-to-subnet-router-with-only-route-smaller-mask-2181",
|
||||
args: args{
|
||||
nodes: []*types.Node{
|
||||
{
|
||||
ID: 1,
|
||||
IPv4: ap("100.64.0.1"),
|
||||
Hostname: "router",
|
||||
User: &types.User{Name: "router"},
|
||||
Hostinfo: &tailcfg.Hostinfo{
|
||||
RoutableIPs: []netip.Prefix{netip.MustParsePrefix("10.99.0.0/16")},
|
||||
},
|
||||
ApprovedRoutes: []netip.Prefix{netip.MustParsePrefix("10.99.0.0/16")},
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
Hostname: "node",
|
||||
User: &types.User{Name: "node"},
|
||||
},
|
||||
},
|
||||
rules: []tailcfg.FilterRule{
|
||||
{
|
||||
SrcIPs: []string{
|
||||
"100.64.0.2/32",
|
||||
},
|
||||
DstPorts: []tailcfg.NetPortRange{
|
||||
{IP: "10.99.0.2/32", Ports: tailcfg.PortRangeAny},
|
||||
},
|
||||
},
|
||||
},
|
||||
node: &types.Node{
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
Hostname: "node",
|
||||
User: &types.User{Name: "node"},
|
||||
},
|
||||
},
|
||||
want: []*types.Node{
|
||||
{
|
||||
ID: 1,
|
||||
IPv4: ap("100.64.0.1"),
|
||||
Hostname: "router",
|
||||
User: &types.User{Name: "router"},
|
||||
Hostinfo: &tailcfg.Hostinfo{
|
||||
RoutableIPs: []netip.Prefix{netip.MustParsePrefix("10.99.0.0/16")},
|
||||
},
|
||||
ApprovedRoutes: []netip.Prefix{netip.MustParsePrefix("10.99.0.0/16")},
|
||||
},
|
||||
},
|
||||
},
|
||||
// Subnet-to-subnet: routers must see each other when ACL
|
||||
// uses only subnet CIDRs. Issue #3157.
|
||||
{
|
||||
name: "subnet-to-subnet-routers-see-each-other-3157",
|
||||
args: args{
|
||||
nodes: []*types.Node{
|
||||
{
|
||||
ID: 1,
|
||||
IPv4: ap("100.64.0.1"),
|
||||
Hostname: "router-a",
|
||||
User: &types.User{Name: "router-a"},
|
||||
Hostinfo: &tailcfg.Hostinfo{
|
||||
RoutableIPs: []netip.Prefix{netip.MustParsePrefix("10.88.8.0/24")},
|
||||
},
|
||||
ApprovedRoutes: []netip.Prefix{netip.MustParsePrefix("10.88.8.0/24")},
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
Hostname: "router-b",
|
||||
User: &types.User{Name: "router-b"},
|
||||
Hostinfo: &tailcfg.Hostinfo{
|
||||
RoutableIPs: []netip.Prefix{netip.MustParsePrefix("10.99.9.0/24")},
|
||||
},
|
||||
ApprovedRoutes: []netip.Prefix{netip.MustParsePrefix("10.99.9.0/24")},
|
||||
},
|
||||
},
|
||||
rules: []tailcfg.FilterRule{
|
||||
{
|
||||
SrcIPs: []string{"10.88.8.0/24"},
|
||||
DstPorts: []tailcfg.NetPortRange{
|
||||
{IP: "10.99.9.0/24", Ports: tailcfg.PortRangeAny},
|
||||
},
|
||||
},
|
||||
},
|
||||
node: &types.Node{
|
||||
ID: 1,
|
||||
IPv4: ap("100.64.0.1"),
|
||||
Hostname: "router-a",
|
||||
User: &types.User{Name: "router-a"},
|
||||
Hostinfo: &tailcfg.Hostinfo{
|
||||
RoutableIPs: []netip.Prefix{netip.MustParsePrefix("10.88.8.0/24")},
|
||||
},
|
||||
ApprovedRoutes: []netip.Prefix{netip.MustParsePrefix("10.88.8.0/24")},
|
||||
},
|
||||
},
|
||||
want: []*types.Node{
|
||||
{
|
||||
ID: 2,
|
||||
IPv4: ap("100.64.0.2"),
|
||||
Hostname: "router-b",
|
||||
User: &types.User{Name: "router-b"},
|
||||
Hostinfo: &tailcfg.Hostinfo{
|
||||
RoutableIPs: []netip.Prefix{netip.MustParsePrefix("10.99.9.0/24")},
|
||||
},
|
||||
ApprovedRoutes: []netip.Prefix{netip.MustParsePrefix("10.99.9.0/24")},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
matchers := matcher.MatchesFromFilterRules(tt.args.rules)
|
||||
gotViews := ReduceNodes(
|
||||
tt.args.node.View(),
|
||||
tt.args.nodes.ViewSlice(),
|
||||
matchers,
|
||||
)
|
||||
// Convert views back to nodes for comparison in tests
|
||||
var got types.Nodes
|
||||
for _, v := range gotViews.All() {
|
||||
got = append(got, v.AsStruct())
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tt.want, got, util.Comparers...); diff != "" {
|
||||
t.Errorf("ReduceNodes() unexpected result (-want +got):\n%s", diff)
|
||||
t.Log("Matchers: ")
|
||||
|
||||
for _, m := range matchers {
|
||||
t.Log("\t+", m.DebugString())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReduceNodesFromPolicy(t *testing.T) {
|
||||
func TestBuildPeerMapFromPolicy(t *testing.T) {
|
||||
n := func(id types.NodeID, ip, hostname, username string, routess ...string) *types.Node {
|
||||
routes := make([]netip.Prefix, 0, len(routess))
|
||||
for _, route := range routess {
|
||||
@@ -1107,19 +278,13 @@ func TestReduceNodesFromPolicy(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, matchers, tt.wantMatchers)
|
||||
|
||||
gotViews := ReduceNodes(
|
||||
tt.node.View(),
|
||||
tt.nodes.ViewSlice(),
|
||||
matchers,
|
||||
)
|
||||
// Convert views back to nodes for comparison in tests
|
||||
var got types.Nodes
|
||||
for _, v := range gotViews.All() {
|
||||
got = append(got, v.AsStruct())
|
||||
var want []types.NodeID
|
||||
for _, n := range tt.want {
|
||||
want = append(want, n.ID)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tt.want, got, util.Comparers...); diff != "" {
|
||||
t.Errorf("TestReduceNodesFromPolicy() unexpected result (-want +got):\n%s", diff)
|
||||
got := pm.BuildPeerMap(tt.nodes.ViewSlice())[tt.node.ID]
|
||||
if !assert.ElementsMatch(t, want, got) {
|
||||
t.Log("Matchers: ")
|
||||
|
||||
for _, m := range matchers {
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
// - TestRoutesCompat: validates filter rule compilation (compileFilterRulesForNode
|
||||
// + ReduceFilterRules) against golden file captures.
|
||||
//
|
||||
// - TestRoutesCompatPeerVisibility: validates peer visibility (CanAccess /
|
||||
// ReduceNodes) for the subnet-to-subnet scenarios (f10–f15). These tests
|
||||
// - TestRoutesCompatPeerVisibility: validates peer visibility (CanAccess)
|
||||
// for the subnet-to-subnet scenarios (f10–f15). These tests
|
||||
// derive expected peer relationships from the golden file captures: if
|
||||
// Tailscale SaaS delivers filter rules to a node, then the subnet routers
|
||||
// referenced in those rules must be visible as peers. This exercises the
|
||||
|
||||
Reference in New Issue
Block a user