Architecture¶
Related Notes
secrets/external-secrets-operator/index | secrets/external-secrets-operator/explanation | secrets/external-secrets-operator/how-to-guides |
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, and more) and its connection parameters.
- Authentication credentials -- references to Kubernetes Secrets holding provider credentials (API keys, tokens, service account references).
- Retry settings -- optional
maxRetriesandretryIntervalfor 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. This enables 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. This enables 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:
- Validate SecretStore reference -- ESO checks
spec.secretStoreRefand verifies the SecretStore exists and itsspec.controllerfield matches (for multi-controller setups). - Instantiate provider client -- Using the SecretStore credentials, ESO creates a provider-specific API client from the registered plugin.
- Fetch secret data -- The controller calls the external API to retrieve the requested secret values, decoding them if required.
- Apply template engine -- If
spec.target.templateis defined, the controller processes Go templates against the fetched data. Templates support advanced transformations includingbase64encode,base64decode,fromJson,toString, and conditional logic. - Create or update Secret -- The controller materializes the final Kubernetes Secret, respecting the configured creation policy.
- Periodic re-sync -- The controller re-queues reconciliation at
spec.refreshIntervalto 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 makes sure that 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 (for example, 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 IAM role of the pod 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. This enables 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, and more) implements this interface. The controller calls the appropriate provider based on the provider field of the SecretStore. New providers can 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¶
Security¶
Related Notes
secrets/external-secrets-operator/index | secrets/external-secrets-operator/explanation | | secrets/external-secrets-operator/how-to-guides
Threat Model¶
ESO operates with elevated privileges inside the Kubernetes cluster. It can create, read, update, and delete Secrets in all namespaces and has access to external secret provider credentials. Understanding its threat surface is critical.
| Threat Vector | Impact | Mitigation |
|---|---|---|
| Compromised ESO pod | Full read access to all synced secrets | Restrict with network policies, pod security standards |
| Leaked provider credentials | Access to external secret store | Use short-lived tokens, IRSA, Workload Identity |
| Malicious ExternalSecret | Exfiltrate secrets from provider to attacker-controlled namespace | RBAC on ExternalSecret creation, OPA/Kyverno policies |
| Malicious SecretStore | Route secret fetching through attacker-controlled endpoint | Validate provider configs, restrict SecretStore creation |
| PushSecret abuse | Push cluster secrets to attacker-controlled external store | Restrict PushSecret RBAC, audit PushSecret creation |
| Privilege escalation via ClusterSecretStore | Cross-namespace secret access | Use namespace conditions on ClusterSecretStore |
RBAC Model¶
Cluster Operator Permissions¶
Cluster operators manage the ESO deployment and ClusterSecretStores. They require broad permissions:
external-secrets.io/clustersecretstores-- CRUD at cluster scope.external-secrets.io/externalsecrets-- Read/list across namespaces for troubleshooting.- Core Kubernetes
secrets-- Read/write across namespaces (delegated to ESO ServiceAccount).
Application Developer Permissions¶
Application developers define ExternalSecrets within their namespaces. Give them limited scope:
# Example: Developer can create ExternalSecrets in their namespace only
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: external-secret-developer
namespace: my-app
rules:
- apiGroups: ["external-secrets.io"]
resources: ["externalsecrets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["external-secrets.io"]
resources: ["secretstores"]
verbs: ["get", "list", "watch"]
SecretStore Creation
If developers can create SecretStore resources, they can define arbitrary provider connections. In most environments, only cluster operators should create SecretStores, while developers reference pre-approved stores.
ESO Controller ServiceAccount¶
The ESO controller runs with a ServiceAccount that has broad permissions. The Helm chart creates:
ClusterRolewith permissions to manageSecretsacross all namespaces.ClusterRolewith permissions to manage all ESO CRDs.ClusterRoleBindingbinding these to the ESO ServiceAccount.
Restrict this by running multiple controllers with different ServiceAccounts, each scoped to specific namespaces or controller names.
Provider Credential Security¶
AWS (IRSA / Pod Identity)¶
The recommended approach for AWS is IAM Roles for Service Accounts (IRSA) or EKS Pod Identity, which eliminates static credentials:
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: aws-secretsmanager
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: eso-sa
With IRSA, the eso-sa ServiceAccount is annotated with an IAM role ARN. AWS SDK in the ESO pod exchanges the ServiceAccount JWT token for temporary STS credentials. No static accessKeyID or secretAccessKey is stored in Kubernetes.
GCP (Workload Identity)¶
For GCP Secret Manager, use Workload Identity to bind a Kubernetes ServiceAccount to a GCP Service Account:
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: gcp-secret-manager
spec:
provider:
gcpsm:
projectID: my-project
auth:
workloadIdentity:
serviceAccountRef:
name: eso-workload-id-sa
clusterLocation: us-central1
clusterName: my-gke-cluster
HashiCorp Vault (Kubernetes Auth)¶
When using Vault as the provider, the Kubernetes auth method allows ESO pods to authenticate using their ServiceAccount JWT token:
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: vault-store
spec:
provider:
vault:
server: "https://vault.example.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "eso-role"
serviceAccountRef:
name: eso-vault-sa
Static Token Risks
Do not use tokenSecretRef with static Vault tokens in production. These tokens never expire unless manually revoked. Prefer the Kubernetes auth method or AppRole with periodic secret IDs.
Namespace Isolation¶
SecretStore (Namespaced)¶
A SecretStore is namespaced and can only be referenced by ExternalSecret resources in the same namespace. Credentials referenced by the SecretStore (via secretRef) must also exist in the same namespace. This provides natural namespace isolation.
ClusterSecretStore (Cluster-Scoped)¶
A ClusterSecretStore can be referenced by ExternalSecret resources in any namespace. To restrict access, use the conditions field:
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
name: restricted-store
spec:
conditions:
- namespaces:
- "team-a"
- "team-b"
secretRef:
namespace: "eso-system"
provider:
vault:
server: "https://vault.example.com"
path: "secret"
auth:
kubernetes:
mountPath: "kubernetes"
role: "eso-role"
This restricts the ClusterSecretStore to only serve ExternalSecret resources in the team-a and team-b namespaces.
Cross-Namespace References¶
Some SecretStore fields allow referencing Kubernetes Secrets in other namespaces (for example, certSecretRef.namespace). In multi-tenant environments, carefully audit these cross-namespace references to prevent privilege escalation.
PushSecret Security Considerations¶
PushSecret reverses the normal data flow by pushing Kubernetes Secrets to external providers. This introduces additional security concerns:
- Data exfiltration risk -- A malicious PushSecret can push secrets to an attacker-controlled external provider. Restrict PushSecret creation with RBAC and admission policies.
- Provider write permissions -- The SecretStore referenced by a PushSecret needs write access to the external provider. Grant minimum required permissions.
- Deletion policy --
PushSecretDeletionPolicyDeleteremoves secrets from the external provider when the PushSecret is deleted. Make sure that this does not cause unintended data loss. - Update policy -- Configure
PushSecretUpdatePolicyto control whether existing provider secrets are overwritten.
# Restrict PushSecret creation with RBAC
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pushsecret-restricted
namespace: my-app
rules:
- apiGroups: ["external-secrets.io"]
resources: ["pushsecrets"]
verbs: ["get", "list"] # No create/update/delete
Admission Control Integration¶
Use Kubernetes admission controllers (OPA Gatekeeper, Kyverno) to enforce policies on ESO resources:
- Allowed providers -- Restrict which provider types can be configured in SecretStores.
- Allowed namespaces -- Restrict which namespaces can create ExternalSecrets or PushSecrets.
- Template validation -- Make sure that ExternalSecret templates do not inject malicious content.
- Creation policy enforcement -- Prevent
creationPolicy: MergeorNonein sensitive namespaces.
Network Security¶
- Deploy ESO in a dedicated namespace with strict network policies.
- Restrict egress traffic from the ESO controller to only the required provider endpoints.
- Use TLS for all provider connections. Verify certificates using
caBundleorcaProviderin SecretStore configuration. - For Vault, use mutual TLS (
tls.certSecretRefandtls.keySecretRef) when the Vault server requires client certificates.
Audit and Observability¶
- ESO emits Kubernetes events for reconciliation successes and failures. Monitor these events for anomalous patterns.
- The
statusfield on ExternalSecret and SecretStore resources reflects the current state (Ready, SecretSynced, SecretSyncedError). - Log all changes to ESO CRDs via Kubernetes audit logging.
- Monitor for unexpected Secret modifications that correlate with ExternalSecret reconciliation events.