Téléverser les fichiers vers "modules"
This commit is contained in:
+123
-65
@@ -1,73 +1,131 @@
|
|||||||
{ config, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.nixosWorkstations.homedUser;
|
||||||
|
|
||||||
|
# LAB : le mot de passe initial est volontairement un mot de passe par défaut
|
||||||
|
# connu. Il ne constitue pas un secret durable : Alice doit le remplacer lors
|
||||||
|
# de sa première session. Pour une future version production, ce credential
|
||||||
|
# devra venir d'une source chiffrée/externe et non du Nix store.
|
||||||
|
initialPasswordCredential = pkgs.writeText
|
||||||
|
"nixos-workstations-${cfg.user}-initial-password"
|
||||||
|
cfg.initialPassword;
|
||||||
|
|
||||||
|
provisionService = "nixos-workstations-provision-${cfg.user}";
|
||||||
|
in
|
||||||
{
|
{
|
||||||
#
|
options.nixosWorkstations.homedUser = {
|
||||||
# systemd-homed
|
enable = lib.mkEnableOption "précréation d'un utilisateur systemd-homed chiffré";
|
||||||
#
|
|
||||||
# Les utilisateurs finaux sont gérés par systemd-homed et non par
|
|
||||||
# users.users.<name>.
|
|
||||||
#
|
|
||||||
# Leur home pourra être stocké dans un volume LUKS2 individuel.
|
|
||||||
#
|
|
||||||
services.homed.enable = true;
|
|
||||||
|
|
||||||
|
user = lib.mkOption {
|
||||||
#
|
type = lib.types.str;
|
||||||
# NixOS possède des utilisateurs système nixbld avec des UID > 1000.
|
default = "alice";
|
||||||
#
|
description = "Compte systemd-homed à préparer automatiquement.";
|
||||||
# Cela déclenche un avertissement de systemd-userdb concernant
|
|
||||||
# l'existence d'utilisateurs "réguliers".
|
|
||||||
#
|
|
||||||
# Dans notre architecture ce warning n'est pas pertinent :
|
|
||||||
# les utilisateurs homed sont créés explicitement avec homectl.
|
|
||||||
#
|
|
||||||
services.userdbd.silenceHighSystemUsers = true;
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# SDDM + systemd-homed
|
|
||||||
#
|
|
||||||
# systemd-homed utilise des UID dynamiques élevés.
|
|
||||||
# La plage réservée aux utilisateurs homed monte jusqu'à 60513.
|
|
||||||
#
|
|
||||||
# NixOS configure normalement SDDM avec MaximumUid = nixbld,
|
|
||||||
# soit environ 30000, ce qui exclurait Alice (UID 60456 dans notre test).
|
|
||||||
#
|
|
||||||
services.displayManager.sddm.settings = {
|
|
||||||
|
|
||||||
Users = {
|
|
||||||
MinimumUid = 1000;
|
|
||||||
MaximumUid = 60513;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
realName = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "Alice";
|
||||||
|
description = "Nom complet de l'utilisateur systemd-homed.";
|
||||||
|
};
|
||||||
|
|
||||||
#
|
initialPassword = lib.mkOption {
|
||||||
# IMPORTANT :
|
type = lib.types.str;
|
||||||
#
|
default = "LaboTest@1980";
|
||||||
# SDDM construit sa liste d'utilisateurs dans sddm-greeter avec
|
description = "Mot de passe temporaire utilisé uniquement pour le premier accès LAB.";
|
||||||
# getpwent().
|
};
|
||||||
#
|
|
||||||
# Sous NixOS, libnss_systemd.so n'est pas dans le chemin standard
|
diskSize = lib.mkOption {
|
||||||
# du linker. Le chemin des modules NSS est fourni par :
|
type = lib.types.str;
|
||||||
#
|
default = "10G";
|
||||||
# config.system.nssModules.path
|
description = "Taille initiale du conteneur LUKS2 du home.";
|
||||||
#
|
|
||||||
# Notre test a confirmé que :
|
|
||||||
#
|
|
||||||
# LD_LIBRARY_PATH=<nssModules.path>
|
|
||||||
# getent -s systemd passwd
|
|
||||||
#
|
|
||||||
# permet immédiatement d'énumérer Alice.
|
|
||||||
#
|
|
||||||
# GreeterEnvironment est le mécanisme SDDM prévu pour transmettre
|
|
||||||
# ces variables au processus sddm-greeter.
|
|
||||||
#
|
|
||||||
# On conserve également la variable nécessaire au greeter
|
|
||||||
# KDE/Wayland de NixOS.
|
|
||||||
#
|
|
||||||
General = {
|
|
||||||
GreeterEnvironment =
|
|
||||||
"QT_WAYLAND_SHELL_INTEGRATION=layer-shell,LD_LIBRARY_PATH=${config.system.nssModules.path}";
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
services.homed.enable = true;
|
||||||
|
|
||||||
|
# Les comptes nixbld NixOS ont des UID > 1000. Nous n'utilisons pas le
|
||||||
|
# workflow first-boot de homed : les comptes sont créés explicitement par
|
||||||
|
# le service ci-dessous.
|
||||||
|
services.userdbd.silenceHighSystemUsers = true;
|
||||||
|
|
||||||
|
# SDDM doit accepter la plage d'UID systemd-homed et son greeter doit
|
||||||
|
# charger libnss_systemd directement. Cette configuration a été validée en
|
||||||
|
# LAB : sans ce LD_LIBRARY_PATH, getpwent() n'énumère pas Alice sous NixOS.
|
||||||
|
services.displayManager.sddm.settings = {
|
||||||
|
Users = {
|
||||||
|
MinimumUid = 1000;
|
||||||
|
MaximumUid = 60513;
|
||||||
|
};
|
||||||
|
|
||||||
|
General = {
|
||||||
|
GreeterEnvironment =
|
||||||
|
"QT_WAYLAND_SHELL_INTEGRATION=layer-shell,LD_LIBRARY_PATH=${config.system.nssModules.path}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Précréation automatique d'Alice. localadm ne lance qu'un
|
||||||
|
# nixos-rebuild switch : ce service est démarré automatiquement et ne fait
|
||||||
|
# rien si le compte homed existe déjà.
|
||||||
|
#
|
||||||
|
# RemainAfterExit est volontairement conservé : SDDM dépend de la réussite
|
||||||
|
# de ce service au démarrage. Le script lui-même reste idempotent.
|
||||||
|
systemd.services.${provisionService} = {
|
||||||
|
description = "Prépare le compte systemd-homed ${cfg.user}";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
before = [ "display-manager.service" ];
|
||||||
|
after = [ "systemd-homed.service" ];
|
||||||
|
requires = [ "systemd-homed.service" ];
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
RemainAfterExit = true;
|
||||||
|
LoadCredential = [
|
||||||
|
"home.new-password:${initialPasswordCredential}"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
path = [
|
||||||
|
pkgs.coreutils
|
||||||
|
pkgs.getent
|
||||||
|
pkgs.systemd
|
||||||
|
];
|
||||||
|
|
||||||
|
script = ''
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
user=${lib.escapeShellArg cfg.user}
|
||||||
|
|
||||||
|
if ${pkgs.systemd}/bin/homectl inspect "$user" >/dev/null 2>&1; then
|
||||||
|
echo "Compte homed $user déjà présent : aucune modification."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Refuser une collision avec un compte UNIX classique du même nom.
|
||||||
|
if ${pkgs.getent}/bin/getent passwd "$user" >/dev/null 2>&1; then
|
||||||
|
echo "ERREUR : $user existe déjà mais n'est pas un compte systemd-homed." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Création du compte systemd-homed $user et de son home LUKS2..."
|
||||||
|
|
||||||
|
${pkgs.systemd}/bin/homectl --no-ask-password create "$user" \
|
||||||
|
--real-name=${lib.escapeShellArg cfg.realName} \
|
||||||
|
--storage=luks \
|
||||||
|
--fs-type=ext4 \
|
||||||
|
--disk-size=${lib.escapeShellArg cfg.diskSize} \
|
||||||
|
--access-mode=0700 \
|
||||||
|
--shell=/run/current-system/sw/bin/bash \
|
||||||
|
--password-change-now=no \
|
||||||
|
--recovery-key=no
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Au boot, le compte doit exister avant le lancement du greeter SDDM.
|
||||||
|
systemd.services.display-manager = {
|
||||||
|
after = [ "${provisionService}.service" ];
|
||||||
|
requires = [ "${provisionService}.service" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
# Étape 1 LAB : la YubiKey est uniquement initialisée par le setup.
|
# La YubiKey est utilisée directement par python-fido2 puis par
|
||||||
# Aucune authentification PAM/FIDO2 n'est activée à ce stade et aucun
|
# systemd-homed. Aucun pam_u2f et aucun fichier u2f-mappings ne sont
|
||||||
# credential spécifique à une clé n'est conservé dans Git.
|
# nécessaires dans cette architecture.
|
||||||
#
|
#
|
||||||
# YubiKey Manager apporte notamment les règles udev permettant l'accès
|
# YubiKey Manager fournit également les règles udev nécessaires à l'accès
|
||||||
# utilisateur à la clé. L'enrôlement systemd-homed viendra à l'étape 3.
|
# utilisateur au périphérique FIDO2.
|
||||||
programs.yubikey-manager.enable = true;
|
programs.yubikey-manager.enable = true;
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-5
@@ -1,13 +1,11 @@
|
|||||||
{ pkgs, ... }:
|
{ pkgs, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
# Les comptes UNIX classiques restent mutables.
|
|
||||||
# Alice n'est volontairement PLUS déclarée ici : elle sera créée
|
|
||||||
# interactivement par systemd-homed avec `homectl create alice`.
|
|
||||||
users.mutableUsers = true;
|
users.mutableUsers = true;
|
||||||
|
|
||||||
# Compte d'administration local classique, conservé comme compte de
|
# Alice n'est PAS déclarée dans users.users : elle appartient exclusivement
|
||||||
# secours pendant tous les essais systemd-homed.
|
# à systemd-homed et est créée automatiquement par modules/homed.nix.
|
||||||
|
|
||||||
users.users.localadm = {
|
users.users.localadm = {
|
||||||
isNormalUser = true;
|
isNormalUser = true;
|
||||||
description = "Administrateur local";
|
description = "Administrateur local";
|
||||||
|
|||||||
+147
-166
@@ -3,155 +3,84 @@
|
|||||||
let
|
let
|
||||||
cfg = config.nixosWorkstations.workstationSetup;
|
cfg = config.nixosWorkstations.workstationSetup;
|
||||||
|
|
||||||
passwordHelper = pkgs.writeTextFile {
|
passwordHelper = pkgs.writeShellScript "nixos-workstations-homed-password-helper" ''
|
||||||
name = "nixos-workstations-password-helper";
|
set -eu
|
||||||
executable = true;
|
umask 077
|
||||||
text = ''
|
|
||||||
#!${pkgs.expect}/bin/expect -f
|
|
||||||
|
|
||||||
# Les secrets arrivent uniquement par stdin depuis l'application.
|
fail() {
|
||||||
# Ils ne sont jamais placés dans argv. La sortie du processus passwd
|
printf '%s\n' "$1" >&2
|
||||||
# reste masquée : seuls des jetons techniques non sensibles sont renvoyés.
|
exit "$2"
|
||||||
log_user 0
|
}
|
||||||
exp_internal 0
|
|
||||||
set timeout 30
|
|
||||||
|
|
||||||
proc fail {token code} {
|
IFS= read -r current || fail INPUT_ERROR 20
|
||||||
puts stderr $token
|
IFS= read -r newpass || fail INPUT_ERROR 20
|
||||||
exit $code
|
IFS= read -r confirm || fail INPUT_ERROR 20
|
||||||
}
|
|
||||||
|
|
||||||
if {[gets stdin current] < 0 || [gets stdin newpass] < 0 || [gets stdin confirm] < 0} {
|
[ "$newpass" = "$confirm" ] || fail CONFIRM_MISMATCH 21
|
||||||
fail "INPUT_ERROR" 20
|
[ -n "$current" ] && [ -n "$newpass" ] || fail INPUT_ERROR 20
|
||||||
}
|
|
||||||
|
|
||||||
if {$newpass ne $confirm} {
|
runtime_dir="''${XDG_RUNTIME_DIR:-/run/user/$(${pkgs.coreutils}/bin/id -u)}"
|
||||||
fail "CONFIRM_MISMATCH" 21
|
cred_dir="$(${pkgs.coreutils}/bin/mktemp -d "$runtime_dir/nixos-workstations-passwd.XXXXXX")"
|
||||||
}
|
log_file="$cred_dir/homectl.log"
|
||||||
|
|
||||||
spawn -noecho ${pkgs.coreutils}/bin/env LC_ALL=C LANG=C /run/wrappers/bin/passwd
|
cleanup() {
|
||||||
|
current=''
|
||||||
|
newpass=''
|
||||||
|
confirm=''
|
||||||
|
${pkgs.coreutils}/bin/rm -f \
|
||||||
|
"$cred_dir/home.password" \
|
||||||
|
"$cred_dir/home.new-password" \
|
||||||
|
"$log_file" 2>/dev/null || true
|
||||||
|
${pkgs.coreutils}/bin/rmdir "$cred_dir" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
# Étape 1 : authentification du mot de passe actuel.
|
${pkgs.coreutils}/bin/printf '%s' "$current" > "$cred_dir/home.password"
|
||||||
# Selon la pile PAM, l'invite peut être "Current password:",
|
${pkgs.coreutils}/bin/printf '%s' "$newpass" > "$cred_dir/home.new-password"
|
||||||
# "(current) UNIX password:" OU simplement "Password:".
|
${pkgs.coreutils}/bin/chmod 600 "$cred_dir/home.password" "$cred_dir/home.new-password"
|
||||||
# Il ne faut donc pas dépendre uniquement des mots Current/Old/UNIX.
|
|
||||||
expect {
|
|
||||||
-re {(?i)(authentication failure|incorrect password|password unchanged|authentication token manipulation error)} {
|
|
||||||
fail "CURRENT_REJECTED" 23
|
|
||||||
}
|
|
||||||
# Si passwd passe directement au nouveau mot de passe, nous refusons :
|
|
||||||
# l'assistant doit toujours vérifier le mot de passe temporaire actuel.
|
|
||||||
-re {(?i)(new|retype|repeat|confirm)[^\r\n]*(password|passphrase)[^\r\n]*[:?]} {
|
|
||||||
fail "CURRENT_PROMPT_MISSING" 22
|
|
||||||
}
|
|
||||||
# Invite PAM générique, notamment "Password:".
|
|
||||||
-re {(?i)(password|passphrase)[^\r\n]*[:?]} {
|
|
||||||
send -- "$current\r"
|
|
||||||
}
|
|
||||||
eof {
|
|
||||||
fail "EARLY_EOF_CURRENT" 24
|
|
||||||
}
|
|
||||||
timeout {
|
|
||||||
fail "TIMEOUT_CURRENT_PROMPT" 124
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set current ""
|
user="$(${pkgs.coreutils}/bin/id -un)"
|
||||||
|
|
||||||
# Étape 2 : le nouveau mot de passe n'est envoyé qu'après validation
|
current=''
|
||||||
# du mot de passe actuel par passwd/PAM.
|
newpass=''
|
||||||
expect {
|
confirm=''
|
||||||
-re {(?i)(authentication failure|incorrect password|password unchanged)} {
|
|
||||||
fail "CURRENT_REJECTED" 25
|
|
||||||
}
|
|
||||||
-re {(?i)new[^\r\n]*(password|passphrase)[^\r\n]*[:?]} {
|
|
||||||
send -- "$newpass\r"
|
|
||||||
}
|
|
||||||
# Une invite générique "Password:" à ce stade est ambiguë : elle peut
|
|
||||||
# être une nouvelle demande du mot de passe actuel. Par sécurité nous
|
|
||||||
# ne tentons jamais une seconde authentification automatiquement.
|
|
||||||
-re {(?i)(password|passphrase)[^\r\n]*[:?]} {
|
|
||||||
fail "CURRENT_REPROMPT" 25
|
|
||||||
}
|
|
||||||
eof {
|
|
||||||
fail "EARLY_EOF_NEW" 26
|
|
||||||
}
|
|
||||||
timeout {
|
|
||||||
fail "TIMEOUT_NEW_PROMPT" 124
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Étape 3 : confirmation du nouveau mot de passe.
|
set +e
|
||||||
expect {
|
CREDENTIALS_DIRECTORY="$cred_dir" \
|
||||||
-re {(?i)(bad password|password unchanged|authentication token manipulation error)} {
|
LC_ALL=C LANG=C \
|
||||||
fail "NEW_REJECTED" 27
|
${pkgs.coreutils}/bin/timeout 60 \
|
||||||
}
|
${pkgs.systemd}/bin/homectl --no-ask-password --no-pager passwd "$user" \
|
||||||
-re {(?i)(retype|repeat|confirm)[^\r\n]*(password|passphrase)[^\r\n]*[:?]} {
|
>"$log_file" 2>&1
|
||||||
send -- "$confirm\r"
|
rc=$?
|
||||||
}
|
set -e
|
||||||
-re {(?i)new[^\r\n]*(password|passphrase)[^\r\n]*[:?]} {
|
|
||||||
fail "NEW_REJECTED" 27
|
|
||||||
}
|
|
||||||
eof {
|
|
||||||
fail "EARLY_EOF_CONFIRM" 28
|
|
||||||
}
|
|
||||||
timeout {
|
|
||||||
fail "TIMEOUT_CONFIRM_PROMPT" 124
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set newpass ""
|
if [ "$rc" -eq 0 ]; then
|
||||||
set confirm ""
|
printf '%s\n' OK
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
# Étape 4 : passwd doit maintenant terminer. Toute nouvelle invite de
|
if [ "$rc" -eq 124 ]; then
|
||||||
# mot de passe signifie que la modification n'a pas été acceptée.
|
fail TIMEOUT_PASSWORD 124
|
||||||
expect {
|
fi
|
||||||
eof {}
|
|
||||||
-re {(?i)(password|passphrase)[^\r\n]*[:?]} {
|
|
||||||
fail "UNEXPECTED_PASSWORD_REPROMPT" 29
|
|
||||||
}
|
|
||||||
timeout {
|
|
||||||
fail "TIMEOUT_FINISH" 124
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set waitResult [wait]
|
if ${pkgs.gnugrep}/bin/grep -Eqi 'password incorrect|not sufficient|bad password' "$log_file"; then
|
||||||
set exitCode [lindex $waitResult 3]
|
fail CURRENT_REJECTED 22
|
||||||
|
fi
|
||||||
|
|
||||||
if {$exitCode == 0} {
|
if ${pkgs.gnugrep}/bin/grep -Eqi 'quality|too short|weak|dictionary' "$log_file"; then
|
||||||
puts "OK"
|
fail NEW_REJECTED 23
|
||||||
exit 0
|
fi
|
||||||
}
|
|
||||||
|
|
||||||
fail "PASSWD_FAILED" $exitCode
|
fail HOMECTL_PASSWD_FAILED 24
|
||||||
'';
|
'';
|
||||||
};
|
|
||||||
|
|
||||||
pinPython = pkgs.python3.withPackages (ps: [
|
pinPython = pkgs.python3.withPackages (ps: [ ps.fido2 ]);
|
||||||
ps.fido2
|
|
||||||
]);
|
|
||||||
|
|
||||||
pinHelper = pkgs.writeTextFile {
|
pinHelper = pkgs.writeTextFile {
|
||||||
name = "nixos-workstations-pin-helper";
|
name = "nixos-workstations-pin-helper";
|
||||||
executable = true;
|
executable = true;
|
||||||
text = ''
|
text = ''
|
||||||
#!${pinPython}/bin/python3
|
#!${pinPython}/bin/python3
|
||||||
"""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:
|
|
||||||
1. new PIN
|
|
||||||
2. confirmation
|
|
||||||
|
|
||||||
Output contains technical tokens only, never PIN values.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from fido2.ctap import CtapError
|
from fido2.ctap import CtapError
|
||||||
@@ -159,18 +88,15 @@ let
|
|||||||
from fido2.ctap2.pin import ClientPin
|
from fido2.ctap2.pin import ClientPin
|
||||||
from fido2.hid import CAPABILITY, CtapHidDevice
|
from fido2.hid import CAPABILITY, CtapHidDevice
|
||||||
|
|
||||||
|
|
||||||
def fail(token: str, code: int) -> "None":
|
def fail(token: str, code: int) -> "None":
|
||||||
print(token, file=sys.stderr, flush=True)
|
print(token, file=sys.stderr, flush=True)
|
||||||
raise SystemExit(code)
|
raise SystemExit(code)
|
||||||
|
|
||||||
|
|
||||||
def read_secret() -> str:
|
def read_secret() -> str:
|
||||||
value = sys.stdin.readline()
|
value = sys.stdin.readline()
|
||||||
if value == "":
|
if value == "":
|
||||||
fail("INPUT_ERROR", 30)
|
fail("INPUT_ERROR", 30)
|
||||||
return value.rstrip("\r\n")
|
return value.rstrip("\\r\\n")
|
||||||
|
|
||||||
|
|
||||||
new_pin = read_secret()
|
new_pin = read_secret()
|
||||||
confirmation = read_secret()
|
confirmation = read_secret()
|
||||||
@@ -182,34 +108,23 @@ let
|
|||||||
fail("PIN_POLICY", 32)
|
fail("PIN_POLICY", 32)
|
||||||
|
|
||||||
devices = []
|
devices = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
devices = list(CtapHidDevice.list_devices())
|
devices = list(CtapHidDevice.list_devices())
|
||||||
|
|
||||||
if len(devices) == 0:
|
if len(devices) == 0:
|
||||||
fail("NO_YUBIKEY", 33)
|
fail("NO_YUBIKEY", 33)
|
||||||
|
|
||||||
if len(devices) > 1:
|
if len(devices) > 1:
|
||||||
fail("MULTIPLE_YUBIKEY", 34)
|
fail("MULTIPLE_YUBIKEY", 34)
|
||||||
|
|
||||||
device = devices[0]
|
device = devices[0]
|
||||||
|
|
||||||
if not (device.capabilities & CAPABILITY.CBOR):
|
if not (device.capabilities & CAPABILITY.CBOR):
|
||||||
fail("NO_FIDO2", 35)
|
fail("NO_FIDO2", 35)
|
||||||
|
|
||||||
ctap = Ctap2(device)
|
ctap = Ctap2(device)
|
||||||
info = ctap.get_info()
|
if ctap.get_info().options.get("clientPin", False):
|
||||||
|
|
||||||
# Étape 1 stricte : on ne doit jamais tenter de valider/changer
|
|
||||||
# un PIN existant. Ce contrôle ne consomme aucune tentative de PIN.
|
|
||||||
if info.options.get("clientPin", False):
|
|
||||||
fail("PIN_ALREADY_CONFIGURED", 36)
|
fail("PIN_ALREADY_CONFIGURED", 36)
|
||||||
|
|
||||||
client_pin = ClientPin(ctap)
|
ClientPin(ctap).set_pin(new_pin)
|
||||||
client_pin.set_pin(new_pin)
|
|
||||||
|
|
||||||
# GET_INFO ne consomme pas de tentative de PIN. Vérifier que la clé
|
|
||||||
# annonce désormais bien clientPin=True avant de déclarer le succès.
|
|
||||||
if not ctap.get_info().options.get("clientPin", False):
|
if not ctap.get_info().options.get("clientPin", False):
|
||||||
fail("PIN_STATE_NOT_UPDATED", 37)
|
fail("PIN_STATE_NOT_UPDATED", 37)
|
||||||
|
|
||||||
@@ -220,30 +135,21 @@ let
|
|||||||
|
|
||||||
except CtapError as exc:
|
except CtapError as exc:
|
||||||
code = exc.code
|
code = exc.code
|
||||||
|
|
||||||
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_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", 46)
|
fail("PIN_FAILED", 46)
|
||||||
|
|
||||||
except (PermissionError, OSError):
|
except (PermissionError, OSError):
|
||||||
fail("NO_YUBIKEY", 47)
|
fail("NO_YUBIKEY", 47)
|
||||||
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
fail("PIN_POLICY", 48)
|
fail("PIN_POLICY", 48)
|
||||||
|
|
||||||
except SystemExit:
|
except SystemExit:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
fail("PIN_HELPER_ERROR", 49)
|
fail("PIN_HELPER_ERROR", 49)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
new_pin = ""
|
new_pin = ""
|
||||||
confirmation = ""
|
confirmation = ""
|
||||||
@@ -255,9 +161,88 @@ let
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fidoEnrollHelper = pkgs.writeShellScript "nixos-workstations-homed-fido-helper" ''
|
||||||
|
set -eu
|
||||||
|
umask 077
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
printf '%s\n' "$1" >&2
|
||||||
|
exit "$2"
|
||||||
|
}
|
||||||
|
|
||||||
|
IFS= read -r current_password || fail INPUT_ERROR 50
|
||||||
|
IFS= read -r token_pin || fail INPUT_ERROR 50
|
||||||
|
|
||||||
|
[ -n "$current_password" ] && [ -n "$token_pin" ] || fail INPUT_ERROR 50
|
||||||
|
|
||||||
|
runtime_dir="''${XDG_RUNTIME_DIR:-/run/user/$(${pkgs.coreutils}/bin/id -u)}"
|
||||||
|
cred_dir="$(${pkgs.coreutils}/bin/mktemp -d "$runtime_dir/nixos-workstations-fido.XXXXXX")"
|
||||||
|
log_file="$cred_dir/homectl.log"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
current_password=''
|
||||||
|
token_pin=''
|
||||||
|
${pkgs.coreutils}/bin/rm -f \
|
||||||
|
"$cred_dir/home.password" \
|
||||||
|
"$cred_dir/home.token-pin" \
|
||||||
|
"$log_file" 2>/dev/null || true
|
||||||
|
${pkgs.coreutils}/bin/rmdir "$cred_dir" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
|
${pkgs.coreutils}/bin/printf '%s' "$current_password" > "$cred_dir/home.password"
|
||||||
|
${pkgs.coreutils}/bin/printf '%s' "$token_pin" > "$cred_dir/home.token-pin"
|
||||||
|
${pkgs.coreutils}/bin/chmod 600 "$cred_dir/home.password" "$cred_dir/home.token-pin"
|
||||||
|
|
||||||
|
user="$(${pkgs.coreutils}/bin/id -un)"
|
||||||
|
|
||||||
|
current_password=''
|
||||||
|
token_pin=''
|
||||||
|
|
||||||
|
set +e
|
||||||
|
CREDENTIALS_DIRECTORY="$cred_dir" \
|
||||||
|
LC_ALL=C LANG=C \
|
||||||
|
${pkgs.coreutils}/bin/timeout 120 \
|
||||||
|
${pkgs.systemd}/bin/homectl --no-ask-password --no-pager update "$user" \
|
||||||
|
--fido2-device=auto \
|
||||||
|
--fido2-with-client-pin=yes \
|
||||||
|
--fido2-with-user-presence=yes \
|
||||||
|
--fido2-with-user-verification=no \
|
||||||
|
>"$log_file" 2>&1
|
||||||
|
rc=$?
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ "$rc" -eq 0 ]; then
|
||||||
|
printf '%s\n' OK
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$rc" -eq 124 ]; then
|
||||||
|
fail TIMEOUT_FIDO 124
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ${pkgs.gnugrep}/bin/grep -Eqi 'PIN.*incorrect|Bad PIN|bad pin' "$log_file"; then
|
||||||
|
fail FIDO_BAD_PIN 51
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ${pkgs.gnugrep}/bin/grep -Eqi 'password.*incorrect|not sufficient|BadPassword' "$log_file"; then
|
||||||
|
fail FIDO_BAD_PASSWORD 52
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ${pkgs.gnugrep}/bin/grep -Eqi 'multiple|more than one.*FIDO|auto.*device' "$log_file"; then
|
||||||
|
fail MULTIPLE_YUBIKEY 53
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ${pkgs.gnugrep}/bin/grep -Eqi 'no.*FIDO|No such device|not found|not inserted' "$log_file"; then
|
||||||
|
fail NO_YUBIKEY 54
|
||||||
|
fi
|
||||||
|
|
||||||
|
fail FIDO_ENROLL_FAILED 55
|
||||||
|
'';
|
||||||
|
|
||||||
workstationSetup = pkgs.stdenv.mkDerivation {
|
workstationSetup = pkgs.stdenv.mkDerivation {
|
||||||
pname = "nixos-workstations-setup";
|
pname = "nixos-workstations-setup";
|
||||||
version = "1.6.0";
|
version = "1.8.0";
|
||||||
|
|
||||||
src = ../workstation-setup;
|
src = ../workstation-setup;
|
||||||
|
|
||||||
@@ -278,6 +263,7 @@ let
|
|||||||
cmakeFlags = [
|
cmakeFlags = [
|
||||||
"-DPASSWORD_HELPER_PATH=${passwordHelper}"
|
"-DPASSWORD_HELPER_PATH=${passwordHelper}"
|
||||||
"-DPIN_HELPER_PATH=${pinHelper}"
|
"-DPIN_HELPER_PATH=${pinHelper}"
|
||||||
|
"-DFIDO_HELPER_PATH=${fidoEnrollHelper}"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -287,24 +273,18 @@ let
|
|||||||
current_user="$(${pkgs.coreutils}/bin/id -un)"
|
current_user="$(${pkgs.coreutils}/bin/id -un)"
|
||||||
target_user=${lib.escapeShellArg cfg.user}
|
target_user=${lib.escapeShellArg cfg.user}
|
||||||
|
|
||||||
# L'autostart ne doit concerner que l'utilisateur configuré.
|
[ "$current_user" = "$target_user" ] || exit 0
|
||||||
if [ "$current_user" != "$target_user" ]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
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-created"
|
pin_marker="$state_dir/yubikey-pin-created"
|
||||||
|
fido_marker="$state_dir/yubikey-fido-enrolled"
|
||||||
|
|
||||||
# Une session déjà finalisée n'affiche plus l'assistant.
|
if [ -e "$password_marker" ] && [ -e "$pin_marker" ] && [ -e "$fido_marker" ]; then
|
||||||
if [ -e "$password_marker" ] && [ -e "$pin_marker" ]; then
|
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Ne jamais générer de core dump contenant potentiellement un secret.
|
|
||||||
ulimit -c 0
|
ulimit -c 0
|
||||||
|
|
||||||
# Laisse Plasma terminer son démarrage.
|
|
||||||
${pkgs.coreutils}/bin/sleep 3
|
${pkgs.coreutils}/bin/sleep 3
|
||||||
|
|
||||||
exec ${workstationSetup}/bin/nixos-workstations-setup \
|
exec ${workstationSetup}/bin/nixos-workstations-setup \
|
||||||
@@ -314,18 +294,19 @@ let
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.nixosWorkstations.workstationSetup = {
|
options.nixosWorkstations.workstationSetup = {
|
||||||
enable = lib.mkEnableOption "assistant plein écran de finalisation du poste";
|
enable = lib.mkEnableOption "assistant de première session";
|
||||||
|
|
||||||
user = lib.mkOption {
|
user = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
default = "alice";
|
default = "alice";
|
||||||
description = "Utilisateur devant effectuer la personnalisation initiale.";
|
description = "Utilisateur devant finaliser son authentification.";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
environment.systemPackages = [
|
environment.systemPackages = [
|
||||||
workstationSetup
|
workstationSetup
|
||||||
|
pkgs.systemd
|
||||||
];
|
];
|
||||||
|
|
||||||
environment.etc."xdg/autostart/nixos-workstations-setup.desktop".text = ''
|
environment.etc."xdg/autostart/nixos-workstations-setup.desktop".text = ''
|
||||||
|
|||||||
Reference in New Issue
Block a user