fix(ansipa): hostname-binary-free FQDN + push-mode orchestration (NAT path)

Found while running the policy matrix and NAT/WAN scenario in VMs:

- ansipa-enforce-policies.sh and the dev_security-scan generated script called
  `hostname -f`, but the hostname binary (inetutils) is not installed on a
  minimal Arch system, so the enforcer aborted with "hostname: command not
  found" on every run. Add an ansipa_fqdn() helper (hostnamectl / getent /
  /etc/hostname / kernel-hostname fallback chain) and use it; inline the same
  fallback in the standalone scan script.

- Fix two stacked bugs that meant CheckMK push mode — the only monitoring
  transport that works when a client is behind NAT — never functioned:
  * ansipa-checkmk-setup.sh referenced IPA_ADMIN_PASS in the DONE_FLAG
    early-exit path, but that variable was assigned only afterwards, so every
    rerun kinit'd with an empty password, failed silently, and skipped the
    push-mode request writer. Resolve config before the early exit.
  * _write_push_requests parsed `ipa hostgroup-show dev_mon_base` members with
    awk that did `next` on the "Member hosts:" line — but ipa prints members on
    that same line, so the only line with hostnames was discarded and no push
    request was ever written. Rewrite the awk to capture the label line's value
    plus wrapped continuation lines. Verified end-to-end: requests are written
    and CheckMK flips the host to cmk_agent_connection=push-agent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUhrcFU8J1Hnf7vNqNxZNi
feat/astal-menu
Amir Alexander Abdelbaki 2026-07-02 12:25:03 +02:00
parent 0cd352da00
commit b21f415cde
4 changed files with 56 additions and 13 deletions

View File

@ -103,7 +103,7 @@ set -euo pipefail
# shellcheck source=lib/ansipa-policy.sh
source /usr/local/lib/ansipa/policy.sh
HOST_FQDN=$(hostname -f 2>/dev/null || hostname)
HOST_FQDN=$(ansipa_fqdn)
command -v ipa &>/dev/null || { warn "ipa not found — host not enrolled in FreeIPA. Exiting."; exit 0; }

View File

@ -27,6 +27,28 @@ POLICY_DIR="/usr/local/lib/ansipa/policies.d"
log() { echo "[$LOG_TAG] $*"; logger -t "$LOG_TAG" "$*" 2>/dev/null || true; }
warn() { echo "[$LOG_TAG][WARN] $*" >&2; logger -t "$LOG_TAG" "WARN: $*" 2>/dev/null || true; }
# ── Hostname resolution ───────────────────────────────────────────────────────
# The `hostname` binary (from inetutils) is NOT installed on a minimal Arch
# system, so `hostname -f` aborts the enforcer with "command not found". Resolve
# the FQDN without depending on it: prefer hostnamectl, then the FQDN via getent
# on the static hostname, then /etc/hostname, then the kernel hostname. Every
# enrolled host has an FQDN (ipa-client-install sets it), so this resolves.
ansipa_fqdn() {
local h=""
if command -v hostname &>/dev/null; then
h=$(hostname -f 2>/dev/null) || h=""
fi
[[ -z "$h" ]] && h=$(hostnamectl --static 2>/dev/null || true)
[[ -z "$h" ]] && h=$(cat /etc/hostname 2>/dev/null || true)
[[ -z "$h" ]] && h=$(cat /proc/sys/kernel/hostname 2>/dev/null || true)
# Upgrade a short name to the FQDN via NSS if resolvable.
if [[ "$h" != *.* ]] && command -v getent &>/dev/null; then
local fqdn; fqdn=$(getent hosts "$h" 2>/dev/null | awk '{for(i=2;i<=NF;i++) if($i ~ /\./){print $i; exit}}')
[[ -n "$fqdn" ]] && h="$fqdn"
fi
printf '%s' "$h"
}
# ── Group discovery ───────────────────────────────────────────────────────────
# Called once by the orchestrator. Sets all policy-state variables needed by
# every policy file, using exactly two IPA queries (one host-show, one group-find).

View File

@ -12,7 +12,10 @@ if [[ "$WANT_SECURITY_SCAN" == true ]]; then
# ansipa-security-scan — daily ClamAV / rkhunter / chkrootkit run + SMB upload.
# Managed by ansipa-enforce-policies — do not edit manually.
LOG=/var/log/ansipa-security-scan.log
HOSTNAME=$(hostname -f 2>/dev/null || hostname)
# `hostname` (inetutils) may be absent on minimal Arch; resolve without it.
HOSTNAME=$( { command -v hostname >/dev/null 2>&1 && hostname -f 2>/dev/null; } \
|| hostnamectl --static 2>/dev/null || cat /etc/hostname 2>/dev/null \
|| cat /proc/sys/kernel/hostname 2>/dev/null )
DATE=$(date +%Y-%m-%d)
{
echo "=== ansipa-security-scan: $DATE $HOSTNAME ==="

View File

@ -27,8 +27,20 @@ warn() { echo "[$LOG_TAG][WARN] $*" >&2; }
_write_push_requests() {
[[ -d /cmk-creds ]] || return 0
local _hosts _h
# `ipa hostgroup-show` prints members on the SAME line as the label
# (" Member hosts: a.example, b.example") and wraps long lists onto
# indented continuation lines. Capture the label line's own value AND any
# continuation lines, stopping at the next " Key:" field. (The previous
# awk did `next` on the label line, discarding the very line that holds the
# hostnames, so no host was ever extracted and no push request written.)
_hosts=$(ipa hostgroup-show dev_mon_base --all 2>/dev/null \
| awk '/Member hosts:/{p=1; next} /^$/{p=0} p{print}' \
| awk '
/^ [A-Za-z][A-Za-z0-9 _-]*:/ {
if ($0 ~ /Member hosts:/) { sub(/.*Member hosts:[[:space:]]*/, ""); p=1; print; next }
else { p=0 }
}
p { print }
' \
| grep -oE '[a-zA-Z0-9._-]+\.[a-zA-Z]{2,}' | sort -u || true)
[[ -z "$_hosts" ]] && return 0
while IFS= read -r _h; do
@ -39,17 +51,12 @@ _write_push_requests() {
done <<< "$_hosts"
}
# Skip re-initialization but still run push-mode requests if already set up
if [[ -f "$DONE_FLAG" ]]; then
[[ -f /etc/ipa/default.conf ]] && \
echo "$IPA_ADMIN_PASS" | kinit "admin@${IPA_REALM}" &>/dev/null && \
_write_push_requests 2>/dev/null; \
kdestroy &>/dev/null || true
log "Already configured (delete $DONE_FLAG to force re-initialization)"
exit 0
fi
# ── Resolve configuration ──────────────────────────────────────────────────────
# Must run BEFORE the DONE_FLAG early-exit: that path calls _write_push_requests,
# which needs IPA_REALM + IPA_ADMIN_PASS to kinit. Previously these were resolved
# only after the early exit, so every rerun kinit'd with an empty password, failed
# silently, and never wrote push-mode requests — leaving NAT'd clients stuck in
# pull mode (unreachable) forever.
# docker-env.service writes container env to /etc/container.env; source it.
[[ -f /etc/container.env ]] && source /etc/container.env || true
@ -59,6 +66,17 @@ CMK_API="${CMK_URL}/${CMK_SITE}/check_mk/api/1.0"
IPA_REALM="${IPA_REALM:-}"
IPA_ADMIN_PASS="${IPA_ADMIN_PASSWORD:-}"
# Skip re-initialization but still run push-mode requests if already set up
if [[ -f "$DONE_FLAG" ]]; then
if [[ -f /etc/ipa/default.conf && -n "$IPA_ADMIN_PASS" && -n "$IPA_REALM" ]]; then
echo "$IPA_ADMIN_PASS" | kinit "admin@${IPA_REALM}" &>/dev/null \
&& _write_push_requests 2>/dev/null
kdestroy &>/dev/null || true
fi
log "Already configured (delete $DONE_FLAG to force re-initialization)"
exit 0
fi
if [[ -z "$IPA_REALM" || -z "$IPA_ADMIN_PASS" ]]; then
warn "IPA_REALM or IPA_ADMIN_PASSWORD not set — cannot configure IPA side"
exit 1