Skip to main content

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

  1. 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
    
  2. Verify ESO is running
    kubectl get pods -n external-secrets-system
    
  3. Ensure ESO CRDs are installed
    kubectl get crd | grep external-secrets
    
  4. Configure at least one SecretStore or ClusterSecretStore
    kubectl get clustersecretstores
    

Setup Instructions

Basic Configuration

Enable the External Secrets integration:
values.yaml
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:
values.yaml
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:
values.yaml
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:
external-secret.yaml
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:
secret-store.yaml
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:
  1. Check ExternalSecret status:
    kubectl describe externalsecret <name>
    
  2. Verify SecretStore/ClusterSecretStore is accessible:
    kubectl get clustersecretstore <name> -o yaml
    
  3. Check ESO controller logs on the host:
    kubectl logs -n external-secrets-system deploy/external-secrets -f
    
  4. 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:
  1. Check IAM roles/service account permissions on the host cluster
  2. Verify the SecretStore authentication configuration
  3. 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:
  1. Check the refreshInterval setting
  2. Verify the ESO controller is running
  3. 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

  1. 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
  2. Implement Least Privilege Access
    • Use namespace-scoped SecretStores when possible
    • Limit ClusterSecretStore access with label selectors
    • Configure appropriate IAM/RBAC permissions
  3. 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
  4. Monitor ExternalSecret Health
    • Set up alerts for failed ExternalSecrets
    • Monitor sync status and error conditions
    • Use Prometheus metrics from ESO controller
  5. Test Secret Rotation
    • Verify secrets update correctly after rotation
    • Test application behavior during secret changes
    • Implement graceful handling of credential updates
  6. 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

Build docs developers (and LLMs) love