Skip to content

Architecture

Overview

External Secrets Operator (ESO) is a Kubernetes operator that synchronizes secrets from external secret management systems into native Kubernetes Secret objects. It extends Kubernetes with Custom Resource Definitions (CRDs) that declare where secrets live and how to synchronize them. The controller fetches secrets from an external API and creates or updates Kubernetes Secrets accordingly.

Component Architecture

graph TD
    subgraph "Kubernetes Cluster"
        MGR["ESO Controller Manager"]
        ESC["ExternalSecret Controller"]
        SSC["SecretStore Controller"]
        WH["Admission Webhooks"]

        ES["ExternalSecret CRD"]
        CSS["ClusterSecretStore CRD"]
        SS["SecretStore CRD"]
        PS["PushSecret CRD"]
        CES["ClusterExternalSecret CRD"]

        K8S["Kubernetes Secret"]

        MGR --> ESC
        MGR --> SSC
        MGR --> WH
        ESC -->|watches| ES
        ESC -->|reads| SS
        ESC -->|reads| CSS
        ESC -->|creates/updates| K8S
        ESC -->|watches| PS
        SSC -->|validates| SS
        SSC -->|validates| CSS
        CES -->|distributes to namespaces| ES
    end

    subgraph "Provider Plugins"
        AWS["AWS Secrets Manager / SSM"]
        VLT["HashiCorp Vault"]
        GCP["GCP Secret Manager"]
        AZ["Azure Key Vault"]
        OP["1Password Connect"]
        DP["Doppler"]
        GL["GitLab"]
        WHK["Webhook (custom HTTP)"]
    end

    ESC -->|fetches secrets| AWS
    ESC -->|fetches secrets| VLT
    ESC -->|fetches secrets| GCP
    ESC -->|fetches secrets| AZ
    ESC -->|fetches secrets| OP
    ESC -->|fetches secrets| DP
    ESC -->|fetches secrets| GL
    ESC -->|fetches secrets| WHK
    PS -->|pushes secrets| AWS
    PS -->|pushes secrets| VLT

Core CRDs

SecretStore

A namespaced resource that defines how to access an external secret provider. It separates the concern of authentication from the secret data itself. A SecretStore contains:

  • Provider configuration -- specifies the external API (AWS, Vault, GCP, Azure, etc.) and its connection parameters.
  • Authentication credentials -- references to Kubernetes Secrets holding provider credentials (API keys, tokens, service account references).
  • Retry settings -- optional maxRetries and retryInterval for HTTP connection resilience.
  • Controller field -- optional selector to target a specific ESO controller instance when running multiple controllers.
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
  name: vault-store
  namespace: my-app
spec:
  provider:
    vault:
      server: "https://vault.example.com"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "my-app-role"
          serviceAccountRef:
            name: "my-app-sa"

ClusterSecretStore

A cluster-scoped SecretStore that serves as a centralized gateway to a secret provider. Any ExternalSecret in any namespace can reference it. Cluster operators typically manage ClusterSecretStores to provide shared access to organizational secret backends.

The conditions field on ClusterSecretStore can restrict which namespaces are allowed to reference it, enabling multi-tenancy isolation.

ExternalSecret

A namespaced resource that declares what data to fetch and how to materialize it as a Kubernetes Secret. Key fields:

Field Purpose
spec.secretStoreRef References a SecretStore or ClusterSecretStore
spec.refreshInterval How often to re-sync (default 1 hour)
spec.target Target Secret name, creation policy, and optional template
spec.data[] Explicit key-to-remoteRef mappings
spec.dataFrom[] Bulk extraction of all keys from a remote secret
spec.target.template Go template for transforming secret data before creation

Creation policies control ownership: - Owner -- ESO creates and manages the Secret lifecycle. Deleting the ExternalSecret deletes the Secret. - Orphan -- ESO creates the Secret but does not delete it when the ExternalSecret is removed. - Merge -- ESO merges values into an existing Secret without overwriting unrelated keys. - None -- ESO does not create the Secret (used with PushSecret or generator patterns).

PushSecret

Reverses the normal flow: pushes data from a Kubernetes Secret out to an external provider. Use cases include: - Syncing certificates generated by cert-manager into Vault or AWS Secrets Manager. - Distributing auto-generated credentials to external systems. - Propagating Secrets across clusters.

PushSecret supports deletionPolicy (delete from provider on removal) and updatePolicy to control how provider-side secrets are managed.

ClusterExternalSecret

Distributes ExternalSecret resources into selected namespaces across the cluster. The controller creates a copy of the ExternalSecret in each matching namespace, enabling bulk distribution of secrets with a single resource.

Secret Sync Flow

