fix(ansipa): full end-to-end test + multiple bug fixes
Fixes found during a complete archiso → FreeIPA DC → client enrollment → ansipa policy deployment test cycle (Rocky 9 client VM, FreeIPA in Docker): arch-autoinstall.sh: - Add openssh to pacstrap — was missing, breaking headless/automated installs - Enable sshd at boot and set PermitRootLogin yes for post-install access ansipa-install-packages.sh: - Fix broken uninstall logic: three always-true conditions caused every package removal to be silently skipped; replace with a state-file-based approach that tracks packages installed by ansipa and removes only those when the IPA group disappears ansipa-enforce-policies.sh: - Add usr_admin policy: creates /etc/sudoers.d/ansipa-usr-admin granting full sudo to the FreeIPA usr_admin user group on every enrolled host; reverted when the IPA group is deleted - Add usr_prt_<printer> policy: auto-adds CUPS printers for FreeIPA user group members at login; printer URI stored in IPA group description; per-user ACL; reverted when group is deleted or membership ends deploy-ansipa-policies.yml: - Document usr_admin and usr_prt_* policies in header comment - Install cups package on clients (required for printer policy) FreeIPA container (image/): - Add docker-env.service: extracts IPA_*/SMB_*/LUKS_*/KEYCLOAK_* env vars from /proc/1/environ into /etc/container.env on container start; services read from there rather than relying on PassEnvironment (which is lost on container restart) - Add run.sh: wrapper that starts FreeIPA with --cgroupns host, required on cgroup v2 hosts (WSL2, recent Linux) because Docker Compose schema does not expose cgroupns_mode; also wires LUKS_KEY_UPLOAD_PASSWORD which the SMB service needs - Dockerfile: copy + enable docker-env.service; add glibc-langpack-en for locale - ansipa-smb.service: use EnvironmentFile=/etc/container.env (via docker-env.service) instead of PassEnvironment; add docker-env.service dependency - ipa-first-boot.service: add docker-env.service dependency + EnvironmentFile - ipa-first-boot.sh: add --skip-mem-check (container has limited cgroup memory visibility); remove --no-reverse from non-DNS path (was invalid without DNS) - docker-compose.yml: add prominent cgroupns note explaining when to use run.sh Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DHops6PU4c2Mv5UyhUj8Msfeat/astal-menu
parent
ae0ad3c4d7
commit
65e859b8f9
|
|
@ -461,7 +461,7 @@ echo "Detected GPU: ${GPU_INFO:-none}"
|
|||
# GPU_PKGS is intentionally unquoted so it expands to multiple words (or nothing).
|
||||
# shellcheck disable=SC2086
|
||||
pacstrap -K /mnt base base-devel "$KERNEL" linux-firmware vim bash zsh git less btop fastfetch \
|
||||
networkmanager grub cryptsetup libfido2 pam-u2f efibootmgr sudo btrfs-progs lvm2 jq $GPU_PKGS
|
||||
networkmanager openssh grub cryptsetup libfido2 pam-u2f efibootmgr sudo btrfs-progs lvm2 jq $GPU_PKGS
|
||||
|
||||
############################################
|
||||
# FSTAB
|
||||
|
|
@ -511,9 +511,15 @@ ln -sf /usr/share/zoneinfo/Europe/Vienna /etc/localtime
|
|||
hwclock --systohc
|
||||
echo "$HOSTNAME" > /etc/hostname
|
||||
|
||||
# NetworkManager
|
||||
# NetworkManager + SSH
|
||||
# Enable at boot so the system has networking on first login without manual setup.
|
||||
systemctl enable NetworkManager
|
||||
# Enable SSH server so the machine is reachable headlessly after install.
|
||||
systemctl enable sshd
|
||||
# Allow root login via password for initial automated post-install steps;
|
||||
# FreeIPA enrollment and ansipa deploy lock this down afterwards.
|
||||
sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config
|
||||
echo "root:${USERPASS}" | chpasswd
|
||||
|
||||
# Populate /etc/skel before user creation so useradd -m copies everything
|
||||
echo "Cloning dotfiles into /etc/skel..."
|
||||
|
|
|
|||
|
|
@ -23,12 +23,25 @@
|
|||
# the drop-in on the next run.
|
||||
#
|
||||
# User-group naming conventions (per-user policies — follow the user across devices):
|
||||
# usr_admin Members get full sudo on every enrolled host via a sudoers
|
||||
# drop-in (/etc/sudoers.d/ansipa-usr-admin). Applied while the
|
||||
# FreeIPA user group exists; drop-in is removed when the group is
|
||||
# deleted. Uses SSSD so group membership resolves without local
|
||||
# accounts.
|
||||
# usr_block-binary-<name> Prevent members of this FreeIPA user group from running <name>
|
||||
# on any enrolled host. Use __ in place of . to support Flatpak
|
||||
# app IDs (e.g. usr_block-binary-org__gimp__Gimp blocks the
|
||||
# Flatpak org.gimp.Gimp). Enforced via a PATH-priority wrapper
|
||||
# that checks group membership at runtime via SSSD/id(1).
|
||||
# Removing the user group from FreeIPA reverts the wrapper.
|
||||
# usr_prt_<printer> Auto-add <printer> to CUPS on any enrolled host where a member
|
||||
# is logged in. The printer URI is stored in the IPA group
|
||||
# description (e.g. ipps://host/printers/name). Access is
|
||||
# per-user only (allow:<username>); each new member who logs in
|
||||
# gets added to the allow list. When the IPA group is deleted
|
||||
# or all members leave, the printer is removed from CUPS on that
|
||||
# host. A profile.d snippet + NOPASSWD sudo rule trigger the
|
||||
# enforcer at login for immediate setup.
|
||||
# usr_scan-notify Members receive scan alert notifications on every enrolled device
|
||||
# they log into. The fetch-alerts timer is installed fleet-wide
|
||||
# when the group exists; the notification daemon starts on login
|
||||
|
|
@ -72,6 +85,9 @@ WANT_TIMESHIFT_BACKUP=false
|
|||
WANT_SECURITY_SCAN=false
|
||||
WANT_SCAN_NOTIFY=false
|
||||
WANT_NO_LOCAL_USERS=false
|
||||
WANT_USR_ADMIN=false
|
||||
ACTIVE_PRT_GROUPS=()
|
||||
ACTIVE_PRT_PRINTERS=()
|
||||
|
||||
if [[ -n "$RAW_GROUPS" ]]; then
|
||||
while IFS=',' read -ra GRP_ARRAY; do
|
||||
|
|
@ -107,6 +123,26 @@ while IFS= read -r _grp; do
|
|||
done <<< "$_BLOCK_LIST"
|
||||
unset _BLOCK_LIST _grp _raw
|
||||
|
||||
# ── Fetch user-group-based admin policy from FreeIPA ─────────────────────────
|
||||
# usr_admin is a FreeIPA *user* group. When it exists, a sudoers drop-in grants
|
||||
# full sudo to the group on every enrolled host (resolved via SSSD/NSS).
|
||||
if ipa group-show usr_admin >/dev/null 2>&1; then
|
||||
WANT_USR_ADMIN=true
|
||||
fi
|
||||
|
||||
# ── Fetch user-group-based printer policies from FreeIPA ─────────────────────
|
||||
# usr_prt_<printer> groups grant CUPS access to <printer> for members.
|
||||
_PRT_LIST=$(ipa group-find --pkey-only 2>/dev/null \
|
||||
| awk '/Group name:/ {print $NF}' \
|
||||
| grep "^usr_prt_" | sort -u || true)
|
||||
|
||||
while IFS= read -r _pgrp; do
|
||||
[[ -z "$_pgrp" ]] && continue
|
||||
ACTIVE_PRT_GROUPS+=("$_pgrp")
|
||||
ACTIVE_PRT_PRINTERS+=("${_pgrp#usr_prt_}")
|
||||
done <<< "$_PRT_LIST"
|
||||
unset _PRT_LIST _pgrp
|
||||
|
||||
# ── Fetch user-group-based scan-notify policy from FreeIPA ────────────────────
|
||||
# usr_scan-notify is a FreeIPA *user* group — membership follows the user to
|
||||
# every enrolled host. Install the fetch-alerts timer on any device where the
|
||||
|
|
@ -119,7 +155,9 @@ log "Device policies — daemon-enable: ${ACTIVE_DAEMON_ENABLE[*]:-none}" \
|
|||
"| daemon-disable: ${ACTIVE_DAEMON_DISABLE[*]:-none}" \
|
||||
"| timeshift-backup: $WANT_TIMESHIFT_BACKUP | security-scan: $WANT_SECURITY_SCAN" \
|
||||
"| no-local-users: $WANT_NO_LOCAL_USERS | local-sudo: ${ACTIVE_LOCAL_SUDO_USERS[*]:-none}"
|
||||
log "User policies — block-binary: ${ACTIVE_BLOCK_BINARIES[*]:-none}" \
|
||||
log "User policies — admin: $WANT_USR_ADMIN" \
|
||||
"| block-binary: ${ACTIVE_BLOCK_BINARIES[*]:-none}" \
|
||||
"| printers: ${ACTIVE_PRT_PRINTERS[*]:-none}" \
|
||||
"| scan-notify: $WANT_SCAN_NOTIFY"
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
|
@ -543,4 +581,194 @@ else
|
|||
fi
|
||||
fi
|
||||
|
||||
# ── usr_admin: global sudo for FreeIPA admin group ───────────────────────────
|
||||
# When the FreeIPA user group usr_admin exists, install a sudoers drop-in that
|
||||
# grants full sudo to that group (resolved via SSSD/NSS) on this host.
|
||||
# The drop-in is removed when the group is deleted from FreeIPA.
|
||||
|
||||
USR_ADMIN_DROPIN="/etc/sudoers.d/ansipa-usr-admin"
|
||||
USR_ADMIN_STATE="$STATE_DIR/usr-admin"
|
||||
|
||||
if [[ "$WANT_USR_ADMIN" == true ]]; then
|
||||
if [[ ! -f "$USR_ADMIN_DROPIN" ]]; then
|
||||
log "Installing usr_admin sudoers drop-in"
|
||||
printf '%%usr_admin ALL=(ALL:ALL) ALL\n' > "$USR_ADMIN_DROPIN"
|
||||
chmod 440 "$USR_ADMIN_DROPIN"
|
||||
fi
|
||||
touch "$USR_ADMIN_STATE"
|
||||
else
|
||||
if [[ -f "$USR_ADMIN_STATE" ]] || [[ -f "$USR_ADMIN_DROPIN" ]]; then
|
||||
rm -f "$USR_ADMIN_DROPIN" "$USR_ADMIN_STATE"
|
||||
log "Removed usr_admin sudoers drop-in (IPA group no longer exists)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── usr_prt_<printer>: per-user printer auto-add via FreeIPA groups ───────────
|
||||
# For each usr_prt_<printer> group in FreeIPA:
|
||||
# - The group description must contain the printer URI
|
||||
# (e.g. "ipps://printserver.corp.example.com/printers/hp-office")
|
||||
# - When a logged-in user is a member of the group, the printer is added to
|
||||
# CUPS on this host and restricted to that user (allow:<user>).
|
||||
# - Multiple users in the group can each gain access on the same host.
|
||||
# - When a user leaves the group their allow entry is revoked; when no users
|
||||
# remain the printer is removed from CUPS on this host entirely.
|
||||
# The profile.d snippet /etc/profile.d/ansipa-printers.sh triggers this enforcer
|
||||
# at login so printers appear immediately rather than waiting for the 30-min tick.
|
||||
#
|
||||
# To register a printer in FreeIPA:
|
||||
# ipa group-mod usr_prt_hp-office \
|
||||
# --desc='ipps://printserver.corp.example.com/printers/hp-office'
|
||||
|
||||
PRT_STATE_DIR="$STATE_DIR/printer-users"
|
||||
PRT_DEFS_DIR="$STATE_DIR/printer-defs"
|
||||
PRT_PROFILED="/etc/profile.d/ansipa-printers.sh"
|
||||
PRT_SUDO_DROPIN="/etc/sudoers.d/ansipa-printers-trigger"
|
||||
mkdir -p "$PRT_STATE_DIR" "$PRT_DEFS_DIR"
|
||||
|
||||
# Read/write per-printer authorized-user state files (<printer>.users)
|
||||
_prt_read_users() {
|
||||
local sf="$PRT_STATE_DIR/${1}.users"
|
||||
[[ -f "$sf" ]] && grep -v '^$' "$sf" | sort -u || true
|
||||
}
|
||||
_prt_write_users() {
|
||||
local printer="$1"; shift
|
||||
if [[ $# -gt 0 ]]; then
|
||||
printf '%s\n' "$@" | sort -u > "$PRT_STATE_DIR/${printer}.users"
|
||||
else
|
||||
> "$PRT_STATE_DIR/${printer}.users"
|
||||
fi
|
||||
}
|
||||
# Apply the full allow list from state (or remove printer if list is empty)
|
||||
_prt_apply_acl() {
|
||||
local printer="$1"
|
||||
local -a users
|
||||
mapfile -t users < <(_prt_read_users "$printer")
|
||||
if [[ ${#users[@]} -gt 0 ]]; then
|
||||
local allow_list
|
||||
allow_list=$(IFS=','; echo "${users[*]}")
|
||||
lpadmin -p "$printer" -u "allow:${allow_list}" 2>/dev/null \
|
||||
|| warn "lpadmin ACL update failed for printer $printer"
|
||||
else
|
||||
lpadmin -x "$printer" 2>/dev/null \
|
||||
&& log "Removed printer $printer (no authorized users remaining)" \
|
||||
|| warn "lpadmin -x failed for printer $printer"
|
||||
rm -f "$PRT_STATE_DIR/${printer}.users"
|
||||
fi
|
||||
}
|
||||
_prt_is_active() {
|
||||
local needle="$1"
|
||||
for _ap in "${ACTIVE_PRT_PRINTERS[@]+"${ACTIVE_PRT_PRINTERS[@]}"}"; do
|
||||
[[ "$_ap" == "$needle" ]] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
if command -v lpadmin &>/dev/null; then
|
||||
# ── Process each active usr_prt_* group ──────────────────────────────────
|
||||
for _i in "${!ACTIVE_PRT_PRINTERS[@]}"; do
|
||||
_PRT="${ACTIVE_PRT_PRINTERS[$_i]}"
|
||||
_GRP="${ACTIVE_PRT_GROUPS[$_i]}"
|
||||
|
||||
# Fetch URI from IPA group description (must start with a scheme://)
|
||||
_URI=$(ipa group-show "$_GRP" --all 2>/dev/null \
|
||||
| awk -F': ' '/Description:/{print $2; exit}' \
|
||||
| grep -E '^[a-z][a-z+.-]+://' || true)
|
||||
|
||||
if [[ -z "$_URI" ]]; then
|
||||
warn "Group $_GRP has no printer URI in its description — set it with:"
|
||||
warn " ipa group-mod $_GRP --desc='ipps://printserver/printers/$_PRT'"
|
||||
continue
|
||||
fi
|
||||
echo "$_URI" > "$PRT_DEFS_DIR/${_GRP}.uri"
|
||||
|
||||
mapfile -t _CUR_USERS < <(_prt_read_users "$_PRT")
|
||||
_UPDATED=false
|
||||
|
||||
# Check logged-in users for new members to grant access to
|
||||
while IFS= read -r _LUSER; do
|
||||
[[ -z "$_LUSER" ]] && continue
|
||||
id -Gn "$_LUSER" 2>/dev/null | tr ' ' '\n' | grep -qxF "$_GRP" || continue
|
||||
printf '%s\n' "${_CUR_USERS[@]+"${_CUR_USERS[@]}"}" | grep -qxF "$_LUSER" && continue
|
||||
|
||||
# Add the printer to CUPS if not present yet
|
||||
if ! lpstat -p "$_PRT" &>/dev/null; then
|
||||
log "Adding printer $_PRT (URI: $_URI)"
|
||||
lpadmin -p "$_PRT" -v "$_URI" -m everywhere -E 2>/dev/null \
|
||||
|| lpadmin -p "$_PRT" -v "$_URI" -E 2>/dev/null \
|
||||
|| { warn "lpadmin failed to add printer $_PRT"; continue; }
|
||||
fi
|
||||
|
||||
log "Granting printer $_PRT access to $_LUSER"
|
||||
_CUR_USERS+=("$_LUSER")
|
||||
_UPDATED=true
|
||||
done < <(who -u 2>/dev/null | awk '{print $1}' | sort -u)
|
||||
|
||||
# Revoke users who left the group
|
||||
_VALID_USERS=()
|
||||
for _U in "${_CUR_USERS[@]+"${_CUR_USERS[@]}"}"; do
|
||||
if id -Gn "$_U" 2>/dev/null | tr ' ' '\n' | grep -qxF "$_GRP"; then
|
||||
_VALID_USERS+=("$_U")
|
||||
else
|
||||
log "Revoking printer $_PRT access for $_U (no longer in $_GRP)"
|
||||
_UPDATED=true
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$_UPDATED" == true ]]; then
|
||||
_prt_write_users "$_PRT" "${_VALID_USERS[@]+"${_VALID_USERS[@]}"}"
|
||||
_prt_apply_acl "$_PRT"
|
||||
fi
|
||||
done
|
||||
unset _i _PRT _GRP _URI _LUSER _U _CUR_USERS _VALID_USERS _UPDATED _ap
|
||||
|
||||
# ── Remove printers whose IPA group was deleted ───────────────────────────
|
||||
shopt -s nullglob
|
||||
for _sf in "$PRT_STATE_DIR"/*.users; do
|
||||
[[ -f "$_sf" ]] || continue
|
||||
_PRT=$(basename "$_sf" .users)
|
||||
_prt_is_active "$_PRT" && continue
|
||||
log "Removing printer $_PRT (IPA group usr_prt_$_PRT deleted)"
|
||||
lpadmin -x "$_PRT" 2>/dev/null || true
|
||||
rm -f "$_sf"
|
||||
done
|
||||
shopt -u nullglob
|
||||
unset _sf _PRT
|
||||
|
||||
# ── profile.d login trigger ───────────────────────────────────────────────
|
||||
# Installs a profile.d snippet + NOPASSWD sudo rule so that when a user
|
||||
# logs in, the enforcer runs immediately and adds their printers without
|
||||
# waiting for the next 30-minute timer tick.
|
||||
if [[ ${#ACTIVE_PRT_PRINTERS[@]} -gt 0 ]]; then
|
||||
if [[ ! -f "$PRT_SUDO_DROPIN" ]]; then
|
||||
log "Installing ansipa-printers sudo trigger rule"
|
||||
printf 'ALL ALL=(root) NOPASSWD: /usr/bin/systemctl start ansipa-enforce-policies.service\n' \
|
||||
> "$PRT_SUDO_DROPIN"
|
||||
chmod 440 "$PRT_SUDO_DROPIN"
|
||||
fi
|
||||
if [[ ! -f "$PRT_PROFILED" ]]; then
|
||||
log "Installing /etc/profile.d/ansipa-printers.sh"
|
||||
cat > "$PRT_PROFILED" <<'PROFILED'
|
||||
# ansipa-printers: trigger policy enforcer at login to auto-add printers.
|
||||
# Managed by ansipa-enforce-policies — do not edit manually.
|
||||
sudo -n /usr/bin/systemctl start ansipa-enforce-policies.service 2>/dev/null &
|
||||
disown 2>/dev/null || true
|
||||
PROFILED
|
||||
chmod 644 "$PRT_PROFILED"
|
||||
fi
|
||||
else
|
||||
if [[ -f "$PRT_SUDO_DROPIN" ]]; then
|
||||
rm -f "$PRT_SUDO_DROPIN"
|
||||
log "Removed ansipa-printers sudo trigger (no active printer groups)"
|
||||
fi
|
||||
if [[ -f "$PRT_PROFILED" ]]; then
|
||||
rm -f "$PRT_PROFILED"
|
||||
log "Removed /etc/profile.d/ansipa-printers.sh (no active printer groups)"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
[[ ${#ACTIVE_PRT_PRINTERS[@]} -gt 0 ]] && \
|
||||
warn "lpadmin not found — install cups to enforce printer policies"
|
||||
fi
|
||||
unset _ap
|
||||
|
||||
log "Policy enforcement complete."
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
set -e
|
||||
|
||||
PREFIX="dev_pkg_"
|
||||
STATE_FILE="/var/lib/ansipa-packages/installed.list"
|
||||
mkdir -p "$(dirname "$STATE_FILE")"
|
||||
|
||||
# Detect distro
|
||||
if [ -f /etc/os-release ]; then
|
||||
|
|
@ -62,7 +64,7 @@ remove_pkg() {
|
|||
# -----------------------------
|
||||
# Get FreeIPA install groups
|
||||
# -----------------------------
|
||||
IPA_GROUPS=$(ipa group-find --pkey-only | awk '{print $1}' | grep "^$PREFIX" || true)
|
||||
IPA_GROUPS=$(ipa group-find --pkey-only | awk '/Group name:/{print $NF}' | grep "^$PREFIX" || true)
|
||||
|
||||
# Extract package names from groups
|
||||
DESIRED_PKGS=()
|
||||
|
|
@ -93,36 +95,40 @@ case "$DISTRO" in
|
|||
esac
|
||||
|
||||
# -----------------------------
|
||||
# Install missing packages
|
||||
# Install missing packages and record them
|
||||
# -----------------------------
|
||||
[[ -f "$STATE_FILE" ]] || touch "$STATE_FILE"
|
||||
|
||||
for PKG in "${DESIRED_PKGS[@]}"; do
|
||||
if ! echo "$INSTALLED_PKGS" | grep -qx "$PKG"; then
|
||||
echo "[INFO] Installing package: $PKG"
|
||||
install_pkg "$PKG"
|
||||
fi
|
||||
# Record that ansipa manages this package
|
||||
grep -qxF "$PKG" "$STATE_FILE" || echo "$PKG" >> "$STATE_FILE"
|
||||
done
|
||||
|
||||
# -----------------------------
|
||||
# Remove packages no longer desired
|
||||
# Only remove packages previously installed by ansipa (tracked in state file).
|
||||
# -----------------------------
|
||||
for PKG in $INSTALLED_PKGS; do
|
||||
if [[ "$PKG" =~ ^.*$ ]]; then
|
||||
if [[ "$PKG" == "$PREFIX"* ]]; then
|
||||
while IFS= read -r PKG; do
|
||||
[[ -z "$PKG" ]] && continue
|
||||
# Still desired? Leave it alone.
|
||||
if printf '%s\n' "${DESIRED_PKGS[@]+"${DESIRED_PKGS[@]}"}" | grep -qxF "$PKG"; then
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
if echo "$PKG" | grep -q "^"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ " ${DESIRED_PKGS[*]} " != *" $PKG "* ]]; then
|
||||
# Only remove packages that were installed via ansipa-install
|
||||
if ipa group-find --pkey-only | grep -q "^$PREFIX$PKG$"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "[INFO] Removing package no longer required: $PKG"
|
||||
echo "[INFO] Removing package no longer required by any IPA group: $PKG"
|
||||
remove_pkg "$PKG"
|
||||
fi
|
||||
done
|
||||
# Remove from state file
|
||||
grep -vxF "$PKG" "$STATE_FILE" > "${STATE_FILE}.tmp" && mv "${STATE_FILE}.tmp" "$STATE_FILE"
|
||||
done < "$STATE_FILE"
|
||||
|
||||
# Persist current desired list (union of what's already tracked + newly added)
|
||||
if [[ ${#DESIRED_PKGS[@]} -gt 0 ]]; then
|
||||
printf '%s\n' "${DESIRED_PKGS[@]}" | sort -u > "${STATE_FILE}.tmp"
|
||||
# Merge: keep previously-managed packages that are still desired
|
||||
sort -u "$STATE_FILE" "${STATE_FILE}.tmp" > "${STATE_FILE}.merged"
|
||||
comm -12 "${STATE_FILE}.merged" "${STATE_FILE}.tmp" > "$STATE_FILE"
|
||||
rm -f "${STATE_FILE}.tmp" "${STATE_FILE}.merged"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@
|
|||
# dev_local-sudo-<username> Grant <username> local sudo on this device; reverted when host leaves
|
||||
#
|
||||
# User policies (FreeIPA user groups — follow the user across all enrolled devices):
|
||||
# usr_admin Grant full sudo on every enrolled host (sudoers drop-in, SSSD-resolved)
|
||||
# usr_block-binary-<name> Block execution of <name> via a PATH-priority wrapper
|
||||
# usr_prt_<printer> Auto-add printer at login (per-user); URI in IPA group description
|
||||
# usr_scan-notify Fetch alerts from server, notify user every 10 min until acknowledged
|
||||
#
|
||||
# Prerequisites:
|
||||
|
|
@ -34,12 +36,13 @@
|
|||
|
||||
tasks:
|
||||
|
||||
- name: Install samba-client (required for scan upload and alert fetch)
|
||||
- name: Install required packages
|
||||
package:
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
loop:
|
||||
- samba-client
|
||||
- cups
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Deploy SMB credentials file
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ ENV container=docker \
|
|||
LANG=en_US.UTF-8 \
|
||||
LC_ALL=en_US.UTF-8
|
||||
|
||||
RUN dnf install -y glibc-langpack-en
|
||||
|
||||
RUN dnf install -y --setopt=install_weak_deps=False \
|
||||
freeipa-server \
|
||||
freeipa-server-dns \
|
||||
|
|
@ -57,6 +59,7 @@ RUN systemctl mask \
|
|||
network.service \
|
||||
NetworkManager.service
|
||||
|
||||
COPY docker-env.service /etc/systemd/system/docker-env.service
|
||||
COPY ipa-first-boot.sh /usr/local/sbin/ipa-first-boot.sh
|
||||
COPY ipa-first-boot.service /etc/systemd/system/ipa-first-boot.service
|
||||
COPY ansipa-smb-setup.sh /usr/local/sbin/ansipa-smb-setup.sh
|
||||
|
|
@ -65,6 +68,7 @@ COPY ansipa-check-scans.sh /usr/local/sbin/ansipa-check-scans.sh
|
|||
RUN chmod +x /usr/local/sbin/ipa-first-boot.sh \
|
||||
&& chmod +x /usr/local/sbin/ansipa-smb-setup.sh \
|
||||
&& chmod +x /usr/local/sbin/ansipa-check-scans.sh \
|
||||
&& systemctl enable docker-env.service \
|
||||
&& systemctl enable ipa-first-boot.service \
|
||||
&& systemctl enable ansipa-smb.service \
|
||||
&& systemctl enable smb.service nmb.service crond.service
|
||||
|
|
|
|||
|
|
@ -2,14 +2,15 @@
|
|||
Description=Ansipa Scan Results SMB Share Setup
|
||||
# Run before smb so smb.conf and the Samba user exist when smbd starts.
|
||||
Before=smb.service
|
||||
After=network.target
|
||||
After=network.target docker-env.service
|
||||
Wants=docker-env.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
# SMB_SCAN_PASSWORD comes from the container environment on first boot.
|
||||
# On subsequent restarts it is read from /data/samba/ansipa-smb.env by the script.
|
||||
PassEnvironment=SMB_SCAN_PASSWORD
|
||||
# Passwords come from /etc/container.env (written by docker-env.service from Docker -e flags).
|
||||
# On subsequent restarts they are read from /data/samba/ansipa-smb.env by the script.
|
||||
EnvironmentFile=/etc/container.env
|
||||
ExecStart=/usr/local/sbin/ansipa-smb-setup.sh
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
|
|
|||
|
|
@ -12,6 +12,18 @@
|
|||
#
|
||||
# To scale out (clients enroll against the same IPA server):
|
||||
# docker compose --profile clients up
|
||||
#
|
||||
# ── cgroupns NOTE ────────────────────────────────────────────────────────────
|
||||
# On hosts using cgroup v2 (WSL2, recent Linux with unified hierarchy) the
|
||||
# FreeIPA container runs /sbin/init (systemd) and exits immediately with code
|
||||
# 255 unless the container shares the host cgroup namespace. Docker Compose
|
||||
# does not yet expose a cgroupns_mode property in its schema, so use the
|
||||
# provided run.sh wrapper instead of plain `docker compose up` on those hosts:
|
||||
#
|
||||
# ./run.sh — starts freeipa only (cgroupns host applied automatically)
|
||||
# ./run.sh all — starts freeipa + postgres + keycloak
|
||||
#
|
||||
# On plain cgroup v1 hosts `docker compose up -d` works without run.sh.
|
||||
|
||||
volumes:
|
||||
freeipa-data:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
[Unit]
|
||||
Description=Export Docker container env vars for systemd services
|
||||
DefaultDependencies=no
|
||||
Before=basic.target ipa-first-boot.service ansipa-smb.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart=/bin/bash -c "tr '\\0' '\\n' < /proc/1/environ | grep -E '^(IPA|SMB|LUKS|KEYCLOAK)_' > /etc/container.env; chmod 600 /etc/container.env"
|
||||
|
||||
[Install]
|
||||
WantedBy=sysinit.target
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
[Unit]
|
||||
Description=FreeIPA Server First-Boot Configuration
|
||||
After=network-online.target dirsrv.target
|
||||
Wants=network-online.target
|
||||
After=network-online.target dirsrv.target docker-env.service
|
||||
Wants=network-online.target docker-env.service
|
||||
ConditionPathExists=!/etc/ipa/default.conf
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
EnvironmentFile=/etc/container.env
|
||||
ExecStart=/usr/local/sbin/ipa-first-boot.sh
|
||||
RemainAfterExit=yes
|
||||
StandardOutput=journal+console
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ ARGS=(
|
|||
--ip-address="$(hostname -I | awk '{print $1}')"
|
||||
--mkhomedir
|
||||
--unattended
|
||||
--skip-mem-check
|
||||
)
|
||||
|
||||
if [[ "$IPA_SETUP_DNS" == "true" ]]; then
|
||||
|
|
@ -55,8 +56,6 @@ if [[ "$IPA_SETUP_DNS" == "true" ]]; then
|
|||
&& ARGS+=(--forwarder="$IPA_DNS_FORWARDER") \
|
||||
|| ARGS+=(--no-forwarders)
|
||||
[[ "$IPA_AUTO_REVERSE" == "true" ]] && ARGS+=(--auto-reverse) || ARGS+=(--no-reverse)
|
||||
else
|
||||
ARGS+=(--no-reverse)
|
||||
fi
|
||||
|
||||
[[ "$IPA_NO_NTP" == "true" ]] && ARGS+=(--no-ntp)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,101 @@
|
|||
#!/usr/bin/env bash
|
||||
# run.sh — Start the FreeIPA stack with correct cgroup namespace on any host.
|
||||
#
|
||||
# Usage:
|
||||
# ./run.sh # freeipa only
|
||||
# ./run.sh all # freeipa + postgres + keycloak
|
||||
# ./run.sh down # stop and remove containers
|
||||
#
|
||||
# Why this exists:
|
||||
# The FreeIPA container runs systemd (/sbin/init) and requires the host cgroup
|
||||
# namespace on systems that use cgroup v2 (WSL2, recent Fedora/Ubuntu, etc.).
|
||||
# Docker Compose's schema does not expose a cgroupns_mode field, so this script
|
||||
# starts the freeipa container via `docker run --cgroupns host` and defers the
|
||||
# rest of the stack to `docker compose up`.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
[[ -f .env ]] || { echo "ERROR: .env not found. Copy .env.example and fill in values."; exit 1; }
|
||||
# shellcheck disable=SC1091
|
||||
source .env
|
||||
|
||||
MODE="${1:-}"
|
||||
|
||||
stop_freeipa() {
|
||||
docker rm -f freeipa 2>/dev/null || true
|
||||
}
|
||||
|
||||
start_freeipa() {
|
||||
stop_freeipa
|
||||
|
||||
# Ensure the compose-managed network exists before docker run uses it
|
||||
docker compose up --no-start freeipa 2>/dev/null || true
|
||||
NETWORK=$(docker network ls --filter name=ipa-net --format '{{.Name}}' | grep ipa-net | head -1)
|
||||
if [[ -z "$NETWORK" ]]; then
|
||||
docker network create --subnet=172.30.0.0/24 ipa-net
|
||||
NETWORK=ipa-net
|
||||
fi
|
||||
|
||||
# Ensure the data volume exists
|
||||
docker volume create freeipa-data 2>/dev/null || true
|
||||
|
||||
docker run -d \
|
||||
--name freeipa \
|
||||
--hostname "${IPA_HOSTNAME:-ipa.example.com}" \
|
||||
--privileged \
|
||||
--cgroupns host \
|
||||
--tmpfs /run \
|
||||
--tmpfs /tmp \
|
||||
-v /sys/fs/cgroup:/sys/fs/cgroup:rw \
|
||||
-v freeipa-data:/data \
|
||||
--network "$NETWORK" \
|
||||
--ip 172.30.0.10 \
|
||||
-e IPA_DOMAIN="${IPA_DOMAIN:?}" \
|
||||
-e IPA_REALM="${IPA_REALM:-}" \
|
||||
-e IPA_ADMIN_PASSWORD="${IPA_ADMIN_PASSWORD:?}" \
|
||||
-e IPA_DM_PASSWORD="${IPA_DM_PASSWORD:?}" \
|
||||
-e IPA_SETUP_DNS="${IPA_SETUP_DNS:-false}" \
|
||||
-e IPA_DNS_FORWARDER="${IPA_DNS_FORWARDER:-}" \
|
||||
-e IPA_SETUP_KRA="${IPA_SETUP_KRA:-false}" \
|
||||
-e SMB_SCAN_PASSWORD="${SMB_SCAN_PASSWORD:?}" \
|
||||
-e LUKS_KEY_UPLOAD_PASSWORD="${LUKS_KEY_UPLOAD_PASSWORD:?}" \
|
||||
-p 389:389 \
|
||||
-p 636:636 \
|
||||
-p 88:88 \
|
||||
-p 88:88/udp \
|
||||
-p 464:464 \
|
||||
-p 464:464/udp \
|
||||
-p 443:443 \
|
||||
-p 445:445 \
|
||||
-p 139:139 \
|
||||
-p 137:137/udp \
|
||||
-p 138:138/udp \
|
||||
freeipa-server:local
|
||||
|
||||
echo "FreeIPA container started. Watch first-boot progress:"
|
||||
echo " docker exec freeipa journalctl -f -u ipa-first-boot.service"
|
||||
echo " (first-boot takes ~10 min)"
|
||||
}
|
||||
|
||||
case "$MODE" in
|
||||
down)
|
||||
stop_freeipa
|
||||
docker compose down
|
||||
echo "Stack stopped."
|
||||
;;
|
||||
all)
|
||||
start_freeipa
|
||||
docker compose up -d postgres keycloak
|
||||
echo "Full stack started (freeipa + postgres + keycloak)."
|
||||
;;
|
||||
"")
|
||||
start_freeipa
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 [all|down]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Loading…
Reference in New Issue