From 6faa2d34767bf174ec9f0ec576da97cd4f3b80fa Mon Sep 17 00:00:00 2001 From: Olivier <1+olivier@noreply.raspot.in> Date: Tue, 18 Aug 2026 17:33:49 +0200 Subject: [PATCH] =?UTF-8?q?T=C3=A9l=C3=A9verser=20les=20fichiers=20vers=20?= =?UTF-8?q?"modules"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/users.nix | 22 ++- modules/workstation-setup.nix | 291 ++++++++++++++++++++++++++++++++++ 2 files changed, 305 insertions(+), 8 deletions(-) create mode 100644 modules/workstation-setup.nix diff --git a/modules/users.nix b/modules/users.nix index cf44b37..c221246 100644 --- a/modules/users.nix +++ b/modules/users.nix @@ -1,11 +1,12 @@ { pkgs, ... }: { - # Les mots de passe peuvent être modifiés localement. - # Un nixos-rebuild ne réécrase pas le mot de passe choisi par l'utilisateur. + # Les mots de passe restent mutables : après la création du compte, + # le mot de passe choisi localement par l'utilisateur est conservé. users.mutableUsers = true; - # Compte d'administration local + # Compte d'administration local. + # Son mot de passe est créé/géré localement et n'est pas défini ici. users.users.localadm = { isNormalUser = true; description = "Administrateur local"; @@ -19,7 +20,7 @@ ]; }; - # Utilisateur standard + # Utilisateur standard. users.users.alice = { isNormalUser = true; description = "Alice"; @@ -27,8 +28,13 @@ # Alice n'est pas administratrice. extraGroups = [ ]; - # Utilisé uniquement lors de la création initiale du compte. - # Demo pass : LaboTest@1980 - initialHashedPassword = "$y$j9T$XiIgBUxXjqAYbQQs3eBtr.$dT01AYyRiBtjoaF1MIG83cx2h0bRxEILE6tpilE2V9D"; + # LAB uniquement. + # Mot de passe temporaire : LaboTest@1980 + # + # Avec users.mutableUsers = true, ce hash sert uniquement lors de la + # création initiale du compte. Après changement via passwd, le nouveau + # mot de passe local est conservé lors des activations suivantes. + initialHashedPassword = + "$6$VwAqDD0hXRojZr2T$5EVBlLOVnOvyJxde9uAGj.ehkiQaRis5SwTrSLuVAhiXXvG4DH9DNC.88pWfiL2O28wanR52.23INxuyEtNSQ/"; }; -} \ No newline at end of file +} diff --git a/modules/workstation-setup.nix b/modules/workstation-setup.nix new file mode 100644 index 0000000..7c2927b --- /dev/null +++ b/modules/workstation-setup.nix @@ -0,0 +1,291 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.nixosWorkstations.workstationSetup; + + passwordHelper = pkgs.writeTextFile { + name = "nixos-workstations-password-helper"; + executable = true; + text = '' + #!${pkgs.expect}/bin/expect -f + + # Les secrets arrivent uniquement par stdin depuis l'application. + # Ils ne sont jamais passés dans argv et aucun transcript n'est affiché. + log_user 0 + exp_internal 0 + set timeout 30 + + if {[gets stdin current] < 0 || [gets stdin newpass] < 0 || [gets stdin confirm] < 0} { + puts stderr "INPUT_ERROR" + exit 20 + } + + if {$newpass ne $confirm} { + puts stderr "CONFIRM_MISMATCH" + exit 21 + } + + spawn -noecho ${pkgs.coreutils}/bin/env LC_ALL=C LANG=C /run/wrappers/bin/passwd + + expect { + -re {(?i)(current.*password|unix password|password.*current).*:} { + send -- "$current\r" + } + eof { + puts stderr "CURRENT_REJECTED" + exit 22 + } + timeout { + puts stderr "TIMEOUT_CURRENT" + exit 124 + } + } + + # Efface la copie du mot de passe actuel dès qu'elle n'est plus utile. + set current "" + + expect { + -re {(?i)new.*password.*:} { + send -- "$newpass\r" + } + eof { + puts stderr "CURRENT_REJECTED" + exit 23 + } + timeout { + puts stderr "TIMEOUT_NEW" + exit 124 + } + } + + expect { + -re {(?i)(retype|repeat|confirm).*password.*:} { + send -- "$confirm\r" + } + -re {(?i)new.*password.*:} { + puts stderr "NEW_REJECTED" + exit 24 + } + eof { + puts stderr "NEW_REJECTED" + exit 25 + } + timeout { + puts stderr "TIMEOUT_CONFIRM" + exit 124 + } + } + + set newpass "" + set confirm "" + + expect eof + set waitResult [wait] + set exitCode [lindex $waitResult 3] + + if {$exitCode == 0} { + puts "OK" + exit 0 + } + + puts stderr "PASSWD_FAILED" + exit $exitCode + ''; + }; + + pinHelper = pkgs.writeTextFile { + name = "nixos-workstations-pin-helper"; + executable = true; + text = '' + #!${pkgs.expect}/bin/expect -f + + # Même principe que pour passwd : aucune valeur secrète dans argv. + log_user 0 + exp_internal 0 + set timeout 30 + + if {[gets stdin current] < 0 || [gets stdin newpin] < 0 || [gets stdin confirm] < 0} { + puts stderr "INPUT_ERROR" + exit 30 + } + + if {$newpin ne $confirm} { + puts stderr "CONFIRM_MISMATCH" + exit 31 + } + + spawn -noecho ${pkgs.coreutils}/bin/env LC_ALL=C LANG=C ${pkgs.yubikey-manager}/bin/ykman fido access change-pin + + set transcript "" + + expect { + -re {Enter your current PIN.*:} { + append transcript $expect_out(buffer) + send -- "$current\r" + } + -re {No YubiKey|Failed to connect|No FIDO} { + puts stderr "NO_YUBIKEY" + exit 32 + } + eof { + append transcript $expect_out(buffer) + if {[regexp -nocase {No YubiKey|Failed to connect|No FIDO} $transcript]} { + puts stderr "NO_YUBIKEY" + } else { + puts stderr "PIN_FAILED" + } + exit 33 + } + timeout { + puts stderr "TIMEOUT_CURRENT_PIN" + exit 124 + } + } + + set current "" + + expect { + -re {Enter your new PIN.*:} { + append transcript $expect_out(buffer) + send -- "$newpin\r" + } + eof { + append transcript $expect_out(buffer) + puts stderr "PIN_FAILED" + exit 34 + } + timeout { + puts stderr "TIMEOUT_NEW_PIN" + exit 124 + } + } + + expect { + -re {(?i)(repeat|confirm).*:} { + append transcript $expect_out(buffer) + send -- "$confirm\r" + } + eof { + append transcript $expect_out(buffer) + puts stderr "PIN_FAILED" + exit 35 + } + timeout { + puts stderr "TIMEOUT_CONFIRM_PIN" + exit 124 + } + } + + set newpin "" + set confirm "" + + expect eof + append transcript $expect_out(buffer) + set waitResult [wait] + set exitCode [lindex $waitResult 3] + + if {$exitCode == 0} { + puts "OK" + exit 0 + } + + if {[regexp -nocase {Wrong PIN|PIN_INVALID} $transcript]} { + puts stderr "WRONG_PIN" + } elseif {[regexp -nocase {authentication is currently blocked|PIN_AUTH_BLOCKED} $transcript]} { + puts stderr "PIN_AUTH_BLOCKED" + } elseif {[regexp -nocase {PIN is blocked|PIN_BLOCKED} $transcript]} { + puts stderr "PIN_BLOCKED" + } elseif {[regexp -nocase {No YubiKey|Failed to connect|No FIDO} $transcript]} { + puts stderr "NO_YUBIKEY" + } elseif {[regexp -nocase {complexity|policy|at least|at most} $transcript]} { + puts stderr "PIN_POLICY" + } else { + puts stderr "PIN_FAILED" + } + + exit $exitCode + ''; + }; + + workstationSetup = pkgs.stdenv.mkDerivation { + pname = "nixos-workstations-setup"; + version = "1.0.0"; + + src = ../workstation-setup; + + nativeBuildInputs = [ + pkgs.cmake + pkgs.ninja + pkgs.pkg-config + pkgs.kdePackages.wrapQtAppsHook + ]; + + buildInputs = with pkgs.kdePackages; [ + qtbase + qtdeclarative + qtwayland + kirigami + ]; + + cmakeFlags = [ + "-DPASSWORD_HELPER_PATH=${passwordHelper}" + "-DPIN_HELPER_PATH=${pinHelper}" + "-DTARGET_USER=${cfg.user}" + ]; + }; + + launcher = pkgs.writeShellScript "nixos-workstations-setup-launcher" '' + set -eu + + if [ "$(${pkgs.coreutils}/bin/id -un)" != ${lib.escapeShellArg cfg.user} ]; then + exit 0 + fi + + state_dir="''${XDG_STATE_HOME:-$HOME/.local/state}/nixos-workstations" + password_marker="$state_dir/password-initialized" + pin_marker="$state_dir/yubikey-pin-initialized" + + if [ -e "$password_marker" ] && [ -e "$pin_marker" ]; then + exit 0 + fi + + # Ne jamais générer de core dump contenant potentiellement un secret. + ulimit -c 0 + + # Laisse Plasma finir son démarrage sans créer de logique de supervision + # susceptible de tuer ou manipuler la session utilisateur. + ${pkgs.coreutils}/bin/sleep 3 + + exec ${workstationSetup}/bin/nixos-workstations-setup + ''; + +in +{ + options.nixosWorkstations.workstationSetup = { + enable = lib.mkEnableOption "assistant plein écran de finalisation du poste"; + + user = lib.mkOption { + type = lib.types.str; + default = "alice"; + description = "Utilisateur devant effectuer la personnalisation initiale."; + }; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = [ + workstationSetup + pkgs.yubikey-manager + ]; + + environment.etc."xdg/autostart/nixos-workstations-setup.desktop".text = '' + [Desktop Entry] + Type=Application + Name=Finalisation du poste + Comment=Personnalisation sécurisée des moyens d'authentification + Exec=${launcher} + OnlyShowIn=KDE; + NoDisplay=true + StartupNotify=false + ''; + }; +}