sequenceDiagram
    participant User as Developer
    participant K8s as Kubernetes API
    participant Ctrl as ESO Controller
    participant Store as SecretStore
    participant Prov as External Provider

    User->>K8s: Apply ExternalSecret
    K8s->>Ctrl: Watch triggers reconciliation
    Ctrl->>K8s: Read referenced SecretStore
    K8s-->>Ctrl: Return SecretStore config + credentials
    Ctrl->>Ctrl: Instantiate provider client from plugin
    Ctrl->>Prov: Fetch secret data (GetSecret)
    Prov-->>Ctrl: Return secret values
    Ctrl->>Ctrl: Apply template engine (Go templating)
    Ctrl->>K8s: Create or update Kubernetes Secret
    Note over Ctrl: Repeats at spec.refreshInterval

Reconciliation Loop

The controller follows a structured reconciliation process:

  1. Validate SecretStore reference -- ESO checks spec.secretStoreRef and verifies the SecretStore exists and its spec.controller field matches (for multi-controller setups).
  2. Instantiate provider client -- Using the SecretStore credentials, ESO creates a provider-specific API client from the registered plugin.
  3. Fetch secret data -- The controller calls the external API to retrieve the requested secret values, decoding them if required.
  4. Apply template engine -- If spec.target.template is defined, the controller processes Go templates against the fetched data. Templates support advanced transformations including base64encode, base64decode, fromJson, toString, and conditional logic.
  5. Create or update Secret -- The controller materializes the final Kubernetes Secret, respecting the configured creation policy.
  6. Periodic re-sync -- The controller re-queues reconciliation at spec.refreshInterval to detect and apply upstream changes.

Provider Plugin Architecture

ESO uses a Go interface-based plugin system. Each provider implements a common interface that abstracts the external API:

Provider interface:
  - GetSecret(ctx, remoteRef) -> []byte
  - GetSecretMap(ctx, remoteRef) -> map[string][]byte
  - PushSecret(ctx, secret, remoteRef) -> error
  - DeleteSecret(ctx, remoteRef) -> error
  - Validate() -> error

Supported providers (as of 2026):

Category Providers
AWS Secrets Manager, SSM Parameter Store
HashiCorp Vault (KV v1/v2)
Google Cloud Secret Manager
Microsoft Azure Key Vault
1Password 1Password Connect
Others Doppler, GitLab, Pulumi Cloud, Akeyless, CyberArk, Fortanix, Senhasegura, Yandex Lockbox, Oracle Vault, IBM Cloud Secrets Manager, Alibaba Cloud
Custom Webhook (arbitrary HTTP endpoint)

Template Engine

The template engine processes Go templates defined in spec.target.template. This allows transformation of external secret data before it is written to the Kubernetes Secret.

Supported capabilities: - Variable substitution -- {{ .secretKey }} references fetched values. - Built-in functions -- base64encode, base64decode, fromJson, toJson, toString, toUint, toInt, toFloat, toUpperCase, toLowerCase, trim, regexMatch, regexReplaceAll. - Pipelines and conditionals -- Full Go template control structures (if, range, with). - Data from extract -- dataFrom.extract automatically maps all keys from the remote secret into the template context.

Multi-Controller Deployment

ESO supports running multiple controller instances within a single cluster. Each controller can be scoped to process only SecretStore and ExternalSecret resources with a matching spec.controller field. This enables: - Separation of tenant workloads. - Different provider configurations per controller. - Gradual rollout of controller upgrades.

Multi-Controller Maturity

Running multiple controllers is not widely tested in production. Test thoroughly before relying on this pattern for critical workloads.

Roles and Responsibilities

Role Responsibility
Cluster Operator Installs ESO, manages ClusterSecretStores, defines access policies
Application Developer Defines ExternalSecrets and application configuration

Each role maps roughly to a Kubernetes RBAC role. ESO itself runs with elevated privileges to create and manage Secrets across all namespaces.

Deployment Model

ESO is typically deployed via Helm into a dedicated namespace (external-secrets-system by default):

Namespace: external-secrets-system
  Deployment: external-secrets (single replica by default)
    - Controller manager with leader election for HA
    - Validating and mutating admission webhooks
  ServiceAccount: external-secrets-sa
  CRDs: SecretStore, ClusterSecretStore, ExternalSecret, ClusterExternalSecret, PushSecret

For high availability, increase the replica count. The controller-runtime leader election mechanism ensures only one replica actively reconciles at a time.


How It Works

Controller reconciliation loop, secret store authentication, provider gRPC interface, and sync lifecycle.

Reconciliation Flow

sequenceDiagram
    participant K8sAPI as K8s API
    participant ESO_H as ESO Controller
    participant Store as SecretStore config
    participant Provider as External Provider (Vault/AWS/GCP)
    participant Secret_H as K8s Secret

    ESO_H->>K8sAPI: Watch ExternalSecret resources
    K8sAPI-->>ESO_H: ExternalSecret created/updated
    ESO_H->>Store: Read SecretStore connection config
    ESO_H->>Provider: Authenticate (SA/IAM/AppRole)
    ESO_H->>Provider: Fetch secret values
    Provider-->>ESO_H: Secret data
    ESO_H->>ESO_H: Apply template (if defined)
    ESO_H->>Secret_H: Create/Update K8s Secret
    Note over ESO_H: Re-sync every refreshInterval (e.g., 1h)

