mirror of
https://github.com/kuhyx/scripts.git
synced 2026-07-04 17:03:13 +02:00
feat: more hosts friction
This commit is contained in:
parent
a69dfff125
commit
03bf1885ae
@ -5,6 +5,7 @@ set -euo pipefail
|
|||||||
TARGET=/etc/hosts
|
TARGET=/etc/hosts
|
||||||
CANON=/usr/local/share/locked-hosts
|
CANON=/usr/local/share/locked-hosts
|
||||||
LOG=/var/log/hosts-guard.log
|
LOG=/var/log/hosts-guard.log
|
||||||
|
SYSLOG_TAG=hosts-unlock
|
||||||
EDITOR_CMD=${EDITOR:-nano}
|
EDITOR_CMD=${EDITOR:-nano}
|
||||||
DELAY_SECONDS=45
|
DELAY_SECONDS=45
|
||||||
|
|
||||||
@ -13,7 +14,14 @@ log() { printf '%s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" | tee -a "$LOG" >
|
|||||||
require_root() { if [[ $EUID -ne 0 ]]; then exec sudo -E bash "$0" "$@"; fi }
|
require_root() { if [[ $EUID -ne 0 ]]; then exec sudo -E bash "$0" "$@"; fi }
|
||||||
require_root "$@"
|
require_root "$@"
|
||||||
|
|
||||||
log "Requested intentional /etc/hosts modification session."
|
echo "Reason for editing /etc/hosts (will be logged):" >&2
|
||||||
|
read -r -p "Enter reason: " REASON
|
||||||
|
if [[ -z ${REASON// } ]]; then
|
||||||
|
echo "Empty reason not allowed. Aborting." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
log "Requested intentional /etc/hosts modification session. Reason: $REASON"
|
||||||
|
logger -t "$SYSLOG_TAG" "session_start user=${SUDO_USER:-$USER} reason='$REASON'"
|
||||||
echo "This action is logged. A cooling-off delay of $DELAY_SECONDS seconds applies." >&2
|
echo "This action is logged. A cooling-off delay of $DELAY_SECONDS seconds applies." >&2
|
||||||
|
|
||||||
for s in hosts-bind-mount.service hosts-guard.path; do
|
for s in hosts-bind-mount.service hosts-guard.path; do
|
||||||
@ -42,9 +50,11 @@ sha_before=$(sha256sum "$TARGET" | awk '{print $1}')
|
|||||||
sha_after=$(sha256sum "$TARGET" | awk '{print $1}')
|
sha_after=$(sha256sum "$TARGET" | awk '{print $1}')
|
||||||
|
|
||||||
if [[ "$sha_before" == "$sha_after" ]]; then
|
if [[ "$sha_before" == "$sha_after" ]]; then
|
||||||
log "No changes made to $TARGET."
|
log "No changes made to $TARGET. Reason: $REASON"
|
||||||
|
logger -t "$SYSLOG_TAG" "no_change user=${SUDO_USER:-$USER} reason='$REASON'"
|
||||||
else
|
else
|
||||||
log "Changes detected. Updating canonical copy and re-enforcing."
|
log "Changes detected. Updating canonical copy and re-enforcing. Reason: $REASON"
|
||||||
|
logger -t "$SYSLOG_TAG" "modified user=${SUDO_USER:-$USER} reason='$REASON'"
|
||||||
cp "$TARGET" "$CANON"
|
cp "$TARGET" "$CANON"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -55,5 +65,6 @@ fi
|
|||||||
systemctl start hosts-guard.path || true
|
systemctl start hosts-guard.path || true
|
||||||
systemctl start hosts-bind-mount.service || true
|
systemctl start hosts-bind-mount.service || true
|
||||||
|
|
||||||
log "Unlock session complete."
|
log "Unlock session complete. Reason: $REASON"
|
||||||
|
logger -t "$SYSLOG_TAG" "session_end user=${SUDO_USER:-$USER} reason='$REASON'"
|
||||||
echo "Done." >&2
|
echo "Done." >&2
|
||||||
|
|||||||
@ -36,6 +36,9 @@ ENABLE_PATH=1
|
|||||||
UNINSTALL=0
|
UNINSTALL=0
|
||||||
DELAY=45
|
DELAY=45
|
||||||
DRY_RUN=0
|
DRY_RUN=0
|
||||||
|
INSTALL_SHELL_HOOKS=1
|
||||||
|
INSTALL_AUDIT_RULE=1
|
||||||
|
ADD_ALIAS_STUB=1
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Helpers
|
# Helpers
|
||||||
@ -61,6 +64,12 @@ while [[ $# -gt 0 ]]; do
|
|||||||
--skip-path-watch) ENABLE_PATH=0 ; shift ;;
|
--skip-path-watch) ENABLE_PATH=0 ; shift ;;
|
||||||
--delay) DELAY=${2:-} ; [[ -z ${DELAY} ]] && { err '--delay requires value'; exit 2; } ; shift 2 ;;
|
--delay) DELAY=${2:-} ; [[ -z ${DELAY} ]] && { err '--delay requires value'; exit 2; } ; shift 2 ;;
|
||||||
--dry-run) DRY_RUN=1 ; shift ;;
|
--dry-run) DRY_RUN=1 ; shift ;;
|
||||||
|
--no-shell-hooks) INSTALL_SHELL_HOOKS=0 ; shift ;;
|
||||||
|
--shell-hooks) INSTALL_SHELL_HOOKS=1 ; shift ;;
|
||||||
|
--no-audit) INSTALL_AUDIT_RULE=0 ; shift ;;
|
||||||
|
--audit) INSTALL_AUDIT_RULE=1 ; shift ;;
|
||||||
|
--no-alias-stub) ADD_ALIAS_STUB=0 ; shift ;;
|
||||||
|
--alias-stub) ADD_ALIAS_STUB=1 ; shift ;;
|
||||||
--uninstall) UNINSTALL=1 ; shift ;;
|
--uninstall) UNINSTALL=1 ; shift ;;
|
||||||
-h|--help) usage; exit 0 ;;
|
-h|--help) usage; exit 0 ;;
|
||||||
*) err "Unknown argument: $1"; usage; exit 2 ;;
|
*) err "Unknown argument: $1"; usage; exit 2 ;;
|
||||||
@ -86,6 +95,10 @@ INSTALL_UNLOCK="/usr/local/sbin/unlock-hosts"
|
|||||||
CANON="/usr/local/share/locked-hosts"
|
CANON="/usr/local/share/locked-hosts"
|
||||||
HOSTS="/etc/hosts"
|
HOSTS="/etc/hosts"
|
||||||
|
|
||||||
|
# Shell hook destinations (user agnostic system-wide skeleton + etc profile.d)
|
||||||
|
ZSH_FILTER_SNIPPET="/etc/zsh/hosts_guard_history_filter.zsh"
|
||||||
|
BASH_FILTER_SNIPPET="/etc/profile.d/hosts_guard_history_filter.sh"
|
||||||
|
|
||||||
SYSTEMD_DIR="/etc/systemd/system"
|
SYSTEMD_DIR="/etc/systemd/system"
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
@ -103,7 +116,9 @@ if [[ $UNINSTALL -eq 1 ]]; then
|
|||||||
"$INSTALL_UNLOCK" \
|
"$INSTALL_UNLOCK" \
|
||||||
"$SYSTEMD_DIR/hosts-guard.service" \
|
"$SYSTEMD_DIR/hosts-guard.service" \
|
||||||
"$SYSTEMD_DIR/hosts-guard.path" \
|
"$SYSTEMD_DIR/hosts-guard.path" \
|
||||||
"$SYSTEMD_DIR/hosts-bind-mount.service"; do
|
"$SYSTEMD_DIR/hosts-bind-mount.service" \
|
||||||
|
"$ZSH_FILTER_SNIPPET" \
|
||||||
|
"$BASH_FILTER_SNIPPET"; do
|
||||||
if [[ -e $f ]]; then run rm -f "$f"; fi
|
if [[ -e $f ]]; then run rm -f "$f"; fi
|
||||||
done
|
done
|
||||||
note "Leaving canonical snapshot at $CANON (remove manually if undesired)."
|
note "Leaving canonical snapshot at $CANON (remove manually if undesired)."
|
||||||
@ -161,6 +176,114 @@ if [[ $DELAY -ne 45 ]]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Install shell history filters (optional)
|
||||||
|
######################################################################
|
||||||
|
if [[ $INSTALL_SHELL_HOOKS -eq 1 ]]; then
|
||||||
|
msg "Installing shell history suppression hooks for unlock command"
|
||||||
|
# Pattern matches commands invoking unlock-hosts (with or without sudo) & setup script force snapshot
|
||||||
|
FILTER_PATTERN='(^|;|&&|\|\|)\s*(sudo\s+)?(/usr/local/sbin/)?unlock-hosts(\s|;|$)'
|
||||||
|
|
||||||
|
# Zsh: use zshaddhistory function
|
||||||
|
if command -v zsh >/dev/null 2>&1; then
|
||||||
|
if [[ $DRY_RUN -eq 1 ]]; then
|
||||||
|
echo "DRY-RUN: would create $ZSH_FILTER_SNIPPET"
|
||||||
|
else
|
||||||
|
cat > "$ZSH_FILTER_SNIPPET" <<'ZEOF'
|
||||||
|
# Added by hosts guard setup – suppress unlock-hosts commands from Zsh history
|
||||||
|
autoload -Uz add-zsh-hook 2>/dev/null || true
|
||||||
|
_hosts_guard_history_filter() {
|
||||||
|
emulate -L zsh
|
||||||
|
setopt extendedglob
|
||||||
|
local line="$1"
|
||||||
|
local _pattern='(^|;|&&|\|\|)\s*(sudo\s+)?(/usr/local/sbin/)?unlock-hosts(\s|;|$)'
|
||||||
|
if [[ $line =~ ${_pattern} ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if typeset -f add-zsh-hook >/dev/null 2>&1; then
|
||||||
|
add-zsh-hook zshaddhistory _hosts_guard_history_filter 2>/dev/null || true
|
||||||
|
else
|
||||||
|
zshaddhistory() { _hosts_guard_history_filter "$1"; }
|
||||||
|
fi
|
||||||
|
ZEOF
|
||||||
|
chmod 644 "$ZSH_FILTER_SNIPPET"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Bash: rely on HISTCONTROL and PROMPT_COMMAND filter
|
||||||
|
if command -v bash >/dev/null 2>&1; then
|
||||||
|
if [[ $DRY_RUN -eq 1 ]]; then
|
||||||
|
echo "DRY-RUN: would create $BASH_FILTER_SNIPPET"
|
||||||
|
else
|
||||||
|
cat > "$BASH_FILTER_SNIPPET" <<'BEOF'
|
||||||
|
# Added by hosts guard setup – suppress unlock-hosts commands from Bash history
|
||||||
|
export HISTCONTROL=ignoredups:erasedups
|
||||||
|
_hosts_guard_hist_filter() {
|
||||||
|
local last_cmd
|
||||||
|
local _pattern='(^|;|&&|\|\|)\s*(sudo\s+)?(/usr/local/sbin/)?unlock-hosts(\s|;|$)'
|
||||||
|
last_cmd=$(history 1 2>/dev/null | sed -E 's/^ *[0-9]+ +//')
|
||||||
|
if [[ -n $last_cmd && $last_cmd =~ ${_pattern} ]]; then
|
||||||
|
local id
|
||||||
|
id=$(history 1 2>/dev/null | awk '{print $1}')
|
||||||
|
if [[ -n $id ]]; then history -d $id 2>/dev/null || true; fi
|
||||||
|
history -w 2>/dev/null || true
|
||||||
|
history -c || true
|
||||||
|
history -r 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
case :${PROMPT_COMMAND-}: in
|
||||||
|
*:_hosts_guard_hist_filter:* ) ;;
|
||||||
|
* ) PROMPT_COMMAND="_hosts_guard_hist_filter${PROMPT_COMMAND:+;${PROMPT_COMMAND}}" ;;
|
||||||
|
esac
|
||||||
|
BEOF
|
||||||
|
chmod 644 "$BASH_FILTER_SNIPPET"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
note "Skipping shell history hooks (--no-shell-hooks)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Add alias stub to discourage raw invocation (shell-level friction)
|
||||||
|
######################################################################
|
||||||
|
if [[ $ADD_ALIAS_STUB -eq 1 ]]; then
|
||||||
|
PROFILE_STUB="/etc/profile.d/hosts_guard_alias_stub.sh"
|
||||||
|
if [[ $DRY_RUN -eq 1 ]]; then
|
||||||
|
echo "DRY-RUN: would create $PROFILE_STUB"
|
||||||
|
else
|
||||||
|
cat > "$PROFILE_STUB" <<'ASTUB'
|
||||||
|
# Added by hosts guard setup – discourages casual use of unlock-hosts name
|
||||||
|
if command -v unlock-hosts >/dev/null 2>&1; then
|
||||||
|
alias unlock-hosts='command_not_found_handle 2>/dev/null || echo "Use: sudo /usr/local/sbin/unlock-hosts (logged & delayed)"'
|
||||||
|
fi
|
||||||
|
ASTUB
|
||||||
|
chmod 644 "$PROFILE_STUB"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Audit rule to record executions (requires auditd)
|
||||||
|
######################################################################
|
||||||
|
if [[ $INSTALL_AUDIT_RULE -eq 1 ]]; then
|
||||||
|
if command -v auditctl >/dev/null 2>&1; then
|
||||||
|
AUDIT_RULE="-w /usr/local/sbin/unlock-hosts -p x -k hosts_unlock"
|
||||||
|
if auditctl -l 2>/dev/null | grep -Fq "/usr/local/sbin/unlock-hosts"; then
|
||||||
|
note "Audit rule already present"
|
||||||
|
else
|
||||||
|
run auditctl $AUDIT_RULE || warn "Failed to add audit rule (runtime)"
|
||||||
|
if [[ $DRY_RUN -eq 1 ]]; then
|
||||||
|
echo "DRY-RUN: would create /etc/audit/rules.d/hosts_unlock.rules"
|
||||||
|
else
|
||||||
|
echo "$AUDIT_RULE" > /etc/audit/rules.d/hosts_unlock.rules
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "auditctl not found; skipping audit rule (install auditd to enable)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Install systemd units
|
# Install systemd units
|
||||||
######################################################################
|
######################################################################
|
||||||
@ -206,6 +329,9 @@ echo "Unlock command: sudo $INSTALL_UNLOCK"
|
|||||||
echo "Delay (seconds): $DELAY"
|
echo "Delay (seconds): $DELAY"
|
||||||
echo "Auto-revert path watch: $([[ $ENABLE_PATH -eq 1 ]] && echo enabled || echo disabled)"
|
echo "Auto-revert path watch: $([[ $ENABLE_PATH -eq 1 ]] && echo enabled || echo disabled)"
|
||||||
echo "Read-only bind mount: $([[ $ENABLE_BIND -eq 1 ]] && echo enabled || echo disabled)"
|
echo "Read-only bind mount: $([[ $ENABLE_BIND -eq 1 ]] && echo enabled || echo disabled)"
|
||||||
|
echo "Shell history suppression: $([[ $INSTALL_SHELL_HOOKS -eq 1 ]] && echo enabled || echo disabled)"
|
||||||
|
echo "Audit rule: $([[ $INSTALL_AUDIT_RULE -eq 1 ]] && echo enabled || echo disabled)"
|
||||||
|
echo "Alias stub: $([[ $ADD_ALIAS_STUB -eq 1 ]] && echo enabled || echo disabled)"
|
||||||
echo
|
echo
|
||||||
echo "Test flow:"
|
echo "Test flow:"
|
||||||
echo " sudo sed -i '1s/.*/# tamper test/' /etc/hosts # Should revert automatically"
|
echo " sudo sed -i '1s/.*/# tamper test/' /etc/hosts # Should revert automatically"
|
||||||
@ -213,5 +339,6 @@ echo " sudo $INSTALL_UNLOCK # Intentional edit workflow"
|
|||||||
echo
|
echo
|
||||||
echo "Uninstall:"
|
echo "Uninstall:"
|
||||||
echo " sudo $0 --uninstall"
|
echo " sudo $0 --uninstall"
|
||||||
|
echo "(Optional) Skip shell history hooks: --no-shell-hooks"
|
||||||
echo
|
echo
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user