From 004ebcaba1dea344d3407ac77713322512ab0471 Mon Sep 17 00:00:00 2001
From: Benjamin George Roberts <benjamin@bgroberts.id.au>
Date: Wed, 24 Aug 2022 20:53:55 +1000
Subject: [PATCH 1/7] initial implementation of autoApprovers support

---
 acls_types.go           | 18 +++++++---
 machine.go              | 75 +++++++++++++++++++++++++++++++++++++++++
 protocol_common_poll.go |  7 ++++
 3 files changed, 95 insertions(+), 5 deletions(-)

diff --git a/acls_types.go b/acls_types.go
index 0f73d6fdf8..b9a5d4da2d 100644
--- a/acls_types.go
+++ b/acls_types.go
@@ -11,11 +11,12 @@ import (
 
 // ACLPolicy represents a Tailscale ACL Policy.
 type ACLPolicy struct {
-	Groups    Groups    `json:"groups"    yaml:"groups"`
-	Hosts     Hosts     `json:"hosts"     yaml:"hosts"`
-	TagOwners TagOwners `json:"tagOwners" yaml:"tagOwners"`
-	ACLs      []ACL     `json:"acls"      yaml:"acls"`
-	Tests     []ACLTest `json:"tests"     yaml:"tests"`
+	Groups        Groups        `json:"groups"        yaml:"groups"`
+	Hosts         Hosts         `json:"hosts"         yaml:"hosts"`
+	TagOwners     TagOwners     `json:"tagOwners"     yaml:"tagOwners"`
+	ACLs          []ACL         `json:"acls"          yaml:"acls"`
+	Tests         []ACLTest     `json:"tests"         yaml:"tests"`
+	AutoApprovers AutoApprovers `json:"autoApprovers" yaml:"autoApprovers"`
 }
 
 // ACL is a basic rule for the ACL Policy.
@@ -42,6 +43,13 @@ type ACLTest struct {
 	Deny   []string `json:"deny,omitempty" yaml:"deny,omitempty"`
 }
 
+// AutoApprovers specify which users (namespaces?), groups or tags have their advertised routes
+// or exit node status automatically enabled
+type AutoApprovers struct {
+	Routes   map[string][]string `json:"routes"   yaml:"routes"`
+	ExitNode []string            `json:"exitNode" yaml:"exitNode"`
+}
+
 // UnmarshalJSON allows to parse the Hosts directly into netaddr objects.
 func (hosts *Hosts) UnmarshalJSON(data []byte) error {
 	newHosts := Hosts{}
diff --git a/machine.go b/machine.go
index 4399029c15..928f78e2f8 100644
--- a/machine.go
+++ b/machine.go
@@ -930,6 +930,81 @@ func (h *Headscale) EnableRoutes(machine *Machine, routeStrs ...string) error {
 	return nil
 }
 
+// Enabled any routes advertised by a machine that match the ACL autoApprovers policy
+// TODO simplify by expanding only for current machine, and by checking if approvedIPs contains machine.IPs[0]
+func (h *Headscale) EnableAutoApprovedRoutes(machine *Machine) error {
+	approvedRoutes := make([]netaddr.IPPrefix, 0, len(machine.HostInfo.RoutableIPs))
+	machines, err := h.ListMachines()
+
+	if err != nil {
+		log.Err(err)
+		return err
+	}
+
+	for _, advertisedRoute := range machine.HostInfo.RoutableIPs {
+		log.Debug().
+			Uint64("machine", machine.ID).
+			Str("advertisedRoute", advertisedRoute.String()).
+			Msg("Client requested to advertise route")
+
+		approved := false
+		routeApprovers := h.aclPolicy.AutoApprovers.Routes[advertisedRoute.String()]
+
+		if advertisedRoute.Bits() == 0 {
+			routeApprovers = h.aclPolicy.AutoApprovers.ExitNode
+		}
+
+		if len(routeApprovers) > 0 {
+			for _, approvedAlias := range routeApprovers {
+
+				approvedIps, err := expandAlias(machines, *h.aclPolicy, approvedAlias, h.cfg.OIDC.StripEmaildomain)
+
+				if err != nil {
+					log.Err(err).
+						Str("alias", approvedAlias).
+						Msg("Failed to expand alias when processing autoApprovers policy")
+					return err
+				}
+
+				for _, machineIp := range machine.IPAddresses {
+					for _, approvedIp := range approvedIps {
+						approved = machineIp.String() == approvedIp
+
+						if approved {
+							break
+						}
+					}
+
+					if approved {
+						break
+					}
+				}
+			}
+		} else {
+			log.Debug().
+				Uint64("client", machine.ID).
+				Str("advertisedRoute", advertisedRoute.String()).
+				Msg("Advertised route is not automatically approved")
+		}
+
+		if approved {
+			approvedRoutes = append(approvedRoutes, advertisedRoute)
+		}
+	}
+
+	for _, approvedRoute := range approvedRoutes {
+		if !contains(machine.EnabledRoutes, approvedRoute) {
+			log.Info().
+				Str("route", approvedRoute.String()).
+				Uint64("client", machine.ID).
+				Msg("Enabling autoApproved route for client")
+			machine.EnabledRoutes = append(machine.EnabledRoutes, approvedRoute)
+		}
+	}
+
+	return nil
+}
+
 func (machine *Machine) RoutesToProto() *v1.Routes {
 	availableRoutes := machine.GetAdvertisedRoutes()
 
diff --git a/protocol_common_poll.go b/protocol_common_poll.go
index 65dcb55632..d6a8fff2d4 100644
--- a/protocol_common_poll.go
+++ b/protocol_common_poll.go
@@ -42,7 +42,14 @@ func (h *Headscale) handlePollCommon(
 				Str("machine", machine.Hostname).
 				Err(err)
 		}
+
+		// update routes with peer information
+		err = h.EnableAutoApprovedRoutes(machine)
+		if err != nil {
+			//TODO
+		}
 	}
+
 	// From Tailscale client:
 	//
 	// ReadOnly is whether the client just wants to fetch the MapResponse,

From 7653ad40d6900f06ec6817440c05132912f92ae7 Mon Sep 17 00:00:00 2001
From: Benjamin George Roberts <benjamin@bgroberts.id.au>
Date: Wed, 24 Aug 2022 21:30:04 +1000
Subject: [PATCH 2/7] Split GetRouteApprovers from EnableAutoApprovedRoutes

---
 acls_types.go | 25 +++++++++++++++++++
 machine.go    | 68 +++++++++++++++++++++------------------------------
 2 files changed, 53 insertions(+), 40 deletions(-)

diff --git a/acls_types.go b/acls_types.go
index b9a5d4da2d..9446144951 100644
--- a/acls_types.go
+++ b/acls_types.go
@@ -108,3 +108,28 @@ func (policy ACLPolicy) IsZero() bool {
 
 	return false
 }
+
+// Returns the list of autoApproving namespaces, groups or tags for a given IPPrefix
+func (autoApprovers *AutoApprovers) GetRouteApprovers(
+	prefix netaddr.IPPrefix,
+) ([]string, error) {
+	if prefix.Bits() == 0 {
+		return autoApprovers.ExitNode, nil // 0.0.0.0/0, ::/0 or equivalent
+	}
+
+	approverAliases := []string{}
+
+	for autoApprovedPrefix, autoApproverAliases := range autoApprovers.Routes {
+		autoApprovedPrefix, err := netaddr.ParseIPPrefix(autoApprovedPrefix)
+		if err != nil {
+			return nil, err
+		}
+
+		if autoApprovedPrefix.Bits() >= prefix.Bits() &&
+			autoApprovedPrefix.Contains(prefix.IP()) {
+			approverAliases = append(approverAliases, autoApproverAliases...)
+		}
+	}
+
+	return approverAliases, nil
+}
diff --git a/machine.go b/machine.go
index 928f78e2f8..43ec47209f 100644
--- a/machine.go
+++ b/machine.go
@@ -931,60 +931,48 @@ func (h *Headscale) EnableRoutes(machine *Machine, routeStrs ...string) error {
 }
 
 // Enabled any routes advertised by a machine that match the ACL autoApprovers policy
-// TODO simplify by expanding only for current machine, and by checking if approvedIPs contains machine.IPs[0]
 func (h *Headscale) EnableAutoApprovedRoutes(machine *Machine) error {
-	approvedRoutes := make([]netaddr.IPPrefix, 0, len(machine.HostInfo.RoutableIPs))
-	machines, err := h.ListMachines()
-
-	if err != nil {
-		log.Err(err)
-		return err
+	if len(machine.IPAddresses) == 0 {
+		return nil // This machine has no IPAddresses, so can't possibly match any autoApprovers ACLs
 	}
 
+	approvedRoutes := make([]netaddr.IPPrefix, 0, len(machine.HostInfo.RoutableIPs))
+	thisMachine := []Machine{*machine}
+
 	for _, advertisedRoute := range machine.HostInfo.RoutableIPs {
-		log.Debug().
-			Uint64("machine", machine.ID).
-			Str("advertisedRoute", advertisedRoute.String()).
-			Msg("Client requested to advertise route")
+
+		if contains(machine.EnabledRoutes, advertisedRoute) {
+			continue // Skip routes that are already enabled for the node
+		}
 
 		approved := false
-		routeApprovers := h.aclPolicy.AutoApprovers.Routes[advertisedRoute.String()]
+		routeApprovers, err := h.aclPolicy.AutoApprovers.GetRouteApprovers(advertisedRoute)
 
-		if advertisedRoute.Bits() == 0 {
-			routeApprovers = h.aclPolicy.AutoApprovers.ExitNode
+		if err != nil {
+			log.Err(err).
+				Str("advertisedRoute", advertisedRoute.String()).
+				Uint64("machineId", machine.ID).
+				Msg("Failed to resolve autoApprovers for advertised route")
+			return err
 		}
 
-		if len(routeApprovers) > 0 {
-			for _, approvedAlias := range routeApprovers {
-
-				approvedIps, err := expandAlias(machines, *h.aclPolicy, approvedAlias, h.cfg.OIDC.StripEmaildomain)
+		for _, approvedAlias := range routeApprovers {
 
-				if err != nil {
-					log.Err(err).
-						Str("alias", approvedAlias).
-						Msg("Failed to expand alias when processing autoApprovers policy")
-					return err
-				}
+			approvedIps, err := expandAlias(thisMachine, *h.aclPolicy, approvedAlias, h.cfg.OIDC.StripEmaildomain)
 
-				for _, machineIp := range machine.IPAddresses {
-					for _, approvedIp := range approvedIps {
-						approved = machineIp.String() == approvedIp
+			if err != nil {
+				log.Err(err).
+					Str("alias", approvedAlias).
+					Msg("Failed to expand alias when processing autoApprovers policy")
+				return err
+			}
 
-						if approved {
-							break
-						}
-					}
+			// approvedIPs should contain all of machine's IPs if it matches the rule, so check for first
+			approved = contains(approvedIps, machine.IPAddresses[0].String())
 
-					if approved {
-						break
-					}
-				}
+			if approved {
+				break
 			}
-		} else {
-			log.Debug().
-				Uint64("client", machine.ID).
-				Str("advertisedRoute", advertisedRoute.String()).
-				Msg("Advertised route is not automatically approved")
 		}
 
 		if approved {

From 60cc9ddb3b7951290d74e91dad365c8609227b1a Mon Sep 17 00:00:00 2001
From: Benjamin George Roberts <benjamin@bgroberts.id.au>
Date: Wed, 24 Aug 2022 22:09:06 +1000
Subject: [PATCH 3/7] Add test for autoApprovers feature

---
 machine.go                                 | 40 ++++++++++-----------
 machine_test.go                            | 41 ++++++++++++++++++++++
 protocol_common_poll.go                    |  5 +--
 tests/acls/acl_policy_autoapprovers.hujson | 24 +++++++++++++
 4 files changed, 84 insertions(+), 26 deletions(-)
 create mode 100644 tests/acls/acl_policy_autoapprovers.hujson

diff --git a/machine.go b/machine.go
index 43ec47209f..099f7facbe 100644
--- a/machine.go
+++ b/machine.go
@@ -945,9 +945,9 @@ func (h *Headscale) EnableAutoApprovedRoutes(machine *Machine) error {
 			continue // Skip routes that are already enabled for the node
 		}
 
-		approved := false
-		routeApprovers, err := h.aclPolicy.AutoApprovers.GetRouteApprovers(advertisedRoute)
-
+		routeApprovers, err := h.aclPolicy.AutoApprovers.GetRouteApprovers(
+			advertisedRoute,
+		)
 		if err != nil {
 			log.Err(err).
 				Str("advertisedRoute", advertisedRoute.String()).
@@ -957,26 +957,22 @@ func (h *Headscale) EnableAutoApprovedRoutes(machine *Machine) error {
 		}
 
 		for _, approvedAlias := range routeApprovers {
-
-			approvedIps, err := expandAlias(thisMachine, *h.aclPolicy, approvedAlias, h.cfg.OIDC.StripEmaildomain)
-
-			if err != nil {
-				log.Err(err).
-					Str("alias", approvedAlias).
-					Msg("Failed to expand alias when processing autoApprovers policy")
-				return err
+			if approvedAlias == machine.Namespace.Name {
+				approvedRoutes = append(approvedRoutes, advertisedRoute)
+			} else {
+				approvedIps, err := expandAlias(thisMachine, *h.aclPolicy, approvedAlias, h.cfg.OIDC.StripEmaildomain)
+				if err != nil {
+					log.Err(err).
+						Str("alias", approvedAlias).
+						Msg("Failed to expand alias when processing autoApprovers policy")
+					return err
+				}
+
+				// approvedIPs should contain all of machine's IPs if it matches the rule, so check for first
+				if contains(approvedIps, machine.IPAddresses[0].String()) {
+					approvedRoutes = append(approvedRoutes, advertisedRoute)
+				}
 			}
-
-			// approvedIPs should contain all of machine's IPs if it matches the rule, so check for first
-			approved = contains(approvedIps, machine.IPAddresses[0].String())
-
-			if approved {
-				break
-			}
-		}
-
-		if approved {
-			approvedRoutes = append(approvedRoutes, advertisedRoute)
 		}
 	}
 
diff --git a/machine_test.go b/machine_test.go
index 5da0906f25..2c0c91d6d2 100644
--- a/machine_test.go
+++ b/machine_test.go
@@ -1051,3 +1051,44 @@ func TestHeadscale_GenerateGivenName(t *testing.T) {
 		})
 	}
 }
+
+func (s *Suite) TestAutoApproveRoutes(c *check.C) {
+	err := app.LoadACLPolicy("./tests/acls/acl_policy_autoapprovers.hujson")
+	c.Assert(err, check.IsNil)
+
+	namespace, err := app.CreateNamespace("test")
+	c.Assert(err, check.IsNil)
+
+	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil)
+	c.Assert(err, check.IsNil)
+
+	nodeKey := key.NewNode()
+
+	defaultRoute := netaddr.MustParseIPPrefix("0.0.0.0/0")
+	route1 := netaddr.MustParseIPPrefix("10.10.0.0/16")
+	route2 := netaddr.MustParseIPPrefix("10.11.0.0/16")
+
+	machine := Machine{
+		ID:             0,
+		MachineKey:     "foo",
+		NodeKey:        NodePublicKeyStripPrefix(nodeKey.Public()),
+		DiscoKey:       "faa",
+		Hostname:       "test",
+		NamespaceID:    namespace.ID,
+		RegisterMethod: RegisterMethodAuthKey,
+		AuthKeyID:      uint(pak.ID),
+		HostInfo: HostInfo{
+			RequestTags: []string{"tag:exit"},
+			RoutableIPs: []netaddr.IPPrefix{defaultRoute, route1, route2},
+		},
+		IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.1")},
+	}
+
+	app.db.Save(&machine)
+
+	machine0ByID, err := app.GetMachineByID(0)
+	c.Assert(err, check.IsNil)
+
+	app.EnableAutoApprovedRoutes(machine0ByID)
+	c.Assert(machine0ByID.GetEnabledRoutes(), check.HasLen, 3)
+}
diff --git a/protocol_common_poll.go b/protocol_common_poll.go
index d6a8fff2d4..37e2dbc791 100644
--- a/protocol_common_poll.go
+++ b/protocol_common_poll.go
@@ -44,10 +44,7 @@ func (h *Headscale) handlePollCommon(
 		}
 
 		// update routes with peer information
-		err = h.EnableAutoApprovedRoutes(machine)
-		if err != nil {
-			//TODO
-		}
+		h.EnableAutoApprovedRoutes(machine)
 	}
 
 	// From Tailscale client:
diff --git a/tests/acls/acl_policy_autoapprovers.hujson b/tests/acls/acl_policy_autoapprovers.hujson
new file mode 100644
index 0000000000..bf564d88f7
--- /dev/null
+++ b/tests/acls/acl_policy_autoapprovers.hujson
@@ -0,0 +1,24 @@
+// This ACL validates autoApprovers support for
+// exit nodes and advertised routes
+
+{
+    "tagOwners": {
+        "tag:exit": ["test"],
+    },
+
+    "groups": {
+        "group:test": ["test"]
+    },
+
+    "acls": [
+        {"action": "accept", "users": ["*"], "ports": ["*:*"]},
+    ],
+
+    "autoApprovers": {
+        "exitNode": ["tag:exit"],
+        "routes": {
+            "10.10.0.0/16": ["group:test"],
+            "10.11.0.0/16": ["test"],
+        }
+    }
+}
\ No newline at end of file

From 842c28adff914d6b6e6ba6cc4461df99b8f88175 Mon Sep 17 00:00:00 2001
From: Benjamin George Roberts <benjamin@bgroberts.id.au>
Date: Mon, 5 Sep 2022 09:33:53 +1000
Subject: [PATCH 4/7] replace netaddr usage with netip

---
 acls_types.go   |  7 ++++---
 machine.go      |  2 +-
 machine_test.go | 10 +++++-----
 3 files changed, 10 insertions(+), 9 deletions(-)

diff --git a/acls_types.go b/acls_types.go
index 0289e09932..6ee5bfdb25 100644
--- a/acls_types.go
+++ b/acls_types.go
@@ -111,7 +111,7 @@ func (policy ACLPolicy) IsZero() bool {
 
 // Returns the list of autoApproving namespaces, groups or tags for a given IPPrefix
 func (autoApprovers *AutoApprovers) GetRouteApprovers(
-	prefix netaddr.IPPrefix,
+	prefix netip.Prefix,
 ) ([]string, error) {
 	if prefix.Bits() == 0 {
 		return autoApprovers.ExitNode, nil // 0.0.0.0/0, ::/0 or equivalent
@@ -120,13 +120,14 @@ func (autoApprovers *AutoApprovers) GetRouteApprovers(
 	approverAliases := []string{}
 
 	for autoApprovedPrefix, autoApproverAliases := range autoApprovers.Routes {
-		autoApprovedPrefix, err := netaddr.ParseIPPrefix(autoApprovedPrefix)
+		autoApprovedPrefix, err := netip.ParsePrefix(autoApprovedPrefix)
+
 		if err != nil {
 			return nil, err
 		}
 
 		if autoApprovedPrefix.Bits() >= prefix.Bits() &&
-			autoApprovedPrefix.Contains(prefix.IP()) {
+			autoApprovedPrefix.Contains(prefix.Masked().Addr()) {
 			approverAliases = append(approverAliases, autoApproverAliases...)
 		}
 	}
diff --git a/machine.go b/machine.go
index 54984d8d23..5b53ef2237 100644
--- a/machine.go
+++ b/machine.go
@@ -936,7 +936,7 @@ func (h *Headscale) EnableAutoApprovedRoutes(machine *Machine) error {
 		return nil // This machine has no IPAddresses, so can't possibly match any autoApprovers ACLs
 	}
 
-	approvedRoutes := make([]netaddr.IPPrefix, 0, len(machine.HostInfo.RoutableIPs))
+	approvedRoutes := make([]netip.Prefix, 0, len(machine.HostInfo.RoutableIPs))
 	thisMachine := []Machine{*machine}
 
 	for _, advertisedRoute := range machine.HostInfo.RoutableIPs {
diff --git a/machine_test.go b/machine_test.go
index e8c9e9a037..7d50db79e2 100644
--- a/machine_test.go
+++ b/machine_test.go
@@ -1063,9 +1063,9 @@ func (s *Suite) TestAutoApproveRoutes(c *check.C) {
 
 	nodeKey := key.NewNode()
 
-	defaultRoute := netaddr.MustParseIPPrefix("0.0.0.0/0")
-	route1 := netaddr.MustParseIPPrefix("10.10.0.0/16")
-	route2 := netaddr.MustParseIPPrefix("10.11.0.0/16")
+	defaultRoute := netip.MustParsePrefix("0.0.0.0/0")
+	route1 := netip.MustParsePrefix("10.10.0.0/16")
+	route2 := netip.MustParsePrefix("10.11.0.0/16")
 
 	machine := Machine{
 		ID:             0,
@@ -1078,9 +1078,9 @@ func (s *Suite) TestAutoApproveRoutes(c *check.C) {
 		AuthKeyID:      uint(pak.ID),
 		HostInfo: HostInfo{
 			RequestTags: []string{"tag:exit"},
-			RoutableIPs: []netaddr.IPPrefix{defaultRoute, route1, route2},
+			RoutableIPs: []netip.Prefix{defaultRoute, route1, route2},
 		},
-		IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.1")},
+		IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.1")},
 	}
 
 	app.db.Save(&machine)

From 688cba7292219b5e311a62938c909b3319515b1d Mon Sep 17 00:00:00 2001
From: Benjamin George Roberts <benjamin@bgroberts.id.au>
Date: Wed, 7 Sep 2022 21:39:56 +1000
Subject: [PATCH 5/7] fix linting mistakes

---
 acls_types.go |  3 +--
 machine.go    | 15 +++++++--------
 2 files changed, 8 insertions(+), 10 deletions(-)

diff --git a/acls_types.go b/acls_types.go
index 6ee5bfdb25..903f848b2b 100644
--- a/acls_types.go
+++ b/acls_types.go
@@ -109,7 +109,7 @@ func (policy ACLPolicy) IsZero() bool {
 	return false
 }
 
-// Returns the list of autoApproving namespaces, groups or tags for a given IPPrefix
+// Returns the list of autoApproving namespaces, groups or tags for a given IPPrefix.
 func (autoApprovers *AutoApprovers) GetRouteApprovers(
 	prefix netip.Prefix,
 ) ([]string, error) {
@@ -121,7 +121,6 @@ func (autoApprovers *AutoApprovers) GetRouteApprovers(
 
 	for autoApprovedPrefix, autoApproverAliases := range autoApprovers.Routes {
 		autoApprovedPrefix, err := netip.ParsePrefix(autoApprovedPrefix)
-
 		if err != nil {
 			return nil, err
 		}
diff --git a/machine.go b/machine.go
index 5b53ef2237..ec0ab4649b 100644
--- a/machine.go
+++ b/machine.go
@@ -930,17 +930,16 @@ func (h *Headscale) EnableRoutes(machine *Machine, routeStrs ...string) error {
 	return nil
 }
 
-// Enabled any routes advertised by a machine that match the ACL autoApprovers policy
-func (h *Headscale) EnableAutoApprovedRoutes(machine *Machine) error {
+// Enabled any routes advertised by a machine that match the ACL autoApprovers policy.
+func (h *Headscale) EnableAutoApprovedRoutes(machine *Machine) {
 	if len(machine.IPAddresses) == 0 {
-		return nil // This machine has no IPAddresses, so can't possibly match any autoApprovers ACLs
+		return // This machine has no IPAddresses, so can't possibly match any autoApprovers ACLs
 	}
 
 	approvedRoutes := make([]netip.Prefix, 0, len(machine.HostInfo.RoutableIPs))
 	thisMachine := []Machine{*machine}
 
 	for _, advertisedRoute := range machine.HostInfo.RoutableIPs {
-
 		if contains(machine.EnabledRoutes, advertisedRoute) {
 			continue // Skip routes that are already enabled for the node
 		}
@@ -953,7 +952,8 @@ func (h *Headscale) EnableAutoApprovedRoutes(machine *Machine) error {
 				Str("advertisedRoute", advertisedRoute.String()).
 				Uint64("machineId", machine.ID).
 				Msg("Failed to resolve autoApprovers for advertised route")
-			return err
+
+			return
 		}
 
 		for _, approvedAlias := range routeApprovers {
@@ -965,7 +965,8 @@ func (h *Headscale) EnableAutoApprovedRoutes(machine *Machine) error {
 					log.Err(err).
 						Str("alias", approvedAlias).
 						Msg("Failed to expand alias when processing autoApprovers policy")
-					return err
+
+					return
 				}
 
 				// approvedIPs should contain all of machine's IPs if it matches the rule, so check for first
@@ -985,8 +986,6 @@ func (h *Headscale) EnableAutoApprovedRoutes(machine *Machine) error {
 			machine.EnabledRoutes = append(machine.EnabledRoutes, approvedRoute)
 		}
 	}
-
-	return nil
 }
 
 func (machine *Machine) RoutesToProto() *v1.Routes {

From d764f52f24ae63709b0597943c412b9b314da9c4 Mon Sep 17 00:00:00 2001
From: Benjamin George Roberts <benjamin@bgroberts.id.au>
Date: Fri, 23 Sep 2022 18:08:59 +1000
Subject: [PATCH 6/7] Update changelog

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 443305dad4..58c7c2f021 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@
 - Give a warning when running Headscale with reverse proxy improperly configured for WebSockets [#788](https://github.com/juanfont/headscale/pull/788)
 - Fix subnet routers with Primary Routes [#811](https://github.com/juanfont/headscale/pull/811)
 - Added support for JSON logs [#653](https://github.com/juanfont/headscale/issues/653)
+- Add support for evaluating `autoApprovers` ACL entries when a machine is registered [#763](https://github.com/juanfont/headscale/pull/763)
 
 ## 0.16.4 (2022-08-21)
 

From 7761a7b23ed411db526b0b6935c5f4975266ad93 Mon Sep 17 00:00:00 2001
From: Benjamin George Roberts <benjamin@bgroberts.id.au>
Date: Fri, 23 Sep 2022 18:46:35 +1000
Subject: [PATCH 7/7] fix autoapprover test following tagged authkey change

---
 machine_test.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/machine_test.go b/machine_test.go
index 25075bc22b..275ab14e22 100644
--- a/machine_test.go
+++ b/machine_test.go
@@ -1058,7 +1058,7 @@ func (s *Suite) TestAutoApproveRoutes(c *check.C) {
 	namespace, err := app.CreateNamespace("test")
 	c.Assert(err, check.IsNil)
 
-	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil)
+	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil)
 	c.Assert(err, check.IsNil)
 
 	nodeKey := key.NewNode()