SecretStore Authentication Chain

Each SecretStore defines a provider and an auth method. The controller resolves credentials before calling the provider:

Provider Auth Method Mechanism
AWS Secrets Manager JWT (IRSA) ESO assumes the pod's IAM role via sts:AssumeRoleWithWebIdentity
AWS Secrets Manager AccessKey Static AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY
HashiCorp Vault Kubernetes ServiceAccount JWT token sent to Vault's auth/kubernetes/login
HashiCorp Vault AppRole RoleID + SecretID sent to auth/approle/login
GCP Secret Manager Workload Identity GCP IAM via federated identity
Azure Key Vault Managed Identity AAD pod identity or user-assigned managed identity

For ClusterSecretStore, the auth credentials are typically namespace-scoped using auth.targetNamespace references, enabling multi-tenant access from a single store definition.

Provider Interface (Internal)

ESO providers implement a Go interface that abstracts secret operations:

GetSecret(ctx, remoteRef) → (secretValue, error)
GetSecretMap(ctx, remoteRef) → (map[string][]byte, error)
SetSecret(ctx, remoteRef, value) → error

Each provider (Vault, AWS SM, GCP SM, etc.) implements this interface. The controller calls the appropriate provider based on the SecretStore's provider field. This architecture allows new providers to be added without modifying the core reconciliation loop.

Sync Lifecycle

stateDiagram-v2
    [*] --> Pending: ExternalSecret created
    Pending --> SecretSynced: First successful sync
    SecretSynced --> SecretSynced: refreshInterval tick
    SecretSynced --> Error: Provider unreachable / auth failure
    Error --> SecretSynced: Retry succeeds
    Error --> Error: Retry with backoff
    SecretSynced --> Deleted: ExternalSecret deleted
    Deleted --> [*]: Owner policy determines Secret cleanup

Ownership Policies

The target.creationPolicy controls what happens to the underlying K8s Secret:

Policy Behavior
Owner (default) ESO creates and owns the Secret. Deleting the ExternalSecret deletes the Secret.
Orphan ESO creates the Secret but does not set ownerReference. Secret persists after ExternalSecret deletion.
Merge ESO merges values into an existing Secret it does not own. Only modifies specified keys.
None ESO does not create or modify Secrets. Used with template to generate data for other controllers.

Template Engine

ESO can transform secret data before writing to the K8s Secret using Go templates:

apiVersion: external-secrets.io/v1
kind: ExternalSecret
spec:
  target:
    template:
      type: kubernetes.io/tls
      data:
        tls.crt: "{{ .tlsCert }}"
        tls.key: "{{ .tlsKey }}"
  data:
    - secretKey: tlsCert
      remoteRef:
        key: prod/cert
        property: certificate
    - secretKey: tlsKey
      remoteRef:
        key: prod/cert
        property: private_key

Template functions include: base64 encode/decode, replace, toYAML, toJson, and access to all fetched secret values.

PushSecret Flow (Reverse Sync)

flowchart LR
    K8sS["K8s Secret"] -->|"PushSecret CRD"| ESO_PS["ESO Controller"]
    ESO_PS -->|"push"| Vault_PS["Vault / AWS SM / GCP SM"]

    style ESO_PS fill:#1565c0,color:#fff

PushSecret enables syncing K8s Secrets to external providers -- the reverse of the normal ExternalSecret flow. Use cases include: - Writing TLS certificates generated by cert-manager to Vault - Pushing service mesh identity credentials to an external store - Synchronizing secrets across clusters via a shared provider

Sources


Benchmarks

Scope

Performance characteristics, scaling limits, and resource consumption for External Secrets Operator.

Sync Performance

Provider Secret Fetch Time Refresh Interval Notes
AWS Secrets Manager 50-200ms 1h default API rate limits apply
HashiCorp Vault 10-50ms 1h default Depends on network
Azure Key Vault 50-200ms 1h default Regional latency
GCP Secret Manager 30-100ms 1h default Fast in-region

Resource Consumption

ExternalSecrets Count Controller CPU Controller Memory
50 50-100m 128Mi
200 100-300m 256Mi
1,000 500m-1 512Mi-1Gi

Scaling Limits

Dimension Limit Notes
ExternalSecrets per cluster 10,000+ Controller memory is bottleneck
Providers per cluster 50+ Multiple SecretStores
Refresh rate Provider API limits AWS: 10,000/sec, Vault: unlimited

Sourcing Status

Unsourced Performance Data

The performance numbers in this document are estimated from vendor documentation, community benchmarks, and engineering judgment. They do not represent controlled benchmarks with documented test conditions. Specific hardware configurations, software versions, and test methodologies were not recorded.

Use these figures as rough guidance only. For production capacity planning, run your own benchmarks against your specific workload and infrastructure.

Sources