mirror of
https://github.com/juanfont/headscale.git
synced 2026-07-17 16:36:02 +00:00
policy/v2: branch tailscale_ssh_data_compat_test on APIResponseCode
Rejection captures (APIResponseCode != 200) now route through NewPolicyManager + SetPolicy mirroring sshtester_compat_test.go; the SaaS Message must be a substring of headscale's error. The accepted path (200) keeps the existing per-node SSHRules comparison. Adds 28 ssh-malformed-* captures and a parallel sshRejectSkipReasons map for 4xx scenarios where headscale and SaaS legitimately disagree.
This commit is contained in:
parent
4c4cebdc29
commit
9026f810fe
29 changed files with 562719 additions and 126 deletions
|
|
@ -3,13 +3,17 @@
|
|||
// from a Tailscale-hosted control plane, and compares headscale's SSH policy
|
||||
// compilation against the captured SSH rules.
|
||||
//
|
||||
// Each file is a testcapture.Capture containing:
|
||||
// - The full policy that was POSTed to Tailscale SaaS (we use tf.Input.FullPolicy
|
||||
// directly instead of reconstructing it from a sub-section)
|
||||
// - The expected SSH rules for each of the 8 test nodes (in tf.Captures[name].SSHRules)
|
||||
// Each capture is one of:
|
||||
// - APIResponseCode == 200 — SaaS accepted the policy; the captured
|
||||
// per-node SSH rules in tf.Captures[name].SSHRules are the source of
|
||||
// truth, and headscale's compileSSHPolicy must produce the same shape.
|
||||
// - APIResponseCode != 200 — SaaS rejected the policy at the API; the
|
||||
// captured Message is the body the user saw. headscale must reject
|
||||
// the same input with an error whose text contains that body as a
|
||||
// substring (mirroring sshtester_compat_test.go).
|
||||
//
|
||||
// Tests known to fail due to unimplemented features or known differences are
|
||||
// skipped with a TODO comment explaining the root cause.
|
||||
// Tests known to diverge are listed in sshSkipReasons (200 path) or
|
||||
// sshRejectSkipReasons (!= 200 path) with a TODO explaining the gap.
|
||||
//
|
||||
// Test data source: testdata/ssh_results/ssh-*.hujson
|
||||
// Source format: github.com/juanfont/headscale/hscontrol/types/testcapture
|
||||
|
|
@ -71,8 +75,11 @@ func loadSSHTestFile(t *testing.T, path string) *testcapture.Capture {
|
|||
return c
|
||||
}
|
||||
|
||||
// sshSkipReasons documents why each skipped test fails and what needs to be
|
||||
// fixed. Tests are grouped by root cause to identify high-impact changes.
|
||||
// sshSkipReasons documents APIResponseCode == 200 captures where SaaS
|
||||
// accepted the policy but headscale either does not yet support the
|
||||
// shape or rejects it stricter than SaaS does. Each entry should
|
||||
// describe the gap a follow-up PR needs to close (or justify why
|
||||
// headscale is intentionally stricter).
|
||||
var sshSkipReasons = map[string]string{
|
||||
// USER_PASSKEY_WILDCARD (2 tests)
|
||||
//
|
||||
|
|
@ -81,17 +88,87 @@ var sshSkipReasons = map[string]string{
|
|||
"ssh-b5": "user:*@passkey wildcard not supported in headscale",
|
||||
"ssh-d10": "user:*@passkey wildcard not supported in headscale",
|
||||
|
||||
// DOMAIN_NOT_ASSOCIATED (4 tests)
|
||||
// AUTOGROUP_AS_SSH_USER (4 tests)
|
||||
//
|
||||
// SaaS accepts `users: ["autogroup:X"]` on an `ssh` rule for
|
||||
// every autogroup name (member, self, tagged, internet); the
|
||||
// resolution then produces zero principals at compile time for
|
||||
// the ones that don't map to actual logins. headscale's
|
||||
// validate() restricts users-side autogroups to just
|
||||
// `autogroup:nonroot` via autogroupForSSHUser, so the policy
|
||||
// fails to parse. Either widen autogroupForSSHUser to mirror
|
||||
// SaaS (and let compilation drop unsupported entries) or
|
||||
// document that headscale is intentionally stricter.
|
||||
"ssh-malformed-user-autogroup-internet": "headscale rejects `users: [autogroup:internet]`; SaaS accepts and compiles to zero principals",
|
||||
"ssh-malformed-user-autogroup-member": "headscale rejects `users: [autogroup:member]`; SaaS accepts and compiles to zero principals",
|
||||
"ssh-malformed-user-autogroup-self": "headscale rejects `users: [autogroup:self]`; SaaS accepts and compiles to zero principals",
|
||||
"ssh-malformed-user-autogroup-tagged": "headscale rejects `users: [autogroup:tagged]`; SaaS accepts and compiles to zero principals",
|
||||
|
||||
// LOCALPART_SHAPE (4 tests)
|
||||
//
|
||||
// SaaS accepts every shape under the `localpart:` prefix
|
||||
// (`localpart:`, `localpart:foo`, `localpart:*@`, and
|
||||
// `localpart:foo@example.com`) and compiles each to zero
|
||||
// principals. headscale's SSHUser.ParseLocalpart enforces the
|
||||
// strict `localpart:*@<domain>` shape and returns errors for
|
||||
// every other form — see hscontrol/policy/v2/types.go
|
||||
// ParseLocalpart and the call site in validate(). The strict
|
||||
// path is arguably correct (these strings can never produce a
|
||||
// useful principal) but it surfaces as a parse error rather
|
||||
// than the SaaS behaviour of silently producing zero
|
||||
// principals.
|
||||
"ssh-malformed-user-localpart-empty": "headscale rejects `localpart:` (missing @); SaaS accepts and compiles to zero principals",
|
||||
"ssh-malformed-user-localpart-no-at": "headscale rejects `localpart:foo` (missing @); SaaS accepts and compiles to zero principals",
|
||||
"ssh-malformed-user-localpart-no-domain": "headscale rejects `localpart:*@` (empty domain); SaaS accepts and compiles to zero principals",
|
||||
"ssh-malformed-user-localpart-no-glob": "headscale rejects `localpart:foo@example.com` (local part not *); SaaS accepts and compiles to zero principals",
|
||||
|
||||
// CHECK_PERIOD_MIN (2 tests)
|
||||
//
|
||||
// SaaS allows `checkPeriod: "0s"` and any sub-minute value;
|
||||
// headscale's SSHCheckPeriodMin = 1 minute rejects both. The
|
||||
// minimum may be a deliberate hardening, but it diverges from
|
||||
// SaaS — needs an explicit decision.
|
||||
"ssh-malformed-checkperiod-zero": "headscale rejects checkPeriod `0s`; SaaS accepts",
|
||||
"ssh-malformed-checkperiod-too-short": "headscale rejects checkPeriod `30s`; SaaS accepts (no minimum)",
|
||||
}
|
||||
|
||||
// sshRejectSkipReasons documents APIResponseCode != 200 captures where
|
||||
// headscale and SaaS legitimately disagree on whether the policy should
|
||||
// be rejected (or where headscale rejects with different wording).
|
||||
var sshRejectSkipReasons = map[string]string{
|
||||
// WORDING_DIFFERS (4 tests)
|
||||
//
|
||||
// headscale rejects these inputs but with different error
|
||||
// text, so the SaaS message is not a substring of headscale's.
|
||||
"ssh-malformed-action-deny": `headscale rejects "deny" with 'invalid SSH action: "deny", must be one of: accept, check' vs SaaS '"deny" is not a valid action'`,
|
||||
"ssh-malformed-action-empty": `headscale rejects empty action with 'invalid SSH action: "", must be one of: accept, check' vs SaaS 'action must be specified'`,
|
||||
"ssh-malformed-checkperiod-malformed": `headscale rejects malformed duration with 'not a valid duration string: "abc"' vs SaaS 'time: invalid duration "abc"'`,
|
||||
"ssh-malformed-checkperiod-too-long": "headscale rejects 200h with 'checkPeriod above maximum of 168 hours (1 week)' vs SaaS 'checkPeriod 200h0m0s is above the max (168h)'",
|
||||
|
||||
// MISSING_VALIDATIONS (5 tests)
|
||||
//
|
||||
// SaaS rejects these inputs; headscale accepts them today.
|
||||
// Fixes belong in hscontrol/policy/v2/types.go validate().
|
||||
"ssh-malformed-action-missing": "headscale accepts missing `action`; SaaS rejects with `action must be specified`",
|
||||
"ssh-malformed-acceptenv-empty": "headscale accepts `acceptEnv: [\"\"]`; SaaS rejects with `acceptEnv values cannot be empty`",
|
||||
"ssh-malformed-users-empty-array": "headscale accepts empty `users: []`; SaaS rejects with `users must be specified`",
|
||||
"ssh-malformed-users-missing": "headscale accepts missing `users`; SaaS rejects with `users must be specified`",
|
||||
"ssh-malformed-user-empty": `headscale accepts empty username ""; SaaS rejects with 'user "" is not valid'`,
|
||||
"ssh-malformed-user-wildcard": `headscale accepts wildcard "*"; SaaS rejects with 'user "*" is not valid' (same gap as sshtester_compat_test.go's sshtest-user-wildcard)`,
|
||||
|
||||
// DOMAIN_NOT_ASSOCIATED (5 tests)
|
||||
//
|
||||
// SaaS validates that email domains in user:*@domain and
|
||||
// localpart:*@domain expressions are configured tailnet domains.
|
||||
// headscale has no concept of "associated tailnet domains" — it
|
||||
// only has users with email addresses. These policies are
|
||||
// legitimately rejected by SaaS but not by headscale.
|
||||
// localpart:*@domain expressions are configured tailnet
|
||||
// domains. headscale has no concept of "associated tailnet
|
||||
// domains" — it only has users with email addresses. These
|
||||
// policies are legitimately rejected by SaaS but not by
|
||||
// headscale.
|
||||
"ssh-b4": "domain validation: headscale has no 'associated tailnet domains' concept",
|
||||
"ssh-d1": "domain validation: headscale has no 'associated tailnet domains' concept",
|
||||
"ssh-e1": "domain validation: headscale has no 'associated tailnet domains' concept",
|
||||
"ssh-e2": "domain validation: headscale has no 'associated tailnet domains' concept",
|
||||
"ssh-malformed-user-localpart-multi-glob": "domain validation: headscale has no 'associated tailnet domains' concept (same gap as ssh-b4/d1/e1/e2)",
|
||||
}
|
||||
|
||||
// TestSSHDataCompat is a data-driven test that loads all ssh-*.hujson test
|
||||
|
|
@ -137,7 +214,63 @@ func TestSSHDataCompat(t *testing.T) {
|
|||
t.Run(tf.TestID, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Check if this test is in the skip list
|
||||
// Build nodes per-scenario from this file's topology.
|
||||
// tscap 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 rewrites SaaS emails to @example.com.
|
||||
policyJSON := []byte(tf.Input.FullPolicy)
|
||||
|
||||
// Branch on the SaaS response code. Captures with
|
||||
// APIResponseCode != 200 are policies SaaS rejected at
|
||||
// the API; headscale must reject the same input. The
|
||||
// 200 path falls through to the existing per-node SSH
|
||||
// rule comparison.
|
||||
if tf.Input.APIResponseCode != 200 {
|
||||
if reason, ok := sshRejectSkipReasons[tf.TestID]; ok {
|
||||
t.Skipf(
|
||||
"TODO: %s — see sshRejectSkipReasons for details",
|
||||
reason,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
pm, parseErr := NewPolicyManager(policyJSON, users, nodes.ViewSlice())
|
||||
|
||||
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 tf.Input.APIResponseBody == nil ||
|
||||
tf.Input.APIResponseBody.Message == "" {
|
||||
return
|
||||
}
|
||||
|
||||
want := tf.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(),
|
||||
)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// APIResponseCode == 200: SaaS accepted; headscale must
|
||||
// match the captured per-node SSH rules.
|
||||
if reason, ok := sshSkipReasons[tf.TestID]; ok {
|
||||
t.Skipf(
|
||||
"TODO: %s — see sshSkipReasons comments for details",
|
||||
|
|
@ -147,29 +280,13 @@ func TestSSHDataCompat(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
// SaaS rejected this policy — verify headscale also rejects it.
|
||||
if tf.Error {
|
||||
testSSHError(t, tf)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Build nodes per-scenario from this file's topology.
|
||||
// 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
|
||||
// captures already rewrite SaaS emails to @example.com.
|
||||
policyJSON := tf.Input.FullPolicy
|
||||
|
||||
pol, err := unmarshalPolicy([]byte(policyJSON))
|
||||
pol, err := unmarshalPolicy(policyJSON)
|
||||
require.NoError(
|
||||
t,
|
||||
err,
|
||||
"%s: policy should parse successfully\nPolicy:\n%s",
|
||||
tf.TestID,
|
||||
policyJSON,
|
||||
tf.Input.FullPolicy,
|
||||
)
|
||||
|
||||
for nodeName, capture := range tf.Captures {
|
||||
|
|
@ -254,97 +371,3 @@ func TestSSHDataCompat(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// sshErrorMessageMap maps Tailscale SaaS error substrings to headscale
|
||||
// equivalents where the wording differs but the meaning is the same.
|
||||
var sshErrorMessageMap = map[string]string{}
|
||||
|
||||
// testSSHError verifies that an invalid policy produces the expected error.
|
||||
func testSSHError(t *testing.T, tf *testcapture.Capture) {
|
||||
t.Helper()
|
||||
|
||||
policyJSON := []byte(tf.Input.FullPolicy)
|
||||
|
||||
pol, err := unmarshalPolicy(policyJSON)
|
||||
if err != nil {
|
||||
// Parse-time error.
|
||||
if tf.Input.APIResponseBody != nil {
|
||||
wantMsg := tf.Input.APIResponseBody.Message
|
||||
if wantMsg != "" {
|
||||
assertSSHErrorContains(t, err, wantMsg, tf.TestID)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
err = pol.validate()
|
||||
if err != nil {
|
||||
if tf.Input.APIResponseBody != nil {
|
||||
wantMsg := tf.Input.APIResponseBody.Message
|
||||
if wantMsg != "" {
|
||||
assertSSHErrorContains(t, err, wantMsg, tf.TestID)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
t.Errorf(
|
||||
"%s: expected error but policy parsed and validated successfully",
|
||||
tf.TestID,
|
||||
)
|
||||
}
|
||||
|
||||
// assertSSHErrorContains checks that an error message matches the
|
||||
// expected Tailscale SaaS message, using progressive fallbacks:
|
||||
// 1. Direct substring match
|
||||
// 2. Mapped equivalent from sshErrorMessageMap
|
||||
// 3. Key-part extraction (tags, autogroups)
|
||||
// 4. t.Errorf on no match (strict)
|
||||
func assertSSHErrorContains(
|
||||
t *testing.T,
|
||||
err error,
|
||||
wantMsg string,
|
||||
testID string,
|
||||
) {
|
||||
t.Helper()
|
||||
|
||||
errStr := err.Error()
|
||||
|
||||
// 1. Direct substring match.
|
||||
if strings.Contains(errStr, wantMsg) {
|
||||
return
|
||||
}
|
||||
|
||||
// 2. Mapped equivalent.
|
||||
for tsKey, hsKey := range sshErrorMessageMap {
|
||||
if strings.Contains(wantMsg, tsKey) &&
|
||||
strings.Contains(errStr, hsKey) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Key-part extraction.
|
||||
for _, part := range []string{
|
||||
"autogroup:",
|
||||
"tag:",
|
||||
"undefined",
|
||||
"not valid",
|
||||
} {
|
||||
if strings.Contains(wantMsg, part) &&
|
||||
strings.Contains(errStr, part) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 4. No match — strict failure.
|
||||
t.Errorf(
|
||||
"%s: error message mismatch\n"+
|
||||
" want (tailscale): %q\n"+
|
||||
" got (headscale): %q",
|
||||
testID,
|
||||
wantMsg,
|
||||
errStr,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
20105
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-acceptenv-bad-glob.hujson
vendored
Normal file
20105
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-acceptenv-bad-glob.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20082
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-acceptenv-empty.hujson
vendored
Normal file
20082
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-acceptenv-empty.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20081
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-action-deny.hujson
vendored
Normal file
20081
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-action-deny.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20081
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-action-empty.hujson
vendored
Normal file
20081
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-action-empty.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20078
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-action-missing.hujson
vendored
Normal file
20078
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-action-missing.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20082
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-checkperiod-malformed.hujson
vendored
Normal file
20082
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-checkperiod-malformed.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20082
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-checkperiod-too-long.hujson
vendored
Normal file
20082
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-checkperiod-too-long.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20099
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-checkperiod-too-short.hujson
vendored
Normal file
20099
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-checkperiod-too-short.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20099
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-checkperiod-zero.hujson
vendored
Normal file
20099
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-checkperiod-zero.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20079
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-dst-empty.hujson
vendored
Normal file
20079
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-dst-empty.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20074
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-dst-missing.hujson
vendored
Normal file
20074
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-dst-missing.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20076
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-src-empty.hujson
vendored
Normal file
20076
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-src-empty.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20074
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-src-missing.hujson
vendored
Normal file
20074
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-src-missing.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20112
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-user-autogroup-internet.hujson
vendored
Normal file
20112
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-user-autogroup-internet.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20112
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-user-autogroup-member.hujson
vendored
Normal file
20112
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-user-autogroup-member.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20104
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-user-autogroup-self.hujson
vendored
Normal file
20104
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-user-autogroup-self.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20112
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-user-autogroup-tagged.hujson
vendored
Normal file
20112
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-user-autogroup-tagged.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20081
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-user-empty.hujson
vendored
Normal file
20081
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-user-empty.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20105
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-user-group-prefix.hujson
vendored
Normal file
20105
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-user-group-prefix.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20104
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-user-localpart-empty.hujson
vendored
Normal file
20104
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-user-localpart-empty.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20083
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-user-localpart-multi-glob.hujson
vendored
Normal file
20083
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-user-localpart-multi-glob.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20104
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-user-localpart-no-at.hujson
vendored
Normal file
20104
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-user-localpart-no-at.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20104
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-user-localpart-no-domain.hujson
vendored
Normal file
20104
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-user-localpart-no-domain.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20112
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-user-localpart-no-glob.hujson
vendored
Normal file
20112
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-user-localpart-no-glob.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20105
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-user-tag-prefix.hujson
vendored
Normal file
20105
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-user-tag-prefix.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20081
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-user-wildcard.hujson
vendored
Normal file
20081
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-user-wildcard.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20081
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-users-empty-array.hujson
vendored
Normal file
20081
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-users-empty-array.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
20078
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-users-missing.hujson
vendored
Normal file
20078
hscontrol/policy/v2/testdata/ssh_results/ssh-malformed-users-missing.hujson
vendored
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue