Ctrl K
ComunidadGuías…

tests/ssh_test.go

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package main

import (
	"bufio"
	"bytes"
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"log"
	"os"
	"strconv"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/bramvdbogaerde/go-scp"
	"github.com/moby/moby/api/types/container"
	"github.com/pkg/sftp"
	"github.com/shellhub-io/shellhub/pkg/api/requests"
	"github.com/shellhub-io/shellhub/pkg/models"
	"github.com/shellhub-io/shellhub/tests/environment"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/testcontainers/testcontainers-go"
	"golang.org/x/crypto/ssh"
)

const (
	ShellHubAgentUsername = environment.AgentUsername
	ShellHubAgentPassword = environment.AgentPassword
)

const (
	ShellHubUsername      = "test"
	ShellHubPassword      = "password"
	ShellHubNamespaceName = "testspace"
	ShellHubNamespace     = "00000000-0000-4000-0000-000000000000"
	ShellHubEmail         = "test@ossystems.com.br"
)

type NewAgentContainerOption func(envs map[string]string)

func NewAgentContainerWithIdentity(identity string) NewAgentContainerOption {
	return func(envs map[string]string) {
		envs["SHELLHUB_PREFERRED_IDENTITY"] = identity
	}
}

func NewAgentContainerWithConnectionVersion(version int) NewAgentContainerOption {
	return func(envs map[string]string) {
		envs["SHELLHUB_TRANSPORT_VERSION"] = strconv.Itoa(version)
	}
}

func NewAgentContainerWithTenant(tenant string) NewAgentContainerOption {
	return func(envs map[string]string) {
		envs["SHELLHUB_TENANT_ID"] = tenant
	}
}

func NewAgentContainerWithHostname(hostname string) NewAgentContainerOption {
	return func(envs map[string]string) {
		envs["SHELLHUB_PREFERRED_HOSTNAME"] = hostname
	}
}

func NewAgentContainerWithTenantOnly(tenant string) NewAgentContainerOption {
	return func(envs map[string]string) {
		delete(envs, "SHELLHUB_PROVISIONING_KEY")
		envs["SHELLHUB_TENANT_ID"] = tenant
	}
}

// NewAgentContainerWithProvisioningKey drops the tenant id, so the device proves the key alone
// resolved the namespace.
func NewAgentContainerWithProvisioningKey(key string) NewAgentContainerOption {
	return func(envs map[string]string) {
		delete(envs, "SHELLHUB_TENANT_ID")
		envs["SHELLHUB_PROVISIONING_KEY"] = key
	}
}

// NewAgentContainerUnpaired drops the tenant id, so the agent asks for a pairing code and joins the
// namespace of whoever accepts it.
func NewAgentContainerUnpaired() NewAgentContainerOption {
	return func(envs map[string]string) {
		delete(envs, "SHELLHUB_TENANT_ID")
		delete(envs, "SHELLHUB_PROVISIONING_KEY")
	}
}

func envOr(key, fallback string) string {
	if value := os.Getenv(key); value != "" {
		return value
	}

	return fallback
}

func agentBuildLog() io.Writer {
	if os.Getenv("SHELLHUB_E2E_AGENT_CONTEXT") != "" || os.Getenv("SHELLHUB_E2E_AGENT_DOCKERFILE") != "" {
		return os.Stderr
	}

	return nil
}

type agentImageBuild struct {
	done chan struct{}
	tag  string
	err  error
}

var agentImages struct {
	sync.Mutex
	builds map[string]*agentImageBuild
}

func buildAgentImage(ctx context.Context, version string, tags ...string) (string, error) {
	return cachedAgentImage(ctx, strings.Join(append([]string{version}, tags...), "-"), environment.AgentBuild{
		Repository: os.Getenv("SHELLHUB_E2E_AGENT_IMAGE"),
		Context:    envOr("SHELLHUB_E2E_AGENT_CONTEXT", ".."),
		Dockerfile: os.Getenv("SHELLHUB_E2E_AGENT_DOCKERFILE"),
		Version:    version,
		Tags:       tags,
		Output:     agentBuildLog(),
	})
}

func cachedAgentImage(ctx context.Context, key string, build environment.AgentBuild) (string, error) {
	agentImages.Lock()
	inflight, ok := agentImages.builds[key]
	if !ok {
		if agentImages.builds == nil {
			agentImages.builds = make(map[string]*agentImageBuild)
		}

		inflight = &agentImageBuild{done: make(chan struct{})}
		agentImages.builds[key] = inflight
	}
	agentImages.Unlock()

	if ok {
		select {
		case <-inflight.done:
			return inflight.tag, inflight.err
		case <-ctx.Done():
			return "", ctx.Err()
		}
	}

	defer close(inflight.done)

	inflight.tag, inflight.err = environment.BuildAgentImage(ctx, run, build)
	if inflight.err != nil {
		agentImages.Lock()
		delete(agentImages.builds, key)
		agentImages.Unlock()
	}

	return inflight.tag, inflight.err
}

func NewAgentContainer(ctx context.Context, relayID string, opts ...NewAgentContainerOption) (testcontainers.Container, error) {
	return NewAgentContainerAtVersion(ctx, relayID, "", opts...)
}

// NewAgentContainerAtVersion builds an agent that reports version instead of "latest" and returns
// its container, created but not started. Each version is built under its own tag, so the agent
// every other test starts keeps its tag; an empty version builds that default agent. A version
// builds once per run unless its build fails, and a caller asking for a version another caller is
// building waits for that build without blocking callers of other versions. It returns the error
// of the build it waited for, even one that the building caller's context ended, or ctx.Err()
// when ctx ends first, or the error of a container creation that fails, bounded by ctx.
func NewAgentContainerAtVersion(ctx context.Context, relayID, version string, opts ...NewAgentContainerOption) (testcontainers.Container, error) {
	image, err := buildAgentImage(ctx, version)
	if err != nil {
		return nil, err
	}

	return newAgentContainer(ctx, relayID, image, opts...)
}

func newAgentContainer(ctx context.Context, relayID, image string, opts ...NewAgentContainerOption) (testcontainers.Container, error) {
	req, err := agentContainerRequest(relayID, image, opts...)
	if err != nil {
		return nil, err
	}

	return createAgentContainer(ctx, req)
}

