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), ESO, and Reloader (event-driven rotation — trigger Deployment rollouts or ESO reconciles from Secret/ConfigMap changes, cloud events, or webhooks) as optional Helm sub-charts. Each component can be toggled independently via the Helm values file (openbao.enabled, dex.enabled, eso.enabled, reloader.enabled).
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 SecretStores at your provider. Dex is similarly optional if you already run an OIDC provider.
A local kubectl + helm v3 CLI and a Kubernetes cluster (v1.27 or newer
recommended) are the minimum. For any non-local install you also need the
platform pieces below — a bare cluster is not enough to reach a working,
TLS-terminated login.
kubectl and helm v3 CLIs installed and pointed at the cluster.ImagePullBackOff and kubectl describe pod shows 401 Unauthorized. Create
a docker-registry Secret and reference it:kubectl create secret docker-registry secureguard-pull \
--namespace secureguard-system \
--docker-server=quay.io \
--docker-username='<robot-user>' \
--docker-password='<robot-token>'
# values.yaml — reaches the first-party pods (and, via global, OpenBao + ESO)
imagePullSecrets:
- name: secureguard-pull
global:
imagePullSecrets:
- name: secureguard-pull
values.yaml
(imagePullSecrets, global.imagePullSecrets, and per–sub-chart
dex.imagePullSecrets / reloader.imagePullSecrets) for mirrored/air-gapped
registries.IngressClass (e.g. ingress-nginx). Set
ingress.className to a class that exists in the cluster — the chart does not
install a controller for you.ClusterIssuer (or a pre-created TLS Secret). The
ingress examples below reference cert-manager.io/cluster-issuer; that issuer
must already exist, or certificates never get issued and the browser sees TLS
errors./dex on the dashboard
host and OpenBao on its own hostname, so a wildcard (e.g.
*.secureguard.example.com) is the simplest fit. See
Install Order (DNS & TLS) for the exact sequence.StorageClass (or an explicit openbao.server.dataStorage.storageClass).
Production OpenBao runs as a Raft HA cluster and each replica needs a
PersistentVolume; without a usable StorageClass the OpenBao pods stay
Pending.gp2/gp3
with the default provisioner settings) do not support online volume
expansion, so a PVC created too small cannot be grown in place later — pick a
headroom-generous openbao.server.dataStorage.size at install time, or use a
StorageClass with allowVolumeExpansion: true.SecureGuard’s Helm chart bundles all necessary Custom Resource Definitions (CRDs) and sub-chart dependencies (OpenBao, Dex, ESO, Reloader).
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.
helm install secureguard oci://quay.io/kubermatic/helm-charts/secureguard \
--namespace secureguard-system \
--create-namespace
operator init/unseal step is required (see Self-Initialization).:30820/ui (NodePort) or via Ingress.You don’t have to use the bundled OpenBao. SecureGuard manages ESO, and ESO can
talk to any of its supported providers.
Disable the bundled OpenBao and point your SecretStore/ClusterSecretStore
resources at your own backend.
Example — an existing HashiCorp Vault or OpenBao cluster:
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):
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.
If your target clusters already have the External Secrets Operator installed and managed by another platform team, disable the bundled ESO sub-chart. The dashboard and proxy still work with the existing ESO installation — SecureGuard simply skips installing its own.
helm install secureguard oci://quay.io/kubermatic/helm-charts/secureguard \
--namespace secureguard-system \
--create-namespace \
--set eso.enabled=false
Note: This only disables the ESO sub-chart. Dex and OpenBao still deploy according to their own toggles (dex.enabled, openbao.enabled) — combine flags as needed for your environment. The SG Agent also auto-discovers externally installed ESO instances on managed clusters and surfaces them read-only in the dashboard.
Beyond installing the components, the chart performs several pieces of automation worth knowing about:
| Component / behaviour | Value | Default |
|---|---|---|
| Dashboard UI + backend proxy | (always deployed) | — |
| Dex OIDC provider | dex.enabled | true |
| OpenBao | openbao.enabled | true |
| External Secrets Operator | eso.enabled | true |
| Reloader (event-driven rotation) | reloader.enabled | false |
| SG Agent Controller (multi-cluster ESO lifecycle, heartbeats) | sgAgent.enabled | false |
| Federation broker (cross-cluster secret serving) | federation.enabled | false |
The SG Agent and Federation are opt-in. Without sgAgent.enabled=true there are no ESODeployment reconciliation, heartbeats, or ESO auto-discovery — the related dashboard pages stay empty. See Multi-Cluster Deployments and the Federation guide.
Automatic wiring (each can be disabled):
auth.sessionSecret is auto-generated on first install and kept stable across upgrades if left empty. Set it explicitly when running multiple releases that must share sessions.openbao.init.enabled, default true) — an ordered post-install/upgrade hook Job (weight 0) runs operator init, unseals every Raft node, enables the Kubernetes auth method, and creates a chart admin role — then revokes the root token (it is never persisted). The Shamir unseal key shares are stored in the <release>-openbao-keys Secret. See Self-Initialization.openbao.kubernetesAuth.enabled, default true) — a post-install Job (weight 5) configures the KV v2 engine, the ESO read policy, and the eso-role bound to the ESO ServiceAccount. Under self-init it authenticates via the chart admin role (no root token exists).openbao.oidc.enabled, default true) — a post-install Job (weight 10) configures OpenBao’s OIDC auth method so users can log in to the OpenBao UI with the same Dex identity, also via the chart admin role.eso.vaultSecretStore.enabled, default true) — when both ESO and OpenBao are enabled, the chart creates a ready-to-use ClusterSecretStore named openbao-backend pointing at the bundled OpenBao (KV v2).serviceAccount.tokenExpirationSeconds (default 3600) controls the lifetime of the proxy/agent tokens; the kubelet rotates them automatically.On a cloud cluster the ingress load-balancer address only exists after the chart is installed, and cert-manager can only issue a certificate once DNS resolves to that address. Follow this order for a first install with ingress + TLS enabled:
kubectl get ingress -n secureguard-system
kubectl get svc -n <ingress-controller-namespace> \
-o jsonpath='{.items[*].status.loadBalancer.ingress[*].hostname}{"\n"}'
*.secureguard.example.com)
pointing at that hostname/IP. Wait for it to propagate.secureguard-tls. Once DNS resolves, the ACME
HTTP-01/DNS-01 challenge completes and the certificate Secret becomes Ready:kubectl get certificate -n secureguard-system
https://secureguard.example.com.No component restart is required between these steps. The proxy performs OIDC
discovery against Dex with automatic retry/back-off, so it self-heals once
the certificate is issued and Dex becomes reachable over HTTPS — you do not need
to restart the proxy after secureguard-tls goes Ready.
By default OpenBao runs as a 3-node Raft HA cluster that the chart initializes and unseals for you. For local development or testing you usually don’t want three replicas or persistent state — enable dev mode instead, where OpenBao runs as a single in-memory node that is automatically unsealed but loses all data on restart:
helm install secureguard oci://quay.io/kubermatic/helm-charts/secureguard \
--namespace secureguard-system \
--create-namespace \
--set openbao.server.dev.enabled=true
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.
If you want persistent storage but only a single node (e.g. a small staging cluster), run the standalone file backend instead of Raft HA:
helm install secureguard oci://quay.io/kubermatic/helm-charts/secureguard \
--namespace secureguard-system \
--create-namespace \
--set openbao.server.standalone.enabled=true \
--set openbao.server.ha.enabled=false
Node sizing. Even the default (non-dev) install schedules a fair number of
pods — dashboard + proxy, Dex, three OpenBao replicas, and the ESO
controller/webhook/cert-controller — plus the init/config hook Jobs. For a
local kind/minikube walkthrough, prefer dev mode (single in-memory OpenBao,
no PVCs) on a node with at least ~4 vCPU / 8 GiB free; the full Raft HA default
additionally needs a working StorageClass and enough headroom for three
persistent OpenBao replicas.
When moving to production, several configurations MUST be applied to ensure a secure, resilient platform. This section covers the availability-oriented settings; for the full security checklist (authentication, RBAC, container security, CSP), work through the Security Hardening guide.
High Availability with Raft integrated storage is the chart default: OpenBao runs as a 3-node cluster, each replica keeping its own copy of the data (no external Consul/etcd needed). Use an odd replica count (3 or 5) so leader elections always have a quorum, and size the data storage for your secret volume.
openbao:
server:
ha:
enabled: true
replicas: 3 # odd number for quorum
raft:
enabled: true
dataStorage:
enabled: true
size: 20Gi
A freshly deployed OpenBao is sealed and must be initialized and unsealed before it can serve secrets. The chart automates this so no manual operator init/unseal is required (openbao.init.enabled, default true):
operator init on the leader, unseals every Raft node, bootstraps Kubernetes auth + a chart admin role, and then revokes the root token — it is used once in memory and never persisted.<release>-openbao-keys Secret (keyShares: 5, keyThreshold: 3 by default).Back up the <release>-openbao-keys Secret — immediately after the first install, as a day-one step. Without the unseal keys you cannot unseal OpenBao and your secrets are unrecoverable. Copy it out of the cluster to your organization’s secrets manager / offline escrow (do not leave the only copy in the same cluster it protects), then verify you can read it back:
kubectl get secret <release>-openbao-keys -n secureguard-system -o yaml > openbao-keys.backup.yaml
For break-glass admin access (the root token is revoked after init and never persisted), run bao operator generate-root using the stored key shares.
openbao:
init:
enabled: true # self-init + unseal (default)
keyShares: 5
keyThreshold: 3
# secretName: "" # defaults to <release>-openbao-keys
Restart behavior (Shamir seal). With the default Shamir seal, a pod that restarts comes back sealed. The init hook re-unseals every node on each helm upgrade; to also re-unseal automatically between upgrades, enable the periodic sweep:
openbao:
init:
unsealCronJob:
enabled: true
schedule: "*/5 * * * *"
For the strongest posture — survives restarts with no unseal key shares stored in a Secret — use KMS auto-unseal instead of Shamir self-init. Disable the init Job and add a seal stanza to the Raft config so OpenBao unseals itself against your cloud KMS on every start:
openbao:
server:
ha:
enabled: true
replicas: 3
raft:
enabled: true
config: |
ui = true
listener "tcp" { tls_disable = 1
address = "[::]:8200" cluster_address = "[::]:8201" }
storage "raft" { path = "/openbao/data"
retry_join { leader_api_addr = "http://<rel>-openbao-0.<rel>-openbao-internal:8200" }
retry_join { leader_api_addr = "http://<rel>-openbao-1.<rel>-openbao-internal:8200" }
retry_join { leader_api_addr = "http://<rel>-openbao-2.<rel>-openbao-internal:8200" } }
seal "awskms" {
region = "eu-west-1"
kms_key_id = "alias/openbao-unseal"
}
service_registration "kubernetes" {}
init:
enabled: false # KMS unseals automatically; no self-init Job or key-shares Secret
OpenBao also supports gcpckms, azurekeyvault, and transit seal types — swap the seal stanza accordingly.
Ensure that traffic to the SecureGuard dashboard, the proxy, and OpenBao is encrypted via TLS. The chart provides three independent ingress blocks: ingress (dashboard + proxy), dexIngress (Dex, exposed at /dex on the dashboard host), and openbaoIngress (OpenBao UI/API on its own hostname). Configure Ingress annotations to use a tool like cert-manager for automatic certificate provisioning.
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
The chart ships a values-production.yaml with a production-oriented starting point: 2 replicas, ingress + TLS enabled, NetworkPolicies and PodDisruptionBudgets on, OpenBao with persistent data/audit storage, and required-secret placeholders (auth.sessionSecret, auth.oidc.clientSecret). Use it as the base for your own values file.
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. For lighter-weight, policy-level isolation within a single instance, OpenBao’s namespace feature is an alternative — see OpenBao Basics.
The bundled OpenBao ships with its web UI enabled. In production, either serve it only via an authenticated, TLS-terminated ingress (openbaoIngress) or disable it entirely:
openbao:
ui:
enabled: false
The SecureGuard Helm chart includes templates for RBAC, network policies, and resource limits. These are critical for production deployments.
The chart provisions a dedicated ServiceAccount, ClusterRole, and ClusterRoleBinding for each component. The proxy’s ClusterRole deliberately grants no standing read access to ExternalSecrets, SecretStores, or Secrets: every Kubernetes API request is impersonated as the logged-in user, so what each user can see and do is governed by the RBAC bound to their user/groups — not by the proxy’s own permissions. The proxy itself holds only the impersonate verb plus narrow bookkeeping permissions (SGAgent registration and per-cluster kubeconfig Secret access). See User Authorization and RBAC Configuration.
serviceAccount:
create: true
# Set to false and provide `name` if you manage the ServiceAccount externally
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:
networkPolicy:
enabled: true
# Restricts proxy ingress to the Ingress controller
# Restricts proxy egress to the Kubernetes API server and OpenBao
For the full policy details and a custom-policy example, see Security Hardening → Network Policies.
Always set resource requests and limits for all SecureGuard components in production. The chart exposes these under each component’s resources key:
proxy:
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
ui:
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi