Dotfiles/setup/modules/FreeipaAnsible/ansible/lib/ansipa-policy.sh

161 lines
7.8 KiB
Bash

#!/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"
}