Skip to content

feat(ra-console): registre des opérateurs dans le navigateur (étape 6e) - #73

Closed
PhilippeVienne wants to merge 10 commits into
feat/ra-console-frontend-revokefrom
feat/ra-console-frontend-operators
Closed

PhilippeVienne wants to merge 10 commits into
feat/ra-console-frontend-revokefrom
feat/ra-console-frontend-operators

Conversation

@PhilippeVienne

Copy link
Copy Markdown
Contributor

Objet

docs/WEBUI.md §10 et §15 étape 6, tranche 6e : un administrateur gère le registre des opérateurs depuis la console. Base : #72 (feat/ra-console-frontend-revoke). Cette branche contient aussi la fusion de #69 (routes de registre, écrites en parallèle sur #67) : merger #69 avant cette PR ; au rebase sur dev, le commit de fusion disparaît. Deux conflits résolus à la fusion (ajouts en fin de action_challenge.rs et de docs/RA-CONSOLE.md, les deux côtés conservés).

  • API : GET /api/v1/operators — opérateurs, clés, clés en attente, en lecture seule sur les tables de ca-server. L'empreinte d'une clé en attente est recalculée avec oe_actions::key_fingerprint, la fonction même de ca-server : l'administrateur la compare hors bande avec celle que l'invité a reçue (§10) ; une console qui afficherait une autre empreinte ferait échouer la confirmation, pas passer une autre clé.
  • Écran des opérateurs :
    • invitation — le jeton s'affiche une seule fois, à transmettre par un canal distinct ; il n'est conservé nulle part ;
    • confirmation d'une clé en attente, après avoir déclaré (case à cocher) avoir comparé l'empreinte avec l'invité ;
    • révocation d'une clé (motif obligatoire) ;
    • changement de rôle (le rôle admin exige un second administrateur : AWAITING_QUORUM, co-signature en salle de quorum de feat(ra-console): révocation et salle de quorum dans le navigateur (étape 6c) #72).
    • Pour les non-administrateurs, ces boutons sont désactivés — affichage seulement, ca-server juge.

Limites

  • La confirmation de clé n'est pas couverte par Playwright : l'enregistrement d'une clé par l'authentificateur virtuel serait refusé par la liste blanche d'attestation. Elle l'est par le test Rust de feat(ra-console): gestion du registre des opérateurs depuis la console #69, complété ici (empreinte affichée = empreinte remise à l'invitée, clé en attente disparue après confirmation).
  • Libre-service sur ses propres clés (ajout/retrait avec réassertion) : non fait.

Vérifications

  • Rust : an_admin_invites_and_confirms_an_operator_through_the_console étendu (lecture du registre, empreinte identique, 401 sans session) ; les 14 tests de action_challenge.rs passent après la fusion.
  • Playwright 10/10 en local sur trois passes : invitation (jeton affiché une fois, frank apparaît), changement de rôle de dave (vérifié côté serveur), révocation de sa clé ; consultation seule pour un non-administrateur. Mutation : forcer l'affichage « administrateur » fait échouer ce dernier parcours.
  • cargo fmt --check, cargo clippy --workspace --all-targets -- -D warnings (1.97 et 1.98.1), cargo test --workspace avec PostgreSQL, cargo audit --ignore RUSTSEC-2023-0071, npm run typecheck : verts.

Revue humaine obligatoire

Voir PROVENANCE.md. Chaque case est cochée par le
contributeur humain qui valide la PR, après l'avoir fait lui-même.

  • Revue d'architecture validée par l'humain
  • Code relu et tests unitaires/intégration vérifiés localement
  • Absence de dépendances tierces incompatibles avec la double licence EUPL-1.2 / AGPL-3.0 (make licenses)
  • Validation de l'apport intellectuel et de la paternité humaine sur la modification

Assistance par IA

  • Cette PR a été produite avec l'assistance de Claude Code : les commits concernés portent la remorque Co-authored-by: Claude <noreply@anthropic.com>, auteur et committer restent humains, et scripts/provenance.py archive a été lancé
  • Cette PR a été écrite sans assistance par IA

PhilippeVienne and others added 10 commits September 27, 2026 10:02
…pe 3a)

docs/WEBUI.md §15 étape 3, schéma de signature §4 étapes 1 à 3 :
`POST /api/v1/webauthn/challenge` relaie à ca-server
(`/internal/v1/challenge`) l'action demandée par l'opérateur connecté ;
ca-server la fige et rend le corps qu'il exécutera, son empreinte et les
options WebAuthn.

- Le challenge vise l'opérateur de la session : `operator_hint` vient de
  la session (Authenticated gagne `operator_id`), jamais du navigateur.
  L'action est relue dans l'énumération fermée d'oe_actions puis
  resérialisée : un champ en trop ne franchit pas la console.
- Seuls approve_request et reject_request sont préparés à ce stade (403
  action_not_available sinon, sans solliciter ca-server). Le rôle et
  l'état de la demande restent jugés par ca-server.
- Journal de la console : ra.action_challenge (AppState gagne `journal`).
- action_challenge.rs, contre le vrai service d'actions de ca-server et
  PostgreSQL ; trois mutations tuées (indice venu du navigateur, filtre
  de l'étape, session non exigée).
- docs/RA-CONSOLE.md mis à jour.

Co-authored-by: Claude <noreply@anthropic.com>
…pe 3b)

docs/WEBUI.md §15 étape 3, §4 étapes 5 à 7 : `POST
/api/v1/requests/{id}/approve|reject` relaie à ca-server
(`/internal/v1/actions`) l'identifiant du challenge et l'assertion brute,
jamais de corps : ca-server exécute celui qu'il a figé.

- Lien assertion ↔ demande contrôlé par ca-server (décision de
  l'utilisateur) : la console joint `expect` (action et demande du
  chemin) ; oe_actions::Service::execute_expecting le compare au corps
  figé avant de retirer l'état de la cérémonie, et refuse (409
  action_mismatch) s'il diffère. Une signature obtenue pour une demande
  ne décide jamais d'une autre, ni l'inverse de ce qui a été signé, et
  l'assertion reste utilisable sur la bonne route. `expect` ne peut que
  restreindre ; `execute` sans attente est inchangé.
- Réponse de la forme du §5 : `decided_by` est l'opérateur dont la clé a
  signé, lu dans le registre de ca-server.
- Journal de la console : ra.action_relayed.
- Tests de bout en bout (approbation, rejet, rejeu, mauvaise cible,
  corps non relayés) et unitaire d'Expect ; trois mutations tuées
  (contrôle retiré, contrôle après consommation, expect non envoyé).

Co-authored-by: Claude <noreply@anthropic.com>
…pe 4a)

docs/WEBUI.md §15 étape 4, §8 : la console prépare `revoke_certificate`
et relaie la signature par `POST /api/v1/certificates/{serial}/revoke`.
La politique de ca-server exige deux ca_operateur distincts : la
première signature est enregistrée, rien n'est révoqué
(AWAITING_QUORUM, 1/2). La co-signature est l'étape 4b.

- oe_actions::Expect gagne `serial` : ca-server compare le certificat de
  la route au corps figé avant toute consommation, comme pour une
  décision ; une cible sans rapport avec le type d'action est refusée.
- ra-console : relay_assertion factorise le relais d'une assertion
  (décisions et révocation) ; numéro de série exigé sous forme
  canonique (hexadécimal minuscule, 20 octets au plus) avant relais.
- Tests : harnais doté d'une vraie CA sur PostgreSQL et du révocateur de
  ca-server ; première signature sans révocation, mauvaise cible,
  forme non canonique, refus pour un ra_operateur. Deux mutations tuées.

Co-authored-by: Claude <noreply@anthropic.com>
…tape 4b)

docs/WEBUI.md §8, §15 étape 4 : deux ca_operateur distincts révoquent
ensemble depuis la console.

