mirror of
https://github.com/juanfont/headscale.git
synced 2026-07-07 16:40:21 +09:00
policy/v2: add sshtester compat runner
Replays recorded policy responses for the sshTests block. 200 captures must evaluate; non-200 captures must reject with the recorded body as a substring of the headscale error. Divergences are listed in knownSSHTesterDivergences.
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
// Replay golden HuJSON captures under testdata/sshtest_results/*.hujson:
|
||||
// the 200 path requires headscale's evaluateSSHTests to pass; the
|
||||
// non-200 path requires headscale to reject the same input with the
|
||||
// captured error body as a substring. Divergences are listed in
|
||||
// knownSSHTesterDivergences with the engine gap each represents.
|
||||
|
||||
package v2
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/juanfont/headscale/hscontrol/types/testcapture"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// knownSSHTesterDivergences documents captures where headscale and the
|
||||
// upstream control plane disagree. Each entry names the engine area a
|
||||
// follow-up needs to touch.
|
||||
var knownSSHTesterDivergences = map[string]string{
|
||||
"sshtest-malformed-dst-bare-ipv6": "bare-IPv6 sshTests dst: upstream parse-accepts then engine-rejects; headscale accepts (IPv4 mirror passes both sides)",
|
||||
}
|
||||
|
||||
func TestSSHTesterCompat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files, err := filepath.Glob(filepath.Join("testdata", "sshtest_results", "*.hujson"))
|
||||
require.NoError(t, err, "failed to glob test files")
|
||||
|
||||
if len(files) == 0 {
|
||||
t.Skip("no sshtest captures yet")
|
||||
}
|
||||
|
||||
users := setupSSHDataCompatUsers()
|
||||
|
||||
for _, file := range files {
|
||||
c, err := testcapture.Read(file)
|
||||
require.NoError(t, err, "reading %s", file)
|
||||
|
||||
t.Run(c.TestID, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if reason, skip := knownSSHTesterDivergences[c.TestID]; skip {
|
||||
t.Skip(reason)
|
||||
}
|
||||
|
||||
// Per-capture nodes: each capture pins its own
|
||||
// topology IPs, and policy `hosts` aliases reference
|
||||
// them by literal IP. A shared fixture would leave
|
||||
// host-alias dsts resolving to no nodes — surface
|
||||
// the path as a load-bearing failure instead.
|
||||
nodes := buildGrantsNodesFromCapture(users, c)
|
||||
|
||||
policyJSON := []byte(c.Input.FullPolicy)
|
||||
|
||||
pm, parseErr := NewPolicyManager(policyJSON, users, nodes.ViewSlice())
|
||||
|
||||
if c.Input.APIResponseCode == 200 {
|
||||
require.NoError(t, parseErr,
|
||||
"tailscale accepted this policy; headscale must parse it")
|
||||
|
||||
_, setErr := pm.SetPolicy(policyJSON)
|
||||
require.NoError(t, setErr,
|
||||
"tailscale accepted this policy; headscale sshTests must pass")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var got error
|
||||
|
||||
switch {
|
||||
case parseErr != nil:
|
||||
got = parseErr
|
||||
default:
|
||||
_, setErr := pm.SetPolicy(policyJSON)
|
||||
got = setErr
|
||||
}
|
||||
|
||||
require.Error(t, got, "tailscale rejected; headscale must reject too")
|
||||
|
||||
if c.Input.APIResponseBody == nil || c.Input.APIResponseBody.Message == "" {
|
||||
return
|
||||
}
|
||||
|
||||
want := c.Input.APIResponseBody.Message
|
||||
if !strings.Contains(got.Error(), want) {
|
||||
t.Errorf("error body mismatch\n tailscale wants: %q\n headscale got: %q", want, got.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+20077
File diff suppressed because it is too large
Load Diff
+20084
File diff suppressed because it is too large
Load Diff
+20102
File diff suppressed because it is too large
Load Diff
Vendored
+20094
File diff suppressed because it is too large
Load Diff
Vendored
+20036
File diff suppressed because it is too large
Load Diff
+20099
File diff suppressed because it is too large
Load Diff
Vendored
+13577
File diff suppressed because it is too large
Load Diff
+20090
File diff suppressed because it is too large
Load Diff
+20094
File diff suppressed because it is too large
Load Diff
+20082
File diff suppressed because it is too large
Load Diff
+20139
File diff suppressed because it is too large
Load Diff
+20099
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+20100
File diff suppressed because it is too large
Load Diff
+20099
File diff suppressed because it is too large
Load Diff
Vendored
+20088
File diff suppressed because it is too large
Load Diff
+20084
File diff suppressed because it is too large
Load Diff
+20084
File diff suppressed because it is too large
Load Diff
Vendored
+20082
File diff suppressed because it is too large
Load Diff
+20082
File diff suppressed because it is too large
Load Diff
Vendored
+20082
File diff suppressed because it is too large
Load Diff
Vendored
+13556
File diff suppressed because it is too large
Load Diff
+13472
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+18776
File diff suppressed because it is too large
Load Diff
+18804
File diff suppressed because it is too large
Load Diff
+18732
File diff suppressed because it is too large
Load Diff
+18751
File diff suppressed because it is too large
Load Diff
+18732
File diff suppressed because it is too large
Load Diff
+18749
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user