A Guide to Managing Kubernetes Secrets with AWS Secrets Manager and External Secrets Operator

Search for a command to run...

No comments yet. Be the first to comment.
A short story about my life as a DevOps engineer It's 2:47 PM. I'm staring at a staging environment that swears it's pointing to the new load balancer. The DNS change was made an hour ago. TTL was 300
Day 1 Day one at the new gig. I'm a DevOps engineer, which means I spend most of my professional life automating away other people's toil. So it was genuinely humbling when IT handed me a fresh M2 Mac
Forgot your spreadsheet password? Resolve how to remove sheet, workbook, and VBA protections.

Managing Kubernetes clusters, contexts, and namespaces can be time-consuming. But fear not! I’ve got you covered with KubeKit smart tools: kubectl (with k alias), helm, kustomize, kc, and kn. These to

This repository provides a GitOps approach to maanage your DNS records live in Git, changes are peer-reviewed, and deployments are automated through CI/CD. When the dashboard is down, your DNS config is still version-controlled and ready to push to a...

Managing secrets in Kubernetes is notoriously tricky. Hardcoding them? Yikes. Storing them in plaintext? Dangerous. In this post, I’ll show you how to securely integrate AWS Secrets Manager into your K8s workflow using External Secrets Operator (ESO) - so you can automate secret syncing and sleep better at night.
This post walks you through a clean, secure approach: syncing secrets from AWS Secrets Manager (SM) into Kubernetes using the External Secrets Operator (ESO). You'll learn how to set it up with Helm, configure access policies, and sync secrets in different formats - using real examples from the trenches.

ExternalSecrets is an open-source Kubernetes plugin that serves two main functions: it injects secrets from supported external providers into your application cluster and synchronizes these injected secrets with their corresponding remote counterparts.
In the ExternalSecrets architecture, two key resources play crucial roles:
SecretStore: This resource manages authentication, enabling your Kubernetes cluster to access AWS resources, specifically secrets. It acts as a bridge, ensuring secure and authorized access to the secrets stored in AWS.
ExternalSecret: This resource is responsible for defining and creating secrets. It utilizes the SecretStore to retrieve specific secrets and provides a template for Kubernetes controllers to generate local secrets within the cluster.
First, install the ESO Helm chart into its own namespace:
kubectl create ns eso
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets -n eso
🚫 Optional: If you manage CRDs manually, add
--set installCRDs=false.
We need to create an IAM user or role with access to read specific secrets from AWS Secrets Manager.
Example IAM Policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:ListSecrets",
"secretsmanager:GetSecretValue",
"secretsmanager:ListSecretVersionIds"
],
"Resource": [
"arn:aws:secretsmanager:ap-southeast-1:071123451249:secret:demo*"
],
"Condition": {
"StringLike": {
"secretsmanager:SecretId": [
"arn:aws:secretsmanager:ap-southeast-1:071123451249:secret:prod/demo"
]
},
"StringEquals": {
"aws:username": ["secret-eso"]
}
}
}
]
}
Fine-grained access: Principle of least privilege.
Conditionals: Limits access to just the needed secrets + specific IAM username.
We create a K8s secret that holds AWS access keys.
echo -n 'KEYID' > ./access-key
echo -n 'SECRETKEY' > ./secret-access-key
kubectl create secret generic demo-awssm-secret \
--from-file=./access-key \
--from-file=./secret-access-key
rm -f ./access-key ./secret-access-key
⚠️ Pro tip: Store these secrets in a GitOps-friendly secret manager like SealedSecrets, SOPS, or External Secrets from your Git repo—not directly in plain YAML files.
This tells ESO how to talk to AWS Secrets Manager:
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: demo-secretstore
spec:
provider:
aws:
service: SecretsManager
region: ap-southeast-1
auth:
secretRef:
accessKeyIDSecretRef:
name: demo-awssm-secret
key: access-key
secretAccessKeySecretRef:
name: demo-awssm-secret
key: secret-access-key
You have three common use cases. Here's what each one looks like:
Best for multi-line config files.
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: demo-secret-as-configmap-template
namespace: demo
spec:
refreshInterval: 5m
secretStoreRef:
name: demo-secretstore
kind: SecretStore
target:
name: demo-config
template:
engineVersion: v2
data:
core-dev.php: "{{ .coredev | toString }}"
custom.ini: "{{ .customini | toString }}"
demo-config.conf: "{{ .conf| toString }}"
service-url.php: "{{ .serviceurl | toString }}"
data:
- secretKey: coredev
remoteRef:
key: prod/demo/core-dev.php
- secretKey: customini
remoteRef:
key: prod/demo/custom.ini
- secretKey: conf
remoteRef:
key: prod/demo/cnf.conf
- secretKey: serviceurl
remoteRef:
key: prod/demo/service-url.php
🧠 Output: A
Secretwith multiple keys mimicking aConfigMap, storing plaintext files.
Ideal for app credentials or API keys.
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: demo-secret
namespace: demo
spec:
refreshInterval: 2m
secretStoreRef:
name: demo-secretstore
kind: SecretStore
target:
name: demo-secret
creationPolicy: Owner
dataFrom:
- extract:
key: prod/demo/secret
🎯 Output: A Kubernetes
Secretwith key/value pairs extracted from a JSON blob in AWS SM.
When you want ESO to inject secrets into a full config file template.
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: demo-secret-in-configmap
namespace: demo
spec:
refreshInterval: 2m
secretStoreRef:
name: demo-secretstore
kind: SecretStore
target:
name: demo-secret-redis-config
template:
data:
redis.conf: |
bind 0.0.0.0
port 6379
requirepass "{{ .redisPassword | toString }}"
protected-mode no
appendonly no
supervised no
save 3600 1 300 10 30 20
dir /opt/redis/data
loglevel notice
logfile "/opt/redis/data/redis.log"
databases 6
data:
- secretKey: redisPassword
remoteRef:
key: prod/demo/redis-credential
⚙️ Output: A fully rendered Redis config with live secrets baked in.