From 00328cc1222e6ef663d2be3d622df8e47eacfd23 Mon Sep 17 00:00:00 2001 From: Sebastian Scheele Date: Sat, 13 Jun 2026 18:07:55 +0200 Subject: [PATCH 1/5] Add SecureGuard docs (unlisted, in development) Add the main documentation tree for the new SecureGuard product, which is still under development. The product is intentionally not published publicly: - Omitted from data/products.yaml (kept as a commented-out reservation block, following the existing KDP convention), so it does not appear in the product dropdown or on the homepage. - Pages carry sitemapexclude, searchexclude and private front matter, so they are excluded from sitemap.xml and the site search index. The pages remain reachable via their direct URL only. --- content/secureguard/_index.en.md | 10 ++++++++ content/secureguard/main/_index.en.md | 34 +++++++++++++++++++++++++++ data/products.yaml | 12 ++++++++++ 3 files changed, 56 insertions(+) create mode 100644 content/secureguard/_index.en.md create mode 100644 content/secureguard/main/_index.en.md diff --git a/content/secureguard/_index.en.md b/content/secureguard/_index.en.md new file mode 100644 index 000000000..dcf551597 --- /dev/null +++ b/content/secureguard/_index.en.md @@ -0,0 +1,10 @@ ++++ +title = "SecureGuard Docs" +sitemapexclude = true +searchexclude = true +private = true ++++ + +SecureGuard is a Kubermatic product currently under development. This documentation is a work in progress and is not yet publicly announced. + +For the latest documentation, please visit the [SecureGuard documentation]({{< ref "./main/" >}}). diff --git a/content/secureguard/main/_index.en.md b/content/secureguard/main/_index.en.md new file mode 100644 index 000000000..7381cf363 --- /dev/null +++ b/content/secureguard/main/_index.en.md @@ -0,0 +1,34 @@ ++++ +title = "Kubermatic SecureGuard" +date = 2026-06-13T09:00:00+02:00 +weight = 8 +description = "Learn how to use Kubermatic SecureGuard to secure your Kubernetes workloads across multi-cloud and on-premise environments." +sitemapexclude = true +searchexclude = true +private = true ++++ + +{{% notice warning %}} +SecureGuard is currently under active development. This documentation is a preview and is not yet publicly announced. Content is subject to change. +{{% /notice %}} + +## What is SecureGuard? + +SecureGuard is a project by Kubermatic, providing security and compliance capabilities for Kubernetes workloads across multi-cloud and on-premise environments. + +## Motivation and Background + +This section will describe the motivation and background for SecureGuard. + +## Table of Content + +{{% children depth=5 %}} +{{% /children %}} + +## Further Information + +Visit [kubermatic.com](https://www.kubermatic.com/) for further information. + +{{% notice tip %}} +For latest updates follow us on Twitter [@Kubermatic](https://twitter.com/Kubermatic) +{{% /notice %}} diff --git a/data/products.yaml b/data/products.yaml index 3721b5ee0..91f0d7c70 100644 --- a/data/products.yaml +++ b/data/products.yaml @@ -166,3 +166,15 @@ conformance-ee: # description: # weight: 6 # shareImage: img/share-developer-platform.png +# SecureGuard is still in development - do not yet reveal to the public. +# The content tree exists under content/secureguard/ and is reachable via direct +# link only (excluded from sitemap and search). Uncomment this block to publish. +# secureguard: +# name: secureguard +# title: SecureGuard +# textName: SecureGuard +# description: +# weight: 8 +# versions: +# - release: main +# name: main From 2cf384195fd6b312cec2bd9da5b257071cd238a9 Mon Sep 17 00:00:00 2001 From: Sebastian Scheele Date: Thu, 25 Jun 2026 17:53:21 +0200 Subject: [PATCH 2/5] docs(secureguard): add user-facing guide pages Migrate the user-facing SecureGuard documentation from the product repo into the Hugo site under content/secureguard/main/. Adds getting-started, architecture, installation, ESO/OpenBao basics, user guide, advanced configuration, federation, security hardening, upgrade guides, troubleshooting, and glossary pages, and enriches the section overview. Pages remain hidden (private/sitemapexclude/searchexclude) until the public announcement. Developer-only docs (API reference, dev notes, example manifests) stay in the product repo and are linked via GitHub URLs. documentation: NONE Signed-off-by: Sebastian Scheele --- content/secureguard/main/_index.en.md | 80 +++- .../main/advanced-configuration/_index.en.md | 355 +++++++++++++++ .../main/architecture/_index.en.md | 209 +++++++++ .../secureguard/main/eso-basics/_index.en.md | 86 ++++ .../secureguard/main/federation/_index.en.md | 430 ++++++++++++++++++ .../main/getting-started/_index.en.md | 158 +++++++ .../secureguard/main/glossary/_index.en.md | 187 ++++++++ .../main/installation/_index.en.md | 208 +++++++++ .../main/openbao-basics/_index.en.md | 60 +++ .../main/security-hardening/_index.en.md | 258 +++++++++++ .../main/troubleshooting/_index.en.md | 121 +++++ .../main/upgrade-guides/_index.en.md | 83 ++++ .../secureguard/main/user-guide/_index.en.md | 241 ++++++++++ 13 files changed, 2465 insertions(+), 11 deletions(-) create mode 100644 content/secureguard/main/advanced-configuration/_index.en.md create mode 100644 content/secureguard/main/architecture/_index.en.md create mode 100644 content/secureguard/main/eso-basics/_index.en.md create mode 100644 content/secureguard/main/federation/_index.en.md create mode 100644 content/secureguard/main/getting-started/_index.en.md create mode 100644 content/secureguard/main/glossary/_index.en.md create mode 100644 content/secureguard/main/installation/_index.en.md create mode 100644 content/secureguard/main/openbao-basics/_index.en.md create mode 100644 content/secureguard/main/security-hardening/_index.en.md create mode 100644 content/secureguard/main/troubleshooting/_index.en.md create mode 100644 content/secureguard/main/upgrade-guides/_index.en.md create mode 100644 content/secureguard/main/user-guide/_index.en.md diff --git a/content/secureguard/main/_index.en.md b/content/secureguard/main/_index.en.md index 7381cf363..816f296d3 100644 --- a/content/secureguard/main/_index.en.md +++ b/content/secureguard/main/_index.en.md @@ -2,7 +2,7 @@ title = "Kubermatic SecureGuard" date = 2026-06-13T09:00:00+02:00 weight = 8 -description = "Learn how to use Kubermatic SecureGuard to secure your Kubernetes workloads across multi-cloud and on-premise environments." +description = "Protect and manage secrets with open-source transparency — a Kubernetes-native secrets management platform built on OpenBao and the External Secrets Operator." sitemapexclude = true searchexclude = true private = true @@ -12,23 +12,81 @@ private = true SecureGuard is currently under active development. This documentation is a preview and is not yet publicly announced. Content is subject to change. {{% /notice %}} -## What is SecureGuard? +**Protect and manage secrets with open-source transparency.** -SecureGuard is a project by Kubermatic, providing security and compliance capabilities for Kubernetes workloads across multi-cloud and on-premise environments. +Kubermatic SecureGuard is a self-hosted, open-source secrets management platform designed for modern, Kubernetes-native environments. It acts as a secure transport layer for secrets, bridging the gap between high-security cryptographic storage and dynamic application needs. -## Motivation and Background +By merging the cryptographic hardening of **OpenBao** with the native orchestration of the **External Secrets Operator (ESO)**, SecureGuard provides a unified, production-grade secrets management solution. It eliminates fragmented secrets management, reduces vendor lock-in, and gives developers native access to credentials directly within Kubernetes. -This section will describe the motivation and background for SecureGuard. +## In Plain English -## Table of Content +Every app needs **secrets** — passwords, API keys, database credentials, TLS certificates. The hard questions are: *where do you store them safely?* and *how do they get to the apps that need them without leaking?* -{{% children depth=5 %}} -{{% /children %}} +SecureGuard answers both by combining three open-source tools and putting a friendly, **read-only-by-default dashboard** on top: + +- **OpenBao** is the **vault** — the one safe, encrypted place where secrets actually live. +- **ESO** (External Secrets Operator) is the **delivery service** — it copies secrets out of the vault into the standard Kubernetes `Secret` objects your apps already use, and keeps them up to date. +- **SecureGuard's dashboard** is the **control room** — it lets you see and manage all of this without memorizing `kubectl` commands, and **without ever exposing the secret values themselves** (the dashboard shows `••••••••`, never the real value). + +{{% notice note %}} +**OpenBao is optional.** It's our **opinionated default** so teams without a vault get a complete, batteries-included stack out of the box. But SecureGuard is **provider-agnostic**: ESO supports many backends (AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, HashiCorp Vault, and [more](https://external-secrets.io/latest/provider/aws-secrets-manager/)). If you already have a vault, point your `SecretStore`s at it and disable the bundled OpenBao (`--set openbao.enabled=false`). Everything else works the same. +{{% /notice %}} + +> **Analogy:** Think of OpenBao as a bank vault, ESO as the armored truck that delivers cash to ATMs (your apps), and SecureGuard as the security desk with the camera monitors — you can watch and direct everything, but you can't reach into the vault and pull the cash out through the monitor. + +## How It Works (The Big Picture) + +```text + 1. STORE 2. DELIVER 3. USE + ┌──────────┐ ESO pulls ┌──────────────────┐ app reads ┌──────────┐ + │ OpenBao │ ───────────▶ │ Kubernetes Secret│ ──────────▶ │ Your App │ + │ (vault) │ & syncs │ (auto-created) │ as env/file│ │ + └──────────┘ └──────────────────┘ └──────────┘ + ▲ ▲ + │ you configure & watch all of this from + └────────── the SecureGuard dashboard ──────────┘ + (values stay masked: ••••••••) +``` + +1. **Store** a secret once in OpenBao (the vault). +2. ESO **delivers** it into a normal Kubernetes `Secret` and keeps it in sync — if you change it in the vault, ESO updates the copy automatically. +3. Your app **uses** that `Secret` like any other (no special SDK or code changes). -## Further Information +You manage steps 1–2 from the SecureGuard dashboard. New to the terms above? See the **[Glossary]({{< ref "/secureguard/main/glossary/" >}})**. -Visit [kubermatic.com](https://www.kubermatic.com/) for further information. +## Core Features +- **OpenBao Core:** Secure backend with encryption in transit and at rest, fine-grained access controls, and full audit logs. +- **ESO Integration:** Dispatches secrets directly into Kubernetes clusters or between multiple external secret stores. +- **Native Kubernetes Secrets Support:** Developers use standard `Secret` objects—no app rewrites or SDKs required. +- **Centralized Management:** Provides a single source of truth across all environments and clusters. +- **Multi-Cluster Support:** Manage ESO deployments across clusters via the SG Agent Controller and ESODeployment CRDs. +- **Federation (optional):** Serve secrets to remote clusters over mTLS without exposing the backend stores — via a standalone broker and the `fedclient` consumer. +- **ReloaderConfig:** Event-driven workload reloading when synced secrets change. +- **Developer First:** Built-in React dashboard for visualizing and managing the secret sync lifecycle. +- **Zero-Knowledge Security:** Secret values are redacted at the proxy layer — they never reach the browser. + +## Target Audience +- **Security Architects**: Establish centralized governance, enforce least-privilege access, and maintain comprehensive audit trails. +- **DevOps & Platform Teams**: Automate the secrets lifecycle, eliminating manual ticketing and enabling "breakage-free" rotation. +- **Developers**: Get immediate, native access to credentials (including AI tokens and API keys) directly within Kubernetes. + +## New Here? Where to Start + +Not sure which doc to read first? Pick the path that matches you: + +| If you are… | Start with… | Then read… | +|---|---|---| +| **Brand new to secrets in Kubernetes** | [OpenBao Basics]({{< ref "/secureguard/main/openbao-basics/" >}}) → [ESO Basics]({{< ref "/secureguard/main/eso-basics/" >}}) | [Glossary]({{< ref "/secureguard/main/glossary/" >}}), [Getting Started]({{< ref "/secureguard/main/getting-started/" >}}) | +| **Just want to try it locally** | [Getting Started]({{< ref "/secureguard/main/getting-started/" >}}) | [User Guide]({{< ref "/secureguard/main/user-guide/" >}}) | +| **A developer using the dashboard day-to-day** | [User Guide]({{< ref "/secureguard/main/user-guide/" >}}) | [Glossary]({{< ref "/secureguard/main/glossary/" >}}) | +| **An operator deploying to production** | [Installation]({{< ref "/secureguard/main/installation/" >}}) | [Security Hardening]({{< ref "/secureguard/main/security-hardening/" >}}), [Advanced Configuration]({{< ref "/secureguard/main/advanced-configuration/" >}}) | +| **Integrating or debugging the API** | [Architecture]({{< ref "/secureguard/main/architecture/" >}}) | [API Reference](https://github.com/kubermatic/secureguard/blob/main/docs/api-reference.md) | {{% notice tip %}} -For latest updates follow us on Twitter [@Kubermatic](https://twitter.com/Kubermatic) +Keep the [Glossary]({{< ref "/secureguard/main/glossary/" >}}) open in a tab. Whenever a term like *ESO*, *SecretStore*, *CRD*, *OIDC*, or *unsealing* is unclear, it's defined there in one line. {{% /notice %}} + +## Documentation + +{{% children depth=5 %}} +{{% /children %}} diff --git a/content/secureguard/main/advanced-configuration/_index.en.md b/content/secureguard/main/advanced-configuration/_index.en.md new file mode 100644 index 000000000..0042c0fb1 --- /dev/null +++ b/content/secureguard/main/advanced-configuration/_index.en.md @@ -0,0 +1,355 @@ ++++ +title = "Advanced Configuration" +date = 2026-06-13T09:00:00+02:00 +weight = 7 +description = "Enterprise configuration for SecureGuard — custom OIDC identity providers, RBAC via impersonation, multi-cluster deployments, the ESO version catalog, and short-lived remote tokens." +sitemapexclude = true +searchexclude = true +private = true ++++ + +For enterprise environments, SecureGuard offers extensive configuration options targeting High Availability (HA), advanced authentication, and multi-cluster setups. + +## Custom Identity Providers (OIDC via Dex) + +By default, the `dex` component included in the SecureGuard Helm chart provisions a static mock user for local testing. In a production environment, you must configure Dex to federate authentication to your organization's primary Identity Provider (IDP). + +Dex supports multiple connectors, including: +- GitHub +- Google +- LDAP / Active Directory +- SAML 2.0 +- Generic OIDC (Okta, Auth0, Keycloak) + +### Example: Configuring a GitHub Connector + +To enable GitHub login for developers, update the `dex.config` section of your `values-production.yaml`. + +```yaml +dex: + config: + issuer: https://dex.secureguard.yourcompany.com + connectors: + - type: github + id: github + name: GitHub + config: + clientID: $GITHUB_CLIENT_ID + clientSecret: $GITHUB_CLIENT_SECRET + redirectURI: https://dex.secureguard.yourcompany.com/callback + orgs: + - name: your-github-org +``` +*Note: You must create an OAuth application in GitHub to obtain the Client ID and Secret, and ensure the `redirectURI` matches your Dex ingress.* + +## User Authorization (RBAC via Impersonation) + +Authentication is **mandatory** in SecureGuard, and authorization is delegated entirely to Kubernetes RBAC. The backend proxy authenticates to the Kubernetes API as its own service account and then **impersonates the logged-in user** on every request, using two headers derived from the user's OIDC token: + +- `Impersonate-User` ← the `email` claim +- `Impersonate-Group` ← each entry in the `groups` claim + +This means **what a user can see and do in the dashboard is exactly what their Kubernetes RBAC allows** — no more, no less. The proxy's own service account is *not* a backdoor: it only holds the `impersonate` verb plus the narrow permissions its controller/agent paths need. Any `Impersonate-*` headers supplied by the browser are stripped before the proxy sets its own, so a user cannot escalate by spoofing an identity. + +{{% notice warning %}} +**Consequence:** a freshly authenticated user who has **no** RBAC bindings will be able to log in but will receive `403 Forbidden` from the API server for every resource. You must grant access explicitly, as shown below. +{{% /notice %}} + +### Prerequisites + +1. **Dex must emit a `groups` claim.** Group-based RBAC only works if your connector is configured to return groups (e.g. GitHub `orgs`/`teams`, an LDAP `groupSearch`, or an OIDC `groups` scope). Without it, bind roles to individual user emails instead. +2. **The proxy service account needs the `impersonate` verb** on `users` and `groups`. The bundled Helm chart and [`k8s/rbac.yaml`](https://github.com/kubermatic/secureguard/blob/main/k8s/rbac.yaml) already include this rule: + ```yaml + - apiGroups: [""] + resources: ["users", "groups"] + verbs: ["impersonate"] + ``` + +### Example: read-only access for a team (group-based) + +Grant everyone in the `platform-engineers` OIDC group cluster-wide read access to ESO resources: + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: secureguard-viewer +rules: + - apiGroups: ["external-secrets.io"] + resources: ["externalsecrets", "secretstores", "clustersecretstores", "pushsecrets"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["namespaces", "events", "secrets"] + verbs: ["get", "list"] # secret values are still redacted by the proxy +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: secureguard-viewer-platform-engineers +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: secureguard-viewer +subjects: + # Group name must match the value Dex puts in the `groups` claim. + - apiGroup: rbac.authorization.k8s.io + kind: Group + name: platform-engineers +``` + +### Example: namespaced read/write for a single user (email-based) + +Grant `alice@yourcompany.com` full control of ESO resources, but only in the `payments` namespace: + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: secureguard-editor + namespace: payments +rules: + - apiGroups: ["external-secrets.io"] + resources: ["externalsecrets", "secretstores", "pushsecrets"] + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] + - apiGroups: [""] + resources: ["events", "secrets"] + verbs: ["get", "list"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: secureguard-editor-alice + namespace: payments +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: secureguard-editor +subjects: + # User name must match the value Dex puts in the `email` claim. + - apiGroup: rbac.authorization.k8s.io + kind: User + name: alice@yourcompany.com +``` + +### Verifying a user's effective access + +Use `kubectl auth can-i` with `--as` / `--as-group` to confirm a binding behaves as expected before the user logs in — this mirrors exactly what the proxy does: + +```bash +# As the user (email claim) +kubectl auth can-i list externalsecrets.external-secrets.io \ + --namespace payments --as alice@yourcompany.com + +# As a group member (groups claim) +kubectl auth can-i list secretstores.external-secrets.io \ + --as-group platform-engineers +``` + +If these return `no`, the user will see `403` errors in the dashboard until the appropriate Role/ClusterRole binding is created. + +## Multi-Cluster Deployments + +A central principle of SecureGuard is centralized governance. You deploy the core SecureGuard stack (OpenBao, Dex, Dashboard, Proxy) in a central "Management" cluster, and use the **SG Agent Controller** to manage ESO lifecycle on "Target" clusters. + +### Architecture Overview + +1. **Management Cluster**: Runs OpenBao, SecureGuard Dashboard, Backend Proxy, SG Agent Controller, and Dex. +2. **Target Clusters**: Run the External Secrets Operator (ESO), deployed and managed by the SG Agent Controller via `ESODeployment` CRDs. + +### Automated Multi-Cluster Setup (ESODeployment) + +The SG Agent Controller automates ESO deployment to target clusters. Create an `ESODeployment` resource on the management cluster: + +```yaml +apiVersion: deploy.secureguard.io/v1alpha1 +kind: ESODeployment +metadata: + name: eso-prod-cluster + namespace: secureguard-system +spec: + targetCluster: prod-us-east + esoVersion: v2.6.0 + scope: cluster + ha: + enabled: true + replicas: 2 + rbac: + createClusterRoles: true + createRoleBindings: true +``` + +The controller will: +1. Connect to the target cluster using the per-cluster kubeconfig Secret +2. Deploy ESO with the specified version and configuration +3. Monitor the deployment status and report back via the `ESODeployment` status field + +{{% notice note %}} +**Only ESO 2.x is supported.** `esoVersion` must be `v2.0.0` or newer; the end-of-life 0.x line is no longer offered. The set of versions the dashboard presents is driven by the **ESO Version Catalog** (see below), not a hardcoded list. +{{% /notice %}} + +### ESO Version Catalog (ESOVersion CRD) + +The ESO versions offered in the dashboard's ESODeployment create/edit form are +**not hardcoded** — they are sourced from cluster-scoped `ESOVersion` resources +(`deploy.secureguard.io/v1alpha1`). This lets operators curate which ESO releases +are available per environment without rebuilding the dashboard: add a release by +applying a CR, retire one by setting `deprecated: true`. + +```yaml +apiVersion: deploy.secureguard.io/v1alpha1 +kind: ESOVersion +metadata: + name: v2-6-0 # cluster-scoped; one CR per release +spec: + version: v2.6.0 # semver (v2.0.0 or newer); shown in the picker + latest: true # marks the recommended/default selection + deprecated: false # when true, hidden from new deployments + releaseDate: "2026-05-04" + minKubeVersion: "1.23" + notes: Latest stable release. +``` + +Behaviour in the dashboard: + +- The version picker lists every non-`deprecated` `ESOVersion`, sorted newest-first. +- The CR flagged `latest: true` is labelled `(latest)` and used as the default; + if none is flagged, the newest version wins. +- The proxy exposes this catalog **read-only** (`GET` on `esoversions`); writes + are rejected by the route allowlist. Curate the catalog with `kubectl apply` + or via the SG Agent, which holds manage permissions on `esoversions`. + +A starter catalog (`v2.0.0`–`v2.6.0`) ships in +[`k8s/samples/eso-deployments/esoversions.yaml`](https://github.com/kubermatic/secureguard/blob/main/k8s/samples/eso-deployments/esoversions.yaml), and the CRD lives in +[`k8s/esoversion-crd.yaml`](https://github.com/kubermatic/secureguard/blob/main/k8s/esoversion-crd.yaml). If the catalog is empty or unreachable, the form falls +back to the version already set on the resource (so editing existing deployments +still works). + +### Manual Multi-Cluster Setup + +If you prefer to manage ESO installations manually, you can deploy ESO independently and connect target clusters to the central OpenBao: + +1. Ensure the Management OpenBao API (`https://openbao.secureguard.yourcompany.com`) is reachable from the Target cluster nodes. +2. Configure Kubernetes authentication in OpenBao for the Target cluster. This requires setting up a Kubernetes Auth Role in the central OpenBao instance that trusts the Service Account tokens issued by the Target cluster's API server. +3. Deploy ESO to the Target Cluster: + ```bash + helm install external-secrets external-secrets/external-secrets \ + --namespace external-secrets \ + --create-namespace \ + --set installCRDs=true + ``` +4. Create a `ClusterSecretStore` in the Target cluster pointing to the Central OpenBao URL. + +### Adding Clusters to the Dashboard + +Clusters can be added to the SecureGuard dashboard by uploading a kubeconfig file via the UI or the API: + +```bash +curl -X POST http://localhost:3001/api/clusters/kubeconfig \ + -F "kubeconfig=@/path/to/target-kubeconfig" \ + -F "registerAgent=true" \ + -F "clusterName=prod-us-east" +``` + +This creates per-cluster Secrets and optionally registers an SGAgent CR. See the [API Reference](https://github.com/kubermatic/secureguard/blob/main/docs/api-reference.md) for details. + +## Proxy Configuration + +The backend proxy is configured via environment variables. See the [API Reference — Environment Variables](https://github.com/kubermatic/secureguard/blob/main/docs/api-reference.md#environment-variables) for the complete list. + +### Key Configuration Options + +| Variable | Default | Description | +|---|---|---| +| `OIDC_ISSUER_URL` | — (**required**) | Dex/OIDC issuer URL — the proxy exits if unset (auth is mandatory) | +| `PORT` | `3001` | Proxy listen port | +| `DEFAULT_CONTEXT` | current-context | Default kubeconfig context | +| `POD_NAMESPACE` | `default` | Namespace for per-cluster Secrets | + +### Route Allowlist + +The proxy only forwards Kubernetes API paths explicitly listed in `proxy/internal/proxy/routes.go`. If you add a new CRD and need dashboard access, you must add the corresponding path patterns to the allowlist. See [API Reference — Route Allowlist](https://github.com/kubermatic/secureguard/blob/main/docs/api-reference.md#route-allowlist) for the current list. + +### Short-Lived Remote-Cluster Tokens + +When you register a remote cluster you upload its kubeconfig, which usually +carries a long-lived (often admin) credential. The proxy **never persists that +credential**. Instead, at registration it replaces it with a short-lived, +self-renewing token so nothing long-lived is ever stored: + +1. **Bootstrap (once):** the uploaded credential is used a single time to + provision a dedicated least-privilege ServiceAccount + (`secureguard-system/secureguard-remote`) and ClusterRole on the **remote** + cluster. That ClusterRole grants exactly two things: `impersonate` on + users/groups (so per-user RBAC still applies), and `create` on its *own* + `serviceaccounts/token` (scoped by `resourceNames`). +2. **Mint + discard:** an initial bound token is minted via the TokenRequest API. + The proxy stores only the server URL, CA, and that token — the uploaded + credential is discarded. +3. **Self-renew:** a background loop renews the token before it expires using the + remote SA's own `serviceaccounts/token` permission, rewriting a per-cluster + token file under `REMOTE_TOKEN_DIR`. The proxy transport re-reads the file per + request. The agent does the same for clusters it talks to. + +This RBAC lives on the **remote** cluster and is created by code +(`proxy/internal/remotebootstrap`) — it is intentionally **not** in the +management-cluster `k8s/rbac.yaml`. The management-cluster proxy/agent +ServiceAccounts do **not** need `create` on `serviceaccounts/token`, because +their own management-cluster identity is the kubelet-projected token, and all +minting happens against the remote cluster with the remote SA's self-granted +permission. + +Minting is unconditional — there is no "store the credential as-is" mode. A +kubeconfig whose credential cannot provision the remote ServiceAccount (for +example a narrowly scoped bearer token) is **rejected at registration**. +Exec-plugin credentials (EKS/GKE/AKS) are the one exception: they are stored +unchanged because client-go rotates them natively, so there is nothing to mint. + +The only related Helm knob is the writable scratch directory for the rotating +token files (the container root filesystem is read-only): + +```yaml +proxy: + remoteTokenDir: /var/run/secureguard/tokens +``` + +{{% notice note %}} +**Recovery window:** because nothing long-lived is stored, an outage longer than the token TTL requires re-registering the cluster. Widen the window by requesting a longer TTL. +{{% /notice %}} + +## RBAC and Network Policies + +The Helm chart includes templates for RBAC, NetworkPolicies, and PodDisruptionBudgets. Enable them in your values file: + +```yaml +# RBAC (enabled by default) +serviceAccount: + create: true + +# Network policies +networkPolicy: + enabled: true + +# Pod Disruption Budget +podDisruptionBudget: + enabled: true + minAvailable: 1 +``` + +For detailed RBAC configuration, see the [Security Hardening Guide]({{< ref "../security-hardening/" >}}). + +## Storage Backends + +While the default SecureGuard chart can deploy OpenBao with integrated Raft storage for HA, you can alternatively configure OpenBao to use external storage backends if mandated by your infrastructure team. + +OpenBao supports various storage backends, although Integrated Storage (Raft) is highly recommended for modern deployments. If required, you can configure PostgreSQL, Consul, or cloud-specific storage (e.g., AWS DynamoDB, GCP Spanner) by modifying the `openbao.server.ha.config` block. + +## Monitoring + +SecureGuard does not currently expose a Prometheus `/metrics` endpoint. Monitoring is achieved through Kubernetes-native observability: + +- **Pod health**: The proxy exposes a `/healthz` endpoint used for liveness and readiness probes +- **Cluster health**: The `/api/clusters/{id}/health` endpoint checks connectivity to each cluster's API server +- **Logs**: The proxy and agent use Go's standard `log` package, outputting structured logs to stdout +- **Kubernetes events**: Use the Event Stream page in the dashboard or `kubectl get events` for real-time activity + +For comprehensive monitoring, integrate with your existing observability stack by collecting pod logs and Kubernetes events. diff --git a/content/secureguard/main/architecture/_index.en.md b/content/secureguard/main/architecture/_index.en.md new file mode 100644 index 000000000..80adcaa17 --- /dev/null +++ b/content/secureguard/main/architecture/_index.en.md @@ -0,0 +1,209 @@ ++++ +title = "Architecture & Security Model" +date = 2026-06-13T09:00:00+02:00 +weight = 2 +description = "Component architecture, authentication flow, multi-cluster routing, and the zero-knowledge security model behind Kubermatic SecureGuard." +sitemapexclude = true +searchexclude = true +private = true ++++ + +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. + +## Component Architecture + +SecureGuard is not a single monolithic application. It is a composition of specialized open-source services coordinated via Kubernetes. + +1. **React Frontend (Dashboard)** — A statically served Single Page Application (SPA) built with React and Vite. It runs in the user's browser, providing a modern interface to visualize and manage secret sync objects. The frontend is a **zero-knowledge client** — it never receives actual secret values. +2. **Backend Proxy** — A Go reverse proxy running in the cluster. It mediates all communication between the browser and Kubernetes, enforces route allowlisting, redacts secret values from API responses, and handles OIDC authentication with session cookies. +3. **SG Agent Controller** — A Go controller-runtime binary (`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. +4. **Dex (OIDC Provider)** — Handles federated identity and Single Sign-On (SSO). It authenticates users against corporate identity providers (Google, GitHub, Okta, LDAP) and issues OIDC tokens consumed exclusively by the backend proxy. +5. **OpenBao** — A Vault-compatible cryptographic engine managing secret storage, encryption, and audit logging. **Bundled as an opinionated default but optional** — SecureGuard manages ESO, which can instead target any supported provider (AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, HashiCorp Vault, …). +6. **External Secrets Operator (ESO)** — Kubernetes custom controllers that continuously sync secrets from OpenBao (or any other supported provider) into native Kubernetes `Secret` resources. +7. **Federation Broker (optional)** — A standalone Go service (`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]({{< ref "../federation/" >}}). + +### Component Diagram + +```mermaid +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 Security Model + +### Zero-Knowledge Secret Handling + +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. + +- **Proxy-Level Redaction**: The Go backend proxy intercepts all `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`. +- **No Reveal Mechanism**: Because the frontend never receives actual secret content, there is no "reveal" toggle or "show password" button. All secret value fields display `••••••••` unconditionally — there is nothing to reveal. +- **No Secure Data in Browser Storage**: Secret values cannot appear in `localStorage`, `sessionStorage`, browser history, URL parameters, React state, or network traces — because they are stripped before the response leaves the proxy. +- **API Proxy Protection**: The React application **never** communicates directly with the Kubernetes API server. All requests go through the backend proxy, which enforces an explicit route allowlist defined in `proxy/internal/proxy/routes.go`. Any unlisted path is rejected with `403 Forbidden`. + +### Route Allowlisting + +The proxy only forwards requests matching explicitly listed Kubernetes API paths: + +- Core APIs: namespaces, events, secrets (read-only, with redaction) +- CRD APIs: ExternalSecret, SecretStore, ClusterSecretStore (`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 Flow + +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. + +### Sequence Diagram + +```mermaid +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) +``` + +### Authentication Details + +1. **User Login** — An unauthenticated user sees the Login page. Clicking "Log in" redirects to `/api/auth/login` on the backend proxy. +2. **PKCE Initiation** — The proxy generates a cryptographic code verifier and challenge, then redirects to Dex with the challenge. +3. **Identity Provider Authentication** — Dex delegates authentication to the configured identity provider (Google, GitHub, Okta, LDAP, etc.). +4. **Callback to Proxy** — Dex redirects back to `/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. +5. **Token Exchange** — The proxy exchanges the authorization code plus the PKCE verifier for tokens. Tokens are verified and stored server-side — they never leave the proxy. +6. **Session Cookie** — The proxy creates a session with user info and sets an HTTP-only, SameSite=Lax cookie with an 8-hour expiry. No permanent "remember me" option exists — this is a secrets management dashboard. +7. **Authenticated Requests** — All subsequent API requests include the session cookie. The proxy validates the session before forwarding allowed requests to the Kubernetes API server. + +## User Impersonation & Authorization + +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` claim +- `Impersonate-Group` ← each entry in the `groups` claim + +The 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. + +{{% notice note %}} +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]({{< ref "../security-hardening/#rbac-configuration" >}}) and [Advanced Configuration → User Authorization]({{< ref "../advanced-configuration/#user-authorization-rbac-via-impersonation" >}}) for binding examples. +{{% /notice %}} + +### Least-Privilege Service Accounts + +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`](https://github.com/kubermatic/secureguard/blob/main/k8s/rbac.yaml) and [`charts/secureguard/templates/rbac.yaml`](https://github.com/kubermatic/secureguard/blob/main/charts/secureguard/templates/rbac.yaml). + +## Multi-Cluster Routing + +SecureGuard supports managing ESO resources across multiple Kubernetes clusters from a single dashboard. + +- **Cluster Discovery** — The proxy reads all contexts from the `KUBECONFIG` file and exposes them via `GET /api/clusters` with health status. +- **Kubeconfig Hot-Reload** — `fsnotify` watches the kubeconfig file. When it changes, the proxy automatically reloads cluster configurations without requiring a restart. +- **Routing Convention** — API requests target a specific cluster via `/api/clusters/{id}/kube/*` or the default cluster via `/api/kube/*`. +- **Frontend Integration** — A Zustand store (`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. + +## SG Agent Controller + +The SG Agent Controller (`agent/`) is a Go binary built with controller-runtime that automates ESO lifecycle management across a fleet of clusters. + +- **ESODeployment Reconciler** — Watches `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 Heartbeat** — For each connected cluster, the controller maintains an `SGAgent` CR with heartbeat timestamps and health status. The dashboard uses these CRs to display cluster connectivity in the ClusterManagement page. +- **Multi-Cluster Access** — The controller uses kubeconfig contexts to reach remote clusters, the same kubeconfig the proxy uses for routing. + +## Resource Editing Model + +The dashboard is intentionally **read-mostly**. What it can mutate is limited by +the proxy's route allowlist, and the editing experience reflects that: + +- **ESODeployment** is the one resource with a full **guided create/edit form** + (React Hook Form + Zod schemas in `src/pages/ESODeployments/`). Form state is + validated against the Zod schema and serialized to the CR on submit. +- **Other ESO resources** (ExternalSecret, SecretStore, PushSecret, + ReloaderConfig) are **viewed as read-only YAML** in their detail pages + (CodeMirror in read-only mode). You can copy the YAML but not edit it here — + create and change these with `kubectl` or GitOps. +- **Day-2 actions** the proxy explicitly allows: force-syncing an ExternalSecret + (a `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](https://github.com/kubermatic/secureguard/blob/main/docs/api-reference.md#route-allowlist) +for the exact permitted operations. + +## Namespace Context + +A global namespace selector in the TopBar drives all views across the dashboard. The selected namespace is: + +- Stored in a Zustand store (`src/store/namespace.ts`) +- Synced bidirectionally with URL search params (`?namespace=...`) +- Defaulted to "All Namespaces" on initial load + +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. + +## Relationship Visualization + +The Visualization page renders an interactive graph showing relationships between: + +- **ExternalSecret** → **SecretStore** / **ClusterSecretStore** (source) +- **ExternalSecret** → **Kubernetes Secret** (target) +- **PushSecret** → **Kubernetes Secret** (source) → **External Provider** (target) + +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. + +## Transport & Session Security + +| 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 | diff --git a/content/secureguard/main/eso-basics/_index.en.md b/content/secureguard/main/eso-basics/_index.en.md new file mode 100644 index 000000000..9a0464335 --- /dev/null +++ b/content/secureguard/main/eso-basics/_index.en.md @@ -0,0 +1,86 @@ ++++ +title = "External Secrets Operator (ESO) Basics" +date = 2026-06-13T09:00:00+02:00 +weight = 4 +description = "How the External Secrets Operator synchronizes secrets from OpenBao (and other providers) into native Kubernetes Secrets within SecureGuard." +sitemapexclude = true +searchexclude = true +private = true ++++ + +The [External Secrets Operator](https://external-secrets.io/) (ESO) is the second pillar of Kubermatic SecureGuard. While OpenBao acts as the secure storage vault, ESO acts as the intelligent delivery mechanism. + +{{% notice tip %}} +**In plain English:** ESO is a delivery service. You keep your secrets locked in a vault (OpenBao). Your apps, however, only know how to read ordinary Kubernetes `Secret` objects. ESO is the courier that fetches a secret from the vault, drops a copy into a Kubernetes `Secret` where your app can pick it up, and keeps re-checking the vault so the copy never goes stale. Your app never has to know the vault exists. + +New to the terms below (CRD, SecretStore, namespace)? See the [Glossary]({{< ref "../glossary/" >}}). +{{% /notice %}} + +{{% notice note %}} +**A note on providers:** These docs use **OpenBao** as the running example because it's SecureGuard's bundled default, but ESO is **provider-agnostic**. The same `SecretStore` → `ExternalSecret` flow works with AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, HashiCorp Vault, and [many others](https://external-secrets.io/latest/provider/aws-secrets-manager/) — only the `SecretStore`'s `provider` block changes. Wherever you read "OpenBao" below, you can substitute your provider of choice. +{{% /notice %}} + +## What is ESO? + +ESO is an open-source Kubernetes operator designed to integrate external secret management systems directly into Kubernetes. Its primary job is to securely pull data out of OpenBao and inject it cleanly into the native Kubernetes namespaces where your applications actually run. + +## Role in SecureGuard + +Historically, developers had to write custom SDK logic into their apps or deploy complex sidecar containers to retrieve secrets from systems like OpenBao. + +With ESO deployed as part of SecureGuard, applications remain completely oblivious to OpenBao's existence. Apps simply mount standard Kubernetes `Secret` objects (as volumes or environment variables). ESO continuously runs in the background, ensuring those standard `Secret` objects remain synchronized with the authoritative source (OpenBao). + +## Core Custom Resources (CRDs) + +ESO's workflow is governed by four primary Custom Resource Definitions. SecureGuard provides the visual UI to manage these objects. + +### 1. SecretStore +A `SecretStore` is a namespaced configuration that tells ESO **how** to connect to OpenBao. +It defines: +* The OpenBao server URL. +* The exact OpenBao Namespace or KV Engine version (v1 vs v2). +* The credentials ESO should use to authenticate (usually pointing to a specific Kubernetes Service Account). + +### 2. ClusterSecretStore +Functionally identical to a `SecretStore`, but scoped globally. A `ClusterSecretStore` allows an administrator to define a single connection to OpenBao that developers in *any* Kubernetes namespace can utilize. + +### 3. ExternalSecret +The `ExternalSecret` is the core object managed by developers. It defines **what** to fetch. +It specifies: +* Which `SecretStore` or `ClusterSecretStore` to use to reach the vault. +* The precise path in OpenBao where the secret resides (e.g., `kv/data/db/postgres`). +* The exact key inside that OpenBao document to extract. +* The name and format of the Kubernetes `Secret` that ESO should generate. + +### 4. PushSecret +The reverse flow. Often, components *inside* Kubernetes generate credentials (e.g., a database operator generates a root password). A `PushSecret` allows you to take an existing Kubernetes `Secret` and automatically push it upstream into OpenBao for safe, centralized storage and auditing. + +## SecureGuard Custom Resources + +In addition to the upstream ESO CRDs, SecureGuard introduces custom resources: + +### ReloaderConfig +A cluster-scoped resource that configures event-driven workload reloading. When a synced Kubernetes Secret changes, the ReloaderConfig triggers rolling restarts of dependent Deployments, StatefulSets, or DaemonSets. + +### ESODeployment +A namespaced resource managed by the SG Agent Controller. It defines the desired ESO installation state for a target cluster, including version, namespace, component configuration, and replica counts. + +### SGAgent +A cluster-scoped resource representing an agent registered with the central SecureGuard dashboard. SGAgents maintain heartbeat health status for connected clusters. + +## Synchronization & Rotation + +When evaluating an `ExternalSecret`, ESO uses a Reconciliation Loop. + +* **Refresh Interval**: You define how often ESO checks OpenBao for updates (e.g., `refreshInterval: 1h`). +* **Automated Updates**: If a Security Admin updates a password inside OpenBao, ESO will detect the change upon its next polling cycle. It will instantly update the corresponding Kubernetes `Secret`. +* **Zero Downtime**: Consequently, if your applications are built to watch for filesystem changes (like many modern cloud-native apps), the credentials rotate underneath the application without requiring a pod restart or human intervention. + +## The Complete Workflow + +To summarize how OpenBao and ESO collaborate in SecureGuard: + +1. **Storage**: A credential is created securely in OpenBao. +2. **Connection**: A DevOps engineer creates a `SecretStore` in Kubernetes configuring the connection to OpenBao using a Kubernetes Service Account. +3. **Definition**: A Developer defines an `ExternalSecret` referencing the `SecretStore` and the path to the credential. +4. **Delivery**: The ESO Controller authenticates with OpenBao, retrieves the secret value via the API, generates a Kubernetes `Secret`, and continuously watches for any future updates. diff --git a/content/secureguard/main/federation/_index.en.md b/content/secureguard/main/federation/_index.en.md new file mode 100644 index 000000000..bd5436cd3 --- /dev/null +++ b/content/secureguard/main/federation/_index.en.md @@ -0,0 +1,430 @@ ++++ +title = "Federation — Cross-Cluster Secret Distribution" +date = 2026-06-13T09:00:00+02:00 +weight = 8 +description = "Serve secret data to many clusters from a central SecureGuard instance over mTLS without exposing the backend secret stores — the federation broker, CRDs, fedclient, and resolution modes." +sitemapexclude = true +searchexclude = true +private = true ++++ + +Federation lets a central SecureGuard instance serve secret data to many +clusters **without exposing the backend secret stores to those clusters**. The +backend credentials (Vault, AWS, …) live in exactly one place — the federation +cluster — instead of being copied into every consuming cluster. + +{{% notice note %}} +**Status:** opt-in and disabled by default. The federation **broker** and CRDs, the first-class **`fedclient`** consumer, and **both resolvers** — the lean Kubernetes-Secret resolver and the live ESO-library resolver (all providers) — are available. See *Resolution modes* below. +{{% /notice %}} + +## Architecture + +```text + backend stores (Vault/AWS/…) + ▲ (only the broker talks to these) + │ + ┌────────┴─────────┐ pull over mTLS ┌──────────────────┐ + │ Federation Broker │ ◄──── x-workload-token ───── │ remote ESO │ + │ (federation/) │ (SA token) │ (Webhook provider)│ + └───────────────────┘ └──────────────────┘ + central cluster: holds backend creds, remote: holds only a + NO write-creds to remote clusters token; no backend creds +``` + +Remote clusters **pull** from the broker; the central cluster holds no +write-credentials to any remote. The broker is a **separate trust boundary** +from the zero-knowledge dashboard proxy — its own binary and its own +least-privilege ServiceAccount. It is the only SecureGuard component that +handles real secret values, so it must never be colocated with the proxy. + +## Custom Resources + +Federation is configured with two cluster-scoped CRDs in the +`federation.secureguard.io/v1alpha1` group. They carry **references and policy +only — never secret values**. + +### FederationServer + +Declares what the broker exposes and which token issuers it trusts. + +```yaml +apiVersion: federation.secureguard.io/v1alpha1 +kind: FederationServer +metadata: + name: default +spec: + listen: + port: 8443 + tls: + secretRef: fed-server-tls # Secret with tls.crt / tls.key + trustedIssuers: # remote clusters whose SA tokens we accept + - name: cluster-b + issuerURL: https://oidc.cluster-b.example/sa + audiences: [secureguard-federation] + exposedStores: # backend stores the broker may resolve + - name: prod-vault + secretStoreRef: + kind: ClusterSecretStore + name: vault + namespace: secrets-hub +``` + +### FederationAuthorization + +A **deny-by-default** policy granting a remote identity read access to specific +stores and key globs. Without a matching policy, every request is denied. + +```yaml +apiVersion: federation.secureguard.io/v1alpha1 +kind: FederationAuthorization +metadata: + name: allow-cluster-b-app +spec: + identity: + kubernetes: + issuer: cluster-b + serviceAccount: app/eso-fetcher # namespace/name on the remote cluster + allow: + - store: prod-vault + keys: + - db/* # path.Match globs; "**" matches any depth + - api/stripe +``` + +Sample manifests live in [`k8s/samples/federation/`](https://github.com/kubermatic/secureguard/blob/main/k8s/samples/federation/). + +## Deploying the broker + +The broker ships as the `federation/` module and is deployed via the Helm +chart, **disabled by default**: + +```yaml +# values.yaml +federation: + enabled: true + serverName: default + tls: + secretName: fed-server-tls # REQUIRED: server cert/key + # mtls: + # clientCASecret: fed-client-ca # optional: require client certs + audiences: secureguard-federation +``` + +The broker runs under the dedicated `secureguard-federation` ServiceAccount +(read of the federation CRs, `tokenreviews:create`, and — for the interim +resolver — `secrets:get` in its hub namespace). + +## Wire contract + +```text +POST /secretstore/{store}/secrets/{secretName} + header x-workload-token: + body {"remoteRef": {"key": "...", "property": "..."}} + -> 200 {"value": "..."} | 401 | 403 | 404 +``` + +The broker authenticates the token via **per-issuer OIDC** (see below), evaluates +the request against `FederationAuthorization` (deny-by-default), resolves the +secret, and returns it. Client-supplied identity headers (`Impersonate-*`, +`X-Forwarded-*`) are stripped to prevent spoofing. Every serve and denial is +audited (principal + reference only — never the value). + +### Authentication — per-issuer OIDC + +Each remote cluster is its own OIDC issuer (its API server publishes +`/.well-known/openid-configuration` + a JWKS endpoint for its ServiceAccount +token signer). For every request the broker: + +1. reads the token's `iss` claim and matches it to a `FederationServer` + `trustedIssuers[]` entry; +2. fetches that issuer's JWKS (cached, auto-refreshed) and **cryptographically + verifies** the token's signature, `exp`, and that its `aud` contains one of + the issuer's `audiences` (falling back to the broker's default audience); +3. maps the token `sub` (`system:serviceaccount::`) to the identity + `/` and attributes it to the matched issuer's `name` (which + `FederationAuthorization.identity.kubernetes.issuer` references). + +Because verification uses only each issuer's **public** keys, the broker holds +**no credentials to any remote cluster** and supports arbitrarily many distinct +issuers. Configure them on the FederationServer: + +```yaml +spec: + trustedIssuers: + - name: cluster-b + issuerURL: https://oidc.cluster-b.example # OIDC discovery base URL + audiences: [secureguard-federation] + # caBundle: # see "Private-CA issuers" below +``` + +Requirements: each remote must expose OIDC discovery reachable from the broker, +and remote pods must mint projected tokens with `aud: secureguard-federation` +(the fedclient and webhook templates already do). + +#### Private-CA issuers (`caBundle`) + +Discovery uses the system root CAs by default (and `http://` issuers via an +insecure-issuer context for dev). For an issuer served behind a private CA — +e.g. a cluster's own API-server SA issuer (`https://kubernetes.default.svc…`) — +set `trustedIssuers[].caBundle` to the issuer's CA (base64 PEM); the broker uses +it to verify TLS to the discovery + JWKS endpoints: + +```yaml +spec: + trustedIssuers: + - name: cluster-b + issuerURL: https://kubernetes.default.svc.cluster.local + audiences: [secureguard-federation] + caBundle: +``` + +For a cluster's API-server issuer, the CA is its `kube-root-ca.crt` ConfigMap, +and the broker's anonymous discovery requests need the +`system:service-account-issuer-discovery` ClusterRole granted (e.g. to +`system:unauthenticated`). The cluster-e2e exercises exactly this path. + +## Consuming from a remote cluster (stock ESO) + +A stock, open-source ESO can pull from the broker using its built-in +**Webhook provider** — no SecureGuard or enterprise components on the remote. + +```yaml +# On the REMOTE cluster — a SecretStore that calls the broker. +apiVersion: external-secrets.io/v1 +kind: ClusterSecretStore +metadata: + name: secureguard-federation +spec: + provider: + webhook: + url: "https://federation.central.example:8443/secretstore/prod-vault/secrets/{{ .remoteRef.key }}" + method: POST + headers: + Content-Type: application/json + x-workload-token: "{{ .token }}" # from the secret ref below + body: '{"remoteRef":{"key":"{{ .remoteRef.key }}"}}' + result: + jsonPath: "$.value" # extract {"value": "..."} + secrets: + - name: token + secretRef: + name: federation-token # holds the SA token + key: token + caBundle: # trust the broker's TLS +--- +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: db-creds + namespace: app +spec: + secretStoreRef: + kind: ClusterSecretStore + name: secureguard-federation + target: + name: db-creds + data: + - secretKey: password + remoteRef: + key: db/creds +``` + +### Authentication trade-off + +The Webhook provider sends a token from a referenced `Secret`, so this MVP path +uses a **bearer ServiceAccount token** rather than a short-lived projected +token minted per request. Mint a scoped token for the `app/eso-fetcher` +ServiceAccount and store it in the `federation-token` Secret; rotate it +regularly. For the stronger, rotating-token path, use the first-class client +below. Prefer **mTLS** (`federation.mtls.clientCASecret`) where you need +stronger transport auth today. + +## Consuming with the first-class client (fedclient) + +`fedclient` is the consumer that uses a **short-lived, kubelet-rotated projected +ServiceAccount token** — audience-bound, with nothing persisted in the process. +It reads the token fresh from a mounted projected-token volume on every request, +so rotation is automatic and requires no extra RBAC or TokenRequest calls. + +Run it as an init container (or sidecar) that fetches the secret into a shared +in-memory volume the app reads: + +```yaml +initContainers: + - name: fetch-secret + image: quay.io/kubermatic/secureguard-fedclient:latest + args: + - --server=https://federation.central.example:8443 + - --store=prod-vault + - --key=db/creds + - --property=password + - --ca=/etc/federation/ca/ca.crt + - --output=/secrets/db-password + volumeMounts: + - { name: federation-token, mountPath: /var/run/secrets/federation, readOnly: true } + - { name: federation-ca, mountPath: /etc/federation/ca, readOnly: true } + - { name: secrets, mountPath: /secrets } +volumes: + - name: federation-token + projected: + sources: + - serviceAccountToken: + audience: secureguard-federation # must match the broker/authz + expirationSeconds: 3600 + path: token +``` + +Full example: [`docs/examples/fedclient-sidecar.yaml`](https://github.com/kubermatic/secureguard/blob/main/docs/examples/fedclient-sidecar.yaml). +`fedclient` exits with a [distinct code per broker outcome](#cli-reliability-version-retries-exit-codes) +and never logs the token or the secret value; it can also be used as a one-shot +CLI for debugging (`--insecure` for dev TLS). + +This is preferable to the Webhook bearer-token path because the token is +short-lived and rotated, and is the recommended consumer for clusters where you +can run the client. The library lives in `federation/internal/client` and can be +embedded directly. + +### Off-cluster: CI pipelines, VMs, laptops, non-K8s containers + +The projected-token volume above is rotated by the **kubelet**, so it only +exists inside a pod. Off cluster there is no kubelet to refresh it — a token read +from a file simply expires and the broker starts returning `401`. For those +environments `fedclient` can **mint and renew the token itself** via the +Kubernetes TokenRequest API (`--token-source=kube`), so it stays valid without a +kubelet. It needs only: + +1. network access to an API server where the consuming ServiceAccount lives + (selected via `--kubeconfig`/`--context`, or the default `KUBECONFIG`/ + `~/.kube/config`), and +2. RBAC allowing the kubeconfig's bootstrap credential to mint that SA's token — + `create` on the `serviceaccounts//token` subresource. + +The minted token is audience-bound (`--audience`, default +`secureguard-federation`), short-lived (`--token-ttl`, default `1h`), held only +in memory, and re-minted within its refresh window. As today, the broker's +cluster must trust the SA's token issuer (the same prerequisite as the projected +path). + +```bash +# Laptop / CI: one-shot fetch, minting a short-lived token via TokenRequest. +fedclient \ + --server=https://federation.central.example:8443 \ + --store=prod-vault --key=db/creds --property=password \ + --ca=./broker-ca.crt \ + --token-source=kube \ + --token-sa=app/eso-fetcher \ + --audience=secureguard-federation \ + --output=./db-password +``` + +The minimal RBAC for the bootstrap credential the kubeconfig authenticates as: + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: fedclient-mint-token + namespace: app +rules: + - apiGroups: [""] + resources: ["serviceaccounts/token"] + resourceNames: ["eso-fetcher"] + verbs: ["create"] +``` + +**CI pipelines** usually inject a short-lived token as a masked secret instead of +a kubeconfig. Use the static source, reading the value from an env var so it +never appears in `argv` or logs: + +```bash +fedclient ... --token-source=static --token-env=FED_TOKEN +``` + +**Long-running hosts** (a VM daemon or a non-Kubernetes container) keep the +secret current with `--watch`: `fedclient` re-fetches on an interval and renews +the minted token in the background, shutting down cleanly on `SIGINT`/`SIGTERM`. + +```bash +fedclient \ + --server=https://federation.central.example:8443 \ + --store=prod-vault --key=db/creds --property=password \ + --ca=/etc/federation/ca.crt \ + --token-source=kube --token-sa=app/eso-fetcher \ + --watch --interval=10m \ + --output=/run/federation/db-password +``` + +| Source | Renewal | Use it for | +| ------------------- | ---------------------------------------- | ------------------------------------------------ | +| `file` (default) | kubelet rotates the projected-token file | In-pod init container / sidecar | +| `kube` | self-mints via TokenRequest, in process | CI with a kubeconfig, VMs, laptops, non-K8s hosts | +| `static` | none (inject a fresh token each run) | CI with a short-lived token secret | + +### CLI reliability: version, retries, exit codes + +`fedclient --version` prints the build version (injected at build time) and exits +`0`. + +For flaky networks (CI runners, NAT gateways) the one-shot path can retry +transient failures with capped exponential backoff. Retries are **opt-in** +(`--retries=0` by default) and only apply to transient errors — `401`/`403`/`404` +are deterministic and never retried. In `--watch` mode the same retry policy +covers the initial fetch so a flaky start does not abort a long-running sidecar. + +```bash +fedclient ... --retries=5 --retry-delay=2s # 1 initial try + up to 5 retries +``` + +The process exit code distinguishes the broker outcome so scripts can branch +without parsing stderr: + +| Exit code | Meaning | +| --------- | -------------------------------------------------------------- | +| `0` | success (also `--version` / `--help`) | +| `1` | configuration, TLS, network, `5xx`, or any other error | +| `11` | `401` Unauthenticated — token invalid, expired, or wrong audience | +| `13` | `403` Forbidden — no `FederationAuthorization` for this identity/key | +| `14` | `404` Not Found — unknown store or key | + +## Resolution modes + +The broker selects a resolver via the image it runs (chosen with +`federation.eso.enabled` in the chart). Both satisfy the same `Resolver` +interface — the wire contract and auth are identical. + +- **Interim — Kubernetes-Secret (default image, `secureguard-federation`):** + serves values from Kubernetes Secrets that already exist on the hub cluster + (e.g. materialized by a central ESO). The `secretStoreRef.namespace` is the + lookup namespace and the request key is the Secret name. Lean image, no ESO + dependency. +- **Live — ESO providers (`secureguard-federation-eso`):** imports **all** ESO + providers and resolves on demand directly against the backend (Vault/AWS/GCP/…) + with **nothing at rest**. The broker reads the ESO `SecretStore`/ + `ClusterSecretStore` named by each `exposedStores[].secretStoreRef`, builds the + provider client, and fetches the secret per request. Enable with: + + ```yaml + federation: + enabled: true + eso: + enabled: true # runs the -eso image + grants the ESO read RBAC + tls: { secretName: fed-server-tls } + ``` + + Trade-offs: the `-eso` image is large (every provider SDK → bigger CVE + surface — built and scanned separately from the lean images), and it grants + the broker **cluster-wide read of ESO stores + their auth Secrets** (a real + privilege increase; see [Security note](#security-notes)). It lives in its own + Go module (`federation/resolve-eso`) so the default broker stays ESO-free. + The ESO source is vendored as a pinned git submodule (currently **v2.6.0**); + providers compile in via the `all_providers` build tag. + +## Security notes + +- The broker handles real secret values and is isolated from the + zero-knowledge proxy — never colocate them. +- Authorization is deny-by-default; scope `allow[].keys` as tightly as possible. +- Enable etcd encryption-at-rest and treat the federation cluster as a hardened + tier. +- No secret values are logged, cached, or persisted; audit logs record + references only. diff --git a/content/secureguard/main/getting-started/_index.en.md b/content/secureguard/main/getting-started/_index.en.md new file mode 100644 index 000000000..c3edfda7e --- /dev/null +++ b/content/secureguard/main/getting-started/_index.en.md @@ -0,0 +1,158 @@ ++++ +title = "Getting Started" +date = 2026-06-13T09:00:00+02:00 +weight = 1 +description = "Quickly deploy Kubermatic SecureGuard in a local or development environment and watch your first secret sync end-to-end." +sitemapexclude = true +searchexclude = true +private = true ++++ + +This guide will walk you through quickly deploying Kubermatic SecureGuard in a local or development environment. This deployment bundles OpenBao (in dev mode), the Dex OIDC provider, External Secrets Operator (ESO), and the SecureGuard dashboard UI. + +{{% notice warning %}} +This guide is intended for development and local testing. Do not use `dev` mode secrets for production workloads. For production deployments, refer to the [Installation]({{< ref "../installation/" >}}) guide. +{{% /notice %}} + +## What You'll Have at the End + +A local SecureGuard you can log into, showing how a secret flows from the vault +to your apps: + +```text + OpenBao (vault) ──ESO syncs──▶ Kubernetes Secret ──▶ your app + ▲ │ + └──── you watch it all from the SecureGuard dashboard ────┘ +``` + +If any of the terms below (ESO, OpenBao, SecretStore, OIDC, Dex) are new, keep +the [Glossary]({{< ref "../glossary/" >}}) handy — each is defined in one line. + +## Prerequisites +Before you begin, ensure you have the following installed: +* [Docker Engine](https://docs.docker.com/engine/install/) 24+ +* [Helm](https://helm.sh/docs/intro/install/) +* A running Kubernetes cluster (e.g., [kind](https://kind.sigs.k8s.io/), [minikube](https://minikube.sigs.k8s.io/), or Docker Desktop Kubernetes) + +## Quickstart Deployment + +1. **Install the Helm Chart** + Deploy the chart directly from the Kubermatic Quay.io registry into your cluster under the `secureguard` release name. The chart will automatically install all required Custom Resource Definitions (CRDs) for the External Secrets Operator. + ```bash + helm install secureguard oci://quay.io/kubermatic/helm-charts/secureguard \ + --namespace secureguard-system \ + --create-namespace \ + --set openbao.server.dev.enabled=true \ + --version 0.2.0 # replace with latest from the releases page + ``` + +2. **Verify the Deployment** + Ensure all pods have started and are reporting `Running` status: + ```bash + kubectl get pods -n secureguard-system + ``` + You should see pods for the backend proxy, the UI, OpenBao, Dex, and the ESO controllers. + +## Navigating the Dashboard + +Once the deployment is up, you need to access the SecureGuard dashboard. + +1. **Port-Forward the Dashboard Service** + *Note: In production, you would configure an Ingress. For local testing, port-forwarding is sufficient.* + ```bash + kubectl port-forward svc/secureguard-ui 8080:80 -n secureguard-system + ``` + +2. **Access the UI** + Open your browser and navigate to `http://localhost:8080`. + +3. **Logging In via Dex** + Authentication is mandatory, so you are redirected to the Dex OIDC login page. The local dev deployment provisions a static admin user — email `admin@secureguard.local`, password `admin` — bound to `cluster-admin`. **Change these immediately for any non-local deployment.** + + {{% notice note %}} + Access is enforced per user: the proxy impersonates the logged-in user on every Kubernetes API request, so what you can see and do is governed by the RBAC bound to your user/groups. A user with no bindings can log in but sees `403` errors until granted access — see [User Authorization]({{< ref "../advanced-configuration/#user-authorization-rbac-via-impersonation" >}}). + {{% /notice %}} + +### Understanding the Security Model Basics + +As you explore the dashboard, keep the following security principles in mind: + +* **Secrets are Masked by Default:** When viewing the status of an `ExternalSecret`, the actual secret values retrieved from OpenBao are masked (`••••••••`). The proxy redacts all secret values before they reach the browser — there is no mechanism to reveal them in the UI. +* **No Direct API Exposure:** The browser UI never contacts the Kubernetes API server directly. All API calls pass through the SecureGuard Backend Proxy, which handles authentication and limits exposed endpoints. +* **No Plaintext Storage:** Secret values are not stored in URL parameters, browser history, or caches to prevent leakage. + +## Create Your First Secret Sync + +Let's watch a secret flow end-to-end. To keep this beginner-friendly we'll use +ESO's built-in **`fake` provider**, which returns values baked into the manifest +— so you don't need OpenBao auth, a cloud account, or any credentials to see +syncing work. (In real life the provider would be OpenBao or a cloud vault.) + +Remember: the dashboard is read-mostly, so we **create** the resources with +`kubectl` and then **watch** them in the UI — exactly how you'd work day-to-day. + +1. **Save this manifest** as `first-secret.yaml`: + + ```yaml + # A self-contained demo: a fake "vault" plus an ExternalSecret that reads it. + apiVersion: external-secrets.io/v1 + kind: ClusterSecretStore + metadata: + name: demo-fake-store # the "how to connect" config (here: fake data) + spec: + provider: + fake: + data: + - key: /demo/db + value: "hunter2" # the pretend secret value + version: v1 + --- + apiVersion: external-secrets.io/v1 + kind: ExternalSecret + metadata: + name: demo-db-credentials + namespace: default + spec: + refreshInterval: 1h + secretStoreRef: + name: demo-fake-store + kind: ClusterSecretStore + target: + name: demo-db-credentials # the Kubernetes Secret ESO will create + data: + - secretKey: password # key inside the created Secret + remoteRef: + key: /demo/db # which entry to read from the store + version: v1 + ``` + +2. **Apply it:** + + ```bash + kubectl apply -f first-secret.yaml + ``` + +3. **Watch it in the dashboard:** + - Open **External Secrets** — `demo-db-credentials` appears and turns + **Synced** (green) within a few seconds. + - Open **Secrets** — ESO has created a Kubernetes Secret named + `demo-db-credentials`, tagged as ESO-managed. Its `password` key is shown + as `••••••••` — the value never reaches your browser, even in this demo. + - Click the ExternalSecret and try **Sync Now** to force an immediate refresh. + +4. **Clean up** when you're done: + + ```bash + kubectl delete -f first-secret.yaml + ``` + +{{% notice tip %}} +**What just happened?** You defined *where to read from* (the `ClusterSecretStore`) and *what to fetch* (the `ExternalSecret`). ESO did the rest: it created and now keeps a normal Kubernetes `Secret` in sync. Swap the `fake` provider for an OpenBao `SecretStore` and the exact same flow works with real, encrypted secrets — see [ESO Basics]({{< ref "../eso-basics/" >}}). +{{% /notice %}} + +## Next Steps +Now that you have a local instance running: +* Tour the dashboard feature by feature in the [User Guide]({{< ref "../user-guide/" >}}). +* Keep the [Glossary]({{< ref "../glossary/" >}}) handy for any unfamiliar term. +* Learn how to deploy to a [Production Environment]({{< ref "../installation/" >}}). +* Explore the [Architecture & Security Model]({{< ref "../architecture/" >}}) to understand how SecureGuard protects your secrets. diff --git a/content/secureguard/main/glossary/_index.en.md b/content/secureguard/main/glossary/_index.en.md new file mode 100644 index 000000000..ba0ee7777 --- /dev/null +++ b/content/secureguard/main/glossary/_index.en.md @@ -0,0 +1,187 @@ ++++ +title = "Glossary" +date = 2026-06-13T09:00:00+02:00 +weight = 12 +description = "Plain-language definitions of every key term used across the SecureGuard documentation — from secrets and vaults to ESO resources, OIDC, and federation." +sitemapexclude = true +searchexclude = true +private = true ++++ + +Plain-language definitions of the terms used throughout the SecureGuard docs. +Skim it once, then refer back whenever a word is unclear. Terms are grouped by +theme; within each group they build on each other. + +{{% notice tip %}} +**New to all of this?** Read the four **core ideas** first — Secret, Vault, Sync, and Dashboard — then dip into the rest as needed. +{{% /notice %}} + +## Core Ideas (start here) + +- **Secret** — Any sensitive value an app needs to run: a password, API key, + database connection string, token, or TLS certificate. The whole point of + SecureGuard is to store and deliver these safely. +- **Vault** — A dedicated, encrypted, audited place to store secrets. In + SecureGuard, the vault is **OpenBao**. Apps never read from the vault + directly — that's ESO's job. +- **Sync** — The continuous process of copying a secret from the vault into a + Kubernetes `Secret` and keeping the copy up to date. If the value changes in + the vault, the synced copy is updated automatically. +- **Dashboard** — SecureGuard's web UI: your "control room" for viewing and + managing secret syncing. It deliberately **never shows real secret values** + (you always see `••••••••`). + +## The Three Building Blocks + +- **OpenBao** — The open-source secrets vault **bundled** with SecureGuard + (forked from HashiCorp Vault). It encrypts secrets, controls who can read + them, and logs every access. It is an **opinionated default, not a + requirement** — you can disable it and use any ESO-supported provider instead. + See [OpenBao Basics]({{< ref "../openbao-basics/" >}}). +- **ESO (External Secrets Operator)** — The Kubernetes component that pulls + secrets out of OpenBao (or AWS, GCP, Azure, etc.) and turns them into native + Kubernetes `Secret` objects, then keeps them in sync. See + [ESO Basics]({{< ref "../eso-basics/" >}}). +- **SecureGuard** — The dashboard + backend proxy that ties OpenBao and ESO + together with a safe, easy-to-use management interface. + +## Kubernetes Terms + +- **Kubernetes** — The system that runs your containerized applications. If + you're brand new, think of it as the "operating system for your cluster of + servers." +- **Cluster** — One Kubernetes environment (a group of machines working + together). SecureGuard can manage many clusters from one dashboard. +- **Namespace** — A folder-like way to divide a cluster into separate areas + (e.g. `payments`, `frontend`). Most resources live inside a namespace. The + dashboard's namespace selector filters everything by namespace. +- **Secret (Kubernetes)** — Kubernetes' built-in object for holding sensitive + data. ESO **creates and updates** these for you from the vault. Apps consume + them as environment variables or mounted files. +- **CRD (Custom Resource Definition)** — A way to teach Kubernetes about new + object types beyond its built-ins. `ExternalSecret`, `SecretStore`, + `PushSecret`, `ESODeployment`, and `SGAgent` are all CRDs. +- **CR (Custom Resource)** — An actual instance of a CRD (e.g. *one specific* + `ExternalSecret` named `db-creds`). +- **Operator / Controller** — A program that watches CRs and makes the cluster + match what they describe (a "reconciliation loop"). ESO and the SG Agent are + controllers. +- **RBAC (Role-Based Access Control)** — Kubernetes' permission system. It + decides who can see or change which resources. In SecureGuard, *your* RBAC + determines what you can do in the dashboard. + +## ESO Resource Types (what you'll manage in the UI) + +- **SecretStore** — Configuration that tells ESO **how to connect** to a vault + (server URL, auth method, credentials). Namespaced: usable only within its own + namespace. +- **ClusterSecretStore** — The same idea as a SecretStore, but cluster-wide: + any namespace can use it. +- **ExternalSecret** — Defines **what to fetch** from a store and **which + Kubernetes Secret to create** from it. This is the object developers create + most often. +- **PushSecret** — The reverse direction: takes an existing Kubernetes Secret + and **pushes it up** into an external provider for safekeeping. +- **ReloaderConfig** — Tells SecureGuard to **restart workloads** (Deployments, + StatefulSets, DaemonSets) automatically when a secret they use changes. +- **ESODeployment** — Describes how ESO itself should be **installed/upgraded** + on a target cluster. The SG Agent acts on it. +- **SGAgent** — Represents a connected cluster's agent and its **heartbeat** + (is it alive and reporting?). +- **Provider** — The external system a store talks to: OpenBao/Vault, AWS + Secrets Manager, GCP Secret Manager, Azure Key Vault, and others. SecureGuard + is provider-agnostic; OpenBao is just the bundled default. +- **`remoteRef` / key / path** — The address of a secret inside the provider + (e.g. `secret/db/postgres`, key `password`). +- **Refresh interval** — How often ESO re-checks the provider for changes + (e.g. `1h`). Shorter = fresher, but more requests. + +## OpenBao / Vault Terms + +- **Secret Engine** — A module inside OpenBao that stores or generates secrets. + The most common is **KV (Key/Value)** for static secrets. +- **KV v1 vs v2** — Two versions of the Key/Value engine. v2 adds versioning of + secret values. SecureGuard's samples use KV v2 mounted at `secret/`. +- **Auth Method** — How a user or machine proves its identity to OpenBao. ESO + uses the **Kubernetes** auth method (it presents a ServiceAccount token). +- **Sealing / Unsealing** — A sealed OpenBao knows where its encrypted data is + but **can't decrypt it** until it's "unsealed" with the master key. Production + setups use **Auto-Unseal** (e.g. AWS KMS) so pods unseal themselves on + restart. See [OpenBao Basics]({{< ref "../openbao-basics/" >}}). +- **Dynamic Secrets** — Credentials OpenBao generates on demand with a built-in + expiry (TTL), instead of storing a static value. +- **Audit Device** — OpenBao's logging of every read/write, for compliance. +- **Dev mode** — A throwaway OpenBao mode that keeps secrets in memory and + auto-unseals. Great for testing; **never for production** (data is lost on + restart). + +## Authentication & Security Terms + +- **OIDC (OpenID Connect)** — The standard login protocol SecureGuard uses for + the dashboard. You log in through your identity provider rather than a local + password. +- **Dex** — A small identity service bundled with SecureGuard that speaks OIDC + and can connect to GitHub, Google, LDAP, Okta, etc. It's the dashboard's + "login broker." +- **IdP (Identity Provider)** — Your organization's source of user accounts + (e.g. Google Workspace, GitHub, Active Directory). Dex federates to it. +- **PKCE** — A security add-on to the OIDC login flow that prevents stolen + login codes from being reused. Handled automatically. +- **Session cookie** — A short-lived, HTTP-only cookie the proxy gives your + browser after login. It proves you're logged in; it is **not** a secret value. +- **Impersonation** — On every request, the proxy tells Kubernetes "treat this + as user *alice@example.com* in groups *X, Y*." Kubernetes RBAC then decides + what's allowed. This is why **your** permissions govern what you see. +- **Zero-knowledge (client)** — The guarantee that the browser/UI **never + receives real secret values** — they're stripped at the proxy. Hence the + unconditional `••••••••`. +- **Redaction** — The proxy replacing secret values with `REDACTED`/`••••••••` + before responses leave the server. +- **Route allowlist** — The fixed list of Kubernetes API paths the proxy is + willing to forward. Anything not on the list is rejected with `403`. +- **Least privilege** — Giving each component only the permissions it truly + needs. The proxy and agent run under separate, narrowly-scoped accounts. + +## SecureGuard Architecture Terms + +- **Backend Proxy** — The Go service that sits between the browser and + Kubernetes. It logs you in, enforces the route allowlist, and redacts secret + values. The browser talks **only** to the proxy, never to Kubernetes directly. +- **SG Agent (Agent Controller)** — A controller that installs/upgrades ESO on + remote clusters (via `ESODeployment`) and reports cluster health (via + `SGAgent`). +- **Management cluster** — The central cluster running the dashboard, proxy, + OpenBao, Dex, and the agent. +- **Target / remote cluster** — A cluster managed *from* the management cluster + (where ESO is deployed and secrets are delivered). +- **Kubeconfig** — A file containing the address and credentials for a cluster. + You upload one to add a cluster to the dashboard. + +## Federation Terms (advanced, optional) + +- **Federation** — Letting one central SecureGuard serve secret values to many + clusters **without** copying the backend credentials into each one. Disabled + by default. See [Federation]({{< ref "../federation/" >}}). +- **Federation Broker** — The standalone service that actually serves those + values over mTLS. It's the **only** component that handles real secret values, + kept isolated from the zero-knowledge proxy. +- **FederationServer / FederationAuthorization** — CRDs that declare what the + broker exposes and which remote identities may read which keys (deny-by-default). +- **fedclient** — A small client that runs on a remote cluster (or a CI job/VM) + to fetch a secret from the broker using a short-lived, rotating token. +- **mTLS (mutual TLS)** — TLS where *both* sides present certificates, so the + broker and client each verify the other. + +## Operations Terms + +- **Helm / chart / values** — Helm is Kubernetes' package manager; a "chart" is + the package (SecureGuard ships as one), and a `values.yaml` file holds your + configuration choices. +- **Sub-chart** — A chart bundled inside another (SecureGuard bundles OpenBao, + Dex, ESO, and Reloader as optional sub-charts). +- **High Availability (HA)** — Running multiple replicas so the service survives + a node or pod failure. +- **Raft / Integrated Storage** — OpenBao's built-in clustered storage used for + HA. +- **Stale secret** — A synced Secret that hasn't refreshed within its expected + interval — the dashboard flags these so you can investigate. diff --git a/content/secureguard/main/installation/_index.en.md b/content/secureguard/main/installation/_index.en.md new file mode 100644 index 000000000..9e6ea20a3 --- /dev/null +++ b/content/secureguard/main/installation/_index.en.md @@ -0,0 +1,208 @@ ++++ +title = "Installation & Deployment" +date = 2026-06-13T09:00:00+02:00 +weight = 3 +description = "Deploy Kubermatic SecureGuard across managed, bring-your-own-provider, and bring-your-own-ESO modes, with production hardening guidance." +sitemapexclude = true +searchexclude = true +private = true ++++ + +Kubermatic SecureGuard is designed to be highly flexible, offering several deployment modes depending on your existing infrastructure and production requirements. + +SecureGuard ships with **OpenBao** (Vault-compatible secret engine), **Dex** (OIDC provider), and **ESO** as optional Helm sub-charts. Each component can be toggled independently via the Helm values file. + +{{% notice note %}} +**OpenBao is optional and opinionated.** It's bundled so teams without a vault get a complete stack out of the box, but SecureGuard is **provider-agnostic** — it manages ESO, and ESO supports many backends (AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, HashiCorp Vault, and others). If you already have a secrets backend, disable OpenBao (`--set openbao.enabled=false`) and point your `SecretStore`s at your provider. Dex is similarly optional if you already run an OIDC provider. +{{% /notice %}} + +## Prerequisites +- A Kubernetes cluster (v1.27 or newer recommended) +- `helm` v3 CLI installed + +## Deployment Modes + +SecureGuard's Helm chart bundles all necessary Custom Resource Definitions (CRDs) and sub-chart dependencies (OpenBao, Dex, ESO). + +### 1. Managed (Default) +In this mode, all components are installed automatically by the SecureGuard Helm chart. This is recommended for clusters that do not already have an established secret manager. + +```bash +helm install secureguard oci://quay.io/kubermatic/helm-charts/secureguard \ + --namespace secureguard-system \ + --create-namespace +``` + +- **Dex** provides OIDC authentication. +- **OpenBao** provides a Vault-compatible secret backend (standalone/production mode by default). +- **ESO** manages external secrets, connected to OpenBao via Kubernetes auth. +- **OpenBao UI** will be available by default at `:30820/ui` (NodePort) or via Ingress. + +### 2. Bring Your Own Provider (External Vault, AWS, GCP, Azure, …) + +You don't have to use the bundled OpenBao. SecureGuard manages ESO, and ESO can +talk to any of its [supported providers](https://external-secrets.io/latest/provider/aws-secrets-manager/). +Disable the bundled OpenBao and point your `SecretStore`/`ClusterSecretStore` +resources at your own backend. + +**Example — an existing HashiCorp Vault or OpenBao cluster:** + +```bash +helm install secureguard oci://quay.io/kubermatic/helm-charts/secureguard \ + --namespace secureguard-system \ + --create-namespace \ + --set openbao.enabled=false \ + --set openbao.externalUrl=https://my-vault.company.internal:8200 +``` + +**Example — a cloud provider (no vault to deploy at all):** + +```bash +helm install secureguard oci://quay.io/kubermatic/helm-charts/secureguard \ + --namespace secureguard-system \ + --create-namespace \ + --set openbao.enabled=false +``` + +*Note: With any external provider you configure the `SecretStore` resources to +authenticate against that provider (e.g. Kubernetes auth for Vault/OpenBao, IRSA +for AWS, Workload Identity for GCP). The dashboard, proxy, and ESO behave +identically regardless of which provider you choose.* + +### 3. Bring Your Own ESO +If your target clusters already have the External Secrets Operator installed and managed by another platform team, you can instruct SecureGuard to deploy only the UI and Proxy, skipping the ESO installation. + +```bash +helm install secureguard oci://quay.io/kubermatic/helm-charts/secureguard \ + --namespace secureguard-system \ + --create-namespace \ + --set eso.enabled=false +``` + +--- + +## Dev Mode for Testing + +By default, OpenBao starts in `standalone` (production-oriented) mode, meaning it writes to persistent storage and requires manual initialization and unsealing. + +For local development or testing, you can enable `dev` mode, where secrets are stored in-memory, automatically unsealed, but lost upon restart: + +```bash +helm install secureguard oci://quay.io/kubermatic/helm-charts/secureguard \ + --namespace secureguard-system \ + --create-namespace \ + --set openbao.server.dev.enabled=true +``` + +{{% notice warning %}} +Dev mode is intended only for local testing. Secrets are stored in-memory and will be lost when the pod restarts. Do not use dev mode in production. +{{% /notice %}} + +--- + +## Production Hardening + +When moving to production, several configurations MUST be applied to ensure a secure, resilient platform. + +### High Availability (HA) + +Enable High Availability with Raft integrated storage and increase the replica count (typically 3 or 5). + +Example `values-production.yaml` snippet: +```yaml +openbao: + server: + ha: + enabled: true + replicas: 3 +``` + +### Automatic Unsealing +In production, you should not manually unseal the OpenBao cluster every time a pod restarts. Configure an auto-unseal mechanism such as AWS KMS, GCP KMS, Transit, or Azure Key Vault. + +```yaml +openbao: + server: + ha: + enabled: true + replicas: 3 + seal: + type: awskms + config: + region: eu-west-1 + kms_key_id: "alias/openbao-unseal" +``` + +### Ingress & TLS +Ensure that traffic to the SecureGuard dashboard, the proxy, and OpenBao is encrypted via TLS. Configure Ingress annotations to use a tool like cert-manager for automatic certificate provisioning. + +```yaml +ingress: + enabled: true + className: "nginx" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + hosts: + - host: secureguard.yourdomain.com + paths: + - path: / + pathType: ImplementationSpecific + tls: + - secretName: secureguard-tls + hosts: + - secureguard.yourdomain.com +``` + +### Tenant Isolation +For multi-tenant environments, the recommendation is deploying distinct secrets management vaults to limit the blast radius. You can deploy multiple, namespace-scoped instances of OpenBao behind the SecureGuard dashboard, isolating teams at the infrastructure level. + +--- + +## RBAC, Network Policies & Resource Limits + +The SecureGuard Helm chart includes templates for RBAC, network policies, and resource limits. These are critical for production deployments. + +### RBAC + +The chart provisions a dedicated `ServiceAccount`, `ClusterRole`, and `ClusterRoleBinding` (see [`k8s/rbac.yaml`](https://github.com/kubermatic/secureguard/blob/main/k8s/rbac.yaml)). The ClusterRole grants the proxy read access to the Kubernetes resources it manages (ExternalSecrets, SecretStores, Secrets, Events, etc.) and nothing more. Review and restrict these permissions further if your deployment does not use all SecureGuard features. + +```yaml +rbac: + create: true + # Set to false if you manage RBAC externally +``` + +### Network Policies + +The chart includes optional `NetworkPolicy` resources that restrict ingress and egress traffic to the SecureGuard components. Enable these in production to limit the blast radius of a potential compromise: + +```yaml +networkPolicy: + enabled: true + # Restricts proxy ingress to the Ingress controller + # Restricts proxy egress to the Kubernetes API server and OpenBao +``` + +### Resource Limits + +Always set resource requests and limits for all SecureGuard components in production. The chart exposes these under each component's `resources` key: + +```yaml +proxy: + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 256Mi + +ui: + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +``` diff --git a/content/secureguard/main/openbao-basics/_index.en.md b/content/secureguard/main/openbao-basics/_index.en.md new file mode 100644 index 000000000..555454c16 --- /dev/null +++ b/content/secureguard/main/openbao-basics/_index.en.md @@ -0,0 +1,60 @@ ++++ +title = "OpenBao Basics" +date = 2026-06-13T09:00:00+02:00 +weight = 5 +description = "Understand OpenBao — the open-source, production-grade secrets vault bundled with Kubermatic SecureGuard — including secret engines, auth methods, and unsealing." +sitemapexclude = true +searchexclude = true +private = true ++++ + +[OpenBao](https://openbao.org/) is the open-source, production-grade secrets management backend at the heart of Kubermatic SecureGuard. It was forked from HashiCorp Vault to provide a completely open, community-driven cryptographic engine. + +{{% notice tip %}} +**In plain English:** OpenBao is a high-security vault for your secrets — like a bank vault, but for passwords, API keys, and certificates. Instead of scattering credentials across config files and `.env` files, you store them in one encrypted, audited place and hand out access carefully. Apps don't open the vault themselves; ESO fetches what they need on their behalf (see [ESO Basics]({{< ref "../eso-basics/" >}})). + +Unfamiliar with a term below (KV engine, auth method, unsealing)? The [Glossary]({{< ref "../glossary/" >}}) defines each in one line. +{{% /notice %}} + +{{% notice note %}} +**OpenBao is optional — it's an opinionated default, not a requirement.** SecureGuard bundles OpenBao so teams **without** an existing vault get a complete stack out of the box. If you already run a secrets backend, you don't need OpenBao at all: SecureGuard manages **ESO**, and ESO works with many providers — AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, HashiCorp Vault, and [others](https://external-secrets.io/latest/provider/aws-secrets-manager/). Just point your `SecretStore`/`ClusterSecretStore` resources at your provider and disable the bundled OpenBao (`--set openbao.enabled=false`). The rest of this page applies only if you choose to use OpenBao as your backend. +{{% /notice %}} + +## What is OpenBao? + +OpenBao provides a centralized, highly secure vault for managing sensitive data tightly. It effectively removes secrets from application source code, configuration files, and basic Kubernetes secrets until they are explicitly needed. + +## Key Concepts + +To effectively operate OpenBao within SecureGuard, you must understand three core concepts: + +### 1. Secret Engines +Secret Engines are the components inside OpenBao where data is either stored, generated, or encrypted. +* **Key/Value (KV)**: This is the most common engine used with SecureGuard. It stores static secrets, structured as JSON objects, at specific hierarchical paths (e.g., `secret/data/database/postgres/password`). +* **Dynamic Secrets**: OpenBao can dynamically generate credentials for databases (e.g., MySQL, PostgreSQL), cloud providers (AWS, GCP), or message queues (RabbitMQ) on-the-fly when requested, ensuring credentials have a naturally limited Time-To-Live (TTL). +* **Transit Engine**: Offers "encryption as a service," allowing applications to encrypt or decrypt data without actually storing it in OpenBao. + +### 2. Auth Methods +Auth methods represent how users or machines prove their identity to OpenBao. +* **OIDC (OpenID Connect)**: Dashboard authentication and OpenBao authentication are separate flows. Dashboard users authenticate via Dex (OIDC) to access the UI. Independently, ESO authenticates to OpenBao using Kubernetes Service Account tokens via the `kubernetes` auth method — dashboard users do not authenticate to OpenBao directly. +* **Kubernetes Auth**: Used by the External Secrets Operator (ESO). ESO running in your cluster presents a Kubernetes Service Account token to OpenBao. OpenBao verifies the token's validity with the Kubernetes API server before granting access. + +### 3. Audit Devices +Every request to OpenBao, whether it is a read from ESO or a write from a developer, is securely logged. These Audit Devices output JSON-formatted logs directly to stdout, syslog, or a file, which can then be ingested by tools like Elasticsearch or Splunk for compliance and anomaly detection. + +## The Role of OpenBao in SecureGuard + +Within the SecureGuard ecosystem, OpenBao acts purely as the **Central Vault**. +It handles: +1. **Encryption**: Ensuring all secrets are encrypted both in transit and at rest using industry-standard encryption (AES-256-GCM for storage, TLS 1.2+ for transit). +2. **RBAC (Role-Based Access Control)**: Enforcing strict least-privilege policies. You define *who* (which OIDC group or which Kubernetes Service Account) can read *what* paths. +3. **Multi-Tenancy**: Through its namespace features, OpenBao allows large organizations to isolate teams. "Team A" vault paths are completely invisible to "Team B". + +## Understanding Unsealing & High Availability + +By default, an OpenBao server starts in a **Sealed** state. It knows where to find the encrypted data, but it does not know how to decrypt it because it lacks the master key. + +* **Manual Unsealing**: Requires multiple operators to independently enter shards of the master key. This is secure but impractical for cloud-native orchestration (e.g., if a pod simply restarts). +* **Auto-Unseal**: In production SecureGuard setups, OpenBao is configured with an Auto-Unseal mechanism (like AWS KMS or Azure Key Vault). When the pod starts, it automatically reaches out to the KMS to retrieve the key and decrypts itself, enabling seamless cluster scaling and recovery. + +When clustered for **High Availability (HA)** using Integrated Raft Storage, only one OpenBao node is the "Active" node processing writes, while the others serve as "Standbys". If the active node fails, another node instantly takes over, guaranteeing virtually zero downtime for your secret synchronization. diff --git a/content/secureguard/main/security-hardening/_index.en.md b/content/secureguard/main/security-hardening/_index.en.md new file mode 100644 index 000000000..06c8dccd7 --- /dev/null +++ b/content/secureguard/main/security-hardening/_index.en.md @@ -0,0 +1,258 @@ ++++ +title = "Security Hardening Guide" +date = 2026-06-13T09:00:00+02:00 +weight = 9 +description = "Production security best practices for SecureGuard — TLS, OIDC/Dex hardening, RBAC via impersonation, network policies, container security, CSP, and supply-chain controls." +sitemapexclude = true +searchexclude = true +private = true ++++ + +This guide covers security best practices for deploying SecureGuard in production environments. SecureGuard manages Kubernetes Secrets and external secret provider credentials — a security lapse can expose API keys, database passwords, TLS certificates, and cloud provider credentials. + +## Core Security Model + +SecureGuard operates on a **zero-knowledge** principle for secret values: + +- The **Backend Proxy** redacts all secret values (`.data` and `.stringData`) before responses reach the browser +- The **frontend never receives** actual secret content — only metadata (names, namespaces, keys, sync status) +- All secret value fields display `••••••••` unconditionally — there is no "reveal" mechanism +- All mutating API calls go through the proxy — the browser never contacts the Kubernetes API server directly + +## TLS Configuration + +### Ingress TLS + +Always terminate TLS at the ingress layer. Use cert-manager for automatic certificate provisioning: + +```yaml +ingress: + enabled: true + className: "nginx" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + nginx.ingress.kubernetes.io/force-ssl-redirect: "true" + nginx.ingress.kubernetes.io/ssl-redirect: "true" + hosts: + - host: secureguard.yourdomain.com + paths: + - path: / + pathType: ImplementationSpecific + tls: + - secretName: secureguard-tls + hosts: + - secureguard.yourdomain.com +``` + +### Internal TLS + +For environments requiring end-to-end encryption: +- Configure OpenBao with TLS certificates for API communication +- Use the `caBundle` field in `SecretStore` resources when OpenBao uses a private CA +- Ensure Dex is served over HTTPS with valid certificates + +## OIDC / Dex Hardening + +### Static Admin User + +The default Helm deployment provisions a built-in static Dex admin user (`dex.staticAdmin.enabled: true`). By default its email is `admin@secureguard.local` and its **password is randomly auto-generated** (16 characters) and stored in the `-dex-admin` Secret — there is no hardcoded password in the Helm path. Retrieve it with: + +```bash +kubectl get secret -dex-admin -n -o jsonpath='{.data.password}' | base64 -d +``` + +{{% notice warning %}} +**Local development and CI only:** the raw dev manifest [`k8s/dex.yaml`](https://github.com/kubermatic/secureguard/blob/main/k8s/dex.yaml) hardcodes static passwords for `admin@secureguard.local` and `viewer@secureguard.local` (bcrypt hash of `admin`). These credentials exist **solely for kind-based local development and CI** (`scripts/deploy-dev-dashboard.sh`) and are **never** used by the Helm chart. Do not deploy `k8s/dex.yaml` to any shared or production cluster. +{{% /notice %}} + +For any non-local deployment, either set a strong `dex.staticAdmin.password` explicitly, or disable the static admin entirely (`dex.staticAdmin.enabled: false`) and federate to a production IDP (see below). + +### Configure a Production IDP + +Federate authentication to your organization's identity provider: + +```yaml +dex: + config: + connectors: + - type: oidc + id: corporate-sso + name: "Corporate SSO" + config: + issuer: https://sso.yourcompany.com + clientID: secureguard + clientSecret: $DEX_CLIENT_SECRET + redirectURI: https://dex.secureguard.yourdomain.com/callback +``` + +### Session Security + +The proxy uses HTTP-only, SameSite=Lax session cookies with 8-hour expiry. For enhanced security: + +- Set a strong `SESSION_SECRET` (minimum 32 bytes of cryptographically random data) +- Do not enable "remember me" functionality — this is a secrets dashboard +- Ensure cookies are marked `Secure` (requires HTTPS) + +## RBAC Configuration + +### Authorization is per-user (impersonation) + +SecureGuard does **not** grant its own service account broad access to your secrets. On every Kubernetes API request the proxy impersonates the logged-in user (`Impersonate-User` = email claim, `Impersonate-Group` = groups claim), so **access is decided by the RBAC you bind to your users and OIDC groups**. The proxy's own permissions are limited to impersonation plus a few internal bookkeeping operations. + +{{% notice note %}} +A user with no RBAC bindings can log in but gets `403 Forbidden` on every resource. Grant access explicitly — see [Advanced Configuration → User Authorization]({{< ref "../advanced-configuration/#user-authorization-rbac-via-impersonation" >}}) for group- and email-based binding examples and `kubectl auth can-i --as` verification. +{{% /notice %}} + +### Least-privilege service accounts + +The proxy and the SG Agent run under **separate** service accounts (see [`k8s/rbac.yaml`](https://github.com/kubermatic/secureguard/blob/main/k8s/rbac.yaml) / [`charts/secureguard/templates/rbac.yaml`](https://github.com/kubermatic/secureguard/blob/main/charts/secureguard/templates/rbac.yaml)): + +```yaml +# Proxy: only impersonation + SGAgent registration (per-cluster kubeconfig +# Secret access is a namespaced Role, not shown here). +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: secureguard-proxy +rules: + - apiGroups: [""] + resources: ["users", "groups"] + verbs: ["impersonate"] + - apiGroups: ["agent.secureguard.io"] + resources: ["sgagents"] + verbs: ["create"] +``` + +The `secureguard-agent` account holds the controller/deployer permissions (SGAgent + ESODeployment reconcile, and the Deployments/ServiceAccounts/Namespaces/ClusterRoles/RoleBindings the deployer creates when installing ESO). It does **not** have impersonation rights, and the proxy does **not** have the agent's deploy rights. + +{{% notice note %}} +**Note on impersonation rights:** `impersonate` on `users`/`groups` is a powerful grant — anyone able to use the proxy SA token can act as any user. Protect the proxy pod/SA token accordingly (no token mounting for other workloads, restricted node access), and prefer constraining impersonation to specific users/groups via a `resourceNames` allowlist if your user set is bounded. +{{% /notice %}} + +### Namespace-Scoped Access + +For multi-tenant environments, consider using namespaced Roles instead of ClusterRoles to restrict which namespaces the dashboard can access. + +## Network Policies + +Restrict pod-to-pod communication to only necessary paths: + +```yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: secureguard-proxy +spec: + podSelector: + matchLabels: + app: secureguard-proxy + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app: secureguard-ui + ports: + - port: 3001 + egress: + # Allow access to Kubernetes API server + - to: [] + ports: + - port: 443 + - port: 6443 + # Allow access to Dex + - to: + - podSelector: + matchLabels: + app: dex + ports: + - port: 5556 +``` + +The Helm chart includes a NetworkPolicy template that can be enabled via `networkPolicy.enabled: true` in your values file. + +## Container Security + +### Non-Root Execution + +All SecureGuard containers run as non-root: +- **Dashboard (nginx)**: Runs as the nginx user +- **Proxy**: Uses `gcr.io/distroless/static-debian12:nonroot` (UID 65534) +- **Agent**: Uses `gcr.io/distroless/static-debian12:nonroot` (UID 65534) + +### Read-Only Filesystem + +Configure containers with read-only root filesystems where possible: + +```yaml +securityContext: + readOnlyRootFilesystem: true + runAsNonRoot: true + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL +``` + +### Resource Limits + +Always set resource requests and limits to prevent resource exhaustion: + +```yaml +resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 256Mi +``` + +### Image Pinning + +Pin all base image versions in Dockerfiles. The project uses: +- `node:22-alpine` (not `:latest`) +- `golang:1.25-alpine` (not `:latest`) +- `gcr.io/distroless/static-debian12:nonroot` (specific tag) + +## Content Security Policy + +Configure CSP headers to prevent XSS and data exfiltration. When using nginx for the frontend: + +```nginx +add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; connect-src 'self'; img-src 'self' data:; frame-ancestors 'none';" always; +``` + +## Supply Chain Security + +### Dependency Auditing + +- Run `npm audit` regularly and address critical/high findings before merge +- Run `go vet ./...` on both the proxy and agent modules +- The CI pipeline runs Trivy vulnerability scans on all Docker images + +### Image Scanning + +The CI/CD pipeline scans all three Docker images (dashboard, proxy, agent) with Trivy for CRITICAL and HIGH CVEs before any release. Images are only pushed to the registry if the scan passes. + +## Operational Security + +### Secret Rotation + +- Rotate the `SESSION_SECRET` periodically (this will invalidate all active sessions) +- Rotate OIDC client secrets in coordination with your IDP +- If using per-cluster kubeconfig Secrets, rotate credentials when team members leave + +### Audit Logging + +- OpenBao provides comprehensive audit logging of all secret access +- Enable Kubernetes audit logging to track API server access +- Monitor proxy logs for unusual access patterns or repeated 403 errors + +### Backup and Recovery + +- **Always** take an OpenBao Raft snapshot before upgrades (see [Upgrade Guides]({{< ref "../upgrade-guides/" >}})) +- Store snapshots in encrypted, access-controlled cold storage +- Test restore procedures regularly diff --git a/content/secureguard/main/troubleshooting/_index.en.md b/content/secureguard/main/troubleshooting/_index.en.md new file mode 100644 index 000000000..138ec76eb --- /dev/null +++ b/content/secureguard/main/troubleshooting/_index.en.md @@ -0,0 +1,121 @@ ++++ +title = "Troubleshooting & FAQ" +date = 2026-06-13T09:00:00+02:00 +weight = 11 +description = "Common operational issues and resolutions for SecureGuard — dashboard access, authentication, OpenBao sealing, ESO sync errors, proxy 403s, and multi-cluster connectivity." +sitemapexclude = true +searchexclude = true +private = true ++++ + +Managing a distributed secret synchronization engine involves dealing with network connectivity, authentication tokens, and vault states. This guide covers common operational issues and how to resolve them. + +## General Issues + +### The Dashboard is Inaccessible or Returns 502 Bad Gateway +- **Cause**: The Backend Proxy is not running or cannot reach the upstream Dex/OpenBao instances, OR the ingress path is misconfigured. +- **Resolution**: + 1. Check the proxy logs: `kubectl logs -l app.kubernetes.io/name=secureguard-proxy -n secureguard-system` + 2. Verify Dex is running and reachable by the proxy. + 3. Ensure your Ingress controller is correctly routing traffic to the `secureguard-ui` service. + +### Cannot Log In (Authentication Failed) +- **Cause**: Usually a misconfiguration in the OIDC issuer URL or client secrets. +- **Resolution**: + 1. Check Dex logs: `kubectl logs -l app.kubernetes.io/name=dex -n secureguard-system` + 2. Ensure the `redirect_uri` configured in your IDP (GitHub, Google, etc.) exactly matches the Dex callback URL. + 3. Verify that clock drift hasn't caused token validation failures (ensure NTP is synced across nodes). + +### Proxy Pod Crash-Loops on Startup (`OIDC_ISSUER_URL is required`) +- **Cause**: Authentication is mandatory — the proxy refuses to start without an OIDC issuer. There is no "auth disabled" mode (the old `AUTH_ENABLED` flag was removed). +- **Resolution**: Set `OIDC_ISSUER_URL` (and the other `OIDC_*` / `SESSION_SECRET` env) on the proxy. With the bundled Dex, this is the in-cluster issuer, e.g. `http://-dex..svc.cluster.local:5556/dex`. + +### Logged In, but Everything Shows `403 Forbidden` +- **Cause**: Access is enforced per user — the proxy impersonates the logged-in user, so the request is evaluated against *your* Kubernetes RBAC, not the proxy's. A user with no RBAC bindings can authenticate but is denied every resource. +- **Resolution**: + 1. Bind a Role/ClusterRole to your user's email or an OIDC group — see [User Authorization]({{< ref "../advanced-configuration/#user-authorization-rbac-via-impersonation" >}}) for examples. + 2. Confirm the binding mirrors what the proxy does: + ```bash + kubectl auth can-i list externalsecrets.external-secrets.io \ + --namespace --as --as-group + ``` + 3. Ensure Dex emits a `groups` claim if you bind to groups (group-based RBAC won't work otherwise). + 4. Verify the proxy's own service account holds the `impersonate` verb on `users`/`groups` (included in `k8s/rbac.yaml` and the Helm chart); without it, impersonation itself returns `403`. + +## OpenBao Issues + +### OpenBao Pods are Running but Not Ready +- **Cause**: By default in production, OpenBao starts in a sealed state and cannot read or write data until unsealed. +- **Resolution**: + 1. Check the vault status: + ```bash + kubectl exec -it secureguard-openbao-0 -n secureguard-system -- bao status + ``` + 2. Look for `Sealed: true`. + 3. If you do not have Auto-Unseal configured, you must manually provide the unseal keys generated during initialization: + ```bash + kubectl exec -it secureguard-openbao-0 -n secureguard-system -- bao operator unseal + kubectl exec -it secureguard-openbao-0 -n secureguard-system -- bao operator unseal + kubectl exec -it secureguard-openbao-0 -n secureguard-system -- bao operator unseal + ``` + 4. Once the threshold is met, the pod will become ready. It is strongly recommended to configure [Auto-Unseal]({{< ref "../installation/" >}}) for production. + +## ESO Synchronization Issues + +When an `ExternalSecret` fails to sync, ESO will update the `status` block of the Custom Resource with detailed condition messages. You can view these directly in the SecureGuard UI or via `kubectl`. + +### Error: `SecretStoreNotFound` +- **Cause**: The `ExternalSecret` references a `SecretStore` or `ClusterSecretStore` that does not exist or is in the wrong namespace. +- **Resolution**: Verify the spelling of the `secretStoreRef.name` and the `secretStoreRef.kind` in your `ExternalSecret` manifest. + +### Error: `SecretStoreNotReady` +- **Cause**: ESO cannot authenticate against the OpenBao backend defined in the `SecretStore`. +- **Resolution**: + 1. This usually indicates an authentication failure (e.g., the Kubernetes Service Account token used by ESO is rejected by OpenBao). + 2. Check the OpenBao auth logs. Ensure the `kubernetes` auth method is enabled on OpenBao and that the role bound to the ESO service account exists and has the correct policies attached. + 3. Verify TLS certificates. If OpenBao uses a self-signed cert, the `SecretStore` must contain the `caBundle`. + +### Error: `SecretSyncedError` +- **Cause**: ESO authenticated successfully, but could not retrieve the specific secret value. +- **Resolution**: + 1. The secret path defined in `data[].remoteRef.key` does not exist in OpenBao. + 2. The OpenBao policy attached to the authentication role does not grant `read` access to that specific path. + 3. Verify the path manually using the OpenBao CLI or UI using the same credentials. + +## Proxy Issues + +### Error: 403 Forbidden from the Proxy +- **Cause**: The requested Kubernetes API path is not in the proxy's route allowlist. +- **Resolution**: + 1. The proxy only forwards explicitly listed K8s API paths. Check the allowlist in `proxy/internal/proxy/routes.go`. + 2. If you've added a new CRD and need API access, add the corresponding path patterns to `routes.go`. + 3. Common mistake: forgetting to add both list and individual resource paths (e.g., both `/apis/group/version/resources` and `/apis/group/version/namespaces/{ns}/resources/{name}`). + +### Proxy Cannot Connect to Kubernetes API +- **Cause**: Invalid or expired kubeconfig, or the cluster is unreachable. +- **Resolution**: + 1. Check proxy logs: `kubectl logs -l app.kubernetes.io/name=secureguard-proxy -n secureguard-system` + 2. Verify the `KUBECONFIG` environment variable points to a valid kubeconfig file. + 3. For in-cluster deployments, ensure the ServiceAccount has the correct RBAC permissions (see `k8s/rbac.yaml`). + +## Multi-Cluster Issues + +### Cluster Shows as "Unhealthy" in the Dashboard +- **Cause**: The proxy cannot reach the target cluster's API server. +- **Resolution**: + 1. Verify network connectivity between the management cluster and the target cluster. + 2. Check that the kubeconfig context for the target cluster has valid, non-expired credentials. + 3. Use the health endpoint directly: `curl http://localhost:3001/api/clusters/{id}/health` + +### Uploaded Kubeconfig Not Showing Clusters +- **Cause**: The kubeconfig upload succeeded but clusters aren't appearing. +- **Resolution**: + 1. Verify the upload response included the expected context names. + 2. Check proxy logs for errors during per-cluster Secret creation. + 3. If using init container mode, the proxy pod may need a restart to pick up new Secrets. + +## Debugging Tips + +- **Event Stream**: Use the Event Stream page in the dashboard to see real-time Kubernetes events across all managed resources. +- **Proxy Debug Logging**: Set the proxy's log level to debug for verbose output. The proxy uses Go's standard `log` package — check pod logs with `kubectl logs`. +- **Sync Error Drawer**: Click on any failed ExternalSecret in the dashboard to open the Sync Error Drawer, which shows detailed error messages and remediation hints. diff --git a/content/secureguard/main/upgrade-guides/_index.en.md b/content/secureguard/main/upgrade-guides/_index.en.md new file mode 100644 index 000000000..7248f9030 --- /dev/null +++ b/content/secureguard/main/upgrade-guides/_index.en.md @@ -0,0 +1,83 @@ ++++ +title = "Upgrade Guides" +date = 2026-06-13T09:00:00+02:00 +weight = 10 +description = "Best practices for upgrading SecureGuard components — OpenBao snapshots, ESO CRD upgrades, and rolling updates for the UI, proxy, and SG Agent." +sitemapexclude = true +searchexclude = true +private = true ++++ + +Keeping SecureGuard updated ensures you have the latest security patches for OpenBao, Dex, ESO, and the UI components. This guide outlines the best practices for upgrading a production deployment. + +## General Upgrade Path + +Because SecureGuard is deployed via Helm, upgrades follow the standard Helm release lifecycle. + +{{% notice note %}} +OCI-based Helm charts do not require `helm repo add` or `helm repo update`. Simply run `helm upgrade` with the registry URL. +{{% /notice %}} + +1. **Review the Release Notes** in the SecureGuard repository for any breaking changes or manual migration steps. +2. **Execute the Upgrade**, passing your custom `values-production.yaml` file to preserve your HA, ingress, and IDP configurations. + ```bash + helm upgrade secureguard oci://quay.io/kubermatic/helm-charts/secureguard \ + -f values-production.yaml \ + --namespace secureguard-system + ``` + +## Pre-Upgrade Requirement: OpenBao Backups (Snapshots) + +{{% notice warning %}} +**CRITICAL:** Before performing any upgrade, especially one that bumps the OpenBao major/minor version, you MUST take a snapshot of the OpenBao integrated storage (Raft). Failure to do so risks catastrophic data loss if the upgrade fails. +{{% /notice %}} + +1. Authenticate to OpenBao with a privileged token: + ```bash + kubectl exec -it secureguard-openbao-0 -n secureguard-system -- /bin/sh + bao login + ``` + +2. Trigger the snapshot and save it locally: + ```bash + bao operator raft snapshot save /tmp/backup.snap + ``` + +3. Copy the snapshot out of the pod to secure cold storage: + ```bash + kubectl cp secureguard-system/secureguard-openbao-0:/tmp/backup.snap ./vault_backup_$(date +%F).snap + ``` + +## Component Specifics + +### Upgrading OpenBao + +When Helm updates the OpenBao StatefulSet image version, Kubernetes will perform a rolling restart of the OpenBao pods. + +* **HA Clusters (Raft)**: The rolling restart must be monitored carefully. When a pod restarts, it will trigger leader election. Ensure that the cluster remains quorate during the roll. +* **Auto-Unseal**: If you have configured Auto-Unseal (e.g., AWS KMS), the pods will automatically unseal and rejoin the cluster upon starting. +* **Manual Unseal**: If you are *not* using Auto-Unseal, the pods will restart into a `Sealed` state. You must manually execute the `bao operator unseal` commands on each newly started pod *before* the StatefulSet controller proceeds to restart the next pod. This requires active operator intervention during the `helm upgrade`. + +### Upgrading the External Secrets Operator (ESO) CRDs + +Helm does not automatically upgrade Custom Resource Definitions (CRDs) during a `helm upgrade` command to prevent accidental deletion of stored data. + +If a new version of SecureGuard bumps the ESO version and introduces new CRD fields, you must manually apply the updated CRDs before upgrading the Helm chart. + +1. Download the new CRD manifests corresponding to the ESO version packaged in the new chart. +2. Apply them directly: + ```bash + kubectl apply -f https://raw.githubusercontent.com/external-secrets/external-secrets/v/deploy/crds/bundle.yaml + ``` +3. Make the new release selectable in the dashboard by adding it to the **ESO + Version Catalog**: apply a new `ESOVersion` CR (and move the `latest: true` + flag onto it). See [Advanced Configuration → ESO Version Catalog]({{< ref "../advanced-configuration/#eso-version-catalog-esoversion-crd" >}}). + Only ESO `v2.0.0` and newer are supported. + +### Upgrading the SecureGuard UI and Proxy + +The React UI and **Go proxy** are stateless applications deployed as standard Kubernetes Deployments. Helm will perform a standard rolling rollout of the new ReplicaSets, ensuring zero-downtime availability of the dashboard during the upgrade. + +### Upgrading the SG Agent Controller + +The SG Agent Controller (`agent/`) is a Go controller-runtime binary deployed as a Kubernetes Deployment. It follows the same rolling update pattern as the proxy — Helm will perform a standard rolling rollout of the new ReplicaSet. No special migration steps are required; the controller is stateless and will automatically resume reconciliation of SGAgent and ESODeployment resources after restart. diff --git a/content/secureguard/main/user-guide/_index.en.md b/content/secureguard/main/user-guide/_index.en.md new file mode 100644 index 000000000..bd7da4fe3 --- /dev/null +++ b/content/secureguard/main/user-guide/_index.en.md @@ -0,0 +1,241 @@ ++++ +title = "User Guide" +date = 2026-06-13T09:00:00+02:00 +weight = 6 +description = "A feature-by-feature tour of the SecureGuard dashboard — managing ExternalSecrets, stores, push secrets, clusters, and federation from a read-mostly control room." +sitemapexclude = true +searchexclude = true +private = true ++++ + +This guide walks you through the key features of the SecureGuard dashboard. It assumes you have a running deployment (see [Getting Started]({{< ref "../getting-started/" >}})) and are logged in. + +## What the Dashboard Can (and Can't) Do + +SecureGuard is, by design, **mostly a read-only "control room."** It is built to +give you safe **visibility** into your secret-syncing setup, plus a few safe +**day-2 operations** — without ever exposing secret values and without turning +the browser into a general-purpose Kubernetes editor. + +Most ESO resources (ExternalSecrets, SecretStores, PushSecrets, ReloaderConfigs) +are **created and edited outside the dashboard** — with `kubectl` or your GitOps +tool (Argo CD, Flux) — so they live in version control and code review. The +dashboard then lets you watch, troubleshoot, and run targeted actions on them. + +| Resource | View | Create | Edit | Delete | Other actions | +|---|:---:|:---:|:---:|:---:|---| +| **ExternalSecret** | ✅ | — | — | ✅ | **Sync Now** (force a refresh) | +| **SecretStore / ClusterSecretStore** | ✅ | — | — | — | — | +| **PushSecret** | ✅ | — | — | ✅ | — | +| **Kubernetes Secret** | ✅ (masked) | — | — | — | — | +| **ReloaderConfig** | ✅ | — | — | ✅ | — | +| **ESODeployment** | ✅ | ✅ | ✅ | ✅ | Guided create/edit form | +| **Cluster** | ✅ | ✅ (upload kubeconfig) | — | ✅ | Health check | +| **Federation (Server / Authorization)** | ✅ | — | — | — | — | + +{{% notice note %}} +**Why so read-only?** Each thing the browser can do must be explicitly allowed by the proxy's [route allowlist](https://github.com/kubermatic/secureguard/blob/main/docs/api-reference.md#route-allowlist). Keeping the surface small is a deliberate security choice — see [Architecture → Security Model]({{< ref "../architecture/#the-security-model" >}}). To create the resources marked "—" above, use `kubectl apply` or GitOps. +{{% /notice %}} + +## Dashboard Overview + +The Dashboard page provides an at-a-glance view of your secrets management posture: + +- **Sync Status Breakdown** — How many ExternalSecrets are synced, pending, or errored +- **Provider Distribution** — Which external secret providers are in use across your stores +- **Recent Sync Errors** — The latest failures with direct links to affected resources +- **Namespace Breakdown** — Resource distribution across namespaces + +## Managing ExternalSecrets + +### Viewing ExternalSecrets + +Navigate to **External Secrets** in the sidebar. The table shows all ExternalSecrets across your selected namespace(s) with: + +- **Name** and **Namespace** +- **Sync Status** — `Synced` (green), `Pending` (amber), or `Error` (red) +- **Secret Store** — Which SecretStore or ClusterSecretStore the secret references +- **Last Synced** — Timestamp of the most recent successful sync +- **Refresh Interval** — How often ESO polls the external provider + +Click any row to open the detail view, which includes: +- Full resource metadata +- Status conditions with timestamps +- Sync history timeline +- The complete resource as **read-only YAML** (you can copy it, but not edit it here) +- The target Kubernetes Secret (with values displayed as `••••••••`) + +### Creating an ExternalSecret + +ExternalSecrets are **not** created from the dashboard. Create them with +`kubectl` or your GitOps tool so they stay in version control. A minimal example: + +```yaml +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: db-creds + namespace: default +spec: + refreshInterval: 1h + secretStoreRef: + name: my-store # an existing SecretStore/ClusterSecretStore + kind: SecretStore + target: + name: db-creds # the Kubernetes Secret ESO will create + data: + - secretKey: password # key in the created Secret + remoteRef: + key: secret/db/postgres # path in the provider + property: password # field at that path +``` + +```bash +kubectl apply -f db-creds-externalsecret.yaml +``` + +Within a few seconds it appears in the dashboard's **External Secrets** list, +where you can watch it sync. (See [ESO Basics]({{< ref "../eso-basics/" >}}) for what each +field means, and the [Glossary]({{< ref "../glossary/" >}}) for the terms.) + +### Day-2 Actions: Sync Now and Delete + +From an ExternalSecret's detail view you can: + +- **Sync Now** — Force ESO to re-fetch from the provider immediately instead of + waiting for the next refresh interval. (Technically, the dashboard adds a + `force-sync` annotation; ESO reacts to it.) Use this after rotating a value in + the provider and wanting it reflected right away. +- **Delete** — Remove the ExternalSecret. A confirmation dialog prevents + accidental deletion. Editing an existing ExternalSecret is done via `kubectl` + / GitOps, then re-viewed here. + +## Managing SecretStores + +SecretStores define **how** to connect to an external secret provider (OpenBao, AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, etc.). + +### SecretStore vs. ClusterSecretStore + +- **SecretStore** — Namespaced. Only ExternalSecrets in the same namespace can reference it. +- **ClusterSecretStore** — Cluster-scoped. ExternalSecrets in any namespace can reference it. + +Both types are managed from the **Secret Stores** page. Use the tab selector to switch between the two. + +### Viewing a SecretStore + +SecretStores are **view-only** in the dashboard — create and edit them with +`kubectl` or GitOps. The list shows each store's provider and its `Ready` +status; the detail view shows the full configuration as read-only YAML and the +store's status conditions. + +When you look at a store's configuration, note that **credential fields are +references, not raw values**: a store points to an existing Kubernetes Secret +(by name and key) that holds the actual provider credentials — the dashboard +never shows or accepts the credentials themselves. + +## Managing PushSecrets + +PushSecrets are the reverse flow — they push Kubernetes Secrets **upstream** to an external provider. + +On the **Push Secrets** page you can **view** all PushSecrets and their sync +status, open a detail view, and **delete** a PushSecret. Creating a PushSecret is +done with `kubectl` or GitOps (the dashboard has no create form). A PushSecret +specifies the source Kubernetes Secret, the target SecretStore, the destination +path in the provider, and which keys to push. + +## Viewing Kubernetes Secrets + +The **Secrets** page lists all Kubernetes Secrets in the selected namespace(s). For each secret: + +- **ESO-managed** secrets are clearly tagged, showing which ExternalSecret created them +- **Secret values are never displayed** — all value fields show `••••••••` +- **Key names** are visible, allowing you to verify the correct keys exist +- **Stale detection** — Secrets that haven't been refreshed within their expected interval are flagged + +This page is view-only — Kubernetes Secrets are created and updated by ESO (from +your ExternalSecrets), not edited by hand in the dashboard. + +## ReloaderConfigs + +ReloaderConfigs trigger automatic workload restarts when synced secrets change. +On the **Reloaders** page you can **view** all ReloaderConfigs and their status, +and **delete** one. They are created with `kubectl` / GitOps and specify: + +- **Target workload** (Deployment, StatefulSet, or DaemonSet) +- **Secret references** to watch +- **Reload strategy** (rolling restart vs. annotation bump) + +## ESODeployments + +ESODeployments manage the ESO operator lifecycle across clusters: + +1. Navigate to **ESO Deployments** +2. View the status of ESO installations across your connected clusters +3. Create or update ESODeployments to install, upgrade, or remove ESO from target clusters +4. Monitor deployment phases: `Deploying`, `Running`, `Upgrading`, `Error` + +## Federation + +The **Federation** page provides read-only visibility into cross-cluster secret +distribution — letting a central SecureGuard instance serve secret data to many +clusters without exposing the backend stores. It has two tabs: + +- **Servers** — `FederationServer` resources, which declare what the broker + exposes (backend stores) and which token issuers it trusts, with Ready status. +- **Authorizations** — `FederationAuthorization` policies, which grant a specific + remote identity read access to specific stores and key globs (deny-by-default). + +These resources carry **references and policy only — never secret values**. +Secret serving happens in the separate federation broker, not in the dashboard +proxy. Federation is opt-in and disabled by default. For setup, the broker, the +`fedclient` consumer, and resolution modes, see the [Federation guide]({{< ref "../federation/" >}}). + +## Event Stream + +The **Event Stream** page shows real-time Kubernetes events related to ESO resources. Use it for: + +- Monitoring sync activity across all ExternalSecrets +- Spotting error patterns in real-time +- Debugging failed syncs without needing `kubectl` + +## Relationship Visualization + +The **Visualization** page renders an interactive graph showing relationships between: + +- ExternalSecrets → SecretStores / ClusterSecretStores +- ExternalSecrets → target Kubernetes Secrets +- PushSecrets → source Kubernetes Secrets + +Click any node to navigate to its detail page. Use the controls to zoom, pan, and re-layout the graph. + +## Multi-Cluster Management + +### Cluster Selector + +The **cluster selector** in the top bar lets you scope the view to a specific cluster or view resources across all clusters. + +### Adding Clusters + +1. Navigate to **Clusters** (the Cluster Management page) +2. Click **Upload Kubeconfig** and select a kubeconfig file +3. Each context in the kubeconfig becomes a new cluster +4. Optionally register an SGAgent for the cluster + +### Cluster Health + +The Cluster Management page shows the health status of all connected clusters. Unhealthy clusters are flagged with the reason (unreachable, auth expired, etc.). + +## Namespace Filtering + +The **namespace selector** in the top bar filters all views by namespace. Select "All Namespaces" to see resources across the entire cluster. + +The selected namespace is persisted in the URL (`?namespace=...`), making it shareable and bookmarkable. + +## Understanding the Security Model + +As you use the dashboard, keep these security principles in mind: + +- **Secret values are never visible** — the proxy redacts all secret data before it reaches your browser. There is no "reveal" button because there is nothing to reveal. +- **No secret data in browser storage** — values are not stored in URL parameters, browser history, local storage, or any cache. +- **All changes go through the proxy** — the browser never contacts the Kubernetes API server directly. The proxy enforces route allowlisting and secret redaction. +- **Credential fields are references** — when creating SecretStores, you provide the *name* of a Kubernetes Secret containing credentials, not the credentials themselves. From f2c57d9f8eb68b2d8b3aec129a5e04abfe795884 Mon Sep 17 00:00:00 2001 From: Sergey Furtak Date: Thu, 25 Jun 2026 23:04:17 +0300 Subject: [PATCH 3/5] try to bump preview Updated the date for the Advanced Configuration page. Signed-off-by: Sergey Furtak --- content/secureguard/main/advanced-configuration/_index.en.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/secureguard/main/advanced-configuration/_index.en.md b/content/secureguard/main/advanced-configuration/_index.en.md index 0042c0fb1..d948bffc6 100644 --- a/content/secureguard/main/advanced-configuration/_index.en.md +++ b/content/secureguard/main/advanced-configuration/_index.en.md @@ -1,6 +1,6 @@ +++ title = "Advanced Configuration" -date = 2026-06-13T09:00:00+02:00 +date = 2026-06-13T09:00:01+02:00 weight = 7 description = "Enterprise configuration for SecureGuard — custom OIDC identity providers, RBAC via impersonation, multi-cluster deployments, the ESO version catalog, and short-lived remote tokens." sitemapexclude = true From cc3d3717a237f76775460cbe3c6bcfb4835e74a3 Mon Sep 17 00:00:00 2001 From: Sergey Furtak Date: Thu, 25 Jun 2026 23:12:28 +0300 Subject: [PATCH 4/5] Change date in advanced configuration metadata Updated the date for the Advanced Configuration page. Signed-off-by: Sergey Furtak --- content/secureguard/main/advanced-configuration/_index.en.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/secureguard/main/advanced-configuration/_index.en.md b/content/secureguard/main/advanced-configuration/_index.en.md index d948bffc6..92a9516bd 100644 --- a/content/secureguard/main/advanced-configuration/_index.en.md +++ b/content/secureguard/main/advanced-configuration/_index.en.md @@ -1,6 +1,6 @@ +++ title = "Advanced Configuration" -date = 2026-06-13T09:00:01+02:00 +date = 2026-06-13T09:00:02+02:00 weight = 7 description = "Enterprise configuration for SecureGuard — custom OIDC identity providers, RBAC via impersonation, multi-cluster deployments, the ESO version catalog, and short-lived remote tokens." sitemapexclude = true From ede0798e4080b559014816249256739912887e66 Mon Sep 17 00:00:00 2001 From: Sergey Furtak Date: Fri, 26 Jun 2026 11:25:24 +0300 Subject: [PATCH 5/5] bump preview Signed-off-by: Sergey Furtak --- content/secureguard/main/advanced-configuration/_index.en.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/secureguard/main/advanced-configuration/_index.en.md b/content/secureguard/main/advanced-configuration/_index.en.md index 92a9516bd..488002e76 100644 --- a/content/secureguard/main/advanced-configuration/_index.en.md +++ b/content/secureguard/main/advanced-configuration/_index.en.md @@ -1,6 +1,6 @@ +++ title = "Advanced Configuration" -date = 2026-06-13T09:00:02+02:00 +date = 2026-06-13T09:00:03+02:00 weight = 7 description = "Enterprise configuration for SecureGuard — custom OIDC identity providers, RBAC via impersonation, multi-cluster deployments, the ESO version catalog, and short-lived remote tokens." sitemapexclude = true