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

1211 lines
50 KiB
Bash
Executable File

#!/bin/bash
# opsec-mode — Master toggle for Advanced OPSEC Mode
# Usage: sudo opsec-mode on|off|status|breakglass|breakglass-off
#
# ON: Activates all subsystems (torrc, DNS, kill switch, MAC, hostname, hardening)
# OFF: Reverses everything back to standard mode (blocked on paranoid levels)
# STATUS: Shows current state of all subsystems with config values
# BREAKGLASS: Emergency override — drops kill switch temporarily
# BREAKGLASS-OFF: End break-glass early
## NOTE: Do NOT use "set -euo pipefail" — it causes silent crashes
## throughout this script when commands like grep, systemctl, etc.
## return non-zero. Use explicit error handling instead.
if [ "$EUID" -ne 0 ]; then
echo "[!] Please run as root: sudo opsec-mode on|off|status"
exit 1
fi
# ─── EARLY ARG PARSE (needed before lock decision) ───────────────────────────
_VERBOSE_EARLY=false
_CMD_EARLY=""
for _a in "$@"; do
case "$_a" in
-v|--verbose) _VERBOSE_EARLY=true ;;
*) [ -z "$_CMD_EARLY" ] && _CMD_EARLY="$_a" ;;
esac
done
echo "[*] opsec-mode: ${_CMD_EARLY:-} (pid $$)"
mkdir -p /run/opsec 2>/dev/null || true
LOCK_FILE="/run/opsec/mode.lock"
PID_FILE="/run/opsec/mode.pid"
# ─── MUTEX: PID-FILE BASED (no inherited fd leaks) ──────────────────────────
# Previous approach used flock(fd 9) but child processes (opsec-harden.sh etc.)
# inherit the fd and hold the lock after the parent exits, causing "Another
# instance running" on the next invocation. Fix: use flock only for the brief
# atomic check, then immediately close the fd. The PID file guards ongoing
# exclusivity, and stale PIDs are detected automatically.
_acquire_lock() {
exec 9>"$LOCK_FILE"
if flock -n 9; then
# Got it clean
echo $$ > "$PID_FILE"
exec 9>&- # Close fd 9 immediately — children won't inherit it
return 0
fi
exec 9>&- # Close our attempt either way
# flock failed — check if the holder is still a real opsec-mode process
local stale_pid
stale_pid=$(cat "$PID_FILE" 2>/dev/null || echo "")
if [ -n "$stale_pid" ] && kill -0 "$stale_pid" 2>/dev/null; then
local stale_cmd
stale_cmd=$(cat "/proc/${stale_pid}/comm" 2>/dev/null || echo "")
if echo "$stale_cmd" | grep -q "opsec-mode"; then
echo "[!] Another opsec-mode instance is running (PID ${stale_pid})"
return 1
fi
fi
# Stale lock: previous opsec-mode exited but a child inherited the fd,
# or the process crashed. Force-reclaim.
echo "[*] Reclaiming stale lock (previous holder gone or inherited by child)"
rm -f "$LOCK_FILE"
exec 9>"$LOCK_FILE"
if flock -n 9; then
echo $$ > "$PID_FILE"
exec 9>&-
return 0
fi
exec 9>&-
echo "[!] Cannot acquire lock even after cleanup"
return 1
}
# ─── DISPATCH READ-ONLY COMMANDS BEFORE LOCK ─────────────────────────────────
# diag and status are read-only — they must not be blocked by a running mode_on
_OWNS_LOCK=false
case "${_CMD_EARLY:-}" in
diag|status) ;; # handled after full init below, but skip lock
*) _acquire_lock || exit 1; _OWNS_LOCK=true ;;
esac
# Only clean up PID file if WE own the lock (diag/status must not delete mode_on's PID)
trap '[ -n "${TAIL_PID:-}" ] && kill "$TAIL_PID" 2>/dev/null; [ "$_OWNS_LOCK" = "true" ] && rm -f "$PID_FILE"' EXIT
STATE_FILE="/var/run/opsec-advanced.enabled"
BOOT_MARKER="/etc/opsec/boot-advanced.enabled"
TORRC_DEFAULT="/etc/tor/torrc-default"
RESOLV_BACKUP="/etc/resolv.conf.backup"
OPSEC_LIB="/usr/local/lib/opsec/opsec-lib.sh"
BREAKGLASS_STATE="/var/run/opsec-breakglass.active"
BREAKGLASS_TIMEOUT="${BREAKGLASS_TIMEOUT:-900}" # 15 minutes default
PREFLIGHT_LOG="/run/opsec/preflight.log"
DEBUG_LOG="/run/opsec/debug.log"
BOOTSTRAP_STOP="/var/run/opsec-bootstrap-stop"
NM_MAC_CONF="/etc/NetworkManager/conf.d/opsec-mac-random.conf"
# Debug logger — appends timestamped entries (prints to terminal in verbose mode)
opsec_debug() {
local msg="[$(date -Is)] $*"
echo "$msg" >> "$DEBUG_LOG"
chmod 600 "$DEBUG_LOG" 2>/dev/null
if [ "$VERBOSE" = "true" ]; then
echo -e "\033[38;5;240m DBG: $*\033[0m"
fi
}
# ─── SOURCE SHARED LIBRARY ────────────────────────────────────────────────────
if [ -f "$OPSEC_LIB" ]; then
. "$OPSEC_LIB"
opsec_load_config 2>/dev/null || true
_HAS_LIB=true
else
# Fallback: hardcoded behavior if lib not yet deployed
_HAS_LIB=false
opsec_green() { echo -e "\033[38;5;49m[+] $*\033[0m"; }
opsec_red() { echo -e "\033[38;5;196m[-] $*\033[0m"; }
opsec_yellow() { echo -e "\033[38;5;214m[*] $*\033[0m"; }
opsec_info() { echo -e "\033[38;5;39m[~] $*\033[0m"; }
opsec_cyan() { echo -e "\033[38;5;51m[>] $*\033[0m"; }
opsec_mag() { echo -e "\033[38;5;201m[*] $*\033[0m"; }
opsec_dim() { echo -e "\033[38;5;244m $*\033[0m"; }
opsec_hdr() { echo -e "\033[38;5;51m━━━ \033[38;5;201m$*\033[38;5;51m ━━━\033[0m"; }
fi
# ─── HELPERS ───────────────────────────────────────────────────────────────────
get_level_type() {
echo "${LEVEL_TYPE:-standard}"
}
is_paranoid() {
[ "$(get_level_type)" = "paranoid" ]
}
is_cloud_level() {
local level="${DEPLOYMENT_LEVEL:-bare-metal-standard}"
case "$level" in
cloud-*) return 0 ;;
*) return 1 ;;
esac
}
# ─── BASE MODE (standard privacy hardening only) ─────────────────────────────
mode_base() {
local profile="${PROFILE_NAME:-default}"
echo ""
opsec_hdr "APPLYING BASE PRIVACY HARDENING"
opsec_dim "Profile: ${profile}"
echo ""
# 1. Disable IPv6
opsec_info "Step 1/5: Disabling IPv6..."
local ipv6_all
ipv6_all=$(sysctl -n net.ipv6.conf.all.disable_ipv6 2>/dev/null || echo "0")
if [ "$ipv6_all" = "1" ]; then
opsec_green "IPv6 already disabled"
else
sysctl -w net.ipv6.conf.all.disable_ipv6=1 >/dev/null
sysctl -w net.ipv6.conf.default.disable_ipv6=1 >/dev/null
opsec_green "IPv6 disabled"
fi
# 2. Set privacy DNS (not Tor — use BASE_DNS)
opsec_info "Step 2/5: Setting privacy DNS..."
local base_dns="${BASE_DNS:-quad9}"
chattr -i /etc/resolv.conf 2>/dev/null || true
if [ ! -f "$RESOLV_BACKUP" ]; then
cp /etc/resolv.conf "$RESOLV_BACKUP" 2>/dev/null || true
chmod 600 "$RESOLV_BACKUP" 2>/dev/null || true
fi
case "$base_dns" in
quad9)
cat > /etc/resolv.conf << 'EOF'
nameserver 9.9.9.9
nameserver 149.112.112.112
EOF
;;
cloudflare)
cat > /etc/resolv.conf << 'EOF'
nameserver 1.1.1.1
nameserver 1.0.0.1
EOF
;;
*)
cat > /etc/resolv.conf << 'EOF'
nameserver 9.9.9.9
nameserver 149.112.112.112
EOF
;;
esac
opsec_green "DNS set to ${base_dns}"
# 3. Randomize MAC (bare-metal only) + reconnect network after
if ! is_cloud_level && [ "${BASE_MAC_RANDOMIZE:-1}" = "1" ]; then
opsec_info "Step 3/5: Randomizing MAC addresses..."
if [ -x /usr/local/bin/randomize-mac.sh ]; then
/usr/local/bin/randomize-mac.sh
# Reconnect WiFi to get new DHCP lease with new MAC
local wifi_if
wifi_if=$(nmcli -t -f DEVICE,TYPE device status 2>/dev/null | grep ':wifi$' | head -1 | cut -d: -f1)
if [ -n "$wifi_if" ]; then
opsec_dim "Reconnecting ${wifi_if} for new DHCP lease..."
nmcli device disconnect "$wifi_if" 2>/dev/null || true
sleep 1
nmcli device connect "$wifi_if" 2>/dev/null || true
sleep 3
fi
else
opsec_yellow "randomize-mac.sh not found — skipping"
fi
else
opsec_dim "Step 3/5: MAC randomization skipped (cloud or disabled)"
fi
# 4. Disable swap + core dumps
opsec_info "Step 4/5: Hardening swap and core dumps..."
if [ "${HARDEN_SWAP:-1}" = "1" ]; then
swapoff -a 2>/dev/null || true
opsec_green "Swap disabled"
fi
if [ "${HARDEN_CORE_DUMPS:-1}" = "1" ]; then
sysctl -w kernel.core_pattern='|/bin/false' >/dev/null 2>&1 || true
opsec_green "Core dumps disabled"
fi
# 5. Apply additional system hardening (base-only: skip ghost-mode features like USB block)
opsec_info "Step 5/5: Applying system hardening..."
if [ -x /usr/local/bin/opsec-harden.sh ]; then
OPSEC_BASE_ONLY=1 /usr/local/bin/opsec-harden.sh apply
else
opsec_yellow "opsec-harden.sh not found — skipping"
fi
echo ""
opsec_hdr "BASE PRIVACY MODE: ACTIVE"
echo ""
opsec_dim "DNS: ${base_dns}"
opsec_dim "IPv6: disabled"
opsec_dim "Swap: disabled"
opsec_dim "Core dumps: disabled"
if ! is_cloud_level; then
opsec_dim "MAC: randomized"
fi
echo ""
opsec_dim "Ghost mode: sudo opsec-mode on"
echo ""
# Run base preflight
if [ -x /usr/local/bin/opsec-preflight.sh ]; then
/usr/local/bin/opsec-preflight.sh --base --enforce || {
opsec_red "Base preflight FAILED — check /run/opsec/preflight.log"
}
fi
}
# ─── ON ──────────────────────────────────────────────────────────────────────
mode_on() {
local rotation="${TOR_CIRCUIT_ROTATION:-30}"
local blacklist="${TOR_BLACKLIST-us,gb,ca,au,nz}"
local socks_port="${TOR_SOCKS_PORT:-9050}"
local dns_port="${TOR_DNS_PORT:-5353}"
local profile="${PROFILE_NAME:-default}"
echo ""
opsec_hdr "ACTIVATING GHOST MODE"
opsec_dim "Profile: ${profile}"
echo ""
opsec_debug "=========================================="
opsec_debug "=== MODE ON START ==="
opsec_debug "=========================================="
# Clear any stale stop signal from previous runs
rm -f "$BOOTSTRAP_STOP"
# 0. Disarm stale KS, then immediately arm filter-only lockdown
if iptables -L GP_FW >/dev/null 2>&1; then
opsec_debug "Step 0: Disarming stale kill switch"
/usr/local/bin/opsec-killswitch.sh off 2>/dev/null
fi
opsec_debug "Step 0.5: Arming filter-only lockdown (pre-bootstrap)"
/usr/local/bin/opsec-killswitch.sh filter-on
opsec_debug " Filter lockdown active — only Tor/VPN/DHCP allowed"
# 1. Disable IPv6
opsec_info "Step 1/10: Disabling IPv6..."
opsec_debug "Step 1: Disabling IPv6"
sysctl -w net.ipv6.conf.all.disable_ipv6=1 >/dev/null 2>&1 || true
sysctl -w net.ipv6.conf.default.disable_ipv6=1 >/dev/null 2>&1 || true
opsec_green "IPv6 disabled"
# 2. Randomize MAC via NetworkManager (prevents NM from reverting macchanger)
opsec_info "Step 2/10: Randomizing MAC addresses..."
opsec_debug "Step 2: MAC randomization via NetworkManager"
if ! is_cloud_level; then
local wifi_if
wifi_if=$(nmcli -t -f DEVICE,TYPE device status 2>/dev/null | grep ':wifi$' | head -1 | cut -d: -f1)
# Configure NetworkManager to use random MACs natively
mkdir -p /etc/NetworkManager/conf.d
cat > "$NM_MAC_CONF" << 'NMEOF'
# OPSEC: random MAC on every connection (managed by opsec-mode)
[device]
wifi.scan-rand-mac-address=yes
[connection]
wifi.cloned-mac-address=random
ethernet.cloned-mac-address=random
NMEOF
opsec_debug " NM MAC config written to ${NM_MAC_CONF}"
# Reload NM config and reconnect to apply random MAC
nmcli general reload 2>/dev/null || true
if [ -n "$wifi_if" ]; then
opsec_debug " Reconnecting WiFi: ${wifi_if}"
local old_mac
old_mac=$(ip link show "$wifi_if" 2>/dev/null | awk '/ether/ {print $2}')
opsec_debug " MAC before: ${old_mac}"
nmcli device disconnect "$wifi_if" 2>/dev/null || true
sleep 2
nmcli device connect "$wifi_if" 2>/dev/null || true
# Wait for connection to establish
for w in $(seq 1 15); do
if nmcli -t -f DEVICE,STATE device status 2>/dev/null | grep "^${wifi_if}:connected" >/dev/null; then
opsec_debug " WiFi reconnected at second ${w}"
break
fi
sleep 1
done
# Wait for actual IP routing (NM "connected" != routed)
opsec_debug " Waiting for network route to establish..."
for r in $(seq 1 10); do
if ip route show default 2>/dev/null | grep -q .; then
opsec_debug " Default route available at second ${r}"
break
fi
sleep 1
done
sleep 2 # Brief settle for ARP/DHCP
fi
local new_mac
new_mac=$(ip link show "$wifi_if" 2>/dev/null | awk '/ether/ {print $2}')
opsec_debug " MAC after: ${new_mac}"
if [ "$old_mac" != "$new_mac" ] 2>/dev/null; then
opsec_green "MAC randomized: ${new_mac}"
else
opsec_yellow "MAC unchanged — NM may need restart"
opsec_debug " WARNING: MAC did not change (${old_mac} -> ${new_mac})"
fi
else
opsec_dim "Skipped (cloud deployment)"
fi
# 3. Randomize hostname
opsec_info "Step 3/10: Randomizing hostname..."
opsec_debug "Step 3: Hostname randomization"
if ! is_cloud_level; then
if [ ! -f /run/opsec/hostname.original ]; then
hostname > /run/opsec/hostname.original
chmod 600 /run/opsec/hostname.original
opsec_dim "Saved original hostname: $(cat /run/opsec/hostname.original)"
opsec_debug " Saved original to tmpfs: $(hostname)"
fi
/usr/local/bin/opsec-hostname-randomize.sh 2>&1 | tee -a "$DEBUG_LOG" || true
opsec_debug " Hostname after randomize: $(hostname)"
else
opsec_dim "Skipped (cloud deployment)"
fi
# 4. Set state marker EARLY — needed for chattr +i in DNS step
touch "$STATE_FILE"
opsec_debug "Step 4: State marker set early (for DNS lock)"
# 5. Deploy hardened torrc + clear stale Tor state + restart Tor
opsec_info "Step 5/10: Deploying hardened Tor configuration..."
opsec_debug "Step 5: Deploying torrc (HAS_LIB=${_HAS_LIB})"
# Clear Tor caches only if TOR_CLEAR_CACHE=1 (preserving guard state)
opsec_debug " Cache clear: TOR_CLEAR_CACHE=${TOR_CLEAR_CACHE:-0}"
systemctl stop tor 2>/dev/null || true
if [ "${TOR_CLEAR_CACHE:-0}" = "1" ]; then
opsec_debug " Clearing Tor descriptor caches"
rm -f /var/lib/tor/cached-descriptors /var/lib/tor/cached-descriptors.new \
/var/lib/tor/cached-microdesc* /var/lib/tor/cached-consensus \
/var/lib/tor/cached-certs 2>/dev/null || true
else
opsec_debug " Keeping Tor descriptor caches (faster bootstrap)"
fi
if [ "$_HAS_LIB" = "true" ]; then
opsec_generate_torrc
opsec_green "Torrc generated from config (blacklist: ${blacklist})"
opsec_debug "Torrc generated via lib (blacklist: ${blacklist})"
else
local torrc_opsec="/etc/tor/torrc-opsec"
if [ -f "$torrc_opsec" ]; then
cp "$torrc_opsec" /etc/tor/torrc
fi
fi
opsec_debug "Current torrc contents:"
cat /etc/tor/torrc >> "$DEBUG_LOG" 2>&1
opsec_debug "---"
# Truncate Tor log so bootstrap check doesn't read stale entries
mkdir -p /run/tor && chown debian-tor:debian-tor /run/tor 2>/dev/null || true
: > /run/tor/notices.log
chown debian-tor:debian-tor /run/tor/notices.log 2>/dev/null || true
opsec_debug "Restarting Tor service..."
systemctl restart tor 2>&1 | tee -a "$DEBUG_LOG"
opsec_debug "systemctl restart tor exit code: $?"
opsec_debug "Tor service status: $(systemctl is-active tor 2>&1)"
# 6. Configure DNS (lock it before bootstrap wait)
opsec_info "Step 6/10: Configuring DNS..."
opsec_debug "Step 6: Configuring DNS (DNS_MODE=${DNS_MODE:-tor})"
if [ ! -f "$RESOLV_BACKUP" ]; then
chattr -i /etc/resolv.conf 2>/dev/null || true
cp /etc/resolv.conf "$RESOLV_BACKUP"
chmod 600 "$RESOLV_BACKUP" 2>/dev/null || true
opsec_debug "Backed up resolv.conf (mode 600)"
fi
if [ "$_HAS_LIB" = "true" ]; then
opsec_generate_resolv
opsec_green "DNS set to mode: ${DNS_MODE:-tor}"
else
chattr -i /etc/resolv.conf 2>/dev/null || true
echo "nameserver 127.0.0.1" > /etc/resolv.conf
chattr +i /etc/resolv.conf
opsec_green "DNS locked to 127.0.0.1 (Tor DNSPort ${dns_port})"
fi
# Force lock DNS regardless — state file is already set
chattr +i /etc/resolv.conf 2>/dev/null || true
# Verify immutable flag actually took hold
if ! lsattr /etc/resolv.conf 2>/dev/null | grep -q 'i'; then
opsec_red "WARNING: chattr +i failed on resolv.conf — DNS lock may not hold"
opsec_debug " CRITICAL: chattr +i verification FAILED — filesystem may not support extended attributes"
else
opsec_debug " chattr +i verified on resolv.conf"
fi
opsec_debug "resolv.conf now: $(cat /etc/resolv.conf 2>/dev/null | tr '\n' ' ')"
# Lock NetworkManager out of DNS management
mkdir -p /etc/NetworkManager/conf.d
cat > /etc/NetworkManager/conf.d/opsec-dns-lock.conf << 'DNSEOF'
[main]
dns=none
DNSEOF
nmcli general reload 2>/dev/null || true
opsec_debug " NM dns=none lock installed"
# 7. Wait for Tor bootstrap (up to 180s — first guard may timeout)
opsec_yellow "Waiting for Tor to fully bootstrap (100%)..."
opsec_debug "Step 7: Tor bootstrap wait (180s max)"
TAIL_PID=""
if [ "$VERBOSE" = "true" ]; then
tail -f /run/tor/notices.log 2>/dev/null &
TAIL_PID=$!
fi
local tor_ready=false
for i in $(seq 1 180); do
# Check if mode_off signaled us to stop
if [ -f "$BOOTSTRAP_STOP" ]; then
opsec_yellow "Bootstrap wait aborted (mode_off requested)"
opsec_debug "Bootstrap loop aborted by stop signal at ${i}s"
rm -f "$BOOTSTRAP_STOP"
break
fi
local boot_pct
boot_pct=$(grep "Bootstrapped" /run/tor/notices.log 2>/dev/null | tail -1 | grep -oP '\d+(?=%)' || true)
if [ "$boot_pct" = "100" ] && ss -tln | grep -q ":${socks_port} "; then
opsec_green "Tor fully bootstrapped (100%) at ${i}s"
opsec_debug "Tor bootstrapped 100% at second ${i}"
tor_ready=true
break
fi
if [ $((i % 5)) -eq 0 ] || [ "$i" -eq 1 ]; then
opsec_dim " Bootstrap: ${boot_pct:-0}% (${i}s)"
opsec_debug "Tor wait ${i}s: bootstrap=${boot_pct:-0}%"
fi
sleep 1
if [ "$i" -eq 180 ]; then
opsec_red "Tor bootstrap timeout after 180s (at ${boot_pct:-0}%)"
opsec_debug "TIMEOUT: Tor at ${boot_pct:-0}% after 180s"
opsec_debug "Last 10 Tor log lines:"
tail -10 /run/tor/notices.log >> "$DEBUG_LOG" 2>&1
fi
done
[ -n "${TAIL_PID:-}" ] && kill "$TAIL_PID" 2>/dev/null
# 8. Add NAT transparent proxy AFTER Tor is ready
opsec_info "Step 8/10: Arming NAT transparent proxy..."
opsec_debug "Step 8: NAT proxy (tor_ready=${tor_ready})"
local ks_exit
if [ "$tor_ready" = "true" ]; then
/usr/local/bin/opsec-killswitch.sh nat-on
ks_exit=$?
opsec_debug " NAT proxy armed with Tor at 100% (exit=${ks_exit})"
else
# If mode_off aborted us, don't arm NAT — teardown is already running
if [ ! -f "$STATE_FILE" ]; then
opsec_yellow "Bootstrap aborted by mode_off — skipping NAT arm"
opsec_debug " Skipping NAT — state file gone (mode_off in progress)"
return 0
fi
opsec_yellow "WARNING: Tor not fully bootstrapped — arming NAT anyway"
/usr/local/bin/opsec-killswitch.sh nat-on
ks_exit=$?
opsec_debug " NAT proxy armed with Tor NOT ready (exit=${ks_exit})"
fi
opsec_debug " Kill switch exit code: ${ks_exit}"
# Flush conntrack so stale entries don't bypass NAT redirect
conntrack -F 2>/dev/null && opsec_debug " conntrack table flushed" || opsec_debug " conntrack flush skipped (not available)"
opsec_debug " Filter rule count: $(iptables -L GP_FW --line-numbers 2>/dev/null | tail -n +3 | wc -l)"
local _nat_count
_nat_count=$(iptables -t nat -L GP_NAT --line-numbers 2>/dev/null | tail -n +3 | wc -l)
opsec_debug " NAT chain (GP_NAT) rule count: ${_nat_count}"
opsec_debug " NAT OUTPUT jump rules: $(iptables -t nat -L OUTPUT --line-numbers 2>/dev/null | tail -n +3 | wc -l)"
# TCP listeners (TransPort 9040, SOCKSPort 9050)
local _tcp_ports
_tcp_ports=$(ss -tlnp 2>/dev/null | grep -E ":(9040|9050|5353)" || echo " NONE")
opsec_debug " Tor TCP ports listening:"
echo "$_tcp_ports" >> "$DEBUG_LOG" 2>&1
if [ "$VERBOSE" = "true" ]; then echo -e "\033[38;5;240m DBG: TCP: ${_tcp_ports}\033[0m"; fi
# UDP listener (DNSPort 5353)
local _udp_ports
_udp_ports=$(ss -ulnp 2>/dev/null | grep -E ":5353" || echo " NONE")
opsec_debug " Tor UDP ports listening:"
echo "$_udp_ports" >> "$DEBUG_LOG" 2>&1
if [ "$VERBOSE" = "true" ]; then echo -e "\033[38;5;240m DBG: UDP: ${_udp_ports}\033[0m"; fi
# 9. Apply system hardening + boot services
opsec_info "Step 9/10: Applying system hardening..."
opsec_debug "Step 9: Hardening + boot services"
if [ -x /usr/local/bin/opsec-harden.sh ]; then
/usr/local/bin/opsec-harden.sh apply 2>&1 | tee -a "$DEBUG_LOG" || true
else
opsec_yellow "opsec-harden.sh not found — skipping"
fi
systemctl enable opsec-mac-randomize.service 2>/dev/null && opsec_green "MAC randomize on boot: enabled" || opsec_yellow "MAC service not found"
systemctl enable opsec-hostname-randomize.service 2>/dev/null && opsec_green "Hostname randomize on boot: enabled" || opsec_yellow "Hostname service not found"
systemctl enable opsec-killswitch.service 2>/dev/null && opsec_green "Kill switch on boot: enabled" || opsec_yellow "Kill switch service not found"
if [ -f "$BOOT_MARKER" ]; then
systemctl enable opsec-boot-advanced.service 2>/dev/null || true
opsec_green "Boot-into-advanced: enabled"
fi
# 10. Start sentry daemon if available
opsec_info "Step 10/10: Starting sentry..."
opsec_debug "Step 10: Sentry + traffic blend"
if [ -x /usr/local/bin/opsec-sentry.sh ]; then
/usr/local/bin/opsec-sentry.sh start 2>/dev/null && opsec_green "Sentry daemon started" || opsec_yellow "Sentry start failed"
else
opsec_dim "Sentry not installed — skipping"
fi
if [ -x /usr/local/bin/opsec-traffic-blend.sh ]; then
/usr/local/bin/opsec-traffic-blend.sh start 2>/dev/null && opsec_green "Traffic blending started" || opsec_dim "Traffic blend not started"
fi
# ─── EXIT GATE: Verify all subsystems before declaring active ────────────
_final_ok=true
if ! systemctl is-active tor >/dev/null 2>&1; then
opsec_red "CRITICAL: Tor is NOT running"; _final_ok=false
fi
if ! iptables -C OUTPUT -j GP_FW 2>/dev/null; then
opsec_red "CRITICAL: Kill switch is NOT armed"; _final_ok=false
fi
# Check NAT rules exist (either in chain or direct OUTPUT fallback)
local _gate_nat_count=0
if iptables -t nat -L GP_NAT >/dev/null 2>&1; then
_gate_nat_count=$(iptables -t nat -L GP_NAT --line-numbers 2>/dev/null | tail -n +3 | wc -l)
fi
if [ "$_gate_nat_count" -lt 3 ]; then
# Check if fallback direct OUTPUT rules are present
local _gate_output_nat
_gate_output_nat=$(iptables -t nat -L OUTPUT --line-numbers 2>/dev/null | tail -n +3 | wc -l)
if [ "$_gate_output_nat" -lt 3 ]; then
opsec_red "CRITICAL: NAT transparent proxy NOT armed (chain=${_gate_nat_count}, output=${_gate_output_nat} rules)"
_final_ok=false
else
opsec_debug " NAT using direct OUTPUT fallback (${_gate_output_nat} rules)"
fi
fi
if ! ss -tln | grep -q ":${socks_port} "; then
opsec_red "CRITICAL: SOCKS port ${socks_port} NOT listening"; _final_ok=false
fi
# Check TransPort (9040) and DNSPort (5353) are listening
if ! ss -tln | grep -q ":9040 "; then
opsec_red "CRITICAL: TransPort 9040 NOT listening"; _final_ok=false
fi
if ! ss -uln | grep -q ":5353 "; then
opsec_red "CRITICAL: DNSPort 5353 NOT listening (UDP)"; _final_ok=false
fi
# Live connectivity test — actually verify traffic flows through Tor
if [ "$_final_ok" = "true" ]; then
opsec_info "Testing transparent proxy connectivity..."
local _tp_result
_tp_result=$(curl -4 -s --max-time 15 https://check.torproject.org/api/ip 2>&1)
opsec_debug " TransProxy test result: ${_tp_result}"
if echo "$_tp_result" | grep -q '"IsTor":true'; then
opsec_green "Transparent proxy VERIFIED — traffic routed through Tor"
elif echo "$_tp_result" | grep -q '"IsTor":false'; then
opsec_red "CRITICAL: Transparent proxy LEAKING — traffic NOT through Tor"
_final_ok=false
else
opsec_yellow "WARNING: Transparent proxy test inconclusive (timeout or network error)"
opsec_debug " curl output: ${_tp_result}"
# Don't fail — services are running, might just be slow
fi
fi
if [ "$_final_ok" = "false" ]; then
opsec_red "=========================================="
opsec_red " GHOST MODE ACTIVATION FAILED"
opsec_red " YOU ARE NOT PROTECTED"
opsec_red "=========================================="
opsec_red " Tearing down partial activation..."
# Clean teardown — don't leave zombie armed state
/usr/local/bin/opsec-killswitch.sh off 2>/dev/null
rm -f /etc/NetworkManager/conf.d/opsec-dns-lock.conf
nmcli general reload 2>/dev/null || true
chattr -i /etc/resolv.conf 2>/dev/null || true
rm -f "$STATE_FILE"
opsec_red " Recovery: sudo opsec-mode off (full cleanup)"
opsec_red "=========================================="
exit 1
fi
echo ""
opsec_hdr "GHOST MODE: ACTIVE"
echo ""
opsec_dim "Profile: ${profile}"
opsec_dim "Kill switch: armed (Tor/VPN only)"
opsec_dim "DNS: ${DNS_MODE:-tor}"
opsec_dim "Exit blacklist: ${blacklist}"
opsec_dim "Circuit rotation: ${rotation}s"
if ! is_cloud_level; then
opsec_dim "MAC addresses: randomized"
opsec_dim "Hostname: randomized"
fi
echo ""
opsec_dim "Test: curl --socks5-hostname 127.0.0.1:${socks_port} https://ifconfig.me"
opsec_dim "Off: sudo opsec-mode off"
echo ""
# Run full preflight verification
if [ -x /usr/local/bin/opsec-preflight.sh ]; then
opsec_info "Running post-activation preflight..."
/usr/local/bin/opsec-preflight.sh --full --enforce || {
opsec_red "Preflight FAILED — kill switch stays armed (safe default)"
opsec_red "Check failures: opsec-preflight"
echo "[$(date -Is)] PREFLIGHT FAIL after mode_on" >> "$PREFLIGHT_LOG"
}
fi
# ─── FINAL STATE SUMMARY ─────────────────────────────────────────────────
opsec_debug "=========================================="
opsec_debug "=== MODE ON FINAL STATE ==="
opsec_debug "=========================================="
opsec_debug "Hostname: $(hostname)"
opsec_debug "DNS: $(cat /etc/resolv.conf 2>/dev/null | tr '\n' ' ')"
opsec_debug "IPv6 disabled: $(sysctl -n net.ipv6.conf.all.disable_ipv6 2>/dev/null)"
opsec_debug "Tor status: $(systemctl is-active tor 2>&1)"
opsec_debug "Tor bootstrap: $(grep 'Bootstrapped' /run/tor/notices.log 2>/dev/null | tail -1 || echo NONE)"
opsec_debug "Tor TCP ports:"
ss -tlnp 2>/dev/null | grep -E ":(9040|9050|5353)" >> "$DEBUG_LOG" 2>&1
opsec_debug "Tor UDP ports:"
ss -ulnp 2>/dev/null | grep -E ":5353" >> "$DEBUG_LOG" 2>&1
opsec_debug "Kill switch chain rule count: $(iptables -L GP_FW --line-numbers 2>/dev/null | tail -n +3 | wc -l)"
opsec_debug "NAT chain (GP_NAT) rule count: $(iptables -t nat -L GP_NAT --line-numbers 2>/dev/null | tail -n +3 | wc -l)"
opsec_debug "State file: $([ -f "$STATE_FILE" ] && echo EXISTS || echo MISSING)"
opsec_debug "Network interfaces:"
ip -br addr 2>/dev/null >> "$DEBUG_LOG" 2>&1
opsec_debug "Default route: $(ip route show default 2>/dev/null | head -1)"
opsec_debug "=========================================="
opsec_debug "=== MODE ON COMPLETE ==="
opsec_debug "=========================================="
}
# ─── OFF ─────────────────────────────────────────────────────────────────────
mode_off() {
# Block mode_off on paranoid levels
if is_paranoid; then
echo ""
opsec_red "BLOCKED: Cannot disable ghost mode on paranoid level."
opsec_red "Current level: ${DEPLOYMENT_LEVEL:-unknown} (LEVEL_TYPE=paranoid)"
opsec_yellow "To lower security, change deployment level first:"
opsec_dim " sudo opsec-config.sh --level apply bare-metal-standard"
opsec_dim " sudo opsec-config.sh --level apply cloud-normal"
echo ""
exit 1
fi
echo ""
opsec_hdr "DEACTIVATING GHOST MODE"
opsec_debug "=== MODE OFF START ==="
echo ""
# Interrupt safety: if mode_off is killed mid-teardown (after kill switch is down),
# re-arm the kill switch to avoid leaving the system exposed
_mode_off_rearm() {
echo ""
opsec_red "INTERRUPTED — re-arming kill switch to prevent exposure"
/usr/local/bin/opsec-killswitch.sh on 2>/dev/null
opsec_debug "mode_off interrupted — kill switch re-armed"
exit 1
}
trap '_mode_off_rearm' INT TERM
# Signal any running mode_on bootstrap loop to stop immediately
touch "$BOOTSTRAP_STOP"
opsec_debug " Stop signal sent to bootstrap loop"
# Stop sentry if running
opsec_debug "Stopping sentry..."
if [ -x /usr/local/bin/opsec-sentry.sh ]; then
opsec_info "Stopping sentry..."
/usr/local/bin/opsec-sentry.sh stop 2>/dev/null || true
fi
# Stop traffic blending if running
if [ -x /usr/local/bin/opsec-traffic-blend.sh ]; then
/usr/local/bin/opsec-traffic-blend.sh stop 2>/dev/null || true
fi
# Clear break-glass if active
rm -f "$BREAKGLASS_STATE"
# 1. Disarm kill switch
opsec_info "Disarming kill switch..."
opsec_debug "Step 1: Disarming kill switch"
/usr/local/bin/opsec-killswitch.sh off
local ks_off_exit=$?
opsec_debug " Kill switch off exit code: ${ks_off_exit}"
opsec_debug " iptables OUTPUT after disarm:"
iptables -L OUTPUT -n --line-numbers >> "$DEBUG_LOG" 2>&1
opsec_debug " NAT OUTPUT after disarm:"
iptables -t nat -L OUTPUT -n --line-numbers >> "$DEBUG_LOG" 2>&1
# 2. Restore DNS FIRST (before NM lock removal to prevent race)
opsec_info "Restoring DNS to base privacy resolver..."
opsec_debug "Step 2: Restoring DNS"
opsec_debug " resolv.conf before: $(cat /etc/resolv.conf 2>/dev/null | tr '\n' ' ')"
chattr -i /etc/resolv.conf 2>/dev/null || true
local base_dns="${BASE_DNS:-quad9}"
case "$base_dns" in
quad9)
cat > /etc/resolv.conf << 'EOF'
nameserver 9.9.9.9
nameserver 149.112.112.112
EOF
;;
cloudflare)
cat > /etc/resolv.conf << 'EOF'
nameserver 1.1.1.1
nameserver 1.0.0.1
EOF
;;
*)
if [ -f "$RESOLV_BACKUP" ]; then
cp "$RESOLV_BACKUP" /etc/resolv.conf
else
cat > /etc/resolv.conf << 'EOF'
nameserver 9.9.9.9
nameserver 149.112.112.112
EOF
fi
;;
esac
# Secure-delete ISP DNS backup (contains pre-opsec resolver config)
if [ -f "$RESOLV_BACKUP" ]; then
shred -fuz -n 1 "$RESOLV_BACKUP" 2>/dev/null || rm -f "$RESOLV_BACKUP"
opsec_debug " resolv.conf.backup securely deleted"
fi
opsec_green "DNS restored to ${base_dns}"
opsec_debug " resolv.conf after: $(cat /etc/resolv.conf 2>/dev/null | tr '\n' ' ')"
# NOW unlock NM DNS management (after DNS is written and settled)
rm -f /etc/NetworkManager/conf.d/opsec-dns-lock.conf
nmcli general reload 2>/dev/null || true
opsec_debug " NM dns=none lock removed (after DNS restore)"
# 3. Restore stock torrc and stop Tor
opsec_info "Restoring default Tor configuration..."
opsec_debug "Step 3: Stopping Tor"
opsec_debug " Tor status before: $(systemctl is-active tor 2>&1)"
if [ -f "$TORRC_DEFAULT" ]; then
cp "$TORRC_DEFAULT" /etc/tor/torrc
opsec_debug " Restored torrc from ${TORRC_DEFAULT}"
else
opsec_debug " WARNING: ${TORRC_DEFAULT} not found"
fi
systemctl stop tor 2>/dev/null || true
opsec_debug " Tor status after: $(systemctl is-active tor 2>&1)"
opsec_green "Tor stopped, default torrc restored"
# 4. Revert system hardening
opsec_info "Reverting system hardening..."
opsec_debug "Step 4: Reverting hardening"
if [ -x /usr/local/bin/opsec-harden.sh ]; then
/usr/local/bin/opsec-harden.sh revert 2>&1 | tee -a "$DEBUG_LOG" || true
else
opsec_debug " opsec-harden.sh not found"
fi
# 5. Restore hostname
opsec_info "Restoring hostname..."
opsec_debug "Step 5: Restoring hostname"
# Check both tmpfs (new) and disk (legacy) locations
local _hostname_file=""
if [ -f /run/opsec/hostname.original ]; then
_hostname_file="/run/opsec/hostname.original"
elif [ -f /etc/opsec/hostname.original ]; then
_hostname_file="/etc/opsec/hostname.original"
fi
opsec_debug "hostname.original location: ${_hostname_file:-NONE}"
opsec_debug "hostname.original content: $(cat "$_hostname_file" 2>/dev/null || echo MISSING)"
opsec_debug "Current hostname: $(hostname)"
if [ -n "$_hostname_file" ]; then
local orig_hostname
orig_hostname=$(cat "$_hostname_file")
local cur_hostname
cur_hostname=$(hostname)
hostnamectl set-hostname "$orig_hostname" 2>/dev/null || {
echo "$orig_hostname" > /etc/hostname
hostname "$orig_hostname"
}
# Update /etc/hosts
if [ "$cur_hostname" != "$orig_hostname" ]; then
sed -i "s/$cur_hostname/$orig_hostname/g" /etc/hosts 2>/dev/null || true
fi
# Secure-delete from both locations
shred -fuz -n 1 "$_hostname_file" 2>/dev/null || rm -f "$_hostname_file"
rm -f /etc/opsec/hostname.original /run/opsec/hostname.original 2>/dev/null
opsec_green "Hostname restored to: ${orig_hostname}"
opsec_debug "Hostname restored: ${cur_hostname} -> ${orig_hostname}"
else
opsec_dim "No saved hostname — keeping current: $(hostname)"
opsec_debug "WARNING: No hostname.original file found, hostname stays: $(hostname)"
fi
# 6. Remove NM MAC randomization config
if [ -f "$NM_MAC_CONF" ]; then
rm -f "$NM_MAC_CONF"
nmcli general reload 2>/dev/null || true
opsec_debug "Step 6: NM MAC config removed, NM reloaded"
opsec_green "MAC randomization config removed"
fi
# 7. Disable boot services
opsec_info "Disabling boot persistence..."
opsec_debug "Step 7: Disabling boot services"
systemctl disable opsec-mac-randomize.service 2>/dev/null || true
systemctl disable opsec-hostname-randomize.service 2>/dev/null || true
systemctl disable opsec-killswitch.service 2>/dev/null || true
systemctl disable opsec-boot-advanced.service 2>/dev/null || true
opsec_green "Boot services disabled"
# 8. Remove state marker (keep boot marker if user wants it)
rm -f "$STATE_FILE"
opsec_green "State marker removed"
opsec_debug "Step 8: State marker removed"
# Teardown complete — clear the interrupt trap (no longer dangerous)
trap - INT TERM
echo ""
opsec_hdr "GHOST MODE: OFF"
echo ""
opsec_dim "Base privacy mode restored"
opsec_dim "Kill switch disarmed"
opsec_dim "DNS using ${base_dns}"
opsec_dim "Tor stopped"
opsec_dim "Hostname restored"
echo ""
opsec_debug "--- MODE OFF FINAL STATE ---"
opsec_debug " Hostname: $(hostname)"
opsec_debug " DNS: $(cat /etc/resolv.conf 2>/dev/null | tr '\n' ' ')"
opsec_debug " Tor: $(systemctl is-active tor 2>&1)"
opsec_debug " iptables OUTPUT rules: $(iptables -L OUTPUT --line-numbers 2>/dev/null | tail -n +3 | wc -l)"
opsec_debug " State file exists: $([ -f "$STATE_FILE" ] && echo YES || echo NO)"
opsec_debug "=== MODE OFF COMPLETE ==="
# Re-apply base hardening (skip MAC re-randomization — already handled)
opsec_debug "Re-applying base hardening..."
BASE_MAC_RANDOMIZE=0 mode_base
}
# ─── BREAK-GLASS ──────────────────────────────────────────────────────────────
mode_breakglass() {
if [ ! -f "$STATE_FILE" ]; then
opsec_red "Break-glass requires ghost mode to be active"
exit 1
fi
local expiry
expiry=$(( $(date +%s) + BREAKGLASS_TIMEOUT ))
echo ""
opsec_hdr "BREAK-GLASS EMERGENCY OVERRIDE"
echo ""
opsec_red "WARNING: Temporarily disabling kill switch"
opsec_red "Direct internet access will be available"
opsec_dim "Auto-expires in $((BREAKGLASS_TIMEOUT / 60)) minutes"
echo ""
# Drop kill switch only
/usr/local/bin/opsec-killswitch.sh off
# Write state file with expiry timestamp
echo "$expiry" > "$BREAKGLASS_STATE"
# Log the event
echo "[$(date -Is)] BREAKGLASS ACTIVATED — expires $(date -d @${expiry} -Is) — timeout ${BREAKGLASS_TIMEOUT}s" >> "$PREFLIGHT_LOG"
opsec_yellow "Kill switch DISARMED — all other OPSEC settings preserved"
opsec_dim "DNS, MAC, hostname remain randomized"
echo ""
opsec_dim "End early: sudo opsec-mode breakglass-off"
opsec_dim "Auto-rearm: $(date -d @${expiry} '+%H:%M:%S')"
echo ""
# Schedule auto-rearm via at or background process
(
exec 9>&- # Release lock fd — don't hold it during sleep
sleep "$BREAKGLASS_TIMEOUT"
if [ -f "$BREAKGLASS_STATE" ]; then
/usr/local/bin/opsec-killswitch.sh on
rm -f "$BREAKGLASS_STATE"
echo "[$(date -Is)] BREAKGLASS EXPIRED — kill switch re-armed" >> "$PREFLIGHT_LOG"
# Re-run preflight
if [ -x /usr/local/bin/opsec-preflight.sh ]; then
/usr/local/bin/opsec-preflight.sh --full --enforce >> "$PREFLIGHT_LOG" 2>&1 || true
fi
fi
) &
disown
}
mode_breakglass_off() {
if [ ! -f "$BREAKGLASS_STATE" ]; then
opsec_yellow "Break-glass is not active"
exit 0
fi
echo ""
opsec_hdr "ENDING BREAK-GLASS"
echo ""
# Re-arm kill switch
/usr/local/bin/opsec-killswitch.sh on
rm -f "$BREAKGLASS_STATE"
echo "[$(date -Is)] BREAKGLASS ENDED MANUALLY — kill switch re-armed" >> "$PREFLIGHT_LOG"
opsec_green "Kill switch re-armed"
opsec_green "Break-glass ended"
echo ""
# Re-run preflight
if [ -x /usr/local/bin/opsec-preflight.sh ]; then
opsec_info "Running preflight verification..."
/usr/local/bin/opsec-preflight.sh --full --enforce || {
opsec_red "Preflight FAILED — check: opsec-preflight"
}
fi
}
# ─── STATUS ──────────────────────────────────────────────────────────────────
mode_status() {
local profile="${PROFILE_NAME:-default}"
local level="${DEPLOYMENT_LEVEL:-bare-metal-standard}"
local level_type="$(get_level_type)"
echo ""
opsec_hdr "OPSEC STATUS"
echo ""
# Mode state
if [ -f "$STATE_FILE" ]; then
opsec_green "Mode: GHOST (active)"
else
opsec_yellow "Mode: BASE PRIVACY"
fi
# Level info
opsec_info "Level: ${level} (${level_type})"
opsec_info "Profile: ${profile}"
# Break-glass warning
if [ -f "$BREAKGLASS_STATE" ]; then
local bg_expiry bg_remaining
bg_expiry=$(cat "$BREAKGLASS_STATE" 2>/dev/null)
bg_remaining=$(( bg_expiry - $(date +%s) ))
if [ "$bg_remaining" -gt 0 ]; then
opsec_red "BREAK-GLASS ACTIVE — kill switch DOWN — ${bg_remaining}s remaining"
fi
fi
echo ""
# Boot-into-advanced
if [ -f "$BOOT_MARKER" ]; then
opsec_green "Boot Mode: PERSISTENT (will activate on reboot)"
else
opsec_dim "Boot Mode: standard (manual activation only)"
fi
echo ""
# ─── Tor ───
opsec_cyan "Tor Configuration"
if systemctl is-active tor >/dev/null 2>&1; then
opsec_green " Status: Running"
if grep -q "ExcludeExitNodes" /etc/tor/torrc 2>/dev/null; then
local blacklist_display
blacklist_display=$(grep "ExcludeExitNodes" /etc/tor/torrc | sed 's/ExcludeExitNodes //' | tr -d '{}')
opsec_green " Torrc: Hardened"
opsec_dim " Blacklist: ${blacklist_display}"
else
opsec_yellow " Torrc: Default"
fi
local rotation_val
rotation_val=$(grep "MaxCircuitDirtiness" /etc/tor/torrc 2>/dev/null | awk '{print $2}')
[ -n "$rotation_val" ] && opsec_dim " Circuit rotation: ${rotation_val}s"
local isolation_val
isolation_val=$(grep "IsolateDestAddr" /etc/tor/torrc 2>/dev/null | head -1)
[ -n "$isolation_val" ] && opsec_green " Stream isolation: ON" || opsec_dim " Stream isolation: off"
local padding_val
padding_val=$(grep "ConnectionPadding" /etc/tor/torrc 2>/dev/null | awk '{print $2}')
[ "$padding_val" = "1" ] && opsec_green " Traffic padding: ON" || opsec_dim " Traffic padding: off"
else
opsec_yellow " Status: Stopped"
fi
echo ""
# ─── IPv6 ───
local ipv6_all
ipv6_all=$(sysctl -n net.ipv6.conf.all.disable_ipv6 2>/dev/null || echo "0")
[ "$ipv6_all" = "1" ] && opsec_green "IPv6: Disabled" || opsec_red "IPv6: ENABLED (risk)"
# ─── DNS ───
local dns_server
dns_server=$(awk '/^nameserver/ {print $2; exit}' /etc/resolv.conf 2>/dev/null)
if [ "$dns_server" = "127.0.0.1" ]; then
opsec_green "DNS: Tor DNS (127.0.0.1)"
if lsattr /etc/resolv.conf 2>/dev/null | grep -q 'i'; then
opsec_green "DNS Lock: Immutable (chattr +i)"
else
opsec_yellow "DNS Lock: Not locked"
fi
elif echo "$dns_server" | grep -qE '^(9\.9\.9\.9|149\.112|1\.1\.1\.1|1\.0\.0\.1)$'; then
opsec_green "DNS: Privacy resolver (${dns_server})"
else
opsec_yellow "DNS: ${dns_server}"
fi
# ─── Kill switch ───
if [ -f "$BREAKGLASS_STATE" ]; then
opsec_red "Kill Switch: DISARMED (BREAK-GLASS)"
elif iptables -L GP_FW >/dev/null 2>&1; then
opsec_green "Kill Switch: ARMED"
else
opsec_yellow "Kill Switch: Off"
fi
# ─── VPN ───
local vpn_if
vpn_if=$(ip -o link show 2>/dev/null | awk -F': ' '{print $2}' | grep -E '^(tun|wg)' | head -1)
if [ -n "$vpn_if" ]; then
local vpn_ip
vpn_ip=$(ip -4 addr show "$vpn_if" 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1)
opsec_green "VPN: Active (${vpn_if}: ${vpn_ip})"
else
opsec_yellow "VPN: Not connected"
fi
# ─── MAC ───
if ! is_cloud_level; then
local primary_if
primary_if=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}' | head -1)
if [ -n "$primary_if" ]; then
local cur_mac first_octet first_dec
cur_mac=$(ip link show "$primary_if" 2>/dev/null | awk '/ether/ {print $2}')
first_octet=$(echo "$cur_mac" | cut -d: -f1)
first_dec=$((16#${first_octet}))
if (( first_dec & 2 )); then
opsec_green "MAC: Randomized (${primary_if}: ${cur_mac})"
else
opsec_yellow "MAC: Hardware (${primary_if}: ${cur_mac})"
fi
fi
fi
echo ""
# ─── Hardening status ───
opsec_cyan "System Hardening"
# Core dumps
local core_pattern
core_pattern=$(sysctl -n kernel.core_pattern 2>/dev/null)
[[ "$core_pattern" == *"/bin/false"* ]] && opsec_green " Core dumps: Disabled" || opsec_dim " Core dumps: enabled"
# Swap
local swap_total
swap_total=$(swapon --show=SIZE --noheadings 2>/dev/null | head -1)
[ -z "$swap_total" ] && opsec_green " Swap: Disabled" || opsec_dim " Swap: active (${swap_total})"
echo ""
# ─── Hostname ───
opsec_info "Hostname: $(hostname)"
# ─── Boot services ───
echo ""
opsec_cyan "Boot Services"
for svc in opsec-boot-advanced opsec-mac-randomize opsec-hostname-randomize opsec-killswitch; do
if systemctl is-enabled "$svc" 2>/dev/null | grep -q enabled; then
opsec_green " ${svc}: enabled"
else
opsec_dim " ${svc}: disabled"
fi
done
# ─── Preflight ───
if [ -x /usr/local/bin/opsec-preflight.sh ]; then
echo ""
opsec_cyan "Preflight Score"
/usr/local/bin/opsec-preflight.sh --score 2>/dev/null || opsec_dim " Preflight not available"
fi
echo ""
}
# ─── DIAGNOSTIC ──────────────────────────────────────────────────────────────
mode_diag() {
echo "=== OPSEC DIAGNOSTIC ==="
echo "Time: $(date -Is)"
echo ""
echo "--- State ---"
echo "Ghost mode: $([ -f "$STATE_FILE" ] && echo ACTIVE || echo INACTIVE)"
echo "Breakglass: $([ -f "$BREAKGLASS_STATE" ] && echo ACTIVE || echo INACTIVE)"
echo ""
echo "--- Tor ---"
echo "Service: $(systemctl is-active tor 2>&1)"
echo "TCP ports: $(ss -tlnp 2>/dev/null | grep -E ':(9040|9050|5353)' || echo NONE)"
echo "UDP ports: $(ss -ulnp 2>/dev/null | grep -E ':5353' || echo NONE)"
echo "Bootstrap: $(grep 'Bootstrapped' /run/tor/notices.log 2>/dev/null | tail -1 || echo NONE)"
echo "Last 5 log:"
tail -5 /run/tor/notices.log 2>/dev/null || echo " (no log)"
echo ""
echo "--- Kill Switch ---"
echo "Filter: $(iptables -L GP_FW >/dev/null 2>&1 && echo ARMED || echo OFF)"
local _diag_nat_count=0
if iptables -t nat -L GP_NAT >/dev/null 2>&1; then
_diag_nat_count=$(iptables -t nat -L GP_NAT --line-numbers 2>/dev/null | tail -n +3 | wc -l)
echo "NAT chain: ARMED (${_diag_nat_count} rules)"
else
echo "NAT chain: OFF"
local _diag_out_nat
_diag_out_nat=$(iptables -t nat -L OUTPUT --line-numbers 2>/dev/null | tail -n +3 | wc -l)
[ "$_diag_out_nat" -gt 0 ] && echo "NAT OUTPUT fallback: ${_diag_out_nat} rules"
fi
echo ""
echo "--- DNS ---"
echo "Resolver: $(grep nameserver /etc/resolv.conf 2>/dev/null | tr '\n' ' ')"
echo "Locked: $(lsattr /etc/resolv.conf 2>/dev/null | grep -q 'i' && echo YES || echo NO)"
echo "NM dns=none: $([ -f /etc/NetworkManager/conf.d/opsec-dns-lock.conf ] && echo YES || echo NO)"
echo ""
echo "--- Network ---"
echo "Route: $(ip route show default 2>/dev/null | head -1)"
echo ""
echo "--- Quick Test ---"
echo -n "SOCKS: "
curl -s --max-time 5 --socks5-hostname 127.0.0.1:9050 https://check.torproject.org/api/ip 2>/dev/null || echo "FAILED"
echo ""
echo -n "TransPort: "
curl -s --max-time 10 https://check.torproject.org/api/ip 2>/dev/null || echo "FAILED (transparent proxy not working)"
}
# ─── APPLY FLAGS ─────────────────────────────────────────────────────────────
VERBOSE="$_VERBOSE_EARLY"
if [ "$VERBOSE" = "true" ]; then
echo -e "\033[38;5;196m[!] VERBOSE: debug output may contain sensitive identifiers\033[0m"
fi
# ─── MAIN ────────────────────────────────────────────────────────────────────
case "${_CMD_EARLY:-}" in
on) mode_on ;;
off) mode_off ;;
status) mode_status ;;
diag) mode_diag ;;
breakglass) mode_breakglass ;;
breakglass-off) mode_breakglass_off ;;
*)
echo "Usage: sudo opsec-mode [--verbose] on|off|status|diag|breakglass|breakglass-off"
echo ""
echo " on — Activate ghost mode (Tor, kill switch, full lockdown)"
echo " off — Deactivate ghost mode (returns to base privacy)"
echo " status — Show current OPSEC subsystem status"
echo " diag — Quick diagnostic of all subsystems"
echo " breakglass — Emergency: temporarily drop kill switch"
echo " breakglass-off — End break-glass, re-arm kill switch"
echo ""
echo " --verbose, -v — Show debug output in real-time"
exit 1
;;
esac