Advanced Configuration

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.

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.

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.

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 already include this rule:
    - 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:

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:

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:

# 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:

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

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.

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.

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.0v2.6.0) ships in k8s/samples/eso-deployments/esoversions.yaml, and the CRD lives in 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:
    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:

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 for details.

Proxy Configuration

The backend proxy is configured via environment variables. See the API Reference — 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 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):

proxy:
  remoteTokenDir: /var/run/secureguard/tokens

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.

RBAC and Network Policies

The Helm chart includes templates for RBAC, NetworkPolicies, and PodDisruptionBudgets. Enable them in your values file:

# 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.

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.