318 lines
8.6 KiB
Nix
318 lines
8.6 KiB
Nix
{ 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 placés dans argv et le transcript enfant n'est pas affiché.
|
|
log_user 0
|
|
exp_internal 0
|
|
set timeout 30
|
|
|
|
proc fail {token code} {
|
|
puts stderr $token
|
|
exit $code
|
|
}
|
|
|
|
if {[gets stdin current] < 0 || [gets stdin newpass] < 0 || [gets stdin confirm] < 0} {
|
|
fail "INPUT_ERROR" 20
|
|
}
|
|
|
|
if {$newpass ne $confirm} {
|
|
fail "CONFIRM_MISMATCH" 21
|
|
}
|
|
|
|
spawn -noecho ${pkgs.coreutils}/bin/env LC_ALL=C LANG=C /run/wrappers/bin/passwd
|
|
|
|
# Étape 1 : passwd doit demander le mot de passe actuel.
|
|
expect {
|
|
-re {(?i)(current|old|unix).*password.*:} {
|
|
send -- "$current\r"
|
|
}
|
|
-re {(?i)new.*password.*:} {
|
|
fail "CURRENT_PROMPT_MISSING" 22
|
|
}
|
|
-re {(?i)(authentication failure|incorrect password|password unchanged)} {
|
|
fail "CURRENT_REJECTED" 23
|
|
}
|
|
eof {
|
|
fail "CURRENT_REJECTED" 24
|
|
}
|
|
timeout {
|
|
fail "TIMEOUT_CURRENT" 124
|
|
}
|
|
}
|
|
|
|
set current ""
|
|
|
|
# Étape 2 : le nouveau mot de passe n'est envoyé qu'après validation
|
|
# du mot de passe actuel par passwd/PAM.
|
|
expect {
|
|
-re {(?i)new.*password.*:} {
|
|
send -- "$newpass\r"
|
|
}
|
|
# Certains passwd redemandent immédiatement le mot de passe courant
|
|
# après une erreur. On s'arrête sans envoyer une seconde tentative.
|
|
-re {(?i)(current|old|unix).*password.*:} {
|
|
fail "CURRENT_REJECTED" 25
|
|
}
|
|
-re {(?i)(authentication failure|incorrect password|password unchanged)} {
|
|
fail "CURRENT_REJECTED" 25
|
|
}
|
|
eof {
|
|
fail "CURRENT_REJECTED" 26
|
|
}
|
|
timeout {
|
|
fail "TIMEOUT_NEW" 124
|
|
}
|
|
}
|
|
|
|
# Étape 3 : confirmation. Un nouveau prompt "New password" à ce stade
|
|
# signifie que PAM/passwd a refusé la valeur proposée.
|
|
expect {
|
|
-re {(?i)(retype|repeat|confirm).*password.*:} {
|
|
send -- "$confirm\r"
|
|
}
|
|
-re {(?i)new.*password.*:} {
|
|
fail "NEW_REJECTED" 27
|
|
}
|
|
eof {
|
|
fail "NEW_REJECTED" 28
|
|
}
|
|
timeout {
|
|
fail "TIMEOUT_CONFIRM" 124
|
|
}
|
|
}
|
|
|
|
set newpass ""
|
|
set confirm ""
|
|
|
|
expect {
|
|
eof {}
|
|
-re {(?i)new.*password.*:} {
|
|
fail "NEW_REJECTED" 29
|
|
}
|
|
timeout {
|
|
fail "TIMEOUT_FINISH" 124
|
|
}
|
|
}
|
|
|
|
set waitResult [wait]
|
|
set exitCode [lindex $waitResult 3]
|
|
|
|
if {$exitCode == 0} {
|
|
puts "OK"
|
|
exit 0
|
|
}
|
|
|
|
fail "PASSWD_FAILED" $exitCode
|
|
'';
|
|
};
|
|
|
|
pinHelper = pkgs.writeTextFile {
|
|
name = "nixos-workstations-pin-helper";
|
|
executable = true;
|
|
text = ''
|
|
#!${pkgs.expect}/bin/expect -f
|
|
|
|
# Une seule invocation de ykman par clic : aucune tentative automatique
|
|
# supplémentaire n'est faite en cas de mauvais PIN.
|
|
log_user 0
|
|
exp_internal 0
|
|
set timeout 30
|
|
|
|
proc fail {token code} {
|
|
puts stderr $token
|
|
exit $code
|
|
}
|
|
|
|
if {[gets stdin current] < 0 || [gets stdin newpin] < 0 || [gets stdin confirm] < 0} {
|
|
fail "INPUT_ERROR" 30
|
|
}
|
|
|
|
if {$newpin ne $confirm} {
|
|
fail "CONFIRM_MISMATCH" 31
|
|
}
|
|
|
|
spawn -noecho ${pkgs.coreutils}/bin/env LC_ALL=C LANG=C ${pkgs.yubikey-manager}/bin/ykman fido access change-pin
|
|
|
|
set transcript ""
|
|
|
|
# Une clé déjà provisionnée doit demander le PIN actuel. Si ykman passe
|
|
# directement au nouveau PIN, la clé n'a pas le pré-provisionnement attendu.
|
|
expect {
|
|
-re {(?i)enter.*current.*pin.*:} {
|
|
append transcript $expect_out(buffer)
|
|
send -- "$current\r"
|
|
}
|
|
-re {(?i)enter.*new.*pin.*:} {
|
|
append transcript $expect_out(buffer)
|
|
fail "PIN_NOT_CONFIGURED" 32
|
|
}
|
|
-re {(?i)(no yubikey|multiple yubikey|failed to connect|no fido|device.*not found|permission denied|access denied)} {
|
|
append transcript $expect_out(buffer)
|
|
fail "NO_YUBIKEY" 33
|
|
}
|
|
eof {
|
|
append transcript $expect_out(buffer)
|
|
if {[regexp -nocase {no yubikey|multiple yubikey|failed to connect|no fido|device.*not found|permission denied|access denied} $transcript]} {
|
|
fail "NO_YUBIKEY" 34
|
|
}
|
|
fail "PIN_FAILED" 35
|
|
}
|
|
timeout {
|
|
fail "TIMEOUT_CURRENT_PIN" 124
|
|
}
|
|
}
|
|
|
|
set current ""
|
|
|
|
expect {
|
|
-re {(?i)enter.*new.*pin.*:} {
|
|
append transcript $expect_out(buffer)
|
|
send -- "$newpin\r"
|
|
}
|
|
eof {
|
|
append transcript $expect_out(buffer)
|
|
fail "PIN_FAILED" 36
|
|
}
|
|
timeout {
|
|
fail "TIMEOUT_NEW_PIN" 124
|
|
}
|
|
}
|
|
|
|
expect {
|
|
-re {(?i)(repeat|confirm).*:} {
|
|
append transcript $expect_out(buffer)
|
|
send -- "$confirm\r"
|
|
}
|
|
eof {
|
|
append transcript $expect_out(buffer)
|
|
fail "PIN_FAILED" 37
|
|
}
|
|
timeout {
|
|
fail "TIMEOUT_CONFIRM_PIN" 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 {pin[_ ]auth[_ ]blocked|ctaperr_pin_auth_blocked|temporarily blocked} $transcript]} {
|
|
fail "PIN_AUTH_BLOCKED" $exitCode
|
|
} elseif {[regexp -nocase {pin[_ ]blocked|ctaperr_pin_blocked|pin is blocked} $transcript]} {
|
|
fail "PIN_BLOCKED" $exitCode
|
|
} elseif {[regexp -nocase {wrong pin|pin_invalid|pin auth invalid|pin verification failed|ctaperr_pin_invalid} $transcript]} {
|
|
fail "WRONG_PIN" $exitCode
|
|
} elseif {[regexp -nocase {no yubikey|multiple yubikey|failed to connect|no fido|device.*not found|permission denied|access denied} $transcript]} {
|
|
fail "NO_YUBIKEY" $exitCode
|
|
} elseif {[regexp -nocase {complexity|policy|minimum pin length|must be at least|too short} $transcript]} {
|
|
fail "PIN_POLICY" $exitCode
|
|
}
|
|
|
|
fail "PIN_FAILED" $exitCode
|
|
'';
|
|
};
|
|
|
|
workstationSetup = pkgs.stdenv.mkDerivation {
|
|
pname = "nixos-workstations-setup";
|
|
version = "1.3.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}"
|
|
];
|
|
};
|
|
|
|
launcher = pkgs.writeShellScript "nixos-workstations-setup-launcher" ''
|
|
set -eu
|
|
|
|
current_user="$(${pkgs.coreutils}/bin/id -un)"
|
|
target_user=${lib.escapeShellArg cfg.user}
|
|
|
|
# L'autostart ne doit concerner que l'utilisateur configuré.
|
|
if [ "$current_user" != "$target_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"
|
|
|
|
# Une session déjà finalisée n'affiche plus l'assistant.
|
|
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 terminer son démarrage.
|
|
${pkgs.coreutils}/bin/sleep 3
|
|
|
|
exec ${workstationSetup}/bin/nixos-workstations-setup \
|
|
--target-user "$target_user"
|
|
'';
|
|
|
|
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
|
|
];
|
|
|
|
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
|
|
'';
|
|
};
|
|
}
|