func agentContainerRequest(relayID, image string, opts ...NewAgentContainerOption) (testcontainers.ContainerRequest, error) {
	raw := make([]byte, 6)
	if _, err := rand.Read(raw); err != nil {
		return testcontainers.ContainerRequest{}, err
	}

	identity := hex.EncodeToString(raw)

	envs := map[string]string{
		"SHELLHUB_PREFERRED_IDENTITY": identity,
		"SHELLHUB_PREFERRED_HOSTNAME": "agent-" + identity,
		"SHELLHUB_SERVER_ADDRESS":     "http://localhost",
		"SHELLHUB_TENANT_ID":          "00000000-0000-4000-0000-000000000000",
		"SHELLHUB_PRIVATE_KEY":        "/tmp/shellhub.key",
		"SHELLHUB_LOG_FORMAT":         "json",
		"SHELLHUB_KEEPALIVE_INTERVAL": "1",
		"SHELLHUB_LOG_LEVEL":          "trace",
		"SHELLHUB_TRANSPORT_VERSION":  "2",
	}

	for _, opt := range opts {
		opt(envs)
	}

	return testcontainers.ContainerRequest{
		Image:  image,
		Env:    envs,
		Labels: run.Labels(),
		HostConfigModifier: func(hc *container.HostConfig) {
			hc.NetworkMode = container.NetworkMode("container:" + relayID)
		},
	}, nil
}

func createAgentContainer(ctx context.Context, req testcontainers.ContainerRequest) (testcontainers.Container, error) {
	return testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
		ContainerRequest: req,
		Logger:           log.New(io.Discard, "", log.LstdFlags),
	})
}

func TestSSHNewNamespace(t *testing.T) {
	t.Parallel()

	ctx := context.Background()

	compose := newSSHEnvironment(t, ctx, newNamespace)
	_, device := startAcceptedAgent(t, ctx, compose)

	sshid := deviceSSHID(device)
	addr := compose.SSHAddress()

	t.Run("password authentication is not offered", func(t *testing.T) {
		config := &ssh.ClientConfig{
			User:            sshid,
			Auth:            []ssh.AuthMethod{ssh.Password(ShellHubAgentPassword)},
			HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
		}

		_, err := ssh.Dial("tcp", addr, config)
		require.Error(t, err)
		require.Contains(t, err.Error(), "attempted methods []")
	})

	t.Run("authenticate with an enrolled identity", func(t *testing.T) {
		signer, data := newSigner(t)

		compose.EnrollIdentity(t, "integration", data)

		config := &ssh.ClientConfig{
			User:            sshid,
			Auth:            []ssh.AuthMethod{ssh.PublicKeys(signer)},
			HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
		}

		var conn *ssh.Client

		require.EventuallyWithT(t, func(tt *assert.CollectT) {
			var err error

			conn, err = ssh.Dial("tcp", addr, config)
			assert.NoError(tt, err)
		}, 30*time.Second, 1*time.Second)

		_ = conn.Close()
	})
}

func TestSSHConnectionV1(t *testing.T) {
	t.Parallel()

	testSSHWithVersion(t, 1)
}

func TestSSHConnectionV2(t *testing.T) {
	t.Parallel()

	testSSHWithVersion(t, 2)
}

