mirror of
https://github.com/tailscale/tailscale.git
synced 2026-05-05 20:26:47 +02:00
ssh/tailssh: harden macOS exit status tests
Use the existing macOS runner account for focused exit-status coverage instead of provisioning a synthetic Directory Services user. Also drain Go SSH client output and add a per-command timeout so stderr output cannot block the test process until the global test timeout expires. Updates #18256 Change-Id: Ic4a0f391c56210023ece20c13d8627b0f5ad68e7 Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
parent
c4a0f391c5
commit
5ea2dedd45
33
.github/workflows/ssh-integrationtest.yml
vendored
33
.github/workflows/ssh-integrationtest.yml
vendored
@ -43,36 +43,13 @@ jobs:
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Create test user
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
|
||||
sudo dscl . -create /Groups/groupone
|
||||
sudo dscl . -create /Groups/groupone PrimaryGroupID 10000
|
||||
sudo dscl . -create /Groups/groupone Password '*'
|
||||
|
||||
sudo dscl . -create /Groups/grouptwo
|
||||
sudo dscl . -create /Groups/grouptwo PrimaryGroupID 10001
|
||||
sudo dscl . -create /Groups/grouptwo Password '*'
|
||||
|
||||
sudo dscl . -create /Users/testuser
|
||||
sudo dscl . -create /Users/testuser UserShell /bin/bash
|
||||
sudo dscl . -create /Users/testuser RealName 'Test User'
|
||||
sudo dscl . -create /Users/testuser UniqueID 10002
|
||||
sudo dscl . -create /Users/testuser PrimaryGroupID 10000
|
||||
sudo dscl . -create /Users/testuser NFSHomeDirectory /Users/testuser
|
||||
|
||||
sudo mkdir -p /Users/testuser
|
||||
sudo chown -R testuser:groupone /Users/testuser
|
||||
sudo chmod 0755 /Users/testuser
|
||||
|
||||
sudo dseditgroup -o edit -a testuser -t user groupone
|
||||
sudo dseditgroup -o edit -a testuser -t user grouptwo
|
||||
id testuser
|
||||
- name: Build test binaries
|
||||
run: |
|
||||
./tool/go test -tags integrationtest -c ./ssh/tailssh -o /tmp/tailssh.test
|
||||
./tool/go build -o /tmp/tailscaled ./cmd/tailscaled
|
||||
- name: Run macOS SSH integration tests
|
||||
- name: Run macOS OpenSSH exit status integration tests
|
||||
run: |
|
||||
sudo env "PATH=$PATH" TAILSCALED_PATH=/tmp/tailscaled /tmp/tailssh.test -test.v -test.timeout=2m -test.run '^(TestOpenSSHExitCodes|TestIntegrationExitCodes)$'
|
||||
sudo env "PATH=$PATH" TAILSCALED_PATH=/tmp/tailscaled TS_SSH_INTEGRATION_TEST_USER=runner /tmp/tailssh.test -test.v -test.timeout=3m -test.run '^TestOpenSSHExitCodes$'
|
||||
- name: Run macOS Go SSH exit status integration tests
|
||||
run: |
|
||||
sudo env "PATH=$PATH" TAILSCALED_PATH=/tmp/tailscaled TS_SSH_INTEGRATION_TEST_USER=runner /tmp/tailssh.test -test.v -test.timeout=3m -test.run '^TestIntegrationExitCodes$'
|
||||
|
||||
@ -731,8 +731,11 @@ readLoop:
|
||||
|
||||
func testClient(t *testing.T, forceV1Behavior bool, allowSendEnv bool, authMethods ...ssh.AuthMethod) *ssh.Client {
|
||||
t.Helper()
|
||||
return testClientForUser(t, "testuser", forceV1Behavior, allowSendEnv, authMethods...)
|
||||
}
|
||||
|
||||
username := "testuser"
|
||||
func testClientForUser(t *testing.T, username string, forceV1Behavior bool, allowSendEnv bool, authMethods ...ssh.AuthMethod) *ssh.Client {
|
||||
t.Helper()
|
||||
addr := testServer(t, username, forceV1Behavior, allowSendEnv)
|
||||
|
||||
cl, err := ssh.Dial("tcp", addr, &ssh.ClientConfig{
|
||||
@ -957,6 +960,7 @@ func (conn *addressFakingConn) RemoteAddr() net.Addr {
|
||||
func TestIntegrationExitCodes(t *testing.T) {
|
||||
debugTest.Store(true)
|
||||
t.Cleanup(func() { debugTest.Store(false) })
|
||||
username := exitCodeTestUser()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@ -984,23 +988,49 @@ func TestIntegrationExitCodes(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := testSession(t, false, false, nil)
|
||||
err := s.Run(tt.cmd)
|
||||
cl := testClientForUser(t, username, false, false)
|
||||
s, err := cl.NewSession()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
type result struct {
|
||||
out []byte
|
||||
err error
|
||||
}
|
||||
done := make(chan result, 1)
|
||||
go func() {
|
||||
out, err := s.CombinedOutput(tt.cmd)
|
||||
done <- result{out: out, err: err}
|
||||
}()
|
||||
|
||||
var out []byte
|
||||
select {
|
||||
case res := <-done:
|
||||
out = res.out
|
||||
err = res.err
|
||||
case <-time.After(20 * time.Second):
|
||||
s.Close()
|
||||
cl.Close()
|
||||
t.Fatalf("ssh command %q timed out", tt.cmd)
|
||||
}
|
||||
|
||||
if tt.wantCode == 0 {
|
||||
if err != nil {
|
||||
t.Fatalf("expected exit code 0, got error: %v", err)
|
||||
t.Fatalf("expected exit code 0, got error: %v; output:\n%s", err, out)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatalf("expected exit code %d, got nil error", tt.wantCode)
|
||||
t.Fatalf("expected exit code %d, got nil error; output:\n%s", tt.wantCode, out)
|
||||
}
|
||||
var exitErr *ssh.ExitError
|
||||
if !errors.As(err, &exitErr) {
|
||||
t.Fatalf("expected *ssh.ExitError, got %T: %v", err, err)
|
||||
t.Fatalf("expected *ssh.ExitError, got %T: %v; output:\n%s", err, err, out)
|
||||
}
|
||||
if exitErr.ExitStatus() != tt.wantCode {
|
||||
t.Errorf("exit code = %d, want %d", exitErr.ExitStatus(), tt.wantCode)
|
||||
t.Errorf("exit code = %d, want %d; output:\n%s", exitErr.ExitStatus(), tt.wantCode, out)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -1015,8 +1045,9 @@ func TestOpenSSHExitCodes(t *testing.T) {
|
||||
|
||||
debugTest.Store(true)
|
||||
t.Cleanup(func() { debugTest.Store(false) })
|
||||
username := exitCodeTestUser()
|
||||
|
||||
addr := testServer(t, "testuser", false, false)
|
||||
addr := testServer(t, username, false, false)
|
||||
host, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@ -1072,7 +1103,7 @@ func TestOpenSSHExitCodes(t *testing.T) {
|
||||
"-o", "StrictHostKeyChecking=no",
|
||||
"-o", "UserKnownHostsFile=/dev/null",
|
||||
"-p", port,
|
||||
"testuser@"+host,
|
||||
username+"@"+host,
|
||||
tt.cmd,
|
||||
)
|
||||
out, err := cmd.CombinedOutput()
|
||||
@ -1086,6 +1117,13 @@ func TestOpenSSHExitCodes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func exitCodeTestUser() string {
|
||||
if username := os.Getenv("TS_SSH_INTEGRATION_TEST_USER"); username != "" {
|
||||
return username
|
||||
}
|
||||
return "testuser"
|
||||
}
|
||||
|
||||
// TestLocalUnixForwardingHalfClose verifies that the bidirectional copy
|
||||
// in Unix socket forwarding uses half-close correctly: when one direction
|
||||
// finishes, the other direction's data is not lost. This tests the bicopy
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user