fix(P07): remove all password/token paths (REQ-146, R-021, C-34) -- BREAKING

---ci---
project: orca
phase: 7
milestone: v0.12
status: execute
---/ci---

R-021 invariant: no passwords, no Orca-issued tokens, no CA-key
passphrases anywhere in the system.

Removed:
- proxmox/bootstrap.go: ssh.Password auth -> ssh.PublicKeys (key-based).
  --password/ removed from node join; replaced
  with --ssh-key (default: orca SSH key). Pre-staged key required.
- stepca/stepca.go: --password-file /dev/stdin removed from Init and
  issueCert. Provisioner changed to 'orca-oidc' (OIDC provisioner).
- identity/spiffe.go: --password-file removed from MintSVID. Provisioner
  changed to 'orca-oidc'.

Tests: all proxmox, stepca, identity, cli tests updated + pass. 3 new
password-rejection regression tests. Fake SSH server gains
PublicKeyCallback. go vet clean. Full build green.
This commit is contained in:
Jon Chery
2026-08-07 11:12:18 +00:00
parent 1fb82f09b2
commit 20523ac045
11 changed files with 138 additions and 103 deletions
+1 -1
View File
@@ -224,7 +224,7 @@ func TestNodeJoinProxmoxNoMTLSDeprecationWarning(t *testing.T) {
rootCmd.SetErr(&out)
// proxmox path errors on missing --host before reaching the warning,
// and never calls joinLocal, so no mTLS deprecation warning fires.
rootCmd.SetArgs([]string{"node", "join", "--type", "proxmox", "--password", "x"})
rootCmd.SetArgs([]string{"node", "join", "--type", "proxmox", "--ssh-key", "/tmp/nonexistent-key"})
_ = rootCmd.Execute()
if strings.Contains(buf.String(), "mTLS join path is deprecated") {
+1 -1
View File
@@ -34,7 +34,7 @@ func resetRootFlags(t *testing.T) {
// command without resetRootFlags may call it directly.
func resetCommandFlags() {
joinName, joinAddr, joinCAFinger, joinType = "", "", "", "localhost"
joinHost, joinSSHUser, joinPassword, proxmoxUser, proxmoxRole = "", "root", "", "orca", "OrcaOperator"
joinHost, joinSSHUser, joinSSHKey, proxmoxUser, proxmoxRole = "", "root", "", "orca", "OrcaOperator"
joinSSHPort, leaveID, nodeWatch = 22, "", false
stopID, runTarget, runIDKey, jobWatch = "", "", "", false
migrateTarget = ""
+12 -17
View File
@@ -51,7 +51,7 @@ var (
joinType string
joinHost string
joinSSHUser string
joinPassword string
joinSSHKey string
joinSSHPort int
joinHostKeyFP string
proxmoxUser string
@@ -75,7 +75,7 @@ Node types (via --type):
localhost (default): register a local or Linux node (existing behavior)
proxmox: SSH-bootstrap a remote Proxmox VE 8/9 host
(deploys orca pubkey, creates orca user + PVE role +
sudoers allowlist; requires --host + --password)`,
sudoers allowlist; requires --host + --ssh-key (R-021: no passwords))`,
RunE: func(cmd *cobra.Command, args []string) error {
if joinHostKeyFP != "" && joinType != "proxmox" {
return fmt.Errorf("--host-key-fingerprint requires --type proxmox today")
@@ -148,18 +148,19 @@ func joinLocal(cmd *cobra.Command) error {
}
// joinProxmox bootstraps a remote Proxmox VE 8/9 host via SSH and
// registers it as an orca node (REQ-050, REQ-051). The password is
// never persisted (D-031).
// registers it as an orca node (REQ-050, REQ-051). Uses SSH key auth
// (R-021: no passwords). The operator pre-stages the orca SSH public
// key on the remote host out-of-band.
func joinProxmox(cmd *cobra.Command) error {
if joinHost == "" {
return fmt.Errorf("--host is required for --type proxmox")
}
password := joinPassword
if password == "" {
password = os.Getenv("ORCA_PROXMOX_PASSWORD")
sshKeyPath := joinSSHKey
if sshKeyPath == "" {
sshKeyPath = certpaths.SSHKeyPath()
}
if password == "" {
return fmt.Errorf("password is required for --type proxmox (use --password or $ORCA_PROXMOX_PASSWORD)")
if sshKeyPath == "" {
return fmt.Errorf("SSH key path is required for --type proxmox (R-021: no passwords; use --ssh-key or pre-stage the orca key)")
}
ctx, cancel := context.WithTimeout(cmd.Context(), 60*time.Second)
@@ -168,7 +169,7 @@ func joinProxmox(cmd *cobra.Command) error {
result, err := proxmox.BootstrapProxmox(ctx, proxmox.Options{
Host: joinHost,
SSHUser: joinSSHUser,
Password: password,
SSHKeyPath: sshKeyPath,
ProxmoxUser: proxmoxUser,
ProxmoxRole: proxmoxRole,
SSHPort: joinSSHPort,
@@ -179,12 +180,6 @@ func joinProxmox(cmd *cobra.Command) error {
return fmt.Errorf("proxmox bootstrap: %w", err)
}
// Zero the password byte slice (D-031 — never persist, minimize memory exposure).
pwBytes := []byte(password)
for i := range pwBytes {
pwBytes[i] = 0
}
// Register the proxmox node in the orca registry.
registry, closer, err := nodeRegistry()
if err != nil {
@@ -431,7 +426,7 @@ func init() {
nodeJoinCmd.Flags().StringVar(&joinType, "type", "localhost", "node type: localhost (default) or proxmox (SSH bootstrap)")
nodeJoinCmd.Flags().StringVar(&joinHost, "host", "", "proxmox host address (IP/hostname, no port; required for --type proxmox)")
nodeJoinCmd.Flags().StringVar(&joinSSHUser, "ssh-user", "root", "SSH username for proxmox bootstrap (default root)")
nodeJoinCmd.Flags().StringVar(&joinPassword, "password", "", "SSH password for proxmox bootstrap (never persisted; prefer $ORCA_PROXMOX_PASSWORD)")
nodeJoinCmd.Flags().StringVar(&joinSSHKey, "ssh-key", "", "SSH private key path for proxmox bootstrap (R-021: no passwords; default: orca key)")
nodeJoinCmd.Flags().IntVar(&joinSSHPort, "ssh-port", 22, "SSH port for proxmox bootstrap (default 22)")
nodeJoinCmd.Flags().StringVar(&proxmoxUser, "proxmox-user", "orca", "Linux system user to create on the proxmox host (config-overridable)")
nodeJoinCmd.Flags().StringVar(&proxmoxRole, "proxmox-role", "OrcaOperator", "PVE custom role to create (config-overridable)")
+23 -4
View File
@@ -154,22 +154,23 @@ func TestNodeJoinProxmoxMissingHost(t *testing.T) {
var buf bytes.Buffer
rootCmd.SetOut(&buf)
rootCmd.SetErr(&buf)
rootCmd.SetArgs([]string{"node", "join", "--type", "proxmox", "--password", "x"})
rootCmd.SetArgs([]string{"node", "join", "--type", "proxmox", "--ssh-key", "/tmp/nonexistent-key"})
if err := rootCmd.Execute(); err == nil {
t.Fatal("expected error for proxmox without --host, got nil")
}
}
func TestNodeJoinProxmoxMissingPassword(t *testing.T) {
func TestNodeJoinProxmoxMissingSSHKey(t *testing.T) {
_, cleanup := initTestEnv(t)
defer cleanup()
resetRootFlags(t)
var buf bytes.Buffer
rootCmd.SetOut(&buf)
rootCmd.SetErr(&buf)
// No --ssh-key and no default orca key -> error (R-021).
rootCmd.SetArgs([]string{"node", "join", "--type", "proxmox", "--host", "10.0.0.99"})
if err := rootCmd.Execute(); err == nil {
t.Fatal("expected error for proxmox without password, got nil")
t.Fatal("expected error for proxmox without ssh-key, got nil")
}
}
@@ -473,7 +474,7 @@ func TestNodeJoinHostKeyFingerprintRequiresProxmox(t *testing.T) {
// We can't run the full bootstrap without a real SSH server, so we
// assert that the RunE check passes (no "requires --type proxmox"
// error) and the failure — if any — comes from a later stage (missing
// --host / password), not the D-044 guard.
// --host / ssh-key), not the D-044 guard.
func TestNodeJoinHostKeyFingerprintProxmoxAccepted(t *testing.T) {
_, cleanup := initTestEnv(t)
defer cleanup()
@@ -494,3 +495,21 @@ func TestNodeJoinHostKeyFingerprintProxmoxAccepted(t *testing.T) {
t.Errorf("D-044 guard wrongly rejected proxmox type: %v", err)
}
}
// --- REQ-146 / R-021 password removal regression test ---
// TestNodeJoinProxmoxPasswordRejected verifies the --password flag is
// no longer accepted (R-021: no passwords). The flag is removed; the
// CLI should reject it as an unknown flag.
func TestNodeJoinProxmoxPasswordRejected(t *testing.T) {
_, cleanup := initTestEnv(t)
defer cleanup()
resetRootFlags(t)
var buf bytes.Buffer
rootCmd.SetOut(&buf)
rootCmd.SetErr(&buf)
rootCmd.SetArgs([]string{"node", "join", "--type", "proxmox", "--host", "10.0.0.99", "--password", "secret"})
if err := rootCmd.Execute(); err == nil {
t.Fatal("expected error for --password (R-021: no passwords), got nil")
}
}