Secrets management
Table of Contents
- The secrets problem
- Sealed Secrets
- SOPS
- HashiCorp Vault
- External Secrets Operator
- Comparison and choice
- Hands-on exercises
1 - The secrets problem
What NOT to do
# ❌ JAMAIS dans Git !
apiVersion: v1
kind: Secret
metadata:
name: my-secret
type: Opaque
data:
password: cGFzc3dvcmQxMjM= # base64 n'est PAS du chiffrement !
# Base64 est réversible
echo "cGFzc3dvcmQxMjM=" | base64 -d
# password123
# Git conserve l'historique
# Même après suppression, le secret reste dans l'historique
The GitOps dilemma
Available solutions
| Solution | Type | Complexity |
|---|---|---|
| Sealed Secrets | Asymmetric encryption | Low |
| SOPS | File encryption | Medium |
| Vault | Centralized management | High |
| External Secrets | Sync from provider | Medium |
🔝 Back to table of contents
2 - Sealed Secrets
Principle
Installation
# Installer le controller
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml
# Installer kubeseal CLI
brew install kubeseal # macOS
# ou
wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/kubeseal-0.24.0-linux-amd64.tar.gz
Usage
# 1. Créer un secret classique (ne pas commiter !)
kubectl create secret generic my-secret \
--from-literal=password=mysuperpassword \
--dry-run=client -o yaml > secret.yaml
# 2. Chiffrer avec kubeseal
kubeseal --format yaml < secret.yaml > sealed-secret.yaml
# 3. Le SealedSecret peut aller dans Git ✅
cat sealed-secret.yaml
# sealed-secret.yaml (peut être committé)
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: my-secret
namespace: default
spec:
encryptedData:
password: AgBj7...long-encrypted-string...==
Scopes
# Strict (namespace + name)
kubeseal --scope strict
# Namespace-wide (même namespace, tout nom)
kubeseal --scope namespace-wide
# Cluster-wide (tout namespace, tout nom)
kubeseal --scope cluster-wide
Pros/Cons
| Pros | Cons |
|---|---|
| Simple to use | Private key in the cluster |
| Kubernetes native | Manual rotation |
| No external dependency | One controller per cluster |
🔝 Back to table of contents
3 - SOPS
Principle
SOPS (Secrets OPerationS) encrypts the values in YAML/JSON files.
# Avant SOPS
apiVersion: v1
kind: Secret
data:
password: mypassword
# Après SOPS (seules les valeurs sont chiffrées)
apiVersion: v1
kind: Secret
data:
password: ENC[AES256_GCM,data:...,iv:...,tag:...,type:str]
sops:
kms: ...
age: ...
Installation
# macOS
brew install sops
# Linux
wget https://github.com/getsops/sops/releases/download/v3.8.1/sops-v3.8.1.linux.amd64 -O /usr/local/bin/sops
chmod +x /usr/local/bin/sops
Configuration with Age
# Générer une clé Age
age-keygen -o key.txt
# Public key: age1...
# Private key dans key.txt
# Configurer .sops.yaml
cat > .sops.yaml << EOF
creation_rules:
- path_regex: secrets/.*\.yaml$
age: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
EOF
Usage
# Chiffrer un fichier
sops -e secret.yaml > secret.enc.yaml
# Éditer in-place (déchiffre, édite, rechiffre)
sops secret.enc.yaml
# Déchiffrer
sops -d secret.enc.yaml > secret.yaml
Integration with Flux
# Flux supporte SOPS nativement
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: my-app
spec:
decryption:
provider: sops
secretRef:
name: sops-age # Secret contenant la clé privée
Pros/Cons
| Pros | Cons |
|---|---|
| Multi-provider (AWS KMS, GCP, Age) | Initial configuration |
| Readable diff | Key to manage |
| Flux native | Not ArgoCD native |
🔝 Back to table of contents
4 - HashiCorp Vault
Principle
Installation
# Helm install
helm repo add hashicorp https://helm.releases.hashicorp.com
helm install vault hashicorp/vault --set "server.dev.enabled=true"
Store a secret
# Activer le secret engine KV
vault secrets enable -path=secret kv-v2
# Stocker un secret
vault kv put secret/myapp/config \
username=admin \
password=supersecret
Vault Agent Injector
# Pod avec injection automatique
apiVersion: v1
kind: Pod
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "myapp"
vault.hashicorp.com/agent-inject-secret-config: "secret/data/myapp/config"
spec:
containers:
- name: myapp
image: myapp:latest
# Les secrets sont injectés dans /vault/secrets/config
Pros/Cons
| Pros | Cons |
|---|---|
| Centralized management | Operational complexity |
| Automatic rotation | Cost (Enterprise) |
| Audit trail | Single point of failure |
| Dynamic secrets | Learning curve |
🔝 Back to table of contents
5 - External Secrets Operator
Principle
Installation
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets
AWS configuration
# SecretStore
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
secretRef:
accessKeyIDSecretRef:
name: aws-creds
key: access-key
secretAccessKeySecretRef:
name: aws-creds
key: secret-key
ExternalSecret
# ExternalSecret (peut être dans Git)
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: my-secret
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets
kind: SecretStore
target:
name: my-secret
data:
- secretKey: password
remoteRef:
key: myapp/prod/db
property: password
Pros/Cons
| Pros | Cons |
|---|---|
| Multi-cloud | External dependency |
| GitOps friendly | Per-provider configuration |
| Automatic sync | Latency |
🔝 Back to table of contents
6 - Comparison and choice
Comparison table
| Solution | Complexity | GitOps Native | Multi-cluster | Cloud Native |
|---|---|---|---|---|
| Sealed Secrets | ⭐ | ✅ | ❌ | ❌ |
| SOPS | ⭐⭐ | ✅ (Flux) | ✅ | ⚠️ |
| Vault | ⭐⭐⭐ | ⚠️ | ✅ | ❌ |
| ESO | ⭐⭐ | ✅ | ✅ | ✅ |
Recommendations
| Scenario | Recommendation |
|---|---|
| Small team, getting started | Sealed Secrets |
| Flux, multi-env | SOPS |
| Enterprise, compliance | Vault |
| Multi-cloud, AWS/GCP/Azure | External Secrets |
🔝 Back to table of contents
7 - Hands-on exercises
Quiz
Q1. Why is base64 not sufficient for secrets?
Answer
Base64 is an encoding, not encryption. Anyone can decode it:
echo "c2VjcmV0" | base64 -d # secret
Q2. Which solution to choose for a quick start?
Answer
Sealed Secrets because:
- Simple to install
- No external dependency
- Native Kubernetes integration
- Intuitive CLI (kubeseal)
Exercise: Create a Sealed Secret
# 1. Créer le secret
kubectl create secret generic db-creds \
--from-literal=username=admin \
--from-literal=password=supersecret \
--dry-run=client -o yaml > secret.yaml
# 2. Chiffrer
kubeseal --format yaml < secret.yaml > sealed-secret.yaml
# 3. Vérifier
cat sealed-secret.yaml
Expected result
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: db-creds
namespace: default
spec:
encryptedData:
username: AgBj7...
password: AgCk9...
🔝 Back to table of contents
Key takeaways
- Never store plaintext secrets in Git (even in base64)
- Sealed Secrets: simple, one cluster
- SOPS: multi-provider, Flux native
- Vault: enterprise, audit, rotation
- External Secrets: cloud-native, multi-provider
- Choose based on complexity and needs