118 lines
3.9 KiB
Go
118 lines
3.9 KiB
Go
package sshutilquality
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"scm.bstein.dev/bstein/ananke/internal/sshutil"
|
|
)
|
|
|
|
// TestHostKeyHeuristics runs one orchestration or CLI step.
|
|
// Signature: TestHostKeyHeuristics(t *testing.T).
|
|
// Why: covers host-key error detection and repair-decision heuristics used by bootstrap SSH retries.
|
|
func TestHostKeyHeuristics(t *testing.T) {
|
|
if sshutil.IsHostKeyError("", nil) {
|
|
t.Fatalf("expected nil error to not be host-key related")
|
|
}
|
|
if !sshutil.IsHostKeyError("Host key verification failed", errors.New("ssh failure")) {
|
|
t.Fatalf("expected host-key verification text to be detected")
|
|
}
|
|
if !sshutil.ShouldAttemptKnownHostsRepair("", errors.New("exit status 255")) {
|
|
t.Fatalf("expected empty-output exit-255 branch to trigger known_hosts repair")
|
|
}
|
|
if sshutil.ShouldAttemptKnownHostsRepair("permission denied", errors.New("permission denied")) {
|
|
t.Fatalf("unexpected known_hosts repair suggestion for non host-key error")
|
|
}
|
|
}
|
|
|
|
// TestKnownHostsDiscoveryAndMissingBinaryBranch runs one orchestration or CLI step.
|
|
// Signature: TestKnownHostsDiscoveryAndMissingBinaryBranch(t *testing.T).
|
|
// Why: verifies known_hosts path discovery and the graceful no-op path when ssh-keygen is unavailable.
|
|
func TestKnownHostsDiscoveryAndMissingBinaryBranch(t *testing.T) {
|
|
dir := t.TempDir()
|
|
cfgPath := filepath.Join(dir, "config")
|
|
keyPath := filepath.Join(dir, "id_ed25519")
|
|
if err := os.WriteFile(cfgPath, []byte("Host *\n"), 0o600); err != nil {
|
|
t.Fatalf("write config: %v", err)
|
|
}
|
|
if err := os.WriteFile(keyPath, []byte("key"), 0o600); err != nil {
|
|
t.Fatalf("write identity: %v", err)
|
|
}
|
|
files := sshutil.KnownHostsFiles(cfgPath, keyPath)
|
|
foundLocal := false
|
|
for _, path := range files {
|
|
if strings.Contains(path, dir) {
|
|
foundLocal = true
|
|
break
|
|
}
|
|
}
|
|
if !foundLocal {
|
|
t.Fatalf("expected derived known_hosts entries under %s, got %v", dir, files)
|
|
}
|
|
|
|
originalPath := os.Getenv("PATH")
|
|
t.Setenv("PATH", filepath.Join(dir, "missing-bin"))
|
|
var logs bytes.Buffer
|
|
sshutil.RepairKnownHosts(context.Background(), log.New(&logs, "", 0), files, []string{"titan-db"}, 22)
|
|
t.Setenv("PATH", originalPath)
|
|
if !strings.Contains(logs.String(), "ssh-keygen missing") {
|
|
t.Fatalf("expected missing ssh-keygen warning, got: %s", logs.String())
|
|
}
|
|
}
|
|
|
|
// TestRepairKnownHostsCoversSuccessNotFoundAndFailure runs one orchestration or CLI step.
|
|
// Signature: TestRepairKnownHostsCoversSuccessNotFoundAndFailure(t *testing.T).
|
|
// Why: exercises cleanup outcomes for successful removal, absent entries, and hard failures.
|
|
func TestRepairKnownHostsCoversSuccessNotFoundAndFailure(t *testing.T) {
|
|
dir := t.TempDir()
|
|
scriptPath := filepath.Join(dir, "ssh-keygen")
|
|
script := `#!/usr/bin/env sh
|
|
args="$*"
|
|
if echo "$args" | grep -q "notfound"; then
|
|
echo "Host notfound not found in $args" >&2
|
|
exit 1
|
|
fi
|
|
if echo "$args" | grep -q "boom"; then
|
|
echo "boom failure" >&2
|
|
exit 2
|
|
fi
|
|
echo "updated" >&2
|
|
exit 0
|
|
`
|
|
if err := os.WriteFile(scriptPath, []byte(script), 0o755); err != nil {
|
|
t.Fatalf("write fake ssh-keygen: %v", err)
|
|
}
|
|
t.Setenv("PATH", dir+":"+os.Getenv("PATH"))
|
|
|
|
knownHosts := filepath.Join(dir, "known_hosts")
|
|
if err := os.WriteFile(knownHosts, []byte("example"), 0o644); err != nil {
|
|
t.Fatalf("write known_hosts: %v", err)
|
|
}
|
|
if err := os.WriteFile(knownHosts+".old", []byte("backup"), 0o644); err != nil {
|
|
t.Fatalf("write known_hosts backup: %v", err)
|
|
}
|
|
|
|
var logs bytes.Buffer
|
|
logger := log.New(&logs, "", 0)
|
|
sshutil.RepairKnownHosts(
|
|
context.Background(),
|
|
logger,
|
|
[]string{knownHosts, knownHosts},
|
|
[]string{"good", "notfound", "boom", "good"},
|
|
22,
|
|
)
|
|
out := logs.String()
|
|
if !strings.Contains(out, "known_hosts repaired: removed good") {
|
|
t.Fatalf("expected successful cleanup log, got: %s", out)
|
|
}
|
|
if !strings.Contains(out, "warning: known_hosts cleanup failed for boom") {
|
|
t.Fatalf("expected failure warning for boom host, got: %s", out)
|
|
}
|
|
}
|