mirror of
https://github.com/slackhq/nebula.git
synced 2026-05-15 20:37:36 +02:00
Some checks failed
gofmt / Run gofmt (push) Failing after 3s
smoke-extra / freebsd-amd64 (push) Failing after 3s
smoke-extra / linux-amd64-ipv6disable (push) Failing after 2s
smoke-extra / netbsd-amd64 (push) Failing after 2s
smoke-extra / openbsd-amd64 (push) Failing after 3s
smoke-extra / linux-386 (push) Failing after 2s
smoke / Run multi node smoke test (push) Failing after 2s
Build and test / Build all and test on ubuntu-linux (push) Failing after 3s
Build and test / Build and test on linux with boringcrypto (push) Failing after 3s
Build and test / Build and test on linux with pkcs11 (push) Failing after 3s
Build and test / Build and test on macos-latest (push) Has been cancelled
Build and test / Build and test on windows-latest (push) Has been cancelled
126 lines
3.6 KiB
Go
126 lines
3.6 KiB
Go
//go:build e2e_testing
|
|
// +build e2e_testing
|
|
|
|
package e2e
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"crypto/rand"
|
|
"encoding/pem"
|
|
"net"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/slackhq/nebula/cert"
|
|
"github.com/slackhq/nebula/cert_test"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/crypto/ssh"
|
|
)
|
|
|
|
func TestSSHDLifecycle(t *testing.T) {
|
|
// TestSSHDLifecycle exercises the in-process sshd through several config reloads and a Control.Stop.
|
|
ca, _, caKey, _ := cert_test.NewTestCaCert(
|
|
cert.Version1, cert.Curve_CURVE25519,
|
|
time.Now(), time.Now().Add(10*time.Minute),
|
|
nil, nil, []string{},
|
|
)
|
|
|
|
hostKeyPEM := generateSSHHostKey(t)
|
|
clientSigner, clientAuthKey := generateSSHClientKey(t)
|
|
sshdAddr := allocLoopbackPort(t)
|
|
|
|
overrides := m{
|
|
"sshd": m{
|
|
"enabled": true,
|
|
"listen": sshdAddr,
|
|
"host_key": hostKeyPEM,
|
|
"authorized_users": []m{{
|
|
"user": "tester",
|
|
"keys": []string{clientAuthKey},
|
|
}},
|
|
},
|
|
}
|
|
control, _, _, _ := newSimpleServer(cert.Version1, ca, caKey, "sshd-test", "10.222.0.1/24", overrides)
|
|
control.Start()
|
|
t.Cleanup(func() { control.Stop() })
|
|
|
|
// sshd binds in a goroutine after Start returns; wait for it.
|
|
require.Eventually(t, func() bool { return canDial(sshdAddr) }, 2*time.Second, 25*time.Millisecond,
|
|
"sshd never started listening")
|
|
|
|
for i := 1; i <= 3; i++ {
|
|
out := sshExecReload(t, sshdAddr, clientSigner)
|
|
assert.Contains(t, out, "Reloading config", "reload cycle %d", i)
|
|
require.Eventually(t, func() bool { return canDial(sshdAddr) }, 2*time.Second, 25*time.Millisecond,
|
|
"sshd not listening after reload cycle %d", i)
|
|
}
|
|
|
|
control.Stop()
|
|
require.Eventually(t, func() bool { return !canDial(sshdAddr) }, 2*time.Second, 25*time.Millisecond,
|
|
"sshd still listening after Control.Stop")
|
|
}
|
|
|
|
func canDial(addr string) bool {
|
|
c, err := net.DialTimeout("tcp", addr, 100*time.Millisecond)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
_ = c.Close()
|
|
return true
|
|
}
|
|
|
|
// allocLoopbackPort grabs an unused TCP port on 127.0.0.1, closes it, and returns the address. There
|
|
// is a small race between releasing the port and the sshd reclaiming it; in practice the OS keeps the
|
|
// port available long enough for the test to bind it.
|
|
func allocLoopbackPort(t *testing.T) string {
|
|
t.Helper()
|
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
|
require.NoError(t, err)
|
|
addr := l.Addr().String()
|
|
require.NoError(t, l.Close())
|
|
return addr
|
|
}
|
|
|
|
func generateSSHHostKey(t *testing.T) string {
|
|
t.Helper()
|
|
_, priv, err := ed25519.GenerateKey(rand.Reader)
|
|
require.NoError(t, err)
|
|
block, err := ssh.MarshalPrivateKey(priv, "nebula-e2e-host")
|
|
require.NoError(t, err)
|
|
return string(pem.EncodeToMemory(block))
|
|
}
|
|
|
|
func generateSSHClientKey(t *testing.T) (ssh.Signer, string) {
|
|
t.Helper()
|
|
_, priv, err := ed25519.GenerateKey(rand.Reader)
|
|
require.NoError(t, err)
|
|
signer, err := ssh.NewSignerFromKey(priv)
|
|
require.NoError(t, err)
|
|
auth := strings.TrimSpace(string(ssh.MarshalAuthorizedKey(signer.PublicKey())))
|
|
return signer, auth
|
|
}
|
|
|
|
func sshExecReload(t *testing.T, addr string, signer ssh.Signer) string {
|
|
t.Helper()
|
|
cfg := &ssh.ClientConfig{
|
|
User: "tester",
|
|
Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)},
|
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
|
Timeout: 2 * time.Second,
|
|
}
|
|
client, err := ssh.Dial("tcp", addr, cfg)
|
|
require.NoError(t, err)
|
|
defer client.Close()
|
|
|
|
sess, err := client.NewSession()
|
|
require.NoError(t, err)
|
|
defer sess.Close()
|
|
|
|
// reload tears the channel down before sending exit-status, so Output returns an error on the
|
|
// channel close. The output buffer still has whatever the reload callback wrote before that.
|
|
out, _ := sess.Output("reload")
|
|
return string(out)
|
|
}
|