orchard/internal/tests/sshserver_test.go

120 lines
3.4 KiB
Go

package tests_test
import (
"context"
"crypto/subtle"
"fmt"
"net"
"runtime"
"testing"
"time"
"github.com/avast/retry-go/v5"
"github.com/cirruslabs/orchard/internal/controller"
"github.com/cirruslabs/orchard/internal/tests/devcontroller"
"github.com/cirruslabs/orchard/internal/tests/platformdependent"
"github.com/cirruslabs/orchard/internal/tests/wait"
v1 "github.com/cirruslabs/orchard/pkg/resource/v1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/ssh"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
func TestSSHServer(t *testing.T) {
// Generate SSH host key for the Controller
publicKey, privateKey, err := ed25519.GenerateKey(nil)
require.NoError(t, err)
sshPublicKey, err := ssh.NewPublicKey(publicKey)
require.NoError(t, err)
signer, err := ssh.NewSignerFromKey(privateKey)
require.NoError(t, err)
// Run the Controller
devClient, devController, _ := devcontroller.StartIntegrationTestEnvironmentWithAdditionalOpts(t,
false, []controller.Option{
controller.WithSSHServer(":0", signer, false),
},
false, nil,
)
// Create a VM to which we'll connect via Controller's SSH server
err = devClient.VMs().Create(context.Background(), platformdependent.VM("test-vm"))
require.NoError(t, err)
// Wait for the VM to start
assert.True(t, wait.Wait(2*time.Minute, func() bool {
vm, err := devClient.VMs().Get(context.Background(), "test-vm")
require.NoError(t, err)
return vm.Status == v1.VMStatusRunning
}), "failed to wait for the VM to start")
// Create a service account whose credentials we'll use to connect to the Controller's SSH server
require.NoError(t, devClient.ServiceAccounts().Create(context.Background(), &v1.ServiceAccount{
Meta: v1.Meta{
Name: "ssh-user",
},
Token: "ssh-password",
Roles: []v1.ServiceAccountRole{
v1.ServiceAccountRoleComputeWrite,
},
}))
// Connect to the VM over Orchard Controller's SSH server
sshAddress, ok := devController.SSHAddress()
require.True(t, ok)
sshClientController, err := ssh.Dial("tcp", sshAddress, &ssh.ClientConfig{
User: "ssh-user",
Auth: []ssh.AuthMethod{
ssh.Password("ssh-password"),
},
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
if subtle.ConstantTimeCompare(sshPublicKey.Marshal(), key.Marshal()) != 1 {
return fmt.Errorf("untrustred public key was presented by the Controller")
}
return nil
},
})
require.NoError(t, err)
defer sshClientController.Close()
netConnVM, err := retry.NewWithData[net.Conn](
retry.Context(t.Context()),
retry.DelayType(retry.FixedDelay),
retry.Delay(time.Second),
retry.Attempts(0),
).Do(func() (net.Conn, error) {
return sshClientController.Dial("tcp", "test-vm:22")
})
require.NoError(t, err)
sshConnVM, sshChansVM, sshReqsVM, err := ssh.NewClientConn(netConnVM, "test-vm:22", &ssh.ClientConfig{
User: "admin",
Auth: []ssh.AuthMethod{
ssh.Password("admin"),
},
HostKeyCallback: func(_ string, _ net.Addr, _ ssh.PublicKey) error {
return nil
},
})
require.NoError(t, err)
sshClientVM := ssh.NewClient(sshConnVM, sshChansVM, sshReqsVM)
defer sshClientVM.Close()
sshSessVM, err := sshClientVM.NewSession()
require.NoError(t, err)
defer sshSessVM.Close()
unameBytes, err := sshSessVM.Output("uname -a")
require.NoError(t, err)
require.Contains(t, string(unameBytes), cases.Title(language.English).String(runtime.GOOS))
}