Partie 11: Migration de passbolt vers k3s
Passbolt c'est quoi ?
Passbolt est un gestionnaire de mots de passe open source conçu principalement pour les équipes et organisations, offrant une solution sécurisée pour stocker, gérer et partager des identifiants et secrets.
Non officielement certifié par l'ANSSI.
Le modèle de sécurité de Passbolt repose sur un chiffrement de bout en bout via OpenPGP.
Contexte
Passbolt tournait initialement sur un NAS Qnap via Docker Compose, avec une base de données MariaDB dédiée. L'objectif était de migrer cette installation vers un cluster K3s pour bénéficier des nouvelles fonctions:
- GitOps avec ArgoCD
- Gestion déclarative de l'infrastructure
- Certificats SSL automatiques via cert-manager
- Haute disponibilité
Architecture cible
┌─────────────────────────────────────────┐
│ Cluster K3s │
│ ┌───────────────────────────────────┐ │
│ │ Namespace: passbolt │ │
│ │ - Deployment Passbolt │ │
│ │ - PVC GPG (clés serveur) │ │
│ │ - PVC JWT (tokens) │ │
│ │ - Ingress (HTTPS + Let's Encrypt)│ │
│ │ - SealedSecrets (credentials) │ │
│ └───────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────┐ │
│ │ Namespace: blog │ │
│ │ - MySQL 8.0 (mutualisé) │ │
│ │ - Base: passbolt │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────┘
Étapes de migration
1. Création du Helm Chart Passbolt
Structure du chart:
helm-charts/passbolt/
├── Chart.yaml
├── values.yaml
└── templates/
├── passbolt-deployment.yaml
├── passbolt-service.yaml
├── passbolt-ingress.yaml
├── passbolt-pvc-gpg.yaml
├── passbolt-pvc-jwt.yaml
├── sealed-secret-mysql.yaml
└── sealed-secret-email.yaml

2. Configuration MySQL mutualisé
Au lieu de déployer un MySQL dédié, j'ai préféré utiliser le MySQL existant du namespace blog:
# values.yaml
mysql:
enabled: false # Pas de MySQL embarqué
external:
enabled: true
host: mysql.blog.svc.cluster.local
port: 3306
database: passbolt
user: passbolt
Création de la base de données:
# Connexion au pod MySQL
kubectl exec -n blog <mysql-pod> -- mysql -uroot -p'***' \
-e "CREATE DATABASE passbolt CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
# Création de l'utilisateur
kubectl exec -n blog <mysql-pod> -- mysql -uroot -p'***' \
-e "CREATE USER 'passbolt'@'%' IDENTIFIED BY '***';"
# Attribution des privilèges
kubectl exec -n blog <mysql-pod> -- mysql -uroot -p'***' \
-e "GRANT ALL PRIVILEGES ON passbolt.* TO 'passbolt'@'%'; FLUSH PRIVILEGES;"
3. Sécurisation avec SealedSecrets
Les mots de passe sont chiffrés avec Bitnami SealedSecrets avant d'être committés dans Git:
# Génération d'un SealedSecret pour MySQL
kubectl create secret generic passbolt-mysql \
--namespace=passbolt \
--from-literal='mysql-password=***' \
--dry-run=client -o yaml | \
kubeseal --controller-name=sealed-secrets-controller \
--controller-namespace=kube-system -o yaml > sealed-secret-mysql.yaml
Point important: Les SealedSecrets doivent être générés avec le bon namespace (--namespace=passbolt), sinon le déchiffrement échouera.
4. Configuration de l'Ingress
# passbolt-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod-dns"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
spec:
ingressClassName: nginx
tls:
- hosts:
- passbolt-test.home-fonta.fr
secretName: passbolt-test-tls
rules:
- host: passbolt-test.home-fonta.fr
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: passbolt
port:
number: 80 # Backend HTTP, TLS géré par Ingress
Piège évité: L'ingress doit pointer vers le port 80 (HTTP), car c'est Nginx Ingress Controller qui gère le TLS, pas l'application.
Mon premier essais n'es pas passé car j'ai mis directement le port 443.
5. Backup des données depuis le NAS
# Export de la base MariaDB
docker exec <mariadb-container> mysqldump -u passbolt -p'***' passbolt \
> /tmp/passbolt_backup.sql
# Export des clés GPG et JWT
docker cp <passbolt-container>:/etc/passbolt/gpg /tmp/passbolt_gpg_backup
docker cp <passbolt-container>:/etc/passbolt/jwt /tmp/passbolt_jwt_backup
# Transfert vers le cluster
scp /tmp/passbolt_backup.sql user@k3s-master:/tmp/
scp -r /tmp/passbolt_gpg_backup user@k3s-master:/tmp/
scp -r /tmp/passbolt_jwt_backup user@k3s-master:/tmp/

6. Import des données dans K3s
# Import de la base de données
kubectl cp /tmp/passbolt_backup.sql blog/<mysql-pod>:/tmp/
kubectl exec -n blog <mysql-pod> -- \
mysql -uroot -p'***' passbolt -e "source /tmp/passbolt_backup.sql"
# Vérification
kubectl exec -n blog <mysql-pod> -- \
mysql -uroot -p'***' -e "USE passbolt; SHOW TABLES;"
Résultat: 35 tables importées (users, secrets, gpgkeys, etc.)
7. Restauration des clés cryptographiques
# Copie des clés GPG dans le PVC
kubectl exec -n passbolt <passbolt-pod> -- rm -rf /etc/passbolt/gpg/*
kubectl cp /tmp/passbolt_gpg_backup/. passbolt/<passbolt-pod>:/etc/passbolt/gpg/
# Copie des clés JWT dans le PVC
kubectl exec -n passbolt <passbolt-pod> -- rm -rf /etc/passbolt/jwt/*
kubectl cp /tmp/passbolt_jwt_backup/. passbolt/<passbolt-pod>:/etc/passbolt/jwt/
# Vérification
kubectl exec -n passbolt <passbolt-pod> -- ls -la /etc/passbolt/gpg
# serverkey.asc, serverkey_private.asc
kubectl exec -n passbolt <passbolt-pod> -- ls -la /etc/passbolt/jwt
# jwt.key, jwt.pem
8. Redémarrage et validation
# Redémarrage de Passbolt pour charger les clés
kubectl rollout restart deployment -n passbolt passbolt
# Test du healthcheck
curl -k https://passbolt-test.home-fonta.fr/healthcheck/status.json
# {"header":{"status":"success"},"body":"OK"}
Configuration DNS
Création d'un enregistrement CNAME pour le sous-domaine de test:
# Via l'API Cloudflare
curl -X POST "https://api.cloudflare.com/client/v4/zones/<zone-id>/dns_records" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
--data '{
"type": "CNAME",
"name": "passbolt-test.home-fonta.fr",
"content": "ha.home-fonta.fr",
"proxied": false,
"ttl": 1
}'
Déploiement GitOps avec ArgoCD
# argocd-apps/applications/passbolt.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: passbolt
namespace: argocd
spec:
project: default
source:
repoURL: git@github.com:user/ansible-k3s.git
targetRevision: HEAD
path: helm-charts/passbolt
destination:
server: https://kubernetes.default.svc
namespace: passbolt
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

Résultats
✅ Migration réussie:
- Tous les utilisateurs et mots de passe migrés
- Connexion fonctionnelle sur
passbolt-test.home-fonta.fr - Certificat SSL automatique via Let's Encrypt
- Base de données partagée avec d'autres applications (blog)
Avantages obtenus:
- Infrastructure as Code (tout dans Git)
- Déploiement automatique via ArgoCD
- Pas de gestion manuelle des certificats
- Meilleure utilisation des ressources (MySQL mutualisé)
- Backups Kubernetes natifs des PVC
Points d'attention
- SealedSecrets et namespaces: Les SealedSecrets doivent être générés avec le bon namespace cible
- Ingress et TLS: Le backend doit être en HTTP (port 80), l'Ingress gère le HTTPS
- Permissions des clés: Les clés GPG/JWT doivent avoir les bonnes permissions pour l'utilisateur
www-data - Période de transition: Garder l'ancien Passbolt actif quelques jours pour valider
Prochaines étapes
- Valider le fonctionnement sur plusieurs jours
- Basculer vers le domaine de production (
passbolt.home-fonta.fr) - Arrêter l'instance Passbolt sur le NAS
- Configurer les backups automatiques des PVC
- Nettoyer les fichiers temporaires de migration