{ 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 ''; }; }