Di episode ini kita akan coba bahas External Secret Manager seperti HashiCorp Vault untuk manage secret di Kubernetes. Kita akan mempelajari bagaimana store, retrieve, dan rotate secret securely, dan best practice untuk secret management.

Catatan
Untuk kalian yang ingin membaca episode sebelumnya, bisa click thumbnail episode 40 di bawah ini
Di episode sebelumnya, kita menjelajahi GitOps, yang menggunakan Git sebagai source of truth untuk Kubernetes deployment. Sekarang kita akan mendalami External Secret Manager, yang menyediakan secure secret management untuk Kubernetes application.
Catatan: Disini saya akan menggunakan Kubernetes Cluster yang di install melalui K3s.
Storing secret di Kubernetes Secret atau Git tidak aman. External Secret Manager seperti HashiCorp Vault menyediakan centralized, secure way untuk manage secret. Pikirkan Vault seperti secure vault untuk secret Anda - ini encrypt, audit, dan control access ke sensitive data. Dengan External Secrets Operator, Anda dapat automatically sync secret dari Vault ke Kubernetes.
External Secret Manager adalah centralized system untuk manage, store, dan rotate secret. Ini menyediakan encryption, audit logging, dan fine-grained access control.
1. Centralized Management
Semua secret di satu secure location.
2. Encryption
Secret encrypted at rest dan in transit.
3. Audit Logging
Track siapa access apa dan kapan.
4. Access Control
Fine-grained permission untuk secret access.
5. Secret Rotation
Automatically rotate secret tanpa downtime.
6. Compliance
Meet security compliance requirement.
7. Multi-Environment
Manage secret di dev, staging, production.
Vault adalah popular open-source secret management tool.
┌─────────────────────────────────────┐
│ Vault Server │
│ ┌─────────────────────────────┐ │
│ │ Secret Storage │ │
│ │ - Database credential │ │
│ │ - API key │ │
│ │ - Certificate │ │
│ └─────────────────────────────┘ │
│ ┌─────────────────────────────┐ │
│ │ Authentication │ │
│ │ - Kubernetes auth │ │
│ │ - JWT auth │ │
│ │ - AppRole │ │
│ └─────────────────────────────┘ │
│ ┌─────────────────────────────┐ │
│ │ Audit Logging │ │
│ │ - Access log │ │
│ │ - Change history │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘# Download Vault
wget https://releases.hashicorp.com/vault/1.15.0/vault_1.15.0_linux_amd64.zip
unzip vault_1.15.0_linux_amd64.zip
# Start Vault di dev mode
vault server -devstorage "file" {
path = "/vault/data"
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_disable = false
tls_cert_file = "/vault/tls/vault.crt"
tls_key_file = "/vault/tls/vault.key"
}
ui = trueExternal Secrets Operator sync secret dari external system ke Kubernetes.
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets \
external-secrets/external-secrets \
-n external-secrets-system \
--create-namespaceSecretStore define bagaimana connect ke Vault:
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend
spec:
provider:
vault:
server: "https://vault.example.com:8200"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "my-app"ExternalSecret define secret mana yang di-sync:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-secret
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: app-secret
creationPolicy: Owner
data:
- secretKey: database-password
remoteRef:
key: database
property: password
- secretKey: api-key
remoteRef:
key: api
property: keyStore key-value secret:
# Enable KV v2 secret engine
vault secrets enable -version=2 kv
# Store secret
vault kv put kv/database \
username=admin \
password=secret123
# Retrieve secret
vault kv get kv/databaseGenerate dynamic database credential:
# Enable database secret engine
vault secrets enable database
# Configure database connection
vault write database/config/my-db \
plugin_name=mysql-database-plugin \
allowed_roles="readonly" \
connection_url="{{username}}:{{password}}@tcp(db.example.com:3306)/" \
username="vault" \
password="vault-password"
# Create role
vault write database/roles/readonly \
db_name=my-db \
creation_statements="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}'; GRANT SELECT ON *.* TO '{{name}}'@'%';" \
default_ttl="1h" \
max_ttl="24h"
# Generate credential
vault read database/creds/readonlyGenerate certificate:
# Enable PKI secret engine
vault secrets enable pki
# Generate root certificate
vault write -field=certificate pki/root/generate/internal \
common_name="example.com" \
ttl=87600h > CA_cert.crt
# Create role
vault write pki/roles/example-dot-com \
allowed_domains="example.com" \
allow_subdomains=true \
max_ttl="72h"
# Issue certificate
vault write pki/issue/example-dot-com \
common_name="app.example.com"# Enable Kubernetes auth
vault auth enable kubernetes
# Configure Kubernetes auth
vault write auth/kubernetes/config \
token_reviewer_jwt=@/var/run/secrets/kubernetes.io/serviceaccount/token \
kubernetes_host=https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT \
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
# Create role
vault write auth/kubernetes/role/my-app \
bound_service_account_names=my-app \
bound_service_account_namespaces=default \
policies=my-app-policy \
ttl=24h# Enable AppRole auth
vault auth enable approle
# Create role
vault write auth/approle/role/my-app \
token_ttl=1h \
token_max_ttl=4h \
policies="my-app-policy"
# Get role ID
vault read auth/approle/role/my-app/role-id
# Generate secret ID
vault write -f auth/approle/role/my-app/secret-idapiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: db-credentials
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: database
property: username
- secretKey: password
remoteRef:
key: database
property: password
---
apiVersion: v1
kind: Pod
metadata:
name: app-with-db
spec:
containers:
- name: app
image: myapp:latest
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: passwordapiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: api-keys
spec:
refreshInterval: 30m
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: api-keys
creationPolicy: Owner
dataFrom:
- extract:
key: api-keysapiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: tls-cert
spec:
refreshInterval: 24h
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: tls-cert
template:
type: kubernetes.io/tls
data:
tls.crt: "{{ .certificate }}"
tls.key: "{{ .private_key }}"
data:
- secretKey: certificate
remoteRef:
key: certificates/app
property: cert
- secretKey: private_key
remoteRef:
key: certificates/app
property: keyapiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: rotating-secret
spec:
refreshInterval: 1h # Refresh setiap jam
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: rotating-secret
creationPolicy: Owner
data:
- secretKey: api-key
remoteRef:
key: api-keys/rotating
property: key# Rotate secret di Vault
vault kv put kv/api-keys/rotating \
key=new-api-key-value
# External Secrets akan automatically sync
# dalam refreshIntervalProblem: Secret exposed di Git history.
# JANGAN LAKUKAN INI - Secret di Git
apiVersion: v1
kind: Secret
metadata:
name: db-secret
data:
password: c2VjcmV0MTIzSolusi: Gunakan External Secret Manager:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-secret
spec:
secretStoreRef:
name: vault-backend
target:
name: db-secretProblem: Compromised secret tetap active.
Solusi: Enable automatic rotation:
spec:
refreshInterval: 1h # Rotate setiap jamProblem: Siapa pun dapat access semua secret.
Solusi: Implement fine-grained policy:
path "kv/data/database" {
capabilities = ["read"]
}
path "kv/data/api-keys" {
capabilities = ["read"]
}
path "kv/data/admin/*" {
capabilities = [] # No access
}Problem: No visibility ke siapa access secret.
Solusi: Enable audit logging:
vault audit enable file file_path=/vault/logs/audit.logProblem: Secret exposed di source code.
Solusi: Selalu gunakan secret management:
# JANGAN LAKUKAN INI
export DB_PASSWORD="secret123"export DB_PASSWORD=$(kubectl get secret db-credentials -o jsonpath='{.data.password}' | base64 -d)auth:
kubernetes:
mountPath: "kubernetes"
role: "my-app"vault write auth/kubernetes/role/my-app \
ttl=24h \
max_ttl=7d# Hanya grant necessary permission
path "kv/data/my-app/*" {
capabilities = ["read"]
}vault audit enable file file_path=/vault/logs/audit.logspec:
refreshInterval: 1hVault Dev
Vault Staging
Vault Productionvault operator raft snapshot save vault-backup.snap# Review audit log
vault audit list
tail -f /vault/logs/audit.log| Aspek | Vault | Kubernetes Secret |
|---|---|---|
| Encryption | Ya | Optional |
| Audit Logging | Ya | Limited |
| Access Control | Fine-grained | RBAC only |
| Secret Rotation | Automatic | Manual |
| Centralized | Ya | Per-cluster |
| Compliance | Ya | Limited |
| Cost | Self-hosted atau managed | Free |
kubectl describe externalsecret app-secret
kubectl get externalsecret -o widevault audit list
tail -f /vault/logs/audit.logkubectl exec -it pod-name -- \
curl -k https://vault.example.com:8200/v1/sys/healthkubectl logs -n external-secrets-system \
deployment/external-secretsPada episode 41 ini, kita telah membahas External Secret Manager di Kubernetes secara mendalam. Kita sudah belajar bagaimana gunakan HashiCorp Vault dengan External Secrets Operator untuk securely manage secret.
Key takeaway:
External Secret Manager essential untuk production Kubernetes deployment untuk ensure secret aman, auditable, dan compliant.
Catatan
Untuk kalian yang ingin melanjutkan ke episode selanjutnya, bisa click thumbnail episode 42 di bawah ini