Introduction
External Secrets Operator (ESO) is a Kubernetes operator that allows you to use external secret management systems, such as AWS Secrets Manager, HashiCorp Vault, and Azure Key Vault, to manage secrets in your Kubernetes cluster. It provides a way to synchronize secrets from these external systems into Kubernetes secrets, making it easier to manage and use secrets in your applications.
In this guide, we will demonstrate how to use the External Secrets Operator to synchronize secrets from one Kubernetes cluster (Cluster 1(Upstream Cluster)) to another (Cluster 2,(Downstream Cluster/s)). This is particularly useful when you want to share secrets between different clusters or environments.
Prerequisites
- 2 Kubernetes clusters (e.g., k3s, EKS, GKE, AKS)
- Cluster 1: Use as Secrets provider
- Cluster 2: Use as Secrets consumer
- kubectl installed and configured to access the clusters
- Helm installed
Architecture

Deploying External Secret Operator in Cluster 2
We will be deploying ESO to the second cluster (Cluster 2) where we want to use the secrets from the first cluster (Cluster 1).
# Add the ESO Helm repository
helm repo add external-secrets https://charts.external-secrets.io\n
# Create a namespace for ESO
kubectl create namespace external-secrets
# Install ESO using Helm
helm install external-secrets external-secrets/external-secrets --namespace=external-secrets --create-namespace
Wait till the ESO pods are up and running. You can check the status of the pods using the following command:
# Verify the installation
kubectl get po,nodes,svc -n external-secrets
NAME READY STATUS RESTARTS AGE
pod/external-secrets-9c44fbf86-mnxk4 1/1 Running 0 166m
pod/external-secrets-cert-controller-7cdbbbd6d5-6jv7d 1/1 Running 0 166m
pod/external-secrets-webhook-cbbb45647-rzpzq 1/1 Running 0 166m
NAME STATUS ROLES AGE VERSION
node/ip-172-31-92-184 Ready control-plane,etcd,master,worker 3h20m v1.32.3+k3s1
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/external-secrets-webhook ClusterIP 10.43.157.109 <none> 443/TCP 166m
Create a Kubernetes Secret in Cluster 1
In Cluster 1, create a Kubernetes secret that you want to synchronize to Cluster 2. For example, let’s create a secret named user-credentials in the default namespace.
# Create a secret in Cluster 1
cat <<-EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: user-credentials
type: Opaque
stringData:
username: peter
password: asdf1234
EOF
Create RBAC configuration in Cluster1
We need an Service Account to access the secret in Cluster 1. Create a Service Account and a RoleBinding to allow the Service Account to access secrets in the default namespace.
cat <<-EOF | kubectl apply -f -
# remote-cluster.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: eso-reader
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: eso-secret-reader
namespace: default
rules:
- apiGroups: [""]
resources:
- secrets
verbs:
- get
- list
- watch
- apiGroups:
- authorization.k8s.io
resources:
- selfsubjectrulesreviews
verbs:
- create
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: eso-secret-reader-binding
namespace: default
subjects:
- kind: ServiceAccount
name: eso-reader
roleRef:
kind: Role
name: eso-secret-reader
apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: Secret
metadata:
name: eso-reader-token
namespace: default
annotations:
kubernetes.io/service-account.name: eso-reader
type: kubernetes.io/service-account-token
EOF
Above RBAC configuration creates a Service Account named eso-reader in the default namespace, a Role named eso-secret-reader that allows access to secrets, and a RoleBinding that binds the Service Account to the Role. This Role allows to Pull the secrets from the default namespace.
Extract the static service account token from the remote cluster
# Extract the token value
kubectl get secrets eso-reader-token -o jsonpath='{.data.token}' | base64 -d > eso/token.txt
# Extract the CA cert
kubectl get secrets eso-reader-token -o jsonpath='{.data.ca\.crt}' | base64 -d > eso/ca.crt
# Get the API server address
kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}'
Configure the ESO in Cluster 2
To pull the secrets from Cluster 1, we need to create SecretStore. with in SecretStore we need to add the Cluster1 URL, Service Account Token and CA cert of Cluster 1.
# create Kubernetes Secret
kubectl -n default create secret generic remote-sa-token --from-literal=token="$(cat ./eso/token.txt)" --dry-run=client -o yaml | kubectl apply -f -
# Create Configmap with CA cert
kubectl create cm kube-root-remote-ca.crt --from-file eso/ca.crt
Once the secret and configmap are created, we can create the SecretStore resource.
cat <<-EOF | kubectl apply -f -
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: remote-k8s-store
namespace: default
spec:
provider:
kubernetes:
server:
url: https://172.31.86.147:6443
caProvider:
type: ConfigMap
name: kube-root-remote-ca.crt
key: ca.crt
remoteNamespace: default
auth:
token:
bearerToken:
name: remote-sa-token
key: token
EOF
Verify the status of SecretStore resource, it should be in Valid state.
kubectl get SecretStore -A
NAMESPACE NAME AGE STATUS CAPABILITIES READY
default remote-k8s-store 129m Valid ReadWrite True
Create ExternalSecret resource in Cluster 2
Now that we have the SecretStore configured, we can create an ExternalSecret resource in Cluster 2 to synchronize the secret from Cluster 1.
Before adding ExternalSecret resource, let’s check the secrets in Cluster 2 default namespace.
kubectl get secrets
NAME TYPE DATA AGE
remote-sa-token Opaque 1 87m
cat <<-EOF | kubectl apply -f -
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: eso-kubernetes-secret
namespace: default #target-namespace
spec:
secretStoreRef:
kind: SecretStore
name: remote-k8s-store
target:
name: pulled-secrets
data:
- secretKey: username
remoteRef:
key: user-credentials
property: username
EOF
Verify the ExternalSecret resource, Status should be Ready and SecretSynced.
kg ExternalSecret
NAME STORETYPE STORE REFRESH INTERVAL STATUS READY
eso-kubernetes-secret SecretStore remote-k8s-store 1h SecretSynced True
Check the Events in default namespace, it should show the SecretSynced event.
kubectl get events
LAST SEEN TYPE REASON OBJECT MESSAGE
2m40s Normal Created externalsecret/eso-kubernetes-secret secret created
42s Normal Created externalsecret/eso-kubernetes-secret secret created
4m12s Normal Valid secretstore/remote-k8s-store store validated
Verify the synchronized secret in Cluster 2
Now, let’s check if the secret has been synchronized to Cluster 2. You should see a new secret named pulled-secrets in the default namespace.
kubectl get secrets
NAME TYPE DATA AGE
pulled-secrets Opaque 1 78s
remote-sa-token Opaque 1 90m
verify the content of the secret.
kubectl get secret pulled-secrets -o jsonpath='{.data.username}' | base64 --decode
peter
To sync these secrets across multiple kubernetes clusters, you can use CD tools like Fleet, ArgoCD or Flux to automate the deployment of ExternalSecret resources in multiple clusters.
Conclusion:
In this guide, we demonstrated how to use the External Secrets Operator (ESO) to synchronize secrets between two Kubernetes clusters. By leveraging ESO, you can securely share secrets across clusters or environments without duplicating sensitive data.
We covered:
- Deploying ESO in Cluster 2 to consume secrets from Cluster 1.
- Configuring RBAC in Cluster 1 to allow secure access to secrets.
- Setting up a SecretStore and ExternalSecret resource to synchronize secrets.
- Validating the synchronization process and verifying the secrets in Cluster 2.
This approach simplifies secret management in multi-cluster environments and ensures that secrets remain consistent and secure. Whether you’re managing secrets across development, staging, or production clusters, ESO provides a powerful and flexible solution.
Feel free to explore additional features of ESO, such as integrating with external secret management systems like AWS Secrets Manager or HashiCorp Vault. For more details, refer to the External Secrets Operator Documentation
Happy Kubernetes-ing! 🚀