Skip to main content

External Secrets Operator

Panduan lengkap instalasi dan konfigurasi External Secrets Operator untuk sinkronisasi secrets dari external secret management systems ke Kubernetes.

Pengenalan

External Secrets Operator (ESO) adalah Kubernetes operator yang mengintegrasikan external secret management systems seperti AWS Secrets Manager, HashiCorp Vault, Google Secrets Manager, Azure Key Vault, dan lainnya dengan Kubernetes.

Masalah yang Dipecahkan

Tanpa External Secrets Operator:

  • ❌ Secrets hardcoded dalam Git repository
  • ❌ Manual sync secrets ke setiap cluster
  • ❌ Sulit manage secrets rotation
  • ❌ Tidak ada centralized secret management
  • ❌ Secrets stored as plain Kubernetes Secrets

Dengan External Secrets Operator:

  • Centralized Management: Single source of truth untuk secrets
  • Automatic Sync: Otomatis pull secrets dari external providers
  • Auto Rotation: Support automatic secret rotation
  • Multiple Providers: Support 20+ secret backends
  • Kubernetes Native: Menggunakan CRDs
  • Security: Secrets tidak pernah di-commit ke Git

Arsitektur

┌─────────────────────────────────────────┐
│ External Secret Providers │
│ (Vault, AWS, GCP, Azure, etc.) │
└──────────────────┬──────────────────────┘

│ API Calls

┌──────────────────▼──────────────────────┐
│ External Secrets Operator │
│ ┌────────────────────────────────┐ │
│ │ SecretStore Controller │ │
│ └────────────────────────────────┘ │
│ ┌────────────────────────────────┐ │
│ │ ExternalSecret Controller │ │
│ └────────────────────────────────┘ │
└──────────────────┬──────────────────────┘

│ Creates/Updates

┌──────────────────▼──────────────────────┐
│ Kubernetes Secrets │
│ (Synchronized from external) │
└─────────────────────────────────────────┘

Instalasi

Prasyarat

# Kubernetes cluster (minimal v1.19)
kubectl version --short

# Helm 3
helm version
# Add External Secrets Helm repository
helm repo add external-secrets https://charts.external-secrets.io
helm repo update

# Install External Secrets Operator
helm install external-secrets \
external-secrets/external-secrets \
--namespace external-secrets-system \
--create-namespace \
--set installCRDs=true

Metode 2: Kubectl dengan Manifest

# Install CRDs dan Operator
kubectl apply -f https://raw.githubusercontent.com/external-secrets/external-secrets/main/deploy/crds/bundle.yaml

# Install Operator
kubectl apply -f https://raw.githubusercontent.com/external-secrets/external-secrets/main/deploy/external-secrets.yaml

Verifikasi Instalasi

# Check pods
kubectl get pods -n external-secrets-system

# Output:
# NAME READY STATUS RESTARTS AGE
# external-secrets-7d9f7f8c8-xxxxx 1/1 Running 0 1m
# external-secrets-cert-controller-5c6f7d8c8-xxxxx 1/1 Running 0 1m
# external-secrets-webhook-5c6f7d8c8-xxxxx 1/1 Running 0 1m

# Check CRDs
kubectl get crd | grep external-secrets

# Expected output:
# externalsecrets.external-secrets.io
# secretstores.external-secrets.io
# clustersecretstores.external-secrets.io

Custom Helm Configuration

values.yaml

# Replica count untuk HA
replicaCount: 2

# Resource limits
resources:
requests:
cpu: 10m
memory: 64Mi
limits:
cpu: 100m
memory: 128Mi

# Webhook configuration
webhook:
replicaCount: 2
resources:
requests:
cpu: 10m
memory: 32Mi

# Cert controller configuration
certController:
replicaCount: 2
resources:
requests:
cpu: 10m
memory: 32Mi

# Enable Prometheus metrics
serviceMonitor:
enabled: true
interval: 30s

# Security context
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000

# Pod disruption budget
podDisruptionBudget:
enabled: true
minAvailable: 1

# Log level
logLevel: info # debug, info, warn, error

Install dengan custom values:

helm install external-secrets \
external-secrets/external-secrets \
--namespace external-secrets-system \
--create-namespace \
-f values.yaml

Secret Providers

1. HashiCorp Vault

Setup Vault SecretStore

apiVersion: v1
kind: Secret
metadata:
name: vault-token
namespace: default
type: Opaque
stringData:
token: "hvs.CAESIJ..."
---
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:
tokenSecretRef:
name: vault-token
key: token

