Partie 11: Migration de passbolt vers k3s

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

Chart.png

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/

scp.png

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

Argocd.png

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

  1. SealedSecrets et namespaces: Les SealedSecrets doivent être générés avec le bon namespace cible
  2. Ingress et TLS: Le backend doit être en HTTP (port 80), l'Ingress gère le HTTPS
  3. Permissions des clés: Les clés GPG/JWT doivent avoir les bonnes permissions pour l'utilisateur www-data
  4. 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

Ressources

Read more