Skip to content

The control plane

The control plane is the vendor console: customers, licences, deployments, usage and billing. It is not part of the product a school receives, and it holds no customer content.

This page documents it because it is a component of the system, and because what it does and does not hold is the answer to a procurement question.

HoldsDoes not hold
Customers and their deploymentsAny student or staff record
Issued licences and their statusAny ticket, repair or loan
Usage counts: technician accounts, managed devices, enabled modules, versionAny connector credential
Billing records and invoicesAnything a school typed into the product
Release records and channels

The control plane is global. Customer data is not, and stays in the region on the deployment. This split is deliberate and is what makes the residency statement precise.

ComponentNotes
The control plane applicationThe same monorepo build as the product, from apps/control-plane
Its own PostgreSQLSeparate from every tenant instance. Storage is never shared between the vendor plane and a customer
A tunnelOne tunnel, several ingress rules

Neither the application nor its database publishes a host port. The tunnel reaches them over a private Docker network, so nothing is exposed on the LAN.

The panel and the telemetry endpoint live on different hostnames on purpose.

Cloudflare Access binds to a hostname, not to a service. Customer instances have no human identity to present at an Access prompt, so an Access-gated hostname would redirect every report they ever send.

So:

  • panel.plugboard.app is Access-gated. A person signing in has an identity, and Access is what verifies it.
  • telemetry.plugboard.app skips Access, and is narrowed two other ways instead.

The telemetry hostname is narrowed by path: only /api/usage, /api/licence/:jti/status and /api/releases/latest route through. Everything else on that hostname, including the console and the whole admin API, is a flat 404 at the edge. Not “401 if you guess the token”: the route does not exist.

And by credential: both endpoints authenticate with the instance’s own licence key, so an open hostname is not an unauthenticated one.

The licence key is the credential. It is an Ed25519-signed token that only this control plane can mint, presented as a bearer token.

Ingest checks, in order: signature, issuer, audience, expiry, that the licence exists and is not revoked, that any licence id in the body matches the bearer, that the timestamp is within the replay window, and that the reporting deployment id matches the licence’s deployment binding when one is set.

A key cannot be forged. The remaining risk is a key being copied off a customer’s server, and three things contain that:

ControlEffect
Replay window, 30 minutes by defaultA captured request cannot be replayed later to re-assert stale counts. Snapshots are idempotent full-state reports, so without this a replay could distort a billing period
Deployment bindingIf set on a licence, reports from any other install are refused outright
Multi-deployment detectionA licence reporting from more than one install is surfaced under Alerts. Not blocked by default, because a legitimate rebuild also produces a new install id, so it wants a human glance rather than an outage

Revocation takes effect on the next report. Revoke in the panel and ingest starts returning 403 immediately.

The panel accepts two credentials, preferring the first.

  1. The Cloudflare Access JWT. Access sends an assertion header to the origin on every authenticated request; the panel verifies it against the team’s JWKS and the application’s audience tag. This identifies which admin is acting, needs no shared secret, and means the console requires no token at all. Service-token JWTs are rejected because they carry no email and so cannot drive the panel.
  2. A shared admin token. Kept because Access policies bind to a hostname rather than to a service, so local development, direct access on the Docker network, or a future tunnel rule that forgets to attach an Access application would otherwise be unauthenticated.

Once browser sign-in is confirmed working, setting PANEL_REQUIRE_ACCESS_JWT=1 refuses the shared token entirely, leaving an Access identity as the only way in.

Issuing licences. Naming a tier fills in the numbers, so issuing a licence means picking a tier rather than retyping nine figures and hoping they match the quote. The key is revealed once and copied to the customer.

Watching deployments check in. Version, region, channel, last report, and the counts the plan is measured against.

Onboarding. Pre-building a deployment, sending the invitation, and watching the customer’s confirmation trigger provisioning. See onboarding.

Releases. A release is registered with the panel on publication, with its version, channel, image digest and notes, so the fleet knows it exists.

Billing roll-ups. Monthly accrual and CSV export. Chargebee owns invoices and payment; entitlement stays in the signed licence the deployment already holds, so a school’s service desk keeps working through a billing outage.

Alerts. Deployments that stopped reporting, licences reporting from more than one install, and anything else that wants a human glance.

  1. The licence signing private key. Lose it and you cannot issue or renew licences for any existing customer, because instances only trust its public half. A copy belongs in a password manager, not only on the host.
  2. The control-plane database. Customers, issued licences, deployments and usage history. Without it you can still issue new licences but the billing record is gone.

Both belong in the same escrow discipline as a customer’s SECRETS_MASTER_KEY. See secrets and keys.

The control plane reads its configuration from an environment file that is generated on the host, mode 600, and never committed. The full variable list is in environment variables.