- Co-signature : `POST /api/v1/webauthn/challenge` accepte
  `{"action_id"}` (action existante, non exécutée, proposée à ce stade),
  puis `POST /api/v1/quorum/{action_id}/sign`. oe_actions::Expect gagne
  `action_id`, comparé avant toute consommation : une co-signature ne
  compte que pour l'action pour laquelle son challenge a été émis.
- Salle d'attente : `GET /api/v1/quorum?state=PENDING` lit `actions` et
  `decision_evidence` de ca-server en lecture seule (décision de
  l'utilisateur) : corps figé, empreinte, signatures, signataires. Le
  rôle de la console gagne SELECT sur `actions` (aucun secret n'y
  figure) ; le test de schéma est mis à jour.
- Écart assumé avec le §8, en plus sûr : pas de tables de collecte, la
  console ne conserve jamais d'assertion (ca-server enregistre chaque
  signature au fil de l'eau). WEBUI.md §8 dit ce qui est construit.
- Tests : révocation à deux de bout en bout, seconde signature du même
  opérateur refusée, exécution unique, co-signature présentée pour une
  autre action sur le même certificat refusée. Deux mutations tuées.
- Mise à jour d'un déploiement : rejouer ra_console_grants.sql.

Co-authored-by: Claude <noreply@anthropic.com>
docs/WEBUI.md §5, §10 : ra-console relaie, sur le schéma des étapes 3 et
4 (challenge puis exécution, cible contrôlée par ca-server), les actions
de registre qu'oe_actions exécute déjà : invite_operator, confirm_key,
revoke_key, set_role.

- Routes (module registry_routes) : POST /api/v1/operators,
  POST /api/v1/credentials/{credential_id}/confirm et /revoke,
  POST /api/v1/operators/{name}/role. Le jeton d'invitation n'existe que
  dans result.invite_token de la réponse, jamais journalisé.
- oe_actions::Expect gagne credential_id et operator ; le contrôle de
  cible devient générique (une cible d'un autre type est refusée), avant
  toute consommation de la cérémonie. Une invitation n'a pas de cible.
- Élever au rôle admin ou changer celui d'un administrateur : deux
  administrateurs, co-signature par /api/v1/quorum/{id}/sign (la route
  de signature joint désormais la cible des actions de registre).
- Tests de bout en bout : invitation puis enregistrement puis
  confirmation, jeton absent du journal de la console ; révocation de
  clé et changement de rôle où seule la cible distingue ; élévation admin
  à deux. Cinq mutations tuées.

Co-authored-by: Claude <noreply@anthropic.com>
docs/WEBUI.md §15 étape 6, docs/UI-UX.md : la console sert son interface,
en TypeScript compilé par esbuild (choix de l'utilisateur), sans
framework ni dépendance d'exécution.

- Assets embarqués dans le binaire (include_bytes!, src/web.rs) depuis
  web/dist, versionné : compiler ra-console n'exige pas Node.
- En-têtes de sécurité sur toutes les réponses, API comprise (UI-UX
  §6.3) : CSP stricte sans unsafe-inline ni CDN, frame-ancestors 'none',
  X-Frame-Options DENY, nosniff, Referrer-Policy, Cache-Control no-store.
  `http::app` compose l'API, le frontend et ces en-têtes.
- Bannière d'environnement (OPENEIDAS_RA_ENVIRONMENT, « non déclaré » à
  défaut), connexion par nom et clé FIDO2, poste de travail (identité et
  rôle relus sur le serveur, compteurs des files), déconnexion,
  verrouillage après 15 min d'inactivité (avertissement à 14). Aucun
  innerHTML : le contenu de l'API est inséré en texte.
- Tests : tests/web.rs (en-têtes et assets, sans navigateur) ; Playwright
  contre une console réelle (examples/e2e_console.rs, PostgreSQL), clé du
  SoftToken confiée à l'authentificateur WebAuthn virtuel de Chromium.
- CI : job frontend (typage strict, dist/ conforme aux sources, parcours
  de bout en bout). Dépendances npm de développement seulement (esbuild
  MIT, TypeScript et Playwright Apache-2.0), jamais dans l'image.

Co-authored-by: Claude <noreply@anthropic.com>
docs/WEBUI.md §15 étape 6, docs/UI-UX.md §3.1, §3.4, §5, §6.1 : les
décisions RA se prennent dans le navigateur, avec la clé FIDO2.

- File des demandes en attente : tableau dense, sélection au clavier
  (j/k, a approuver, r rejeter), inspecteur latéral.
- Justification avant signature, obligatoire pour un rejet (garde
  d'interface : attribut required et contrôle du script).
- Modale de signature (<dialog> natif) : corps figé par ca-server
  affiché tel quel avec son empreinte SHA-256, puis la clé ; erreur sans
  fermeture, challenge redemandé s'il est consommé ou expiré, Échap
  bloqué pendant la cérémonie matérielle.
- Harnais e2e : le vrai service d'actions de ca-server derrière le lien
  mTLS, et huit demandes en attente. Parcours Playwright : approbation
  (corps et empreinte affichés, état APPROVED côté serveur), rejet au
  clavier avec motif obligatoire, renoncement sans effet.

Co-authored-by: Claude <noreply@anthropic.com>
…tape 6c)

docs/WEBUI.md §8, §15 étape 6, docs/UI-UX.md §3.2 : deux opérateurs CA
distincts révoquent un certificat depuis la console.

- `GET /api/v1/certificates?status=issued|revoked` : certificats émis, en
  lecture seule sur la table de ca-server, numéro de série sous la forme
  canonique qu'attend la révocation (test Rust : liste, filtre, passage
  en « revoked » après double signature, refus sans session).
- Navigation entre demandes, certificats et quorum. Écran des
  certificats : motif RFC 5280 parmi ceux qu'admet ca-server,
  justification obligatoire, modale de signature ; la première
  signature part en salle de quorum.
- Salle de quorum : corps figé, empreinte, signataires ; co-signature
  désactivée pour qui a déjà signé (ca-server la refuserait aussi).
- Harnais e2e : trois opérateurs (alice RA, bob et carol CA), une CA
  sur la même base avec son révocateur, deux certificats. Parcours :
  révocation à deux de bout en bout, refus pour un opérateur RA.
  Mutation tuée (co-signature par soi-même non désactivée).

Co-authored-by: Claude <noreply@anthropic.com>
…' into feat/ra-console-frontend-operators

# Conflicts:
#	bin/ra-console/tests/action_challenge.rs
#	docs/RA-CONSOLE.md
docs/WEBUI.md §10, §15 étape 6 : un administrateur gère le registre
depuis la console, chaque écriture étant une action signée que ca-server
juge (routes de #69, fusionnées dans cette branche).

- `GET /api/v1/operators` : opérateurs, clés, clés en attente, en lecture
  seule ; l'empreinte d'une clé en attente est recalculée avec
  oe_actions::key_fingerprint, la fonction même de ca-server (test Rust :
  identique à celle remise à l'invitée).
- Écran des opérateurs : invitation (jeton affiché une seule fois),
  confirmation d'une clé en attente après déclaration de comparaison de
  l'empreinte hors bande, révocation de clé (motif obligatoire),
  changement de rôle (admin : second administrateur en salle de quorum).
  Boutons désactivés pour les non-administrateurs (affichage seulement).
- Harnais e2e : un administrateur et un opérateur de test. Parcours :
  invitation, changement de rôle, révocation de clé ; consultation seule
  pour un non-administrateur (mutation tuée).

Co-authored-by: Claude <noreply@anthropic.com>
@PhilippeVienne
PhilippeVienne force-pushed the feat/ra-console-frontend-revoke branch 3 times, most recently from 37e74fe to 2a3f381 Compare September 30, 2026 18:08
@PhilippeVienne
PhilippeVienne deleted the branch feat/ra-console-frontend-revoke September 30, 2026 18:42
@PhilippeVienne

Copy link
Copy Markdown
Contributor Author

Remplacée par #91 (base dev) : fermée par GitHub à la suppression de sa branche de base après la fusion de #90.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant