Files
nixos-workstations/modules/workstation-setup.nix
T

324 lines
8.8 KiB
Nix

{ config, lib, pkgs, ... }:
let
cfg = config.nixosWorkstations.workstationSetup;
passwordHelper = pkgs.writeShellScript "nixos-workstations-homed-password-helper" ''
set -eu
umask 077
fail() {
printf '%s\n' "$1" >&2
exit "$2"
}
IFS= read -r current || fail INPUT_ERROR 20
IFS= read -r newpass || fail INPUT_ERROR 20
IFS= read -r confirm || fail INPUT_ERROR 20
[ "$newpass" = "$confirm" ] || fail CONFIRM_MISMATCH 21
[ -n "$current" ] && [ -n "$newpass" ] || fail INPUT_ERROR 20
runtime_dir="''${XDG_RUNTIME_DIR:-/run/user/$(${pkgs.coreutils}/bin/id -u)}"
cred_dir="$(${pkgs.coreutils}/bin/mktemp -d "$runtime_dir/nixos-workstations-passwd.XXXXXX")"
log_file="$cred_dir/homectl.log"
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
${pkgs.coreutils}/bin/printf '%s' "$current" > "$cred_dir/home.password"
${pkgs.coreutils}/bin/printf '%s' "$newpass" > "$cred_dir/home.new-password"
${pkgs.coreutils}/bin/chmod 600 "$cred_dir/home.password" "$cred_dir/home.new-password"
user="$(${pkgs.coreutils}/bin/id -un)"
current=''
newpass=''
confirm=''
set +e
CREDENTIALS_DIRECTORY="$cred_dir" \
LC_ALL=C LANG=C \
${pkgs.coreutils}/bin/timeout 60 \
${pkgs.systemd}/bin/homectl --no-ask-password --no-pager passwd "$user" \
>"$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_PASSWORD 124
fi
if ${pkgs.gnugrep}/bin/grep -Eqi 'password incorrect|not sufficient|bad password' "$log_file"; then
fail CURRENT_REJECTED 22
fi
if ${pkgs.gnugrep}/bin/grep -Eqi 'quality|too short|weak|dictionary' "$log_file"; then
fail NEW_REJECTED 23
fi
fail HOMECTL_PASSWD_FAILED 24
'';
pinPython = pkgs.python3.withPackages (ps: [ ps.fido2 ]);
pinHelper = pkgs.writeTextFile {
name = "nixos-workstations-pin-helper";
executable = true;
text = ''
#!${pinPython}/bin/python3
import sys
from fido2.ctap import CtapError
from fido2.ctap2 import Ctap2
from fido2.ctap2.pin import ClientPin
from fido2.hid import CAPABILITY, CtapHidDevice
def fail(token: str, code: int) -> "None":
print(token, file=sys.stderr, flush=True)
raise SystemExit(code)
def read_secret() -> str:
value = sys.stdin.readline()
if value == "":
fail("INPUT_ERROR", 30)
return value.rstrip("\\r\\n")
new_pin = read_secret()
confirmation = read_secret()
if new_pin != confirmation:
fail("CONFIRM_MISMATCH", 31)
if len(new_pin) < 4 or len(new_pin.encode("utf-8")) > 63:
fail("PIN_POLICY", 32)
devices = []
try:
devices = list(CtapHidDevice.list_devices())
if len(devices) == 0:
fail("NO_YUBIKEY", 33)
if len(devices) > 1:
fail("MULTIPLE_YUBIKEY", 34)
device = devices[0]
if not (device.capabilities & CAPABILITY.CBOR):
fail("NO_FIDO2", 35)
ctap = Ctap2(device)
if ctap.get_info().options.get("clientPin", False):
fail("PIN_ALREADY_CONFIGURED", 36)
ClientPin(ctap).set_pin(new_pin)
if not ctap.get_info().options.get("clientPin", False):
fail("PIN_STATE_NOT_UPDATED", 37)
new_pin = ""
confirmation = ""
print("OK", flush=True)
raise SystemExit(0)
except CtapError as exc:
code = exc.code
if code == CtapError.ERR.PIN_AUTH_BLOCKED:
fail("PIN_AUTH_BLOCKED", 41)
if code == CtapError.ERR.PIN_BLOCKED:
fail("PIN_BLOCKED", 42)
if code == CtapError.ERR.PIN_POLICY_VIOLATION:
fail("PIN_POLICY", 44)
fail("PIN_FAILED", 46)
except (PermissionError, OSError):
fail("NO_YUBIKEY", 47)
except ValueError:
fail("PIN_POLICY", 48)
except SystemExit:
raise
except Exception:
fail("PIN_HELPER_ERROR", 49)
finally:
new_pin = ""
confirmation = ""
for dev in devices:
try:
dev.close()
except Exception:
pass
'';
};
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 {
pname = "nixos-workstations-setup";
version = "1.8.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}"
"-DFIDO_HELPER_PATH=${fidoEnrollHelper}"
];
};
launcher = pkgs.writeShellScript "nixos-workstations-setup-launcher" ''
set -eu
current_user="$(${pkgs.coreutils}/bin/id -un)"
target_user=${lib.escapeShellArg cfg.user}
[ "$current_user" = "$target_user" ] || exit 0
state_dir="''${XDG_STATE_HOME:-$HOME/.local/state}/nixos-workstations"
password_marker="$state_dir/password-initialized"
pin_marker="$state_dir/yubikey-pin-created"
fido_marker="$state_dir/yubikey-fido-enrolled"
if [ -e "$password_marker" ] && [ -e "$pin_marker" ] && [ -e "$fido_marker" ]; then
exit 0
fi
ulimit -c 0
${pkgs.coreutils}/bin/sleep 3
exec ${workstationSetup}/bin/nixos-workstations-setup \
--target-user "$target_user"
'';
in
{
options.nixosWorkstations.workstationSetup = {
enable = lib.mkEnableOption "assistant de première session";
user = lib.mkOption {
type = lib.types.str;
default = "alice";
description = "Utilisateur devant finaliser son authentification.";
};
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [
workstationSetup
pkgs.systemd
];
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
'';
};
}