Overview
The External Secrets integration allows vCluster to leverage an existing External Secrets Operator (ESO) installation running on the host cluster. This enables virtual clusters to fetch secrets from external secret management systems without deploying ESO separately in each virtual cluster.
Key Benefits:
- Reuse host cluster’s External Secrets Operator installation
- Reduce resource overhead by sharing ESO infrastructure
- Access external secret stores (AWS, Azure, GCP, Vault, etc.) from virtual clusters
- Centralized secret management across multiple virtual clusters
The External Secrets integration is not supported in private nodes mode. It requires the shared or dedicated nodes architecture.
How It Works
The integration syncs External Secrets custom resources between virtual and host clusters:
From Virtual Cluster → Host Cluster:
ExternalSecrets - Secret definitions that reference external stores
SecretStores - Namespace-scoped store configurations (optional)
From Host Cluster → Virtual Cluster:
ClusterSecretStores - Cluster-wide store configurations available to virtual clusters
Bidirectional Sync:
SecretStores can be synced bidirectionally when enabled
The External Secrets Operator running on the host cluster processes the synced ExternalSecret resources and creates the corresponding Kubernetes secrets, which are then available in the virtual cluster.
Prerequisites
-
External Secrets Operator must be installed on the host cluster
# Install using Helm
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets -n external-secrets-system --create-namespace
-
Verify ESO is running
kubectl get pods -n external-secrets-system
-
Ensure ESO CRDs are installed
kubectl get crd | grep external-secrets
-
Configure at least one SecretStore or ClusterSecretStore
kubectl get clustersecretstores
Setup Instructions
Basic Configuration
Enable the External Secrets integration:
integrations:
externalSecrets:
enabled: true
Deploy your vCluster:
vcluster create my-vcluster -n team-x -f values.yaml
Advanced Configuration
Customize resource syncing and webhook behavior:
integrations:
externalSecrets:
enabled: true
# Reuse host cluster webhooks (recommended)
webhook:
enabled: false
sync:
toHost:
# Sync ExternalSecrets from virtual to host
externalSecrets:
selector:
matchLabels:
sync: "true"
# Optionally sync SecretStores
stores:
enabled: false
selector:
matchLabels:
shared: "true"
fromHost:
# Sync ClusterSecretStores from host to virtual cluster
clusterStores:
enabled: false
selector:
matchLabels:
vcluster-accessible: "true"
Selective Syncing with Labels
Sync only ExternalSecrets with specific labels:
integrations:
externalSecrets:
enabled: true
sync:
toHost:
externalSecrets:
selector:
matchLabels:
environment: production
team: platform
Usage Examples
Using a ClusterSecretStore from the Host
If ClusterSecretStore syncing is enabled, stores from the host are available in your virtual cluster:
# List available ClusterSecretStores
kubectl get clustersecretstores
Create an ExternalSecret using a host ClusterSecretStore:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: aws-secret
namespace: default
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-parameter-store # ClusterSecretStore from host
kind: ClusterSecretStore
target:
name: my-app-secret
creationPolicy: Owner
data:
- secretKey: password
remoteRef:
key: /production/database/password
Apply the ExternalSecret:
kubectl apply -f external-secret.yaml
Creating a SecretStore in the Virtual Cluster
Create a namespace-scoped SecretStore:
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend
namespace: default
spec:
provider:
vault:
server: "https://vault.example.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "demo-role"
serviceAccountRef:
name: external-secrets-sa
kubectl apply -f secret-store.yaml
Complete Example: AWS Secrets Manager
Step 1: Create a ClusterSecretStore on the host cluster:
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: aws-secrets-manager
labels:
vcluster-accessible: "true"
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: external-secrets
namespace: external-secrets-system
Step 2: In your virtual cluster, create an ExternalSecret:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: database-credentials
namespace: production
spec:
refreshInterval: 15m
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: db-credentials
creationPolicy: Owner
dataFrom:
- extract:
key: production/database
Step 3: Verify the secret was created:
kubectl get secret db-credentials -n production
kubectl get externalsecret database-credentials -n production
Using with HashiCorp Vault
Step 1: Configure a ClusterSecretStore for Vault on the host:
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: vault-backend
labels:
vcluster-accessible: "true"
spec:
provider:
vault:
server: "https://vault.company.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes/vcluster"
role: "external-secrets"
Step 2: Create ExternalSecrets in your virtual cluster:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: api-keys
namespace: default
spec:
refreshInterval: 10m
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: api-credentials
data:
- secretKey: apiKey
remoteRef:
key: apps/my-app
property: api_key
- secretKey: apiSecret
remoteRef:
key: apps/my-app
property: api_secret
Validation
1. Verify ExternalSecret Status
# Check ExternalSecret status
kubectl get externalsecrets
kubectl describe externalsecret aws-secret
Healthy output:
Status:
Conditions:
Type: Ready
Status: True
Reason: SecretSynced
Refresh Time: 2026-03-05T10:30:00Z
Synced Resource Version: 1-abc123def456
2. Check Generated Secret
# Verify the secret was created
kubectl get secret my-app-secret
kubectl describe secret my-app-secret
3. Validate on Host Cluster
# Switch to host context
vcluster disconnect
# Check synced ExternalSecrets
kubectl get externalsecrets -n team-x
kubectl get secrets -n team-x
4. Test Secret Refresh
Update the secret in your external store and wait for the refresh interval:
# Force an immediate refresh by deleting and recreating
kubectl delete externalsecret aws-secret
kubectl apply -f external-secret.yaml
# Watch for updates
kubectl get externalsecret aws-secret -w
Common Issues and Solutions
Issue: ExternalSecrets Not Creating Secrets
Symptoms: ExternalSecret exists but target secret is not created
Solution:
-
Check ExternalSecret status:
kubectl describe externalsecret <name>
-
Verify SecretStore/ClusterSecretStore is accessible:
kubectl get clustersecretstore <name> -o yaml
-
Check ESO controller logs on the host:
kubectl logs -n external-secrets-system deploy/external-secrets -f
-
Validate authentication to the external secret store
Issue: ClusterSecretStores Not Visible
Symptoms: kubectl get clustersecretstores returns empty
Solution:
Enable ClusterSecretStore syncing:
integrations:
externalSecrets:
enabled: true
sync:
fromHost:
clusterStores:
enabled: true
Label the stores on the host cluster:
kubectl label clustersecretstore aws-secrets-manager vcluster-accessible=true
Issue: Permission Denied Errors
Symptoms: ExternalSecret shows permission errors in status
Solution:
- Check IAM roles/service account permissions on the host cluster
- Verify the SecretStore authentication configuration
- Ensure RBAC permissions for ESO service account
Issue: Integration Conflicts
Error: external-secrets integration is enabled but external-secrets custom resource is also set in sync.toHost.customResources
Solution:
Remove manual ExternalSecrets CRD configuration:
# ❌ Don't do this when integration is enabled
sync:
toHost:
customResources:
externalsecrets.external-secrets.io:
enabled: true
# ✅ Use integration instead
integrations:
externalSecrets:
enabled: true
Issue: Secrets Not Refreshing
Symptoms: ExternalSecret doesn’t update when external secret changes
Solution:
- Check the
refreshInterval setting
- Verify the ESO controller is running
- Force a refresh by annotating the ExternalSecret:
kubectl annotate externalsecret <name> force-sync="$(date +%s)"
Configuration Reference
Complete configuration options from chart/values.yaml:906-937:
integrations:
externalSecrets:
# Enable/disable the integration
enabled: false
# Webhook configuration
webhook:
# Reuse host cluster webhooks
enabled: false
# Sync configuration
sync:
# Resources synced from virtual to host
toHost:
# ExternalSecret resources
externalSecrets:
selector:
matchLabels: {}
# SecretStore resources (namespace-scoped)
stores:
enabled: false
selector:
matchLabels: {}
# Resources synced from host to virtual
fromHost:
# ClusterSecretStore resources
clusterStores:
enabled: false
selector:
matchLabels: {}
Best Practices
-
Use ClusterSecretStores for Shared Infrastructure
- Configure ClusterSecretStores on the host cluster
- Share common secret stores across multiple virtual clusters
- Use label selectors to control access
-
Implement Least Privilege Access
- Use namespace-scoped SecretStores when possible
- Limit ClusterSecretStore access with label selectors
- Configure appropriate IAM/RBAC permissions
-
Set Appropriate Refresh Intervals
- Balance between freshness and API rate limits
- Use longer intervals (1h+) for rarely-changing secrets
- Use shorter intervals (5-15m) for frequently-rotated credentials
-
Monitor ExternalSecret Health
- Set up alerts for failed ExternalSecrets
- Monitor sync status and error conditions
- Use Prometheus metrics from ESO controller
-
Test Secret Rotation
- Verify secrets update correctly after rotation
- Test application behavior during secret changes
- Implement graceful handling of credential updates
-
Use Secret Templates
- Transform external secrets to match application requirements
- Combine multiple remote secrets into one Kubernetes secret
- Add computed fields or default values
Example: Multi-Cloud Setup
Configure access to secrets from multiple cloud providers:
# AWS Secrets Manager
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: aws-store
labels:
vcluster-accessible: "true"
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: external-secrets
---
# Azure Key Vault
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: azure-store
labels:
vcluster-accessible: "true"
spec:
provider:
azurekv:
authType: WorkloadIdentity
vaultUrl: "https://myvault.vault.azure.net"
serviceAccountRef:
name: external-secrets
---
# GCP Secret Manager
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: gcp-store
labels:
vcluster-accessible: "true"
spec:
provider:
gcpsm:
projectID: "my-project"
auth:
workloadIdentity:
serviceAccountRef:
name: external-secrets