Daemon trust model
The TurboPanel daemon (turbopaneld) is a high-privilege service on every managed server. This page describes what it can do, what it sends upstream, and how to revoke access.
Enrollment
- Operator creates a one-shot license in the console.
- Installer runs
curl -fsSL turbopanel.sh | TURBOPANEL_LICENSE=… sh. - Daemon proves possession of a generated Ed25519 key via challenge/response (
turbopanel-daemon-enroll-v1). - Control plane attaches daemon identity to a
serverrow; license is consumed.
Re-enrollment requires the persisted serverId or a fresh license.
Connectivity
| Path | Detail |
|---|---|
| Transport | TLS + WebSocket /ws/daemon/v1 |
| Session | 15-minute daemon JWT (EdDSA), refreshed via auth challenge |
| Trust | Platform CA bundle on self-hosted (GET /api/daemon/v1/instance/ca → /etc/turbopanel/instance-ca.pem) or system roots (public TLS) — no skip-verify mode. Control-plane identity is the JWKS/Ed25519 JWT; the CA is a transport anchor only. |
| Presence | Heartbeats project timeSync, addresses, and online state to Postgres |
Co-located dev may dial a Unix socket when configured; remote nodes always use WSS to the public URL.
Commands the daemon can run
Approved command types include deploy/stop, ping, hostname/reboot, timezone/NTP, TurboFabric reconcile, platform CA trust reconcile (server.tls.trust.reconcile), managed engine lifecycle, and orchestration playbooks invoked through Ansible.
Commands are queued in Postgres, dispatched over the cell, and correlated by commandId. The daemon executes only handlers registered in its command pipeline.
Files and privileges
| Area | Path / privilege |
|---|---|
| State | /var/lib/turbopanel (keys, license, wireguard material mode 0600) |
| Config | /etc/turbopanel/daemon.env, /etc/turbopanel/instance-ca.pem (platform CA bundle) |
| Logs | /var/log/turbopanel/daemon.log |
| Production user | tp:tp with passwordless sudo for Ansible |
| Orchestration | /opt/turbopanel/share/orchestration playbooks and roles |
WireGuard PSK files are deleted after apply; they never appear in Ansible extra-vars. The daemon owns TurboFabric apply directly (no Ansible round-trip).
Secrets
- License token on disk until enrollment completes
- WireGuard private keys under
/var/lib/turbopanel/network/wireguard/(mode0600) - No workload application secrets are stored in daemon state by default — compose injection comes from control plane at deploy time
Updates
Channel manifest from CDN → run.sh reconciles /opt/turbopanel and restarts turbopaneld.service. Install and update downloads use HTTPS from the published CDN origin; the channel manifest lists each artifact URL with a SHA-256 checksum, and the installer verifies the downloaded bytes against that hash before extraction. There is no separate cryptographic signature on manifests or artifacts today — treat TLS to the CDN plus checksum verification as the current trust boundary. Artifact signing may be added in a future release.
Data sent to the control plane
| Data | Purpose |
|---|---|
| Hostname, OS, addresses, time sync | Fleet UI and automation |
| Host metrics samples | Charts and alerting (see server metrics) |
| Command results / logs (on demand) | Operator actions |
| Public daemon key fingerprint | Authentication |
Daemons do not upload arbitrary filesystem contents unless a specific command requests it (e.g. managed logs tail).
Revocation
- Delete or revoke the server in the org console.
- Remove
turbopaneldand state on the host (uninstall). - Rotate org secrets if you suspect key compromise.
Managed vs self-hosted
The daemon trust model is identical — only the control plane URL and TLS trust root differ: hosted edge TLS, or self-hosted Platform CA by default with an uploaded or Let's Encrypt certificate using the system trust store instead. See Control plane TLS modes.
Platform CA rotation (self-hosted)
The instance-wide platform CA lives under /var/lib/turbopanel/tls/ (ca.crt, ca.key, ca-bundle.pem). It is not the per-organization TLS library (/api/client/v1/tls). Public-URL apply regenerates only the leaf; rotating the root requires an explicit TURBOPANEL_TLS_CA_ROTATE=1 on the generator.
Rotation procedure:
- Rotate — mint the new root while keeping the outgoing CA in
ca-bundle.pem(current first). The control plane still presents a leaf signed by a CA that existing daemons already trust. - Overlap — fan
server.tls.trust.reconcileto every connected server over the existing WSS session. Each daemon atomically writes/etc/turbopanel/instance-ca.pemand rebuilds its HTTP client from the bundle. - Confirm all daemons acked — wait until every connected server reports
{ applied, fingerprint }matching the new first-cert fingerprint. - Retire — drop the old CA from the bundle only after those acks. Do not retire first: a daemon still on the stale pin cannot fetch a replacement CA over the killed WSS session.
A daemon left on a stale anchor parks with a greppable tls-trust log (dialed host, CA path, local fingerprint). Recovery: re-run the installer with --instance-ca or --insecure-tls so run.sh can fetch the new bundle (pinned --cacert first; one guarded unpinned retry when the old pin is dead). See troubleshooting.
Organization CA rotation (managed-database / ProxySQL / replication leaves — not daemon → control-plane trust) follows the same order via the org TLS library. This is not the Platform CA above — see Organization CA for the full model. Mint a new active generation while the outgoing CA stays in the overlap trust bundle (POST /api/client/v1/tls/ca/rotate), distribute by fanning managed.apply and managed.ingress.reconcile, confirm those commands succeeded (GET /api/client/v1/tls/ca/rotation), then retire (POST /api/client/v1/tls/ca/retire) so the old generation leaves the bundle. Do not retire first.
Future: tenant hosting leaves (Caddy-fronted web services) are operator-pinned library certificates, Caddy tls internal, or a Let's Encrypt row Caddy issues on the serving host (tlsMode: acme). They are never issued by the Organization CA. See Hostnames and TLS.
Related
Last updated on