Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,446 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user