Security model
What NebulaCtrl secures, what you secure when you self-host it, and how the control plane, the agents, your identity provider and your Git providers trust each other.
NebulaCtrl is a control plane that decides what runs in your clusters and stores the secrets those workloads need. Its security model has two halves: what the software enforces, and what you must secure around it. This page draws the line between them.
How the pieces trust each other
flowchart LR
subgraph You["You operate"]
Proxy["Reverse proxy<br/>TLS"]
CP["Control plane<br/>container"]
DB[("PostgreSQL")]
ENV[".env<br/>master key"]
IdP["OIDC provider"]
end
Browser["Browser<br/>session cookie"] --> Proxy
CI["CI<br/>API token"] --> Proxy
Git["Git providers<br/>signed webhooks"] --> Proxy
Proxy --> CP
CP --> DB
ENV -.-> CP
CP <--> IdP
subgraph Cluster["Each cluster"]
Agent["Agent<br/>dials out"]
end
Agent -->|"HTTPS, bearer token"| Proxy
- People sign in at your OIDC provider. The control plane never sees a password. A browser holds a session cookie; a script holds an API token.
- Git providers push events to the control plane as webhooks. Each delivery is signed or carries a secret, and unsigned deliveries are refused.
- Agents dial the control plane over HTTPS with a bearer token. The control plane never connects to a cluster, so clusters need no inbound rule for it.
- Nodes talk to each other only inside a WireGuard mesh, including across clusters.
The control plane is the root of trust for your clusters. Whoever controls it, or holds an admin API token, can choose what runs in the environment namespaces of every cluster bound to it.
What NebulaCtrl provides
| Area | What it enforces |
|---|---|
| Authentication | One external OIDC provider. Authorization code flow with PKCE, state and nonce. An unverified or missing email is refused. Identity is issuer plus sub. Sessions are random 32-byte values, stored hashed, HttpOnly and SameSite=Lax, with an absolute 30-day lifetime. |
| Second factor | An organization setting that refuses sessions without a second factor, judged from the provider's amr claim. |
| Authorization | Four roles, checked on every request against the organization's current membership. Organizations are isolated from each other. API tokens can be limited to projects. Production changes need an admin's approval. |
| Secrets at rest | Envelope encryption with AES-256-GCM for every stored secret. API tokens, session tokens and the SCIM token are stored only as hashes. See Secrets and sealing. |
| Audit | An append-only record of state-changing actions, with the actor and what changed. See the audit log. |
| Web surface | A CSRF header on cookie-authenticated writes, no CORS, a strict Content Security Policy, X-Frame-Options: DENY, no-store on API responses, and request bodies capped at 1 MiB. |
| Abuse limits | Per-address rate limits on sign-in, cluster enrollment and mesh registration. Open event streams are capped per credential and re-check their credential every 30 seconds. |
| Outbound calls | For hosts you name in the console, such as Git providers, registries, object stores and webhooks, the control plane resolves the name first and refuses to connect to loopback, link-local, private and reserved addresses. This blocks requests aimed at cloud metadata endpoints. The one exception is a private SSH target that you explicitly authorize for a cluster install. |
| Agent link | Single-use enrollment tokens that expire in an hour, then a permanent bearer token stored hashed. Removing a cluster stops its tokens working. |
| Cluster scope | The agent reads cluster inventory and gets full access only inside the namespaces of environments bound to its cluster. A separate access broker with its own credential grants that access. Admission policies bound what the broker and the build jobs can do. |
| Network | A WireGuard mesh for all node-to-node traffic. Cluster nodes get a default-deny ufw policy, fail2ban, unattended upgrades and key-only SSH login when a key exists. K3s runs with secrets encryption on. |
| Container | The control plane runs as an unprivileged user, with a read-only filesystem, no Linux capabilities and no-new-privileges. The bundled database is not published on any port. |
What you secure
| Area | Your responsibility |
|---|---|
| The host | Patch the operating system and Docker. Limit who can run docker or sudo: that grants access to .env and the database. The updater service runs as the user who installed NebulaCtrl. |
| Network edge | Terminate TLS with a valid certificate. Keep the control plane reachable only through your proxy, and tell it which proxies to trust. |
| The master key | Protect .env and back it up separately from database dumps. Rotate it on your schedule. Losing it loses every sealed secret. |
| The database | Restrict who can connect, use TLS to an external server, encrypt storage at rest, and back it up. Only listed secrets are sealed; other data, such as member emails, service configuration and variable names, is stored as plain text. |
| Identity | Run the OIDC provider securely. Enforce multi-factor authentication there, remove people when they leave, and report a second factor in the amr claim if you require one in NebulaCtrl. Deactivating someone at the provider does not end their NebulaCtrl session. Remove them from the organization, or deprovision them over SCIM; that takes effect on their next request. |
| Access | Choose who is an owner or admin. Review members and API tokens. Give each token the lowest role, a project scope and an expiry. |
| Clusters | Secure the nodes: patching, physical and cloud access, and who holds SSH keys. Keep agents current. Your workloads, their images and their dependencies are outside what NebulaCtrl checks. |
| Integrations | Keep Cloudflare, Tailscale, registry, Git and object store credentials narrowly scoped at their providers. |
| Recovery | Test restores. Keep audit exports if you need them past the life of the organization. |
API tokens are credentials for the control plane
An unrestricted API token carries its role across the whole API, including deploying, creating clusters and servers, and changing members, registries and Git connections. A few operations, such as creating tokens or organizations, are refused to every token. A stolen admin token needs no sign-in and no second factor. See Create an API token.
Boundaries
- NebulaCtrl has no local accounts and no second factor of its own.
- Sealing protects the database and its backups. It does not protect a running control plane from someone who controls the host, because the process holds the key.
- Secret values reach your clusters as Kubernetes Secrets in the environment's namespace. Anyone who can read those Secrets in the cluster can read the values.
- The audit record covers actions taken through NebulaCtrl. Direct changes to the database or to a cluster do not appear in it.
Related
Uninstall the control plane
Stop and remove the NebulaCtrl control plane, its updater service and its data from the host, and decide what happens to the clusters it managed.
Harden a production install
A checklist for locking down a self-hosted NebulaCtrl control plane, its identity and access settings, its integrations and the clusters it manages.