fix auto approver on register and new policy (#2506)

* fix issue auto approve route on register bug

This commit fixes an issue where routes where not approved
on a node during registration. This cause the auto approval
to require the node to readvertise the routes.

Fixes #2497
Fixes #2485

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* hsic: only set db policy if exist

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* policy: calculate changed based on policy and filter

v1 is a bit simpler than v2, it does not pre calculate the auto approver map
and we cannot tell if it is changed.

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
Kristoffer Dalby 2025-03-31 15:55:07 +02:00 committed by GitHub
parent e3521be705
commit 5a18e91317
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 575 additions and 217 deletions

View file

@ -866,6 +866,11 @@ func (h *Headscale) Serve() error {
log.Info().
Msg("ACL policy successfully reloaded, notifying nodes of change")
err = h.autoApproveNodes()
if err != nil {
log.Error().Err(err).Msg("failed to approve routes after new policy")
}
ctx := types.NotifyCtx(context.Background(), "acl-sighup", "na")
h.nodeNotifier.NotifyAll(ctx, types.UpdateFull())
}
@ -1166,3 +1171,36 @@ func (h *Headscale) loadPolicyManager() error {
return errOut
}
// autoApproveNodes mass approves routes on all nodes. It is _only_ intended for
// use when the policy is replaced. It is not sending or reporting any changes
// or updates as we send full updates after replacing the policy.
// TODO(kradalby): This is kind of messy, maybe this is another +1
// for an event bus. See example comments here.
func (h *Headscale) autoApproveNodes() error {
err := h.db.Write(func(tx *gorm.DB) error {
nodes, err := db.ListNodes(tx)
if err != nil {
return err
}
for _, node := range nodes {
changed := policy.AutoApproveRoutes(h.polMan, node)
if changed {
err = tx.Save(node).Error
if err != nil {
return err
}
h.primaryRoutes.SetRoutes(node.ID, node.SubnetRoutes()...)
}
}
return nil
})
if err != nil {
return fmt.Errorf("auto approving routes for nodes: %w", err)
}
return nil
}

View file

@ -10,6 +10,7 @@ import (
"time"
"github.com/juanfont/headscale/hscontrol/db"
"github.com/juanfont/headscale/hscontrol/policy"
"github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/util"
"gorm.io/gorm"
@ -212,6 +213,9 @@ func (h *Headscale) handleRegisterWithAuthKey(
nodeToRegister.Expiry = &regReq.Expiry
}
// Ensure any auto approved routes are handled before saving.
policy.AutoApproveRoutes(h.polMan, &nodeToRegister)
ipv4, ipv6, err := h.ipAlloc.Next()
if err != nil {
return nil, fmt.Errorf("allocating IPs: %w", err)
@ -266,7 +270,7 @@ func (h *Headscale) handleRegisterInteractive(
return nil, fmt.Errorf("generating registration ID: %w", err)
}
newNode := types.RegisterNode{
nodeToRegister := types.RegisterNode{
Node: types.Node{
Hostname: regReq.Hostinfo.Hostname,
MachineKey: machineKey,
@ -278,12 +282,15 @@ func (h *Headscale) handleRegisterInteractive(
}
if !regReq.Expiry.IsZero() {
newNode.Node.Expiry = &regReq.Expiry
nodeToRegister.Node.Expiry = &regReq.Expiry
}
// Ensure any auto approved routes are handled before saving.
policy.AutoApproveRoutes(h.polMan, &nodeToRegister.Node)
h.registrationCache.Set(
registrationId,
newNode,
nodeToRegister,
)
return &tailcfg.RegisterResponse{

View file

@ -739,6 +739,11 @@ func (api headscaleV1APIServer) SetPolicy(
// Only send update if the packet filter has changed.
if changed {
err = api.h.autoApproveNodes()
if err != nil {
return nil, err
}
ctx := types.NotifyCtx(context.Background(), "acl-update", "na")
api.h.nodeNotifier.NotifyAll(ctx, types.UpdateFull())
}

View file

@ -53,14 +53,15 @@ func NewPolicyManager(polB []byte, users []types.User, nodes types.Nodes) (*Poli
}
type PolicyManager struct {
mu sync.Mutex
pol *ACLPolicy
mu sync.Mutex
pol *ACLPolicy
polHash deephash.Sum
users []types.User
nodes types.Nodes
filterHash deephash.Sum
filter []tailcfg.FilterRule
filterHash deephash.Sum
}
// updateLocked updates the filter rules based on the current policy and nodes.
@ -71,13 +72,16 @@ func (pm *PolicyManager) updateLocked() (bool, error) {
return false, fmt.Errorf("compiling filter rules: %w", err)
}
polHash := deephash.Hash(pm.pol)
filterHash := deephash.Hash(&filter)
if filterHash == pm.filterHash {
if polHash == pm.polHash && filterHash == pm.filterHash {
return false, nil
}
pm.filter = filter
pm.filterHash = filterHash
pm.polHash = polHash
return true, nil
}