sshutil

package
v0.10.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 16, 2026 License: Apache-2.0 Imports: 4 Imported by: 0

Documentation

Overview

Package sshutil holds the small SSH helpers shared by the CLI and the TUI: installing a public key into a live guest's authorized_keys over SSH. It lives outside cmd so internal/tui (which cannot import cmd) can reuse the exact same logic behind `hlab {vm,ct} add-ssh-key` and the dashboard's inject-key action.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AgentHasKeys added in v0.10.4

func AgentHasKeys() bool

AgentHasKeys reports whether the local SSH agent holds at least one identity.

It gates the dotfiles catalog entry: the playbook clones dotfiles_repo (an SSH URL) from inside the guest over the operator's forwarded agent, so an agent with no identities cannot clone it. It lives here rather than in cmd because the engine — which both the CLI and the TUI go through — is where that check has to happen for every caller to get it.

It fails closed: an ssh-add that won't run at all counts as "no keys", since a forwarded agent hlab cannot even interrogate is not one Ansible can clone with.

func AppendAuthorizedKey

func AppendAuthorizedKey(user, ip, pubKey string) error

AppendAuthorizedKey installs a public key into a live guest's ~/.ssh/authorized_keys over SSH, non-interactively (BatchMode) and idempotently: it creates ~/.ssh (700) and authorized_keys (600) if missing and appends the key only when the exact line isn't already present (grep -qxF).

BatchMode=yes disables every interactive prompt (notably the password prompt), so this NEVER blocks asking for a password: if the guest doesn't already trust a key hlab can use, ssh fails with "Permission denied" instead of prompting. That auth failure is translated into an actionable message (see authFailureHint) rather than a raw ssh dump — the usual cause is a keyless guest whose sshd refuses password auth (e.g. an LXC container's root, PermitRootLogin prohibit-password), which no amount of retrying over SSH can fix.

func AuthorizedKeyCommand

func AuthorizedKeyCommand(pubKey string) string

AuthorizedKeyCommand returns the idempotent shell command that appends pubKey to the login user's ~/.ssh/authorized_keys. Shared by both install paths — SSH (AppendAuthorizedKey) and the Proxmox console (engine.InjectSSHKeyViaConsole, which logs in as root, where ~ is /root) — so the key is installed identically however hlab reaches the guest.

func AuthorizedKeyCommandFor

func AuthorizedKeyCommandFor(user string) string

AuthorizedKeyCommandFor returns the idempotent `/bin/sh -c` script that appends an SSH public key to a NAMED user's ~/.ssh/authorized_keys with correct ownership and permissions. Unlike AuthorizedKeyCommand (which targets the connection user's ~, i.e. root over the LXC console), this resolves the user's home via `getent passwd` and chowns everything to that user — it is used by the VM guest-agent path (engine.InjectSSHKeyViaAgent), where the agent runs as root but the key belongs to the unprivileged login user.

The key is NOT embedded in the script: it is read from stdin (`key=$(cat)`), so the caller feeds it via the guest agent's input-data. That avoids shell-quoting the key into the command entirely — the script is identical for every key.

func Forget added in v0.10.3

func Forget(host string) error

Forget removes every host key recorded for host from the known_hosts files ssh would actually consult for it.

hlab is what puts those entries there in the first place: AppendAuthorizedKey connects with StrictHostKeyChecking=accept-new, and the plain ssh behind `hlab {vm,ct} ssh` records the key on the usual TOFU prompt. Both write to the operator's real known_hosts. Nothing ever took them back out, so destroying a guest and creating another one at the same address (the normal homelab pattern — recycled test IDs and a small static-IP pool) left a stale entry behind and the next ssh died with "REMOTE HOST IDENTIFICATION HAS CHANGED".

Callers must hook this to a *mutation* (create/destroy), never to a connection. At create/destroy hlab knows first-hand that the guest at host is brand new or gone, so the recorded key is stale by construction and dropping it decides nothing about trust. Dropping entries at connect time instead would silently turn every genuine man-in-the-middle warning into an accept, which is the one thing known_hosts exists to prevent.

Removal goes through `ssh-keygen -R` rather than editing the file directly: known_hosts is often hashed (HashKnownHosts), where the hostname never appears in plain text and no grep/sed can find it. ssh-keygen also handles every key type at once and retains the previous contents as <file>.old.

func HostKeysMismatch added in v0.10.3

func HostKeysMismatch(recorded, live map[string]string) bool

HostKeysMismatch reports whether anything recorded for a host contradicts what it actually presents — the precise definition of a stale entry, and the only thing `known-hosts clean --all` is allowed to remove.

Only algorithms present on BOTH sides are compared. An algorithm recorded but not offered (or offered but not recorded) is not a contradiction: sshd may have stopped offering a type, or the operator may only ever have accepted one. That asymmetry is normal and must not cost a correct entry.

func IsHostKeyMismatch added in v0.10.3

func IsHostKeyMismatch(sshOutput string) bool

IsHostKeyMismatch reports whether ssh output is the "host identification has changed" refusal — a stale known_hosts entry — rather than any other failure. AppendAuthorizedKey connects with accept-new, which silently records an unknown host but hard-fails on a changed one, and that failure is not an auth failure, so authFailureHint doesn't catch it and the caller would otherwise print a raw ssh dump.

func KeylessAddKeyError

func KeylessAddKeyError(name string, isLXC bool) error

KeylessAddKeyError is the error returned by the add-ssh-key / inject-key flows when a key cannot be installed at all. For a VM the recovery channel is the QEMU guest agent (engine.InjectSSHKeyViaAgent), so the VM branch is the fallback shown only when that channel can't be used — the agent isn't running or the token lacks VM.GuestAgent.Unrestricted (the real agent/privilege error is threaded up instead where possible). For a container hlab CAN inject the first key over the Proxmox console using the root password: it uses the stored password, or prompts for it (or takes `--password`), so a keyless container is recoverable as long as you know the root password. This error is for the case where even that is impossible (no password stored, none entered, and no prompt available — e.g. a non-interactive run). The fix is to supply the root password or seed a key out of band. Shared by the CLI handler, the TUI inject flow and the engine's console injection.

func KnownHostsFiles added in v0.10.3

func KnownHostsFiles(host string) []string

KnownHostsFiles returns the user known_hosts files ssh would consult for host, resolved through `ssh -G` so that a UserKnownHostsFile override in the operator's ssh_config is honoured — a bare `ssh-keygen -R` would edit the default file, which may not be the one in use. Files that don't exist are dropped: ssh-keygen -R fails outright (exit 255, "Cannot stat") on a missing file, and a file with no entries is nothing to clean anyway.

Best-effort by design: any failure to interrogate ssh yields no files, and the caller simply skips the cleanup.

func LiveHostKeys added in v0.10.3

func LiveHostKeys(host string) (map[string]string, error)

LiveHostKeys returns the host keys the guest at host currently presents, keyed by algorithm, read straight off the wire with ssh-keyscan. An unreachable guest yields an error, which callers treat as "don't touch this entry" — a host we cannot ask about is a host we cannot prove stale.

func RecordedHostKeys added in v0.10.3

func RecordedHostKeys(host string) map[string]string

RecordedHostKeys returns the keys known_hosts currently records for host, keyed by algorithm. ssh-keygen -F is used rather than reading the file, so hashed entries resolve correctly.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL