Files
Omika/docs/DEPLOIEMENT_PRODUCTION_DIAGNOSTICS.md
T
2026-10-23 18:46:50 +02:00

24 KiB
Raw Blame History

Déploiement Production — Diagnostics multi-serveurs (T19/T20)

Statut : préparation (aucun déploiement réel effectué). Document de référence pour le futur déploiement contrôlé du Diagnostic Center central et de l'API métier/mobile chez chaque client. Aucune valeur de secret (clé, mot de passe) n'est contenue ici.


1. Responsabilités des composants (§30)

Composant Rôle Installation
Omika.Api API métier/mobile installée chez chaque client (licence, bootstrap, métier, POST /api/diagnostics). Le mobile ne connaît QUE cette API. Serveur du client
Omika.Diagnostics.Api API centrale, installée uniquement sur le serveur Omika. Reçoit les lots des serveurs clients (POST /api/v1/diagnostics/batches), authentifie (ClientCode + ApiKey), impose le ClientCode authentifié, écrit le spool central. Aucune logique métier. Serveur central Omika
Omika.Diagnostics.Worker Import du spool central JSONL → PostgreSQL (diagnostic_event). Déduplication par event_id. Serveur central Omika
Omika.Diagnostics.Web Interface de consultation/administration (Dashboard, Diagnostics, Erreurs, Télémétrie, Référentiel, Clients Diagnostics, Système). Serveur central Omika

Le mobile ne connaît jamais Omika.Diagnostics.Api/Worker/Web, ni PostgreSQL Diagnostics, ni aucune clé central. Le serveur client ne connaît jamais la ConnectionString PostgreSQL centrale (§43) : seul le composant central autorisé détient cette configuration.


2. Architecture de flux

APPAREILS MOBILES ──HTTPS──▶ SERVEUR DU CLIENT
                              Omika.Api (+ Omika.Worker métier)
                              └─▶ Spool local borné (DiagnosticsForwarding.Spool)
                                    └─▶ DiagnosticsForwarder (BackgroundService dans Omika.Api)
                                          └─ HTTPS ──▶ SERVEUR OMIKA CENTRAL
                                                        Omika.Diagnostics.Api
                                                        └─▶ Spool central (Diagnostics:Central)
                                                              └─▶ Omika.Diagnostics.Worker
                                                                    └─▶ PostgreSQL omika_diagnostics
                                                                          └─▶ Omika.Diagnostics.Web

3. Services Windows cibles (§51)

Serveur client

  • OmikaApi (process Omika.Api ; contient le forwarder de diagnostics — aucun service dédié).
  • OmikaWorker (Worker de synchronisation métier, service réel existant).
  • API Licence / service existant (inchangé).

Serveur central Omika

  • OmikaDiagnosticsApi (API centrale, Kestrel).
  • OmikaDiagnosticsWorker (import JSONL → PostgreSQL).
  • OmikaDiagnosticsWeb (interface, Kestrel derrière reverse proxy Apache).
  • PostgreSQL (omika_diagnostics).

Les noms de services sont posés par les scripts d'activation serveur (sc.exe / New-Service) et doivent correspondre aux valeurs UseWindowsService(...) du code.


4. Ports (§52)

Service Port local Développement Port Production (à confirmer selon convention existante)
Omika.Api (client) 5097 défini par Kestrel:Endpoints / appsettings.Production.json (loopback derrière Apache)
Omika.Diagnostics.Api (central) 5098 à définir (loopback Kestrel derrière Apache/HTTPS)
Omika.Diagnostics.Web (central) 5080 à définir (loopback Kestrel derrière Apache/HTTPS)
PostgreSQL 5432 5432 (loopback central uniquement)

Règle : les services web sont exposés en HTTPS via le reverse proxy Apache ; Kestrel reste en HTTP sur loopback. Aucun port n'est choisi arbitrairement : la convention Apache/Kestrel existante de omika.applilogik.fr s'applique.


5. HTTPS (§37/§38)

  • Le flux serveur client → API centrale est HTTPS uniquement en Production.
  • DiagnosticsForwarding:Endpoint doit être une URL https://….
  • Règle T20 : si l'environnement est Production et que l'Endpoint est en http://, le forwarder refuse l'envoi (log explicite une fois), le métier continue (fail-open), le spool local est conservé. Omika.Api n'est jamais arrêté à cause de Diagnostics.
  • Le certificat TLS réel n'est pas configuré dans ce document (à poser sur le reverse proxy).

