integration: guard SSH acceptEnv forwarding

Updates #3559
This commit is contained in:
Kristoffer Dalby committed 2026-10-29 20:51:36 +01:00
1 parent 8cf43952af
commit 52842f4c0d
3 files changed
+34 -9

No files matched your search

@@ -141,11 +141,6 @@ var unmodelledTailnetStateCaps = []nodecap.Cap{
// server (no su, in-process SFTP). Internal tuning; the embedded // server (no su, in-process SFTP). Internal tuning; the embedded
// server picks Tailscale-vendored defaults without the cap. // server picks Tailscale-vendored defaults without the cap.
nodecap.SSHBehaviorV1, nodecap.SSHBehaviorV1,
// [tailcfg.NodeAttrSSHEnvironmentVariables]: gates SendEnv
// forwarding in the embedded SSH server. Internal; default chosen
// by the server.
nodecap.SSHEnvironmentVariables,
} }
// strippedCapPrefixes lists URL/string prefixes for parameterized or // strippedCapPrefixes lists URL/string prefixes for parameterized or
+4 -3
View File
@@ -168,9 +168,9 @@ func TestNodeAttrsRevokesWhenRemoved(t *testing.T) {
} }
// TestNodeAttrsBaselineCapsAlwaysOn verifies that the baseline caps // TestNodeAttrsBaselineCapsAlwaysOn verifies that the baseline caps
// (Admin, SSH, FileSharing, DefaultAutoUpdate) are emitted on every // (Admin, SSH, SSHEnvironmentVariables, FileSharing, DefaultAutoUpdate)
// node regardless of whether the policy mentions them. Taildrive // are emitted on every node regardless of whether the policy mentions them.
// (drive:share / drive:access) is policy-driven and is verified // Taildrive (drive:share / drive:access) is policy-driven and is verified
// through TestNodeAttrsAddsToBaseline and the integration // through TestNodeAttrsAddsToBaseline and the integration
// TestGrantCapDrive flow. // TestGrantCapDrive flow.
func TestNodeAttrsBaselineCapsAlwaysOn(t *testing.T) { func TestNodeAttrsBaselineCapsAlwaysOn(t *testing.T) {
@@ -189,6 +189,7 @@ func TestNodeAttrsBaselineCapsAlwaysOn(t *testing.T) {
for _, w := range []nodecap.Cap{ for _, w := range []nodecap.Cap{
nodecap.Admin, nodecap.Admin,
nodecap.SSH, nodecap.SSH,
nodecap.SSHEnvironmentVariables,
nodecap.FileSharing, nodecap.FileSharing,
nodecap.DefaultAutoUpdate, nodecap.DefaultAutoUpdate,
} { } {
+30 -1
View File
@@ -101,7 +101,8 @@ func TestSSHOneUserToAll(t *testing.T) {
new(policyv2.AutoGroupMember), new(policyv2.AutoGroupMember),
new(policyv2.AutoGroupTagged), new(policyv2.AutoGroupTagged),
}, },
Users: []policyv2.SSHUser{policyv2.SSHUser("ssh-it-user")}, Users: []policyv2.SSHUser{policyv2.SSHUser("ssh-it-user")},
AcceptEnv: []string{"HS_ACCEPTED"},
}, },
}, },
}, },
@@ -132,6 +133,7 @@ func TestSSHOneUserToAll(t *testing.T) {
} }
assertSSHHostname(t, client, peer) assertSSHHostname(t, client, peer)
assertSSHAcceptEnv(t, client, peer)
} }
} }
@@ -579,6 +581,33 @@ func assertSSHHostname(t *testing.T, client TailscaleClient, peer TailscaleClien
require.Contains(t, peer.ContainerID(), strings.ReplaceAll(result, "\n", "")) require.Contains(t, peer.ContainerID(), strings.ReplaceAll(result, "\n", ""))
} }
func assertSSHAcceptEnv(t *testing.T, client TailscaleClient, peer TailscaleClient) {
t.Helper()
peerFQDN, err := peer.FQDN()
require.NoError(t, err)
command := []string{
"env", "HS_ACCEPTED=yes", "HS_REJECTED=yes",
"/usr/bin/ssh", "-o StrictHostKeyChecking=no", "-o ConnectTimeout=1",
"-o SendEnv=HS_ACCEPTED", "-o SendEnv=HS_REJECTED",
"ssh-it-user@" + peerFQDN, "'env'",
}
require.EventuallyWithT(t, func(c *assert.CollectT) {
stdout, stderr, err := client.Execute(command)
if !assert.NoError(c, err, "SSH stderr: %s", stderr) {
return
}
vars := strings.Split(strings.TrimSpace(stdout), "\n")
assert.Contains(c, vars, "HS_ACCEPTED=yes")
assert.NotContains(c, vars, "HS_REJECTED=yes")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll,
"SSH from %s to %s should forward only acceptEnv variables",
client.Hostname(), peer.Hostname())
}
func assertSSHPermissionDenied(t *testing.T, client TailscaleClient, peer TailscaleClient) { func assertSSHPermissionDenied(t *testing.T, client TailscaleClient, peer TailscaleClient) {
t.Helper() t.Helper()