func testSSHWithVersion(t *testing.T, connectionVersion int) {
	t.Helper()

	type Environment struct {
		services *environment.DockerCompose
		agent    testcontainers.Container
		signer   ssh.Signer
	}

	tests := []struct {
		name    string
		options []NewAgentContainerOption
		run     func(*testing.T, *Environment, *models.Device)
	}{
		{
			name:    "reconnect to server",
			options: []NewAgentContainerOption{NewAgentContainerWithIdentity("custom-reconnect")},
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				ctx := context.Background()

				err := environment.agent.Stop(ctx, nil)
				require.NoError(t, err)

				environment.services.AwaitDeviceOffline(t, device.UID)

				err = environment.agent.Start(ctx)
				require.NoError(t, err)

				environment.services.AwaitDeviceOnline(t, device.UID)

				conn := dialDevice(t, ctx, environment.services, device, environment.signer)
				defer conn.Close() //nolint:errcheck // the test is over once the new tunnel answered

				assert.Equal(t, "reconnected", runOnDevice(t, conn, "echo -n reconnected"))
			},
		},
		{
			name:    "authenticate with password",
			options: []NewAgentContainerOption{NewAgentContainerWithIdentity("custom-password")},
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				skipUnlessAgentAcceptsPasswords(t)

				config := &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.Password(ShellHubAgentPassword),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				}

				var conn *ssh.Client

				require.EventuallyWithT(t, func(tt *assert.CollectT) {
					var err error

					conn, err = ssh.Dial("tcp", environment.services.SSHAddress(), config)
					assert.NoError(tt, err)
				}, 30*time.Second, 1*time.Second)

				_ = conn.Close()
			},
		},
		{
			name: "fail to authenticate with password",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				skipUnlessAgentAcceptsPasswords(t)

				config := &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.Password("wrongpassword"),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				}

				err := handshakeWith(t.Context(), environment.services.SSHAddress(), config)
				require.ErrorContains(t, err, "ssh: unable to authenticate, attempted methods [none password]")
			},
		},
		{
			name: "authenticate with public key",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				ctx := context.Background()

				signer := registerDeviceKey(t, ctx, environment.services)

				config := &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.PublicKeys(signer),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				}

				conn, err := ssh.Dial("tcp", environment.services.SSHAddress(), config)
				require.NoError(t, err)

				_ = conn.Close()
			},
		},
		{
			name: "fail to authenticate with public key",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				signer, _ := newSigner(t)

				config := &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.PublicKeys(signer),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				}

				err := handshakeWith(t.Context(), environment.services.SSHAddress(), config)
				require.ErrorContains(t, err, "ssh: unable to authenticate, attempted methods [none publickey]")
			},
		},
		{
			name: "connection EXEC and a SHELL on same connection",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				config := &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.PublicKeys(environment.signer),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				}

				var conn *ssh.Client

				require.EventuallyWithT(t, func(tt *assert.CollectT) {
					var err error

					conn, err = ssh.Dial("tcp", environment.services.SSHAddress(), config)
					assert.NoError(tt, err)
				}, 30*time.Second, 1*time.Second)

				{
					sess, err := conn.NewSession()
					require.NoError(t, err)

					output, err := sess.Output(`echo -n "test"`)
					require.NoError(t, err)

					assert.Equal(t, "test", string(output))

					_ = sess.Close()
				}
				{
					sess, err := conn.NewSession()
					require.NoError(t, err)

					err = sess.RequestPty("xterm", 100, 100, ssh.TerminalModes{
						ssh.ECHO:          1,
						ssh.TTY_OP_ISPEED: 14400,
						ssh.TTY_OP_OSPEED: 14400,
					})
					require.NoError(t, err)

					err = sess.Shell()
					require.NoError(t, err)

					_ = sess.Close()
				}

				_ = conn.Close()
			},
		},
		{
			name:    "connection EXEC",
			options: []NewAgentContainerOption{NewAgentContainerWithIdentity("custom-exec")},
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				config := &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.PublicKeys(environment.signer),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				}

				var conn *ssh.Client

				require.EventuallyWithT(t, func(tt *assert.CollectT) {
					var err error

					conn, err = ssh.Dial("tcp", environment.services.SSHAddress(), config)
					assert.NoError(tt, err)
				}, 30*time.Second, 1*time.Second)

				sess, err := conn.NewSession()
				require.NoError(t, err)

				output, err := sess.Output(`echo -n "test"`)
				require.NoError(t, err)

				assert.Equal(t, "test", string(output))

				_ = sess.Close()
				_ = conn.Close()
			},
		},
		{
			name: "connection EXEC with non zero status code",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				config := &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.PublicKeys(environment.signer),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				}

				var conn *ssh.Client

				require.EventuallyWithT(t, func(tt *assert.CollectT) {
					var err error

					conn, err = ssh.Dial("tcp", environment.services.SSHAddress(), config)
					assert.NoError(tt, err)
				}, 30*time.Second, 1*time.Second)

				sess, err := conn.NewSession()
				require.NoError(t, err)

				var status *ssh.ExitError

				output, err := sess.CombinedOutput(`echo -n "test" 1>&2; exit 142`)
				require.ErrorAs(t, err, &status)

				assert.Equal(t, 142, status.ExitStatus())
				assert.Equal(t, "test", string(output))

				_ = sess.Close()
				_ = conn.Close()
			},
		},
		{
			name:    "connection SFTP to download file",
			options: []NewAgentContainerOption{},
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				config := &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.PublicKeys(environment.signer),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				}

				var conn *ssh.Client

				require.EventuallyWithT(t, func(tt *assert.CollectT) {
					var err error

					conn, err = ssh.Dial("tcp", environment.services.SSHAddress(), config)
					assert.NoError(tt, err)
				}, 30*time.Second, 1*time.Second)

				sess, err := sftp.NewClient(conn)
				require.NoError(t, err)

				received, err := sess.OpenFile("/etc/os-release", os.O_RDONLY)
				require.NoError(t, err)

				var data string

				_, err = fmt.Fscanf(received, "%s", &data)
				require.NoError(t, err)

				assert.Contains(t, data, "Alpine")

				_ = sess.Close()
				_ = conn.Close()
			},
		},
		{
			name:    "connection SCP to upload file",
			options: []NewAgentContainerOption{},
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				config := &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.PublicKeys(environment.signer),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				}

				var conn *ssh.Client

				require.EventuallyWithT(t, func(tt *assert.CollectT) {
					var err error

					conn, err = ssh.Dial("tcp", environment.services.SSHAddress(), config)
					assert.NoError(tt, err)
				}, 30*time.Second, 1*time.Second)

				sess, err := scp.NewClientBySSH(conn)
				require.NoError(t, err)

				const content = "uploaded through scp\n"

				err = sess.CopyFilePassThru(t.Context(), strings.NewReader(content), "/tmp/scp-uploaded", "0640", nil)
				require.NoError(t, err)

				sess.Close()

				assert.Equal(t, content, runOnDevice(t, conn, "cat /tmp/scp-uploaded"))
				assert.Equal(t, "640\n", runOnDevice(t, conn, "stat -c %a /tmp/scp-uploaded"))

				_ = conn.Close()
			},
		},
		{
			name:    "connection SCP to download file",
			options: []NewAgentContainerOption{},
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				config := &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.PublicKeys(environment.signer),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				}

				var conn *ssh.Client

				require.EventuallyWithT(t, func(tt *assert.CollectT) {
					var err error

					conn, err = ssh.Dial("tcp", environment.services.SSHAddress(), config)
					assert.NoError(tt, err)
				}, 30*time.Second, 1*time.Second)

				const content = "downloaded through scp\n"

				runOnDevice(t, conn, "printf %s '"+content+"' > /tmp/scp-downloaded")

				sess, err := scp.NewClientBySSH(conn)
				require.NoError(t, err)

				var file bytes.Buffer

				err = sess.CopyFromRemotePassThru(t.Context(), &file, "/tmp/scp-downloaded", nil)
				require.NoError(t, err)

				sess.Close()

				assert.Equal(t, content, file.String())

				_ = conn.Close()
			},
		},
		{
			name: "connection EXEC of a shell builtin",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				conn := dialDevice(t, t.Context(), environment.services, device, environment.signer)
				defer conn.Close() //nolint:errcheck // the test is over once the builtin answered

				sess, err := conn.NewSession()
				require.NoError(t, err)
				defer sess.Close() //nolint:errcheck // Output already waited for the exit status

				output, err := sess.Output("cd /var/log && pwd && type -t cd")
				require.NoError(t, err)

				assert.Equal(t, "/var/log\nbuiltin\n", string(output))
			},
		},
		{
			name: "connection SFTP to make a directory",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				conn := dialDevice(t, t.Context(), environment.services, device, environment.signer)
				defer conn.Close() //nolint:errcheck // the test is over once the directory is read back

				client, err := sftp.NewClient(conn)
				require.NoError(t, err)
				defer client.Close() //nolint:errcheck // the test reads its outcome over an exec channel, so a failed close leaves nothing to assert

				require.NoError(t, client.Mkdir("/tmp/sftp-made"))

				assert.Equal(t, "directory\n", runOnDevice(t, conn, "stat -c %F /tmp/sftp-made"))
			},
		},
		{
			name: "connection SFTP to list a directory",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				conn := dialDevice(t, t.Context(), environment.services, device, environment.signer)
				defer conn.Close() //nolint:errcheck // the test is over once the listing is read

				runOnDevice(t, conn, "mkdir /tmp/sftp-listed && printf 12345 > /tmp/sftp-listed/file && mkdir /tmp/sftp-listed/dir")

				client, err := sftp.NewClient(conn)
				require.NoError(t, err)
				defer client.Close() //nolint:errcheck // the listing is already read

				entries, err := client.ReadDir("/tmp/sftp-listed")
				require.NoError(t, err)

				listed := map[string]os.FileInfo{}
				for _, entry := range entries {
					listed[entry.Name()] = entry
				}

				require.Len(t, listed, 2)
				require.Contains(t, listed, "file")
				require.Contains(t, listed, "dir")
				assert.False(t, listed["file"].IsDir())
				assert.Equal(t, int64(5), listed["file"].Size())
				assert.True(t, listed["dir"].IsDir())
			},
		},
		{
			name: "connection SCP to upload a directory recursively",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				conn := dialDevice(t, t.Context(), environment.services, device, environment.signer)
				defer conn.Close() //nolint:errcheck // the test is over once the tree is read back

				sendSCPTree(t, conn, "/tmp", scpDirectory{
					name: "scp-tree",
					mode: "0750",
					files: map[string]string{
						"top.txt": "at the top",
					},
					dirs: []scpDirectory{{
						name: "nested",
						mode: "0700",
						files: map[string]string{
							"deep.txt": "one level down",
						},
					}},
				})

				assert.Equal(t, "at the top|one level down", runOnDevice(t, conn,
					`printf '%s|%s' "$(cat /tmp/scp-tree/top.txt)" "$(cat /tmp/scp-tree/nested/deep.txt)"`))
				assert.Equal(t, "750 700\n", runOnDevice(t, conn, "stat -c %a /tmp/scp-tree /tmp/scp-tree/nested | paste -sd ' '"))
			},
		},
		{
			name:    "direct tcpip port redirect",
			options: []NewAgentContainerOption{},
			run: func(t *testing.T, env *Environment, device *models.Device) {
				t.Helper()

				config := &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.PublicKeys(env.signer),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				}

				conn, err := ssh.Dial("tcp", env.services.SSHAddress(), config)
				require.NoError(t, err)

				type Data struct {
					DestAddr   string
					DestPort   uint32
					OriginAddr string
					OriginPort uint32
				}

				orig, err := strconv.Atoi(environment.ReservePort(t))
				require.NoError(t, err)

				data := Data{
					DestAddr:   "127.0.0.1",
					DestPort:   80,
					OriginAddr: "127.0.0.1",
					OriginPort: uint32(orig), //nolint:gosec
				}

				ch, _, err := conn.OpenChannel("direct-tcpip", ssh.Marshal(data))
				require.NoError(t, err)

				_, err = ch.Write([]byte("GET / HTTP/1.0\r\n\r\n"))
				require.NoError(t, err)

				status, err := bufio.NewReader(ch).ReadString('\n')
				require.NoError(t, err)
				require.True(t, strings.HasPrefix(status, "HTTP/1."), status)

				_ = ch.Close()
				_ = conn.Close()
			},
		},
		{
			name: "connection EXEC outlives a silent pause",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				config := &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.PublicKeys(environment.signer),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				}

				conn, err := ssh.Dial("tcp", environment.services.SSHAddress(), config)
				require.NoError(t, err)

				sess, err := conn.NewSession()
				require.NoError(t, err)

				output, err := sess.CombinedOutput("sleep 3 && echo -n 'still alive'")
				require.NoError(t, err)

				assert.Equal(t, "still alive", string(output))

				_ = sess.Close()
				_ = conn.Close()
			},
		},
		{
			name:    "connection SFTP to upload large file",
			options: []NewAgentContainerOption{},
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				config := &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.PublicKeys(environment.signer),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				}

				conn, err := ssh.Dial("tcp", environment.services.SSHAddress(), config)
				require.NoError(t, err)
				defer conn.Close() //nolint:errcheck

				client, err := sftp.NewClient(conn)
				require.NoError(t, err)
				defer client.Close() //nolint:errcheck

				fileSize := 10 * 1024 * 1024 // 10MB
				randomData := make([]byte, fileSize)
				rand.Read(randomData)

				tempFile, err := os.CreateTemp(t.TempDir(), "large-file-test-*.bin")
				require.NoError(t, err)
				defer os.Remove(tempFile.Name()) //nolint:errcheck

				_, err = tempFile.Write(randomData)
				require.NoError(t, err)
				_ = tempFile.Close()

				localFile, err := os.Open(tempFile.Name())
				require.NoError(t, err)
				defer localFile.Close() //nolint:errcheck

				remoteFile, err := client.Create("/tmp/large-file-test.bin")
				require.NoError(t, err)

				written, err := io.Copy(remoteFile, localFile)
				require.NoError(t, err)
				assert.Equal(t, int64(fileSize), written)
				require.NoError(t, remoteFile.Close())

				sum := sha256.Sum256(randomData)
				received := strings.Fields(runOnDevice(t, conn, "sha256sum /tmp/large-file-test.bin"))
				require.NotEmpty(t, received)
				assert.Equal(t, hex.EncodeToString(sum[:]), received[0])
			},
		},
		{
			name: "OpenSSH scp in SFTP mode uploads a file and exits 0",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				execInAgent(t, environment.agent, "ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-f", "/tmp/scp-key")
				registerAuthorizedKey(t, t.Context(), environment.services, "openssh-scp", execInAgent(t, environment.agent, "cat", "/tmp/scp-key.pub"))

				execInAgent(t, environment.agent, "sh", "-c", "head -c 1048576 /dev/urandom > /tmp/scp-source")

				execInAgent(t, environment.agent, "scp", "-s",
					"-i", "/tmp/scp-key",
					"-o", "BatchMode=yes",
					"-o", "StrictHostKeyChecking=no",
					"-o", "UserKnownHostsFile=/dev/null",
					"-o", "User="+deviceSSHID(device),
					"-P", "2222",
					"/tmp/scp-source", "server:/tmp/scp-received",
				)

				sent := strings.Fields(execInAgent(t, environment.agent, "sha256sum", "/tmp/scp-source"))
				received := strings.Fields(execInAgent(t, environment.agent, "sha256sum", "/tmp/scp-received"))
				require.NotEmpty(t, sent)
				require.NotEmpty(t, received)
				assert.Equal(t, sent[0], received[0])
			},
		},
		{
			name: "connection EXEC with large output",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				config := &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.PublicKeys(environment.signer),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				}

				conn, err := ssh.Dial("tcp", environment.services.SSHAddress(), config)
				require.NoError(t, err)
				defer conn.Close() //nolint:errcheck

				sess, err := conn.NewSession()
				require.NoError(t, err)
				defer sess.Close() //nolint:errcheck

				output, err := sess.Output("yes X | tr -d '\n' | head -c 1048576")
				require.NoError(t, err)

				assert.Len(t, output, 1024*1024)
				for _, b := range output {
					assert.Equal(t, byte('X'), b)
				}
			},
		},
		{
			name: "connection EXEC with separate stdout and stderr",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				config := &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.PublicKeys(environment.signer),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				}

				conn, err := ssh.Dial("tcp", environment.services.SSHAddress(), config)
				require.NoError(t, err)
				defer conn.Close() //nolint:errcheck

				sess, err := conn.NewSession()
				require.NoError(t, err)
				defer sess.Close() //nolint:errcheck

				var stdout, stderr bytes.Buffer

				sess.Stdout = &stdout
				sess.Stderr = &stderr

				err = sess.Run(`echo -n "to stdout"; echo -n "to stderr" 1>&2; exit 7`)

				var status *ssh.ExitError

				require.ErrorAs(t, err, &status)
				assert.Equal(t, 7, status.ExitStatus())

				assert.Equal(t, "to stdout", stdout.String())
				assert.Equal(t, "to stderr", stderr.String())
			},
		},
		{
			name: "connection EXEC delivers a signal to the command",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				sess, output := startInterruptReporter(t, environment.services, environment.signer, device, false)

				require.NoError(t, sess.Signal(ssh.SIGINT))

				requireInterruptReported(t, sess, output)
			},
		},
		{
			name: "connection EXEC with Pty delivers a signal to the command",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				sess, output := startInterruptReporter(t, environment.services, environment.signer, device, true)

				require.NoError(t, sess.Signal(ssh.SIGINT))

				requireInterruptReported(t, sess, output)
			},
		},
		{
			name: "connection SHELL with Pty delivers a signal to the foreground job",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				sess := openSignalSession(t, environment.services, environment.signer, device, true)

				stdin, err := sess.StdinPipe()
				require.NoError(t, err)

				stdout, err := sess.StdoutPipe()
				require.NoError(t, err)

				require.NoError(t, sess.Shell())

				_, err = io.WriteString(stdin, `signal-reporter INT; echo "STA""TUS=$?"; exit`+"\n")
				require.NoError(t, err)

				output := bufio.NewReader(stdout)

				var seen strings.Builder
				for !strings.Contains(seen.String(), "READY") {
					line, err := output.ReadString('\n')
					seen.WriteString(line)
					require.NoError(t, err, "the job never started: %q", seen.String())
				}

				require.NoError(t, sess.Signal(ssh.SIGINT))

				rest := readRemaining(t, output, "the shell never finished the line after the signal")
				assert.Contains(t, rest, "GOT-INT")
				assert.Contains(t, rest, "STATUS=3")
			},
		},
		{
			name: "connection EXEC answers a signal that asks for a reply",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				sess, output := startInterruptReporter(t, environment.services, environment.signer, device, false)

				replied := make(chan bool, 1)

				go func() {
					ok, err := sess.SendRequest("signal", true, ssh.Marshal(struct{ Signal string }{Signal: string(ssh.SIGINT)}))
					replied <- err == nil && ok
				}()

				select {
				case ok := <-replied:
					assert.True(t, ok, "the signal was not accepted")
				case <-time.After(10 * time.Second):
					t.Fatal("a signal asking for a reply never got one")
				}

				requireInterruptReported(t, sess, output)
			},
		},
		{
			name: "connection EXEC with large stderr",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				config := &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.PublicKeys(environment.signer),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				}

				conn, err := ssh.Dial("tcp", environment.services.SSHAddress(), config)
				require.NoError(t, err)
				defer conn.Close() //nolint:errcheck

				sess, err := conn.NewSession()
				require.NoError(t, err)
				defer sess.Close() //nolint:errcheck

				var stdout, stderr bytes.Buffer

				sess.Stdout = &stdout
				sess.Stderr = &stderr

				done := make(chan error, 1)

				go func() {
					done <- sess.Run("yes E | tr -d '\n' | head -c 1048576 1>&2; echo -n done")
				}()

				select {
				case err := <-done:
					require.NoError(t, err)
				case <-time.After(60 * time.Second):
					t.Fatal("writing 1MiB to stderr never completed")
				}

				assert.Equal(t, 1024*1024, stderr.Len())
				assert.Equal(t, "done", stdout.String())
			},
		},
		{
			name: "connection SHELL with Pty reports the exit code",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				config := &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.PublicKeys(environment.signer),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				}

				conn, err := ssh.Dial("tcp", environment.services.SSHAddress(), config)
				require.NoError(t, err)
				defer conn.Close() //nolint:errcheck

				sess, err := conn.NewSession()
				require.NoError(t, err)
				defer sess.Close() //nolint:errcheck

				stdin, err := sess.StdinPipe()
				require.NoError(t, err)

				require.NoError(t, sess.RequestPty("xterm", 24, 80, ssh.TerminalModes{}))
				require.NoError(t, sess.Shell())

				_, err = io.WriteString(stdin, "exit 42\n")
				require.NoError(t, err)

				done := make(chan error, 1)

				go func() { done <- sess.Wait() }()

				select {
				case err := <-done:
					var status *ssh.ExitError

					require.ErrorAs(t, err, &status)
					assert.Equal(t, 42, status.ExitStatus())
				case <-time.After(60 * time.Second):
					t.Fatal("a shell with a pty never reported an exit status")
				}
			},
		},
		{
			name: "connection SHELL with Pty reports a successful exit",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				config := &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.PublicKeys(environment.signer),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				}

				conn, err := ssh.Dial("tcp", environment.services.SSHAddress(), config)
				require.NoError(t, err)
				defer conn.Close() //nolint:errcheck

				sess, err := conn.NewSession()
				require.NoError(t, err)
				defer sess.Close() //nolint:errcheck

				stdin, err := sess.StdinPipe()
				require.NoError(t, err)

				require.NoError(t, sess.RequestPty("xterm", 24, 80, ssh.TerminalModes{}))
				require.NoError(t, sess.Shell())

				_, err = io.WriteString(stdin, "exit 0\n")
				require.NoError(t, err)

				done := make(chan error, 1)

				go func() { done <- sess.Wait() }()

				select {
				case err := <-done:
					require.NoError(t, err)
				case <-time.After(60 * time.Second):
					t.Fatal("a shell with a pty never reported an exit status")
				}
			},
		},
		{
			name: "connection SHELL without Pty",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				config := &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.PublicKeys(environment.signer),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				}

				conn, err := ssh.Dial("tcp", environment.services.SSHAddress(), config)
				require.NoError(t, err)
				defer conn.Close() //nolint:errcheck

				sess, err := conn.NewSession()
				require.NoError(t, err)
				defer sess.Close() //nolint:errcheck

				stdin, err := sess.StdinPipe()
				require.NoError(t, err)

				var stdout, stderr bytes.Buffer

				sess.Stdout = &stdout
				sess.Stderr = &stderr

				require.NoError(t, sess.Shell())

				_, err = io.WriteString(stdin, "echo -n heredoc_out\necho -n heredoc_err 1>&2\nexit 5\n")
				require.NoError(t, err)
				require.NoError(t, stdin.Close())

				done := make(chan error, 1)

				go func() { done <- sess.Wait() }()

				select {
				case err := <-done:
					var status *ssh.ExitError

					require.ErrorAs(t, err, &status)
					assert.Equal(t, 5, status.ExitStatus())
				case <-time.After(60 * time.Second):
					t.Fatal("a shell without a pty never produced anything")
				}

				assert.Equal(t, "heredoc_out", stdout.String())
				assert.Equal(t, "heredoc_err", stderr.String())
			},
		},
		{
			name: "connection keepalive reaches the client",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				reqs := openShellWatchingGlobalRequests(t, t.Context(), environment.services.SSHAddress(), &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.PublicKeys(environment.signer),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				})

				awaitGlobalRequest(t, reqs, "keepalive", 60*time.Second)
			},
		},
		{
			name: "connection EXEC with environment variables",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				config := &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.PublicKeys(environment.signer),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				}

				conn, err := ssh.Dial("tcp", environment.services.SSHAddress(), config)
				require.NoError(t, err)
				defer conn.Close() //nolint:errcheck

				sess, err := conn.NewSession()
				require.NoError(t, err)
				defer sess.Close() //nolint:errcheck

				err = sess.Setenv("LC_TEST", "allowed")
				require.NoError(t, err)

				err = sess.Setenv("TEST_VAR1", "blocked")
				require.NoError(t, err)

				output, err := sess.Output("echo -n $LC_TEST:$TEST_VAR1")
				require.NoError(t, err)

				assert.Equal(t, "allowed:", string(output))
			},
		},
		{
			name: "terminal window size change",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				config := &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.PublicKeys(environment.signer),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				}

				conn, err := ssh.Dial("tcp", environment.services.SSHAddress(), config)
				require.NoError(t, err)
				defer conn.Close() //nolint:errcheck

				sess, err := conn.NewSession()
				require.NoError(t, err)
				defer sess.Close() //nolint:errcheck

				err = sess.RequestPty("xterm", 24, 80, ssh.TerminalModes{
					ssh.ECHO: 0,
				})
				require.NoError(t, err)

				require.NoError(t, sess.WindowChange(48, 160))

				resize := startResizeReporter(t, sess)

				for _, size := range [][2]int{{40, 120}, {24, 80}, {50, 200}, {30, 100}} {
					resize(size[0], size[1])
				}
			},
		},
		{
			name: "connection EXEC with invalid command",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				config := &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.PublicKeys(environment.signer),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				}

				conn, err := ssh.Dial("tcp", environment.services.SSHAddress(), config)
				require.NoError(t, err)
				defer conn.Close() //nolint:errcheck

				sess, err := conn.NewSession()
				require.NoError(t, err)
				defer sess.Close() //nolint:errcheck

				_, err = sess.Output("this-command-does-not-exist")
				require.Error(t, err)

				var exitErr *ssh.ExitError
				require.ErrorAs(t, err, &exitErr)
				assert.NotEqual(t, 0, exitErr.ExitStatus())
			},
		},
		{
			name: "handling special characters in commands and output",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				config := &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.PublicKeys(environment.signer),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				}

				conn, err := ssh.Dial("tcp", environment.services.SSHAddress(), config)
				require.NoError(t, err)
				defer conn.Close() //nolint:errcheck

				sess, err := conn.NewSession()
				require.NoError(t, err)
				defer sess.Close() //nolint:errcheck

				specialChars := "!@#$%^&*()_+{}[]|;:,.<>/?`~"
				output, err := sess.Output(fmt.Sprintf("echo -n '%s'", specialChars))
				require.NoError(t, err)

				assert.Equal(t, specialChars, string(output))

				unicodeChars := "こんにちは世界 ñáéíóú 你好世界"
				sess2, err := conn.NewSession()
				require.NoError(t, err)
				defer sess2.Close() //nolint:errcheck

				output, err = sess2.Output(fmt.Sprintf("echo -n '%s'", unicodeChars))
				require.NoError(t, err)

				assert.Equal(t, unicodeChars, string(output))
			},
		},
		{
			name: "multiple concurrent SSH sessions",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				config := &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.PublicKeys(environment.signer),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				}

				const numConnections = 5
				var wg sync.WaitGroup
				errors := make(chan error, numConnections)

				for i := range numConnections {
					wg.Add(1)
					go func(id int) {
						defer wg.Done()

						conn, err := ssh.Dial("tcp", environment.services.SSHAddress(), config)
						if err != nil {
							errors <- fmt.Errorf("connection %d failed: %w", id, err)

							return
						}
						defer conn.Close() //nolint:errcheck

						sess, err := conn.NewSession()
						if err != nil {
							errors <- fmt.Errorf("session %d failed: %w", id, err)

							return
						}
						defer sess.Close() //nolint:errcheck

						expected := fmt.Sprintf("session-%d", id)
						output, err := sess.Output(fmt.Sprintf("echo -n '%s'", expected))
						if err != nil {
							errors <- fmt.Errorf("command %d failed: %w", id, err)

							return
						}

						if string(output) != expected {
							errors <- fmt.Errorf("unexpected output from session %d: got %q, want %q", id, string(output), expected)
						}
					}(i)
				}

				wg.Wait()
				close(errors)

				for err := range errors {
					require.NoError(t, err)
				}
			},
		},
		{
			name: "connection verifies the server's host key",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				hostKey := environment.services.SSHHostKey(t)

				conn := dialClient(t, t.Context(), environment.services.SSHAddress(), &ssh.ClientConfig{
					User:            deviceSSHID(device),
					Auth:            []ssh.AuthMethod{ssh.PublicKeys(environment.signer)},
					HostKeyCallback: ssh.FixedHostKey(hostKey),
				})
				_ = conn.Close()

				impostor, _ := newSigner(t)

				err := handshakeWith(t.Context(), environment.services.SSHAddress(), &ssh.ClientConfig{
					User:            deviceSSHID(device),
					Auth:            []ssh.AuthMethod{ssh.PublicKeys(environment.signer)},
					HostKeyCallback: ssh.FixedHostKey(impostor.PublicKey()),
				})
				require.ErrorContains(t, err, "ssh: host key mismatch")
			},
		},
		{
			name: "connection with subsystem request (sftp)",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				config := &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.PublicKeys(environment.signer),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				}

				conn, err := ssh.Dial("tcp", environment.services.SSHAddress(), config)
				require.NoError(t, err)
				defer conn.Close() //nolint:errcheck

				sess, err := conn.NewSession()
				require.NoError(t, err)
				defer sess.Close() //nolint:errcheck

				err = sess.RequestSubsystem("sftp")
				require.NoError(t, err)

				stdin, err := sess.StdinPipe()
				require.NoError(t, err)

				stdout, err := sess.StdoutPipe()
				require.NoError(t, err)

				initPacket := []byte{0, 0, 0, 5, 1, 0, 0, 0, 3} // SSH_FXP_INIT with version 3
				_, err = stdin.Write(initPacket)
				require.NoError(t, err)

				response := make([]byte, 9)
				n, err := stdout.Read(response)
				require.NoError(t, err)
				assert.Equal(t, 9, n)
				assert.Equal(t, byte(2), response[4]) // SSH_FXP_VERSION
			},
		},
		{
			name: "connection with pseudo-terminal modes",
			run: func(t *testing.T, environment *Environment, device *models.Device) {
				t.Helper()

				config := &ssh.ClientConfig{
					User: deviceSSHID(device),
					Auth: []ssh.AuthMethod{
						ssh.PublicKeys(environment.signer),
					},
					HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec
				}

				conn, err := ssh.Dial("tcp", environment.services.SSHAddress(), config)
				require.NoError(t, err)
				defer conn.Close() //nolint:errcheck

				sess, err := conn.NewSession()
				require.NoError(t, err)
				defer sess.Close() //nolint:errcheck

				modes := ssh.TerminalModes{
					ssh.ECHO:  0,
					ssh.VINTR: 0x07,
				}

				require.NoError(t, sess.RequestPty("xterm-256color", 24, 80, modes))

				output, err := sess.Output("stty -a")
				require.NoError(t, err, string(output))

				settings := strings.Fields(string(output))
				assert.Contains(t, settings, "-echo", "echo is still on: %s", output)
				assert.Contains(t, string(output), "intr = ^G", "the interrupt character is not the one asked for: %s", output)
			},
		},
	}

	ctx := context.Background()

	compose := newSSHEnvironment(t, ctx, legacyNamespace)
	signer := registerDeviceKey(t, ctx, compose)

	for _, tc := range tests {
		test := tc
		t.Run(test.name, func(tt *testing.T) {
			opts := append([]NewAgentContainerOption{
				NewAgentContainerWithConnectionVersion(connectionVersion),
			}, test.options...)

			agent, device := startAcceptedAgent(tt, ctx, compose, opts...)

			test.run(tt, &Environment{
				services: compose,
				agent:    agent,
				signer:   signer,
			}, device)
		})
	}
}

