Ctrl K
ComunidadGuías…

tests/agent_forwarding_test.go

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

Archivo original ↗Preguntar a KiBot
package main

import (
	"crypto/rand"
	"crypto/rsa"
	"io"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/agent"
)

const forwardingUser = "forwarded"

// TestAgentSSHAgentForwarding opens a shell as an unprivileged account with agent forwarding
// requested, so the socket's owner tells the session user apart from the root the agent runs as.
// Only a shell session receives SSH_AUTH_SOCK.
func TestAgentSSHAgentForwarding(t *testing.T) {
	t.Parallel()

	compose := newSSHEnvironment(t, t.Context(), legacyNamespace)

	shellhubAgent, device := startAcceptedAgent(t, t.Context(), compose)
	execInAgent(t, shellhubAgent, "adduser", "-D", forwardingUser)

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

	forwardedPublicKey, err := ssh.NewPublicKey(&forwardedKey.PublicKey)
	require.NoError(t, err)

	keyring := agent.NewKeyring()
	require.NoError(t, keyring.Add(agent.AddedKey{PrivateKey: forwardedKey}))

	conn := dialClientWithin(t, t.Context(), compose.SSHAddress(), &ssh.ClientConfig{
		User:            forwardingUser + "@" + ShellHubNamespaceName + "." + device.Name,
		Auth:            []ssh.AuthMethod{ssh.PublicKeys(registerDeviceKey(t, t.Context(), compose))},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec // the test stack's host key is ephemeral
	}, 30*time.Second)

	defer conn.Close() //nolint:errcheck // the test is over once the shell exited

	require.NoError(t, agent.ForwardToAgent(conn, keyring))

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

	defer sess.Close() //nolint:errcheck // the shell already exited once stdout reached EOF

	require.NoError(t, agent.RequestAgentForwarding(sess))
	require.NoError(t, sess.RequestPty("xterm", 40, 400, ssh.TerminalModes{ssh.ECHO: 0}))

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

	sess.Stdin = strings.NewReader(`echo "OWNER=$(stat -c %U:%G "$SSH_AUTH_SOCK" "${SSH_AUTH_SOCK%/*}" | tr '\n' ' ')"; echo "KEYS=$(ssh-add -L)"; exit` + "\n")

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

	output := make(chan string, 1)

	go func() {
		read, _ := io.ReadAll(stdout)
		output <- string(read)
	}()

	var transcript string

	select {
	case transcript = <-output:
	case <-time.After(60 * time.Second):
		require.FailNow(t, "the shell never exited")
	}

	lines := map[string]string{}

	for line := range strings.Lines(transcript) {
		for _, marker := range []string{"OWNER", "KEYS"} {
			if _, value, ok := strings.Cut(line, marker+"="); ok && !strings.Contains(value, "$(") {
				lines[marker] = strings.TrimSpace(value)
			}
		}
	}

	t.Run("the forwarded agent answers inside the session", func(t *testing.T) {
		assert.Equal(t, strings.TrimSpace(string(ssh.MarshalAuthorizedKey(forwardedPublicKey))), lines["KEYS"], transcript)
	})

	t.Run("the agent socket and its directory belong to the session user", func(t *testing.T) {
		owner := forwardingUser + ":" + forwardingUser

		assert.Equal(t, owner+" "+owner, lines["OWNER"], transcript)
	})
}