From 87e2ae4d52632c5276e903ebdc1db92de1b82dd6 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 30 Apr 2024 07:23:16 +0200 Subject: [PATCH] add autogroup:internet, fix reduce filter rules (#1917) --- CHANGELOG.md | 1 + hscontrol/policy/acls.go | 61 +++- hscontrol/policy/acls_test.go | 564 +++++++++++++++++++++++++++++++++- 3 files changed, 619 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8c7590f4b..7cd8283029 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ after improving the test harness as part of adopting [#1460](https://github.com/ - Add support for deleting api keys [#1702](https://github.com/juanfont/headscale/pull/1702) - Add command to backfill IP addresses for nodes missing IPs from configured prefixes. [#1869](https://github.com/juanfont/headscale/pull/1869) - Log available update as warning [#1877](https://github.com/juanfont/headscale/pull/1877) +- Add `autogroup:internet` to Policy [#1917](https://github.com/juanfont/headscale/pull/1917) ## 0.22.3 (2023-05-12) diff --git a/hscontrol/policy/acls.go b/hscontrol/policy/acls.go index 0f6158c659..1196995d09 100644 --- a/hscontrol/policy/acls.go +++ b/hscontrol/policy/acls.go @@ -36,6 +36,38 @@ const ( expectedTokenItems = 2 ) +var theInternetSet *netipx.IPSet + +// theInternet returns the IPSet for the Internet. +// https://www.youtube.com/watch?v=iDbyYGrswtg +func theInternet() *netipx.IPSet { + if theInternetSet != nil { + return theInternetSet + } + + var internetBuilder netipx.IPSetBuilder + internetBuilder.AddPrefix(netip.MustParsePrefix("2000::/3")) + internetBuilder.AddPrefix(netip.MustParsePrefix("0.0.0.0/0")) + + // Delete Private network addresses + // https://datatracker.ietf.org/doc/html/rfc1918 + internetBuilder.RemovePrefix(netip.MustParsePrefix("fc00::/7")) + internetBuilder.RemovePrefix(netip.MustParsePrefix("10.0.0.0/8")) + internetBuilder.RemovePrefix(netip.MustParsePrefix("172.16.0.0/12")) + internetBuilder.RemovePrefix(netip.MustParsePrefix("192.168.0.0/16")) + + // Delete Tailscale networks + internetBuilder.RemovePrefix(netip.MustParsePrefix("fd7a:115c:a1e0::/48")) + internetBuilder.RemovePrefix(netip.MustParsePrefix("100.64.0.0/10")) + + // Delete "cant find DHCP networks" + internetBuilder.RemovePrefix(netip.MustParsePrefix("fe80::/10")) // link-loca + internetBuilder.RemovePrefix(netip.MustParsePrefix("169.254.0.0/16")) + + theInternetSet, _ := internetBuilder.IPSet() + return theInternetSet +} + // For some reason golang.org/x/net/internal/iana is an internal package. const ( protocolICMP = 1 // Internet Control Message @@ -221,28 +253,28 @@ func ReduceFilterRules(node *types.Node, rules []tailcfg.FilterRule) []tailcfg.F // record if the rule is actually relevant for the given node. dests := []tailcfg.NetPortRange{} + DEST_LOOP: for _, dest := range rule.DstPorts { expanded, err := util.ParseIPSet(dest.IP, nil) // Fail closed, if we cant parse it, then we should not allow // access. if err != nil { - continue + continue DEST_LOOP } if node.InIPSet(expanded) { dests = append(dests, dest) + continue DEST_LOOP } // If the node exposes routes, ensure they are note removed // when the filters are reduced. if node.Hostinfo != nil { - // TODO(kradalby): Evaluate if we should only keep - // the routes if the route is enabled. This will - // require database access in this part of the code. if len(node.Hostinfo.RoutableIPs) > 0 { for _, routableIP := range node.Hostinfo.RoutableIPs { - if expanded.ContainsPrefix(routableIP) { + if expanded.OverlapsPrefix(routableIP) { dests = append(dests, dest) + continue DEST_LOOP } } } @@ -517,6 +549,7 @@ func (pol *ACLPolicy) expandSource( // - a host // - an ip // - a cidr +// - an autogroup // and transform these in IPAddresses. func (pol *ACLPolicy) ExpandAlias( nodes types.Nodes, @@ -542,6 +575,10 @@ func (pol *ACLPolicy) ExpandAlias( return pol.expandIPsFromTag(alias, nodes) } + if isAutoGroup(alias) { + return expandAutoGroup(alias) + } + // if alias is a user if ips, err := pol.expandIPsFromUser(alias, nodes); ips != nil { return ips, err @@ -862,6 +899,16 @@ func (pol *ACLPolicy) expandIPsFromIPPrefix( return build.IPSet() } +func expandAutoGroup(alias string) (*netipx.IPSet, error) { + switch { + case strings.HasPrefix(alias, "autogroup:internet"): + return theInternet(), nil + + default: + return nil, fmt.Errorf("unknown autogroup %q", alias) + } +} + func isWildcard(str string) bool { return str == "*" } @@ -874,6 +921,10 @@ func isTag(str string) bool { return strings.HasPrefix(str, "tag:") } +func isAutoGroup(str string) bool { + return strings.HasPrefix(str, "autogroup:") +} + // TagsOfNode will return the tags of the current node. // Invalid tags are tags added by a user on a node, and that user doesn't have authority to add this tag. // Valid tags are tags added by a user that is allowed in the ACL policy to add this tag. diff --git a/hscontrol/policy/acls_test.go b/hscontrol/policy/acls_test.go index 417ed1d1c5..dd4d95bb36 100644 --- a/hscontrol/policy/acls_test.go +++ b/hscontrol/policy/acls_test.go @@ -1765,6 +1765,108 @@ func TestACLPolicy_generateFilterRules(t *testing.T) { } } +// tsExitNodeDest is the list of destination IP ranges that are allowed when +// you dump the filter list from a Tailscale node connected to Tailscale SaaS. +var tsExitNodeDest = []tailcfg.NetPortRange{ + { + IP: "0.0.0.0-9.255.255.255", + Ports: tailcfg.PortRangeAny, + }, + { + IP: "11.0.0.0-100.63.255.255", + Ports: tailcfg.PortRangeAny, + }, + { + IP: "100.128.0.0-169.253.255.255", + Ports: tailcfg.PortRangeAny, + }, + { + IP: "169.255.0.0-172.15.255.255", + Ports: tailcfg.PortRangeAny, + }, + { + IP: "172.32.0.0-192.167.255.255", + Ports: tailcfg.PortRangeAny, + }, + { + IP: "192.169.0.0-255.255.255.255", + Ports: tailcfg.PortRangeAny, + }, + { + IP: "2000::-3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", + Ports: tailcfg.PortRangeAny, + }, +} + +// hsExitNodeDest is the list of destination IP ranges that are allowed when +// we use headscale "autogroup:internet" +var hsExitNodeDest = []tailcfg.NetPortRange{ + {IP: "0.0.0.0/5", Ports: tailcfg.PortRangeAny}, + {IP: "8.0.0.0/7", Ports: tailcfg.PortRangeAny}, + {IP: "11.0.0.0/8", Ports: tailcfg.PortRangeAny}, + {IP: "12.0.0.0/6", Ports: tailcfg.PortRangeAny}, + {IP: "16.0.0.0/4", Ports: tailcfg.PortRangeAny}, + {IP: "32.0.0.0/3", Ports: tailcfg.PortRangeAny}, + {IP: "64.0.0.0/3", Ports: tailcfg.PortRangeAny}, + {IP: "96.0.0.0/6", Ports: tailcfg.PortRangeAny}, + {IP: "100.0.0.0/10", Ports: tailcfg.PortRangeAny}, + {IP: "100.128.0.0/9", Ports: tailcfg.PortRangeAny}, + {IP: "101.0.0.0/8", Ports: tailcfg.PortRangeAny}, + {IP: "102.0.0.0/7", Ports: tailcfg.PortRangeAny}, + {IP: "104.0.0.0/5", Ports: tailcfg.PortRangeAny}, + {IP: "112.0.0.0/4", Ports: tailcfg.PortRangeAny}, + {IP: "128.0.0.0/3", Ports: tailcfg.PortRangeAny}, + {IP: "160.0.0.0/5", Ports: tailcfg.PortRangeAny}, + {IP: "168.0.0.0/8", Ports: tailcfg.PortRangeAny}, + {IP: "169.0.0.0/9", Ports: tailcfg.PortRangeAny}, + {IP: "169.128.0.0/10", Ports: tailcfg.PortRangeAny}, + {IP: "169.192.0.0/11", Ports: tailcfg.PortRangeAny}, + {IP: "169.224.0.0/12", Ports: tailcfg.PortRangeAny}, + {IP: "169.240.0.0/13", Ports: tailcfg.PortRangeAny}, + {IP: "169.248.0.0/14", Ports: tailcfg.PortRangeAny}, + {IP: "169.252.0.0/15", Ports: tailcfg.PortRangeAny}, + {IP: "169.255.0.0/16", Ports: tailcfg.PortRangeAny}, + {IP: "170.0.0.0/7", Ports: tailcfg.PortRangeAny}, + {IP: "172.0.0.0/12", Ports: tailcfg.PortRangeAny}, + {IP: "172.32.0.0/11", Ports: tailcfg.PortRangeAny}, + {IP: "172.64.0.0/10", Ports: tailcfg.PortRangeAny}, + {IP: "172.128.0.0/9", Ports: tailcfg.PortRangeAny}, + {IP: "173.0.0.0/8", Ports: tailcfg.PortRangeAny}, + {IP: "174.0.0.0/7", Ports: tailcfg.PortRangeAny}, + {IP: "176.0.0.0/4", Ports: tailcfg.PortRangeAny}, + {IP: "192.0.0.0/9", Ports: tailcfg.PortRangeAny}, + {IP: "192.128.0.0/11", Ports: tailcfg.PortRangeAny}, + {IP: "192.160.0.0/13", Ports: tailcfg.PortRangeAny}, + {IP: "192.169.0.0/16", Ports: tailcfg.PortRangeAny}, + {IP: "192.170.0.0/15", Ports: tailcfg.PortRangeAny}, + {IP: "192.172.0.0/14", Ports: tailcfg.PortRangeAny}, + {IP: "192.176.0.0/12", Ports: tailcfg.PortRangeAny}, + {IP: "192.192.0.0/10", Ports: tailcfg.PortRangeAny}, + {IP: "193.0.0.0/8", Ports: tailcfg.PortRangeAny}, + {IP: "194.0.0.0/7", Ports: tailcfg.PortRangeAny}, + {IP: "196.0.0.0/6", Ports: tailcfg.PortRangeAny}, + {IP: "200.0.0.0/5", Ports: tailcfg.PortRangeAny}, + {IP: "208.0.0.0/4", Ports: tailcfg.PortRangeAny}, + {IP: "224.0.0.0/3", Ports: tailcfg.PortRangeAny}, + {IP: "2000::/3", Ports: tailcfg.PortRangeAny}, +} + +func TestTheInternet(t *testing.T) { + internetSet := theInternet() + + internetPrefs := internetSet.Prefixes() + + for i, _ := range internetPrefs { + if internetPrefs[i].String() != hsExitNodeDest[i].IP { + t.Errorf("prefix from internet set %q != hsExit list %q", internetPrefs[i].String(), hsExitNodeDest[i].IP) + } + } + + if len(internetPrefs) != len(hsExitNodeDest) { + t.Fatalf("expected same length of prefixes, internet: %d, hsExit: %d", len(internetPrefs), len(hsExitNodeDest)) + } +} + func TestReduceFilterRules(t *testing.T) { tests := []struct { name string @@ -1869,15 +1971,473 @@ func TestReduceFilterRules(t *testing.T) { }, }, }, + { + name: "1786-reducing-breaks-exit-nodes-the-client", + pol: ACLPolicy{ + Hosts: Hosts{ + // Exit node + "internal": netip.MustParsePrefix("100.64.0.100/32"), + }, + Groups: Groups{ + "group:team": {"user3", "user2", "user1"}, + }, + ACLs: []ACL{ + { + Action: "accept", + Sources: []string{"group:team"}, + Destinations: []string{ + "internal:*", + }, + }, + { + Action: "accept", + Sources: []string{"group:team"}, + Destinations: []string{ + "autogroup:internet:*", + }, + }, + }, + }, + node: &types.Node{ + IPv4: iap("100.64.0.1"), + IPv6: iap("fd7a:115c:a1e0::1"), + User: types.User{Name: "user1"}, + }, + peers: types.Nodes{ + &types.Node{ + IPv4: iap("100.64.0.2"), + IPv6: iap("fd7a:115c:a1e0::2"), + User: types.User{Name: "user2"}, + }, + // "internal" exit node + &types.Node{ + IPv4: iap("100.64.0.100"), + IPv6: iap("fd7a:115c:a1e0::100"), + User: types.User{Name: "user100"}, + Hostinfo: &tailcfg.Hostinfo{ + RoutableIPs: []netip.Prefix{types.ExitRouteV4, types.ExitRouteV6}, + }, + }, + }, + want: []tailcfg.FilterRule{}, + }, + { + name: "1786-reducing-breaks-exit-nodes-the-exit", + pol: ACLPolicy{ + Hosts: Hosts{ + // Exit node + "internal": netip.MustParsePrefix("100.64.0.100/32"), + }, + Groups: Groups{ + "group:team": {"user3", "user2", "user1"}, + }, + ACLs: []ACL{ + { + Action: "accept", + Sources: []string{"group:team"}, + Destinations: []string{ + "internal:*", + }, + }, + { + Action: "accept", + Sources: []string{"group:team"}, + Destinations: []string{ + "autogroup:internet:*", + }, + }, + }, + }, + node: &types.Node{ + IPv4: iap("100.64.0.100"), + IPv6: iap("fd7a:115c:a1e0::100"), + User: types.User{Name: "user100"}, + Hostinfo: &tailcfg.Hostinfo{ + RoutableIPs: []netip.Prefix{types.ExitRouteV4, types.ExitRouteV6}, + }, + }, + peers: types.Nodes{ + &types.Node{ + IPv4: iap("100.64.0.2"), + IPv6: iap("fd7a:115c:a1e0::2"), + User: types.User{Name: "user2"}, + }, + &types.Node{ + IPv4: iap("100.64.0.1"), + IPv6: iap("fd7a:115c:a1e0::1"), + User: types.User{Name: "user1"}, + }, + }, + want: []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.100/32", + Ports: tailcfg.PortRangeAny, + }, + { + IP: "fd7a:115c:a1e0::100/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: hsExitNodeDest, + }, + }, + }, + { + name: "1786-reducing-breaks-exit-nodes-the-example-from-issue", + pol: ACLPolicy{ + Hosts: Hosts{ + // Exit node + "internal": netip.MustParsePrefix("100.64.0.100/32"), + }, + Groups: Groups{ + "group:team": {"user3", "user2", "user1"}, + }, + ACLs: []ACL{ + { + Action: "accept", + Sources: []string{"group:team"}, + Destinations: []string{ + "internal:*", + }, + }, + { + Action: "accept", + Sources: []string{"group:team"}, + Destinations: []string{ + "0.0.0.0/5:*", + "8.0.0.0/7:*", + "11.0.0.0/8:*", + "12.0.0.0/6:*", + "16.0.0.0/4:*", + "32.0.0.0/3:*", + "64.0.0.0/2:*", + "128.0.0.0/3:*", + "160.0.0.0/5:*", + "168.0.0.0/6:*", + "172.0.0.0/12:*", + "172.32.0.0/11:*", + "172.64.0.0/10:*", + "172.128.0.0/9:*", + "173.0.0.0/8:*", + "174.0.0.0/7:*", + "176.0.0.0/4:*", + "192.0.0.0/9:*", + "192.128.0.0/11:*", + "192.160.0.0/13:*", + "192.169.0.0/16:*", + "192.170.0.0/15:*", + "192.172.0.0/14:*", + "192.176.0.0/12:*", + "192.192.0.0/10:*", + "193.0.0.0/8:*", + "194.0.0.0/7:*", + "196.0.0.0/6:*", + "200.0.0.0/5:*", + "208.0.0.0/4:*", + }, + }, + }, + }, + node: &types.Node{ + IPv4: iap("100.64.0.100"), + IPv6: iap("fd7a:115c:a1e0::100"), + User: types.User{Name: "user100"}, + Hostinfo: &tailcfg.Hostinfo{ + RoutableIPs: []netip.Prefix{types.ExitRouteV4, types.ExitRouteV6}, + }, + }, + peers: types.Nodes{ + &types.Node{ + IPv4: iap("100.64.0.2"), + IPv6: iap("fd7a:115c:a1e0::2"), + User: types.User{Name: "user2"}, + }, + &types.Node{ + IPv4: iap("100.64.0.1"), + IPv6: iap("fd7a:115c:a1e0::1"), + User: types.User{Name: "user1"}, + }, + }, + want: []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.100/32", + Ports: tailcfg.PortRangeAny, + }, + { + IP: "fd7a:115c:a1e0::100/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: "0.0.0.0/5", Ports: tailcfg.PortRangeAny}, + {IP: "8.0.0.0/7", Ports: tailcfg.PortRangeAny}, + {IP: "11.0.0.0/8", Ports: tailcfg.PortRangeAny}, + {IP: "12.0.0.0/6", Ports: tailcfg.PortRangeAny}, + {IP: "16.0.0.0/4", Ports: tailcfg.PortRangeAny}, + {IP: "32.0.0.0/3", Ports: tailcfg.PortRangeAny}, + {IP: "64.0.0.0/2", Ports: tailcfg.PortRangeAny}, + {IP: "fd7a:115c:a1e0::1/128", Ports: tailcfg.PortRangeAny}, + {IP: "fd7a:115c:a1e0::2/128", Ports: tailcfg.PortRangeAny}, + {IP: "fd7a:115c:a1e0::100/128", Ports: tailcfg.PortRangeAny}, + {IP: "128.0.0.0/3", Ports: tailcfg.PortRangeAny}, + {IP: "160.0.0.0/5", Ports: tailcfg.PortRangeAny}, + {IP: "168.0.0.0/6", Ports: tailcfg.PortRangeAny}, + {IP: "172.0.0.0/12", Ports: tailcfg.PortRangeAny}, + {IP: "172.32.0.0/11", Ports: tailcfg.PortRangeAny}, + {IP: "172.64.0.0/10", Ports: tailcfg.PortRangeAny}, + {IP: "172.128.0.0/9", Ports: tailcfg.PortRangeAny}, + {IP: "173.0.0.0/8", Ports: tailcfg.PortRangeAny}, + {IP: "174.0.0.0/7", Ports: tailcfg.PortRangeAny}, + {IP: "176.0.0.0/4", Ports: tailcfg.PortRangeAny}, + {IP: "192.0.0.0/9", Ports: tailcfg.PortRangeAny}, + {IP: "192.128.0.0/11", Ports: tailcfg.PortRangeAny}, + {IP: "192.160.0.0/13", Ports: tailcfg.PortRangeAny}, + {IP: "192.169.0.0/16", Ports: tailcfg.PortRangeAny}, + {IP: "192.170.0.0/15", Ports: tailcfg.PortRangeAny}, + {IP: "192.172.0.0/14", Ports: tailcfg.PortRangeAny}, + {IP: "192.176.0.0/12", Ports: tailcfg.PortRangeAny}, + {IP: "192.192.0.0/10", Ports: tailcfg.PortRangeAny}, + {IP: "193.0.0.0/8", Ports: tailcfg.PortRangeAny}, + {IP: "194.0.0.0/7", Ports: tailcfg.PortRangeAny}, + {IP: "196.0.0.0/6", Ports: tailcfg.PortRangeAny}, + {IP: "200.0.0.0/5", Ports: tailcfg.PortRangeAny}, + {IP: "208.0.0.0/4", Ports: tailcfg.PortRangeAny}, + }, + }, + }, + }, + { + name: "1786-reducing-breaks-exit-nodes-app-connector-like", + pol: ACLPolicy{ + Hosts: Hosts{ + // Exit node + "internal": netip.MustParsePrefix("100.64.0.100/32"), + }, + Groups: Groups{ + "group:team": {"user3", "user2", "user1"}, + }, + ACLs: []ACL{ + { + Action: "accept", + Sources: []string{"group:team"}, + Destinations: []string{ + "internal:*", + }, + }, + { + Action: "accept", + Sources: []string{"group:team"}, + Destinations: []string{ + "8.0.0.0/8:*", + "16.0.0.0/8:*", + }, + }, + }, + }, + node: &types.Node{ + IPv4: iap("100.64.0.100"), + IPv6: iap("fd7a:115c:a1e0::100"), + User: types.User{Name: "user100"}, + Hostinfo: &tailcfg.Hostinfo{ + RoutableIPs: []netip.Prefix{netip.MustParsePrefix("8.0.0.0/16"), netip.MustParsePrefix("16.0.0.0/16")}, + }, + }, + peers: types.Nodes{ + &types.Node{ + IPv4: iap("100.64.0.2"), + IPv6: iap("fd7a:115c:a1e0::2"), + User: types.User{Name: "user2"}, + }, + &types.Node{ + IPv4: iap("100.64.0.1"), + IPv6: iap("fd7a:115c:a1e0::1"), + User: types.User{Name: "user1"}, + }, + }, + want: []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.100/32", + Ports: tailcfg.PortRangeAny, + }, + { + IP: "fd7a:115c:a1e0::100/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: "8.0.0.0/8", + Ports: tailcfg.PortRangeAny, + }, + { + IP: "16.0.0.0/8", + Ports: tailcfg.PortRangeAny, + }, + }, + }, + }, + }, + { + name: "1786-reducing-breaks-exit-nodes-app-connector-like2", + pol: ACLPolicy{ + Hosts: Hosts{ + // Exit node + "internal": netip.MustParsePrefix("100.64.0.100/32"), + }, + Groups: Groups{ + "group:team": {"user3", "user2", "user1"}, + }, + ACLs: []ACL{ + { + Action: "accept", + Sources: []string{"group:team"}, + Destinations: []string{ + "internal:*", + }, + }, + { + Action: "accept", + Sources: []string{"group:team"}, + Destinations: []string{ + "8.0.0.0/16:*", + "16.0.0.0/16:*", + }, + }, + }, + }, + node: &types.Node{ + IPv4: iap("100.64.0.100"), + IPv6: iap("fd7a:115c:a1e0::100"), + User: types.User{Name: "user100"}, + Hostinfo: &tailcfg.Hostinfo{ + RoutableIPs: []netip.Prefix{netip.MustParsePrefix("8.0.0.0/8"), netip.MustParsePrefix("16.0.0.0/8")}, + }, + }, + peers: types.Nodes{ + &types.Node{ + IPv4: iap("100.64.0.2"), + IPv6: iap("fd7a:115c:a1e0::2"), + User: types.User{Name: "user2"}, + }, + &types.Node{ + IPv4: iap("100.64.0.1"), + IPv6: iap("fd7a:115c:a1e0::1"), + User: types.User{Name: "user1"}, + }, + }, + want: []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.100/32", + Ports: tailcfg.PortRangeAny, + }, + { + IP: "fd7a:115c:a1e0::100/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: "8.0.0.0/16", + Ports: tailcfg.PortRangeAny, + }, + { + IP: "16.0.0.0/16", + Ports: tailcfg.PortRangeAny, + }, + }, + }, + }, + }, + { + name: "1817-reduce-breaks-32-mask", + pol: ACLPolicy{ + Hosts: Hosts{ + "vlan1": netip.MustParsePrefix("172.16.0.0/24"), + "dns1": netip.MustParsePrefix("172.16.0.21/32"), + }, + Groups: Groups{ + "group:access": {"user1"}, + }, + ACLs: []ACL{ + { + Action: "accept", + Sources: []string{"group:access"}, + Destinations: []string{ + "tag:access-servers:*", + "dns1:*", + }, + }, + }, + }, + node: &types.Node{ + IPv4: iap("100.64.0.100"), + IPv6: iap("fd7a:115c:a1e0::100"), + User: types.User{Name: "user100"}, + Hostinfo: &tailcfg.Hostinfo{ + RoutableIPs: []netip.Prefix{netip.MustParsePrefix("172.16.0.0/24")}, + }, + ForcedTags: types.StringList{"tag:access-servers"}, + }, + peers: types.Nodes{ + &types.Node{ + IPv4: iap("100.64.0.1"), + IPv6: iap("fd7a:115c:a1e0::1"), + User: types.User{Name: "user1"}, + }, + }, + want: []tailcfg.FilterRule{ + { + SrcIPs: []string{"100.64.0.1/32", "fd7a:115c:a1e0::1/128"}, + DstPorts: []tailcfg.NetPortRange{ + { + IP: "100.64.0.100/32", + Ports: tailcfg.PortRangeAny, + }, + { + IP: "fd7a:115c:a1e0::100/128", + Ports: tailcfg.PortRangeAny, + }, + { + IP: "172.16.0.21/32", + Ports: tailcfg.PortRangeAny, + }, + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - rules, _ := tt.pol.CompileFilterRules( + got, _ := tt.pol.CompileFilterRules( append(tt.peers, tt.node), ) - got := ReduceFilterRules(tt.node, rules) + got = ReduceFilterRules(tt.node, got) if diff := cmp.Diff(tt.want, got); diff != "" { log.Trace().Interface("got", got).Msg("result")