func openSignalSession(t *testing.T, compose *environment.DockerCompose, signer ssh.Signer, device *models.Device, pty bool) *ssh.Session {
	t.Helper()

	conn := dialDevice(t, t.Context(), compose, device, signer)
	t.Cleanup(func() { _ = conn.Close() })

	sess, err := conn.NewSession()
	require.NoError(t, err)
	t.Cleanup(func() { _ = sess.Close() })

	if pty {
		require.NoError(t, sess.RequestPty("xterm", 24, 80, ssh.TerminalModes{}))
	}

	return sess
}

func startInterruptReporter(t *testing.T, compose *environment.DockerCompose, signer ssh.Signer, device *models.Device, pty bool) (*ssh.Session, *bufio.Reader) {
	t.Helper()

	sess := openSignalSession(t, compose, signer, device, pty)

	stdout, err := sess.StdoutPipe()
	require.NoError(t, err)

	require.NoError(t, sess.Start("exec signal-reporter INT"))

	output := bufio.NewReader(stdout)

	line, err := output.ReadString('\n')
	require.NoError(t, err)
	require.Equal(t, "READY", strings.TrimSpace(line))

	return sess, output
}

func requireInterruptReported(t *testing.T, sess *ssh.Session, output *bufio.Reader) {
	t.Helper()

	rest := readRemaining(t, output, "the command never reported the interrupt")

	var status *ssh.ExitError

	require.ErrorAs(t, sess.Wait(), &status, "the command finished without reporting the interrupt: %q", rest)
	assert.Equal(t, 3, status.ExitStatus())
	assert.Equal(t, "GOT-INT", strings.TrimSpace(rest))
}

func readRemaining(t *testing.T, output io.Reader, never string) string {
	t.Helper()

	type read struct {
		rest []byte
		err  error
	}

	finished := make(chan read, 1)

	go func() {
		rest, err := io.ReadAll(output)
		finished <- read{rest, err}
	}()

	select {
	case got := <-finished:
		require.NoError(t, got.err)

		return string(got.rest)
	case <-time.After(30 * time.Second):
		t.Fatal(never)

		return ""
	}
}

func newSigner(t *testing.T) (ssh.Signer, string) {
	t.Helper()

	key, err := rsa.GenerateKey(rand.Reader, 2048)
	require.NoError(t, err)

	pub, err := ssh.NewPublicKey(&key.PublicKey)
	require.NoError(t, err)

	signer, err := ssh.NewSignerFromKey(key)
	require.NoError(t, err)

	return signer, string(ssh.MarshalAuthorizedKey(pub))
}

func registerDeviceKey(t *testing.T, ctx context.Context, compose *environment.DockerCompose) ssh.Signer {
	t.Helper()

	signer, data := newSigner(t)

	registerAuthorizedKey(t, ctx, compose, "e2e", data)

	return signer
}

func registerAuthorizedKey(t *testing.T, ctx context.Context, compose *environment.DockerCompose, name, data string) {
	t.Helper()

	resp, err := compose.R(ctx).
		SetBody(&requests.PublicKeyCreate{
			Name:     name,
			Username: ".*",
			Data:     []byte(data),
			Filter: requests.PublicKeyFilter{
				Hostname: ".*",
			},
		}).
		Post("/api/sshkeys/public-keys")
	require.NoError(t, err)
	require.Equal(t, 200, resp.StatusCode(), resp.String())
}

