v0.107.0: key-auth-first bridge + staged-secret wipe on escrow confirm
Key-auth-first: a KeyAuthProber seam lets the bridge skip consume+install when the already-installed key still authenticates (pinned to the freshly verified host key) — descriptor changes on provisioned guests no longer loop on consume-404. Fingerprint verify still precedes everything. Wipe-on-escrowed: confirm-escrow now calls the agent's new DELETE /escrow/stage-secret (v0.78.0) best-effort, closing the hygiene gap where a ceremony-less confirm left the staged password file behind. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -191,6 +191,45 @@ func (SSHCopyIDInstaller) Install(ctx context.Context, host, user string, port i
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- SFTPKeyAuthProber: does the ALREADY-INSTALLED key still authenticate? (key-auth-first) ---
|
||||
|
||||
// SFTPKeyAuthProber probes passwordless auth with the existing installed key (KeyPath), pinned to the
|
||||
// freshly-verified knownHosts line. No key file → ok=false (fresh guest). The probe never logs secrets.
|
||||
type SFTPKeyAuthProber struct {
|
||||
KeyPath string // the installed key, e.g. <dataDir>/offbox/ssh_key
|
||||
Timeout time.Duration // per-probe budget; 0 → 20s
|
||||
}
|
||||
|
||||
func (p SFTPKeyAuthProber) Probe(ctx context.Context, host, user string, port int, knownHosts string) (string, bool) {
|
||||
pem, err := os.ReadFile(p.KeyPath)
|
||||
if err != nil {
|
||||
return "", false // no existing key — a fresh guest; take the full path
|
||||
}
|
||||
timeout := p.Timeout
|
||||
if timeout == 0 {
|
||||
timeout = 20 * time.Second
|
||||
}
|
||||
pctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
work, err := os.MkdirTemp("", "felhom-keyprobe-")
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
defer os.RemoveAll(work)
|
||||
khPath := filepath.Join(work, "known_hosts")
|
||||
if err := os.WriteFile(khPath, []byte(knownHosts+"\n"), 0o600); err != nil {
|
||||
return "", false
|
||||
}
|
||||
probe := exec.CommandContext(pctx, "sftp", "-b", "-", "-P", strconv.Itoa(port),
|
||||
"-i", p.KeyPath, "-oBatchMode=yes", "-oConnectTimeout=10",
|
||||
"-oStrictHostKeyChecking=yes", "-oUserKnownHostsFile="+khPath, user+"@"+host)
|
||||
probe.Stdin = strings.NewReader("pwd\n")
|
||||
if err := probe.Run(); err != nil {
|
||||
return "", false // auth refused / unreachable — fall through to the full path
|
||||
}
|
||||
return string(pem), true
|
||||
}
|
||||
|
||||
func truncate(b []byte) string {
|
||||
s := strings.TrimSpace(string(b))
|
||||
if len(s) > 300 {
|
||||
|
||||
Reference in New Issue
Block a user