mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 13:23:15 +02:00
Protect nsswitch.conf and resolved.conf from hosts bypass
- Add enforce-resolved.sh: validates ReadEtcHosts=yes, prevents DNSOverTLS bypass, removes drop-in overrides, locks drop-in dir - Add resolved-guard.path/service: watches /etc/systemd/resolved.conf and its drop-in directory for tampering - Update pacman hooks to unlock/relock nsswitch.conf and resolved.conf alongside /etc/hosts during package transactions - Extend setup_hosts_guard.sh with --skip-resolved option, resolved canonical snapshot, drop-in directory locking, and enforcement - Add resolved.conf checks to check_and_enable_services.sh: validates ReadEtcHosts, DNSOverTLS, drop-in overrides, immutable attribute, and resolved-guard.path status with auto-fix capability Fixed on live system: ReadEtcHosts was set to 'no' and nsswitch.conf was missing 'files' in the hosts line, completely bypassing /etc/hosts.
This commit is contained in:
parent
feac2a7aa8
commit
6ec85106b7
147
linux_configuration/hosts/guard/enforce-resolved.sh
Executable file
147
linux_configuration/hosts/guard/enforce-resolved.sh
Executable file
@ -0,0 +1,147 @@
|
||||
#!/bin/bash
|
||||
# Guard script to enforce canonical /etc/systemd/resolved.conf
|
||||
# Ensures ReadEtcHosts=yes and prevents DNS-over-TLS bypass of /etc/hosts
|
||||
# Installed to /usr/local/sbin/enforce-resolved.sh by setup_hosts_guard.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
CANONICAL_SOURCE="/usr/local/share/locked-resolved.conf"
|
||||
TARGET="/etc/systemd/resolved.conf"
|
||||
DROPIN_DIR="/etc/systemd/resolved.conf.d"
|
||||
LOG_FILE="/var/log/resolved-guard.log"
|
||||
|
||||
log() {
|
||||
printf '%s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" | tee -a "$LOG_FILE" >&2
|
||||
}
|
||||
|
||||
# Validate that resolved.conf has correct settings to honour /etc/hosts
|
||||
# Critical settings:
|
||||
# ReadEtcHosts=yes — must be present and set to yes
|
||||
# DNSOverTLS=no — must NOT be opportunistic/yes (bypasses local hosts)
|
||||
validate_resolved() {
|
||||
local file="$1"
|
||||
|
||||
# ReadEtcHosts must be explicitly yes (not commented, not "no")
|
||||
local read_hosts
|
||||
read_hosts=$(grep -E '^\s*ReadEtcHosts\s*=' "$file" 2>/dev/null | tail -1 |
|
||||
sed 's/.*=\s*//' | tr -d '[:space:]')
|
||||
if [[ "$read_hosts" != "yes" ]]; then
|
||||
log "INVALID: ReadEtcHosts='$read_hosts' (expected 'yes') in $file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# DNSOverTLS must not be set to yes or opportunistic
|
||||
local dot
|
||||
dot=$(grep -E '^\s*DNSOverTLS\s*=' "$file" 2>/dev/null | tail -1 |
|
||||
sed 's/.*=\s*//' | tr -d '[:space:]')
|
||||
if [[ -n "$dot" && "$dot" != "no" ]]; then
|
||||
log "INVALID: DNSOverTLS='$dot' (must be 'no' or commented out) in $file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Remove any drop-in overrides that could bypass protections
|
||||
enforce_no_dropins() {
|
||||
if [[ -d "$DROPIN_DIR" ]]; then
|
||||
local count
|
||||
count=$(find "$DROPIN_DIR" -name '*.conf' -type f 2>/dev/null | wc -l)
|
||||
if [[ "$count" -gt 0 ]]; then
|
||||
log "TAMPERING: Found $count drop-in config(s) in $DROPIN_DIR — removing"
|
||||
find "$DROPIN_DIR" -name '*.conf' -type f -delete
|
||||
log "Removed all drop-in overrides"
|
||||
fi
|
||||
# Lock the directory itself to prevent new drop-ins
|
||||
chattr +i "$DROPIN_DIR" 2>/dev/null || log "Failed to lock $DROPIN_DIR"
|
||||
else
|
||||
# Create and lock the directory to prevent creation with overrides
|
||||
mkdir -p "$DROPIN_DIR"
|
||||
chattr +i "$DROPIN_DIR" 2>/dev/null || log "Failed to lock $DROPIN_DIR"
|
||||
log "Created and locked empty $DROPIN_DIR"
|
||||
fi
|
||||
}
|
||||
|
||||
# Main enforcement logic
|
||||
log "Starting resolved.conf enforcement"
|
||||
|
||||
# 1. Handle drop-in overrides first
|
||||
enforce_no_dropins
|
||||
|
||||
# 2. Check current resolved.conf
|
||||
if [[ ! -f "$TARGET" ]]; then
|
||||
log "CRITICAL: $TARGET does not exist"
|
||||
if [[ -f "$CANONICAL_SOURCE" ]]; then
|
||||
chattr -i "$TARGET" 2>/dev/null || true
|
||||
cp "$CANONICAL_SOURCE" "$TARGET"
|
||||
chmod 644 "$TARGET"
|
||||
chattr +i "$TARGET" 2>/dev/null || log "Failed to set immutable on $TARGET"
|
||||
log "Restored $TARGET from canonical copy"
|
||||
else
|
||||
log "ERROR: No canonical source at $CANONICAL_SOURCE — cannot restore"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! validate_resolved "$TARGET"; then
|
||||
log "TAMPERING DETECTED in $TARGET"
|
||||
|
||||
if [[ -f "$CANONICAL_SOURCE" ]]; then
|
||||
chattr -i "$TARGET" 2>/dev/null || true
|
||||
cp "$CANONICAL_SOURCE" "$TARGET"
|
||||
chmod 644 "$TARGET"
|
||||
chattr +i "$TARGET" 2>/dev/null || log "Failed to set immutable on $TARGET"
|
||||
log "Restored $TARGET from canonical copy"
|
||||
else
|
||||
log "No canonical source — applying emergency fix"
|
||||
chattr -i "$TARGET" 2>/dev/null || true
|
||||
|
||||
# Fix ReadEtcHosts
|
||||
if grep -qE '^\s*ReadEtcHosts\s*=' "$TARGET"; then
|
||||
sed -i -E 's/^\s*ReadEtcHosts\s*=.*/ReadEtcHosts=yes/' "$TARGET"
|
||||
elif grep -q '^\[Resolve\]' "$TARGET"; then
|
||||
sed -i '/^\[Resolve\]/a ReadEtcHosts=yes' "$TARGET"
|
||||
else
|
||||
printf '\n[Resolve]\nReadEtcHosts=yes\n' >>"$TARGET"
|
||||
fi
|
||||
|
||||
# Fix DNSOverTLS
|
||||
if grep -qE '^\s*DNSOverTLS\s*=' "$TARGET"; then
|
||||
sed -i -E 's/^\s*DNSOverTLS\s*=.*/#DNSOverTLS=no/' "$TARGET"
|
||||
fi
|
||||
|
||||
chattr +i "$TARGET" 2>/dev/null || true
|
||||
log "Emergency fix applied"
|
||||
fi
|
||||
|
||||
# Restart resolved to pick up changes
|
||||
systemctl restart systemd-resolved 2>/dev/null || log "Failed to restart systemd-resolved"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 3. If canonical exists, check for any drift
|
||||
if [[ -f "$CANONICAL_SOURCE" ]]; then
|
||||
if ! cmp -s "$CANONICAL_SOURCE" "$TARGET"; then
|
||||
log "Drift detected in $TARGET — restoring canonical"
|
||||
chattr -i "$TARGET" 2>/dev/null || true
|
||||
cp "$CANONICAL_SOURCE" "$TARGET"
|
||||
chmod 644 "$TARGET"
|
||||
chattr +i "$TARGET" 2>/dev/null || log "Failed to set immutable"
|
||||
log "Restored $TARGET from canonical copy"
|
||||
systemctl restart systemd-resolved 2>/dev/null || log "Failed to restart systemd-resolved"
|
||||
else
|
||||
log "No drift detected in $TARGET"
|
||||
fi
|
||||
else
|
||||
log "Creating initial canonical snapshot"
|
||||
mkdir -p "$(dirname "$CANONICAL_SOURCE")"
|
||||
cp "$TARGET" "$CANONICAL_SOURCE"
|
||||
chmod 644 "$CANONICAL_SOURCE"
|
||||
chattr +i "$CANONICAL_SOURCE" 2>/dev/null || log "Failed to protect canonical copy"
|
||||
fi
|
||||
|
||||
# 4. Ensure immutable attribute is set on live file
|
||||
chattr -i "$TARGET" 2>/dev/null || true
|
||||
chattr +i "$TARGET" 2>/dev/null || log "Failed to set immutable on $TARGET"
|
||||
|
||||
log "resolved.conf enforcement complete"
|
||||
@ -3,6 +3,9 @@
|
||||
# This file is sourced by pacman-pre-unlock-hosts.sh and pacman-post-relock-hosts.sh
|
||||
|
||||
TARGET=/etc/hosts
|
||||
NSSWITCH=/etc/nsswitch.conf
|
||||
RESOLVED_CONF=/etc/systemd/resolved.conf
|
||||
RESOLVED_DROPIN=/etc/systemd/resolved.conf.d
|
||||
LOGTAG=hosts-guard-hook
|
||||
|
||||
# Check if target has a read-only mount
|
||||
@ -38,7 +41,7 @@ collapse_mounts() {
|
||||
|
||||
# Stop systemd units related to hosts guard
|
||||
stop_units_if_present() {
|
||||
local units=(hosts-bind-mount.service hosts-guard.path)
|
||||
local units=(hosts-bind-mount.service hosts-guard.path nsswitch-guard.path resolved-guard.path)
|
||||
for u in "${units[@]}"; do
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
if systemctl list-unit-files 2>/dev/null | grep -q "^$u"; then
|
||||
@ -48,24 +51,36 @@ stop_units_if_present() {
|
||||
done
|
||||
}
|
||||
|
||||
# Remove immutable/append-only attributes
|
||||
remove_host_attrs() {
|
||||
if command -v lsattr >/dev/null 2>&1; then
|
||||
local attrs
|
||||
attrs=$(lsattr -d "$TARGET" 2>/dev/null || true)
|
||||
if echo "$attrs" | grep -q " i "; then
|
||||
chattr -i "$TARGET" >/dev/null 2>&1 || true
|
||||
fi
|
||||
if echo "$attrs" | grep -q " a "; then
|
||||
chattr -a "$TARGET" >/dev/null 2>&1 || true
|
||||
fi
|
||||
# Remove immutable/append-only attributes from a file
|
||||
_remove_attrs_for() {
|
||||
local f="$1"
|
||||
if [[ -e "$f" ]] && command -v lsattr >/dev/null 2>&1; then
|
||||
chattr -ia "$f" >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
# Apply immutable attribute
|
||||
# Remove immutable/append-only attributes from all guarded files
|
||||
remove_host_attrs() {
|
||||
_remove_attrs_for "$TARGET"
|
||||
}
|
||||
|
||||
remove_all_guard_attrs() {
|
||||
_remove_attrs_for "$TARGET"
|
||||
_remove_attrs_for "$NSSWITCH"
|
||||
_remove_attrs_for "$RESOLVED_CONF"
|
||||
_remove_attrs_for "$RESOLVED_DROPIN"
|
||||
}
|
||||
|
||||
# Apply immutable attribute to all guarded files
|
||||
apply_immutable() {
|
||||
if command -v chattr >/dev/null 2>&1; then
|
||||
chattr +i "$TARGET" >/dev/null 2>&1 || true
|
||||
chattr +i "$NSSWITCH" >/dev/null 2>&1 || true
|
||||
chattr +i "$RESOLVED_CONF" >/dev/null 2>&1 || true
|
||||
# Lock drop-in dir to prevent creation of override files
|
||||
if [[ -d "$RESOLVED_DROPIN" ]]; then
|
||||
chattr +i "$RESOLVED_DROPIN" >/dev/null 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
@ -75,10 +90,12 @@ apply_ro_bind_mount() {
|
||||
mount -o remount,ro,bind "$TARGET" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
# Start the path watcher service
|
||||
# Start all path watcher services
|
||||
start_path_watcher() {
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
systemctl start hosts-guard.path >/dev/null 2>&1 || true
|
||||
systemctl start nsswitch-guard.path >/dev/null 2>&1 || true
|
||||
systemctl start resolved-guard.path >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# pacman-post-relock-hosts.sh - Re-apply hosts guard protections after pacman
|
||||
# pacman-post-relock-hosts.sh - Re-apply all guard protections after pacman
|
||||
# Re-locks: /etc/hosts, /etc/nsswitch.conf, /etc/systemd/resolved.conf
|
||||
set -euo pipefail
|
||||
|
||||
# Source shared functions
|
||||
@ -8,22 +9,30 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/hosts-guard-common.sh"
|
||||
|
||||
ENFORCE=/usr/local/sbin/enforce-hosts.sh
|
||||
ENFORCE_NSSWITCH=/usr/local/sbin/enforce-nsswitch.sh
|
||||
ENFORCE_RESOLVED=/usr/local/sbin/enforce-resolved.sh
|
||||
|
||||
log_hook "post" "relocking(start)"
|
||||
|
||||
# Collapse any stacked mounts first
|
||||
collapse_mounts
|
||||
|
||||
# Run enforcement script if available
|
||||
# Run enforcement scripts if available
|
||||
if [[ -x $ENFORCE ]]; then
|
||||
"$ENFORCE" >/dev/null 2>&1 || true
|
||||
fi
|
||||
if [[ -x $ENFORCE_NSSWITCH ]]; then
|
||||
"$ENFORCE_NSSWITCH" >/dev/null 2>&1 || true
|
||||
fi
|
||||
if [[ -x $ENFORCE_RESOLVED ]]; then
|
||||
"$ENFORCE_RESOLVED" >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
# Apply protections
|
||||
# Apply protections (immutable on all guarded files)
|
||||
apply_immutable
|
||||
apply_ro_bind_mount
|
||||
|
||||
# Start the path watcher
|
||||
# Start all path watchers
|
||||
start_path_watcher
|
||||
|
||||
log_hook "post" "relocking(done)"
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# pacman-pre-unlock-hosts.sh - Temporarily unlock /etc/hosts before pacman
|
||||
# pacman-pre-unlock-hosts.sh - Temporarily unlock guarded config files before pacman
|
||||
# Unlocks: /etc/hosts, /etc/nsswitch.conf, /etc/systemd/resolved.conf
|
||||
set -euo pipefail
|
||||
|
||||
# Source shared functions
|
||||
@ -7,11 +8,11 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=hosts-guard-common.sh
|
||||
source "$SCRIPT_DIR/hosts-guard-common.sh"
|
||||
|
||||
# Remove protective attributes
|
||||
remove_host_attrs
|
||||
# Remove protective attributes from all guarded files
|
||||
remove_all_guard_attrs
|
||||
sudo rm /etc/hosts
|
||||
|
||||
# Stop guard services
|
||||
# Stop guard services (hosts, nsswitch, resolved watchers)
|
||||
stop_units_if_present
|
||||
|
||||
log_hook "pre" "unlocking(start)"
|
||||
|
||||
10
linux_configuration/hosts/guard/resolved-guard.path
Normal file
10
linux_configuration/hosts/guard/resolved-guard.path
Normal file
@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=Watch /etc/systemd/resolved.conf for tampering (hosts bypass protection)
|
||||
|
||||
[Path]
|
||||
PathChanged=/etc/systemd/resolved.conf
|
||||
PathChanged=/etc/systemd/resolved.conf.d
|
||||
Unit=resolved-guard.service
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
12
linux_configuration/hosts/guard/resolved-guard.service
Normal file
12
linux_configuration/hosts/guard/resolved-guard.service
Normal file
@ -0,0 +1,12 @@
|
||||
[Unit]
|
||||
Description=Enforce canonical /etc/systemd/resolved.conf (prevents hosts bypass)
|
||||
After=local-fs.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/sbin/enforce-resolved.sh
|
||||
Nice=10
|
||||
IOSchedulingClass=idle
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@ -34,6 +34,7 @@ DO_SNAPSHOT=1
|
||||
ENABLE_BIND=1
|
||||
ENABLE_PATH=1
|
||||
ENABLE_NSSWITCH=1
|
||||
ENABLE_RESOLVED=1
|
||||
UNINSTALL=0
|
||||
DELAY=45
|
||||
DRY_RUN=0
|
||||
@ -89,6 +90,10 @@ while [[ $# -gt 0 ]]; do
|
||||
ENABLE_NSSWITCH=0
|
||||
shift
|
||||
;;
|
||||
--skip-resolved)
|
||||
ENABLE_RESOLVED=0
|
||||
shift
|
||||
;;
|
||||
--delay)
|
||||
DELAY=${2:-}
|
||||
[[ -z ${DELAY} ]] && {
|
||||
@ -157,14 +162,21 @@ UNIT_BIND_SERVICE="$SCRIPT_DIR/hosts-bind-mount.service"
|
||||
TEMPLATE_ENFORCE_NSSWITCH="$SCRIPT_DIR/enforce-nsswitch.sh"
|
||||
UNIT_NSSWITCH_SERVICE="$SCRIPT_DIR/nsswitch-guard.service"
|
||||
UNIT_NSSWITCH_PATH="$SCRIPT_DIR/nsswitch-guard.path"
|
||||
TEMPLATE_ENFORCE_RESOLVED="$SCRIPT_DIR/enforce-resolved.sh"
|
||||
UNIT_RESOLVED_SERVICE="$SCRIPT_DIR/resolved-guard.service"
|
||||
UNIT_RESOLVED_PATH="$SCRIPT_DIR/resolved-guard.path"
|
||||
|
||||
INSTALL_ENFORCE="/usr/local/sbin/enforce-hosts.sh"
|
||||
INSTALL_UNLOCK="/usr/local/sbin/unlock-hosts"
|
||||
INSTALL_ENFORCE_NSSWITCH="/usr/local/sbin/enforce-nsswitch.sh"
|
||||
INSTALL_ENFORCE_RESOLVED="/usr/local/sbin/enforce-resolved.sh"
|
||||
CANON="/usr/local/share/locked-hosts"
|
||||
CANON_NSSWITCH="/usr/local/share/locked-nsswitch.conf"
|
||||
CANON_RESOLVED="/usr/local/share/locked-resolved.conf"
|
||||
HOSTS="/etc/hosts"
|
||||
NSSWITCH="/etc/nsswitch.conf"
|
||||
RESOLVED_CONF="/etc/systemd/resolved.conf"
|
||||
RESOLVED_DROPIN="/etc/systemd/resolved.conf.d"
|
||||
|
||||
# Shell hook destinations (user agnostic system-wide skeleton + etc profile.d)
|
||||
ZSH_FILTER_SNIPPET="/etc/zsh/hosts_guard_history_filter.zsh"
|
||||
@ -177,7 +189,7 @@ SYSTEMD_DIR="/etc/systemd/system"
|
||||
######################################################################
|
||||
if [[ $UNINSTALL -eq 1 ]]; then
|
||||
note "Uninstalling hosts guard components ( protections removed )"
|
||||
for u in hosts-guard.path hosts-guard.service hosts-bind-mount.service nsswitch-guard.path nsswitch-guard.service; do
|
||||
for u in hosts-guard.path hosts-guard.service hosts-bind-mount.service nsswitch-guard.path nsswitch-guard.service resolved-guard.path resolved-guard.service; do
|
||||
if systemctl list-unit-files | grep -q "^$u"; then
|
||||
run systemctl disable --now "$u" || true
|
||||
fi
|
||||
@ -186,16 +198,19 @@ if [[ $UNINSTALL -eq 1 ]]; then
|
||||
"$INSTALL_ENFORCE" \
|
||||
"$INSTALL_UNLOCK" \
|
||||
"$INSTALL_ENFORCE_NSSWITCH" \
|
||||
"$INSTALL_ENFORCE_RESOLVED" \
|
||||
"$SYSTEMD_DIR/hosts-guard.service" \
|
||||
"$SYSTEMD_DIR/hosts-guard.path" \
|
||||
"$SYSTEMD_DIR/hosts-bind-mount.service" \
|
||||
"$SYSTEMD_DIR/nsswitch-guard.service" \
|
||||
"$SYSTEMD_DIR/nsswitch-guard.path" \
|
||||
"$SYSTEMD_DIR/resolved-guard.service" \
|
||||
"$SYSTEMD_DIR/resolved-guard.path" \
|
||||
"$ZSH_FILTER_SNIPPET" \
|
||||
"$BASH_FILTER_SNIPPET"; do
|
||||
if [[ -e $f ]]; then run rm -f "$f"; fi
|
||||
done
|
||||
note "Leaving canonical snapshots at $CANON and $CANON_NSSWITCH (remove manually if undesired)."
|
||||
note "Leaving canonical snapshots at $CANON, $CANON_NSSWITCH and $CANON_RESOLVED (remove manually if undesired)."
|
||||
if [[ $DRY_RUN -eq 0 ]]; then systemctl daemon-reload; fi
|
||||
msg "Uninstall complete"
|
||||
exit 0
|
||||
@ -369,6 +384,8 @@ run install -m 644 "$UNIT_GUARD_PATH" "$SYSTEMD_DIR/hosts-guard.path"
|
||||
run install -m 644 "$UNIT_BIND_SERVICE" "$SYSTEMD_DIR/hosts-bind-mount.service"
|
||||
run install -m 644 "$UNIT_NSSWITCH_SERVICE" "$SYSTEMD_DIR/nsswitch-guard.service"
|
||||
run install -m 644 "$UNIT_NSSWITCH_PATH" "$SYSTEMD_DIR/nsswitch-guard.path"
|
||||
run install -m 644 "$UNIT_RESOLVED_SERVICE" "$SYSTEMD_DIR/resolved-guard.service"
|
||||
run install -m 644 "$UNIT_RESOLVED_PATH" "$SYSTEMD_DIR/resolved-guard.path"
|
||||
|
||||
if [[ $DRY_RUN -eq 0 ]]; then systemctl daemon-reload; fi
|
||||
|
||||
@ -432,6 +449,77 @@ else
|
||||
note "Skipping nsswitch protection (--skip-nsswitch)"
|
||||
fi
|
||||
|
||||
if [[ $ENABLE_RESOLVED -eq 1 ]]; then
|
||||
msg "Enabling resolved.conf protection (hosts bypass prevention)"
|
||||
msg "Installing resolved enforcement script -> $INSTALL_ENFORCE_RESOLVED"
|
||||
run install -m 755 "$TEMPLATE_ENFORCE_RESOLVED" "$INSTALL_ENFORCE_RESOLVED"
|
||||
|
||||
# Ensure ReadEtcHosts=yes in resolved.conf before snapshotting
|
||||
if [[ -f "$RESOLVED_CONF" ]]; then
|
||||
local_read_hosts=$(grep -E '^\s*ReadEtcHosts\s*=' "$RESOLVED_CONF" 2>/dev/null |
|
||||
tail -1 | sed 's/.*=\s*//' | tr -d '[:space:]')
|
||||
if [[ "$local_read_hosts" != "yes" ]]; then
|
||||
msg "Fixing ReadEtcHosts in resolved.conf (was: '$local_read_hosts')"
|
||||
chattr -i "$RESOLVED_CONF" 2>/dev/null || true
|
||||
if grep -qE '^\s*ReadEtcHosts\s*=' "$RESOLVED_CONF"; then
|
||||
run sed -i -E 's/^\s*ReadEtcHosts\s*=.*/ReadEtcHosts=yes/' "$RESOLVED_CONF"
|
||||
elif grep -q '^\[Resolve\]' "$RESOLVED_CONF"; then
|
||||
run sed -i '/^\[Resolve\]/a ReadEtcHosts=yes' "$RESOLVED_CONF"
|
||||
else
|
||||
printf '\n[Resolve]\nReadEtcHosts=yes\n' >>"$RESOLVED_CONF"
|
||||
fi
|
||||
msg "resolved.conf ReadEtcHosts fixed: $(grep 'ReadEtcHosts' "$RESOLVED_CONF")"
|
||||
fi
|
||||
|
||||
# Ensure DNSOverTLS is not set to yes or opportunistic
|
||||
local_dot=$(grep -E '^\s*DNSOverTLS\s*=' "$RESOLVED_CONF" 2>/dev/null |
|
||||
tail -1 | sed 's/.*=\s*//' | tr -d '[:space:]')
|
||||
if [[ -n "$local_dot" && "$local_dot" != "no" ]]; then
|
||||
msg "Disabling DNSOverTLS in resolved.conf (was: '$local_dot')"
|
||||
chattr -i "$RESOLVED_CONF" 2>/dev/null || true
|
||||
run sed -i -E 's/^\s*DNSOverTLS\s*=.*/#DNSOverTLS=no/' "$RESOLVED_CONF"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Lock drop-in directory to prevent override files
|
||||
if [[ -d "$RESOLVED_DROPIN" ]]; then
|
||||
# Remove any existing drop-in overrides
|
||||
local_count=$(find "$RESOLVED_DROPIN" -name '*.conf' -type f 2>/dev/null | wc -l)
|
||||
if [[ "$local_count" -gt 0 ]]; then
|
||||
warn "Removing $local_count drop-in override(s) from $RESOLVED_DROPIN"
|
||||
find "$RESOLVED_DROPIN" -name '*.conf' -type f -delete
|
||||
fi
|
||||
chattr +i "$RESOLVED_DROPIN" 2>/dev/null || warn "Failed to lock $RESOLVED_DROPIN"
|
||||
else
|
||||
run mkdir -p "$RESOLVED_DROPIN"
|
||||
chattr +i "$RESOLVED_DROPIN" 2>/dev/null || warn "Failed to lock $RESOLVED_DROPIN"
|
||||
fi
|
||||
|
||||
# Create resolved.conf canonical snapshot if needed
|
||||
if [[ -f "$RESOLVED_CONF" ]]; then
|
||||
if [[ ! -f "$CANON_RESOLVED" ]]; then
|
||||
msg "Creating canonical resolved.conf snapshot at $CANON_RESOLVED"
|
||||
run cp "$RESOLVED_CONF" "$CANON_RESOLVED"
|
||||
run chmod 644 "$CANON_RESOLVED"
|
||||
chattr +i "$CANON_RESOLVED" 2>/dev/null || warn "Failed to protect canonical resolved copy"
|
||||
fi
|
||||
fi
|
||||
|
||||
run systemctl enable --now resolved-guard.path
|
||||
|
||||
# Perform initial resolved enforcement
|
||||
if [[ $DRY_RUN -eq 1 ]]; then
|
||||
echo "DRY-RUN: would run $INSTALL_ENFORCE_RESOLVED"
|
||||
else
|
||||
"$INSTALL_ENFORCE_RESOLVED" || warn "resolved enforcement returned non-zero"
|
||||
fi
|
||||
|
||||
# Restart resolved to pick up corrected config
|
||||
run systemctl restart systemd-resolved
|
||||
else
|
||||
note "Skipping resolved.conf protection (--skip-resolved)"
|
||||
fi
|
||||
|
||||
msg "Performing initial hosts enforcement"
|
||||
if [[ $DRY_RUN -eq 1 ]]; then
|
||||
echo "DRY-RUN: would run $INSTALL_ENFORCE"
|
||||
@ -446,13 +534,16 @@ echo
|
||||
msg "Hosts guard setup complete"
|
||||
echo "Canonical hosts copy: $CANON"
|
||||
echo "Canonical nsswitch copy: $CANON_NSSWITCH"
|
||||
echo "Canonical resolved copy: $CANON_RESOLVED"
|
||||
echo "Enforce script: $INSTALL_ENFORCE"
|
||||
echo "nsswitch enforce: $INSTALL_ENFORCE_NSSWITCH"
|
||||
echo "resolved enforce: $INSTALL_ENFORCE_RESOLVED"
|
||||
echo "Unlock command: sudo $INSTALL_UNLOCK"
|
||||
echo "Delay (seconds): $DELAY"
|
||||
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 "nsswitch protection: $([[ $ENABLE_NSSWITCH -eq 1 ]] && echo enabled || echo disabled)"
|
||||
echo "resolved protection: $([[ $ENABLE_RESOLVED -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)"
|
||||
|
||||
@ -513,6 +513,61 @@ check_hosts() {
|
||||
status="warning"
|
||||
fi
|
||||
|
||||
# Check resolved.conf has ReadEtcHosts=yes
|
||||
if [[ -f /etc/systemd/resolved.conf ]]; then
|
||||
local read_etc_hosts
|
||||
read_etc_hosts=$(grep -E '^\s*ReadEtcHosts\s*=' /etc/systemd/resolved.conf 2>/dev/null |
|
||||
tail -1 | sed 's/.*=\s*//' | tr -d '[:space:]')
|
||||
if [[ "$read_etc_hosts" == "yes" ]]; then
|
||||
msg "resolved.conf ReadEtcHosts=yes"
|
||||
else
|
||||
issues+=("resolved.conf ReadEtcHosts='$read_etc_hosts' — /etc/hosts is bypassed by systemd-resolved!")
|
||||
status="error"
|
||||
fi
|
||||
|
||||
# Check DNSOverTLS is not enabled
|
||||
local dns_over_tls
|
||||
dns_over_tls=$(grep -E '^\s*DNSOverTLS\s*=' /etc/systemd/resolved.conf 2>/dev/null |
|
||||
tail -1 | sed 's/.*=\s*//' | tr -d '[:space:]')
|
||||
if [[ -z "$dns_over_tls" || "$dns_over_tls" == "no" ]]; then
|
||||
msg "resolved.conf DNSOverTLS is disabled"
|
||||
else
|
||||
issues+=("resolved.conf DNSOverTLS='$dns_over_tls' — can bypass /etc/hosts!")
|
||||
status="error"
|
||||
fi
|
||||
|
||||
# Check for drop-in overrides
|
||||
if [[ -d /etc/systemd/resolved.conf.d ]]; then
|
||||
local dropin_count
|
||||
dropin_count=$(find /etc/systemd/resolved.conf.d -name '*.conf' -type f 2>/dev/null | wc -l)
|
||||
if [[ "$dropin_count" -gt 0 ]]; then
|
||||
issues+=("Found $dropin_count resolved.conf drop-in override(s) — potential bypass!")
|
||||
status="error"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check immutable attribute
|
||||
if command -v lsattr &>/dev/null; then
|
||||
if lsattr /etc/systemd/resolved.conf 2>/dev/null | grep -q '.*i.*e.*'; then
|
||||
msg "resolved.conf has immutable attribute"
|
||||
else
|
||||
issues+=("resolved.conf missing immutable attribute")
|
||||
[[ "$status" == "ok" ]] && status="warning"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
issues+=("/etc/systemd/resolved.conf does not exist")
|
||||
[[ "$status" == "ok" ]] && status="warning"
|
||||
fi
|
||||
|
||||
# Check resolved guard
|
||||
if systemctl is-enabled resolved-guard.path &>/dev/null; then
|
||||
msg "resolved-guard.path is enabled"
|
||||
else
|
||||
issues+=("resolved-guard.path is not enabled")
|
||||
[[ "$status" == "ok" ]] && status="warning"
|
||||
fi
|
||||
|
||||
# Report issues
|
||||
if [[ $status != "ok" ]]; then
|
||||
for issue in "${issues[@]}"; do
|
||||
@ -543,6 +598,56 @@ check_hosts() {
|
||||
fi
|
||||
fi
|
||||
|
||||
# Fix resolved.conf if ReadEtcHosts is not yes
|
||||
if [[ -f /etc/systemd/resolved.conf ]]; then
|
||||
local resolved_reh
|
||||
resolved_reh=$(grep -E '^\s*ReadEtcHosts\s*=' /etc/systemd/resolved.conf 2>/dev/null |
|
||||
tail -1 | sed 's/.*=\s*//' | tr -d '[:space:]')
|
||||
if [[ "$resolved_reh" != "yes" ]]; then
|
||||
note "Fixing resolved.conf — setting ReadEtcHosts=yes..."
|
||||
chattr -i /etc/systemd/resolved.conf 2>/dev/null || true
|
||||
if grep -qE '^\s*ReadEtcHosts\s*=' /etc/systemd/resolved.conf; then
|
||||
run sed -i -E 's/^\s*ReadEtcHosts\s*=.*/ReadEtcHosts=yes/' /etc/systemd/resolved.conf
|
||||
elif grep -q '^\[Resolve\]' /etc/systemd/resolved.conf; then
|
||||
run sed -i '/^\[Resolve\]/a ReadEtcHosts=yes' /etc/systemd/resolved.conf
|
||||
else
|
||||
printf '\n[Resolve]\nReadEtcHosts=yes\n' >>/etc/systemd/resolved.conf
|
||||
fi
|
||||
chattr +i /etc/systemd/resolved.conf 2>/dev/null || true
|
||||
run systemctl restart systemd-resolved
|
||||
((FIXES_APPLIED++)) || true
|
||||
msg "resolved.conf ReadEtcHosts fixed"
|
||||
fi
|
||||
|
||||
# Fix DNSOverTLS if enabled
|
||||
local resolved_dot
|
||||
resolved_dot=$(grep -E '^\s*DNSOverTLS\s*=' /etc/systemd/resolved.conf 2>/dev/null |
|
||||
tail -1 | sed 's/.*=\s*//' | tr -d '[:space:]')
|
||||
if [[ -n "$resolved_dot" && "$resolved_dot" != "no" ]]; then
|
||||
note "Fixing resolved.conf — disabling DNSOverTLS..."
|
||||
chattr -i /etc/systemd/resolved.conf 2>/dev/null || true
|
||||
run sed -i -E 's/^\s*DNSOverTLS\s*=.*/#DNSOverTLS=no/' /etc/systemd/resolved.conf
|
||||
chattr +i /etc/systemd/resolved.conf 2>/dev/null || true
|
||||
run systemctl restart systemd-resolved
|
||||
((FIXES_APPLIED++)) || true
|
||||
msg "resolved.conf DNSOverTLS disabled"
|
||||
fi
|
||||
|
||||
# Remove drop-in overrides
|
||||
if [[ -d /etc/systemd/resolved.conf.d ]]; then
|
||||
local dropin_fix_count
|
||||
dropin_fix_count=$(find /etc/systemd/resolved.conf.d -name '*.conf' -type f 2>/dev/null | wc -l)
|
||||
if [[ "$dropin_fix_count" -gt 0 ]]; then
|
||||
note "Removing $dropin_fix_count resolved.conf drop-in override(s)..."
|
||||
chattr -i /etc/systemd/resolved.conf.d 2>/dev/null || true
|
||||
find /etc/systemd/resolved.conf.d -name '*.conf' -type f -delete
|
||||
chattr +i /etc/systemd/resolved.conf.d 2>/dev/null || true
|
||||
run systemctl restart systemd-resolved
|
||||
((FIXES_APPLIED++)) || true
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Run hosts install first
|
||||
if [[ ! -f /etc/hosts ]] || [[ $(wc -l </etc/hosts) -lt 100 ]]; then
|
||||
note "Installing hosts file..."
|
||||
@ -579,6 +684,8 @@ check_hosts() {
|
||||
# Re-verify after fixes
|
||||
if [[ $DRY_RUN -eq 0 ]]; then
|
||||
if systemctl is-enabled hosts-guard.path &>/dev/null &&
|
||||
systemctl is-enabled nsswitch-guard.path &>/dev/null &&
|
||||
systemctl is-enabled resolved-guard.path &>/dev/null &&
|
||||
[[ -f /usr/local/sbin/enforce-hosts.sh ]] &&
|
||||
[[ -f /usr/local/share/locked-hosts ]] &&
|
||||
[[ -f /etc/pacman.d/hooks/10-unlock-etc-hosts.hook ]]; then
|
||||
|
||||
Loading…
Reference in New Issue
Block a user