Skip to content

Security

Security reference for the control plane: the threat model, enrollment and credential machinery, middleware guarantees, audit events and the operator checklist.

Commercial security status

Threat model

Actor Can do Must NOT be able to
Panel user (Client API) Manage own instances. See or touch others' instances (existence never leaked: 404, not 403).
Application API key Read/write per its scope. Exceed its ACL level; obtain it without a manual grant.
Hypervisor agent Heartbeat, push metrics, claim and drive its own tasks. Impersonate another hypervisor, touch foreign tasks (404), inject command strings, replay old requests.
Whoever holds an enrollment token Enroll once, within the TTL. Reuse the token, enroll after expiry/revocation, enumerate tokens (hashes only).
Proxmox API token (operator-scoped) What PVE ACLs allow. Exceed least privilege (operator duty).
Database reader See rows. Recover any secret: credential secrets are bcrypt, enrollment tokens are SHA-256, driver configs are encrypted.

Out of scope for this stage: per-request HMAC signing and mTLS — the deliberate future hardening path.

No-arbitrary-shell guarantee

Agents receive exactly one thing: validated task payloads. A per-type validator whitelists keys; unknown keys are rejected at task creation, and no schema anywhere carries a command string, path or shell fragment. There is no "run this" field in the protocol, and the agent API exposes no endpoint that accepts one. Agent-reported capability strings are intersected with an allow-list before persisting.

Root-password policy

Regenerating the root password generates a fresh random password, returns it once in the response, and never stores it — no column exists for it. The operation is recorded for audit. Applying the password inside the guest is a cloud-init/agent concern; until then the panel generates and displays but does not inject.

What is never logged

  • Raw enrollment tokens — only the SHA-256 hash is stored.
  • Credential secrets — never stored (bcrypt only), never logged.
  • Proxmox token secrets — stored encrypted; API errors are re-thrown with the secret replaced by [redacted].
  • Extension exception messages never contain internals.

Audit events

Event When
cloud:agent.enrolled Successful enrollment.
cloud:agent.enrollment_token_reused Burned token presented again.
cloud:agent.authentication_failed Failed credential verification.
cloud:agent.revoked Credential revoked.
cloud:hypervisor.* Registration, health transitions, maintenance.
cloud:instance.* Full control-plane audit via the activity bridge.

Operator checklist: Proxmox tokens (least privilege)

  1. Create a dedicated PVE user (for example pterocloud@pve) and API token. Prefer --privsep 0 so the role is the single source of truth.
  2. Grant exactly the privileges the driver needs — see Proxmox permissions.
  3. Scope to /nodes/{node} and the storage path — never grant / unless required.
  4. Use pinned_fingerprint or a correct CA; never disable TLS verification.
  5. Rotate by registering a new token, updating the connection, then deleting the old token in PVE.
  6. Keep the VMID start above every manually created VMID so panel-allocated VMIDs never collide with hand-made VMs.

Future hardening path

  • HMAC request signing — sign method+path+timestamp+body with the credential secret, making the timestamp mandatory and killing replay without trusting TLS alone. Header names and the timestamp window are already in place.
  • mTLS — per-hypervisor client certificates as an alternative to bearer secrets.
  • Nonce store — deliberately skipped now; HMAC + timestamp makes replay windows small enough that a nonce table adds little.