policy/v2: compare compat captures through the policy manager

ACL, grant and SSH captures now also check FilterForNode and SSHPolicy,
which add the global merge and caches the per-node compile skips.
This commit is contained in:
Kristoffer Dalby committed 2026-10-27 14:15:01 +00:00
1 parent d11b876ea1
commit 412303dd29
3 files changed
+60 -7

No files matched your search

@@ -343,6 +343,11 @@ func testACLSuccess(
tf.TestID,
)
// The manager adds the global merge and its caches on top of the
// per-node compile; nodes get their filter from it.
pm, err := NewPolicyManager(policyJSON, users, nodes.ViewSlice())
require.NoError(t, err, "%s: policy manager should load the policy", tf.TestID)
for nodeName, capture := range tf.Captures {
t.Run(nodeName, func(t *testing.T) {
node := findNodeByGivenName(nodes, nodeName)
@@ -386,6 +391,18 @@ func testACLSuccess(
diff,
)
}
pmRules, err := pm.FilterForNode(node.View())
require.NoError(t, err)
if diff := cmp.Diff(wantRules, pmRules, opts...); diff != "" {
t.Errorf(
"%s/%s: policy manager filter rules mismatch (-want +got):\n%s",
tf.TestID,
nodeName,
diff,
)
}
})
}
}
@@ -292,6 +292,11 @@ func testGrantSuccess(
err = pol.validate()
require.NoError(t, err, "%s: policy should validate successfully", tf.TestID)
// The manager adds the global merge and its caches on top of the
// per-node compile; nodes get their filter from it.
pm, err := NewPolicyManager(policyJSON, users, nodes.ViewSlice())
require.NoError(t, err, "%s: policy manager should load the policy", tf.TestID)
for nodeName, capture := range tf.Captures {
t.Run(nodeName, func(t *testing.T) {
node := findGrantsNode(nodes, nodeName)
@@ -323,6 +328,18 @@ func testGrantSuccess(
diff,
)
}
pmRules, err := pm.FilterForNode(node.View())
require.NoError(t, err)
if diff := cmp.Diff(wantRules, pmRules, opts...); diff != "" {
t.Errorf(
"%s/%s: policy manager filter rules mismatch (-tailscale +headscale):\n%s",
tf.TestID,
nodeName,
diff,
)
}
})
}
}
@@ -171,6 +171,10 @@ func TestSSHDataCompat(t *testing.T) {
tf.Input.FullPolicy,
)
// Nodes get their SSH policy through the manager's cache.
pm, err := NewPolicyManager(policyJSON, users, nodes.ViewSlice())
require.NoError(t, err, "%s: policy manager should load the policy", tf.TestID)
for nodeName, capture := range tf.Captures {
t.Run(nodeName, func(t *testing.T) {
node := findNodeByGivenName(nodes, nodeName)
@@ -220,15 +224,30 @@ func TestSSHDataCompat(t *testing.T) {
)
}
pmSSH, err := pm.SSHPolicy("https://unused", node.View())
require.NoError(t, err)
if diff := cmp.Diff(wantSSH, pmSSH, opts...); diff != "" {
t.Errorf(
"%s/%s: policy manager SSH policy mismatch (-tailscale +headscale):\n%s",
tf.TestID,
nodeName,
diff,
)
}
// EquateEmpty hides "rules":null vs "rules":[];
// pin the captured shape separately.
if gotSSH != nil && capture.Netmap != nil &&
capture.Netmap.SSHPolicy != nil {
assert.Equalf(t,
capture.Netmap.SSHPolicy.Rules == nil,
gotSSH.Rules == nil,
"%s/%s: rules null-vs-[] mismatch", tf.TestID, nodeName,
)
if capture.Netmap != nil && capture.Netmap.SSHPolicy != nil {
for _, got := range []*tailcfg.SSHPolicy{gotSSH, pmSSH} {
if got != nil {
assert.Equalf(t,
capture.Netmap.SSHPolicy.Rules == nil,
got.Rules == nil,
"%s/%s: rules null-vs-[] mismatch", tf.TestID, nodeName,
)
}
}
}
// Separate presence check: the fields ignored by