Security Model
Three-layer admin gating, encrypted credentials, and zero kubeconfig material at rest on controller disk.
Three-Layer Admin Gating
Every kubernetes operation — read, write, or subscription — passes three independent admin checks before reaching the cluster. The redundancy is intentional: a bug or a malicious change at any one layer fails closed at the next.
- GraphQL resolver (
bosca-server) — every@Fieldresolver inKubernetesQueriesController,KubernetesMutationsController, andKubernetesSubscriptionsControllercallsgroups.verifyHasAdminGroup(authentication)as its first statement. No operation runs without an admin principal on the GraphQL request. - Controller HTTP route (
kubernetes-controller) — every@RouteController(authentication = REQUIRED)validates the inbound JWT and re-checks admin membership on its own.bosca-servermints a fresh JWT representing the original caller for every outbound request; the controller never trusts bosca-server's authorization assertion implicitly. - fabric8 caller — the kubeconfig the controller uses to talk to the cluster is itself an admin credential. There's no per-call ACL inside the controller because the kubeconfig has already been admin-class since registration. The earlier two layers are what prevent non-admin callers from reaching this layer at all.
Kubeconfig Credential Storage
Kubeconfigs are encrypted at rest with the platform's security.encryption.key using the same encryption service that protects every other Bosca credential (OAuth tokens, API keys, encryption-at-rest fields). The encrypted blob lives in kubernetes.cluster_credential Postgres; the raw kubeconfig is never:
- Written to the controller's persistent filesystem.
- Passed via environment variables (process listings would leak it).
- Passed via argv to
helmorkubectl(same reason). - Logged. Ever.
When the controller needs to use a kubeconfig — to build a fabric8 client or to feed helm install --kubeconfig <file> — it decrypts the blob in memory and writes it to a private temp file with 0600 permissions for the duration of the operation. The temp file is deleted in a Kotlin finally, regardless of success or exception.
The security.encryption.key must be configured to the same value on bosca-server and kubernetes-controller. The default of changeme is intentionally rejected by production deployments — set SECURITY_ENCRYPTION_KEY to a real high-entropy value before registering any cluster.
Helm Subprocess Isolation
Helm install / upgrade / rollback / uninstall all shell out to the helm binary. Per-call isolation:
- Kubeconfig written to
/tmp/...with0600perms, passed via--kubeconfig <file>, deleted infinally. - Values YAML written to a sibling temp file, passed via
--values <file>. Never passed via argv — process listings would expose override values, which can include database passwords or API tokens. - Helm subprocess has a 300-second timeout matching the controller's streaming-response cap. Hung subprocesses are reaped.
- Each invocation runs in a fresh subprocess; there's no long-lived helm daemon that could be tricked into operating on another cluster's kubeconfig.
Subscription Authentication
WebSocket subscriptions for pod logs, events, workload status, and helm release status authenticate at three points:
- The
connection_initmessage on/graphqlwsincludes the JWT. - The resolver's
verifyHasAdminGroupruns once at subscribe time. - The controller re-mints a JWT and re-checks admin on every upstream HTTP request that feeds the stream, not just the first. Credential revocation between subscribe and the next read closes the stream from the controller side.
Audit Trail
Every mutation flows through bosca-server's GraphQL endpoint, which means every mutation is logged in the standard Bosca request log with the authenticated principal id, the operation name, and the resolver path. The kubernetes-controller logs every inbound HTTP call with similar detail. For destructive operations (delete resource, uninstall release, scale to zero) the studio additionally adds a confirm modal with a typed-input gate; this is UX, not authorization — operators with admin can perform the operation either way.
Future Work
Bosca's RBAC is coarse for kubernetes today: anyone in the admin group can do anything anywhere. A finer-grained "kubernetes-admin" role bound to specific clusters or namespaces is on the roadmap once Bosca's permission system supports per-subsystem capabilities. Pod exec and port-forward are similarly deferred — both require bidirectional byte streams the platform's WebSocket transport can theoretically support but the studio's ergonomics work hasn't been scoped.