# 06 — Import des utilisateurs depuis Furious Squad > Procédure complète qui a permis d'importer 88 collaborateurs Seize de Furious vers Zitadel le 5 juin 2026. --- ## 🎯 Objectif Synchroniser les **88 collaborateurs Seize actifs** depuis Furious Squad (source de vérité RH) vers Zitadel (Identity Provider), avec : - 1 compte Zitadel par collaborateur - Metadata Furious préservées (job_title, manager, BU, etc.) - Mots de passe temporaires générés - Possibilité d'attribuer des rôles RBAC ensuite --- ## 🏗️ Architecture du process ``` Furious API (REST) │ │ HTTP Basic Auth (FURIOUS_USERNAME:PASSWORD) ▼ read-all-users.sh ──────────► furious-users-raw.json │ │ ▼ import-furious-to-zitadel.py │ ┌───────────────┼───────────────┐ │ │ │ dry-run test prod (rien) (3 users) (85 users) │ ▼ Zitadel API v2 ──── service account │ furious-import-service ▼ 88 users créés │ ▼ users-credentials.csv (mdps temporaires) │ ▼ distribution-teams.csv (3 colonnes) │ ▼ Distribution manuelle via Teams ``` --- ## 📁 Structure des scripts ``` ~/furious-to-zitadel/ ├── .env # FURIOUS_USERNAME, PASSWORD, etc. ├── venv/ # Environnement Python ├── zitadelfuriousimportkey.json # ⚠️ Clé service account (chmod 600) │ ├── read-all-users.sh # Lecture Furious → JSON ├── test-auth.sh # Test connexion API Furious ├── test-1-user.sh # Test lecture d'un user spécifique │ ├── import-furious-to-zitadel.py # ⭐ Script principal d'import ├── merge-duplicates.py # Fusion des doublons (HKA, PSP, ...) ├── reset-test-users-password.py # Reset mdp des users de test ├── extract-distribution-csv.py # Génère CSV pour distribution ├── generate-roles-mapping.py # Génère JSON mapping email → rôle ├── assign-roles.py # Attribue les rôles dans Zitadel │ ├── furious-users-raw.json # Données brutes Furious ├── users-credentials.csv # 89 lignes avec mdps ├── distribution-teams.csv # 88 lignes pour Teams ├── roles-mapping.json # 87 mappings email → rôle └── import.log # Logs d'exécution ``` --- ## 📄 Configuration `.env` ```env FURIOUS_USERNAME= FURIOUS_PASSWORD= FURIOUS_API_URL=https://seize-consulting.furious-squad.com/api/v2 ``` ⚠️ `chmod 600 .env` --- ## 🔑 Création du compte API Furious ### 1. Connexion Furious Connexion à `https://seize-consulting.furious-squad.com` avec un compte admin. ### 2. Configuration → Générer un utilisateur d'API - Nom : `import-zitadel` (ou similaire) - Permissions : - ☑️ User > Search (lecture seule) - ❌ Tout le reste (principe du moindre privilège) - Generate ### 3. Récupérer les identifiants Format identifiant : `.<6chiffres>` (exemple : `import-zitadel.123456`) + Mot de passe généré aléatoirement. ⚠️ **Sauvegarder immédiatement dans le gestionnaire de mots de passe.** ### 4. Tester ```bash cd ~/furious-to-zitadel source venv/bin/activate ./test-auth.sh # Doit retourner un JSON avec une réponse valide (pas 401/403) ``` --- ## 🤖 Création du service account Zitadel ### 1. Console Zitadel → Users → New - **Type** : Service user - **Username** : `furious-import-service` - **Access Token Type** : **JWT** (important !) ### 2. Ajouter au rôle IAM_USER_MANAGER - Aller dans Default Settings (top-right) → Administrators → New - Sélectionner le service user `furious-import-service` - Cocher **IAM_USER_MANAGER** - ❌ Ne PAS cocher IAM_OWNER ### 3. Générer la clé JSON - Profil du service user → Keys → New - Type : **JSON** - Expiration : 1 an (ou jamais selon besoin) - **Download immediately** → fichier `.json` - ⚠️ La clé n'est jamais re-affichée ### 4. Transférer sur le serveur ```bash # Depuis le PC (Windows PowerShell ou Linux) scp 375992380685287427.json hedi@:~/furious-to-zitadel/zitadelfuriousimportkey.json # Sur le serveur ssh hedi@ chmod 600 ~/furious-to-zitadel/zitadelfuriousimportkey.json ``` --- ## 🐍 Environnement Python ```bash cd ~/furious-to-zitadel python3 -m venv venv source venv/bin/activate pip install requests pyjwt cryptography ``` --- ## 🚀 Procédure d'import — pas à pas ### Étape 1 — Lire les users depuis Furious ```bash cd ~/furious-to-zitadel source venv/bin/activate ./read-all-users.sh ``` → Génère `furious-users-raw.json` (29 Ko, 94 users dont 88 actifs). ### Étape 2 — Test d'authentification Zitadel ```bash # Test que la clé Zitadel marche python3 -c " import json data = json.load(open('zitadelfuriousimportkey.json')) print('UserId:', data.get('userId')) print('KeyId:', data.get('keyId')) print('Type:', data.get('type')) " ``` ### Étape 3 — Dry-run (simulation, aucune écriture) ```bash python3 import-furious-to-zitadel.py --mode dry-run ``` → Affiche **ce que ferait** le script sans rien créer. Doit afficher : ``` [DRY-RUN] Would create: yves.canauxdebonfils@seizeconsulting.com (Yves Canaux de Bonfils) [DRY-RUN] Would create: oussama.azzabi@seizeconsulting.com (Oussama Azzabi) ... Total : 88 users would be created ``` ### Étape 4 — Test sur 3 users ```bash python3 import-furious-to-zitadel.py --mode test ``` → Crée **3 users** dans Zitadel (les furious_id 1, 2, 3). **Vérification dans Zitadel UI** : Console → Users → vérifier que les 3 users sont créés avec leurs metadata. ### Étape 5 — Import production ⚠️ **Étape sensible — ne pas interrompre.** ```bash python3 import-furious-to-zitadel.py --mode prod | tee -a import.log ``` → Crée les **85 users restants**. Durée : ~2 minutes. Logs en temps réel + sauvegarde dans `import.log`. ### Étape 6 — Vérification ```bash # Compter les users créés dans Zitadel via API python3 -c " # Voir le script pour récupérer le count " # Compter les lignes du CSV de credentials wc -l users-credentials.csv # Doit afficher : 89 (1 header + 88 users) ``` --- ## 🔀 Gestion des doublons ### Contexte 4 anciens admins existaient déjà dans Zitadel avant l'import : - HKA (Hedi Kalboussi) → ID `375119988404518915` - PSP (Philippe Spoljaric) → ID `375120224527056899` - PYT (Pierre-Yves Tachon) → ID `375778940960112643` - Jules (Jules Bataille) → ID `375777592659148803` L'import a créé des **doublons** avec leurs emails complets. ### Stratégie Voir [DECISIONS.md D5](../DECISIONS.md). En bref : - **Garder l'ancien** compte (avec MFA configuré) - **Supprimer le nouveau** doublon - **Copier les metadata Furious** du nouveau vers l'ancien (pour pas perdre l'info) ### Exécution ```bash # 1. Éditer le script merge-duplicates.py pour adapter le mapping nano merge-duplicates.py # Configurer la variable DUPLICATES : DUPLICATES = [ { "keep_user_id": "375119988404518915", # HKA ancien "delete_email": "hedi.kalboussi@seizeconsulting.com", "furious_id": 5, }, # ... 3 autres ] # 2. Lancer python3 merge-duplicates.py ``` → Pour chaque doublon : 1. Lit le user "nouveau" via email 2. Copie ses metadata sur l'ancien 3. Supprime le nouveau --- ## 🎭 Attribution des rôles RBAC ### Prérequis Avant d'attribuer des rôles, créer les 8 rôles dans Zitadel (Project Infra Seize). Voir [07-rbac-intranet.md](./07-rbac-intranet.md) pour la création des rôles. ### Mapping job_title → rôle Voir `matrices/job-title-to-role.csv` pour la matrice complète. Résumé : ```python JOB_TITLE_TO_ROLE = { "président": "direction", "directeur": "direction", "directeur associé": "direction", "senior manager": "manager", "sénior manager": "manager", "manager": "manager", "manager bi": "manager", "expert": "expert", "expert n2": "expert", "consultant expert niveau 1": "expert", "consultant sénior": "consultant_senior", "consultant expérimenté": "consultant_senior", "consultant expérimentée": "consultant_senior", "consultant junior": "consultant_junior", "stagiaire": "stagiaire", "assistante": "support", # ... etc } # Cas spécial : status="apprenti" → role="alternant" ``` ### Exécution ```bash # 1. Générer le mapping email → role python3 generate-roles-mapping.py # 2. Attribuer les rôles dans Zitadel python3 assign-roles.py ``` → Le script : 1. Lit chaque user actif Furious 2. Détermine son rôle (via mapping job_title ou status) 3. Cherche le user dans Zitadel par email 4. Lui attribue le rôle via API `POST /v1/users/{id}/grants` **Vérification** : Console Zitadel → Users → 1 user → Authorizations → doit voir le rôle attribué. --- ## 📊 Format CSV produit ### `users-credentials.csv` (sensible, à protéger) ```csv email,prenom,nom,trigramme,furious_id,zitadel_user_id,mdp_temporaire yves.canauxdebonfils@seizeconsulting.com,Yves,Canaux de Bonfils,YCB,1,375990xxxxxxxxxxx,Y8sR3kp9XmF2 ... ``` ### `distribution-teams.csv` (pour distribution mdp) ```csv Nom complet,Email,Mot de passe Yves Canaux de Bonfils,yves.canauxdebonfils@seizeconsulting.com,Y8sR3kp9XmF2 ... ``` --- ## ⚠️ Anti-patterns à éviter 1. ❌ **Ne PAS sauter le dry-run** : permet de détecter des erreurs avant impact prod 2. ❌ **Ne PAS importer avec rôles directement** : importer d'abord, attribuer ensuite (cf. D6) 3. ❌ **Ne PAS donner IAM_OWNER au service account** : utiliser IAM_USER_MANAGER 4. ❌ **Ne PAS oublier de supprimer le CSV** après distribution des mdps 5. ❌ **Ne PAS coller les identifiants Furious** dans une conversation IA --- ## 🔄 Synchronisation périodique (à mettre en place) Aujourd'hui, l'import est **manuel** (one-shot du 5 juin). À l'avenir : ### Option recommandée : workflow n8n quotidien Workflow n8n à 3h du matin : 1. **HTTP Request** vers Furious : récupérer la liste des users 2. **HTTP Request** vers Zitadel : récupérer la liste des users actuels 3. **Code node** : calculer le diff 4. **Switch** : - Nouveau dans Furious → POST Zitadel pour créer - Départ dans Furious (departure_date != "0000-00-00") → PATCH Zitadel pour désactiver - Changement job_title → PATCH metadata + réattribuer rôle 5. **Slack/Teams notification** : résumé du sync ⏱️ Estimation : 1 journée de dev n8n. --- ## 🐛 Troubleshooting import ### "Erreur 401 lors de l'auth Zitadel" Causes possibles : 1. Clé JSON corrompue → re-télécharger depuis Zitadel 2. Service account n'a plus le rôle IAM_USER_MANAGER → re-attribuer 3. Token JWT mal généré → vérifier que `pyjwt` est bien installé ### "User déjà existant" lors de l'import Normal pour les doublons. Le script `import-furious-to-zitadel.py` skip les users existants par défaut. ### "Erreur 400 sur certains users" Causes courantes : 1. **Email invalide** (espace, caractères spéciaux) dans Furious → corriger dans Furious 2. **Mot de passe ne respecte pas la policy** → le script génère mdp 16 chars random 3. **Username déjà pris** → choisir un autre format (email complet par exemple) ### "Le script lit les bonnes données mais Zitadel ne les voit pas" Vérifier la **réponse HTTP** retournée par chaque création : ```bash python3 import-furious-to-zitadel.py --mode test --verbose ``` --- ## 📚 Voir aussi - [07-rbac-intranet.md](./07-rbac-intranet.md) — Suite de l'import : attribution des rôles RBAC - [03-zitadel-setup.md](./03-zitadel-setup.md) — Création du service account Zitadel - [../matrices/job-title-to-role.csv](../matrices/job-title-to-role.csv) — Matrice de mapping