policy/v2, servertest: compare rules after a device is deleted

SaaS keeps a deleted device's addresses in other nodes' filter and SSH
rules until a later policy push. Headscale drops them with the device.
This commit is contained in:
Kristoffer Dalby committed 2026-10-30 14:55:38 +00:00
1 parent bedacd4662
commit ea6a526fa7
3 files changed
+17 -29

No files matched your search

@@ -257,13 +257,6 @@ var routesSkipReasons = map[string]string{
"routes-m6-subnet-to-subnet-multi-route/m-odin2/peers": sameUserPeers,
}
// notCompared reports whether key falls under one of prefixes.
func notCompared(prefixes []string, key string) bool {
return slices.ContainsFunc(prefixes, func(p string) bool {
return strings.HasPrefix(key, p)
})
}
const filterAddressForm = "FILTER_ADDRESS_FORM: same addresses, different wire form"
const sameUserPeers = "SAME_USER_PEERS: SaaS lists a user's own devices as each other's peers with no rule linking them; headscale peers only what the policy links"
@@ -41,19 +41,18 @@ var policySequenceSkipReasons = map[string]string{
"policy-generation/G3/rt-b/filter": viaRuleMerged,
"policy-generation/G4/rt-b/filter": viaRuleMerged,
"policy-generation/G5/rt-b/filter": viaRuleMerged,
"policy-generation/G7/rt-plain/filter": deletedDeviceInRules,
"policy-generation/G7/srv/ssh-rules": deletedDeviceInRules,
"policy-generation/G7/srv/uid-zero": deletedDeviceInRules,
"policy-generation/G7/srv/filter": deletedDeviceInRules,
"policy-generation/G7/rt-b/filter": "VIA_RULE_MERGED+DELETED_DEVICE_IN_RULES: the via grant's rule apart from the ACL rule, and the deleted device's addresses kept",
}
const viaRuleMerged = "VIA_RULE_MERGED: on a via router headscale merges the via grant's filter rule with an ACL rule of the same sources; SaaS sends the two apart"
// policySequenceNotCompared lists, by key prefix, checks left out: SaaS's
// answer there is in doubt, so it is no reference.
var policySequenceNotCompared = []string{
"policy-generation/G7/rt-plain/filter",
"policy-generation/G7/srv/ssh-rules",
"policy-generation/G7/srv/uid-zero",
"policy-generation/G7/srv/filter",
"policy-generation/G7/rt-b/filter",
}
const (
viaRuleMerged = "VIA_RULE_MERGED: on a via router headscale merges the via grant's filter rule with an ACL rule of the same sources; SaaS sends the two apart"
deletedDeviceInRules = "DELETED_DEVICE_IN_RULES: SaaS keeps a deleted device's addresses in other nodes' filter and SSH rules until the next policy change; headscale drops them with the device"
)
// TestPolicySequenceCompat follows the capture with one PolicyManager, as
// headscale does: SetNodes after each node change, SetPolicy for the edit,
@@ -144,10 +143,6 @@ func runPolicySequenceCompat(t *testing.T, id string) {
t.Run(alias, func(t *testing.T) {
check := func(name string, mismatch func() string) {
if notCompared(policySequenceNotCompared, key+name) {
return
}
evaluated[key+name] = true
compatCheck(t, policySequenceSkipReasons, key+name, mismatch)
}
@@ -105,22 +105,23 @@ var mapStreamSkipReasons = map[string]string{
"policy-generation/G3/thor-2/peer-owners": sameUserPeers,
"policy-generation/G4/thor-1/peer-owners": sameUserPeers,
"policy-generation/G4/thor-2/peer-owners": sameUserPeers,
"policy-generation/G7/rt-b/filter": deletedDeviceInRules,
"policy-generation/G7/rt-plain/filter": deletedDeviceInRules,
"policy-generation/G7/srv/filter": deletedDeviceInRules,
"policy-generation/G7/srv/ssh": deletedDeviceInRules,
}
const (
policyResponseToEveryNode = "POLICY_RESPONSE_TO_EVERY_NODE: headscale sends every connected node a policy response when the policy, a node's tags or approved routes, or the set of nodes change; SaaS sends only the nodes whose peers or filter change"
restartEndpointsKept = "RESTART_ENDPOINTS_KEPT: headscale keeps a restarted node's last endpoints at its peers until it reports new ones; SaaS sends peers the node without endpoints until then"
announcePeerResent = "ANNOUNCE_PEER_RESENT: headscale sends a router's peers its entry again whenever its announced routes change; SaaS sends the unchanged entry only after some announcements"
deletedDeviceInRules = "DELETED_DEVICE_IN_RULES: SaaS keeps a deleted device's addresses in other nodes' filter and SSH rules until the next policy change; headscale drops them with the device"
)
// mapStreamNotCompared lists, by key prefix, checks left out: SaaS's answer
// there is in doubt, so it is no reference.
var mapStreamNotCompared = []string{
"policy-generation/G7/rt-b/filter",
"policy-generation/G7/rt-plain/filter",
"policy-generation/G7/srv/filter",
"policy-generation/G7/srv/ssh",
"user-role/S1/own-a/frames",
"user-role/S1/own-b/frames",
"user-role/S1/own-b/peers",
@@ -1179,8 +1180,7 @@ func replayPolicySequence(t *testing.T, id string) {
}
t.Run(step.Name, func(t *testing.T) {
prefix := id + "/" + step.Name + "/"
holdChecks(t, mapStreamSkipReasons, prefix, compared(prefix, sequenceChecks(t, r, step, saasNames, names)), evaluated)
holdChecks(t, mapStreamSkipReasons, id+"/"+step.Name+"/", sequenceChecks(t, r, step, saasNames, names), evaluated)
})
}