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()thenvalidate()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_atmatters. - 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/REVOKEDlease supersedes an olderACTIVElease. - An older
lease_generationnever 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 →
200lock UI (no license details).
- Protected JSON/API →
- 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.