Vault dengan Kubernetes Auth

apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-k8s-auth
namespace: default
spec:
provider:
vault:
server: "https://vault.example.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "myapp-role"
serviceAccountRef:
name: myapp-sa

2. AWS Secrets Manager

apiVersion: v1
kind: Secret
metadata:
name: aws-credentials
namespace: default
type: Opaque
stringData:
access-key-id: "AKIAIOSFODNN7EXAMPLE"
secret-access-key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
---
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets-manager
namespace: default
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
secretRef:
accessKeyIDSecretRef:
name: aws-credentials
key: access-key-id
secretAccessKeySecretRef:
name: aws-credentials
key: secret-access-key

AWS dengan IRSA (IAM Roles for Service Accounts)

apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets-manager-irsa
namespace: default
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: external-secrets-sa

3. Google Secret Manager

apiVersion: v1
kind: Secret
metadata:
name: gcp-credentials
namespace: default
type: Opaque
stringData:
credentials.json: |
{
"type": "service_account",
"project_id": "my-project",
"private_key_id": "...",
"private_key": "...",
"client_email": "...",
"client_id": "...",
"auth_uri": "...",
"token_uri": "...",
"auth_provider_x509_cert_url": "...",
"client_x509_cert_url": "..."
}
---
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: gcp-secret-manager
namespace: default
spec:
provider:
gcpsm:
projectID: "my-project"
auth:
secretRef:
secretAccessKeySecretRef:
name: gcp-credentials
key: credentials.json

4. Azure Key Vault

apiVersion: v1
kind: Secret
metadata:
name: azure-credentials
namespace: default
type: Opaque
stringData:
client-id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
client-secret: "your-client-secret"
---
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: azure-keyvault
namespace: default
spec:
provider:
azurekv:
vaultUrl: "https://my-vault.vault.azure.net"
tenantId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
authSecretRef:
clientId:
name: azure-credentials
key: client-id
clientSecret:
name: azure-credentials
key: client-secret

5. Doppler

apiVersion: v1
kind: Secret
metadata:
name: doppler-token
namespace: default
type: Opaque
stringData:
token: "dp.st.xxxx"
---
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: doppler
namespace: default
spec:
provider:
doppler:
auth:
secretRef:
dopplerToken:
name: doppler-token
key: token

ClusterSecretStore vs SecretStore

SecretStore (Namespaced)

apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: my-store
namespace: app-namespace # Only usable in this namespace
spec:
provider:
vault:
server: "https://vault.example.com"
path: "secret"
# ...

ClusterSecretStore (Cluster-wide)

apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: vault-backend # Available in all namespaces
spec:
provider:
vault:
server: "https://vault.example.com"
path: "secret"
version: "v2"
auth:
tokenSecretRef:
name: vault-token
namespace: external-secrets-system # Must specify namespace
key: token

ExternalSecret Resources

1. Basic ExternalSecret

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: myapp-secrets
namespace: default
spec:
# Refresh interval
refreshInterval: 1h

# SecretStore reference
secretStoreRef:
name: vault-backend
kind: SecretStore

# Target Kubernetes Secret
target:
name: myapp-secrets
creationPolicy: Owner

# Data to fetch
data:
- secretKey: database-password
remoteRef:
key: myapp/database
property: password

- secretKey: api-key
remoteRef:
key: myapp/api
property: key

2. Multiple Keys dari Single Secret

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: database-secrets
namespace: default
spec:
refreshInterval: 30m
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: database-credentials
data:
- secretKey: DB_HOST
remoteRef:
key: database/prod
property: host

- secretKey: DB_PORT
remoteRef:
key: database/prod
property: port

- secretKey: DB_USER
remoteRef:
key: database/prod
property: username

- secretKey: DB_PASSWORD
remoteRef:
key: database/prod
property: password

3. DataFrom - Import All Keys

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: all-secrets
namespace: default
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: all-app-secrets
dataFrom:
- extract:
key: myapp/config # Import semua key dari path ini

4. Template Secrets

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: templated-secret
namespace: default
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: app-config
template:
engineVersion: v2
data:
config.yaml: |
database:
host: {{ .db_host }}
port: {{ .db_port }}
username: {{ .db_user }}
password: {{ .db_password }}
api:
key: {{ .api_key }}
url: https://api.example.com
data:
- secretKey: db_host
remoteRef:
key: database/prod
property: host
- secretKey: db_port
remoteRef:
key: database/prod
property: port
- secretKey: db_user
remoteRef:
key: database/prod
property: username
- secretKey: db_password
remoteRef:
key: database/prod
property: password
- secretKey: api_key
remoteRef:
key: api/keys
property: production