func skipUnlessAgentAcceptsPasswords(t *testing.T) {
	t.Helper()

	if os.Getenv("SHELLHUB_E2E_AGENT_PASSWORD") == "false" {
		t.Skip("SHELLHUB_E2E_AGENT_PASSWORD=false: the agent under test does not accept passwords")
	}
}

func deviceSSHID(device *models.Device) string {
	return ShellHubAgentUsername + "@" + ShellHubNamespaceName + "." + device.Name
}

type namespaceModel bool

const (
	legacyNamespace namespaceModel = true
	newNamespace    namespaceModel = false
)

func newSSHEnvironment(t *testing.T, ctx context.Context, model namespaceModel) *environment.DockerCompose {
	t.Helper()

	return newConfiguredSSHEnvironment(t, ctx, environment.New(t, run), model)
}

func newConfiguredSSHEnvironment(t *testing.T, ctx context.Context, configurator *environment.DockerComposeConfigurator, model namespaceModel) *environment.DockerCompose {
	t.Helper()

	compose := configurator.Up(ctx)
	t.Cleanup(compose.Down)

	compose.NewUser(t, ShellHubUsername, ShellHubEmail, ShellHubPassword)
	ownNamespace(t, ctx, compose, model)

	return compose
}

var harnessProvisioningKeys sync.Map

