#!/usr/bin/env bash # policy: usr_ctl_dnshostfile — grant specific IPA users write access to # /etc/hosts via a POSIX ACL. Default (no membership) leaves /etc/hosts at # its normal root-only permissions — membership is what GRANTS the right, # it does not restrict anything for non-members. # # Scope: /etc/hosts only, deliberately not /etc/resolv.conf. On systems using # systemd-resolved, /etc/resolv.conf is usually a symlink to a stub file on # tmpfs that gets regenerated on boot/network changes, so a static ACL there # would silently vanish; covering it properly would mean resolving and # re-applying to whatever the symlink currently targets on every run, which # is a deliberate follow-up rather than something folded in here silently. # # Requires: the 'acl' package (setfacl/getfacl) on the client. # Security note: this is a real grant, not a cosmetic one — a member can # redirect any hostname to any IP on this machine (local DNS override), which # is enough to locally spoof a login page or update server. Grant deliberately. HOSTS_FILE="/etc/hosts" DNSHOST_STATE="$STATE_DIR/dnshostfile-acl-users" [[ -f "$DNSHOST_STATE" ]] || touch "$DNSHOST_STATE" if ! command -v setfacl &>/dev/null; then warn "usr_ctl_dnshostfile: setfacl not found (install the 'acl' package) — skipping" else _dnshost_grp_exists=false grep -qxF "usr_ctl_dnshostfile" <<< "$_ALL_USER_GROUPS" && _dnshost_grp_exists=true _DNSHOST_USERS=() if [[ "$_dnshost_grp_exists" == true ]]; then while IFS= read -r _u; do [[ -z "$_u" ]] && continue _DNSHOST_USERS+=("$_u") done < <(_ipa_group_member_users usr_ctl_dnshostfile) fi _dnshost_in_desired() { local n="$1" for _d in "${_DNSHOST_USERS[@]+"${_DNSHOST_USERS[@]}"}"; do [[ "$_d" == "$n" ]] && return 0; done return 1 } # Grant: add an ACL entry for every currently-desired user (idempotent). for _u in "${_DNSHOST_USERS[@]+"${_DNSHOST_USERS[@]}"}"; do getent passwd "$_u" &>/dev/null || continue if ! getfacl "$HOSTS_FILE" 2>/dev/null | grep -qxF "user:${_u}:rw-"; then setfacl -m "u:${_u}:rw" "$HOSTS_FILE" \ && log "usr_ctl_dnshostfile: granted $_u write access to $HOSTS_FILE" \ || warn "usr_ctl_dnshostfile: setfacl grant failed for $_u" fi done # Revert: remove the ACL entry for anyone previously granted who is no # longer in the group (or the group was deleted entirely). _NEW_DNSHOST_STATE=() while IFS= read -r _old_u; do [[ -z "$_old_u" ]] && continue if _dnshost_in_desired "$_old_u"; then _NEW_DNSHOST_STATE+=("$_old_u") else setfacl -x "u:${_old_u}" "$HOSTS_FILE" 2>/dev/null \ && log "usr_ctl_dnshostfile: revoked $_old_u write access to $HOSTS_FILE" fi done < "$DNSHOST_STATE" if [[ ${#_DNSHOST_USERS[@]} -gt 0 ]]; then printf '%s\n' "${_DNSHOST_USERS[@]}" | sort -u > "$DNSHOST_STATE" else > "$DNSHOST_STATE" fi unset _dnshost_grp_exists _DNSHOST_USERS _u _NEW_DNSHOST_STATE _old_u fi