Téléverser les fichiers vers "modules"

This commit is contained in:
2026-08-19 11:40:56 +02:00
parent c7e98d14a4
commit 2ab3c19731
3 changed files with 36 additions and 77 deletions
+7 -40
View File
@@ -1,44 +1,11 @@
{ ... }: { ... }:
{ {
# Module NixOS officiel : installe YubiKey Manager et les règles udev # Étape 1 LAB : la YubiKey est uniquement initialisée par le setup.
# nécessaires pour que ykman puisse accéder à la clé en session utilisateur. # Aucune authentification PAM/FIDO2 n'est activée à ce stade et aucun
# credential spécifique à une clé n'est conservé dans Git.
#
# YubiKey Manager apporte notamment les règles udev permettant l'accès
# utilisateur à la clé. L'enrôlement systemd-homed viendra à l'étape 3.
programs.yubikey-manager.enable = true; programs.yubikey-manager.enable = true;
}
# Déploiement du mapping utilisateur <-> YubiKey
environment.etc."u2f-mappings".source = ../u2f-mappings;
# Authentification PAM avec FIDO2 / YubiKey
security.pam.u2f = {
enable = true;
# Une authentification YubiKey réussie suffit.
# Le mot de passe reste disponible comme mécanisme de secours
# pour les comptes qui ne s'authentifient pas par FIDO2.
control = "sufficient";
settings = {
authfile = "/etc/u2f-mappings";
# Identifiant commun à tous nos futurs postes.
origin = "pam://nixos-workstations";
appid = "pam://nixos-workstations";
# Indique à l'utilisateur qu'il doit toucher la clé.
cue = true;
# Le PIN FIDO2 est maintenant obligatoire.
pinverification = 1;
# Pas de biométrie / vérification utilisateur intégrée.
# La vérification repose explicitement sur le PIN.
userverification = 0;
};
};
# Le changement de mot de passe via `passwd` doit rester une opération
# PAM/Unix classique. Le module U2F global ne doit pas intercepter
# l'authentification demandée par `passwd`, sinon le helper attend une
# interaction FIDO2 qu'il ne doit pas gérer à cette étape.
security.pam.services.passwd.u2f.enable = false;
}
-1
View File
@@ -35,7 +35,6 @@ in
outlookWeb outlookWeb
# FIDO2 / YubiKey # FIDO2 / YubiKey
pam_u2f
libfido2 libfido2
yubikey-manager yubikey-manager
]; ];
+29 -36
View File
@@ -136,15 +136,20 @@ let
executable = true; executable = true;
text = '' text = ''
#!${pinPython}/bin/python3 #!${pinPython}/bin/python3
"""Change a FIDO2 PIN without exposing secrets in argv. """Initialize the PIN of a virgin FIDO2 authenticator.
STEP 1 LAB contract:
- exactly one FIDO2 HID device must be connected;
- the authenticator must support CTAP2;
- no FIDO2 PIN must currently be configured;
- the new PIN and its confirmation arrive on stdin;
- secrets are never placed in argv or emitted in output.
Input on stdin, one UTF-8 line each: Input on stdin, one UTF-8 line each:
1. current PIN 1. new PIN
2. new PIN 2. confirmation
3. confirmation
Output contains technical tokens only, never the PIN values. Output contains technical tokens only, never PIN values.
Exactly one CTAP2 change_pin operation is attempted.
""" """
import sys import sys
@@ -167,22 +172,16 @@ let
return value.rstrip("\r\n") return value.rstrip("\r\n")
current = read_secret()
new_pin = read_secret() new_pin = read_secret()
confirmation = read_secret() confirmation = read_secret()
if new_pin != confirmation: if new_pin != confirmation:
fail("CONFIRM_MISMATCH", 31) fail("CONFIRM_MISMATCH", 31)
if len(current) < 4 or len(new_pin) < 4: if len(new_pin) < 4 or len(new_pin.encode("utf-8")) > 63:
fail("PIN_POLICY", 32)
# CTAP2 PINs are UTF-8 strings with a maximum encoded size of 63 bytes.
if len(current.encode("utf-8")) > 63 or len(new_pin.encode("utf-8")) > 63:
fail("PIN_POLICY", 32) fail("PIN_POLICY", 32)
devices = [] devices = []
device = None
try: try:
devices = list(CtapHidDevice.list_devices()) devices = list(CtapHidDevice.list_devices())
@@ -201,57 +200,51 @@ let
ctap = Ctap2(device) ctap = Ctap2(device)
info = ctap.get_info() info = ctap.get_info()
# clientPin=True means a PIN is currently configured. # Étape 1 stricte : on ne doit jamais tenter de valider/changer
if not info.options.get("clientPin", False): # un PIN existant. Ce contrôle ne consomme aucune tentative de PIN.
fail("PIN_NOT_CONFIGURED", 36) if info.options.get("clientPin", False):
fail("PIN_ALREADY_CONFIGURED", 36)
client_pin = ClientPin(ctap) client_pin = ClientPin(ctap)
client_pin.set_pin(new_pin)
# IMPORTANT: exactly one PIN-changing command. Do not verify the old PIN # GET_INFO ne consomme pas de tentative de PIN. Vérifier que la clé
# first: that would add an unnecessary extra PIN operation. # annonce désormais bien clientPin=True avant de déclarer le succès.
client_pin.change_pin(current, new_pin) if not ctap.get_info().options.get("clientPin", False):
fail("PIN_STATE_NOT_UPDATED", 37)
# Forget Python references as soon as the operation is complete.
current = ""
new_pin = "" new_pin = ""
confirmation = "" confirmation = ""
print("OK", flush=True) print("OK", flush=True)
raise SystemExit(0) raise SystemExit(0)
except CtapError as exc: except CtapError as exc:
code = exc.code code = exc.code
if code == CtapError.ERR.PIN_INVALID:
fail("WRONG_PIN", 40)
if code == CtapError.ERR.PIN_AUTH_BLOCKED: if code == CtapError.ERR.PIN_AUTH_BLOCKED:
fail("PIN_AUTH_BLOCKED", 41) fail("PIN_AUTH_BLOCKED", 41)
if code == CtapError.ERR.PIN_BLOCKED: if code == CtapError.ERR.PIN_BLOCKED:
fail("PIN_BLOCKED", 42) fail("PIN_BLOCKED", 42)
if code == CtapError.ERR.PIN_NOT_SET:
fail("PIN_NOT_CONFIGURED", 43)
if code == CtapError.ERR.PIN_POLICY_VIOLATION: if code == CtapError.ERR.PIN_POLICY_VIOLATION:
fail("PIN_POLICY", 44) fail("PIN_POLICY", 44)
if code == CtapError.ERR.PIN_NOT_SET:
fail("PIN_STATE_ERROR", 45)
fail("PIN_FAILED", 45) fail("PIN_FAILED", 46)
except (PermissionError, OSError): except (PermissionError, OSError):
fail("NO_YUBIKEY", 46) fail("NO_YUBIKEY", 47)
except ValueError: except ValueError:
# python-fido2 may reject an invalid PIN before sending CTAP2. fail("PIN_POLICY", 48)
fail("PIN_POLICY", 47)
except SystemExit: except SystemExit:
raise raise
except Exception: except Exception:
# Do not expose exception text: it could contain device/environment fail("PIN_HELPER_ERROR", 49)
# details and is not needed by the UI. The setup log stores the token.
fail("PIN_HELPER_ERROR", 48)
finally: finally:
current = ""
new_pin = "" new_pin = ""
confirmation = "" confirmation = ""
for dev in devices: for dev in devices:
@@ -264,7 +257,7 @@ let
workstationSetup = pkgs.stdenv.mkDerivation { workstationSetup = pkgs.stdenv.mkDerivation {
pname = "nixos-workstations-setup"; pname = "nixos-workstations-setup";
version = "1.5.0"; version = "1.6.0";
src = ../workstation-setup; src = ../workstation-setup;
@@ -301,7 +294,7 @@ let
state_dir="''${XDG_STATE_HOME:-$HOME/.local/state}/nixos-workstations" state_dir="''${XDG_STATE_HOME:-$HOME/.local/state}/nixos-workstations"
password_marker="$state_dir/password-initialized" password_marker="$state_dir/password-initialized"
pin_marker="$state_dir/yubikey-pin-initialized" pin_marker="$state_dir/yubikey-pin-created"
# Une session déjà finalisée n'affiche plus l'assistant. # Une session déjà finalisée n'affiche plus l'assistant.
if [ -e "$password_marker" ] && [ -e "$pin_marker" ]; then if [ -e "$password_marker" ] && [ -e "$pin_marker" ]; then