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
parent
0cd352da00
commit
b21f415cde
|
|
@ -103,7 +103,7 @@ set -euo pipefail
|
||||||
# shellcheck source=lib/ansipa-policy.sh
|
# shellcheck source=lib/ansipa-policy.sh
|
||||||
source /usr/local/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; }
|
command -v ipa &>/dev/null || { warn "ipa not found — host not enrolled in FreeIPA. Exiting."; exit 0; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,28 @@ POLICY_DIR="/usr/local/lib/ansipa/policies.d"
|
||||||
log() { echo "[$LOG_TAG] $*"; logger -t "$LOG_TAG" "$*" 2>/dev/null || true; }
|
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; }
|
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 ───────────────────────────────────────────────────────────
|
# ── Group discovery ───────────────────────────────────────────────────────────
|
||||||
# Called once by the orchestrator. Sets all policy-state variables needed by
|
# 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).
|
# every policy file, using exactly two IPA queries (one host-show, one group-find).
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,10 @@ if [[ "$WANT_SECURITY_SCAN" == true ]]; then
|
||||||
# ansipa-security-scan — daily ClamAV / rkhunter / chkrootkit run + SMB upload.
|
# ansipa-security-scan — daily ClamAV / rkhunter / chkrootkit run + SMB upload.
|
||||||
# Managed by ansipa-enforce-policies — do not edit manually.
|
# Managed by ansipa-enforce-policies — do not edit manually.
|
||||||
LOG=/var/log/ansipa-security-scan.log
|
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)
|
DATE=$(date +%Y-%m-%d)
|
||||||
{
|
{
|
||||||
echo "=== ansipa-security-scan: $DATE $HOSTNAME ==="
|
echo "=== ansipa-security-scan: $DATE $HOSTNAME ==="
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,20 @@ warn() { echo "[$LOG_TAG][WARN] $*" >&2; }
|
||||||
_write_push_requests() {
|
_write_push_requests() {
|
||||||
[[ -d /cmk-creds ]] || return 0
|
[[ -d /cmk-creds ]] || return 0
|
||||||
local _hosts _h
|
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 \
|
_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)
|
| grep -oE '[a-zA-Z0-9._-]+\.[a-zA-Z]{2,}' | sort -u || true)
|
||||||
[[ -z "$_hosts" ]] && return 0
|
[[ -z "$_hosts" ]] && return 0
|
||||||
while IFS= read -r _h; do
|
while IFS= read -r _h; do
|
||||||
|
|
@ -39,17 +51,12 @@ _write_push_requests() {
|
||||||
done <<< "$_hosts"
|
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 ──────────────────────────────────────────────────────
|
# ── 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.
|
# docker-env.service writes container env to /etc/container.env; source it.
|
||||||
[[ -f /etc/container.env ]] && source /etc/container.env || true
|
[[ -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_REALM="${IPA_REALM:-}"
|
||||||
IPA_ADMIN_PASS="${IPA_ADMIN_PASSWORD:-}"
|
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
|
if [[ -z "$IPA_REALM" || -z "$IPA_ADMIN_PASS" ]]; then
|
||||||
warn "IPA_REALM or IPA_ADMIN_PASSWORD not set — cannot configure IPA side"
|
warn "IPA_REALM or IPA_ADMIN_PASSWORD not set — cannot configure IPA side"
|
||||||
exit 1
|
exit 1
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue