Files
CoM-ghost_protocol/opsec/scripts/opsec-killswitch.sh
T

262 lines
9.4 KiB
Bash
Executable File

#!/bin/bash
# OPSEC Kill Switch — iptables rules to block non-Tor/VPN traffic
# Config-aware: reads KILLSWITCH_* settings from /etc/opsec/opsec.conf
# Usage: opsec-killswitch.sh on|off|status
if [ "$EUID" -ne 0 ]; then
echo "Please run as root"
exit 1
fi
# Source config if available
OPSEC_LIB="/usr/local/lib/opsec/opsec-lib.sh"
if [ -f "$OPSEC_LIB" ]; then
. "$OPSEC_LIB"
opsec_load_config 2>/dev/null || true
else
opsec_green() { echo -e "\033[38;5;49m[+] $*\033[0m"; }
opsec_info() { echo -e "\033[38;5;39m[~] $*\033[0m"; }
fi
# Config values with defaults
ALLOW_DHCP="${KILLSWITCH_ALLOW_DHCP:-1}"
ALLOW_OPENVPN="${KILLSWITCH_ALLOW_OPENVPN:-1}"
ALLOW_WIREGUARD="${KILLSWITCH_ALLOW_WIREGUARD:-1}"
EXTRA_PORTS="${KILLSWITCH_EXTRA_PORTS:-}"
TRANS_PORT="${TOR_TRANS_PORT:-9040}"
DNS_PORT="${TOR_DNS_PORT:-5353}"
CHAIN="GP_FW"
TOR_UID=$(id -u debian-tor 2>/dev/null || id -u tor 2>/dev/null || echo "")
KS_LOG="/run/opsec/debug.log"
mkdir -p /run/opsec 2>/dev/null || true
KS_ERR=$(mktemp /run/opsec/.ks-err.XXXXXX 2>/dev/null || mktemp /tmp/.ks-err.XXXXXX)
KS_FAIL=0
trap 'rm -f "$KS_ERR"' EXIT
ks_log() { echo "[$(date -Is)] [killswitch] $*" >> "$KS_LOG"; }
# Wrapper that logs failures and tracks failure count
ipt() {
if ! iptables "$@" 2>>"$KS_ERR"; then
ks_log "FAILED: iptables $*$(cat "$KS_ERR" 2>/dev/null)"
cat "$KS_ERR" >&2 2>/dev/null
KS_FAIL=$((KS_FAIL + 1))
: > "$KS_ERR" 2>/dev/null
return 1
fi
: > "$KS_ERR" 2>/dev/null
return 0
}
ipt6() {
if ! ip6tables "$@" 2>>"$KS_ERR"; then
ks_log "FAILED: ip6tables $*$(cat "$KS_ERR" 2>/dev/null)"
KS_FAIL=$((KS_FAIL + 1))
: > "$KS_ERR" 2>/dev/null
return 1
fi
: > "$KS_ERR" 2>/dev/null
return 0
}
ks_filter_on() {
opsec_info "Arming OPSEC kill switch (filter-only)..."
ks_log "=== KILL SWITCH FILTER-ONLY ==="
ks_log "TOR_UID=${TOR_UID:-NONE}"
ks_log "ALLOW_DHCP=${ALLOW_DHCP} ALLOW_OPENVPN=${ALLOW_OPENVPN} ALLOW_WIREGUARD=${ALLOW_WIREGUARD}"
ks_log "EXTRA_PORTS=${EXTRA_PORTS:-none}"
iptables -N "$CHAIN" 2>/dev/null || iptables -F "$CHAIN"
ipt -A "$CHAIN" -o lo -j ACCEPT
ipt -A "$CHAIN" -i lo -j ACCEPT
ipt -A "$CHAIN" -d 127.0.0.0/8 -j ACCEPT
ipt -A "$CHAIN" -m state --state ESTABLISHED,RELATED -j ACCEPT
if [ -n "$TOR_UID" ]; then
ipt -A "$CHAIN" -m owner --uid-owner "$TOR_UID" -j ACCEPT
else
ks_log "WARNING: No TOR_UID — Tor will be blocked by filter!"
fi
ipt -A "$CHAIN" -o tun+ -j ACCEPT
ipt -A "$CHAIN" -i tun+ -j ACCEPT
ipt -A "$CHAIN" -o wg+ -j ACCEPT
ipt -A "$CHAIN" -i wg+ -j ACCEPT
[ "$ALLOW_DHCP" = "1" ] && ipt -A "$CHAIN" -p udp --dport 67:68 --sport 67:68 -j ACCEPT
[ "$ALLOW_OPENVPN" = "1" ] && { ipt -A "$CHAIN" -p udp --dport 1194 -j ACCEPT; ipt -A "$CHAIN" -p tcp --dport 1194 -j ACCEPT; }
[ "$ALLOW_WIREGUARD" = "1" ] && ipt -A "$CHAIN" -p udp --dport 51820 -j ACCEPT
if [ -n "$EXTRA_PORTS" ]; then
local IFS=','
for rule in $EXTRA_PORTS; do
local proto=$(echo "$rule" | cut -d: -f1)
local port=$(echo "$rule" | cut -d: -f2)
[ -n "$proto" ] && [ -n "$port" ] && ipt -A "$CHAIN" -p "$proto" --dport "$port" -j ACCEPT
done
fi
ipt -A "$CHAIN" -j DROP
iptables -D OUTPUT -j "$CHAIN" 2>/dev/null
ipt -I OUTPUT 1 -j "$CHAIN"
# IPv6
ip6tables -N "$CHAIN" 2>/dev/null || ip6tables -F "$CHAIN"
ipt6 -A "$CHAIN" -o lo -j ACCEPT
ipt6 -A "$CHAIN" -j DROP
ip6tables -D OUTPUT -j "$CHAIN" 2>/dev/null
ipt6 -I OUTPUT 1 -j "$CHAIN"
ks_log "Filter-only lockdown active (no NAT)"
opsec_green "Kill switch filter ARMED (pre-bootstrap)"
}
ks_nat_on() {
opsec_info "Arming NAT transparent proxy..."
ks_log "=== NAT PROXY ON ==="
local NAT_CHAIN="GP_NAT"
# Remove jump before flush to prevent brief bypass window
iptables -t nat -D OUTPUT -j "$NAT_CHAIN" 2>/dev/null
iptables -t nat -N "$NAT_CHAIN" 2>/dev/null || iptables -t nat -F "$NAT_CHAIN"
# Tor UID RETURN
[ -n "$TOR_UID" ] && ipt -t nat -A "$NAT_CHAIN" -p tcp -m owner --uid-owner "$TOR_UID" -j RETURN
# VPN RETURN — BEFORE DNS redirect
ipt -t nat -A "$NAT_CHAIN" -o tun+ -j RETURN
ipt -t nat -A "$NAT_CHAIN" -o wg+ -j RETURN
# DNS redirect
ipt -t nat -A "$NAT_CHAIN" -p udp --dport 53 -j REDIRECT --to-ports "$DNS_PORT"
ipt -t nat -A "$NAT_CHAIN" -p tcp --dport 53 -j REDIRECT --to-ports "$DNS_PORT"
# Loopback RETURN
ipt -t nat -A "$NAT_CHAIN" -d 127.0.0.0/8 -j RETURN
# TCP catch-all TransPort
ipt -t nat -A "$NAT_CHAIN" -p tcp -j REDIRECT --to-ports "$TRANS_PORT"
# Install jump
ipt -t nat -I OUTPUT 1 -j "$NAT_CHAIN"
# Verify chain was populated (at least 3 rules: DNS redirect + TCP catch-all + others)
local nat_count
nat_count=$(iptables -t nat -L "$NAT_CHAIN" --line-numbers 2>/dev/null | tail -n +3 | wc -l)
ks_log "NAT chain rule count: ${nat_count}"
if [ "$nat_count" -lt 3 ]; then
ks_log "WARNING: NAT chain has only ${nat_count} rules — falling back to direct OUTPUT rules"
opsec_info "NAT chain underpopulated (${nat_count} rules) — using direct OUTPUT fallback"
# Remove the jump to the empty/broken chain
iptables -t nat -D OUTPUT -j "$NAT_CHAIN" 2>/dev/null
iptables -t nat -F "$NAT_CHAIN" 2>/dev/null
iptables -t nat -X "$NAT_CHAIN" 2>/dev/null
# Direct OUTPUT rules as fallback
[ -n "$TOR_UID" ] && iptables -t nat -A OUTPUT -p tcp -m owner --uid-owner "$TOR_UID" -j RETURN 2>/dev/null
iptables -t nat -A OUTPUT -o tun+ -j RETURN 2>/dev/null
iptables -t nat -A OUTPUT -o wg+ -j RETURN 2>/dev/null
iptables -t nat -A OUTPUT -p udp --dport 53 -j REDIRECT --to-ports "$DNS_PORT" 2>/dev/null
iptables -t nat -A OUTPUT -p tcp --dport 53 -j REDIRECT --to-ports "$DNS_PORT" 2>/dev/null
iptables -t nat -A OUTPUT -d 127.0.0.0/8 -j RETURN 2>/dev/null
iptables -t nat -A OUTPUT -p tcp -j REDIRECT --to-ports "$TRANS_PORT" 2>/dev/null
local fb_count
fb_count=$(iptables -t nat -L OUTPUT --line-numbers 2>/dev/null | tail -n +3 | wc -l)
ks_log "Fallback OUTPUT NAT rule count: ${fb_count}"
fi
if [ "$KS_FAIL" -gt 0 ]; then
ks_log "WARNING: ${KS_FAIL} iptables commands failed during NAT setup"
opsec_info "WARNING: ${KS_FAIL} iptables rule(s) failed — check /run/opsec/debug.log"
return 1
fi
ks_log "NAT transparent proxy armed"
opsec_green "Kill switch fully ARMED — Tor + NAT active"
}
ks_on() {
ks_filter_on
ks_nat_on
}
ks_off() {
opsec_info "Disarming OPSEC kill switch..."
ks_log "=== KILL SWITCH OFF ==="
# Filter chain
iptables -D OUTPUT -j "$CHAIN" 2>/dev/null
iptables -F "$CHAIN" 2>/dev/null
iptables -X "$CHAIN" 2>/dev/null
# NAT chain (config-independent teardown)
local NAT_CHAIN="GP_NAT"
iptables -t nat -D OUTPUT -j "$NAT_CHAIN" 2>/dev/null
iptables -t nat -F "$NAT_CHAIN" 2>/dev/null
iptables -t nat -X "$NAT_CHAIN" 2>/dev/null
# Clean up fallback direct OUTPUT NAT rules (if chain approach failed during arm)
# Loop-delete to catch duplicates — keeps removing until no match left
local _fb_cleaned=0
while iptables -t nat -D OUTPUT -p tcp -j REDIRECT --to-ports "$TRANS_PORT" 2>/dev/null; do _fb_cleaned=$((_fb_cleaned+1)); done
while iptables -t nat -D OUTPUT -p udp --dport 53 -j REDIRECT --to-ports "$DNS_PORT" 2>/dev/null; do _fb_cleaned=$((_fb_cleaned+1)); done
while iptables -t nat -D OUTPUT -p tcp --dport 53 -j REDIRECT --to-ports "$DNS_PORT" 2>/dev/null; do _fb_cleaned=$((_fb_cleaned+1)); done
while iptables -t nat -D OUTPUT -d 127.0.0.0/8 -j RETURN 2>/dev/null; do _fb_cleaned=$((_fb_cleaned+1)); done
while iptables -t nat -D OUTPUT -o tun+ -j RETURN 2>/dev/null; do _fb_cleaned=$((_fb_cleaned+1)); done
while iptables -t nat -D OUTPUT -o wg+ -j RETURN 2>/dev/null; do _fb_cleaned=$((_fb_cleaned+1)); done
if [ -n "$TOR_UID" ]; then
while iptables -t nat -D OUTPUT -p tcp -m owner --uid-owner "$TOR_UID" -j RETURN 2>/dev/null; do _fb_cleaned=$((_fb_cleaned+1)); done
fi
[ "$_fb_cleaned" -gt 0 ] && ks_log "Cleaned ${_fb_cleaned} fallback NAT OUTPUT rules"
# IPv6
ip6tables -D OUTPUT -j "$CHAIN" 2>/dev/null
ip6tables -F "$CHAIN" 2>/dev/null
ip6tables -X "$CHAIN" 2>/dev/null
# Flush conntrack to clear stale NAT entries
conntrack -F 2>/dev/null && ks_log "conntrack flushed" || true
ks_log "Kill switch disarmed"
opsec_green "Kill switch DISARMED — normal traffic allowed"
}
ks_status() {
if iptables -L "$CHAIN" >/dev/null 2>&1; then
opsec_green "Kill switch is ARMED"
echo ""
echo "IPv4 rules:"
iptables -L "$CHAIN" -v -n --line-numbers
echo ""
echo "NAT chain:"
if iptables -t nat -L GP_NAT >/dev/null 2>&1; then
iptables -t nat -L GP_NAT -v -n --line-numbers
else
echo " (not armed)"
fi
echo ""
echo "IPv6 rules:"
ip6tables -L "$CHAIN" -v -n --line-numbers 2>/dev/null
else
opsec_info "Kill switch is OFF"
fi
}
case "${1}" in
on) ks_on ;;
filter-on) ks_filter_on ;;
nat-on) ks_nat_on ;;
off) ks_off ;;
status) ks_status ;;
*)
echo "Usage: $(basename "$0") on|off|filter-on|nat-on|status"
exit 1
;;
esac