6. Configuration Omika.Api (§31/§32/§33/§34/§35)

Section DiagnosticsForwarding (dans src/Omika.Api/appsettings.json, surchargée par variables d'environnement / appsettings.Production.json non versionné) :

Clé Défaut Rôle
DiagnosticsForwarding:Enabled false Kill switch. DiagnosticsForwarding__Enabled=true uniquement lors du déploiement serveur client.
DiagnosticsForwarding:Endpoint "" URL centrale absolue HTTPS (ex. https://…/api/v1/diagnostics/batches). Laisser vide tant qu'aucune URL réelle.
DiagnosticsForwarding:ApiKey "" Secret à installer sur le serveur client via DiagnosticsForwarding__ApiKey. Jamais dans Git/log/rapport.
DiagnosticsForwarding:TimeoutSeconds 10 Timeout réseau d'un envoi (le forwarder est background : jamais de blocage métier).
DiagnosticsForwarding:BatchSize 100 Événements max par lot.
DiagnosticsForwarding:MaxBatchBytes 524288 Taille max d'un lot (premier seuil).
DiagnosticsForwarding:RetryInitialSeconds 15 Backoff initial.
DiagnosticsForwarding:RetryMaxMinutes 30 Plafond du backoff.
DiagnosticsForwarding:Spool:MaxFileSizeMb 10 Rotation de fichier.
DiagnosticsForwarding:Spool:MaxTotalSizeMb 1024 Quota disque total.
DiagnosticsForwarding:Spool:RetentionDays 14 Rétention.

Le défaut Enabled=false est volontaire (sécurité, opt-in). Aucun placeholder fictif (https://diagnostics.exemple.fr) ne doit devenir une valeur réelle accidentellement.


7. Configuration Omika.Diagnostics.Api (§36)

Section Diagnostics (dans src/Omika.Diagnostics.Api/appsettings.json, surchargée par variables) :

Clé Défaut Rôle
ConnectionStrings:DiagnosticsDatabase (ou Diagnostics:ConnectionString) "" PostgreSQL central (secret, injecté serveur).
Diagnostics:Central:Directory logs/diagnostics-central Spool central — en Production, chemin explicite (voir §9).
Diagnostics:Central:MaxRequestBodyBytes 524288 Corps de requête borné.
Diagnostics:Central:MaxEventsPerBatch 100 Événements max par lot.
Diagnostics:Central:SchemaVersion 1 Version de schéma de transport.

URL/port : via ASPNETCORE_URLS (ou Kestrel:Endpoints) derrière le reverse proxy HTTPS.


8. Spool client — emplacement Production (§40)

Le service Windows peut démarrer depuis C:\Windows\System32 : ne jamais dépendre du current working directory. Utiliser un chemin explicite configurable :

C:\ProgramData\Omika\Diagnostics\Spool

(via Observability:Remote:Directory / DiagnosticsForwarding — à poser dans la configuration serveur client). Le quota (MaxTotalSizeMb) et la rétention (RetentionDays) évitent un remplissage disque : à quota atteint, les fichiers les plus anciens sont supprimés (log du nombre), jamais le fichier actif, jamais d'échec métier.


9. Spool central — emplacement Production (§41)

Même réflexion pour Omika.Diagnostics.Api :

C:\ProgramData\Omika\Diagnostics\Central

(via Diagnostics:Central:Directory). Le Worker central lit ce dossier (import JSONL → PostgreSQL, checkpoint import-checkpoint.json).


10. Permissions Windows (§42)

Principe de moindre privilège (jamais Administrateur inutilement) :

Compte service Dossier Droits
OmikaApi C:\ProgramData\Omika\Diagnostics\Spool lecture + écriture
OmikaDiagnosticsApi C:\ProgramData\Omika\Diagnostics\Central lecture + écriture
OmikaDiagnosticsWorker C:\ProgramData\Omika\Diagnostics\Central lecture (checkpoint → écriture du checkpoint selon le dossier)

11. PostgreSQL (§43/§54)

  • Base dédiée omika_diagnostics. PostgreSQL ne doit jamais être exposé aux serveurs clients : seuls les composants centraux autorisés y accèdent (firewall : loopback central uniquement).
  • Rôles : voir migrations 003/004/009/010 (omika_diagnostics_owner, _worker, _web, _api).
    • omika_diagnostics_api (LOGIN) : Omika.Diagnostics.Api — SELECT + UPDATE(last_seen_at_utc) sur diagnostics_clients uniquement. Aucun droit sur diagnostics_users.
    • omika_diagnostics_web (LOGIN) : Omika.Diagnostics.Web — SELECT sur diagnostic_event ; SELECT/INSERT/UPDATE sur diagnostics_clients (admin /Clients) ; SELECT/INSERT/UPDATE (colonnes password_hash, role, enabled, updated_at_utc, password_changed_at_utc, last_login_at_utc) + USAGE séquence sur diagnostics_users (admin /Users + login).
    • omika_diagnostics_worker (LOGIN) : Omika.Diagnostics.Worker — INSERT/SELECT/DELETE sur diagnostic_event ; aucun droit sur diagnostics_clients ni diagnostics_users.
  • Le serveur client NE DOIT PAS connaître la ConnectionString PostgreSQL centrale.

12. Migrations — inventaire (§44/§45/§46)

Ordre d'application sur le serveur central : 001 → 002 → 003 → 004 → 005 → 006 → 007 → 008 → 009 → 010.

Fichier But Développement appliqué ? Production nécessaire ?
001_diagnostics_schema.sql diagnostic_event + telemetry_metric_hourly + index + déduplication event_id. Oui Oui
002_actor_key.sql Colonnes actor_key/actor_key_version (HMAC pseudonymisation). Oui Oui
003_diagnostics_roles.sql Rôles/grants serveur (owner/worker/web), moindre privilège. Non (à exécuter sur le serveur PostgreSQL Production, en superuser) Oui
004_diagnostics_retention.sql Grant SELECT, DELETE au worker pour la rétention bornée. Non (idem, serveur Production) Oui
005_diagnostics_client_context.sql Colonnes client_code/server_instance/environment. Oui Oui
006_diagnostics_device_license.sql Colonnes appareil/licence. Oui Oui
007_diagnostics_usage.sql Colonnes screen_code/feature_code. Oui Oui
008_diagnostics_clients.sql Table diagnostics_clients (registre des serveurs clients, hash SHA-256). Oui Oui
009_diagnostics_clients_grants.sql Ownership + GRANT moindre privilège de diagnostics_clients (_api/_web), rôle omika_diagnostics_api. Non (serveur PostgreSQL Production, superuser) Oui
010_diagnostics_web_users.sql Table diagnostics_users (utilisateurs Web) + ownership omika_diagnostics_owner + GRANT moindre privilège _web uniquement (aucun droit API/Worker). Non (serveur PostgreSQL Production, superuser) Oui

003/004/009/010 : contiennent des CREATE ROLE / ALTER ROLE / GRANT au niveau serveur/instance. Elles ne sont pas appliquées sur le PC de développement (elles exigent le superuser PostgreSQL Production et fixent les comptes de service). Elles devront être exécutées sur le serveur PostgreSQL Production, dans l'ordre, avant le pointage des comptes de service Worker/Web/Api (omika_diagnostics_worker / omika_diagnostics_web / omika_diagnostics_api), puis les mots de passe posés manuellement (\password, jamais dans un script/log). Ne pas les exécuter localement « pour du vert ».

T22 : 008 crée diagnostics_clients après 003/004, donc sans ownership ni GRANT applicatif. 009 transfère la propriété à omika_diagnostics_owner (même propriétaire logique que diagnostic_event/telemetry_metric_hourly), crée le rôle omika_diagnostics_api (absent du modèle 003), et pose les GRANT minimum identifiés par l'audit des consommateurs réels : API = SELECT + UPDATE(last_seen_at_utc) ; Web = SELECT/INSERT/UPDATE(enabled, api_key_hash, key_prefix, key_created_at_utc) + USAGE séquence ; Worker = aucun. 009 est idempotent (re-exécutable sans erreur) et utilise current_database() pour GRANT CONNECT (portable, testé sur omika_diagnostics_preprod).


13. Authentification ApiKey — audit final (§55/§56)

  • Clé : 32 octets aléatoires, Base64Url, préfixe omkdiag_.
  • Stockage : SHA-256 hexadécimal (64) uniquement (diagnostics_clients.api_key_hash) + key_prefix.
  • Vérification : CryptographicOperations.FixedTimeEquals ; hash factice si client inconnu (anti-timing).
  • 401 : client inconnu OU mauvaise clé (indistinguables). 403 : client désactivé.
  • Tenant autoritaire : tout ClientCode payload ≠ authentifié → 400 + anomalie (jamais de confiance au payload).
  • Réponses sans fuite : jamais de hash, de clé, de stacktrace, ni l'existence précise d'un client.

14. Procédure — nouveau client Production (§73)

  1. Omika.Diagnostics.Web → Clients Diagnostics (Admin).
  2. Nouveau client.
  3. Saisir le CodeClient exact (ex. BATIK.01, normalisé UPPER(TRIM)).
  4. Créer le client → la clé est générée.
  5. Copier immédiatement la clé (bouton « Copier »).
  6. Installer la clé sur le serveur client : variable DiagnosticsForwarding__ApiKey.
  7. Configurer DiagnosticsForwarding__Endpoint (URL centrale HTTPS).
  8. Activer : DiagnosticsForwarding__Enabled=true.
  9. Redémarrer OmikaApi.
  10. Vérifier last_seen_at_utc dans Clients Diagnostics.
  11. Vérifier un diagnostic réel (Dashboard / Diagnostics).

15. Clé perdue (§74)

La clé n'est pas récupérable (hash seul stocké). Si elle n'a pas été copiée : faire Régénérer la clé, puis mettre à jour le serveur client avec la nouvelle clé.


16. Rotation de clé (§75)

Version V1 : régénération → ancienne clé immédiatement invalide. Pendant l'intervalle où le serveur client n'a pas encore la nouvelle clé, les envois échouent en 401 et le spool local s'accumule (backlog borné, reprise automatique). Acceptable.


17. Panne centrale (§76)

  • Ne pas arrêter OmikaApi (le métier continue).
  • Ne pas vider le spool manuellement (les événements seront renvoyés).
  • Vérifier l'espace disque (quota/rétention bornent le spool).
  • Vérifier les logs du forwarder.
  • Attendre le rétablissement : reprise automatique (backoff borné 15 s → 30 min).

18. Disque spool (§77)

  • MaxTotalSizeMb (défaut 1024) : quota disque total ; à dépassement, suppression des fichiers les plus anciens (jamais le fichier actif, log du nombre supprimé).
  • RetentionDays (défaut 14) : purge des fichiers plus anciens.
  • MaxFileSizeMb (défaut 10) : rotation de fichier.
  • Fail-open absolu : aucune de ces opérations ne bloque le métier.

Validation empirique T22 (pipeline réel, panne centrale simulée) :

  • Quota MaxTotalSizeMb=1 : 6000 événements fake (~3,6 Mo) → DiagnosticsForwarder a abandonné 14 fichier(s) de spool, spool final 768 Ko (≤ 1 Mo), fichier actif préservé, /health 200 (aucun crash métier), Working Set stable ~115–125 Mo (backlog sur disque ≠ RAM).
  • Rétention RetentionDays=1 : fichier ancien (-2 j) supprimé, fichier récent + fichier actif conservés, central DOWN pendant tout le test (la maintenance ne dépend pas du central).
  • Correctif T22 : EnforceBounds (quota/rétention) était appelé uniquement quand le spool était vide ; pendant une panne centrale (backlog jamais vide), le quota n'était donc pas appliqué. Il est désormais appelé à chaque passe du forwarder, indépendamment du backlog. Test : ForwardOnce_BacklogOverQuota_StillEnforcesBounds.

19. Ordre de démarrage (reprise/rollback)

Démarrage central : PostgreSQL → OmikaDiagnosticsWorker → OmikaDiagnosticsApi → OmikaDiagnosticsWeb.

Démarrage client : OmikaWorker (métier) → OmikaApi (métier + forwarder).

Rollback : DiagnosticsForwarding__Enabled=false + redémarrage OmikaApi coupe immédiatement tout envoi central sans toucher au métier ni au spool (aucune perte de données).


20. Dépendance SQLite (mobile / Desktop local)

Mise à jour T22 — audit + correctif CVE.

  • Version retenue : Microsoft.Data.Sqlite 10.0.10 (inchangé) + SQLitePCLRaw 2.1.12 épinglé transitivement (Directory.Packages.props, CentralPackageTransitivePinningEnabled=true).
  • Raison : SQLitePCLRaw.lib.e_sqlite3 2.1.11 (transitif de Microsoft.Data.Sqlite 10.0.10) embarque une bibliothèque SQLite native < 3.50.2, vulnérable à CVE-2025-6965 / GHSA-2m69-gcr7-jv3q (troncature d'entier dans findOrCreateAggInfoColumn, resolve.c). Correctif = SQLite native 3.50.2, livré par le patch 2.1.12 (famille 2.x, drop-in, ABI-compatible). Pas de saut vers SQLitePCLRaw 3.x (breaking) ni vers Microsoft.Data.Sqlite 11 (majeur).
  • Projets concernés : Omika.App.Local (offline-first : cache métier, brouillons, sync, DiagnosticOutboxSqliteStore) → Omika.App → têtes Omika.App.Desktop / Omika.App.Android / Omika.App.iOS. Aucun impact sur Omika.Api, Omika.Worker ni les projets Diagnostics.
  • Validation :
    • dotnet restore OK ; dotnet list package --include-transitive --vulnerable → « aucun package vulnérable » ; arbre confirmé SQLitePCLRaw.* = 2.1.12.
    • Tests SQLite/Outbox ciblés : 148/148.
    • Builds Release : Omika.App.Desktop 0 erreur, Omika.App.Android 0 erreur.
    • Omika.App.Desktop (Debug, flux dev) démarré réellement : ouverture omika.db OK + bootstrap DEV BOOTSTRAP OK (aucun crash SQLite).

Surface réellement exposée : la vulnérabilité nécessite une requête SQL construite avec un nombre extrême de termes d'agrégation ; Omika n'exécute que des requêtes applicatives fixes et paramétrées (jamais de SQL utilisateur), sur une base locale non accessible à distance. Le correctif 2.1.12 est néanmoins appliqué (moindre coût, aucun risque de régression constaté).


21. Gestion des utilisateurs Diagnostics.Web (T23)

21.1 Distinction fondamentale

Concept Définition Identifiant Stockage
Utilisateur Diagnostics personne qui ouvre Omika.Diagnostics.Web username diagnostics_users (hash PBKDF2-SHA256)
Client Diagnostics serveur client qui envoie des événements ClientCode diagnostics_clients (hash SHA-256)

Les deux registres sont totalement séparés et ne doivent jamais être confondus.

21.2 Modèle utilisateur

  • Table diagnostics_users (migration 010) : id, username (unique, insensible à la casse), password_hash, role (Support / Admin), enabled, created_at_utc, updated_at_utc, password_changed_at_utc, last_login_at_utc.
  • Aucun mot de passe en clair : seule la colonne password_hash existe. Le hash est produit par DiagnosticPasswordHasher (PBKDF2-SHA256, 100 000 itérations, sel aléatoire).
  • Aucune suppression physique (V1) : un utilisateur est désactivé (enabled=false), jamais DELETE.

21.3 Rôles

Rôle Accès
Support Dashboard, Diagnostics, Erreurs, Traces, Synchronisation, Telemetry, Parc, Versions, Référentiel, Mon compte.
Admin Tout Support + Clients Diagnostics + Utilisateurs + Système.

/System, /Clients et /Users sont Admin uniquement. Un Support y obtient un refus (redirection /AccessDenied). Un Admin conserve tous les droits de consultation Support.

21.4 Actions d'administration (/Users, Admin uniquement)

  • Création : saisie username + password + confirmation + rôle. Le Web hache automatiquement (DiagnosticPasswordHasher.Hash) ; l'Admin ne génère/copie jamais un hash.
  • Désactivation / Réactivation (enabled=false/true).
  • Changement de rôle Support → Admin / Admin → Support.
  • Réinitialisation du mot de passe (nouveau mot de passe + confirmation → hash + UPDATE).

21.5 Protections

  • Dernier Admin : impossible de désactiver ou rétrograder le dernier Admin actif (« Impossible de retirer le dernier administrateur actif. »).
  • Auto-désactivation : un Admin ne peut pas désactiver son propre compte.
  • Auto-rétrogradation : un Admin ne peut pas retirer son propre rôle Admin.

21.6 Règles de validation

  • Username : obligatoire, trim, unicité insensible à la casse, borné (≤ 64), caractères lettres / chiffres / . / - / _.
  • Mot de passe : non vide, ≥ 12 caractères, confirmation identique. Pas de politique de complexité superflue (V1).

21.7 Connexion (login hybride)

  1. Si le username existe dans diagnostics_users → la DB est autoritaire (jamais de fallback).
    • Vérification PBKDF2 (DiagnosticPasswordHasher.Verify) ; enabled=false → refus.
  2. Sinon → fallback opérateurs DiagnosticsAuth:Operators (bootstrap temporaire).
  3. Message d'échec générique : « Identifiants invalides. » (jamais « inconnu » / « désactivé » / « mauvais mot de passe »).
  • last_login_at_utc est mis à jour (best-effort) après un login DB réussi.
  • Anti-bruteforce léger (in-memory, mono-instance) : 5 échecs / 5 min → blocage 10 min (clé = username + IP). Aucun Redis, aucune session distribuée.
  • Anti-timing : hash factice calculé quand le username est inconnu (même principe que l'ApiKey).
  • Cookie : HttpOnly, SameSite=Lax, Secure=Always hors Développement, expiration 12 h (glissante). Production = HTTPS.
  • Un utilisateur désactivé avec un cookie existant est re-déconnecté à la prochaine re-vérification DB (fréquence bornée ~60 s, cache mémoire mono-instance) ; au pire ~1 minute de grâce, jamais « plusieurs jours ».

21.8 « Mon compte » (/Account)

  • Tout utilisateur authentifié peut changer son propre mot de passe : mot de passe actuel (vérifié réellement) + nouveau + confirmation. En cas de mot de passe actuel faux → refus sans modifier le hash.
  • Les opérateurs bootstrap (configuration) ne peuvent pas changer leur mot de passe ici.

22. Bootstrap du premier Admin Production (T23)

Production démarre sans utilisateur DB. Conserver la possibilité de configurer temporairement un opérateur bootstrap (DiagnosticsAuth:Operators) pour créer le premier Admin DB.

22.1 Générer le hash bootstrap (hors ligne, pas d'endpoint Web)

Outil console dédié (jamais un endpoint Web anonyme) :

dotnet run --project tools/Omika.DiagnosticHashTool -- "<mot-de-passe-bootstrap>"
# ou, pour éviter de laisser le mot de passe dans l'historique du shell :
dotnet run --project tools/Omika.DiagnosticHashTool -- --stdin

⚠️ Le mot de passe bootstrap ne doit jamais être dans Git, dans le rapport, ni dans un appsettings versionné. Seul le hash est placé dans la configuration / le secret store Production.

22.2 Procédure premier déploiement (12 étapes)

  1. Déployer Omika.Diagnostics.Web (services + reverse proxy HTTPS, cf. §3-§5).
  2. Configurer temporairement l'opérateur bootstrap Admin (hash seulement) : DiagnosticsAuth__Operators__0__Username / ...__PasswordHash / ...__Roles__0=Admin.
  3. Démarrer le service OmikaDiagnosticsWeb.
  4. Se connecter avec l'opérateur bootstrap.
  5. Aller sur /Users.
  6. Créer l'Admin DB permanent (username + mot de passe + rôle Admin).
  7. Se déconnecter.
  8. Tester le login avec l'Admin DB (plus le bootstrap).
  9. Retirer l'opérateur bootstrap de la configuration (DiagnosticsAuth:Operators = []).
  10. Redémarrer le service OmikaDiagnosticsWeb.
  11. Confirmer que le bootstrap est refusé (login → « Identifiants invalides. »).
  12. Confirmer que l'Admin DB fonctionne (Dashboard / Diagnostics / Clients / Users).

22.3 Installation Production neuve

Si PostgreSQL Diagnostics Production est créé de zéro : appliquer 001 → 010 dans l'ordre (jamais uniquement les dernières). Si une installation existe déjà : appliquer uniquement les migrations manquantes, dans l'ordre.