Skip to content

License Runtime

The signed runtime lease

The licensing server issues a short-lived, signed lease alongside the long-lived license certificate. The lease contains claims including:

Claim Meaning
schema_version Protocol schema; unknown critical versions are rejected.
kid Signing key id; must be a pinned trusted key.
license_id Opaque license identifier (safe to show operators).
product Must match KRAPPLE_PRODUCT.
installation_id Binds the lease to this installation.
status ACTIVE/EXPIRING/EXPIRED/SUSPENDED/REVOKED/…
not_before UTC; a future value fails closed.
expires_at UTC business expiry.
lease_expires_at UTC runtime lease expiry (server-signed).
issued_at, server_time, nonce Freshness / replay protection.
lease_generation Monotonic; newer states win.
entitlements.features / .limits Feature flags and numeric limits.

The Ed25519 signature covers the exact certificate bytes as transmitted. Only the server holds the private key.

Refresh

  • Calls resume() then validate() over HTTPS with certificate verification, bounded connect/overall timeouts, jitter and exponential backoff.
  • Serializes concurrent workers with a cache lock (pterocloud:license:refresh).
  • Never extends the lease client-side — only the server-signed lease_expires_at matters.
  • Ingests a fresh signed lease into the lease store.
  • Records transitions and anomalies as security events.

Scheduled every five minutes by default.

Store rules

  • A still-valid verified lease is never overwritten by an invalid/corrupt response.
  • A newer valid SUSPENDED/REVOKED lease supersedes an older ACTIVE lease.
  • An older lease_generation never overwrites a newer one.
  • Deleting local state yields UNVERIFIED / locked (never "free").
  • Editing local state makes the signature invalid and locks.
  • Copying state to another installation makes the binding mismatch and locks.
  • The store is re-verified on every read; encryption at rest is defence in depth, the signature is the trust boundary.

Enforcement points

Route middleware (RequirePteroCloudLicense) is the first line:

  • Unconfigured install (no product) → pass through.
  • Usable lease → pass through.
  • Locked:
    • Protected JSON/API → 403 {"error":"LICENSE_REQUIRED","state":…}.
    • Protected admin page → 200 lock UI (no license details).
  • Allow-list while locked: license page/activation/refresh, diagnostics, safe health, uninstall/repair.

The middleware is not the only enforcement point. The service layer independently blocks commercial mutations via LicenseGate::authorizeOperation() for: instance provisioning, volume creation, additional/floating IP allocation, provider creation, hypervisor creation, new console sessions, native node enrollment tokens and non-recovery migrations. Removing the middleware does not unlock these paths.

Queue jobs

Jobs check at dispatch and at execution; a serialized flag is never trusted. A job whose license lapsed mid-flight unwinds through the rollback checkpoint (never abandoning half-created infrastructure). Blocked jobs are not blindly replayed on unlock.

Agents and consoles

  • No master/private key ever ships to an agent.
  • No new commercial tasks are dispatched while locked.
  • Existing QEMU/Proxmox guests keep running.
  • Existing console sessions are not force-terminated; new sessions are denied while locked.