Skip to content

[feature-request] Expose VM SSH host public key(s) in the customer API after provisioning #154

Description

@akovalenko

Summary

After a VM is provisioned, expose its SSH host public key(s) through the customer API, so a client can verify the host key out-of-band on the first SSH connect instead of falling back to blind trust-on-first-use (StrictHostKeyChecking=accept-new).

Motivation

On the very first SSH connection to a freshly provisioned VM, the host key is unknown, so automation has to accept whatever key is presented (accept-new) or disable checking entirely (=no). There is currently no channel in the customer API to learn the host key ahead of — or independently from — that first connection:

  • GET /api/v1/vm/{id} (ApiVmStatus) returns id / created / expires / mac_address / image / template / ssh_key / ip_assignments / status / auto_renewal_enabled — no host key. (ssh_key here is the customer's authorized key, not the VM's host key.)
  • The only out-of-band channel today is the serial-console WebSocket (GET /api/v1/vm/{id}/console): cloud-init prints the host-key fingerprint banner there at first boot. But it's a live stream with no scrollback, so it only helps if a client is attached at exactly the right moment during boot, and it yields free-text fingerprints rather than structured keys.

Net effect: secure, automated first-connect verification is impractical.

Proposed solution

Add the VM's SSH host public key(s) to the customer API, populated once the VM reaches running:

  • A read-only field on ApiVmStatus, e.g.:

    "host_ssh_keys": [
      {
        "type": "ssh-ed25519",
        "public_key": "AAAAC3NzaC1lZDI1NTE5AAAA...",
        "fingerprint_sha256": "SHA256:..."
      }
    ]

    (or a dedicated GET /api/v1/vm/{id}/host-keys endpoint).

  • null / empty until first boot completes; populated after capture.

  • Re-captured after re-install (which regenerates the host keys). Normal reconfig / IP changes do not change them — the shared vendor snippet already sets ssh_deletekeys: false — so the value is otherwise stable for the life of the VM.

Capture (host-side, no guest credentials required)

The API already SSHes to the Proxmox node and runs a post-provision worker (state creating → running). Once the VM is running, the node can obtain the guest's host key without logging into the guest, e.g.:

  • ssh-keyscan <vm-ip> from the Proxmox node — simple, image-agnostic; or
  • qemu-guest-agent guest-exec reading /etc/ssh/ssh_host_*_key.pub, if the guest agent is present — authoritative, since it reads the on-disk key rather than a presented one.

Only public keys are read or stored.

Why this shape

An alternative would be letting the client submit its own host key at order time (cloud-init ssh_keys: injection), which would give the fingerprint a-priori. Exposing the generated key is a better fit for this API:

  • Public-key only — no need to accept or store customer private keys (secret at rest / extra attack surface).
  • Additive, read-only — no changes to the order request structs, and no new per-VM user-data snippet mechanism (today there is only a single shared vendor snippet).
  • Fits the existing post-boot worker and the re-install lifecycle naturally.
  • Matches how other providers surface host keys (Hetzner Cloud, DigitalOcean, EC2 all expose them post-boot; none accept a client-supplied host private key).

Alternative considered

Accept a client-supplied host key at order time and inject it via cloud-init. Strictly stronger trust (the fingerprint is known before the VM even boots, requiring zero trust in the boot), but it means ingesting and storing private-key material and adding a per-VM user-data path that doesn't exist today. Noted for completeness; the read-only expose above is preferred.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions