#!/usr/bin/env bash # ansipa-policy.sh — shared library for ansipa policy enforcement. # # Sourced by ansipa-enforce-policies.sh before any policy file is loaded. # Provides: constants, logging, group discovery, policystore sync, # and the _smb_parse_cred() helper (shared across usr_smb and usr_policystore). # # After _ansipa_discover() the following variables are exported for policy files: # RAW_GROUPS (comma-sep host-group string from ipa host-show) # _ALL_USER_GROUPS (newline-sep full list from one ipa group-find call) # ACTIVE_DAEMON_ENABLE ACTIVE_DAEMON_DISABLE (arrays, unit name suffixes) # WANT_TIMESHIFT_BACKUP WANT_SECURITY_SCAN WANT_NO_LOCAL_USERS (booleans) # ACTIVE_LOCAL_SUDO_USERS ACTIVE_SSH_USERS ACTIVE_MON_DEV (arrays) # WANT_USR_ADMIN WANT_SCAN_NOTIFY (booleans) # ACTIVE_BLOCK_BINARIES ACTIVE_BLOCK_IPA_GROUPS (parallel arrays) # ACTIVE_PRT_GROUPS ACTIVE_PRT_PRINTERS (parallel arrays) # ACTIVE_MON_USR (array, usr_mon_* suffixes) # ── Constants ───────────────────────────────────────────────────────────────── STATE_DIR="/var/lib/ansipa-policies" BLOCK_DIR="/usr/local/bin" CRON_DIR="/etc/cron.d" LOG_TAG="ansipa-policies" POLICY_DIR="/usr/local/lib/ansipa/policies.d" # ── Logging ─────────────────────────────────────────────────────────────────── 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; } # ── 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). _ansipa_discover() { # ── Host groups (device policies) ───────────────────────────────────────── RAW_GROUPS=$(ipa host-show "$HOST_FQDN" --all 2>/dev/null \ | grep -i "Member of host-groups:" | sed 's/.*: //' || true) ACTIVE_DAEMON_ENABLE=() ACTIVE_DAEMON_DISABLE=() ACTIVE_LOCAL_SUDO_USERS=() ACTIVE_SSH_USERS=() ACTIVE_MON_DEV=() WANT_TIMESHIFT_BACKUP=false WANT_SECURITY_SCAN=false WANT_NO_LOCAL_USERS=false if [[ -n "$RAW_GROUPS" ]]; then while IFS=',' read -ra GRP_ARRAY; do for g in "${GRP_ARRAY[@]}"; do g="${g// /}" case "$g" in dev_daemon-enable-*) ACTIVE_DAEMON_ENABLE+=("${g#dev_daemon-enable-}") ;; dev_daemon-disable-*) ACTIVE_DAEMON_DISABLE+=("${g#dev_daemon-disable-}") ;; dev_timeshift-backup) WANT_TIMESHIFT_BACKUP=true ;; dev_security-scan) WANT_SECURITY_SCAN=true ;; dev_no-local-users) WANT_NO_LOCAL_USERS=true ;; dev_local-sudo-*) ACTIVE_LOCAL_SUDO_USERS+=("${g#dev_local-sudo-}") ;; dev_ssh_*) ACTIVE_SSH_USERS+=("${g#dev_ssh_}") ;; dev_mon_*) ACTIVE_MON_DEV+=("${g#dev_mon_}") ;; esac done done <<< "$RAW_GROUPS" fi # ── User groups (one call, all prefixes) ────────────────────────────────── _ALL_USER_GROUPS=$(ipa group-find --pkey-only 2>/dev/null \ | awk '/Group name:/ {print $NF}' | sort -u || true) WANT_USR_ADMIN=false WANT_SCAN_NOTIFY=false ACTIVE_BLOCK_BINARIES=() ACTIVE_BLOCK_IPA_GROUPS=() ACTIVE_PRT_GROUPS=() ACTIVE_PRT_PRINTERS=() ACTIVE_MON_USR=() while IFS= read -r _grp; do [[ -z "$_grp" ]] && continue case "$_grp" in usr_admin) WANT_USR_ADMIN=true ;; usr_scan-notify) WANT_SCAN_NOTIFY=true ;; usr_block-binary-*) local _raw="${_grp#usr_block-binary-}" ACTIVE_BLOCK_BINARIES+=("${_raw//__/.}") ACTIVE_BLOCK_IPA_GROUPS+=("$_grp") ;; usr_prt_*) ACTIVE_PRT_GROUPS+=("$_grp") ACTIVE_PRT_PRINTERS+=("${_grp#usr_prt_}") ;; usr_mon_*) ACTIVE_MON_USR+=("${_grp#usr_mon_}") ;; esac done <<< "$_ALL_USER_GROUPS" unset _grp _raw 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}" \ "| ssh-keys: ${ACTIVE_SSH_USERS[*]:-none} | mon-dev: ${ACTIVE_MON_DEV[*]:-none}" log "User policies — admin: $WANT_USR_ADMIN" \ "| block-binary: ${ACTIVE_BLOCK_BINARIES[*]:-none}" \ "| printers: ${ACTIVE_PRT_PRINTERS[*]:-none}" \ "| scan-notify: $WANT_SCAN_NOTIFY | mon-usr: ${ACTIVE_MON_USR[*]:-none}" } # ── SMB credential parser ───────────────────────────────────────────────────── # Used by both usr_smb.sh and usr_policystore.sh. # Outputs "host samba_user password" when the IPA group description contains # a cifs:// credential (written by ansipa-smb-setup.sh). _smb_parse_cred() { local _desc _desc=$(ipa group-show "$1" --all 2>/dev/null \ | awk -F': ' '/Description:/{print $2; exit}' || true) [[ "$_desc" =~ ^cifs://([^:]+):([^:]+):(.+)$ ]] && \ echo "${BASH_REMATCH[1]} ${BASH_REMATCH[2]} ${BASH_REMATCH[3]}" } # ── Policystore sync ────────────────────────────────────────────────────────── # Called by the orchestrator after discovery. Reads the SMB credential from the # usr_smb_adm_policystore IPA group description, then syncs *.sh files from the # ansipa-policystore SMB share into $POLICY_DIR. # # On SMB failure: warns and returns 0; the orchestrator continues with whatever # files are already in $POLICY_DIR (Ansible-deployed or previously synced). _ansipa_sync_policystore() { command -v smbclient &>/dev/null || return 0 local _cred_line _host _suser _spass _cred_line=$(_smb_parse_cred "usr_smb_adm_policystore" 2>/dev/null || true) if [[ -z "$_cred_line" ]]; then return 0 # group absent or no credential yet — use local files fi read -r _host _suser _spass <<< "$_cred_line" local _tmp _tmp=$(mktemp -d) # Build a temporary smbclient credentials file (avoids shell-quoting issues) local _creds_tmp _creds_tmp=$(mktemp) printf 'username=%s\npassword=%s\ndomain=WORKGROUP\n' "$_suser" "$_spass" \ > "$_creds_tmp" chmod 600 "$_creds_tmp" if smbclient "//${_host}/ansipa-policystore" -A "$_creds_tmp" \ -c "cd policies.d; mask *.sh; recurse ON; prompt OFF; lcd ${_tmp}; mget *" \ &>/dev/null then # Atomic swap: remove stale files and replace with synced set. rm -f "$POLICY_DIR"/*.sh 2>/dev/null || true cp "$_tmp"/*.sh "$POLICY_DIR"/ 2>/dev/null \ && log "Synced policy store from //${_host}/ansipa-policystore/policies.d" \ || warn "Policystore sync: no .sh files on share — using local files" else warn "Policystore sync failed (SMB unreachable?) — using cached local files" fi rm -rf "$_tmp" rm -f "$_creds_tmp" }