Kubermatic SecureGuard is engineered with security and multi-layered protection as its core design principles. This document describes the component architecture, authentication flow, multi-cluster routing, and the zero-knowledge security model.
SecureGuard is not a single monolithic application. It is a composition of specialized open-source services coordinated via Kubernetes.
agent/) that manages ESO lifecycle across clusters. It watches ESODeployment CRs on the management cluster and deploys, upgrades, or deletes ESO on remote clusters via multi-cluster kubeconfig contexts. It also maintains SGAgent CRs with heartbeat health status for each connected cluster.Secret resources.federation/, disabled by default) that serves secret values to remote clusters over mTLS without exposing the backend stores. It is a separate trust boundary from the zero-knowledge proxy — it is the only SecureGuard component that handles real secret values, runs under its own least-privilege ServiceAccount, and must never be colocated with the proxy. See Federation.graph TB
subgraph "User Browser"
FE["React Frontend (SPA)"]
end
subgraph "Management Cluster"
PROXY["Backend Proxy (Go)"]
DEX["Dex (OIDC Provider)"]
AGENT["SG Agent Controller"]
ESO["External Secrets Operator"]
KUBE["kube-apiserver"]
OPENBAO["OpenBao"]
end
subgraph "Remote Clusters"
RKUBE["kube-apiserver"]
RESO["ESO (managed)"]
end
FE -->|"HTTP (session cookie)"| PROXY
PROXY -->|"Allowed K8s API paths"| KUBE
PROXY -->|"OIDC auth"| DEX
AGENT -->|"Watches ESODeployment CRs"| KUBE
AGENT -->|"Deploys/manages ESO"| RKUBE
AGENT -->|"Updates SGAgent heartbeat"| KUBE
ESO -->|"Syncs secrets"| OPENBAO
RESO -->|"Syncs secrets"| OPENBAO
PROXY -.->|"Secret values REDACTED"| FE
The primary security directive of SecureGuard is ensuring that secret values never reach the browser. This is enforced architecturally at the proxy layer — not by UI-level masking.
v1/Secret and v1/SecretList API responses via ModifyResponse. It replaces every value in .data and .stringData with "REDACTED", preserving only key names. This is implemented in redactSecretResponse() in proxy/internal/proxy/proxy.go.•••••••• unconditionally — there is nothing to reveal.localStorage, sessionStorage, browser history, URL parameters, React state, or network traces — because they are stripped before the response leaves the proxy.proxy/internal/proxy/routes.go. Any unlisted path is rejected with 403 Forbidden.The proxy only forwards requests matching explicitly listed Kubernetes API paths:
external-secrets.io/v1), PushSecret (external-secrets.io/v1alpha1), ReloaderConfig, ESODeployment, ESOVersion (read-only version catalog), SGAgent, and the read-only Federation CRs (FederationServer, FederationAuthorization)All other paths — including direct access to pods, nodes, RBAC resources, or arbitrary CRDs — are rejected. This limits the blast radius independently of the impersonated user’s RBAC: even a cluster-admin user can only reach the allowlisted paths through the dashboard.
Authentication is mandatory — there is no disabled mode. The proxy requires OIDC_ISSUER_URL and refuses to start without it. SecureGuard uses OIDC Authorization Code flow with PKCE (Proof Key for Code Exchange) via Dex. Critically, OIDC tokens never reach the frontend — the proxy handles the entire token exchange and issues HTTP-only session cookies.
sequenceDiagram
participant User as Browser
participant FE as React Frontend
participant Proxy as Backend Proxy
participant Dex as Dex (OIDC)
participant IDP as Identity Provider
User->>FE: Access SecureGuard UI
FE->>Proxy: GET /api/auth/login
Proxy->>Proxy: Generate PKCE code verifier + challenge
Proxy->>Dex: Authorization request (code challenge)
Dex->>IDP: Authenticate user
IDP->>Dex: Authentication result
Dex->>Proxy: Redirect to /api/auth/callback (authorization code)
Note over Proxy: Callback goes to the proxy, NOT the frontend
Proxy->>Dex: Exchange code + verifier for tokens
Dex->>Proxy: ID token + access token
Proxy->>Proxy: Verify ID token, extract user info
Proxy->>Proxy: Store user info in session
Proxy->>User: Set HTTP-only session cookie, redirect to UI
Note over FE,Proxy: All subsequent requests use the session cookie
FE->>Proxy: GET /api/kube/... (session cookie)
Proxy->>Proxy: Validate session
Proxy->>FE: Proxied K8s API response (secrets redacted)
/api/auth/login on the backend proxy./api/auth/callback on the backend proxy (not the frontend). This is critical — the authorization code is never exposed to the browser’s JavaScript context.Authentication answers who you are; authorization is delegated entirely to Kubernetes RBAC via impersonation. On every forwarded Kubernetes API request the proxy authenticates as its own service account and adds impersonation headers derived from the user’s OIDC token:
Impersonate-User ← the email claimImpersonate-Group ← each entry in the groups claimThe Kubernetes API server then evaluates the request against the RBAC bound to that user/groups. As a result, what a user can see and do in the dashboard is exactly what their cluster RBAC allows — the proxy holds no standing access to ESO resources on the user’s behalf. Any client-supplied Impersonate-* headers are stripped before the proxy sets its own, preventing identity spoofing.
A freshly authenticated user with no RBAC bindings can log in but receives 403 Forbidden for every resource until an operator grants access. See Security Hardening → RBAC and Advanced Configuration → User Authorization for binding examples.
The proxy and the SG Agent run under separate service accounts so each holds only what it needs:
secureguard-proxy — impersonate on users/groups, create on SGAgents, and management of per-cluster kubeconfig Secrets in its own namespace. It has no standing read/write on ESO resources (that flows through impersonation).secureguard-agent — the controller/deployer permissions: SGAgent and ESODeployment reconcile (plus /status), and the resources the deployer creates when installing ESO into target namespaces (Deployments, ServiceAccounts, Namespaces, ClusterRoles, RoleBindings), events, and leader-election Leases.Both are defined in k8s/rbac.yaml and charts/secureguard/templates/rbac.yaml.
SecureGuard supports managing ESO resources across multiple Kubernetes clusters from a single dashboard.
KUBECONFIG file and exposes them via GET /api/clusters with health status.fsnotify watches the kubeconfig file. When it changes, the proxy automatically reloads cluster configurations without requiring a restart./api/clusters/{id}/kube/* or the default cluster via /api/kube/*.src/store/cluster.ts) holds the selected cluster. The TopBar provides a cluster dropdown. When a specific cluster is selected, all API hooks route requests through the cluster-specific path. “All Clusters” is the default, aggregating data across clusters.The SG Agent Controller (agent/) is a Go binary built with controller-runtime that automates ESO lifecycle management across a fleet of clusters.
ESODeployment CRs on the management cluster. When created, it deploys ESO to the target remote cluster. It handles upgrades (version changes) and deletions (cleanup) automatically.SGAgent CR with heartbeat timestamps and health status. The dashboard uses these CRs to display cluster connectivity in the ClusterManagement page.The dashboard is intentionally read-mostly. What it can mutate is limited by the proxy’s route allowlist, and the editing experience reflects that:
src/pages/ESODeployments/). Form state is
validated against the Zod schema and serialized to the CR on submit.kubectl or GitOps.PATCH that adds a force-sync annotation) and deleting ExternalSecrets,
PushSecrets, and ReloaderConfigs.This keeps version-controlled resources as the source of truth and keeps the browser’s write surface small. See the route allowlist for the exact permitted operations.
A global namespace selector in the TopBar drives all views across the dashboard. The selected namespace is:
src/store/namespace.ts)?namespace=...)All API hooks consume the selected namespace and scope their queries accordingly. Changing the namespace updates all visible resource lists, charts, and the relationship graph simultaneously.
The Visualization page renders an interactive graph showing relationships between:
The graph is built with @xyflow/react for rendering and @dagrejs/dagre for automatic hierarchical layout. Nodes are color-coded by resource type and display sync status. Clicking a node navigates to the resource’s detail page.
| Control | Implementation |
|---|---|
| TLS | Enforced in production; plaintext HTTP rejected |
| Session Cookies | HTTP-only, SameSite=Lax, Secure flag, 8-hour expiry |
| CSRF Protection | State parameter in OIDC flow; SameSite cookie attribute |
| Content Security Policy | CSP headers to prevent XSS and data exfiltration |
| RBAC | Per-user: proxy impersonates the logged-in user, so K8s RBAC governs access. Proxy/agent run as separate least-privilege service accounts |
| Container Security | Distroless base image, non-root user, pinned image versions |