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.
What it holds
Section titled “What it holds”| Holds | Does not hold |
|---|---|
| Customers and their deployments | Any student or staff record |
| Issued licences and their status | Any ticket, repair or loan |
| Usage counts: technician accounts, managed devices, enabled modules, version | Any connector credential |
| Billing records and invoices | Anything 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.
What runs
Section titled “What runs”| Component | Notes |
|---|---|
| The control plane application | The same monorepo build as the product, from apps/control-plane |
| Its own PostgreSQL | Separate from every tenant instance. Storage is never shared between the vendor plane and a customer |
| A tunnel | One 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.
Two hostnames for one service
Section titled “Two hostnames for one service”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.appis Access-gated. A person signing in has an identity, and Access is what verifies it.telemetry.plugboard.appskips 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.
How an instance authenticates
Section titled “How an instance authenticates”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:
| Control | Effect |
|---|---|
| Replay window, 30 minutes by default | A 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 binding | If set on a licence, reports from any other install are refused outright |
| Multi-deployment detection | A 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.
Authentication for people
Section titled “Authentication for people”The panel accepts two credentials, preferring the first.
- 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.
- 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.
What it is used for
Section titled “What it is used for”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.
The two things that must be backed up
Section titled “The two things that must be backed up”- 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.
- 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.
Configuration
Section titled “Configuration”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.