Skip to main content

Format

A self-hosted license is an Ed25519-signed JWT:
  • Signature scheme: EdDSA (RFC 8037 Ed25519).
  • The radar-hub binary embeds the Skyhook-controlled public key at build time. The matching private key never leaves Skyhook’s secret store.
  • Verified offline at boot - no network call required for the signature check itself.

Claims

The control plane rejects any JWT whose iss != "skyhook" or aud doesn’t include "radar-hub". Required claims: sub, org, exp. Everything else is optional.

How verification works

At boot

The control plane reads the JWT from one of:
  1. RADAR_HUB_LICENSE_KEY env var (preferred).
  2. /etc/radar-hub/license file (Helm Secret mount).
Boot logs report one of (all prefixed with radar-hub: in the log line - grep for these strings): License-set path (the most common case):
  • radar-hub: license verified - happy path. Same log line carries license id, org, tier, max_clusters, expiry.
  • radar-hub: license is expired (warn-only - hub keeps serving) - signature OK, past exp. Continues serving.
  • radar-hub: license verification failed - signature mismatch or missing required claim. Continues serving in warn-only mode for v1. Investigate immediately - this almost always means the wrong build talking to a real key, or vice versa.
License-not-set path (separate slog call earlier in boot, also v1-warn-only):
  • radar-hub: self-hosted mode but no RADAR_HUB_LICENSE_KEY (or /etc/radar-hub/license) - running in unlicensed mode (warn-only banner) - the JWT file/env wasn’t supplied. Set license.key in your Helm values or mount a Secret to /etc/radar-hub/license.
Placeholder-key build (won’t appear on official ghcr.io/skyhook-dev/radar-hub images):
  • radar-hub: embedded license public key is the all-zero placeholder; rebuild with the real key before shipping to a customer - the build wasn’t cut against the real Skyhook public key, so license verification cannot succeed against any real customer JWT. Only happens on a locally-built image - the official ghcr.io/skyhook-dev/radar-hub:<version> images embed the production public key. If you see this in production, you’re running an unofficial build; pull the published tag instead.

Warn-only enforcement

The control plane does NOT block writes when the license is invalid or expired. Enforcement is contractual via the Subscription Agreement, not technical. The user-visible behavior is:
  • A persistent yellow banner in the web app when verification has failed.
  • Boot log lines visible to the operator.
  • Heartbeat reports the issue back to Skyhook.
Warn-only is intentional: a license blip should never compound a real outage by locking your team out of their own observability tool.

License heartbeat

The control plane posts a heartbeat every 6 hours. On by default; disable with --set license.heartbeat.enabled=false if your environment forbids outbound HTTPS.
  • Endpoint: POST https://api.radarhq.io/v1/license/heartbeat
  • Payload: license_id, hub_version, hub_mode, org_name, cluster_count, audit_event_count_24h, started_at.
  • Response: optional renewed JWT, the latest published hub_version, optional message string.
What’s NOT sent:
  • No cluster names, no user emails, no audit-event content.
  • No customer Postgres data.
  • No tunnel traffic.
The full heartbeat schema and a sample payload are reproduced in the Privacy Policy + Subscription Agreement so a customer’s security review can audit the wire shape. The customer can audit live by tailing control-plane logs - every heartbeat call is logged.

Failure ladder

When the control plane can’t reach the heartbeat endpoint: If the underlying JWT itself expires AND the heartbeat hasn’t renewed it for 14d, the banner shows “License expired - contact your account team”. Still no write block.

Rotation

Licenses are issued for a fixed term (typically 12 months) matching the contract. Renewal is either:
  • Heartbeat-renewed: the renewal response carries a new JWT. The control plane stores it in Postgres and uses it on next boot.
  • Manual (fallback when heartbeat is disabled, or when your account team needs to push an out-of-cycle renewal): your account team emails a new JWT; you update the Helm value or Secret + roll the radar-hub Deployment.

Key rotation (Skyhook-side)

The embedded public key is append-only. Skyhook will publish a new chart appVersion with both the old and new public keys when the signing key rotates; existing deployments keep working until they upgrade past the cutover release. You don’t need to do anything for a public-key rotation - just stay on supported chart versions and run helm upgrade at your normal cadence.

What you control

  • The license JWT itself (rotate by upgrading the chart with a new value).
  • Cookie sealing key (hub.cookiePassword) - independent from the license; rotating it just signs everyone out.
  • The break-glass admin (always available, regardless of license state).

What Skyhook controls

  • The signing private key.
  • The license-server URL.
  • The terms of the Subscription Agreement that backs warn-only enforcement.