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.
SecureGuard operates on a zero-knowledge principle for secret values:
.data and .stringData) before responses reach the browser•••••••• unconditionally — there is no “reveal” mechanismAlways terminate TLS at the ingress layer — base ingress configuration is covered in Installation → Ingress & TLS. For hardening, additionally force HTTPS redirects so no session cookie ever travels over plaintext:
ingress:
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
Apply the same to dexIngress and openbaoIngress if you expose Dex or the OpenBao UI.
For environments requiring end-to-end encryption:
caBundle field in SecretStore resources when OpenBao uses a private CAThe 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 <release>-dex-admin Secret — there is no hardcoded password in the Helm path. Retrieve it with:
kubectl get secret <release>-dex-admin -n <namespace> -o jsonpath='{.data.password}' | base64 -d
Local development and CI only: the raw dev manifests in the source repository (k8s/dex.yaml) hardcode 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 and are never used by the Helm chart. Do not deploy the raw dev manifests to any shared or production cluster.
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).
Federate authentication to your organization’s identity provider:
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
The proxy uses HTTP-only, SameSite=Lax session cookies with 8-hour expiry. For enhanced security:
SESSION_SECRET (minimum 32 bytes of cryptographically random data)Secure (requires HTTPS)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.
A user with no RBAC bindings can log in but gets 403 Forbidden on every resource. Grant access explicitly — see Advanced Configuration → User Authorization for group- and email-based binding examples and kubectl auth can-i --as verification.
The proxy and the SG Agent run under separate service accounts, provisioned by the Helm chart’s RBAC templates:
# 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.
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.
For multi-tenant environments, consider using namespaced Roles instead of ClusterRoles to restrict which namespaces the dashboard can access.
Restrict pod-to-pod communication to only necessary paths:
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.
All SecureGuard containers run as non-root:
gcr.io/distroless/static-debian12:nonroot (UID 65534)gcr.io/distroless/static-debian12:nonroot (UID 65534)Configure containers with read-only root filesystems where possible:
securityContext:
readOnlyRootFilesystem: true
runAsNonRoot: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
Always set resource requests and limits to prevent resource exhaustion — see Installation → Resource Limits for the per-component values.
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)Configure CSP headers to prevent XSS and data exfiltration. When using nginx for the frontend:
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;
The Google Fonts entries exist for the default typography. For air-gapped or maximally locked-down deployments, self-host the fonts and drop the external fonts.googleapis.com / fonts.gstatic.com sources from the policy.
npm audit regularly and address critical/high findings before mergego vet ./... on both the proxy and agent modulesThe 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.
SESSION_SECRET periodically (this will invalidate all active sessions)