Skip to main content

Secrets management


Table of Contents

  1. The secrets problem
  2. Sealed Secrets
  3. SOPS
  4. HashiCorp Vault
  5. External Secrets Operator
  6. Comparison and choice
  7. 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

SolutionTypeComplexity
Sealed SecretsAsymmetric encryptionLow
SOPSFile encryptionMedium
VaultCentralized managementHigh
External SecretsSync from providerMedium

🔝 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

ProsCons
Simple to usePrivate key in the cluster
Kubernetes nativeManual rotation
No external dependencyOne 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

ProsCons
Multi-provider (AWS KMS, GCP, Age)Initial configuration
Readable diffKey to manage
Flux nativeNot 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

ProsCons
Centralized managementOperational complexity
Automatic rotationCost (Enterprise)
Audit trailSingle point of failure
Dynamic secretsLearning 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

ProsCons
Multi-cloudExternal dependency
GitOps friendlyPer-provider configuration
Automatic syncLatency

🔝 Back to table of contents


6 - Comparison and choice

Comparison table

SolutionComplexityGitOps NativeMulti-clusterCloud Native
Sealed Secrets
SOPS⭐⭐✅ (Flux)⚠️
Vault⭐⭐⭐⚠️
ESO⭐⭐

Recommendations

ScenarioRecommendation
Small team, getting startedSealed Secrets
Flux, multi-envSOPS
Enterprise, complianceVault
Multi-cloud, AWS/GCP/AzureExternal 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

🔝 Back to table of contents


← Previous chapter | Next chapter: Multi-environments →