d9d0beda3b
94 new tests across 4 packages. Coverage: engine 8.3%→65.1%, transport
26.3%→84.6%, proxmox 5.1%→82.7%, audit 0%→100%. Bug fix: dispatch.go
bytesReadCloser.Read returned fmt.Errorf("EOF") instead of io.EOF —
broke HTTP request body transmission (latent since v0.2 P02).
---ci---
project: orca
phase: 3
milestone: v0.7
status: verify
requirements:
covered: [REQ-055]
partial: []
---/ci---
333 lines
8.4 KiB
Go
333 lines
8.4 KiB
Go
package proxmox
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"golang.org/x/crypto/ssh"
|
|
)
|
|
|
|
func TestSudoersContent(t *testing.T) {
|
|
content := sudoersContent("orca")
|
|
|
|
if !strings.Contains(content, "NOPASSWD: NOEXEC: /usr/bin/pct") {
|
|
t.Error("missing NOEXEC on pct (AD-020)")
|
|
}
|
|
if !strings.Contains(content, "NOPASSWD: NOEXEC: /usr/bin/qm") {
|
|
t.Error("missing NOEXEC on qm (AD-020)")
|
|
}
|
|
|
|
if !strings.Contains(content, "NOPASSWD: /usr/bin/apt-get") {
|
|
t.Error("missing NOPASSWD on apt-get")
|
|
}
|
|
if !strings.Contains(content, "NOPASSWD: /usr/bin/dpkg") {
|
|
t.Error("missing NOPASSWD on dpkg")
|
|
}
|
|
if strings.Contains(content, "NOEXEC: /usr/bin/apt-get") {
|
|
t.Error("apt-get must NOT have NOEXEC (breaks maintainer scripts)")
|
|
}
|
|
if strings.Contains(content, "NOEXEC: /usr/bin/dpkg") {
|
|
t.Error("dpkg must NOT have NOEXEC (breaks maintainer scripts)")
|
|
}
|
|
|
|
for _, line := range strings.Split(content, "\n") {
|
|
trimmed := strings.TrimSpace(line)
|
|
if strings.HasPrefix(trimmed, "#") || trimmed == "" {
|
|
continue
|
|
}
|
|
if strings.Contains(trimmed, "pvesh") {
|
|
t.Errorf("pvesh must be EXCLUDED from sudoers command lines (AD-020): %s", trimmed)
|
|
}
|
|
}
|
|
|
|
if !strings.HasPrefix(content, "# /etc/sudoers.d/orca") {
|
|
t.Error("missing managed-by-orca header")
|
|
}
|
|
if !strings.Contains(content, "orca ALL=(root)") {
|
|
t.Error("missing orca user in sudoers")
|
|
}
|
|
}
|
|
|
|
func TestSudoersContent_CustomUser(t *testing.T) {
|
|
content := sudoersContent("custom-orca")
|
|
if !strings.Contains(content, "custom-orca ALL=(root)") {
|
|
t.Error("missing custom-orca user in sudoers")
|
|
}
|
|
}
|
|
|
|
func TestOrcaOperatorPrivileges(t *testing.T) {
|
|
privs := strings.Fields(OrcaOperatorPrivileges)
|
|
expected := map[string]bool{
|
|
"VM.Audit": true,
|
|
"Datastore.AllocateSpace": true,
|
|
"SDN.Use": true,
|
|
}
|
|
if len(privs) != 3 {
|
|
t.Errorf("expected 3 privileges, got %d: %v", len(privs), privs)
|
|
}
|
|
for _, p := range privs {
|
|
if !expected[p] {
|
|
t.Errorf("unexpected privilege %q", p)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBootstrapProxmox_Validation(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
_, err := BootstrapProxmox(ctx, Options{Password: "pw"})
|
|
if err == nil || !strings.Contains(err.Error(), "host is required") {
|
|
t.Errorf("expected host-required error, got %v", err)
|
|
}
|
|
|
|
_, err = BootstrapProxmox(ctx, Options{Host: "10.0.0.1"})
|
|
if err == nil || !strings.Contains(err.Error(), "password is required") {
|
|
t.Errorf("expected password-required error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDefaultOptions(t *testing.T) {
|
|
if DefaultProxmoxUser != "orca" {
|
|
t.Errorf("DefaultProxmoxUser = %q, want orca", DefaultProxmoxUser)
|
|
}
|
|
if DefaultProxmoxRole != "OrcaOperator" {
|
|
t.Errorf("DefaultProxmoxRole = %q, want OrcaOperator", DefaultProxmoxRole)
|
|
}
|
|
if DefaultSSHPort != 22 {
|
|
t.Errorf("DefaultSSHPort = %d, want 22", DefaultSSHPort)
|
|
}
|
|
}
|
|
|
|
type mockSSHDialer struct {
|
|
client *ssh.Client
|
|
err error
|
|
calls int
|
|
lastAddr string
|
|
lastCfg *ssh.ClientConfig
|
|
}
|
|
|
|
func (m *mockSSHDialer) DialContext(ctx context.Context, network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) {
|
|
m.calls++
|
|
m.lastAddr = addr
|
|
m.lastCfg = config
|
|
if m.err != nil {
|
|
return nil, m.err
|
|
}
|
|
return m.client, nil
|
|
}
|
|
|
|
func setupORCAHome(t *testing.T) string {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
t.Setenv("ORCA_HOME", dir)
|
|
knownHosts := filepath.Join(dir, "known_hosts")
|
|
if err := os.WriteFile(knownHosts, []byte{}, 0o600); err != nil {
|
|
t.Fatalf("create known_hosts: %v", err)
|
|
}
|
|
return dir
|
|
}
|
|
|
|
func TestBootstrapProxmox_SSHAuthFailure(t *testing.T) {
|
|
orig := sshDialer
|
|
defer func() { sshDialer = orig }()
|
|
sshDialer = &mockSSHDialer{err: errors.New("ssh: handshake failed: ssh: unable to authenticate")}
|
|
|
|
setupORCAHome(t)
|
|
|
|
_, err := BootstrapProxmox(context.Background(), Options{
|
|
Host: "10.0.0.1",
|
|
Password: "pw",
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected error, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "ssh") {
|
|
t.Errorf("error should mention ssh, got: %v", err)
|
|
}
|
|
if !strings.Contains(err.Error(), "ssh dial") {
|
|
t.Errorf("error should mention ssh dial, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestBootstrapProxmox_SSHDialCalledWithCorrectAddr(t *testing.T) {
|
|
orig := sshDialer
|
|
defer func() { sshDialer = orig }()
|
|
dialer := &mockSSHDialer{err: errors.New("connection refused")}
|
|
sshDialer = dialer
|
|
|
|
setupORCAHome(t)
|
|
|
|
_, _ = BootstrapProxmox(context.Background(), Options{
|
|
Host: "10.0.0.42",
|
|
Password: "pw",
|
|
SSHPort: 2222,
|
|
})
|
|
if dialer.calls != 1 {
|
|
t.Errorf("dialer calls = %d, want 1", dialer.calls)
|
|
}
|
|
if dialer.lastAddr != "10.0.0.42:2222" {
|
|
t.Errorf("dial addr = %q, want 10.0.0.42:2222", dialer.lastAddr)
|
|
}
|
|
}
|
|
|
|
func TestBootstrapProxmox_DefaultSSHPort(t *testing.T) {
|
|
orig := sshDialer
|
|
defer func() { sshDialer = orig }()
|
|
dialer := &mockSSHDialer{err: errors.New("connection refused")}
|
|
sshDialer = dialer
|
|
|
|
setupORCAHome(t)
|
|
|
|
_, _ = BootstrapProxmox(context.Background(), Options{
|
|
Host: "10.0.0.99",
|
|
Password: "pw",
|
|
})
|
|
if dialer.lastAddr != "10.0.0.99:22" {
|
|
t.Errorf("dial addr = %q, want 10.0.0.99:22 (default port)", dialer.lastAddr)
|
|
}
|
|
}
|
|
|
|
func TestBootstrapProxmox_CustomSSHUser(t *testing.T) {
|
|
orig := sshDialer
|
|
defer func() { sshDialer = orig }()
|
|
dialer := &mockSSHDialer{err: errors.New("connection refused")}
|
|
sshDialer = dialer
|
|
|
|
setupORCAHome(t)
|
|
|
|
_, _ = BootstrapProxmox(context.Background(), Options{
|
|
Host: "10.0.0.1",
|
|
Password: "pw",
|
|
SSHUser: "custom-admin",
|
|
})
|
|
if dialer.calls != 1 {
|
|
t.Errorf("dialer calls = %d, want 1", dialer.calls)
|
|
}
|
|
if dialer.lastCfg == nil || dialer.lastCfg.User != "custom-admin" {
|
|
t.Errorf("ssh user not propagated, got %+v", dialer.lastCfg)
|
|
}
|
|
}
|
|
|
|
func TestBootstrapProxmox_SSHKeyGenerated(t *testing.T) {
|
|
orig := sshDialer
|
|
defer func() { sshDialer = orig }()
|
|
sshDialer = &mockSSHDialer{err: errors.New("connection refused")}
|
|
|
|
dir := setupORCAHome(t)
|
|
|
|
_, _ = BootstrapProxmox(context.Background(), Options{
|
|
Host: "10.0.0.1",
|
|
Password: "pw",
|
|
})
|
|
|
|
keyPath := filepath.Join(dir, "orca_ssh_key")
|
|
pubPath := filepath.Join(dir, "orca_ssh_key.pub")
|
|
if _, err := os.Stat(keyPath); err != nil {
|
|
t.Errorf("SSH key not generated at %s: %v", keyPath, err)
|
|
}
|
|
if _, err := os.Stat(pubPath); err != nil {
|
|
t.Errorf("SSH pub not generated at %s: %v", pubPath, err)
|
|
}
|
|
}
|
|
|
|
func TestBootstrapProxmox_KnownHostsFileCreated(t *testing.T) {
|
|
orig := sshDialer
|
|
defer func() { sshDialer = orig }()
|
|
sshDialer = &mockSSHDialer{err: errors.New("connection refused")}
|
|
|
|
dir := setupORCAHome(t)
|
|
|
|
_, _ = BootstrapProxmox(context.Background(), Options{
|
|
Host: "10.0.0.1",
|
|
Password: "pw",
|
|
})
|
|
|
|
knownHosts := filepath.Join(dir, "known_hosts")
|
|
if _, err := os.Stat(knownHosts); err != nil {
|
|
t.Errorf("known_hosts not created at %s: %v", knownHosts, err)
|
|
}
|
|
}
|
|
|
|
func TestBootstrapProxmox_NilLogger(t *testing.T) {
|
|
orig := sshDialer
|
|
defer func() { sshDialer = orig }()
|
|
sshDialer = &mockSSHDialer{err: errors.New("connection refused")}
|
|
|
|
setupORCAHome(t)
|
|
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Fatalf("nil logger panicked: %v", r)
|
|
}
|
|
}()
|
|
_, _ = BootstrapProxmox(context.Background(), Options{
|
|
Host: "10.0.0.1",
|
|
Password: "pw",
|
|
Logger: nil,
|
|
})
|
|
}
|
|
|
|
func TestBootstrapProxmox_CustomLogger(t *testing.T) {
|
|
orig := sshDialer
|
|
defer func() { sshDialer = orig }()
|
|
sshDialer = &mockSSHDialer{err: errors.New("connection refused")}
|
|
|
|
setupORCAHome(t)
|
|
|
|
var buf bytes.Buffer
|
|
log := slog.New(slog.NewTextHandler(&buf, nil))
|
|
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Fatalf("custom logger panicked: %v", r)
|
|
}
|
|
}()
|
|
_, _ = BootstrapProxmox(context.Background(), Options{
|
|
Host: "10.0.0.1",
|
|
Password: "pw",
|
|
Logger: log,
|
|
})
|
|
_ = buf.String()
|
|
}
|
|
|
|
func TestBootstrapProxmox_ContextCancelled(t *testing.T) {
|
|
orig := sshDialer
|
|
defer func() { sshDialer = orig }()
|
|
sshDialer = &mockSSHDialer{err: errors.New("connection refused")}
|
|
|
|
setupORCAHome(t)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
_, err := BootstrapProxmox(ctx, Options{
|
|
Host: "10.0.0.1",
|
|
Password: "pw",
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected error with cancelled context")
|
|
}
|
|
}
|
|
|
|
func TestDeployPubKey_EmptyPubLine(t *testing.T) {
|
|
err := deployPubKey(nil, "orca", "")
|
|
if err == nil {
|
|
t.Error("expected error for empty pub line")
|
|
}
|
|
if !strings.Contains(err.Error(), "empty pub line") {
|
|
t.Errorf("error should mention empty pub line, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDeployPubKey_WhitespaceOnlyPubLine(t *testing.T) {
|
|
err := deployPubKey(nil, "orca", " \n \t ")
|
|
if err == nil {
|
|
t.Error("expected error for whitespace-only pub line")
|
|
}
|
|
}
|