5. Conversion - Decode Base64

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: decoded-secret
namespace: default
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: decoded-credentials
data:
- secretKey: tls.crt
remoteRef:
key: certificates/prod
property: cert
decodingStrategy: Base64 # Decode from base64

- secretKey: tls.key
remoteRef:
key: certificates/prod
property: key
decodingStrategy: Base64

Contoh Lengkap End-to-End

Example: Web Application dengan Database

1. Setup SecretStore (Vault):

apiVersion: v1
kind: Secret
metadata:
name: vault-token
namespace: production
type: Opaque
stringData:
token: "hvs.CAESIJ..."
---
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-production
namespace: production
spec:
provider:
vault:
server: "https://vault.example.com"
path: "secret"
version: "v2"
auth:
tokenSecretRef:
name: vault-token
key: token

2. Create ExternalSecret:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: webapp-secrets
namespace: production
spec:
refreshInterval: 15m
secretStoreRef:
name: vault-production
kind: SecretStore
target:
name: webapp-credentials
creationPolicy: Owner
data:
# Database credentials
- secretKey: DB_HOST
remoteRef:
key: production/database
property: host
- secretKey: DB_PORT
remoteRef:
key: production/database
property: port
- secretKey: DB_NAME
remoteRef:
key: production/database
property: name
- secretKey: DB_USER
remoteRef:
key: production/database
property: username
- secretKey: DB_PASSWORD
remoteRef:
key: production/database
property: password

# API Keys
- secretKey: STRIPE_API_KEY
remoteRef:
key: production/api-keys
property: stripe
- secretKey: SENDGRID_API_KEY
remoteRef:
key: production/api-keys
property: sendgrid

3. Use in Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
containers:
- name: webapp
image: myapp/webapp:2.0.0
envFrom:
- secretRef:
name: webapp-credentials # Secret created by ExternalSecret
ports:
- containerPort: 8080

4. Verify:

# Check ExternalSecret status
kubectl get externalsecret webapp-secrets -n production

# Check if Kubernetes Secret created
kubectl get secret webapp-credentials -n production

# Describe ExternalSecret
kubectl describe externalsecret webapp-secrets -n production

Monitoring dan Troubleshooting

Check Status

# List all ExternalSecrets
kubectl get externalsecrets --all-namespaces

# Check status
kubectl get externalsecret myapp-secrets -n default -o yaml

# Expected conditions:
# conditions:
# - type: Ready
# status: "True"

Common Issues

Issue 1: ExternalSecret Not Syncing

# Check ExternalSecret events
kubectl describe externalsecret myapp-secrets -n default

# Check operator logs
kubectl logs -n external-secrets-system -l app.kubernetes.io/name=external-secrets

# Common causes:
# - Invalid SecretStore configuration
# - Wrong credentials
# - Network connectivity issues
# - Secret not found in backend

Issue 2: Authentication Failed

# For Vault
vault login <token>
vault kv get secret/myapp/config

# For AWS
aws secretsmanager get-secret-value --secret-id myapp/config

# Verify credentials in SecretStore Secret
kubectl get secret vault-token -n default -o yaml

Issue 3: Secret Not Found

# Verify path di backend
# For Vault:
vault kv list secret/myapp

# Check remoteRef path
kubectl get externalsecret myapp-secrets -n default -o yaml | grep remoteRef -A 3

Debug Logs

# Enable debug logging
kubectl set env deployment/external-secrets \
-n external-secrets-system \
LOG_LEVEL=debug

# View logs
kubectl logs -n external-secrets-system \
-l app.kubernetes.io/name=external-secrets \
--tail=100 -f

Monitoring dengan Prometheus

ServiceMonitor

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: external-secrets
namespace: external-secrets-system
spec:
selector:
matchLabels:
app.kubernetes.io/name: external-secrets
endpoints:
- port: metrics
interval: 30s

Key Metrics

# ExternalSecret sync status
externalsecret_sync_calls_total

# ExternalSecret sync errors
externalsecret_sync_calls_error

# SecretStore connection status
externalsecret_status_condition{condition="Ready"}

# Sync duration
externalsecret_sync_duration_seconds

Grafana Dashboard

Import dashboard: 16170 (External Secrets Operator)

Best Practices

1. Use ClusterSecretStore untuk Shared Providers

Recommended:

kind: ClusterSecretStore  # Reusable across all namespaces

2. Set Appropriate Refresh Interval

spec:
refreshInterval: 15m # Balance between freshness and API calls
  • Development: 5-15m
  • Production: 30m-1h
  • Rarely changing: 6h-24h

3. Use CreationPolicy Owner

target:
creationPolicy: Owner # ESO manages lifecycle

4. Implement RBAC

apiVersion: v1
kind: ServiceAccount
metadata:
name: myapp-sa
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: externalsecret-reader
namespace: default
rules:
- apiGroups: ["external-secrets.io"]
resources: ["externalsecrets"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: myapp-externalsecret
namespace: default
subjects:
- kind: ServiceAccount
name: myapp-sa
roleRef:
kind: Role
name: externalsecret-reader
apiGroup: rbac.authorization.k8s.io

5. Monitor Secret Expiration

# Add alerts
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: externalsecret-alerts
namespace: external-secrets-system
spec:
groups:
- name: external-secrets
interval: 30s
rules:
- alert: ExternalSecretSyncFailed
expr: externalsecret_sync_calls_error > 0
for: 5m
labels:
severity: warning
annotations:
summary: "ExternalSecret {{ $labels.name }} sync failed"

6. Use Secret Rotation

# In Vault, enable auto-rotation
spec:
provider:
vault:
server: "https://vault.example.com"
path: "database"
version: "v2"
auth:
kubernetes:
role: "myapp"

7. Namespace Isolation

# Use namespaced SecretStore untuk sensitive apps
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: production-secrets
namespace: production # Isolated to production namespace
spec:
# ...

Advanced Usage

Generator - Webhook untuk Random Secrets

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: generated-password
spec:
target:
name: random-password
dataFrom:
- sourceRef:
generatorRef:
apiVersion: generators.external-secrets.io/v1alpha1
kind: Password
name: my-password
---
apiVersion: generators.external-secrets.io/v1alpha1
kind: Password
metadata:
name: my-password
spec:
length: 32
digits: 10
symbols: 10
symbolCharacters: "-_$@"
noUpper: false
allowRepeat: true

PushSecret - Sync TO External Provider

apiVersion: external-secrets.io/v1alpha1
kind: PushSecret
metadata:
name: push-to-vault
spec:
refreshInterval: 10m
secretStoreRefs:
- name: vault-backend
kind: SecretStore
selector:
secret:
name: local-secret # Kubernetes Secret to push
data:
- match:
secretKey: password
remoteRef:
remoteKey: myapp/credentials
property: password

Migration dari Kubernetes Secrets

Step-by-Step Migration

1. Backup existing secrets:

kubectl get secret myapp-secrets -n default -o yaml > backup.yaml

2. Upload ke external provider (contoh Vault):

vault kv put secret/myapp/config \
DB_PASSWORD="old-password" \
API_KEY="old-key"

3. Create ExternalSecret:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: myapp-secrets
namespace: default
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
target:
name: myapp-secrets # Same name as old secret
dataFrom:
- extract:
key: myapp/config

4. Delete old secret:

kubectl delete secret myapp-secrets -n default

5. Verify:

kubectl get externalsecret myapp-secrets -n default
kubectl get secret myapp-secrets -n default

Upgrade

# Update Helm repo
helm repo update

# Check current version
helm list -n external-secrets-system

# Upgrade
helm upgrade external-secrets \
external-secrets/external-secrets \
--namespace external-secrets-system \
--reuse-values

Uninstall

# Delete all ExternalSecrets
kubectl delete externalsecrets --all --all-namespaces

# Delete all SecretStores
kubectl delete secretstores --all --all-namespaces
kubectl delete clustersecretstores --all

# Uninstall Helm release
helm uninstall external-secrets -n external-secrets-system

# Delete CRDs (optional)
kubectl delete crd externalsecrets.external-secrets.io
kubectl delete crd secretstores.external-secrets.io
kubectl delete crd clustersecretstores.external-secrets.io

# Delete namespace
kubectl delete namespace external-secrets-system

Kesimpulan

External Secrets Operator adalah solusi modern untuk secret management di Kubernetes, memungkinkan Anda untuk:

  • Centralized secret management
  • Automatic synchronization
  • Support multiple secret backends
  • GitOps-friendly (no secrets in Git)

Kapan Menggunakan?

Gunakan jika:

  • Menggunakan external secret management (Vault, AWS, etc.)
  • Butuh centralized secret management
  • Multi-cluster deployments
  • Compliance requirements

Resources