const harnessProvisioningKeyName = "harness"

func useManualProvisioningKey(t *testing.T, compose *environment.DockerCompose) string {
	t.Helper()

	key := compose.CreateProvisioningKey(t, &requests.CreateProvisioningKey{
		Name: harnessProvisioningKeyName,
		Mode: string(models.ProvisioningKeyModeManual),
	})
	harnessProvisioningKeys.Store(compose, key.Key)

	return key.Key
}

func harnessProvisioningKey(compose *environment.DockerCompose) (string, bool) {
	key, ok := harnessProvisioningKeys.Load(compose)
	if !ok {
		return "", false
	}

	return key.(string), true //nolint:forcetypeassert // the map holds nothing but the keys useManualProvisioningKey stores
}

func requireHarnessProvisioningKey(t *testing.T, compose *environment.DockerCompose) string {
	t.Helper()

	key, ok := harnessProvisioningKey(compose)
	require.True(t, ok, "the stack's namespace is legacy and has no provisioning key")

	return key
}

func withHarnessCredential(compose *environment.DockerCompose) NewAgentContainerOption {
	return func(envs map[string]string) {
		if key, ok := harnessProvisioningKey(compose); ok {
			NewAgentContainerWithProvisioningKey(key)(envs)
		}
	}
}

func harnessDeviceAuthRequest(t *testing.T, compose *environment.DockerCompose, hostname, mac string) requests.DeviceAuth {
	t.Helper()

	req := newDeviceAuthRequest(t, hostname, mac)
	if key, ok := harnessProvisioningKey(compose); ok {
		req.TenantID = ""
		req.ProvisioningKey = key
	}

	return req
}

