From 3ee375b1c129e1aa98aaf06fc3f1e8c30a0b02e7 Mon Sep 17 00:00:00 2001 From: The_miro Date: Thu, 2 Jul 2026 11:27:46 +0200 Subject: [PATCH] fix(ansipa): FreeIPA enrollment for Arch/Fedora/RHEL + autofs AUR workaround MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found while testing client enrollment across distros in VMs: - Restore the executable bit on freeipa-enroll.sh (lost on a prior edit), which made freeipa-client.sh fail with a misleading "not found" for every path. - Arch: freeipa-client is AUR-only, not the repo package "freeipa" — pacman -S freeipa died with "target not found". Install sssd/krb5 from repos and freeipa-client via an aur_install helper that drops to the sudo user. - Bundle autofs-pkgbuild/: freeipa-client (AUR) hard-depends on autofs (AUR), whose PKGBUILD pins autofs-5.1.9.tar.xz — pruned from kernel.org at the 5.2.0 release, so it 404s and blocks all Arch enrollment. Build autofs ourselves from upstream git pinned to a 5.1.9-series commit that already carries the cyrus-sasl function-pointer fix GCC 14+ needs, and install it before the freeipa-client AUR build so the broken AUR autofs is never touched. Verified the bundled PKGBUILD builds and installs cleanly on Arch. - Fedora vs RHEL/Rocky/Alma use different client package names: keep freeipa-client on Fedora, use ipa-client on the RHEL family (freeipa-client does not exist there). Split the case arms accordingly. - Guard both freeipa-client.sh entry points against unattended/no-TTY runs: the module TUI would hang on a dialog menu, and the wrapper would block on an interactive password read when the shipped answerfile has a blank password. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01MUhrcFU8J1Hnf7vNqNxZNi --- .../FreeipaAnsible/autofs-pkgbuild/PKGBUILD | 79 +++++++++++++ .../autofs-arch-configuration-path.patch | 29 +++++ .../modules/FreeipaAnsible/freeipa-client.sh | 4 + .../modules/FreeipaAnsible/freeipa-enroll.sh | 97 +++++++++++++-- .../optional-Modules/apps/freeipa-client.sh | 111 ++++++++++-------- 5 files changed, 265 insertions(+), 55 deletions(-) create mode 100644 setup/modules/FreeipaAnsible/autofs-pkgbuild/PKGBUILD create mode 100644 setup/modules/FreeipaAnsible/autofs-pkgbuild/autofs-arch-configuration-path.patch mode change 100644 => 100755 setup/modules/FreeipaAnsible/freeipa-enroll.sh diff --git a/setup/modules/FreeipaAnsible/autofs-pkgbuild/PKGBUILD b/setup/modules/FreeipaAnsible/autofs-pkgbuild/PKGBUILD new file mode 100644 index 0000000..520c5ba --- /dev/null +++ b/setup/modules/FreeipaAnsible/autofs-pkgbuild/PKGBUILD @@ -0,0 +1,79 @@ +# PKGBUILD — autofs (ansipa self-hosted build) +# +# WHY THIS EXISTS: +# The AUR `autofs` package is a hard dependency of the AUR `freeipa-client` +# package (which ansipa needs to enroll Arch hosts into FreeIPA). As of the +# autofs 5.2.0 upstream release, the kernel.org mirror pruned the +# autofs-5.1.9.tar.xz tarball that the AUR PKGBUILD pins, so the AUR autofs +# build fails with a 404 on its source — which in turn makes freeipa-client +# (and therefore every Arch FreeIPA enrollment) impossible to install. +# +# Rather than depend on that fragile, currently-broken tarball URL, this +# PKGBUILD builds autofs from the upstream git repository pinned to a fixed +# commit on the 5.1.9 maintenance series. That commit already contains the +# upstream fix for "incompatible function pointer types in cyrus-sasl module" +# (commit b7ff971b) that GCC 14+ requires, so we do not need the separate +# downstream compiler patch the AUR fetched from the (also fragile) mirror. +# +# freeipa-enroll.sh builds and installs this package before invoking the AUR +# helper for freeipa-client, so the dependency is already satisfied and the +# broken AUR autofs is never touched. +# +# Based on the AUR autofs PKGBUILD (maintainers: Alexander Jacocks, Hyacinthe +# Cartiaux et al.), reworked to build from git. + +pkgname=autofs +pkgver=5.1.9 +pkgrel=99 # high pkgrel so this local build wins over a stale AUR copy +pkgdesc='A kernel-based automounter for Linux (ansipa git build — fixes dead AUR tarball)' +arch=('x86_64') +url='https://git.kernel.org/pub/scm/linux/storage/autofs/autofs.git' +license=('GPL-2.0-or-later') +depends=('libxml2') +makedepends=('git' 'libldap' 'krb5' 'kmod' 'sssd' 'libnsl' 'rpcsvc-proto' 'systemd') +optdepends=('krb5: for LDAP support' + 'sssd: for SSSD integration') +backup=('etc/autofs/auto.master' + 'etc/autofs/auto.misc' + 'etc/autofs/auto.net' + 'etc/autofs/auto.smb' + 'etc/autofs/autofs.conf' + 'etc/autofs/autofs_ldap_auth.conf' + 'etc/default/autofs') +# Pinned commit on the 5.1.9 maintenance branch (tip as of this writing). +# Includes b7ff971b (cyrus-sasl function-pointer fix, needed for GCC 14+). +_commit='dec0b32389b670398ef1351193977daa629a6dea' +source=("${pkgname}::git+https://git.kernel.org/pub/scm/linux/storage/autofs/autofs.git#commit=${_commit}" + 'autofs-arch-configuration-path.patch') +sha256sums=('SKIP' + '9974887b47de70f0b871230da96fa4620562289915b55401383232794578d18c') + +prepare() { + cd "${pkgname}" + # Relocate config paths to Arch's /etc/autofs layout (matches the AUR package). + patch --forward --strip=1 --input=../autofs-arch-configuration-path.patch + # configure is committed upstream; refresh its mtime so make does not try to + # regenerate it (autofs uses a hand-written build system, not automake, so no + # autoreconf is required — a stale-timestamp regen would just fail). + touch configure +} + +build() { + cd "${pkgname}" + ./configure --prefix=/usr \ + --sysconfdir=/etc/autofs \ + --sbindir=/usr/bin \ + --with-mapdir=/etc/autofs \ + --with-confdir=/etc/default \ + --without-hesiod \ + --enable-ignore-busy \ + --with-libtirpc \ + --with-systemd + make +} + +package() { + cd "${pkgname}" + make INSTALLROOT="${pkgdir}" install install_samples + install -dm755 "${pkgdir}/etc/autofs/auto.master.d" +} diff --git a/setup/modules/FreeipaAnsible/autofs-pkgbuild/autofs-arch-configuration-path.patch b/setup/modules/FreeipaAnsible/autofs-pkgbuild/autofs-arch-configuration-path.patch new file mode 100644 index 0000000..f6df5c2 --- /dev/null +++ b/setup/modules/FreeipaAnsible/autofs-pkgbuild/autofs-arch-configuration-path.patch @@ -0,0 +1,29 @@ +--- a/samples/auto.master ++++ b/samples/auto.master +@@ -4,7 +4,7 @@ + # mount-point [map-type[,format]:]map [options] + # For details of the format look at auto.master(5). + # +-/misc /etc/auto.misc ++/misc /etc/autofs/auto.misc + # + # NOTE: mounts done from a hosts map will be mounted with the + # "nosuid" and "nodev" options unless the "suid" and "dev" +@@ -12,14 +12,14 @@ + # + /net -hosts + # +-# Include /etc/auto.master.d/*.autofs ++# Include /etc/autofs/auto.master.d/*.autofs + # To add an extra map using this mechanism you will need to add +-# two configuration items - one /etc/auto.master.d/extra.autofs file ++# two configuration items - one /etc/autofs/auto.master.d/extra.autofs file + # (using the same line format as the auto.master file) + # and a separate mount map (e.g. /etc/auto.extra or an auto.extra NIS map) + # that is referred to by the extra.autofs file. + # +-+dir:/etc/auto.master.d +++dir:/etc/autofs/auto.master.d + # + # If you have fedfs set up and the related binaries, either + # built as part of autofs or installed from another package, diff --git a/setup/modules/FreeipaAnsible/freeipa-client.sh b/setup/modules/FreeipaAnsible/freeipa-client.sh index 7643e91..356f67f 100755 --- a/setup/modules/FreeipaAnsible/freeipa-client.sh +++ b/setup/modules/FreeipaAnsible/freeipa-client.sh @@ -80,6 +80,10 @@ if [[ "$MODE" == "answerfile" ]]; then [[ -n "$AF_NTP" ]] && ARGS+=(--ntp-server "$AF_NTP") if [[ -z "$AF_PASSWORD" ]]; then + if [[ "${MARCHY_UNATTENDED:-0}" == "1" || ! -t 0 ]]; then + echo "Error: answerfile has no password and no terminal is attached to prompt for one — skipping enrollment." >&2 + exit 1 + fi printf '[?] Password for %s@%s: ' "${AF_PRINCIPAL:-admin}" "${AF_REALM:-REALM}" read -rs AF_PASSWORD; echo fi diff --git a/setup/modules/FreeipaAnsible/freeipa-enroll.sh b/setup/modules/FreeipaAnsible/freeipa-enroll.sh old mode 100644 new mode 100755 index 9727825..5aaefe9 --- a/setup/modules/FreeipaAnsible/freeipa-enroll.sh +++ b/setup/modules/FreeipaAnsible/freeipa-enroll.sh @@ -21,6 +21,10 @@ set -euo pipefail +# Absolute path of this script's directory, so bundled build files (e.g. the +# autofs-pkgbuild/ fallback for Arch) resolve regardless of the caller's CWD. +SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + # ─── Colours ──────────────────────────────────────────────────────────────── RED='\033[0;31m' GREEN='\033[0;32m' @@ -140,7 +144,7 @@ else fi case "$OS_ID" in - rhel|centos|rocky|almalinux|fedora) + fedora) PKG_MANAGER="dnf" IPA_CLIENT_PKG="freeipa-client" SSSD_PKGS="sssd sssd-ipa sssd-tools" @@ -149,6 +153,18 @@ case "$OS_ID" in FIDO2_PKGS="pam-u2f" FIDO2_TOOLS_PKGS="pamu2fcfg" ;; + rhel|centos|rocky|almalinux) + PKG_MANAGER="dnf" + # RHEL 8+ (and derivatives Rocky/Alma/CentOS Stream) renamed the + # freeipa-client package to ipa-client — freeipa-client does not + # exist in these repos, so `dnf install freeipa-client` fails outright. + IPA_CLIENT_PKG="ipa-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" @@ -161,13 +177,16 @@ case "$OS_ID" in ;; arch) PKG_MANAGER="pacman" - IPA_CLIENT_PKG="freeipa" + # freeipa-client is AUR-only on Arch (the official repos only carry + # sssd); it must be installed via an AUR helper, NOT plain pacman — + # `pacman -S freeipa-client` fails with "target not found". + IPA_CLIENT_PKG="freeipa-client" 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" + warn "Arch Linux: ensure an AUR helper (yay/paru) is available for AUR packages" ;; *) error "Unsupported OS: $OS_ID" @@ -255,15 +274,79 @@ fi # ─── Install packages ───────────────────────────────────────────────────────── section "Installing packages" -# On Arch, freeipa pulls ipa-client; sssd is separate +# aur_install — install AUR packages via yay/paru. AUR helpers refuse +# to run as root, so drop to the invoking sudo user (makepkg builds as them, +# their sudo rights handle the final pacman -U). +aur_install() { + local helper="" + command -v yay &>/dev/null && helper="yay" + [[ -z "$helper" ]] && command -v paru &>/dev/null && helper="paru" + if [[ -z "$helper" ]]; then + error "No AUR helper (yay/paru) found — cannot install: $*" + error "Install one first, e.g.: pacman -S --needed base-devel git && " + return 1 + fi + local run_as="${SUDO_USER:-}" + if [[ -n "$run_as" && "$run_as" != "root" ]]; then + sudo -u "$run_as" "$helper" -S --noconfirm --needed "$@" + else + # Running as plain root (no sudo user to drop to) — makepkg forbids root. + error "AUR install of '$*' needs a non-root user; re-run via sudo from a regular account." + return 1 + fi +} + +# build_local_pkg — build a bundled PKGBUILD and pacman -U it. +# makepkg refuses to run as root, so the build runs as the invoking sudo user in +# a writable copy of the dir (the repo copy may be read-only); we then install +# the resulting package as root. Used to satisfy freeipa-client's autofs +# dependency without the broken AUR autofs (see autofs-pkgbuild/PKGBUILD). +build_local_pkg() { + local src_dir="$1" run_as="${SUDO_USER:-}" + [[ -d "$src_dir" ]] || { error "PKGBUILD dir not found: $src_dir"; return 1; } + if [[ -z "$run_as" || "$run_as" == "root" ]]; then + error "Local package build needs a non-root user; re-run via sudo from a regular account." + return 1 + fi + local build_dir + build_dir=$(sudo -u "$run_as" mktemp -d) || return 1 + cp -r "$src_dir"/. "$build_dir/" + chown -R "$run_as": "$build_dir" + # Build only (as the user); -s pulls makedepends, -f overwrites a stale build. + if ! sudo -u "$run_as" bash -c "cd '$build_dir' && makepkg -sf --noconfirm --needed"; then + error "makepkg failed in $build_dir" + rm -rf "$build_dir" + return 1 + fi + # Install every package artifact produced (as root — we already are). + pacman -U --noconfirm "$build_dir"/*.pkg.tar.* || { rm -rf "$build_dir"; return 1; } + rm -rf "$build_dir" +} + +# On Arch, sssd comes from the official repos but freeipa-client is AUR-only if [[ "$OS_ID" == "arch" ]]; then # Ensure pacman db is fresh pacman -Sy --noconfirm - # freeipa is AUR-only; skip if ipa-client-install already present + # sssd and Kerberos tooling are in the official repos + pkg_install $SSSD_PKGS krb5 cyrus-sasl-gssapi + # freeipa-client is AUR-only; skip if ipa-client-install already present if command -v ipa-client-install &>/dev/null; then - log "ipa-client-install already present — skipping AUR freeipa install" + log "ipa-client-install already present — skipping AUR freeipa-client install" else - pkg_install $IPA_CLIENT_PKG $SSSD_PKGS + # freeipa-client (AUR) hard-depends on autofs, but the AUR autofs package + # is currently unbuildable: its pinned source tarball (autofs-5.1.9.tar.xz) + # was pruned from the kernel.org mirror when 5.2.0 released, so it 404s and + # takes freeipa-client — and thus the whole enrollment — down with it. + # Build autofs ourselves from upstream git (see autofs-pkgbuild/) and install + # it first so the AUR helper sees the dependency already satisfied and never + # touches the broken AUR autofs. + if ! pacman -Qq autofs &>/dev/null; then + log "Building autofs from bundled git PKGBUILD (works around broken AUR autofs)..." + build_local_pkg "$SELF_DIR/autofs-pkgbuild" \ + || { error "autofs build failed — cannot satisfy freeipa-client dependency"; exit 1; } + fi + log "Installing $IPA_CLIENT_PKG from the AUR (this builds from source — can take a while)..." + aur_install $IPA_CLIENT_PKG || { error "freeipa-client install failed"; exit 1; } fi # oddjob (for mkhomedir) — skip if not available in official repos if pacman -Si oddjob &>/dev/null 2>&1; then diff --git a/setup/modules/optional-Modules/apps/freeipa-client.sh b/setup/modules/optional-Modules/apps/freeipa-client.sh index b47e744..a674c0f 100755 --- a/setup/modules/optional-Modules/apps/freeipa-client.sh +++ b/setup/modules/optional-Modules/apps/freeipa-client.sh @@ -77,6 +77,69 @@ fi # (it will exist after freeipa-client is installed). systemctl enable sssd.service 2>/dev/null || true +# ── run_enroll helper ───────────────────────────────────────────────────────── +# Runs the enrolment either via the server-generated freeipa-client.sh wrapper +# (preferred, because it already has the correct domain/realm baked in) or by +# falling back to calling ipa-client-install directly with the same arguments. +run_enroll() { + local args=("$@") + if [[ -x "$CLIENT_SCRIPT" ]]; then + # Preferred path: use the pre-generated client script from the server + exec "$CLIENT_SCRIPT" "${args[@]}" + else + # Fall back to ipa-client-install directly + local cmd=(ipa-client-install --unattended) + local dom="" rlm="" srv="" hst="" pri="admin" pwd="" ntp="" + local mkhomedir=true sudo_=true dns=true fido2=false + declare -a fido2_users=() + + # Parse our internal argument format and translate to ipa-client-install flags + for ((i=0; i<${#args[@]}; i++)); do + case "${args[$i]}" in + --domain) dom="${args[$((i+1))]}"; ((i++)) ;; + --realm) rlm="${args[$((i+1))]}"; ((i++)) ;; + --server) srv="${args[$((i+1))]}"; ((i++)) ;; + --hostname) hst="${args[$((i+1))]}"; ((i++)) ;; + --principal) pri="${args[$((i+1))]}"; ((i++)) ;; + --password) pwd="${args[$((i+1))]}"; ((i++)) ;; + --ntp-server) ntp="${args[$((i+1))]}"; ((i++)) ;; + --no-mkhomedir) mkhomedir=false ;; + --no-sudo) sudo_=false ;; + --no-dns-update) dns=false ;; + --fido2) fido2=true ;; + --fido2-user) fido2_users+=("${args[$((i+1))]}"); ((i++)) ;; + esac + done + + # Build the ipa-client-install command from parsed values + [[ -n "$dom" ]] && cmd+=(--domain "$dom") + [[ -n "$rlm" ]] && cmd+=(--realm "$rlm") + [[ -n "$srv" ]] && cmd+=(--server "$srv") + [[ -n "$hst" ]] && cmd+=(--hostname "$hst") + [[ -n "$ntp" ]] && cmd+=(--ntp-server "$ntp") + cmd+=(--principal "$pri" --password "$pwd") + $mkhomedir && cmd+=(--mkhomedir) || cmd+=(--no-mkhomedir) + $sudo_ && cmd+=(--enable-dns-updates) || true + ! $dns && cmd+=(--no-dns-update) || true + exec "${cmd[@]}" + fi +} + +# Unattended mode (answerfile-driven install, no TTY): the interactive dialog +# TUI below would hang forever with no one to answer it. Auto-enroll from the +# default answerfile if the server already provisioned one, otherwise skip +# cleanly — packages are installed and the module can be re-run manually later. +if [[ "${MARCHY_UNATTENDED:-0}" == "1" || ! -t 0 ]]; then + if [[ -f "$DEFAULT_AF" ]]; then + echo "[+] Unattended mode — enrolling from $DEFAULT_AF" + run_enroll --answerfile "$DEFAULT_AF" + else + echo "[!] Unattended mode — no answerfile at $DEFAULT_AF, skipping enrollment." + echo " Enroll later with: $CLIENT_SCRIPT --answerfile $DEFAULT_AF" + fi + exit 0 +fi + # Ensure dialog is available for the TUI; install it on-the-fly if missing. command -v dialog &>/dev/null || pacman -S --noconfirm --needed dialog @@ -140,54 +203,6 @@ CHOICE=$(d --title " FreeIPA Client Enrollment " \ "manual" "Enter enrollment data manually" \ "skip" "Skip — enroll later") || CHOICE="skip" -# ── run_enroll helper ───────────────────────────────────────────────────────── -# Runs the enrolment either via the server-generated freeipa-client.sh wrapper -# (preferred, because it already has the correct domain/realm baked in) or by -# falling back to calling ipa-client-install directly with the same arguments. -run_enroll() { - local args=("$@") - if [[ -x "$CLIENT_SCRIPT" ]]; then - # Preferred path: use the pre-generated client script from the server - exec "$CLIENT_SCRIPT" "${args[@]}" - else - # Fall back to ipa-client-install directly - local cmd=(ipa-client-install --unattended) - local dom="" rlm="" srv="" hst="" pri="admin" pwd="" ntp="" - local mkhomedir=true sudo_=true dns=true fido2=false - declare -a fido2_users=() - - # Parse our internal argument format and translate to ipa-client-install flags - for ((i=0; i<${#args[@]}; i++)); do - case "${args[$i]}" in - --domain) dom="${args[$((i+1))]}"; ((i++)) ;; - --realm) rlm="${args[$((i+1))]}"; ((i++)) ;; - --server) srv="${args[$((i+1))]}"; ((i++)) ;; - --hostname) hst="${args[$((i+1))]}"; ((i++)) ;; - --principal) pri="${args[$((i+1))]}"; ((i++)) ;; - --password) pwd="${args[$((i+1))]}"; ((i++)) ;; - --ntp-server) ntp="${args[$((i+1))]}"; ((i++)) ;; - --no-mkhomedir) mkhomedir=false ;; - --no-sudo) sudo_=false ;; - --no-dns-update) dns=false ;; - --fido2) fido2=true ;; - --fido2-user) fido2_users+=("${args[$((i+1))]}"); ((i++)) ;; - esac - done - - # Build the ipa-client-install command from parsed values - [[ -n "$dom" ]] && cmd+=(--domain "$dom") - [[ -n "$rlm" ]] && cmd+=(--realm "$rlm") - [[ -n "$srv" ]] && cmd+=(--server "$srv") - [[ -n "$hst" ]] && cmd+=(--hostname "$hst") - [[ -n "$ntp" ]] && cmd+=(--ntp-server "$ntp") - cmd+=(--principal "$pri" --password "$pwd") - $mkhomedir && cmd+=(--mkhomedir) || cmd+=(--no-mkhomedir) - $sudo_ && cmd+=(--enable-dns-updates) || true - ! $dns && cmd+=(--no-dns-update) || true - exec "${cmd[@]}" - fi -} - # ── Answerfile mode ─────────────────────────────────────────────────────────── # The answerfile is a JSON file pre-generated by freeipa-server.sh with domain, # realm, and server already filled in. The user only needs to add the password.