diff --git a/setup/modules/freeipa-enroll.sh b/setup/modules/freeipa-enroll.sh new file mode 100644 index 0000000..9216ec6 --- /dev/null +++ b/setup/modules/freeipa-enroll.sh @@ -0,0 +1,637 @@ +#!/bin/bash +# freeipa-enroll.sh — Enroll a Linux host into FreeIPA +# Usage: sudo ./freeipa-enroll.sh [options] +# +# Options: +# -d, --domain IPA domain (e.g. corp.example.com) +# -r, --realm Kerberos realm (e.g. CORP.EXAMPLE.COM) [optional, derived from domain] +# -s, --server IPA server hostname (e.g. ipa01.corp.example.com) +# -h, --hostname This host's FQDN [optional, uses current hostname] +# -p, --principal Admin principal [default: admin] +# -w, --password Admin password (or use IPA_PASSWORD env var) +# -m, --mkhomedir Enable home directory creation on login [default: true] +# --no-mkhomedir Disable home directory creation +# --sudo Configure sudo rules via SSSD [default: true] +# --no-sudo Disable sudo via SSSD +# --no-dns-update Skip DNS record update +# --ntp-server Custom NTP server [optional] +# --fido2 Enable FIDO2 key authentication via PAM +# --fido2-user IPA username to register FIDO2 credential for (repeatable) +# --uninstall Remove FreeIPA client + +set -euo pipefail + +# ─── Colours ──────────────────────────────────────────────────────────────── +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' + +log() { echo -e "${GREEN}[+]${NC} $*"; } +warn() { echo -e "${YELLOW}[!]${NC} $*"; } +error() { echo -e "${RED}[✗]${NC} $*" >&2; } +info() { echo -e "${CYAN}[i]${NC} $*"; } +section() { echo -e "\n${BLUE}━━━ $* ━━━${NC}"; } + +# ─── Defaults ─────────────────────────────────────────────────────────────── +IPA_DOMAIN="freeipa.abdelbaki.eu" +IPA_REALM="FREEIPA.ABDELBAKI.EU" +IPA_SERVER="freeipa.abdelbaki.eu" +IPA_HOSTNAME="" +IPA_PRINCIPAL="admin" +IPA_PASSWORD="${IPA_PASSWORD:-}" +MKHOMEDIR=true +CONFIGURE_SUDO=true +DNS_UPDATE=true +NTP_SERVER="" +ENABLE_FIDO2=false +FIDO2_USERS=() +UNINSTALL=false + +# ─── Argument parsing ──────────────────────────────────────────────────────── +usage() { + grep '^#' "$0" | grep -v '#!/' | sed 's/^# \{0,1\}//' + exit 0 +} + +while [[ $# -gt 0 ]]; do + case $1 in + -d|--domain) IPA_DOMAIN="$2"; shift 2 ;; + -r|--realm) IPA_REALM="$2"; shift 2 ;; + -s|--server) IPA_SERVER="$2"; shift 2 ;; + -h|--hostname) IPA_HOSTNAME="$2"; shift 2 ;; + -p|--principal) IPA_PRINCIPAL="$2"; shift 2 ;; + -w|--password) IPA_PASSWORD="$2"; shift 2 ;; + -m|--mkhomedir) MKHOMEDIR=true; shift ;; + --no-mkhomedir) MKHOMEDIR=false; shift ;; + --sudo) CONFIGURE_SUDO=true; shift ;; + --no-sudo) CONFIGURE_SUDO=false; shift ;; + --no-dns-update) DNS_UPDATE=false; shift ;; + --ntp-server) NTP_SERVER="$2"; shift 2 ;; + --fido2) ENABLE_FIDO2=true; shift ;; + --fido2-user) FIDO2_USERS+=("$2"); shift 2 ;; + --uninstall) UNINSTALL=true; shift ;; + --help) usage ;; + *) error "Unknown option: $1"; exit 1 ;; + esac +done + +# ─── Validation ────────────────────────────────────────────────────────────── +section "Validating inputs" + +[[ $EUID -ne 0 ]] && { error "Must be run as root"; exit 1; } + +if [[ "$UNINSTALL" == false ]]; then + [[ -z "$IPA_DOMAIN" ]] && { error "--domain is required"; exit 1; } + [[ -z "$IPA_SERVER" ]] && { error "--server is required"; exit 1; } + + if [[ -z "$IPA_REALM" ]]; then + IPA_REALM="${IPA_DOMAIN^^}" + log "Realm derived from domain: $IPA_REALM" + fi + + if [[ -z "$IPA_HOSTNAME" ]]; then + IPA_HOSTNAME=$(hostname -f) + log "Using current hostname: $IPA_HOSTNAME" + fi + + if [[ -z "$IPA_PASSWORD" ]]; then + read -rsp "Enter password for ${IPA_PRINCIPAL}@${IPA_REALM}: " IPA_PASSWORD + echo + fi + + [[ -z "$IPA_PASSWORD" ]] && { error "Password is required"; exit 1; } + + # If --fido2-user was passed, implicitly enable fido2 + [[ ${#FIDO2_USERS[@]} -gt 0 ]] && ENABLE_FIDO2=true +fi + +# ─── Uninstall path ─────────────────────────────────────────────────────────── +if [[ "$UNINSTALL" == true ]]; then + section "Uninstalling FreeIPA client" + if command -v ipa-client-install &>/dev/null; then + ipa-client-install --uninstall --unattended + log "Client uninstalled" + else + warn "ipa-client-install not found — may not have been installed" + fi + + # Clean up FIDO2 PAM config if present + if [[ -f /etc/pam.d/fido2-auth ]]; then + rm -f /etc/pam.d/fido2-auth + log "Removed FIDO2 PAM config" + fi + exit 0 +fi + +# ─── Detect OS ─────────────────────────────────────────────────────────────── +section "Detecting OS" + +if [[ -f /etc/os-release ]]; then + source /etc/os-release + OS_ID="${ID}" + OS_VERSION="${VERSION_ID%%.*}" + log "Detected: $PRETTY_NAME" +else + error "Cannot detect OS — /etc/os-release missing" + exit 1 +fi + +case "$OS_ID" in + rhel|centos|rocky|almalinux|fedora) + PKG_MANAGER="dnf" + IPA_CLIENT_PKG="freeipa-client" + SSSD_PKGS="sssd sssd-ipa sssd-tools" + ODDJOB_PKGS="oddjob oddjob-mkhomedir" + CHRONY_PKG="chrony" + FIDO2_PKGS="pam-u2f" + FIDO2_TOOLS_PKGS="pamu2fcfg" + ;; + debian|ubuntu) + PKG_MANAGER="apt-get" + IPA_CLIENT_PKG="freeipa-client" + SSSD_PKGS="sssd sssd-ipa" + ODDJOB_PKGS="" + CHRONY_PKG="chrony" + FIDO2_PKGS="libpam-u2f" + FIDO2_TOOLS_PKGS="pamu2fcfg" + warn "Debian/Ubuntu support is best-effort — Rocky/Alma recommended" + ;; + arch) + PKG_MANAGER="pacman" + IPA_CLIENT_PKG="freeipa" + SSSD_PKGS="sssd" + ODDJOB_PKGS="oddjob" + CHRONY_PKG="chrony" + FIDO2_PKGS="pam-u2f" + FIDO2_TOOLS_PKGS="pamu2fcfg" + warn "Arch Linux: ensure the AUR helper (yay/paru) is available for AUR packages" + ;; + *) + error "Unsupported OS: $OS_ID" + exit 1 + ;; +esac + +# ─── Helper: install packages ───────────────────────────────────────────────── +pkg_install() { + local pkgs=("$@") + [[ ${#pkgs[@]} -eq 0 ]] && return 0 + case "$PKG_MANAGER" in + dnf) dnf install -y "${pkgs[@]}" ;; + apt-get) DEBIAN_FRONTEND=noninteractive apt-get install -y "${pkgs[@]}" ;; + pacman) pacman -Sy --noconfirm "${pkgs[@]}" ;; + esac +} + +# ─── Pre-flight checks ──────────────────────────────────────────────────────── +section "Pre-flight checks" + +log "Checking connectivity to IPA server $IPA_SERVER..." +if ! ping -c 2 -W 3 "$IPA_SERVER" &>/dev/null; then + error "Cannot reach IPA server $IPA_SERVER — check network/DNS" + exit 1 +fi + +for port in 443 88; do + if ! timeout 5 bash -c "echo >/dev/tcp/$IPA_SERVER/$port" 2>/dev/null; then + warn "Port $port on $IPA_SERVER unreachable — enrollment may fail" + fi +done + +# FIDO2 pre-flight: check a key is actually plugged in +if [[ "$ENABLE_FIDO2" == true ]]; then + if ! command -v fido2-token &>/dev/null; then + warn "fido2-token not found yet — will install libfido2 tooling" + else + FIDO2_DEVICES=$(fido2-token -L 2>/dev/null | wc -l) + if [[ "$FIDO2_DEVICES" -eq 0 ]]; then + warn "No FIDO2 devices detected. Plug in the key before the registration step." + else + log "Detected $FIDO2_DEVICES FIDO2 device(s)" + fi + fi +fi + +log "Checking time synchronization..." +if command -v chronyc &>/dev/null; then + OFFSET=$(chronyc tracking 2>/dev/null | awk '/System time/ {print $4}' | tr -d '-') + if [[ -n "$OFFSET" ]]; then + INT_OFFSET=${OFFSET%.*} + if [[ "${INT_OFFSET:-0}" -gt 60 ]]; then + warn "Time offset is ${OFFSET}s — Kerberos requires <300s skew" + else + log "Time offset: ${OFFSET}s — OK" + fi + fi +else + warn "chrony not found — installing and syncing" + pkg_install "$CHRONY_PKG" + systemctl enable --now chronyd + sleep 3 +fi + +if [[ -n "$NTP_SERVER" ]]; then + log "Configuring NTP server: $NTP_SERVER" + echo "server $NTP_SERVER iburst" >> /etc/chrony.conf + systemctl restart chronyd + chronyc makestep +fi + +if [[ -f /etc/ipa/default.conf ]]; then + warn "This host appears to already be enrolled in an IPA domain" + read -rp "Re-enroll? This will uninstall first. [y/N]: " CONFIRM + if [[ "$CONFIRM" =~ ^[Yy]$ ]]; then + log "Uninstalling existing client..." + ipa-client-install --uninstall --unattended || true + else + log "Aborting" + exit 0 + fi +fi + +# ─── Install packages ───────────────────────────────────────────────────────── +section "Installing packages" + +# On Arch, freeipa pulls ipa-client; sssd is separate +if [[ "$OS_ID" == "arch" ]]; then + # Ensure pacman db is fresh + pacman -Sy --noconfirm + pkg_install $IPA_CLIENT_PKG $SSSD_PKGS $ODDJOB_PKGS + + # Arch: pam-u2f may be in AUR — try pacman first, fall back to yay/paru + if [[ "$ENABLE_FIDO2" == true ]]; then + if ! pacman -Qi pam-u2f &>/dev/null; then + if command -v yay &>/dev/null; then + log "Installing pam-u2f via yay (AUR)" + sudo -u nobody yay -S --noconfirm pam-u2f pamu2fcfg 2>/dev/null || \ + warn "AUR install failed — install pam-u2f manually from AUR" + elif command -v paru &>/dev/null; then + log "Installing pam-u2f via paru (AUR)" + sudo -u nobody paru -S --noconfirm pam-u2f pamu2fcfg 2>/dev/null || \ + warn "AUR install failed — install pam-u2f manually from AUR" + else + warn "pam-u2f not in official repos and no AUR helper found." + warn "Install manually: yay -S pam-u2f pamu2fcfg" + fi + fi + # libfido2 for fido2-token tooling + pkg_install libfido2 + fi +else + pkg_install $IPA_CLIENT_PKG $SSSD_PKGS + + if [[ -n "$ODDJOB_PKGS" ]]; then + pkg_install $ODDJOB_PKGS + fi + + if [[ "$ENABLE_FIDO2" == true ]]; then + pkg_install $FIDO2_PKGS libfido2 + # pamu2fcfg may be a separate package on some distros + pkg_install $FIDO2_TOOLS_PKGS 2>/dev/null || true + fi +fi + +log "Packages installed" + +# ─── Set hostname ───────────────────────────────────────────────────────────── +section "Configuring hostname" + +CURRENT_HOSTNAME=$(hostname -f 2>/dev/null || hostname) +if [[ "$CURRENT_HOSTNAME" != "$IPA_HOSTNAME" ]]; then + log "Setting hostname to $IPA_HOSTNAME" + hostnamectl set-hostname "$IPA_HOSTNAME" +fi + +HOST_IP=$(ip route get 1 | awk '{print $7; exit}') +SHORT_NAME="${IPA_HOSTNAME%%.*}" + +sed -i "/\b${IPA_HOSTNAME}\b/d" /etc/hosts +sed -i "/\b${SHORT_NAME}\b/d" /etc/hosts +echo "$HOST_IP $IPA_HOSTNAME $SHORT_NAME" >> /etc/hosts +log "Updated /etc/hosts: $HOST_IP $IPA_HOSTNAME" + +# ─── Arch-specific pre-enrollment setup ────────────────────────────────────── +if [[ "$OS_ID" == "arch" ]]; then + section "Arch-specific setup" + + # Arch ships nscd rather than sssd providing nss by default; + # make sure nsswitch.conf will work with sss + if ! grep -q "sss" /etc/nsswitch.conf; then + sed -i 's/^passwd:.*/passwd: files sss/' /etc/nsswitch.conf + sed -i 's/^group:.*/group: files sss/' /etc/nsswitch.conf + sed -i 's/^shadow:.*/shadow: files sss/' /etc/nsswitch.conf + log "Updated nsswitch.conf for SSSD" + fi + + # Ensure the IPA CA cert directory exists (ipa-client-install expects it) + mkdir -p /etc/ipa +fi + +# ─── Run ipa-client-install ─────────────────────────────────────────────────── +section "Enrolling with FreeIPA" + +ENROLL_ARGS=( + --domain="$IPA_DOMAIN" + --realm="$IPA_REALM" + --server="$IPA_SERVER" + --hostname="$IPA_HOSTNAME" + --principal="$IPA_PRINCIPAL" + --password="$IPA_PASSWORD" + --unattended +) + +[[ "$MKHOMEDIR" == true ]] && ENROLL_ARGS+=(--mkhomedir) +[[ "$DNS_UPDATE" == false ]] && ENROLL_ARGS+=(--no-dns-update) +[[ "$CONFIGURE_SUDO" == true ]] && ENROLL_ARGS+=(--enable-dns-updates) + +log "Running ipa-client-install..." +if ipa-client-install "${ENROLL_ARGS[@]}"; then + log "Enrollment successful" +else + error "ipa-client-install failed" + exit 1 +fi + +# ─── SSSD configuration ─────────────────────────────────────────────────────── +section "Configuring SSSD" + +if [[ "$CONFIGURE_SUDO" == true ]]; then + if grep -q "^services" /etc/sssd/sssd.conf; then + if ! grep -q "sudo" /etc/sssd/sssd.conf; then + sed -i 's/^services = .*/& , sudo/' /etc/sssd/sssd.conf + log "Added sudo to SSSD services" + fi + fi + + if ! grep -q "^sudoers:.*sss" /etc/nsswitch.conf; then + if grep -q "^sudoers:" /etc/nsswitch.conf; then + sed -i 's/^sudoers:.*/sudoers: files sss/' /etc/nsswitch.conf + else + echo "sudoers: files sss" >> /etc/nsswitch.conf + fi + log "Configured nsswitch.conf for sudo via SSSD" + fi +fi + +systemctl restart sssd +systemctl enable sssd +log "SSSD restarted" + +# ─── PAM / homedir ──────────────────────────────────────────────────────────── +section "Configuring PAM" + +if [[ "$MKHOMEDIR" == true ]]; then + case "$OS_ID" in + rhel|centos|rocky|almalinux|fedora) + systemctl enable --now oddjobd 2>/dev/null || true + authselect enable-feature with-mkhomedir 2>/dev/null || \ + authconfig --enablemkhomedir --update 2>/dev/null || true + ;; + arch) + # Arch uses pam_mkhomedir directly in PAM stack + if ! grep -q "pam_mkhomedir" /etc/pam.d/system-login; then + sed -i '/^session.*pam_systemd/a session optional pam_mkhomedir.so skel=/etc/skel umask=077' \ + /etc/pam.d/system-login + log "Added pam_mkhomedir to /etc/pam.d/system-login" + fi + ;; + debian|ubuntu) + pam-auth-update --enable mkhomedir 2>/dev/null || true + ;; + esac + log "Home directory auto-creation enabled" +fi + +# ─── FIDO2 configuration ────────────────────────────────────────────────────── +if [[ "$ENABLE_FIDO2" == true ]]; then + section "Configuring FIDO2 authentication" + + # Check pam_u2f.so is actually present after install + PAM_U2F_SO=$(find /usr/lib /lib -name "pam_u2f.so" 2>/dev/null | head -1) + if [[ -z "$PAM_U2F_SO" ]]; then + error "pam_u2f.so not found — FIDO2 PAM module not installed correctly" + warn "Skipping FIDO2 configuration. Install pam-u2f manually and re-run with --fido2" + else + log "Found pam_u2f.so at: $PAM_U2F_SO" + + # ── Central credential store ───────────────────────────────────────── + # We use a central /etc/u2f_mappings file so credentials are stored + # system-wide (not per-user ~/.config/Yubico/u2f_keys), which is + # easier to manage and distribute via Ansible/Puppet. + U2F_MAPPINGS="/etc/u2f_mappings" + touch "$U2F_MAPPINGS" + chmod 600 "$U2F_MAPPINGS" + + # ── PAM stack integration ───────────────────────────────────────────── + # Strategy: FIDO2 as a SECOND factor (after password) for sudo and sshd. + # Change 'required' to 'sufficient' if you want FIDO2 as sole factor. + # + # We write a drop-in snippet and include it rather than patching + # distro PAM files directly, to survive pam-config updates. + + PAM_FIDO2_SNIPPET="/etc/pam.d/fido2-u2f" + cat > "$PAM_FIDO2_SNIPPET" <<'PAMEOF' +# pam-u2f FIDO2 second factor +# auth required = must present FIDO2 key (touch required by default) +# nouserok = allow login even if user has no key registered +# (remove this once all users have keys enrolled) +# authfile = path to central credential mapping file +auth required pam_u2f.so authfile=/etc/u2f_mappings nouserok cue +PAMEOF + + # Inject into sudo PAM — after the first 'auth' line (after password) + configure_pam_sudo() { + local pam_file="$1" + if [[ -f "$pam_file" ]] && ! grep -q "pam_u2f" "$pam_file"; then + # Insert after the last 'auth' line that isn't pam_u2f + sed -i '/^auth.*pam_sss\|^auth.*pam_unix/a auth include fido2-u2f' "$pam_file" + log "Injected FIDO2 into $pam_file" + fi + } + + case "$OS_ID" in + rhel|centos|rocky|almalinux|fedora) + configure_pam_sudo /etc/pam.d/sudo + configure_pam_sudo /etc/pam.d/sshd + ;; + arch) + configure_pam_sudo /etc/pam.d/sudo + configure_pam_sudo /etc/pam.d/sshd + ;; + debian|ubuntu) + configure_pam_sudo /etc/pam.d/sudo + configure_pam_sudo /etc/pam.d/sshd + ;; + esac + + # ── udev rule for FIDO2 devices ─────────────────────────────────────── + # Without this, non-root users can't talk to the USB HID device + UDEV_RULE="/etc/udev/rules.d/70-u2f.rules" + if [[ ! -f "$UDEV_RULE" ]]; then + cat > "$UDEV_RULE" <<'UDEVEOF' +# FIDO2/U2F device access for all users +# Covers YubiKey, Nitrokey, SoloKey, Google Titan, and generic FIDO2 keys +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", TAG+="uaccess", GROUP="plugdev", MODE="0660" +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", TAG+="uaccess", GROUP="plugdev", MODE="0660" +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", TAG+="uaccess", GROUP="plugdev", MODE="0660" +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1d6b", TAG+="uaccess", GROUP="plugdev", MODE="0660" +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2581", TAG+="uaccess", GROUP="plugdev", MODE="0660" +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", TAG+="uaccess", GROUP="plugdev", MODE="0660" +UDEVEOF + udevadm control --reload-rules + udevadm trigger + log "Installed udev rules for FIDO2 devices" + fi + + # ── plugdev group ───────────────────────────────────────────────────── + if ! getent group plugdev &>/dev/null; then + groupadd plugdev + log "Created plugdev group" + fi + + # ── Register keys for specified users ───────────────────────────────── + if [[ ${#FIDO2_USERS[@]} -gt 0 ]]; then + section "Registering FIDO2 keys" + info "You will be prompted to touch your FIDO2 key for each user." + info "Credentials are stored in $U2F_MAPPINGS" + echo + + for IPA_USER in "${FIDO2_USERS[@]}"; do + echo -e "${CYAN}━━━ Registering key for user: $IPA_USER ━━━${NC}" + + # Check if user already has an entry + if grep -q "^${IPA_USER}:" "$U2F_MAPPINGS" 2>/dev/null; then + warn "User $IPA_USER already has a FIDO2 credential in $U2F_MAPPINGS" + read -rp " Add an additional key for $IPA_USER? [y/N]: " ADD_MORE + [[ ! "$ADD_MORE" =~ ^[Yy]$ ]] && continue + fi + + info "Insert the FIDO2 key for $IPA_USER and press Enter when ready..." + read -r + + # Check key is present + if command -v fido2-token &>/dev/null; then + DETECTED=$(fido2-token -L 2>/dev/null | wc -l) + if [[ "$DETECTED" -eq 0 ]]; then + warn "No FIDO2 device detected — skipping $IPA_USER" + continue + fi + fi + + info "Touch the key when it blinks..." + + # pamu2fcfg outputs: username:credentialID,publicKey + if NEW_CRED=$(pamu2fcfg -u "$IPA_USER" -o "pam://$(hostname -f)" 2>/dev/null); then + if grep -q "^${IPA_USER}:" "$U2F_MAPPINGS"; then + # Append additional key to existing line (pamu2fcfg format) + EXTRA=$(echo "$NEW_CRED" | cut -d: -f2-) + sed -i "s|^${IPA_USER}:.*|&:${EXTRA}|" "$U2F_MAPPINGS" + log "Added additional FIDO2 key for $IPA_USER" + else + echo "$NEW_CRED" >> "$U2F_MAPPINGS" + log "Registered FIDO2 key for $IPA_USER" + fi + + # Add user to plugdev group + usermod -aG plugdev "$IPA_USER" 2>/dev/null || true + else + error "pamu2fcfg failed for $IPA_USER — key may not support this operation" + warn "You can register manually later:" + warn " pamu2fcfg -u $IPA_USER -o pam://\$(hostname -f) >> $U2F_MAPPINGS" + fi + done + else + # No users specified — print instructions for manual registration + info "No --fido2-user specified. To register keys manually:" + echo + echo " # As root, for each user:" + echo " pamu2fcfg -u -o pam://\$(hostname -f) >> $U2F_MAPPINGS" + echo + echo " # Then add user to plugdev group:" + echo " usermod -aG plugdev " + echo + fi + + log "FIDO2 PAM configuration complete" + info "Note: 'nouserok' is set — users without a registered key can still log in." + info "Remove 'nouserok' from $PAM_FIDO2_SNIPPET once all users have keys enrolled." + fi +fi + +# ─── SSH configuration ──────────────────────────────────────────────────────── +section "Configuring SSH" + +SSHD_CONFIG="/etc/ssh/sshd_config" + +set_sshd_option() { + local key="$1" val="$2" + if grep -qE "^#?${key}" "$SSHD_CONFIG"; then + sed -i "s|^#\?${key}.*|${key} ${val}|" "$SSHD_CONFIG" + else + echo "${key} ${val}" >> "$SSHD_CONFIG" + fi +} + +set_sshd_option "GSSAPIAuthentication" "yes" +set_sshd_option "GSSAPICleanupCredentials" "yes" +set_sshd_option "AuthorizedKeysCommand" "/usr/bin/sss_ssh_authorizedkeys" +set_sshd_option "AuthorizedKeysCommandUser" "nobody" + +# If FIDO2 is enabled, allow keyboard-interactive so pam_u2f can prompt for touch +if [[ "$ENABLE_FIDO2" == true ]]; then + set_sshd_option "ChallengeResponseAuthentication" "yes" + set_sshd_option "AuthenticationMethods" "publickey,keyboard-interactive" + log "SSH configured for FIDO2 second factor (keyboard-interactive)" +fi + +systemctl restart sshd +log "SSH configured" + +# ─── Verify enrollment ──────────────────────────────────────────────────────── +section "Verifying enrollment" + +if echo "$IPA_PASSWORD" | kinit "${IPA_PRINCIPAL}@${IPA_REALM}" &>/dev/null; then + log "Kerberos authentication: OK" + kdestroy &>/dev/null +else + warn "Could not obtain Kerberos ticket — check credentials/connectivity" +fi + +if id "admin" &>/dev/null; then + log "SSSD user lookup: OK (resolved 'admin' from IPA)" +else + warn "SSSD user lookup failed — SSSD may still be starting up" +fi + +# ─── Summary ────────────────────────────────────────────────────────────────── +section "Enrollment complete" + +cat <@$(hostname -f) + • Check SSSD: sssctl domain-status + • Check Kerberos: kinit @ + • List FIDO2 creds: cat /etc/u2f_mappings + • Register key: pamu2fcfg -u -o pam://$(hostname -f) >> /etc/u2f_mappings + +EOF