From 3ffae8ac201dfa4ea8d824c2dfadac0d73cd57c6 Mon Sep 17 00:00:00 2001 From: vinhjaxt Date: Sun, 8 Sep 2024 17:04:51 +0700 Subject: [PATCH 1/5] Add acl autogroup member and autogroup self --- hscontrol/policy/acls.go | 177 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 171 insertions(+), 6 deletions(-) diff --git a/hscontrol/policy/acls.go b/hscontrol/policy/acls.go index ff73985ba3..f16169d62f 100644 --- a/hscontrol/policy/acls.go +++ b/hscontrol/policy/acls.go @@ -68,6 +68,24 @@ func theInternet() *netipx.IPSet { return theInternetSet } +// vinhjaxt +var allTheIps *netipx.IPSet + +func getAllTheIps() *netipx.IPSet { + if allTheIps != nil { + return allTheIps + } + + var build netipx.IPSetBuilder + build.AddPrefix(netip.MustParsePrefix("::/0")) + build.AddPrefix(netip.MustParsePrefix("0.0.0.0/0")) + + allTheIps, _ := build.IPSet() + return allTheIps +} + +// !vinhjaxt + // For some reason golang.org/x/net/internal/iana is an internal package. const ( protocolICMP = 1 // Internet Control Message @@ -169,13 +187,51 @@ func (pol *ACLPolicy) CompileFilterRules( var rules []tailcfg.FilterRule - for index, acl := range pol.ACLs { + // vinhjaxt + polACLs := pol.ACLs + for index := 0; index < len(polACLs); index++ { + acl := polACLs[index] + aclDestinations := acl.Destinations + if acl.Action != "accept" { return nil, ErrInvalidAction } var srcIPs []string for srcIndex, src := range acl.Sources { + // vinhjaxt + if strings.HasPrefix(src, "autogroup:member") { + // split all autogroup:self and others + var oldDst []string + var newDst []string + + for _, dst := range aclDestinations { + if strings.HasPrefix(dst, "autogroup:self") { + newDst = append(newDst, dst) + } else { + oldDst = append(oldDst, dst) + } + } + + if len(oldDst) == 0 { + // all moved to new, only need to change source + src = "autogroup:self" + } else if len(newDst) != 0 { + // apart moved to new + + aclDestinations = oldDst + + splitAcl := ACL{ + Action: acl.Action, + Sources: []string{"autogroup:self"}, + Destinations: newDst, + } + polACLs = append(polACLs, splitAcl) + // Don't do pol.ACLs = polACLs, race condition + } + } + // !vinhjaxt + srcs, err := pol.expandSource(src, nodes) if err != nil { return nil, fmt.Errorf("parsing policy, acl index: %d->%d: %w", index, srcIndex, err) @@ -189,12 +245,19 @@ func (pol *ACLPolicy) CompileFilterRules( } destPorts := []tailcfg.NetPortRange{} - for _, dest := range acl.Destinations { + for _, dest := range aclDestinations { alias, port, err := parseDestination(dest) if err != nil { return nil, err } + // vinhjaxt + if strings.HasPrefix(alias, "autogroup:self") { + if len(acl.Sources) != 1 || !(acl.Sources[0] == "autogroup:self" || acl.Sources[0] == "autogroup:member") { + return nil, errors.New(`dst "autogroup:self" only works with one src "autogroup:member" or "autogroup:self"`) + } + } + expanded, err := pol.ExpandAlias( nodes, alias, @@ -309,9 +372,21 @@ func (pol *ACLPolicy) CompileSSHPolicy( AllowLocalPortForwarding: false, } - for index, sshACL := range pol.SSHs { + // vinhjaxt + polSSHs := pol.SSHs + for index := 0; index < len(polSSHs); index++ { + sshACL := polSSHs[index] + sshACLDestinations := sshACL.Destinations + var dest netipx.IPSetBuilder - for _, src := range sshACL.Destinations { + for _, src := range sshACLDestinations { + // vinhjaxt + if strings.HasPrefix(src, "autogroup:self") { + if len(sshACL.Sources) != 1 || !(sshACL.Sources[0] == "autogroup:self" || sshACL.Sources[0] == "autogroup:member") { + return nil, errors.New(`dst "autogroup:self" only works with one src "autogroup:member" or "autogroup:self"`) + } + } + expanded, err := pol.ExpandAlias(append(peers, node), src) if err != nil { return nil, err @@ -361,6 +436,41 @@ func (pol *ACLPolicy) CompileSSHPolicy( }) } } else { + // vinhjaxt + if strings.HasPrefix(rawSrc, "autogroup:member") { + // split all autogroup:self and others + var oldDst []string + var newDst []string + + for _, dst := range sshACLDestinations { + if strings.HasPrefix(dst, "autogroup:self") { + newDst = append(newDst, dst) + } else { + oldDst = append(oldDst, dst) + } + } + + if len(oldDst) == 0 { + // all moved to new, only need to change source + rawSrc = "autogroup:self" + } else if len(newDst) != 0 { + // apart moved to new + + sshACLDestinations = oldDst + + splitAcl := SSH{ + Action: sshACL.Action, + Sources: []string{"autogroup:self"}, + Destinations: newDst, + Users: sshACL.Users, + CheckPeriod: sshACL.CheckPeriod, + } + polSSHs = append(polSSHs, splitAcl) + // Don't do pol.SSHs = polSSHs, race condition + } + } + // !vinhjaxt + expandedSrcs, err := pol.ExpandAlias( peers, rawSrc, @@ -561,7 +671,8 @@ func (pol *ACLPolicy) ExpandAlias( } if isAutoGroup(alias) { - return expandAutoGroup(alias) + // vinhjaxt + return expandAutoGroup(pol, alias, nodes) } // if alias is a user @@ -880,11 +991,65 @@ func (pol *ACLPolicy) expandIPsFromIPPrefix( return build.IPSet() } -func expandAutoGroup(alias string) (*netipx.IPSet, error) { +// vinhjaxt +func expandAutoGroup(pol *ACLPolicy, alias string, nodes types.Nodes) (*netipx.IPSet, error) { switch { case strings.HasPrefix(alias, "autogroup:internet"): return theInternet(), nil + // vinhjaxt + case strings.HasPrefix(alias, "autogroup:self"): + // all user's devices, not tagged devices + { + var build netipx.IPSetBuilder + if len(nodes) == 0 { + return build.IPSet() + } + + currentNode := nodes[len(nodes)-1] // /mapper/mapper.go#L544 + for _, node := range nodes { + if node.User.Name == currentNode.User.Name { + // same user name + node.AppendToIPSet(&build) + } + } + return build.IPSet() + } + case strings.HasPrefix(alias, "autogroup:member"): + // all users (not tagged devices) + { + var build netipx.IPSetBuilder + + for _, node := range nodes { + if len(node.ForcedTags) != 0 { // auto tag + continue + } + if tags, _ := pol.TagsOfNode(node); len(tags) != 0 { // valid tag manual add by user (tagOwner) + continue + } + node.AppendToIPSet(&build) + } + return build.IPSet() + } + case strings.HasPrefix(alias, "autogroup:tagged"): + // all tagged devices + { + var build netipx.IPSetBuilder + + for _, node := range nodes { + if len(node.ForcedTags) != 0 { // auto tag + node.AppendToIPSet(&build) + } else if tags, _ := pol.TagsOfNode(node); len(tags) != 0 { // valid tag manual add by user (tagOwner) + node.AppendToIPSet(&build) + } + } + return build.IPSet() + } + + case strings.HasPrefix(alias, "autogroup:danger-all"): + // all ips + return getAllTheIps(), nil + // !vinhjaxt default: return nil, fmt.Errorf("unknown autogroup %q", alias) } From 2732e7a017d114fe2887455e12507355fbdca93e Mon Sep 17 00:00:00 2001 From: Gabe Cook Date: Sat, 12 Oct 2024 02:48:13 -0500 Subject: [PATCH 2/5] chore(policy): ACL code cleanups and lint fixes --- hscontrol/policy/acls.go | 184 +++++++++++++++++++-------------------- 1 file changed, 88 insertions(+), 96 deletions(-) diff --git a/hscontrol/policy/acls.go b/hscontrol/policy/acls.go index f16169d62f..9c14a43f08 100644 --- a/hscontrol/policy/acls.go +++ b/hscontrol/policy/acls.go @@ -28,12 +28,22 @@ var ( ErrInvalidTag = errors.New("invalid tag") ErrInvalidPortFormat = errors.New("invalid port format") ErrWildcardIsNeeded = errors.New("wildcard as port is required for the protocol") + ErrUnknownAutogroup = errors.New("unknown autogroup") + ErrAutogroupSelf = errors.New(`dst "autogroup:self" only works with one src "autogroup:member" or "autogroup:self"`) ) const ( portRangeBegin = 0 portRangeEnd = 65535 expectedTokenItems = 2 + + autogroupPrefix = "autogroup:" + autogroupInternet = "autogroup:internet" + autogroupSelf = "autogroup:self" + autogroupMember = "autogroup:member" + autogroupTagged = "autogroup:tagged" + autogroupNonRoot = "autogroup:nonroot" + autogroupDangerAll = "autogroup:danger-all" ) var theInternetSet *netipx.IPSet @@ -68,12 +78,11 @@ func theInternet() *netipx.IPSet { return theInternetSet } -// vinhjaxt -var allTheIps *netipx.IPSet +var allIPSet *netipx.IPSet -func getAllTheIps() *netipx.IPSet { - if allTheIps != nil { - return allTheIps +func allIPs() *netipx.IPSet { + if allIPSet != nil { + return allIPSet } var build netipx.IPSetBuilder @@ -81,11 +90,10 @@ func getAllTheIps() *netipx.IPSet { build.AddPrefix(netip.MustParsePrefix("0.0.0.0/0")) allTheIps, _ := build.IPSet() + return allTheIps } -// !vinhjaxt - // For some reason golang.org/x/net/internal/iana is an internal package. const ( protocolICMP = 1 // Internet Control Message @@ -187,11 +195,10 @@ func (pol *ACLPolicy) CompileFilterRules( var rules []tailcfg.FilterRule - // vinhjaxt - polACLs := pol.ACLs - for index := 0; index < len(polACLs); index++ { - acl := polACLs[index] - aclDestinations := acl.Destinations + acls := pol.ACLs + for index := 0; index < len(acls); index++ { + acl := acls[index] + destinations := acl.Destinations if acl.Action != "accept" { return nil, ErrInvalidAction @@ -199,38 +206,36 @@ func (pol *ACLPolicy) CompileFilterRules( var srcIPs []string for srcIndex, src := range acl.Sources { - // vinhjaxt - if strings.HasPrefix(src, "autogroup:member") { + if strings.HasPrefix(src, autogroupMember) { // split all autogroup:self and others var oldDst []string var newDst []string - for _, dst := range aclDestinations { - if strings.HasPrefix(dst, "autogroup:self") { + for _, dst := range destinations { + if strings.HasPrefix(dst, autogroupSelf) { newDst = append(newDst, dst) } else { oldDst = append(oldDst, dst) } } - if len(oldDst) == 0 { + switch { + case len(oldDst) == 0: // all moved to new, only need to change source - src = "autogroup:self" - } else if len(newDst) != 0 { + src = autogroupSelf + case len(newDst) != 0: // apart moved to new - aclDestinations = oldDst + destinations = oldDst - splitAcl := ACL{ + splitACL := ACL{ Action: acl.Action, - Sources: []string{"autogroup:self"}, + Sources: []string{autogroupSelf}, Destinations: newDst, } - polACLs = append(polACLs, splitAcl) - // Don't do pol.ACLs = polACLs, race condition + acls = append(acls, splitACL) } } - // !vinhjaxt srcs, err := pol.expandSource(src, nodes) if err != nil { @@ -245,16 +250,15 @@ func (pol *ACLPolicy) CompileFilterRules( } destPorts := []tailcfg.NetPortRange{} - for _, dest := range aclDestinations { + for _, dest := range destinations { alias, port, err := parseDestination(dest) if err != nil { return nil, err } - // vinhjaxt - if strings.HasPrefix(alias, "autogroup:self") { - if len(acl.Sources) != 1 || !(acl.Sources[0] == "autogroup:self" || acl.Sources[0] == "autogroup:member") { - return nil, errors.New(`dst "autogroup:self" only works with one src "autogroup:member" or "autogroup:self"`) + if strings.HasPrefix(alias, autogroupSelf) { + if len(acl.Sources) != 1 || acl.Sources[0] != autogroupSelf && acl.Sources[0] != autogroupMember { + return nil, ErrAutogroupSelf } } @@ -372,18 +376,16 @@ func (pol *ACLPolicy) CompileSSHPolicy( AllowLocalPortForwarding: false, } - // vinhjaxt - polSSHs := pol.SSHs - for index := 0; index < len(polSSHs); index++ { - sshACL := polSSHs[index] - sshACLDestinations := sshACL.Destinations + sshs := pol.SSHs + for index := 0; index < len(sshs); index++ { + sshACL := sshs[index] + destinations := sshACL.Destinations var dest netipx.IPSetBuilder - for _, src := range sshACLDestinations { - // vinhjaxt - if strings.HasPrefix(src, "autogroup:self") { - if len(sshACL.Sources) != 1 || !(sshACL.Sources[0] == "autogroup:self" || sshACL.Sources[0] == "autogroup:member") { - return nil, errors.New(`dst "autogroup:self" only works with one src "autogroup:member" or "autogroup:self"`) + for _, src := range destinations { + if strings.HasPrefix(src, autogroupSelf) { + if len(sshACL.Sources) != 1 || sshACL.Sources[0] != autogroupSelf && sshACL.Sources[0] != autogroupMember { + return nil, ErrAutogroupSelf } } @@ -436,40 +438,38 @@ func (pol *ACLPolicy) CompileSSHPolicy( }) } } else { - // vinhjaxt - if strings.HasPrefix(rawSrc, "autogroup:member") { + if strings.HasPrefix(rawSrc, autogroupMember) { // split all autogroup:self and others var oldDst []string var newDst []string - for _, dst := range sshACLDestinations { - if strings.HasPrefix(dst, "autogroup:self") { + for _, dst := range destinations { + if strings.HasPrefix(dst, autogroupSelf) { newDst = append(newDst, dst) } else { oldDst = append(oldDst, dst) } } - if len(oldDst) == 0 { + switch { + case len(oldDst) == 0: // all moved to new, only need to change source - rawSrc = "autogroup:self" - } else if len(newDst) != 0 { + rawSrc = autogroupSelf + case len(newDst) != 0: // apart moved to new - sshACLDestinations = oldDst + destinations = oldDst - splitAcl := SSH{ + splitACL := SSH{ Action: sshACL.Action, - Sources: []string{"autogroup:self"}, + Sources: []string{autogroupSelf}, Destinations: newDst, Users: sshACL.Users, CheckPeriod: sshACL.CheckPeriod, } - polSSHs = append(polSSHs, splitAcl) - // Don't do pol.SSHs = polSSHs, race condition + sshs = append(sshs, splitACL) } } - // !vinhjaxt expandedSrcs, err := pol.ExpandAlias( peers, @@ -671,7 +671,6 @@ func (pol *ACLPolicy) ExpandAlias( } if isAutoGroup(alias) { - // vinhjaxt return expandAutoGroup(pol, alias, nodes) } @@ -991,67 +990,60 @@ func (pol *ACLPolicy) expandIPsFromIPPrefix( return build.IPSet() } -// vinhjaxt func expandAutoGroup(pol *ACLPolicy, alias string, nodes types.Nodes) (*netipx.IPSet, error) { switch { - case strings.HasPrefix(alias, "autogroup:internet"): + case strings.HasPrefix(alias, autogroupInternet): return theInternet(), nil - // vinhjaxt - case strings.HasPrefix(alias, "autogroup:self"): + case strings.HasPrefix(alias, autogroupSelf): // all user's devices, not tagged devices - { - var build netipx.IPSetBuilder - if len(nodes) == 0 { - return build.IPSet() - } - - currentNode := nodes[len(nodes)-1] // /mapper/mapper.go#L544 + var build netipx.IPSetBuilder + if len(nodes) != 0 { + currentNode := nodes[len(nodes)-1] for _, node := range nodes { - if node.User.Name == currentNode.User.Name { - // same user name + if node.User.ID == currentNode.User.ID { node.AppendToIPSet(&build) } } - return build.IPSet() } - case strings.HasPrefix(alias, "autogroup:member"): + + return build.IPSet() + + case strings.HasPrefix(alias, autogroupMember): // all users (not tagged devices) - { - var build netipx.IPSetBuilder + var build netipx.IPSetBuilder - for _, node := range nodes { - if len(node.ForcedTags) != 0 { // auto tag - continue - } - if tags, _ := pol.TagsOfNode(node); len(tags) != 0 { // valid tag manual add by user (tagOwner) - continue - } - node.AppendToIPSet(&build) + for _, node := range nodes { + if len(node.ForcedTags) != 0 { // auto tag + continue } - return build.IPSet() + if tags, _ := pol.TagsOfNode(node); len(tags) != 0 { // valid tag manual add by user (tagOwner) + continue + } + node.AppendToIPSet(&build) } - case strings.HasPrefix(alias, "autogroup:tagged"): + + return build.IPSet() + + case strings.HasPrefix(alias, autogroupTagged): // all tagged devices - { - var build netipx.IPSetBuilder + var build netipx.IPSetBuilder - for _, node := range nodes { - if len(node.ForcedTags) != 0 { // auto tag - node.AppendToIPSet(&build) - } else if tags, _ := pol.TagsOfNode(node); len(tags) != 0 { // valid tag manual add by user (tagOwner) - node.AppendToIPSet(&build) - } + for _, node := range nodes { + if len(node.ForcedTags) != 0 { // auto tag + node.AppendToIPSet(&build) + } else if tags, _ := pol.TagsOfNode(node); len(tags) != 0 { // valid tag manual add by user (tagOwner) + node.AppendToIPSet(&build) } - return build.IPSet() } - case strings.HasPrefix(alias, "autogroup:danger-all"): - // all ips - return getAllTheIps(), nil - // !vinhjaxt + return build.IPSet() + + case strings.HasPrefix(alias, autogroupDangerAll): + return allIPs(), nil + default: - return nil, fmt.Errorf("unknown autogroup %q", alias) + return nil, fmt.Errorf("%w: %q", ErrUnknownAutogroup, alias) } } @@ -1068,7 +1060,7 @@ func isTag(str string) bool { } func isAutoGroup(str string) bool { - return strings.HasPrefix(str, "autogroup:") + return strings.HasPrefix(str, autogroupPrefix) } // TagsOfNode will return the tags of the current node. From dd528a1753d4540cadee7ecc56687656c5ff66ca Mon Sep 17 00:00:00 2001 From: Gabe Cook Date: Sat, 12 Oct 2024 19:16:15 -0500 Subject: [PATCH 3/5] chore(policy): Change `expandAutoGroup` into a receiver func --- hscontrol/policy/acls.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hscontrol/policy/acls.go b/hscontrol/policy/acls.go index 9c14a43f08..936bd43a15 100644 --- a/hscontrol/policy/acls.go +++ b/hscontrol/policy/acls.go @@ -671,7 +671,7 @@ func (pol *ACLPolicy) ExpandAlias( } if isAutoGroup(alias) { - return expandAutoGroup(pol, alias, nodes) + return pol.expandAutoGroup(alias, nodes) } // if alias is a user @@ -990,7 +990,7 @@ func (pol *ACLPolicy) expandIPsFromIPPrefix( return build.IPSet() } -func expandAutoGroup(pol *ACLPolicy, alias string, nodes types.Nodes) (*netipx.IPSet, error) { +func (pol *ACLPolicy) expandAutoGroup(alias string, nodes types.Nodes) (*netipx.IPSet, error) { switch { case strings.HasPrefix(alias, autogroupInternet): return theInternet(), nil From 2d1c52ef1585c15a8d9fecb11194307b67c8281f Mon Sep 17 00:00:00 2001 From: Gabe Cook Date: Fri, 8 Nov 2024 12:21:27 -0600 Subject: [PATCH 4/5] test(policy): Add autogroup tests --- hscontrol/policy/acls_test.go | 255 ++++++++++++++++++++++++++++++++++ 1 file changed, 255 insertions(+) diff --git a/hscontrol/policy/acls_test.go b/hscontrol/policy/acls_test.go index 1c6e4de863..7a3a1e9861 100644 --- a/hscontrol/policy/acls_test.go +++ b/hscontrol/policy/acls_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "go4.org/netipx" "gopkg.in/check.v1" + "gorm.io/gorm" "tailscale.com/net/tsaddr" "tailscale.com/tailcfg" ) @@ -1594,6 +1595,40 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { } func TestACLPolicy_generateFilterRules(t *testing.T) { + user1Node := &types.Node{ + IPv4: iap("100.100.100.100"), + User: types.User{ + Model: gorm.Model{ + ID: 1, + }, + }, + } + + user2Node := &types.Node{ + IPv4: iap("100.100.101.100"), + User: types.User{ + Model: gorm.Model{ + ID: 2, + }, + }, + Hostinfo: &tailcfg.Hostinfo{}, + } + + user1Node2 := &types.Node{ + IPv4: iap("100.100.102.100"), + User: types.User{ + Model: gorm.Model{ + ID: 1, + }, + }, + } + + serverNode := &types.Node{ + IPv4: iap("100.100.103.100"), + ForcedTags: []string{"tag:server"}, + Hostinfo: &tailcfg.Hostinfo{}, + } + type field struct { pol ACLPolicy } @@ -1711,6 +1746,175 @@ func TestACLPolicy_generateFilterRules(t *testing.T) { }, wantErr: false, }, + { + name: "autogroup-member-to-internet", + field: field{ + pol: ACLPolicy{ + ACLs: []ACL{ + { + Action: "accept", + Sources: []string{"autogroup:member"}, + Destinations: []string{"autogroup:internet:*"}, + }, + }, + }, + }, + args: args{ + nodes: types.Nodes{user2Node, serverNode, user1Node2, user1Node}, + }, + want: []tailcfg.FilterRule{ + { + SrcIPs: []string{"100.100.100.100/32", "100.100.101.100/32", "100.100.102.100/32"}, + DstPorts: hsExitNodeDest, + }, + }, + wantErr: false, + }, + { + name: "autogroup-member-to-self", + field: field{ + pol: ACLPolicy{ + ACLs: []ACL{ + { + Action: "accept", + Sources: []string{"autogroup:member"}, + Destinations: []string{"autogroup:self:*"}, + }, + }, + }, + }, + args: args{ + nodes: types.Nodes{user2Node, serverNode, user1Node2, user1Node}, + }, + want: []tailcfg.FilterRule{ + { + SrcIPs: []string{"100.100.100.100/32", "100.100.102.100/32"}, + DstPorts: []tailcfg.NetPortRange{ + {IP: "100.100.100.100/32", Ports: tailcfg.PortRangeAny}, + {IP: "100.100.102.100/32", Ports: tailcfg.PortRangeAny}, + }, + }, + }, + wantErr: false, + }, + { + name: "autogroup-member-to-member", + field: field{ + pol: ACLPolicy{ + ACLs: []ACL{ + { + Action: "accept", + Sources: []string{"autogroup:member"}, + Destinations: []string{"autogroup:member:*"}, + }, + }, + }, + }, + args: args{ + nodes: types.Nodes{user2Node, serverNode, user1Node2, user1Node}, + }, + want: []tailcfg.FilterRule{ + { + SrcIPs: []string{"100.100.100.100/32", "100.100.101.100/32", "100.100.102.100/32"}, + DstPorts: []tailcfg.NetPortRange{ + {IP: "100.100.100.100/32", Ports: tailcfg.PortRangeAny}, + {IP: "100.100.101.100/32", Ports: tailcfg.PortRangeAny}, + {IP: "100.100.102.100/32", Ports: tailcfg.PortRangeAny}, + }, + }, + }, + wantErr: false, + }, + { + name: "autogroup-member-to-tagged", + field: field{ + pol: ACLPolicy{ + ACLs: []ACL{ + { + Action: "accept", + Sources: []string{"autogroup:member"}, + Destinations: []string{"autogroup:tagged:*"}, + }, + }, + }, + }, + args: args{ + nodes: types.Nodes{user2Node, serverNode, user1Node2, user1Node}, + }, + want: []tailcfg.FilterRule{ + { + SrcIPs: []string{"100.100.100.100/32", "100.100.101.100/32", "100.100.102.100/32"}, + DstPorts: []tailcfg.NetPortRange{ + { + IP: "100.100.103.100/32", + Ports: tailcfg.PortRangeAny, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "autogroup-member-to-all", + field: field{ + pol: ACLPolicy{ + ACLs: []ACL{ + { + Action: "accept", + Sources: []string{"autogroup:member"}, + Destinations: []string{"autogroup:danger-all:*"}, + }, + }, + }, + }, + args: args{ + nodes: types.Nodes{user2Node, serverNode, user1Node2, user1Node}, + }, + want: []tailcfg.FilterRule{ + { + SrcIPs: []string{"100.100.100.100/32", "100.100.101.100/32", "100.100.102.100/32"}, + DstPorts: []tailcfg.NetPortRange{ + {IP: "0.0.0.0/0", Ports: tailcfg.PortRangeAny}, + {IP: "::/0", Ports: tailcfg.PortRangeAny}, + }, + }, + }, + wantErr: false, + }, + { + name: "autogroup-unknown", + field: field{ + pol: ACLPolicy{ + ACLs: []ACL{ + { + Action: "accept", + Sources: []string{"autogroup:member"}, + Destinations: []string{"autogroup:fake:*"}, + }, + }, + }, + }, + args: args{}, + want: nil, + wantErr: true, + }, + { + name: "autogroup-multiple-to-self", + field: field{ + pol: ACLPolicy{ + ACLs: []ACL{ + { + Action: "accept", + Sources: []string{"autogroup:member", "autogroup:tagged"}, + Destinations: []string{"autogroup:self"}, + }, + }, + }, + }, + args: args{}, + want: nil, + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -3387,6 +3591,57 @@ func TestSSHRules(t *testing.T) { }, want: &tailcfg.SSHPolicy{Rules: nil}, }, + { + name: "autogroup-member-to-tagged", + node: types.Node{ + Hostname: "testnodes", + IPv4: iap("100.64.0.1"), + UserID: 0, + User: types.User{ + Name: "user1", + }, + }, + peers: types.Nodes{ + &types.Node{ + Hostname: "testnodes2", + IPv4: iap("100.64.99.42"), + UserID: 0, + User: types.User{ + Name: "user1", + }, + }, + }, + pol: ACLPolicy{ + Groups: Groups{ + "group:test": []string{"user1"}, + }, + Hosts: Hosts{ + "client": netip.PrefixFrom(netip.MustParseAddr("100.64.99.42"), 32), + }, + ACLs: []ACL{ + { + Action: "accept", + Sources: []string{"*"}, + Destinations: []string{"*:*"}, + }, + }, + SSHs: []SSH{ + { + Action: "accept", + Sources: []string{"group:test"}, + Destinations: []string{"100.64.99.42"}, + Users: []string{"autogroup:nonroot"}, + }, + { + Action: "accept", + Sources: []string{"*"}, + Destinations: []string{"100.64.99.42"}, + Users: []string{"autogroup:nonroot"}, + }, + }, + }, + want: &tailcfg.SSHPolicy{Rules: nil}, + }, } for _, tt := range tests { From fd038b9165d1b563c90f4420bcc1d98c6c54f0b5 Mon Sep 17 00:00:00 2001 From: Gabe Cook Date: Fri, 8 Nov 2024 12:31:10 -0600 Subject: [PATCH 5/5] chore: Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 465adc87df..c385ef24be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - Fixed processing of fields in post request in MoveNode rpc [#2179](https://github.com/juanfont/headscale/pull/2179) - Added conversion of 'Hostname' to 'givenName' in a node with FQDN rules applied [#2198](https://github.com/juanfont/headscale/pull/2198) - Fixed updating of hostname and givenName when it is updated in HostInfo [#2199](https://github.com/juanfont/headscale/pull/2199) +- Added autogroup ACLs [#2230](https://github.com/juanfont/headscale/pull/2230) ## 0.23.0 (2024-09-18)