policy/v2: SaaS-derived compat tests for nodeAttrs
Adds a data-driven test that loads testdata/nodeattrs_results/*.hujson and diffs the captured SaaS-rendered netmaps against headscale's compileNodeAttrs output. Each capture is one scenario the SaaS control plane has rendered against the same policy headscale is asked to compile -- the test enforces shape parity per node. tailnet_state_caps.go enumerates the caps SaaS emits where headscale has no equivalent concept yet (user-role admin/owner, tailnet lock, services host, app connectors, internal magicsock and SSH tuning, tailnet-state metadata) plus the always-on baseline (admin, ssh, file-sharing) and the taildrive pair. stripUnmodelledTailnetStateCaps filters both sides of cmp.Diff so the comparison focuses on the policy-driven caps. PeerCapMap encodes which caps the Tailscale client reads from the peer view (suggest-exit-node when exit routes are approved, etc.) for use by the mapper. testcapture switches to typed tailcfg/netmap/filtertype/apitype values so schema drift between the capture tool and headscale becomes a compile error rather than a silent test failure. Existing compat suites (acl, grants, routes, ssh, issue_3212) move to the typed shape. The 53 SelfNode netmap captures and the 7 anonymizer-corrupted suggest-charmander -> suggest-exit-node restorations in routes_results / issue_3212 ride along.
This commit is contained in:
parent
3f73ed5404
commit
078b9e308f
72 changed files with 463447 additions and 183 deletions
|
|
@ -1,6 +1,6 @@
|
|||
// This file implements a data-driven test runner for grant compatibility
|
||||
// tests. It loads HuJSON golden files from testdata/grant_results/grant-*.hujson
|
||||
// and via-grant-*.hujson, captured from Tailscale SaaS by tscap, and compares
|
||||
// and via-grant-*.hujson, captured from a Tailscale-hosted control plane, and compares
|
||||
// headscale's grants engine output against the captured packet filter rules.
|
||||
//
|
||||
// Each file is a testcapture.Capture containing:
|
||||
|
|
@ -35,8 +35,8 @@ import (
|
|||
|
||||
// setupGrantsCompatUsers returns the 3 test users for grants compatibility tests.
|
||||
// Users get norse-god names; nodes get original-151 pokémon names — matching
|
||||
// the anonymized identifiers tscap writes into the capture files
|
||||
// (see github.com/kradalby/tscap/anonymize).
|
||||
// the anonymized identifiers the capture tool writes into the capture files
|
||||
// .
|
||||
func setupGrantsCompatUsers() types.Users {
|
||||
return types.Users{
|
||||
{Model: gorm.Model{ID: 1}, Name: "odin", Email: "odin@example.com"},
|
||||
|
|
@ -269,7 +269,7 @@ func findGrantsNode(nodes types.Nodes, name string) *types.Node {
|
|||
}
|
||||
|
||||
// buildGrantsNodesFromCapture constructs types.Nodes from a capture's
|
||||
// topology section. Each scenario in tscap uses clean-slate mode, so
|
||||
// topology section. Each scenario in the capture tool uses clean-slate mode, so
|
||||
// node IPs differ between scenarios; this builds the node set with
|
||||
// the IPs that were actually present during that capture.
|
||||
func buildGrantsNodesFromCapture(
|
||||
|
|
@ -332,7 +332,7 @@ func buildGrantsNodesFromCapture(
|
|||
}
|
||||
|
||||
// convertPolicyUserEmails used to map SaaS-side emails to @example.com.
|
||||
// tscap now anonymizes the policy JSON at write time (kratail2tid -> odin,
|
||||
// captures anonymize the policy JSON at write time (kratail2tid -> odin,
|
||||
// kristoffer -> thor, monitorpasskeykradalby -> freya), so the captured
|
||||
// FullPolicy is already in its final form and this is a passthrough that
|
||||
// just adapts the captured string value to the []byte that the policy
|
||||
|
|
@ -405,12 +405,12 @@ func TestGrantsCompat(t *testing.T) {
|
|||
}
|
||||
|
||||
// Build nodes per-scenario from this file's topology.
|
||||
// tscap uses clean-slate mode, so each scenario has
|
||||
// the capture tool uses clean-slate mode, so each scenario has
|
||||
// different node IPs.
|
||||
nodes := buildGrantsNodesFromCapture(users, tf)
|
||||
|
||||
// Use the captured full policy as is (anonymization
|
||||
// in tscap already rewrote SaaS emails).
|
||||
// the capture tool already rewrote SaaS emails).
|
||||
policyJSON := convertPolicyUserEmails(tf.Input.FullPolicy)
|
||||
|
||||
if tf.Input.APIResponseCode == 400 || tf.Error {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue