mirror of
https://github.com/juanfont/headscale.git
synced 2026-11-01 03:04:07 +09:00
policy: pin SSH forwarding behavior and recommend upgrades
This commit is contained in:
1 parent
bd1876e1fa
commit
998c4b0619
11 files changed
+15977
-1
No files matched your search
+1
-1
@@ -29,7 +29,7 @@ config*.yaml
|
|||||||
!config-example.yaml
|
!config-example.yaml
|
||||||
derp.yaml
|
derp.yaml
|
||||||
*.hujson
|
*.hujson
|
||||||
!hscontrol/policy/v2/testdata/*/*.hujson
|
!hscontrol/policy/v2/testdata/**/*.hujson
|
||||||
*.key
|
*.key
|
||||||
/db.sqlite
|
/db.sqlite
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
|
|||||||
@@ -150,6 +150,22 @@ the ACME DNS-01 challenge through a built-in DNS provider: `acmedns`,
|
|||||||
- Deleting an ephemeral node no longer logs a spurious `ephemeral node deletion failed` error later [#3575](https://github.com/juanfont/headscale/pull/3575)
|
- Deleting an ephemeral node no longer logs a spurious `ephemeral node deletion failed` error later [#3575](https://github.com/juanfont/headscale/pull/3575)
|
||||||
- Add the v2 `acl/validate` endpoint, which Terraform provider 0.29.3 and newer call to check `tailscale_acl` at plan time; before it, those releases failed with `invalid character 'p' after top-level value`
|
- Add the v2 `acl/validate` endpoint, which Terraform provider 0.29.3 and newer call to check `tailscale_acl` at plan time; before it, those releases failed with `invalid character 'p' after top-level value`
|
||||||
|
|
||||||
|
## 0.29.7 (202x-xx-xx)
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
We recommend that all users upgrade to Headscale 0.29.7 and Tailscale v1.104.4 or newer,
|
||||||
|
especially those using Tailscale SSH. Tailscale v1.104.4 fixes vulnerabilities in SSH
|
||||||
|
forwarding, SSH session handling and Serve. These fixes run in `tailscaled` on your
|
||||||
|
nodes; upgrading Headscale alone does not apply them. See Tailscale's
|
||||||
|
[security bulletins](https://tailscale.com/security-bulletins#ts-2026-014) and
|
||||||
|
[Tailscale SSH: Findings and fixes](https://tailscale.com/blog/tailscale-ssh-findings-fixes).
|
||||||
|
|
||||||
|
Headscale follows Tailscale's SSH policy behavior: forwarding is allowed by default,
|
||||||
|
with no per-rule forwarding controls. As an interim mitigation for SSH forwarding
|
||||||
|
vulnerabilities, set `TS_SSH_DISABLE_FORWARDING=1` in the destination `tailscaled`
|
||||||
|
service environment and restart the service, as described in the security bulletin.
|
||||||
|
|
||||||
## 0.29.6 (2026-10-24)
|
## 0.29.6 (2026-10-24)
|
||||||
|
|
||||||
**Minimum supported Tailscale client version: v1.80.0**
|
**Minimum supported Tailscale client version: v1.80.0**
|
||||||
|
|||||||
@@ -58,6 +58,68 @@ func loadSSHTestFile(t *testing.T, path string) *testcapture.Capture {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSSHForwardingDataCompat pins upstream forwarding actions and rejects
|
||||||
|
// unsupported policy fields. The complete captures retain SSHUsers, but its new
|
||||||
|
// numeric-user deny entry needs separate user-mapping compatibility coverage.
|
||||||
|
func TestSSHForwardingDataCompat(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
files, err := filepath.Glob(filepath.Join("testdata", "ssh_results", "forwarding", "ssh-*.hujson"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, files)
|
||||||
|
|
||||||
|
users := setupSSHDataCompatUsers()
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
tf := loadSSHTestFile(t, file)
|
||||||
|
|
||||||
|
t.Run(tf.TestID, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
require.Empty(t, tf.CaptureError)
|
||||||
|
require.NotEmpty(t, tf.Captures)
|
||||||
|
|
||||||
|
pol, err := unmarshalPolicy([]byte(tf.Input.FullPolicy))
|
||||||
|
if tf.Input.APIResponseCode != 200 {
|
||||||
|
require.Equal(t, 400, tf.Input.APIResponseCode)
|
||||||
|
require.NotNil(t, tf.Input.APIResponseBody)
|
||||||
|
field, ok := strings.CutPrefix(tf.Input.APIResponseBody.Message, "json: unknown field ")
|
||||||
|
require.True(t, ok, "expected an upstream unknown-field rejection")
|
||||||
|
require.ErrorIs(t, err, ErrUnknownField)
|
||||||
|
require.ErrorContains(t, err, fmt.Sprintf("%s: %s", ErrUnknownField, field))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
nodes := buildGrantsNodesFromCapture(users, tf)
|
||||||
|
|
||||||
|
for nodeName, capture := range tf.Captures {
|
||||||
|
t.Run(nodeName, func(t *testing.T) {
|
||||||
|
node := findNodeByGivenName(nodes, nodeName)
|
||||||
|
require.NotNil(t, node)
|
||||||
|
|
||||||
|
got, err := pol.compileSSHPolicy("https://unused", users, node.View(), nodes.ViewSlice())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var rules []*tailcfg.SSHRule
|
||||||
|
if got != nil {
|
||||||
|
rules = got.Rules
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Len(t, rules, len(capture.SSHRules))
|
||||||
|
|
||||||
|
for i, want := range capture.SSHRules {
|
||||||
|
require.NotNil(t, want.Action)
|
||||||
|
assert.Equal(t, want.Action, rules[i].Action)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// sshSkipReasons documents captures the upstream control plane accepts
|
// sshSkipReasons documents captures the upstream control plane accepts
|
||||||
// but headscale cannot yet represent. Each entry names the feature gap.
|
// but headscale cannot yet represent. Each entry names the feature gap.
|
||||||
var sshSkipReasons = map[string]string{
|
var sshSkipReasons = map[string]string{
|
||||||
|
|||||||
+1977
File diff suppressed because it is too large.
Load diff
+2001
File diff suppressed because it is too large.
Load diff
+1985
File diff suppressed because it is too large.
Load diff
+1985
File diff suppressed because it is too large.
Load diff
+1985
File diff suppressed because it is too large.
Load diff
+1995
File diff suppressed because it is too large.
Load diff
+1985
File diff suppressed because it is too large.
Load diff
+1985
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user