Post-repair hardware validation pipeline for Proxmox cluster hosts. Go orchestrator + in-image agent + mkosi live image + bundled dnsmasq PXE + SQLite + HTMX/SSE UI + notify registry + janitor + full docs.
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
package hold
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// TestIssueRoundTrip checks that the private key we write is parseable
|
||||
// with the standard openssh library and that its derived public key
|
||||
// byte-for-byte matches the authorized_key line we handed the agent.
|
||||
// If this drifts — e.g. we swap from ed25519 to something else, or
|
||||
// mangle the comment — the operator's `ssh -i path root@ip` breaks
|
||||
// silently. The test is the only early-warning we have.
|
||||
func TestIssueRoundTrip(t *testing.T) {
|
||||
kp, err := Issue(42)
|
||||
if err != nil {
|
||||
t.Fatalf("Issue: %v", err)
|
||||
}
|
||||
|
||||
// Parse the private key back.
|
||||
signer, err := ssh.ParsePrivateKey(kp.PrivatePEM)
|
||||
if err != nil {
|
||||
t.Fatalf("ParsePrivateKey: %v", err)
|
||||
}
|
||||
|
||||
// The public derived from the signer must match the authorized_key line.
|
||||
gotAuth := strings.TrimRight(string(ssh.MarshalAuthorizedKey(signer.PublicKey())), "\n")
|
||||
wantAuth := kp.AuthorizedKey
|
||||
// Authorized_keys comment is ours; compare just the type+b64 prefix.
|
||||
gotParts := strings.SplitN(gotAuth, " ", 3)
|
||||
wantParts := strings.SplitN(wantAuth, " ", 3)
|
||||
if len(gotParts) < 2 || len(wantParts) < 2 {
|
||||
t.Fatalf("unexpected authorized_key shape got=%q want=%q", gotAuth, wantAuth)
|
||||
}
|
||||
if gotParts[0] != wantParts[0] || gotParts[1] != wantParts[1] {
|
||||
t.Fatalf("public key mismatch:\n got %s\n want %s", gotAuth, wantAuth)
|
||||
}
|
||||
if !strings.Contains(wantAuth, "vetting-hold-42") {
|
||||
t.Fatalf("authorized_key line missing run tag: %q", wantAuth)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIssueKeysAreEd25519 pins the algorithm — anything other than
|
||||
// ed25519 would surprise operators who've been told their hold key is
|
||||
// ed25519 (and would change key-file sizes, path handling, etc.).
|
||||
func TestIssueKeysAreEd25519(t *testing.T) {
|
||||
kp, err := Issue(1)
|
||||
if err != nil {
|
||||
t.Fatalf("Issue: %v", err)
|
||||
}
|
||||
signer, err := ssh.ParsePrivateKey(kp.PrivatePEM)
|
||||
if err != nil {
|
||||
t.Fatalf("ParsePrivateKey: %v", err)
|
||||
}
|
||||
if got := signer.PublicKey().Type(); got != ssh.KeyAlgoED25519 {
|
||||
t.Fatalf("key algorithm: got %s, want ssh-ed25519", got)
|
||||
}
|
||||
// Paranoia: the Ed25519 public key underneath should be 32 bytes.
|
||||
edPub, ok := signer.PublicKey().(ssh.CryptoPublicKey)
|
||||
if !ok {
|
||||
t.Fatalf("public key does not expose CryptoPublicKey")
|
||||
}
|
||||
raw, ok := edPub.CryptoPublicKey().(ed25519.PublicKey)
|
||||
if !ok {
|
||||
t.Fatalf("public key is not ed25519.PublicKey")
|
||||
}
|
||||
if len(raw) != ed25519.PublicKeySize {
|
||||
t.Fatalf("ed25519 pubkey size = %d, want %d", len(raw), ed25519.PublicKeySize)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWritePrivateToSetsPerms(t *testing.T) {
|
||||
kp, err := Issue(7)
|
||||
if err != nil {
|
||||
t.Fatalf("Issue: %v", err)
|
||||
}
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "nested", "hold.key")
|
||||
abs, err := kp.WritePrivateTo(path)
|
||||
if err != nil {
|
||||
t.Fatalf("WritePrivateTo: %v", err)
|
||||
}
|
||||
if !filepath.IsAbs(abs) {
|
||||
t.Fatalf("expected absolute path, got %q", abs)
|
||||
}
|
||||
buf, err := os.ReadFile(abs)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFile: %v", err)
|
||||
}
|
||||
if !bytes.Equal(buf, kp.PrivatePEM) {
|
||||
t.Fatalf("on-disk bytes differ from in-memory PEM")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user