Skip to content

feat(actions): récupération de l'administrateur (recover-admin) et garde du quorum impossible - #24

Merged
PhilippeVienne merged 1 commit into
devfrom
feat/recover-admin
Sep 21, 2026
Merged

PhilippeVienne merged 1 commit into
devfrom
feat/recover-admin

Conversation

@PhilippeVienne

Copy link
Copy Markdown
Contributor

PR empilée sur #22 (la pile : #19 → #20 → #21 → #22 → celle-ci). Base : feat/quorum. À rebrancher sur dev après chaque merge : gh api -X PATCH repos/open-eidas/open-eidas/pulls/N -f base=dev.

Contenu

Première tranche du §21 de docs/WEBUI.md (modes de défaillance de ca-server).

ca-server operators recover-admin

Rend la main quand les administrateurs « actifs » ont perdu leurs clés sans qu'elles soient révoquées : bootstrap-admin refuse à raison (un administrateur actif existe) et personne n'a plus l'autorité de les révoquer. Le système est verrouillé.

read -rs PIN
printf %s "$PIN" | ca-server operators recover-admin bob \
    --reason "clé de alice perdue, ticket 1234" --confirm-recovery --pin-stdin
  • Garde de l'hôte : le PIN du token PKCS#11 est lu sur l'entrée standard (jamais en argument ni en variable d'environnement) et doit ouvrir le token de la CA émettrice. La valeur de OPENEIDAS_ISSUING_PIN du service, lisible de tout accès shell, n'est volontairement pas ce qui compte. Un PIN refusé est consigné (operators.admin_recovery_refused).
  • Motif écrit et drapeau explicite obligatoires.
  • Crée (ou ré-invite) un administrateur sans rien désactiver du registre existant : la clé perdue est ensuite révoquée par une action signée du nouvel administrateur.
  • Écrit avant de valider l'événement distinct operators.admin_recovery (motif, nombre d'administrateurs actifs, jamais le jeton). Journal en échec : rien n'est créé.
  • La clé de la récupération entre directement dans le registre (initiated_by = confirmed_by = 'recover-admin'), sans confirmation d'un tiers : il n'y en a plus. Contrairement à l'invitation d'amorçage, elle ne contrôle pas l'administrateur actif, puisque c'est son cas d'usage.

Garde « quorum impossible »

Une action à N signatures n'est plus créée si le rôle compte moins de N titulaires actifs (non désactivés, au moins une clé non révoquée). La laisser en attente ferait croire qu'un second signataire va venir.

Divers

  • Invite obtient un Debug qui masque le jeton : un {:?} ne peut pas le laisser passer dans un journal. Clippy relève alors un expect() dans un test d'amorçage de dev, corrigé.
  • docs/CA.md documente bootstrap-admin et recover-admin.

Points d'attention

  • La politique de récupération reste à décider (O8) : témoin requis, deux détenteurs du PIN, revue a posteriori. Le code impose le PIN, le motif et la trace, pas le témoin.
  • Un seul administrateur créé ne suffit pas à en élever un autre (deux signatures) : lancer la commande une seconde fois.
  • Le test de la commande avec le vrai PIN exige softhsm2-util et s'ignore sans lui : la CI n'en dispose pas (à vérifier), il y tournera donc à vide. Je l'ai exécuté en local avec SoftHSM ; à traiter comme les tests PostgreSQL (TODO §1).
  • Hors périmètre, restant dans le §21 : operators audit (re-vérification de la chaîne de signatures du registre) et operators reconcile (rejeu du journal au démarrage, /healthz en 503 sur divergence). Deux chantiers plus lourds, en TODO.

Vérifications

  • cargo fmt --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace (avec PostgreSQL, 67 suites), cargo audit --ignore RUSTSEC-2023-0071 : verts.
  • 5 tests de récupération sur PostgreSQL réel : le scénario complet (système verrouillé → bootstrap-admin refuse → récupération → le nouvel administrateur révoque la clé perdue par une action signée), ré-invitation d'un administrateur existant, usage unique, refus (motif vide ou trop long, nom d'un autre rôle, administrateur désactivé, durée abusive) sans rien créer, journal en échec.
  • 1 test de la garde du quorum impossible, 1 test de la commande avec un vrai SoftHSM (PIN correct présenté alors que l'environnement du service en porte un faux, et inversement).
  • Contrôle par mutation : garde du quorum retirée ; contrôle de l'administrateur actif étendu à la récupération ; récupération qui désactive les autres administrateurs ; PIN non vérifié ; PIN de l'environnement du service comparé au lieu du PIN présenté. Chacun fait échouer le test visé.

…rde du quorum impossible

ca-server operators recover-admin <nom> --reason <motif> --confirm-recovery
--pin-stdin : rend la main quand les administrateurs « actifs » ont perdu
leurs clés et que personne n'a plus l'autorité de les révoquer, ce que
bootstrap-admin refuse à raison (docs/WEBUI.md §21).

- garde de l'hôte : le PIN du token PKCS#11 est lu sur l'entrée standard,
  jamais en argument ni en variable d'environnement, et doit ouvrir le token
  de la CA émettrice. La valeur de OPENEIDAS_ISSUING_PIN du service, lisible
  de tout accès shell, n'est pas ce qui compte. Un PIN refusé est consigné
  (operators.admin_recovery_refused) ;
- un motif écrit et un drapeau explicite sont obligatoires ;
- crée (ou ré-invite) un administrateur sans rien désactiver du registre
  existant ; la clé perdue est ensuite révoquée par une action signée ;
- écrit avant de valider l'événement distinct operators.admin_recovery (motif,
  nombre d'administrateurs actifs, jamais le jeton) ; journal en échec, rien
  n'est créé ;
- la clé de la récupération entre directement dans le registre, sans
  confirmation d'un tiers, à la différence de l'amorçage sans contrôle de
  l'administrateur actif, puisque c'est précisément son cas d'usage.

Garde « quorum impossible » : une action à N signatures n'est plus créée si
le rôle compte moins de N titulaires actifs (non désactivés, au moins une clé
non révoquée). La laisser en attente ferait croire qu'un second signataire va
venir.

Invite obtient un Debug qui masque le jeton, pour qu'un {:?} ne puisse pas le
laisser passer dans un journal. clippy relève alors un expect() dans un test
d'amorçage, corrigé.

docs/CA.md documente bootstrap-admin et recover-admin.
@PhilippeVienne
PhilippeVienne changed the base branch from feat/quorum to dev September 21, 2026 09:40
@PhilippeVienne
PhilippeVienne merged commit b30e8a1 into dev Sep 21, 2026
26 checks passed
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