mirror of
https://github.com/juanfont/headscale.git
synced 2026-07-08 00:50:20 +09:00
@@ -514,124 +514,3 @@ func testACLSuccess(
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestACLCompatGlobalEquivalence verifies that the global filter
|
||||
// compilation path (PolicyManager.FilterForNode) produces the same
|
||||
// output as the per-node path (compileFilterRulesForNode +
|
||||
// ReduceFilterRules) for all ACL golden files that use ACL-only
|
||||
// policies (no autogroup:self, no via grants).
|
||||
//
|
||||
// Files using autogroup:self are skipped because they correctly
|
||||
// require per-node compilation (needsPerNodeFilter=true), where
|
||||
// the global path intentionally diverges.
|
||||
func TestACLCompatGlobalEquivalence(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files, err := filepath.Glob(
|
||||
filepath.Join("testdata", "acl_results", "ACL-*.hujson"),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, files)
|
||||
|
||||
users := setupACLCompatUsers()
|
||||
nodes := setupACLCompatNodes(users)
|
||||
|
||||
tested := 0
|
||||
skippedSelf := 0
|
||||
skippedErr := 0
|
||||
|
||||
for _, file := range files {
|
||||
tf := loadACLTestFile(t, file)
|
||||
|
||||
t.Run(tf.TestID, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if reason, ok := aclSkipReasons[tf.TestID]; ok {
|
||||
t.Skipf("TODO: %s", reason)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if tf.Error {
|
||||
skippedErr++
|
||||
|
||||
t.Skip("error case, not applicable")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
policyJSON := convertPolicyUserEmails(
|
||||
tf.Input.FullPolicy,
|
||||
)
|
||||
|
||||
// Check if policy uses autogroup:self — if so, the
|
||||
// global and per-node paths intentionally diverge.
|
||||
if strings.Contains(string(policyJSON), "autogroup:self") {
|
||||
skippedSelf++
|
||||
|
||||
t.Skip(
|
||||
"uses autogroup:self, global path " +
|
||||
"intentionally diverges",
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Per-node path.
|
||||
pol, err := unmarshalPolicy(policyJSON)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = pol.validate()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Global path.
|
||||
pm, err := NewPolicyManager(
|
||||
policyJSON, users, nodes.ViewSlice(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, node := range nodes {
|
||||
nv := node.View()
|
||||
|
||||
perNodeRules, err := pol.compileFilterRulesForNode(
|
||||
users, nv, nodes.ViewSlice(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
perNodeReduced := policyutil.ReduceFilterRules(
|
||||
nv, perNodeRules,
|
||||
)
|
||||
|
||||
globalReduced, err := pm.FilterForNode(nv)
|
||||
require.NoError(t, err)
|
||||
|
||||
opts := append(
|
||||
cmpOptions(),
|
||||
cmpopts.EquateEmpty(),
|
||||
)
|
||||
if diff := cmp.Diff(
|
||||
perNodeReduced,
|
||||
globalReduced,
|
||||
opts...,
|
||||
); diff != "" {
|
||||
t.Errorf(
|
||||
"%s/%s: global vs per-node "+
|
||||
"filter mismatch "+
|
||||
"(-perNode +global):\n%s",
|
||||
tf.TestID,
|
||||
node.GivenName,
|
||||
diff,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
tested++
|
||||
})
|
||||
}
|
||||
|
||||
t.Logf(
|
||||
"ACL global equivalence: tested=%d, "+
|
||||
"skippedSelf=%d, skippedErr=%d",
|
||||
tested, skippedSelf, skippedErr,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -618,116 +618,3 @@ func testGrantSuccess(
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGrantsCompatGlobalEquivalence verifies that the global filter
|
||||
// compilation path (PolicyManager.FilterForNode) produces the same
|
||||
// output as the per-node path (compileFilterRulesForNode +
|
||||
// ReduceFilterRules) for all GRANT golden files that don't require
|
||||
// per-node compilation.
|
||||
//
|
||||
// Files using via grants or autogroup:self are skipped because they
|
||||
// correctly require per-node compilation (needsPerNodeFilter=true).
|
||||
func TestGrantsCompatGlobalEquivalence(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files, err := filepath.Glob(
|
||||
filepath.Join("testdata", "grant_results", "GRANT-*.hujson"),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, files)
|
||||
|
||||
users := setupGrantsCompatUsers()
|
||||
allNodes := setupGrantsCompatNodes(users)
|
||||
|
||||
for _, file := range files {
|
||||
tf := loadGrantTestFile(t, file)
|
||||
|
||||
t.Run(tf.TestID, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if reason, ok := grantSkipReasons[tf.TestID]; ok {
|
||||
t.Skipf("TODO: %s", reason)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if tf.Error {
|
||||
t.Skip("error case")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
policyJSON := convertPolicyUserEmails(
|
||||
tf.Input.FullPolicy,
|
||||
)
|
||||
policyStr := string(policyJSON)
|
||||
|
||||
// Skip files that require per-node compilation.
|
||||
if strings.Contains(policyStr, "autogroup:self") {
|
||||
t.Skip("uses autogroup:self")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if strings.Contains(policyStr, "\"via\"") {
|
||||
t.Skip("uses via grants")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Select nodes based on topology size.
|
||||
nodes := allNodes
|
||||
if _, hasNewNodes := tf.Captures["exit-a"]; !hasNewNodes {
|
||||
nodes = allNodes[:8]
|
||||
}
|
||||
|
||||
// Per-node path.
|
||||
pol, err := unmarshalPolicy(policyJSON)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = pol.validate()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Global path.
|
||||
pm, err := NewPolicyManager(
|
||||
policyJSON, users, nodes.ViewSlice(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, node := range nodes {
|
||||
nv := node.View()
|
||||
|
||||
perNodeRules, err := pol.compileFilterRulesForNode(
|
||||
users, nv, nodes.ViewSlice(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
perNodeReduced := policyutil.ReduceFilterRules(
|
||||
nv, perNodeRules,
|
||||
)
|
||||
|
||||
globalReduced, err := pm.FilterForNode(nv)
|
||||
require.NoError(t, err)
|
||||
|
||||
opts := append(
|
||||
cmpOptions(),
|
||||
cmpopts.EquateEmpty(),
|
||||
)
|
||||
if diff := cmp.Diff(
|
||||
perNodeReduced,
|
||||
globalReduced,
|
||||
opts...,
|
||||
); diff != "" {
|
||||
t.Errorf(
|
||||
"%s/%s: global vs per-node "+
|
||||
"filter mismatch "+
|
||||
"(-perNode +global):\n%s",
|
||||
tf.TestID,
|
||||
node.GivenName,
|
||||
diff,
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1092,100 +1092,6 @@ func TestRoutesCompatReduceRoutes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestRoutesCompatGlobalEquivalence verifies that the global filter
|
||||
// compilation path (compileFilterRules + ReduceFilterRules, used in
|
||||
// production for ACL-only policies) produces the same output as the
|
||||
// per-node path (compileFilterRulesForNode + ReduceFilterRules, used
|
||||
// by TestRoutesCompat).
|
||||
//
|
||||
// All 98 ROUTES golden files use ACL-only policies (no autogroup:self,
|
||||
// no via grants), so production code takes the global path. The
|
||||
// TestRoutesCompat test always uses the per-node path. If these two
|
||||
// paths ever diverge, this test will detect it.
|
||||
func TestRoutesCompatGlobalEquivalence(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files, err := filepath.Glob(
|
||||
filepath.Join("testdata", "routes_results", "ROUTES-*.hujson"),
|
||||
)
|
||||
require.NoError(t, err, "failed to glob test files")
|
||||
require.NotEmpty(
|
||||
t,
|
||||
files,
|
||||
"no ROUTES-*.hujson test files found",
|
||||
)
|
||||
|
||||
for _, file := range files {
|
||||
tf := loadRoutesTestFile(t, file)
|
||||
|
||||
t.Run(tf.TestID, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if reason, ok := routesSkipReasons[tf.TestID]; ok {
|
||||
t.Skipf("TODO: %s", reason)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
users, nodes := buildRoutesUsersAndNodes(t, tf.Topology)
|
||||
policyJSON := convertPolicyUserEmails(tf.Input.FullPolicy)
|
||||
|
||||
// Per-node path: compile per-node, then reduce.
|
||||
pol, err := unmarshalPolicy(policyJSON)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = pol.validate()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Global path: create PolicyManager (compiles global
|
||||
// filter internally).
|
||||
pm, err := NewPolicyManager(
|
||||
policyJSON, users, nodes.ViewSlice(),
|
||||
)
|
||||
require.NoError(t, err,
|
||||
"%s: failed to create PolicyManager", tf.TestID,
|
||||
)
|
||||
|
||||
for _, node := range nodes {
|
||||
nv := node.View()
|
||||
|
||||
// Per-node path (what TestRoutesCompat uses).
|
||||
perNodeRules, err := pol.compileFilterRulesForNode(
|
||||
users, nv, nodes.ViewSlice(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
perNodeReduced := policyutil.ReduceFilterRules(
|
||||
nv, perNodeRules,
|
||||
)
|
||||
|
||||
// Global path (what production FilterForNode
|
||||
// uses for ACL-only policies).
|
||||
globalReduced, err := pm.FilterForNode(nv)
|
||||
require.NoError(t, err)
|
||||
|
||||
opts := append(
|
||||
cmpOptions(),
|
||||
cmpopts.EquateEmpty(),
|
||||
)
|
||||
if diff := cmp.Diff(
|
||||
perNodeReduced,
|
||||
globalReduced,
|
||||
opts...,
|
||||
); diff != "" {
|
||||
t.Errorf(
|
||||
"%s/%s: global vs per-node filter "+
|
||||
"mismatch (-perNode +global):\n%s",
|
||||
tf.TestID,
|
||||
node.GivenName,
|
||||
diff,
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRoutesCompatExitNodePeerVisibility validates that CanAccess
|
||||
// correctly handles exit node peer visibility for the b-series golden
|
||||
// files. These files exercise exit route behaviors (b1-b10) which
|
||||
|
||||
Reference in New Issue
Block a user