func ownNamespace(t *testing.T, ctx context.Context, compose *environment.DockerCompose, model namespaceModel) {
	t.Helper()

	if model == legacyNamespace {
		compose.NewLegacyNamespace(t, ShellHubUsername, ShellHubNamespaceName, ShellHubNamespace)
	} else {
		compose.NewNamespace(t, ShellHubUsername, ShellHubNamespaceName, ShellHubNamespace)
	}

	auth := models.UserAuthResponse{}

	require.EventuallyWithT(t, func(tt *assert.CollectT) {
		resp, err := compose.R(ctx).
			SetBody(map[string]string{
				"username": ShellHubUsername,
				"password": ShellHubPassword,
			}).
			SetResult(&auth).
			Post("/api/login")
		assert.Equal(tt, 200, resp.StatusCode())
		assert.NoError(tt, err)
	}, 30*time.Second, 1*time.Second)

	compose.JWT(auth.Token)

	if model == newNamespace {
		useManualProvisioningKey(t, compose)
	}
}

func startAgent(t *testing.T, ctx context.Context, compose *environment.DockerCompose, opts ...NewAgentContainerOption) testcontainers.Container {
	t.Helper()

	opts = append([]NewAgentContainerOption{withHarnessCredential(compose)}, opts...)

	agent, err := NewAgentContainer(ctx, compose.Service(environment.ServiceRelay).GetContainerID(), opts...)
	require.NoError(t, err)

	return runAgent(t, ctx, agent)
}

func startCustomAgent(t *testing.T, ctx context.Context, compose *environment.DockerCompose, customize func(*testcontainers.ContainerRequest), opts ...NewAgentContainerOption) testcontainers.Container {
	t.Helper()

	image, err := buildAgentImage(ctx, "")
	require.NoError(t, err)

	opts = append([]NewAgentContainerOption{withHarnessCredential(compose)}, opts...)

	req, err := agentContainerRequest(compose.Service(environment.ServiceRelay).GetContainerID(), image, opts...)
	require.NoError(t, err)

	customize(&req)

	agent, err := createAgentContainer(ctx, req)
	require.NoError(t, err)

	return runAgent(t, ctx, agent)
}

func runAgent(t *testing.T, ctx context.Context, agent testcontainers.Container) testcontainers.Container {
	t.Helper()

	require.NoError(t, agent.Start(ctx))

	t.Cleanup(func() {
		_ = agent.Stop(context.Background(), nil)
		_ = agent.Terminate(context.Background())
	})

	return agent
}

func startAcceptedAgent(t *testing.T, ctx context.Context, compose *environment.DockerCompose, opts ...NewAgentContainerOption) (testcontainers.Container, *models.Device) {
	t.Helper()

	agent := startAgent(t, ctx, compose, opts...)

	return agent, acceptPendingDevice(t, compose)
}

func acceptPendingDevice(t *testing.T, compose *environment.DockerCompose) *models.Device {
	t.Helper()

	pending := compose.AwaitDeviceWithStatus(t, models.DeviceStatusPending)

	compose.UpdateDeviceStatus(t, pending.UID, environment.DeviceActionAccept)

	device := compose.AwaitDeviceOnline(t, pending.UID)

	return &device
}