From f1ba1f050de3c3e4bde33b581ecdb6145c5e4482 Mon Sep 17 00:00:00 2001 From: alexz Date: Sun, 4 Jan 2026 09:01:52 +0000 Subject: [PATCH] new image structure for cisco-vpn and related scripts --- apps/rego-tunnel/shared/cisco-vpn | 817 ++++++++++++++++++ .../shared/cisco-vpn-connect.desktop | 7 + .../shared/cisco-vpn-connect.service.user | 15 + .../shared/cisco-vpn.sh.orig-20251228-143916 | 729 ++++++++++++++++ apps/rego-tunnel/shared/fix-netplan-ens3.sh | 55 ++ .../shared/generate-traefik-basicauth.sh | 29 + apps/rego-tunnel/shared/host-routing.sh | 203 +++++ .../shared/install-cisco-vpn-autostart.sh | 34 + .../install-cisco-vpn-connect-service.sh | 14 + .../shared/install-cisco-vpn-user-service.sh | 25 + apps/rego-tunnel/shared/setup-network.sh | 149 ++++ apps/rego-tunnel/shared/start-dnsmasq.sh | 38 + apps/rego-tunnel/shared/start-vm.sh | 116 +++ apps/rego-tunnel/shared/supervisord.conf | 46 + 14 files changed, 2277 insertions(+) create mode 100644 apps/rego-tunnel/shared/cisco-vpn create mode 100644 apps/rego-tunnel/shared/cisco-vpn-connect.desktop create mode 100644 apps/rego-tunnel/shared/cisco-vpn-connect.service.user create mode 100644 apps/rego-tunnel/shared/cisco-vpn.sh.orig-20251228-143916 create mode 100644 apps/rego-tunnel/shared/fix-netplan-ens3.sh create mode 100644 apps/rego-tunnel/shared/generate-traefik-basicauth.sh create mode 100644 apps/rego-tunnel/shared/host-routing.sh create mode 100644 apps/rego-tunnel/shared/install-cisco-vpn-autostart.sh create mode 100644 apps/rego-tunnel/shared/install-cisco-vpn-connect-service.sh create mode 100644 apps/rego-tunnel/shared/install-cisco-vpn-user-service.sh create mode 100644 apps/rego-tunnel/shared/setup-network.sh create mode 100644 apps/rego-tunnel/shared/start-dnsmasq.sh create mode 100644 apps/rego-tunnel/shared/start-vm.sh create mode 100644 apps/rego-tunnel/shared/supervisord.conf diff --git a/apps/rego-tunnel/shared/cisco-vpn b/apps/rego-tunnel/shared/cisco-vpn new file mode 100644 index 0000000..a9ad415 --- /dev/null +++ b/apps/rego-tunnel/shared/cisco-vpn @@ -0,0 +1,817 @@ +#!/bin/bash + +# Dover VPN Connection Script with Semi-Automation +# Usage: ./cisco-vpn.sh [-c|--connect] [-d|--disconnect] [-m|--menu] [-r|--routes] [-h|--hosts] [--help] +# +# Options: +# -c, --connect Start Cisco AnyConnect (optionally auto-login) and exit +# -d, --disconnect Disconnect (kill Cisco processes) and exit +# -m, --menu Skip auto-login, show menu directly +# -r, --routes Show current routing table and exit +# -h, --hosts Show /etc/hosts and exit +# -s, --status Show VPN and network status and exit +# --help Show this help message +# +# Keyboard shortcuts (global, work anywhere): +# Ctrl+1 - Type email +# Ctrl+2 - Type password +# Ctrl+3 - Type TOTP code +# Ctrl+4 - Type email + Tab + password (combo) +# Ctrl+5 - Full sequence: email + Tab + password + Tab + TOTP + Enter + +EMAIL="c-azaw@regoproducts.com" +PASSWORD='Lz@83278327$$@@' +TOTP_SECRET="rzqtqskdwkhz6zyr" +VPN_HOST="vpn-ord1.dovercorp.com" +TARGET_IP="10.35.33.230" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +GRAY='\033[0;90m' +NC='\033[0m' + +# Flags +SKIP_AUTO_LOGIN=false +DO_CONNECT=false +DO_DISCONNECT=false + +# Logging function with timestamp +log() { + local level="$1" + local msg="$2" + local timestamp=$(date '+%H:%M:%S') + case $level in + INFO) echo -e "${GRAY}[$timestamp]${NC} ${GREEN}[INFO]${NC} $msg" ;; + WARN) echo -e "${GRAY}[$timestamp]${NC} ${YELLOW}[WARN]${NC} $msg" ;; + ERROR) echo -e "${GRAY}[$timestamp]${NC} ${RED}[ERROR]${NC} $msg" ;; + DEBUG) echo -e "${GRAY}[$timestamp]${NC} ${CYAN}[DEBUG]${NC} $msg" ;; + CMD) echo -e "${GRAY}[$timestamp]${NC} ${GRAY}[CMD]${NC} $msg" ;; + *) echo -e "${GRAY}[$timestamp]${NC} $msg" ;; + esac +} + +# Run command with logging +run_cmd() { + local desc="$1" + shift + log CMD "$desc: $*" + output=$("$@" 2>&1) + local rc=$? + if [ -n "$output" ]; then + echo "$output" | while IFS= read -r line; do + echo -e " ${GRAY}│${NC} $line" + done + fi + return $rc +} + +# Show help +show_help() { + echo -e "${CYAN}Dover VPN Connection Script${NC}" + echo "" + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " -c, --connect Start Cisco AnyConnect and exit" + echo " -d, --disconnect Disconnect (kill Cisco processes) and exit" + echo " -m, --menu Skip auto-login, show menu directly" + echo " -r, --routes Show current routing table and exit" + echo " -h, --hosts Show /etc/hosts and exit" + echo " -s, --status Show VPN and network status and exit" + echo " --help Show this help message" + echo "" + echo "Menu Options:" + echo " 1 - Start Cisco AnyConnect (kill existing + launch)" + echo " 2 - Copy credentials to clipboard (one by one)" + echo " 3 - Show live TOTP" + echo " 4 - Setup IP forwarding rules only" + echo " 5 - Test connection to target" + echo " 6 - Show network status" + echo " 7 - Kill all Cisco processes" + echo " 8 - Show routing table" + echo " 9 - Show /etc/hosts" + echo " e - Edit /etc/hosts" + echo " q - Quit" +} + +# Show routing table +show_routes() { + echo -e "${CYAN}========================================${NC}" + echo -e "${CYAN} Current Routing Table ${NC}" + echo -e "${CYAN}========================================${NC}" + echo "" + echo -e "${GREEN}IPv4 Routes:${NC}" + ip -4 route show | while IFS= read -r line; do + # Highlight VPN-related routes + if echo "$line" | grep -qE "cscotun|tun[0-9]"; then + echo -e " ${YELLOW}$line${NC}" + elif echo "$line" | grep -qE "10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\."; then + echo -e " ${CYAN}$line${NC}" + else + echo " $line" + fi + done + echo "" + echo -e "${GREEN}VPN Interface:${NC}" + local vpn_iface=$(get_vpn_interface) + if [ -n "$vpn_iface" ]; then + ip addr show "$vpn_iface" 2>/dev/null | grep -E "inet|link" | while IFS= read -r line; do + echo " $line" + done + else + echo -e " ${RED}No VPN interface found${NC}" + fi +} + +# Show /etc/hosts +show_hosts() { + echo -e "${CYAN}========================================${NC}" + echo -e "${CYAN} /etc/hosts ${NC}" + echo -e "${CYAN}========================================${NC}" + echo "" + cat -n /etc/hosts +} + +# Edit /etc/hosts +edit_hosts() { + local editor="${EDITOR:-nano}" + if command -v "$editor" &>/dev/null; then + sudo "$editor" /etc/hosts + else + log ERROR "No editor found. Set EDITOR environment variable." + log INFO "Try: sudo nano /etc/hosts" + fi +} + +# Function to get current TOTP +get_totp() { + oathtool --totp -b "$TOTP_SECRET" +} + +# Function to detect VPN tunnel interface dynamically +get_vpn_interface() { + # Look for cscotun* or tun* interfaces that are UP + local iface=$(ip link show | grep -oP '(cscotun\d+|tun\d+)(?=:.*UP)' | head -1) + if [ -z "$iface" ]; then + # Fallback: any cscotun interface + iface=$(ip link show | grep -oP 'cscotun\d+' | head -1) + fi + echo "$iface" +} + +# Function to get VM's IP on the bridge network (for container routing) +get_vm_bridge_ip() { + # Get IP from ens3 (main adapter with 100.100.0.x) + ip addr show ens3 2>/dev/null | grep -oP 'inet \K[\d.]+' | head -1 +} + +# Function to get container gateway IP +get_container_gateway() { + # The container bridge is at 100.100.0.1 + echo "100.100.0.1" +} + +# Function to get VPN tunnel IP +get_vpn_ip() { + local iface=$(get_vpn_interface) + if [ -n "$iface" ]; then + ip addr show "$iface" 2>/dev/null | grep -oP 'inet \K[\d.]+' | head -1 + fi +} + +# Start xbindkeys for keyboard macros +start_xbindkeys() { + log INFO "Starting keyboard macro listener (xbindkeys)..." + + # Kill any existing xbindkeys + pkill xbindkeys 2>/dev/null + sleep 0.5 + + # Start xbindkeys + xbindkeys -f ~/.xbindkeysrc 2>/dev/null & + XBINDKEYS_PID=$! + + if pgrep xbindkeys >/dev/null; then + log DEBUG "xbindkeys started (PID: $(pgrep xbindkeys))" + log INFO "Keyboard shortcuts active: Ctrl+1=email, Ctrl+2=pass, Ctrl+3=TOTP, Ctrl+4=combo, Ctrl+5=all" + else + log WARN "Failed to start xbindkeys" + fi +} + +# Stop xbindkeys +stop_xbindkeys() { + if pgrep xbindkeys >/dev/null; then + log INFO "Stopping keyboard macro listener..." + pkill xbindkeys 2>/dev/null + log DEBUG "xbindkeys stopped" + fi +} + +# Kill all Cisco-related processes +kill_cisco_processes() { + local kill_agentd="${1:-false}" + log INFO "Killing all Cisco-related processes..." + + local killed=0 + local my_pid=$$ + local my_ppid=$(ps -o ppid= -p $$ | tr -d ' ') + + # Kill vpnui specifically (not just any process with "vpn" in name) + for pid in $(pgrep -x "vpnui" 2>/dev/null); do + if [ "$pid" != "$my_pid" ] && [ "$pid" != "$my_ppid" ]; then + log DEBUG "Killing vpnui (PID $pid)" + sudo kill -9 "$pid" 2>/dev/null && ((killed++)) + fi + done + + # Optionally kill vpnagentd (useful to clear stale state) + if [ "$kill_agentd" = "true" ]; then + for pid in $(pgrep -x "vpnagentd" 2>/dev/null); do + if [ "$pid" != "$my_pid" ] && [ "$pid" != "$my_ppid" ]; then + log DEBUG "Killing vpnagentd (PID $pid)" + sudo kill -9 "$pid" 2>/dev/null && ((killed++)) + fi + done + fi + + # Kill Cisco-specific processes by exact path + for proc in cstub cscan acwebsecagent vpndownloader; do + for pid in $(pgrep -x "$proc" 2>/dev/null); do + log DEBUG "Killing $proc (PID $pid)" + sudo kill -9 "$pid" 2>/dev/null && ((killed++)) + done + done + + # Kill openconnect (exact match) + for pid in $(pgrep -x "openconnect" 2>/dev/null); do + log DEBUG "Killing openconnect (PID $pid)" + sudo kill -9 "$pid" 2>/dev/null && ((killed++)) + done + + if [ $killed -eq 0 ]; then + log INFO "No Cisco processes were running" + else + log INFO "Killed $killed process(es)" + sleep 1 + fi +} + +# Disconnect VPN (best-effort) +disconnect_vpn() { + log INFO "Disconnecting Cisco AnyConnect..." + + # If vpncli exists, attempt a clean disconnect first (ignore failures) + if [ -x /opt/cisco/secureclient/bin/vpncli ]; then + run_cmd "Attempting clean disconnect via vpncli" sudo /opt/cisco/secureclient/bin/vpncli -s <<'EOF' || true +disconnect +exit +EOF + fi + + # Always kill agent/UI processes afterwards + kill_cisco_processes "true" + + # Confirm status + if check_vpn_status; then + log WARN "VPN still appears connected (interface still up)" + return 1 + fi + + log INFO "VPN disconnected" + return 0 +} + +# Function to setup iptables rules for forwarding +setup_forwarding() { + log INFO "Setting up IP forwarding rules for $TARGET_IP..." + + local vpn_iface=$(get_vpn_interface) + if [ -z "$vpn_iface" ]; then + log ERROR "No VPN interface found! Is VPN connected?" + return 1 + fi + + local vpn_ip=$(get_vpn_ip) + local vm_bridge_ip=$(get_vm_bridge_ip) + local container_gw=$(get_container_gateway) + + log DEBUG "VPN interface: $vpn_iface" + log DEBUG "VPN IP: $vpn_ip" + log DEBUG "VM bridge IP: $vm_bridge_ip" + log DEBUG "Container gateway: $container_gw" + + # Enable IP forwarding + run_cmd "Enabling IP forwarding" sudo sysctl -w net.ipv4.ip_forward=1 + + # NAT masquerade for traffic from container network (100.100.0.0/24) going through VPN + # This is the ONLY masquerade rule needed - source-based, not destination-based + if ! sudo iptables -t nat -C POSTROUTING -s 100.100.0.0/24 -o "$vpn_iface" -j MASQUERADE 2>/dev/null; then + run_cmd "Adding NAT masquerade for container network -> VPN" sudo iptables -t nat -A POSTROUTING -s 100.100.0.0/24 -o "$vpn_iface" -j MASQUERADE + else + log DEBUG "NAT masquerade for container network already exists" + fi + + # Forward rules + if ! sudo iptables -C FORWARD -d "$TARGET_IP" -j ACCEPT 2>/dev/null; then + run_cmd "Adding forward rule (to target)" sudo iptables -A FORWARD -d "$TARGET_IP" -j ACCEPT + else + log DEBUG "Forward rule (to target) already exists" + fi + + if ! sudo iptables -C FORWARD -s "$TARGET_IP" -j ACCEPT 2>/dev/null; then + run_cmd "Adding forward rule (from target)" sudo iptables -A FORWARD -s "$TARGET_IP" -j ACCEPT + else + log DEBUG "Forward rule (from target) already exists" + fi + + # Accept forwarding from container network + if ! sudo iptables -C FORWARD -s 100.100.0.0/24 -j ACCEPT 2>/dev/null; then + run_cmd "Adding forward rule (from container network)" sudo iptables -A FORWARD -s 100.100.0.0/24 -j ACCEPT + else + log DEBUG "Forward rule (from container network) already exists" + fi + + if ! sudo iptables -C FORWARD -d 100.100.0.0/24 -j ACCEPT 2>/dev/null; then + run_cmd "Adding forward rule (to container network)" sudo iptables -A FORWARD -d 100.100.0.0/24 -j ACCEPT + else + log DEBUG "Forward rule (to container network) already exists" + fi + + # Cisco VPN chain bypass (insert at top if chain exists) + if sudo iptables -L ciscovpn -n &>/dev/null; then + if ! sudo iptables -C ciscovpn -o "$vpn_iface" -d "$TARGET_IP" -j ACCEPT 2>/dev/null; then + run_cmd "Adding ciscovpn bypass (outbound)" sudo iptables -I ciscovpn 1 -o "$vpn_iface" -d "$TARGET_IP" -j ACCEPT + else + log DEBUG "Ciscovpn bypass (outbound) already exists" + fi + + if ! sudo iptables -C ciscovpn -i "$vpn_iface" -s "$TARGET_IP" -j ACCEPT 2>/dev/null; then + run_cmd "Adding ciscovpn bypass (inbound)" sudo iptables -I ciscovpn 2 -i "$vpn_iface" -s "$TARGET_IP" -j ACCEPT + else + log DEBUG "Ciscovpn bypass (inbound) already exists" + fi + + # Also allow container network through ciscovpn chain + if ! sudo iptables -C ciscovpn -s 100.100.0.0/24 -j ACCEPT 2>/dev/null; then + run_cmd "Adding ciscovpn bypass (container network)" sudo iptables -I ciscovpn 3 -s 100.100.0.0/24 -j ACCEPT + fi + else + log DEBUG "ciscovpn chain does not exist (yet)" + fi + + log INFO "Forwarding rules configured" + echo "" + log INFO "Container should now be able to reach $TARGET_IP through this VM" + echo "" +} + +# Copy credentials to clipboard as alternative +copy_to_clipboard() { + log INFO "Starting clipboard credential rotation..." + echo "" + + log INFO "Copying EMAIL to clipboard" + echo "$EMAIL" | xclip -selection clipboard + echo -e " ${CYAN}Email ready: $EMAIL${NC}" + echo -e " Paste now (Ctrl+V), then press ${GREEN}Enter${NC} here for password..." + read -r + + log INFO "Copying PASSWORD to clipboard" + echo "$PASSWORD" | xclip -selection clipboard + echo -e " ${CYAN}Password ready${NC}" + echo -e " Paste now (Ctrl+V), then press ${GREEN}Enter${NC} here for TOTP..." + read -r + + TOTP=$(get_totp) + log INFO "Copying TOTP to clipboard" + echo "$TOTP" | xclip -selection clipboard + echo -e " ${CYAN}TOTP ready: $TOTP${NC}" + echo -e " Paste now (Ctrl+V)" +} + +# Print current TOTP with countdown +show_totp() { + log INFO "Starting live TOTP display (Ctrl+C to stop)" + echo "" + while true; do + TOTP=$(get_totp) + SECONDS_LEFT=$((30 - ($(date +%s) % 30))) + echo -ne "\r ${CYAN}Current TOTP:${NC} ${GREEN}$TOTP${NC} (expires in ${YELLOW}${SECONDS_LEFT}s${NC}) " + sleep 1 + done +} + +# Show network status +show_network_status() { + log INFO "Current network status:" + + # VM IPs + echo "" + log DEBUG "VM Network Interfaces:" + ip -4 addr show | grep -E "inet |^[0-9]+:" | while IFS= read -r line; do + echo -e " ${GRAY}│${NC} $line" + done + + # VPN status + echo "" + local vpn_iface=$(get_vpn_interface) + if [ -n "$vpn_iface" ]; then + local vpn_ip=$(get_vpn_ip) + log INFO "VPN Status: ${GREEN}CONNECTED${NC}" + log DEBUG " Interface: $vpn_iface" + log DEBUG " VPN IP: $vpn_ip" + else + log WARN "VPN Status: ${RED}NOT CONNECTED${NC}" + fi + + # Bridge IP (for container routing) + local vm_bridge_ip=$(get_vm_bridge_ip) + if [ -n "$vm_bridge_ip" ]; then + log DEBUG "VM IP on bridge: $vm_bridge_ip" + fi + + # Container gateway + local container_gw=$(get_container_gateway) + log DEBUG "Container gateway: $container_gw" + + # Default gateway + echo "" + log DEBUG "Default gateway:" + ip route show default | while IFS= read -r line; do + echo -e " ${GRAY}│${NC} $line" + done + + echo "" +} + +# Main menu +main_menu() { + echo -e "${GREEN}Options:${NC}" + echo -e " ${CYAN}1${NC} - Start Cisco AnyConnect (kill existing + launch)" + echo -e " ${CYAN}2${NC} - Copy credentials to clipboard (one by one)" + echo -e " ${CYAN}3${NC} - Show live TOTP" + echo -e " ${CYAN}4${NC} - Setup IP forwarding rules only" + echo -e " ${CYAN}5${NC} - Test connection to $TARGET_IP" + echo -e " ${CYAN}6${NC} - Show network status" + echo -e " ${CYAN}7${NC} - Kill all Cisco processes" + echo -e " ${CYAN}8${NC} - Show routing table" + echo -e " ${CYAN}9${NC} - Show /etc/hosts" + echo -e " ${CYAN}e${NC} - Edit /etc/hosts" + echo -e " ${CYAN}q${NC} - Quit" + echo "" +} + +# Check if VPN is already connected +check_vpn_status() { + local vpn_iface=$(get_vpn_interface) + if [ -n "$vpn_iface" ]; then + local vpn_ip=$(get_vpn_ip) + log INFO "VPN is ${GREEN}CONNECTED${NC}" + log DEBUG " Interface: $vpn_iface" + log DEBUG " VPN IP: $vpn_ip" + return 0 + else + log WARN "VPN is ${RED}NOT CONNECTED${NC}" + return 1 + fi +} + +# Focus on Cisco AnyConnect window +focus_vpn_window() { + local win_id=$(xdotool search --name "Cisco" 2>/dev/null | head -1) + if [ -n "$win_id" ]; then + xdotool windowactivate --sync "$win_id" 2>/dev/null + sleep 0.3 + return 0 + fi + return 1 +} + +# Auto-login sequence using xdotool (no auto-focus, types to active window) +auto_login() { + log INFO "Starting automated login sequence..." + + # Wait for UI to fully load + log DEBUG "Waiting 5s for UI to load..." + sleep 5 + + # Press Enter to initiate connection + log DEBUG "Pressing Enter to start connection..." + xdotool key Return + sleep 5 + + # Type email + log DEBUG "Typing email..." + xdotool type --delay 50 "$EMAIL" + xdotool key Return + sleep 5 + + # Type password + log DEBUG "Typing password..." + xdotool type --delay 50 "$PASSWORD" + xdotool key Return + sleep 5 + + # Type TOTP + log DEBUG "Typing TOTP..." + local totp=$(oathtool --totp -b "$TOTP_SECRET") + log DEBUG "TOTP: $totp" + xdotool type --delay 50 "$totp" + xdotool key Return + sleep 5 + + # Extra enters for any confirmation dialogs + log DEBUG "Sending confirmation enters..." + xdotool key Return + sleep 2 + xdotool key Return + sleep 5 + xdotool key Return + + log INFO "Auto-login sequence completed" +} + +# Start Cisco AnyConnect with logging +start_anyconnect() { + local do_auto_login="$1" + log INFO "=== Starting Cisco AnyConnect VPN ===" + echo "" + + # Kill existing processes first + # Always restart vpnagentd for a clean session + kill_cisco_processes "true" + + # Start vpnagentd if not running + if ! pgrep -x vpnagentd >/dev/null; then + log INFO "Starting vpnagentd..." + sudo /opt/cisco/secureclient/bin/vpnagentd & + log DEBUG "Waiting for vpnagentd to initialize..." + sleep 5 + fi + + # Show credentials + log INFO "Credentials for SSO login:" + echo -e " ${CYAN}Email: $EMAIL${NC}" + echo -e " ${CYAN}Password: $PASSWORD${NC}" + TOTP=$(get_totp) + echo -e " ${CYAN}TOTP: $TOTP${NC}" + echo "" + + # Start AnyConnect with GPU/WebKit workarounds + log INFO "Launching Cisco AnyConnect UI..." + export GDK_BACKEND=x11 + export WEBKIT_DISABLE_DMABUF_RENDERER=1 + /opt/cisco/secureclient/bin/vpnui & + VPNUI_PID=$! + log DEBUG "vpnui started with PID $VPNUI_PID" + + if [ "$do_auto_login" = "true" ]; then + # Run auto-login in background + auto_login & + AUTO_LOGIN_PID=$! + log DEBUG "Auto-login started with PID $AUTO_LOGIN_PID" + else + log INFO "Manual login mode - use keyboard shortcuts or menu option 2 for credentials" + return 0 + fi + + # Wait for VPN to connect + log INFO "Waiting for VPN connection..." + local wait_count=0 + local max_wait=300 # 5 minutes + while [ -z "$(get_vpn_interface)" ]; do + sleep 2 + ((wait_count+=2)) + if [ $((wait_count % 10)) -eq 0 ]; then + log DEBUG "Still waiting for VPN... (${wait_count}s)" + fi + if [ $wait_count -ge $max_wait ]; then + log ERROR "Timeout waiting for VPN connection after ${max_wait}s" + stop_xbindkeys + return 1 + fi + done + + log INFO "VPN connected!" + local vpn_iface=$(get_vpn_interface) + local vpn_ip=$(get_vpn_ip) + log DEBUG " Interface: $vpn_iface" + log DEBUG " VPN IP: $vpn_ip" + + # Wait a bit for routes to stabilize + log DEBUG "Waiting for routes to stabilize..." + sleep 3 + + # Setup forwarding + setup_forwarding + + # Test connection + log INFO "Testing connection to $TARGET_IP..." + if ping -c 2 -W 3 "$TARGET_IP" &>/dev/null; then + log INFO "Connection test: ${GREEN}SUCCESS${NC}" + else + log WARN "Connection test: ${RED}FAILED${NC} (may need manual route on Windows)" + fi + + # Monitor and auto-reconnect + log INFO "Monitoring VPN connection (will auto-reconnect if disconnected)..." + while true; do + sleep 30 + if [ -z "$(get_vpn_interface)" ]; then + log WARN "VPN disconnected! Reconnecting in 5 seconds..." + sleep 5 + start_anyconnect "$do_auto_login" + return $? + fi + done +} + +# Parse command line arguments +parse_args() { + while [[ $# -gt 0 ]]; do + case $1 in + -c|--connect) + DO_CONNECT=true + shift + ;; + -d|--disconnect) + DO_DISCONNECT=true + shift + ;; + -m|--menu) + SKIP_AUTO_LOGIN=true + shift + ;; + -r|--routes) + show_routes + exit 0 + ;; + -h|--hosts) + show_hosts + exit 0 + ;; + -s|--status) + echo -e "${CYAN}========================================${NC}" + echo -e "${CYAN} VPN and Network Status ${NC}" + echo -e "${CYAN}========================================${NC}" + echo "" + check_vpn_status + echo "" + show_network_status + exit 0 + ;; + --help) + show_help + exit 0 + ;; + *) + echo "Unknown option: $1" + echo "Use --help for usage information" + exit 1 + ;; + esac + done + + if [ "$DO_CONNECT" = "true" ] && [ "$DO_DISCONNECT" = "true" ]; then + echo "Error: --connect and --disconnect are mutually exclusive" + exit 1 + fi +} + +# Main +parse_args "$@" + +if [ "$DO_DISCONNECT" = "true" ]; then + echo -e "${CYAN}========================================${NC}" + echo -e "${CYAN} Dover VPN Connection Script ${NC}" + echo -e "${CYAN}========================================${NC}" + echo "" + disconnect_vpn + exit $? +fi + +if [ "$DO_CONNECT" = "true" ]; then + echo -e "${CYAN}========================================${NC}" + echo -e "${CYAN} Dover VPN Connection Script ${NC}" + echo -e "${CYAN}========================================${NC}" + echo "" + if [ "$SKIP_AUTO_LOGIN" = "true" ]; then + start_anyconnect "false" + else + start_anyconnect "true" + fi + exit $? +fi + +echo -e "${CYAN}========================================${NC}" +echo -e "${CYAN} Dover VPN Connection Script ${NC}" +echo -e "${CYAN}========================================${NC}" +echo "" + +log INFO "Script started" +echo "" + +# Check current status +if check_vpn_status; then + echo "" + log INFO "VPN already connected. Setting up forwarding..." + setup_forwarding +elif [ "$SKIP_AUTO_LOGIN" = "true" ]; then + echo "" + log INFO "Menu mode - skipping auto-login" +else + echo "" + log INFO "Auto-starting VPN connection..." + echo "" + start_anyconnect "true" +fi + +echo "" +main_menu + +while true; do + echo -ne "${CYAN}Choice: ${NC}" + read -r choice + + case $choice in + 1) + echo "" + if [ "$SKIP_AUTO_LOGIN" = "true" ]; then + start_anyconnect "false" + else + start_anyconnect "true" + fi + echo "" + main_menu + ;; + 2) + echo "" + copy_to_clipboard + echo "" + main_menu + ;; + 3) + echo "" + show_totp + echo "" + main_menu + ;; + 4) + echo "" + setup_forwarding + echo "" + main_menu + ;; + 5) + echo "" + log INFO "Testing connection to $TARGET_IP..." + if ping -c 3 "$TARGET_IP"; then + log INFO "Connection test: ${GREEN}SUCCESS${NC}" + else + log ERROR "Connection test: ${RED}FAILED${NC}" + fi + echo "" + main_menu + ;; + 6) + echo "" + show_network_status + main_menu + ;; + 7) + echo "" + kill_cisco_processes + echo "" + main_menu + ;; + 8) + echo "" + show_routes + echo "" + main_menu + ;; + 9) + echo "" + show_hosts + echo "" + main_menu + ;; + e|E) + echo "" + edit_hosts + echo "" + main_menu + ;; + q|Q) + log INFO "Goodbye!" + exit 0 + ;; + *) + log ERROR "Invalid choice" + ;; + esac +done diff --git a/apps/rego-tunnel/shared/cisco-vpn-connect.desktop b/apps/rego-tunnel/shared/cisco-vpn-connect.desktop new file mode 100644 index 0000000..b863bdf --- /dev/null +++ b/apps/rego-tunnel/shared/cisco-vpn-connect.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Type=Application +Name=Cisco VPN Auto-Connect +Comment=Auto-connect Cisco VPN on login +Exec=/usr/bin/cisco-vpn --connect +Terminal=false +X-GNOME-Autostart-enabled=true diff --git a/apps/rego-tunnel/shared/cisco-vpn-connect.service.user b/apps/rego-tunnel/shared/cisco-vpn-connect.service.user new file mode 100644 index 0000000..c9eda33 --- /dev/null +++ b/apps/rego-tunnel/shared/cisco-vpn-connect.service.user @@ -0,0 +1,15 @@ +[Unit] +Description=Cisco VPN auto-connect (user session) +After=graphical-session.target +Wants=graphical-session.target + +[Service] +Type=simple +Environment=DISPLAY=:0 +ExecStart=/usr/bin/cisco-vpn --connect +Restart=on-failure +RestartSec=10 +TimeoutStartSec=0 + +[Install] +WantedBy=default.target diff --git a/apps/rego-tunnel/shared/cisco-vpn.sh.orig-20251228-143916 b/apps/rego-tunnel/shared/cisco-vpn.sh.orig-20251228-143916 new file mode 100644 index 0000000..916d1aa --- /dev/null +++ b/apps/rego-tunnel/shared/cisco-vpn.sh.orig-20251228-143916 @@ -0,0 +1,729 @@ +#!/bin/bash + +# Dover VPN Connection Script with Semi-Automation +# Usage: ./cisco-vpn.sh [-m|--menu] [-r|--routes] [-h|--hosts] [--help] +# +# Options: +# -m, --menu Skip auto-login, show menu directly +# -r, --routes Show current routing table and exit +# -h, --hosts Show /etc/hosts and exit +# -s, --status Show VPN and network status and exit +# --help Show this help message +# +# Keyboard shortcuts (global, work anywhere): +# Ctrl+1 - Type email +# Ctrl+2 - Type password +# Ctrl+3 - Type TOTP code +# Ctrl+4 - Type email + Tab + password (combo) +# Ctrl+5 - Full sequence: email + Tab + password + Tab + TOTP + Enter + +EMAIL="c-azaw@regoproducts.com" +PASSWORD='Lz@83278327$$@@' +TOTP_SECRET="rzqtqskdwkhz6zyr" +VPN_HOST="vpn-ord1.dovercorp.com" +TARGET_IP="10.35.33.230" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +GRAY='\033[0;90m' +NC='\033[0m' + +# Flags +SKIP_AUTO_LOGIN=false + +# Logging function with timestamp +log() { + local level="$1" + local msg="$2" + local timestamp=$(date '+%H:%M:%S') + case $level in + INFO) echo -e "${GRAY}[$timestamp]${NC} ${GREEN}[INFO]${NC} $msg" ;; + WARN) echo -e "${GRAY}[$timestamp]${NC} ${YELLOW}[WARN]${NC} $msg" ;; + ERROR) echo -e "${GRAY}[$timestamp]${NC} ${RED}[ERROR]${NC} $msg" ;; + DEBUG) echo -e "${GRAY}[$timestamp]${NC} ${CYAN}[DEBUG]${NC} $msg" ;; + CMD) echo -e "${GRAY}[$timestamp]${NC} ${GRAY}[CMD]${NC} $msg" ;; + *) echo -e "${GRAY}[$timestamp]${NC} $msg" ;; + esac +} + +# Run command with logging +run_cmd() { + local desc="$1" + shift + log CMD "$desc: $*" + output=$("$@" 2>&1) + local rc=$? + if [ -n "$output" ]; then + echo "$output" | while IFS= read -r line; do + echo -e " ${GRAY}│${NC} $line" + done + fi + return $rc +} + +# Show help +show_help() { + echo -e "${CYAN}Dover VPN Connection Script${NC}" + echo "" + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " -m, --menu Skip auto-login, show menu directly" + echo " -r, --routes Show current routing table and exit" + echo " -h, --hosts Show /etc/hosts and exit" + echo " -s, --status Show VPN and network status and exit" + echo " --help Show this help message" + echo "" + echo "Menu Options:" + echo " 1 - Start Cisco AnyConnect (kill existing + launch)" + echo " 2 - Copy credentials to clipboard (one by one)" + echo " 3 - Show live TOTP" + echo " 4 - Setup IP forwarding rules only" + echo " 5 - Test connection to target" + echo " 6 - Show network status" + echo " 7 - Kill all Cisco processes" + echo " 8 - Show routing table" + echo " 9 - Show /etc/hosts" + echo " e - Edit /etc/hosts" + echo " q - Quit" +} + +# Show routing table +show_routes() { + echo -e "${CYAN}========================================${NC}" + echo -e "${CYAN} Current Routing Table ${NC}" + echo -e "${CYAN}========================================${NC}" + echo "" + echo -e "${GREEN}IPv4 Routes:${NC}" + ip -4 route show | while IFS= read -r line; do + # Highlight VPN-related routes + if echo "$line" | grep -qE "cscotun|tun[0-9]"; then + echo -e " ${YELLOW}$line${NC}" + elif echo "$line" | grep -qE "10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\."; then + echo -e " ${CYAN}$line${NC}" + else + echo " $line" + fi + done + echo "" + echo -e "${GREEN}VPN Interface:${NC}" + local vpn_iface=$(get_vpn_interface) + if [ -n "$vpn_iface" ]; then + ip addr show "$vpn_iface" 2>/dev/null | grep -E "inet|link" | while IFS= read -r line; do + echo " $line" + done + else + echo -e " ${RED}No VPN interface found${NC}" + fi +} + +# Show /etc/hosts +show_hosts() { + echo -e "${CYAN}========================================${NC}" + echo -e "${CYAN} /etc/hosts ${NC}" + echo -e "${CYAN}========================================${NC}" + echo "" + cat -n /etc/hosts +} + +# Edit /etc/hosts +edit_hosts() { + local editor="${EDITOR:-nano}" + if command -v "$editor" &>/dev/null; then + sudo "$editor" /etc/hosts + else + log ERROR "No editor found. Set EDITOR environment variable." + log INFO "Try: sudo nano /etc/hosts" + fi +} + +# Function to get current TOTP +get_totp() { + oathtool --totp -b "$TOTP_SECRET" +} + +# Function to detect VPN tunnel interface dynamically +get_vpn_interface() { + # Look for cscotun* or tun* interfaces that are UP + local iface=$(ip link show | grep -oP '(cscotun\d+|tun\d+)(?=:.*UP)' | head -1) + if [ -z "$iface" ]; then + # Fallback: any cscotun interface + iface=$(ip link show | grep -oP 'cscotun\d+' | head -1) + fi + echo "$iface" +} + +# Function to get VM's IP on the bridge network (for container routing) +get_vm_bridge_ip() { + # Get IP from ens3 (main adapter with 100.100.0.x) + ip addr show ens3 2>/dev/null | grep -oP 'inet \K[\d.]+' | head -1 +} + +# Function to get container gateway IP +get_container_gateway() { + # The container bridge is at 100.100.0.1 + echo "100.100.0.1" +} + +# Function to get VPN tunnel IP +get_vpn_ip() { + local iface=$(get_vpn_interface) + if [ -n "$iface" ]; then + ip addr show "$iface" 2>/dev/null | grep -oP 'inet \K[\d.]+' | head -1 + fi +} + +# Start xbindkeys for keyboard macros +start_xbindkeys() { + log INFO "Starting keyboard macro listener (xbindkeys)..." + + # Kill any existing xbindkeys + pkill xbindkeys 2>/dev/null + sleep 0.5 + + # Start xbindkeys + xbindkeys -f ~/.xbindkeysrc 2>/dev/null & + XBINDKEYS_PID=$! + + if pgrep xbindkeys >/dev/null; then + log DEBUG "xbindkeys started (PID: $(pgrep xbindkeys))" + log INFO "Keyboard shortcuts active: Ctrl+1=email, Ctrl+2=pass, Ctrl+3=TOTP, Ctrl+4=combo, Ctrl+5=all" + else + log WARN "Failed to start xbindkeys" + fi +} + +# Stop xbindkeys +stop_xbindkeys() { + if pgrep xbindkeys >/dev/null; then + log INFO "Stopping keyboard macro listener..." + pkill xbindkeys 2>/dev/null + log DEBUG "xbindkeys stopped" + fi +} + +# Kill all Cisco-related processes +kill_cisco_processes() { + log INFO "Killing all Cisco-related processes..." + + local killed=0 + local my_pid=$$ + local my_ppid=$(ps -o ppid= -p $$ | tr -d ' ') + + # Kill vpnui specifically (not just any process with "vpn" in name) + for pid in $(pgrep -x "vpnui" 2>/dev/null); do + if [ "$pid" != "$my_pid" ] && [ "$pid" != "$my_ppid" ]; then + log DEBUG "Killing vpnui (PID $pid)" + sudo kill -9 "$pid" 2>/dev/null && ((killed++)) + fi + done + + # Note: Don't kill vpnagentd - we need it running + + # Kill Cisco-specific processes by exact path + for proc in cstub cscan acwebsecagent vpndownloader; do + for pid in $(pgrep -x "$proc" 2>/dev/null); do + log DEBUG "Killing $proc (PID $pid)" + sudo kill -9 "$pid" 2>/dev/null && ((killed++)) + done + done + + # Kill openconnect (exact match) + for pid in $(pgrep -x "openconnect" 2>/dev/null); do + log DEBUG "Killing openconnect (PID $pid)" + sudo kill -9 "$pid" 2>/dev/null && ((killed++)) + done + + if [ $killed -eq 0 ]; then + log INFO "No Cisco processes were running" + else + log INFO "Killed $killed process(es)" + sleep 1 + fi +} + +# Function to setup iptables rules for forwarding +setup_forwarding() { + log INFO "Setting up IP forwarding rules for $TARGET_IP..." + + local vpn_iface=$(get_vpn_interface) + if [ -z "$vpn_iface" ]; then + log ERROR "No VPN interface found! Is VPN connected?" + return 1 + fi + + local vpn_ip=$(get_vpn_ip) + local vm_bridge_ip=$(get_vm_bridge_ip) + local container_gw=$(get_container_gateway) + + log DEBUG "VPN interface: $vpn_iface" + log DEBUG "VPN IP: $vpn_ip" + log DEBUG "VM bridge IP: $vm_bridge_ip" + log DEBUG "Container gateway: $container_gw" + + # Enable IP forwarding + run_cmd "Enabling IP forwarding" sudo sysctl -w net.ipv4.ip_forward=1 + + # NAT masquerade for traffic from container network (100.100.0.0/24) going through VPN + # This is the ONLY masquerade rule needed - source-based, not destination-based + if ! sudo iptables -t nat -C POSTROUTING -s 100.100.0.0/24 -o "$vpn_iface" -j MASQUERADE 2>/dev/null; then + run_cmd "Adding NAT masquerade for container network -> VPN" sudo iptables -t nat -A POSTROUTING -s 100.100.0.0/24 -o "$vpn_iface" -j MASQUERADE + else + log DEBUG "NAT masquerade for container network already exists" + fi + + # Forward rules + if ! sudo iptables -C FORWARD -d "$TARGET_IP" -j ACCEPT 2>/dev/null; then + run_cmd "Adding forward rule (to target)" sudo iptables -A FORWARD -d "$TARGET_IP" -j ACCEPT + else + log DEBUG "Forward rule (to target) already exists" + fi + + if ! sudo iptables -C FORWARD -s "$TARGET_IP" -j ACCEPT 2>/dev/null; then + run_cmd "Adding forward rule (from target)" sudo iptables -A FORWARD -s "$TARGET_IP" -j ACCEPT + else + log DEBUG "Forward rule (from target) already exists" + fi + + # Accept forwarding from container network + if ! sudo iptables -C FORWARD -s 100.100.0.0/24 -j ACCEPT 2>/dev/null; then + run_cmd "Adding forward rule (from container network)" sudo iptables -A FORWARD -s 100.100.0.0/24 -j ACCEPT + else + log DEBUG "Forward rule (from container network) already exists" + fi + + if ! sudo iptables -C FORWARD -d 100.100.0.0/24 -j ACCEPT 2>/dev/null; then + run_cmd "Adding forward rule (to container network)" sudo iptables -A FORWARD -d 100.100.0.0/24 -j ACCEPT + else + log DEBUG "Forward rule (to container network) already exists" + fi + + # Cisco VPN chain bypass (insert at top if chain exists) + if sudo iptables -L ciscovpn -n &>/dev/null; then + if ! sudo iptables -C ciscovpn -o "$vpn_iface" -d "$TARGET_IP" -j ACCEPT 2>/dev/null; then + run_cmd "Adding ciscovpn bypass (outbound)" sudo iptables -I ciscovpn 1 -o "$vpn_iface" -d "$TARGET_IP" -j ACCEPT + else + log DEBUG "Ciscovpn bypass (outbound) already exists" + fi + + if ! sudo iptables -C ciscovpn -i "$vpn_iface" -s "$TARGET_IP" -j ACCEPT 2>/dev/null; then + run_cmd "Adding ciscovpn bypass (inbound)" sudo iptables -I ciscovpn 2 -i "$vpn_iface" -s "$TARGET_IP" -j ACCEPT + else + log DEBUG "Ciscovpn bypass (inbound) already exists" + fi + + # Also allow container network through ciscovpn chain + if ! sudo iptables -C ciscovpn -s 100.100.0.0/24 -j ACCEPT 2>/dev/null; then + run_cmd "Adding ciscovpn bypass (container network)" sudo iptables -I ciscovpn 3 -s 100.100.0.0/24 -j ACCEPT + fi + else + log DEBUG "ciscovpn chain does not exist (yet)" + fi + + log INFO "Forwarding rules configured" + echo "" + log INFO "Container should now be able to reach $TARGET_IP through this VM" + echo "" +} + +# Copy credentials to clipboard as alternative +copy_to_clipboard() { + log INFO "Starting clipboard credential rotation..." + echo "" + + log INFO "Copying EMAIL to clipboard" + echo "$EMAIL" | xclip -selection clipboard + echo -e " ${CYAN}Email ready: $EMAIL${NC}" + echo -e " Paste now (Ctrl+V), then press ${GREEN}Enter${NC} here for password..." + read -r + + log INFO "Copying PASSWORD to clipboard" + echo "$PASSWORD" | xclip -selection clipboard + echo -e " ${CYAN}Password ready${NC}" + echo -e " Paste now (Ctrl+V), then press ${GREEN}Enter${NC} here for TOTP..." + read -r + + TOTP=$(get_totp) + log INFO "Copying TOTP to clipboard" + echo "$TOTP" | xclip -selection clipboard + echo -e " ${CYAN}TOTP ready: $TOTP${NC}" + echo -e " Paste now (Ctrl+V)" +} + +# Print current TOTP with countdown +show_totp() { + log INFO "Starting live TOTP display (Ctrl+C to stop)" + echo "" + while true; do + TOTP=$(get_totp) + SECONDS_LEFT=$((30 - ($(date +%s) % 30))) + echo -ne "\r ${CYAN}Current TOTP:${NC} ${GREEN}$TOTP${NC} (expires in ${YELLOW}${SECONDS_LEFT}s${NC}) " + sleep 1 + done +} + +# Show network status +show_network_status() { + log INFO "Current network status:" + + # VM IPs + echo "" + log DEBUG "VM Network Interfaces:" + ip -4 addr show | grep -E "inet |^[0-9]+:" | while IFS= read -r line; do + echo -e " ${GRAY}│${NC} $line" + done + + # VPN status + echo "" + local vpn_iface=$(get_vpn_interface) + if [ -n "$vpn_iface" ]; then + local vpn_ip=$(get_vpn_ip) + log INFO "VPN Status: ${GREEN}CONNECTED${NC}" + log DEBUG " Interface: $vpn_iface" + log DEBUG " VPN IP: $vpn_ip" + else + log WARN "VPN Status: ${RED}NOT CONNECTED${NC}" + fi + + # Bridge IP (for container routing) + local vm_bridge_ip=$(get_vm_bridge_ip) + if [ -n "$vm_bridge_ip" ]; then + log DEBUG "VM IP on bridge: $vm_bridge_ip" + fi + + # Container gateway + local container_gw=$(get_container_gateway) + log DEBUG "Container gateway: $container_gw" + + # Default gateway + echo "" + log DEBUG "Default gateway:" + ip route show default | while IFS= read -r line; do + echo -e " ${GRAY}│${NC} $line" + done + + echo "" +} + +# Main menu +main_menu() { + echo -e "${GREEN}Options:${NC}" + echo -e " ${CYAN}1${NC} - Start Cisco AnyConnect (kill existing + launch)" + echo -e " ${CYAN}2${NC} - Copy credentials to clipboard (one by one)" + echo -e " ${CYAN}3${NC} - Show live TOTP" + echo -e " ${CYAN}4${NC} - Setup IP forwarding rules only" + echo -e " ${CYAN}5${NC} - Test connection to $TARGET_IP" + echo -e " ${CYAN}6${NC} - Show network status" + echo -e " ${CYAN}7${NC} - Kill all Cisco processes" + echo -e " ${CYAN}8${NC} - Show routing table" + echo -e " ${CYAN}9${NC} - Show /etc/hosts" + echo -e " ${CYAN}e${NC} - Edit /etc/hosts" + echo -e " ${CYAN}q${NC} - Quit" + echo "" +} + +# Check if VPN is already connected +check_vpn_status() { + local vpn_iface=$(get_vpn_interface) + if [ -n "$vpn_iface" ]; then + local vpn_ip=$(get_vpn_ip) + log INFO "VPN is ${GREEN}CONNECTED${NC}" + log DEBUG " Interface: $vpn_iface" + log DEBUG " VPN IP: $vpn_ip" + return 0 + else + log WARN "VPN is ${RED}NOT CONNECTED${NC}" + return 1 + fi +} + +# Focus on Cisco AnyConnect window +focus_vpn_window() { + local win_id=$(xdotool search --name "Cisco" 2>/dev/null | head -1) + if [ -n "$win_id" ]; then + xdotool windowactivate --sync "$win_id" 2>/dev/null + sleep 0.3 + return 0 + fi + return 1 +} + +# Auto-login sequence using xdotool (no auto-focus, types to active window) +auto_login() { + log INFO "Starting automated login sequence..." + + # Wait for UI to fully load + log DEBUG "Waiting 5s for UI to load..." + sleep 5 + + # Press Enter to initiate connection + log DEBUG "Pressing Enter to start connection..." + xdotool key Return + sleep 5 + + # Type email + log DEBUG "Typing email..." + xdotool type --delay 50 "$EMAIL" + xdotool key Return + sleep 5 + + # Type password + log DEBUG "Typing password..." + xdotool type --delay 50 "$PASSWORD" + xdotool key Return + sleep 5 + + # Type TOTP + log DEBUG "Typing TOTP..." + local totp=$(oathtool --totp -b "$TOTP_SECRET") + log DEBUG "TOTP: $totp" + xdotool type --delay 50 "$totp" + xdotool key Return + sleep 5 + + # Extra enters for any confirmation dialogs + log DEBUG "Sending confirmation enters..." + xdotool key Return + sleep 2 + xdotool key Return + sleep 5 + xdotool key Return + + log INFO "Auto-login sequence completed" +} + +# Start Cisco AnyConnect with logging +start_anyconnect() { + local do_auto_login="$1" + log INFO "=== Starting Cisco AnyConnect VPN ===" + echo "" + + # Kill existing processes first + kill_cisco_processes + + # Start vpnagentd if not running + if ! pgrep -x vpnagentd >/dev/null; then + log INFO "Starting vpnagentd..." + sudo /opt/cisco/secureclient/bin/vpnagentd & + log DEBUG "Waiting for vpnagentd to initialize..." + sleep 5 + fi + + # Show credentials + log INFO "Credentials for SSO login:" + echo -e " ${CYAN}Email: $EMAIL${NC}" + echo -e " ${CYAN}Password: $PASSWORD${NC}" + TOTP=$(get_totp) + echo -e " ${CYAN}TOTP: $TOTP${NC}" + echo "" + + # Start AnyConnect with GPU/WebKit workarounds + log INFO "Launching Cisco AnyConnect UI..." + export GDK_BACKEND=x11 + export WEBKIT_DISABLE_DMABUF_RENDERER=1 + /opt/cisco/secureclient/bin/vpnui & + VPNUI_PID=$! + log DEBUG "vpnui started with PID $VPNUI_PID" + + if [ "$do_auto_login" = "true" ]; then + # Run auto-login in background + auto_login & + AUTO_LOGIN_PID=$! + log DEBUG "Auto-login started with PID $AUTO_LOGIN_PID" + else + log INFO "Manual login mode - use keyboard shortcuts or menu option 2 for credentials" + return 0 + fi + + # Wait for VPN to connect + log INFO "Waiting for VPN connection..." + local wait_count=0 + local max_wait=300 # 5 minutes + while [ -z "$(get_vpn_interface)" ]; do + sleep 2 + ((wait_count+=2)) + if [ $((wait_count % 10)) -eq 0 ]; then + log DEBUG "Still waiting for VPN... (${wait_count}s)" + fi + if [ $wait_count -ge $max_wait ]; then + log ERROR "Timeout waiting for VPN connection after ${max_wait}s" + stop_xbindkeys + return 1 + fi + done + + log INFO "VPN connected!" + local vpn_iface=$(get_vpn_interface) + local vpn_ip=$(get_vpn_ip) + log DEBUG " Interface: $vpn_iface" + log DEBUG " VPN IP: $vpn_ip" + + # Wait a bit for routes to stabilize + log DEBUG "Waiting for routes to stabilize..." + sleep 3 + + # Setup forwarding + setup_forwarding + + # Test connection + log INFO "Testing connection to $TARGET_IP..." + if ping -c 2 -W 3 "$TARGET_IP" &>/dev/null; then + log INFO "Connection test: ${GREEN}SUCCESS${NC}" + else + log WARN "Connection test: ${RED}FAILED${NC} (may need manual route on Windows)" + fi +} + +# Parse command line arguments +parse_args() { + while [[ $# -gt 0 ]]; do + case $1 in + -m|--menu) + SKIP_AUTO_LOGIN=true + shift + ;; + -r|--routes) + show_routes + exit 0 + ;; + -h|--hosts) + show_hosts + exit 0 + ;; + -s|--status) + echo -e "${CYAN}========================================${NC}" + echo -e "${CYAN} VPN and Network Status ${NC}" + echo -e "${CYAN}========================================${NC}" + echo "" + check_vpn_status + echo "" + show_network_status + exit 0 + ;; + --help) + show_help + exit 0 + ;; + *) + echo "Unknown option: $1" + echo "Use --help for usage information" + exit 1 + ;; + esac + done +} + +# Main +parse_args "$@" + +echo -e "${CYAN}========================================${NC}" +echo -e "${CYAN} Dover VPN Connection Script ${NC}" +echo -e "${CYAN}========================================${NC}" +echo "" + +log INFO "Script started" +echo "" + +# Check current status +if check_vpn_status; then + echo "" + log INFO "VPN already connected. Setting up forwarding..." + setup_forwarding +elif [ "$SKIP_AUTO_LOGIN" = "true" ]; then + echo "" + log INFO "Menu mode - skipping auto-login" +else + echo "" + log INFO "Auto-starting VPN connection..." + echo "" + start_anyconnect "true" +fi + +echo "" +main_menu + +while true; do + echo -ne "${CYAN}Choice: ${NC}" + read -r choice + + case $choice in + 1) + echo "" + if [ "$SKIP_AUTO_LOGIN" = "true" ]; then + start_anyconnect "false" + else + start_anyconnect "true" + fi + echo "" + main_menu + ;; + 2) + echo "" + copy_to_clipboard + echo "" + main_menu + ;; + 3) + echo "" + show_totp + echo "" + main_menu + ;; + 4) + echo "" + setup_forwarding + echo "" + main_menu + ;; + 5) + echo "" + log INFO "Testing connection to $TARGET_IP..." + if ping -c 3 "$TARGET_IP"; then + log INFO "Connection test: ${GREEN}SUCCESS${NC}" + else + log ERROR "Connection test: ${RED}FAILED${NC}" + fi + echo "" + main_menu + ;; + 6) + echo "" + show_network_status + main_menu + ;; + 7) + echo "" + kill_cisco_processes + echo "" + main_menu + ;; + 8) + echo "" + show_routes + echo "" + main_menu + ;; + 9) + echo "" + show_hosts + echo "" + main_menu + ;; + e|E) + echo "" + edit_hosts + echo "" + main_menu + ;; + q|Q) + log INFO "Goodbye!" + exit 0 + ;; + *) + log ERROR "Invalid choice" + ;; + esac +done diff --git a/apps/rego-tunnel/shared/fix-netplan-ens3.sh b/apps/rego-tunnel/shared/fix-netplan-ens3.sh new file mode 100644 index 0000000..0ef2006 --- /dev/null +++ b/apps/rego-tunnel/shared/fix-netplan-ens3.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +set -euo pipefail + +MAC="52:54:00:12:34:56" +IP_CIDR="100.100.0.2/24" +GW="100.100.0.1" +DNS1="8.8.8.8" +DNS2="1.1.1.1" + +backup_dir="/etc/netplan/backup-$(date +%Y%m%d-%H%M%S)" +mkdir -p "$backup_dir" + +# Back up all current netplan YAMLs +cp -a /etc/netplan/*.yaml "$backup_dir"/ 2>/dev/null || true + +# Move any NetworkManager-generated netplan snippets out of the way +if ls /etc/netplan/90-NM-*.yaml >/dev/null 2>&1; then + mv /etc/netplan/90-NM-*.yaml "$backup_dir"/ || true +fi + +cat > /etc/netplan/00-installer-config.yaml </dev/null || true + +netplan generate +netplan apply + +sleep 2 + +echo "==== Interfaces (ens3/ens4) ====" +ip -br a | grep -E '^(ens3|ens4)\b' || true + +echo "==== Routes ====" +ip route | sed -n '1,10p' + +echo "==== Ping ====" +ping -c 1 -W 2 1.1.1.1 diff --git a/apps/rego-tunnel/shared/generate-traefik-basicauth.sh b/apps/rego-tunnel/shared/generate-traefik-basicauth.sh new file mode 100644 index 0000000..e265963 --- /dev/null +++ b/apps/rego-tunnel/shared/generate-traefik-basicauth.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Generates a Traefik BasicAuth users string using Apache MD5 (apr1). +# Output format matches Traefik label: basicauth.users="user:hash" +# +# Usage: +# ./generate-traefik-basicauth.sh +# +# Recommended to paste output into: +# /etc/runtipi/app-data/runtipi/rego-tunnel/app.env +# as: +# NOVNC_BASIC_AUTH_USERS='' + +user="${1:-}" +pass="${2:-}" + +if [[ -z "$user" || -z "$pass" ]]; then + echo "Usage: $0 " >&2 + exit 2 +fi + +if ! command -v openssl >/dev/null 2>&1; then + echo "openssl not found; cannot generate apr1 hash" >&2 + exit 1 +fi + +hash="$(openssl passwd -apr1 "$pass")" +printf "%s:%s\n" "$user" "$hash" diff --git a/apps/rego-tunnel/shared/host-routing.sh b/apps/rego-tunnel/shared/host-routing.sh new file mode 100644 index 0000000..58dba73 --- /dev/null +++ b/apps/rego-tunnel/shared/host-routing.sh @@ -0,0 +1,203 @@ +#!/usr/bin/env bash +set -euo pipefail + +ACTION="${1:-start}" + +APP_ENV="/etc/runtipi/app-data/runtipi/rego-tunnel/app.env" +if [[ -f "$APP_ENV" ]]; then + # shellcheck disable=SC1090 + source "$APP_ENV" +fi + +CONTAINER_NAME="${CONTAINER_NAME:-rego-tunnel_runtipi-rego-tunnel-1}" +# Prefer the user-config network (stable host bridge name) if it exists. +NET_NAME="${NET_NAME:-}" +NET_NAME_FALLBACK="${NET_NAME_FALLBACK:-rego-tunnel_runtipi_network}" +NET_NAME_PREFERRED="${NET_NAME_PREFERRED:-rego-tunnel_runtipi_vpn_static}" +TARGET_IP="${TARGET_IP:-10.35.33.230}" +HOST_BRIDGE_ALIAS="${HOST_BRIDGE_ALIAS:-br-rego-tunnel}" +VM_SUBNET="${VM_SUBNET:-100.100.0.0}" + +TARGET_CIDR="$TARGET_IP/32" +VM_SUBNET_CIDR="${VM_SUBNET_CIDR:-${VM_SUBNET}/24}" + +log() { + echo "[rego-routing] $*" >&2 +} + +remove_stale_nft_redirects() { + # Some previous setups may have installed a transparent proxy (REDSOCKS) + # redirecting TCP destined for TARGET_IP to a local port (e.g. 12345). + # That breaks the "single target IP via container" gateway model. + # + # We only remove rules that match our TARGET_IP in the ip nat/REDSOCKS chain. + if ! command -v nft >/dev/null 2>&1; then + return 0 + fi + + local handles + handles="$(nft -a list chain ip nat REDSOCKS 2>/dev/null | awk -v ip="$TARGET_IP" '/ip daddr/ && $0 ~ ip && /redirect to :/ {for(i=1;i<=NF;i++) if($i=="handle") print $(i+1)}' || true)" + if [[ -z "$handles" ]]; then + return 0 + fi + + while read -r h; do + [[ -n "$h" ]] || continue + nft delete rule ip nat REDSOCKS handle "$h" 2>/dev/null || true + done <<< "$handles" +} + +remove_stale_host_routes() { + # The VM's internal subnet (default 100.100.0.0/24) should live only inside + # the rego-tunnel container/VM. If the host has a route for it (left over from + # older scripts), it can cause confusing routing. + ip -4 route del "$VM_SUBNET_CIDR" 2>/dev/null || true +} + +get_lan_if() { + # Avoid early-exit pipelines (pipefail + SIGPIPE) by not exiting awk early. + ip route show default 0.0.0.0/0 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev"){print $(i+1)}}' +} + +choose_net_name() { + if [[ -n "${NET_NAME:-}" ]]; then + echo "$NET_NAME" + return 0 + fi + + if docker network inspect "$NET_NAME_PREFERRED" >/dev/null 2>&1; then + echo "$NET_NAME_PREFERRED" + return 0 + fi + + echo "$NET_NAME_FALLBACK" +} + +get_bridge_and_container_ip() { + local chosen net_id bridge_opt bridge cip + chosen="$(choose_net_name)" + + net_id="$(docker network inspect -f '{{.Id}}' "$chosen" 2>/dev/null || true)" + if [[ -z "$net_id" ]]; then + return 1 + fi + + # If Docker network specifies an explicit bridge name, use it. + bridge_opt="$(docker network inspect -f '{{index .Options "com.docker.network.bridge.name"}}' "$chosen" 2>/dev/null || true)" + if [[ -n "$bridge_opt" && "$bridge_opt" != "" ]]; then + bridge="$bridge_opt" + else + bridge="br-${net_id:0:12}" + fi + + cip="$(docker inspect -f "{{(index .NetworkSettings.Networks \"$chosen\").IPAddress}}" "$CONTAINER_NAME" 2>/dev/null || true)" + if [[ -z "$cip" ]]; then + return 1 + fi + + echo "$bridge $cip" +} + +set_bridge_alias() { + local bridge="$1" + + # Docker creates the bridge name (br-); renaming it breaks Docker. + # Setting an alias is safe and improves readability in `ip link` output. + if [[ -n "${HOST_BRIDGE_ALIAS:-}" ]]; then + ip link set dev "$bridge" alias "$HOST_BRIDGE_ALIAS" 2>/dev/null || true + fi +} + +container_apply() { + docker exec "$CONTAINER_NAME" sh -lc 'set -e +BR="${BRIDGE_NAME:-br-rego-vpn}" +VM="${VM_NET_IP:-100.100.0.2}" +TARGET="${TARGET_IP:-10.35.33.230}" + +echo 1 > /proc/sys/net/ipv4/ip_forward +ip route replace "$TARGET/32" via "$VM" dev "$BR" 2>/dev/null || ip route replace "$TARGET" via "$VM" dev "$BR" 2>/dev/null || true +iptables -C FORWARD -d "$TARGET" -j ACCEPT 2>/dev/null || iptables -A FORWARD -d "$TARGET" -j ACCEPT +iptables -C FORWARD -s "$TARGET" -j ACCEPT 2>/dev/null || iptables -A FORWARD -s "$TARGET" -j ACCEPT +iptables -t nat -C POSTROUTING -o "$BR" -d "$TARGET" -j MASQUERADE 2>/dev/null || iptables -t nat -A POSTROUTING -o "$BR" -d "$TARGET" -j MASQUERADE +' +} + +container_remove() { + docker exec "$CONTAINER_NAME" sh -lc 'set -e +BR="${BRIDGE_NAME:-br-rego-vpn}" +VM="${VM_NET_IP:-100.100.0.2}" +TARGET="${TARGET_IP:-10.35.33.230}" + +ip route del "$TARGET/32" via "$VM" dev "$BR" 2>/dev/null || true +iptables -t nat -D POSTROUTING -o "$BR" -d "$TARGET" -j MASQUERADE 2>/dev/null || true +' || true +} + +apply_host_rules() { + local lan_if bridge cip + + remove_stale_nft_redirects + remove_stale_host_routes + + lan_if="$(get_lan_if)" + if [[ -z "$lan_if" ]]; then + log "WARN: could not detect LAN default interface; skipping host iptables" + fi + + read -r bridge cip < <(get_bridge_and_container_ip) + set_bridge_alias "$bridge" + + echo 1 > /proc/sys/net/ipv4/ip_forward + + ip route replace "$TARGET_CIDR" via "$cip" dev "$bridge" + + if [[ -n "${lan_if:-}" ]]; then + iptables -C DOCKER-USER -i "$lan_if" -o "$bridge" -d "$TARGET_CIDR" -j ACCEPT 2>/dev/null || \ + iptables -I DOCKER-USER 1 -i "$lan_if" -o "$bridge" -d "$TARGET_CIDR" -j ACCEPT + + iptables -C DOCKER-USER -i "$bridge" -o "$lan_if" -s "$TARGET_CIDR" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || \ + iptables -I DOCKER-USER 1 -i "$bridge" -o "$lan_if" -s "$TARGET_CIDR" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT + fi + + container_apply + + log "OK: routing $TARGET_CIDR via $cip ($bridge)" +} + +remove_host_rules() { + local lan_if bridge cip + + lan_if="$(get_lan_if)" + if read -r bridge cip < <(get_bridge_and_container_ip); then + ip route del "$TARGET_CIDR" via "$cip" dev "$bridge" 2>/dev/null || true + + if [[ -n "${lan_if:-}" ]]; then + iptables -D DOCKER-USER -i "$lan_if" -o "$bridge" -d "$TARGET_CIDR" -j ACCEPT 2>/dev/null || true + iptables -D DOCKER-USER -i "$bridge" -o "$lan_if" -s "$TARGET_CIDR" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true + fi + fi + + container_remove +} + +case "$ACTION" in + start) + # Wait briefly for docker + container/network to be ready. + for _ in {1..30}; do + if get_bridge_and_container_ip >/dev/null 2>&1; then + apply_host_rules + exit 0 + fi + sleep 2 + done + log "ERROR: container/network not ready; no routing applied" + exit 0 + ;; + stop) + remove_host_rules + ;; + *) + echo "Usage: $0 {start|stop}" >&2 + exit 2 + ;; +esac diff --git a/apps/rego-tunnel/shared/install-cisco-vpn-autostart.sh b/apps/rego-tunnel/shared/install-cisco-vpn-autostart.sh new file mode 100644 index 0000000..97aff20 --- /dev/null +++ b/apps/rego-tunnel/shared/install-cisco-vpn-autostart.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Installs an XDG autostart entry so Cisco VPN auto-connect runs when the GUI user logs in. +# Intended to be executed inside the VM. +# +# Usage: +# ./install-cisco-vpn-autostart.sh [username] + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DESKTOP_FILE_SRC="$SCRIPT_DIR/cisco-vpn-connect.desktop" + +if [[ ! -f "$DESKTOP_FILE_SRC" ]]; then + echo "Missing $DESKTOP_FILE_SRC" >&2 + exit 1 +fi + +TARGET_USER="${1:-${SUDO_USER:-$(id -un)}}" +TARGET_HOME="$(getent passwd "$TARGET_USER" | cut -d: -f6)" + +if [[ -z "$TARGET_HOME" || ! -d "$TARGET_HOME" ]]; then + echo "Could not determine home directory for user: $TARGET_USER" >&2 + exit 1 +fi + +AUTOSTART_DIR="$TARGET_HOME/.config/autostart" +DEST_FILE="$AUTOSTART_DIR/cisco-vpn-connect.desktop" + +sudo -n mkdir -p "$AUTOSTART_DIR" +sudo -n install -m 0644 "$DESKTOP_FILE_SRC" "$DEST_FILE" +sudo -n chown "$TARGET_USER:$TARGET_USER" "$DEST_FILE" + +echo "Installed autostart entry: $DEST_FILE" +echo "It will run on next GUI login for user: $TARGET_USER" diff --git a/apps/rego-tunnel/shared/install-cisco-vpn-connect-service.sh b/apps/rego-tunnel/shared/install-cisco-vpn-connect-service.sh new file mode 100644 index 0000000..2c8ada0 --- /dev/null +++ b/apps/rego-tunnel/shared/install-cisco-vpn-connect-service.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Installs and enables the cisco-vpn systemd unit on the machine where you run this. +# Intended to be executed inside the VM. + +UNIT_SRC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +UNIT_NAME="cisco-vpn-connect.service" + +sudo install -m 0644 "$UNIT_SRC_DIR/$UNIT_NAME" "/etc/systemd/system/$UNIT_NAME" +sudo systemctl daemon-reload +sudo systemctl enable --now "$UNIT_NAME" + +sudo systemctl --no-pager --full status "$UNIT_NAME" || true diff --git a/apps/rego-tunnel/shared/install-cisco-vpn-user-service.sh b/apps/rego-tunnel/shared/install-cisco-vpn-user-service.sh new file mode 100644 index 0000000..d96219b --- /dev/null +++ b/apps/rego-tunnel/shared/install-cisco-vpn-user-service.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Installs a systemd *user* service to run Cisco VPN auto-connect in the logged-in GUI session. +# Intended to be executed inside the VM as the desktop user (not root). +# +# Usage: +# ./install-cisco-vpn-user-service.sh + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +UNIT_SRC="$SCRIPT_DIR/cisco-vpn-connect.service.user" +UNIT_NAME="cisco-vpn-connect.service" + +if [[ ! -f "$UNIT_SRC" ]]; then + echo "Missing $UNIT_SRC" >&2 + exit 1 +fi + +mkdir -p "$HOME/.config/systemd/user" +install -m 0644 "$UNIT_SRC" "$HOME/.config/systemd/user/$UNIT_NAME" + +systemctl --user daemon-reload +systemctl --user enable --now "$UNIT_NAME" + +systemctl --user --no-pager --full status "$UNIT_NAME" || true diff --git a/apps/rego-tunnel/shared/setup-network.sh b/apps/rego-tunnel/shared/setup-network.sh new file mode 100644 index 0000000..8ecd67c --- /dev/null +++ b/apps/rego-tunnel/shared/setup-network.sh @@ -0,0 +1,149 @@ +#!/bin/bash +# Setup TAP/Bridge networking for QEMU VM +# Defaults: +# BRIDGE_NAME=br-rego-vpn +# TAP_NAME=tap0 +# BRIDGE_CIDR=100.100.0.1/24 +# VM_NET_IP=100.100.0.2 +# VM_SUBNET=100.100.0.0/24 + +set -e + +BRIDGE_NAME="${BRIDGE_NAME:-br-rego-vpn}" +TAP_NAME="${TAP_NAME:-tap0}" +BRIDGE_CIDR="${BRIDGE_CIDR:-100.100.0.1}" +VM_NET_IP="${VM_NET_IP:-100.100.0.2}" +VM_SUBNET="${VM_SUBNET:-100.100.0.0}" +TARGET_IP="${TARGET_IP:-10.35.33.230}" + +# Optional second bridge/tap for a second VM NIC (pure L2 with the container). +# This is opt-in: set BRIDGE2_NAME and TAP2_NAME (and optionally BRIDGE2_CIDR). +BRIDGE2_NAME="${BRIDGE2_NAME:-}" +TAP2_NAME="${TAP2_NAME:-}" +BRIDGE2_CIDR="${BRIDGE2_CIDR:-}" +BRIDGE2_UPLINK_IF="${BRIDGE2_UPLINK_IF:-}" + +if [[ "$BRIDGE_CIDR" != */* ]]; then + BRIDGE_CIDR="$BRIDGE_CIDR/24" +fi + +if [[ "$VM_SUBNET" != */* ]]; then + VM_SUBNET="$VM_SUBNET/24" +fi + +# Pick the outbound interface based on the container's default route. +# (In Docker, this is not always eth1 when multiple networks are attached.) +WAN_IF="$(ip route show default 0.0.0.0/0 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev"){print $(i+1); exit}}')" +if [ -z "${WAN_IF}" ]; then + WAN_IF="eth1" +fi + +# Ensure bridge exists +if ! ip link show "$BRIDGE_NAME" &>/dev/null; then + ip link add "$BRIDGE_NAME" type bridge +fi + +# Ensure bridge has address and is up +ip addr show dev "$BRIDGE_NAME" | grep -qF "$BRIDGE_CIDR" || ip addr add "$BRIDGE_CIDR" dev "$BRIDGE_NAME" 2>/dev/null || true +ip link set "$BRIDGE_NAME" up + +# Ensure TAP exists +if ! ip link show "$TAP_NAME" &>/dev/null; then + ip tuntap add "$TAP_NAME" mode tap +fi + +# Ensure TAP is attached and up +ip link set "$TAP_NAME" master "$BRIDGE_NAME" 2>/dev/null || true +ip link set "$TAP_NAME" up + +# Optional second bridge/tap (no NAT rules are applied here) +if [ -n "$BRIDGE2_NAME" ] || [ -n "$TAP2_NAME" ]; then + if [ -z "$BRIDGE2_NAME" ] || [ -z "$TAP2_NAME" ]; then + echo "[rego-tunnel] WARN: BRIDGE2_NAME and TAP2_NAME must both be set to enable the second bridge" + else + # Optionally bridge an existing container uplink into BRIDGE2 (e.g. eth0/eth1) + # so the VM NIC shares the same L2 network as that uplink. + if [ -n "$BRIDGE2_UPLINK_IF" ]; then + if ! ip link show "$BRIDGE2_UPLINK_IF" &>/dev/null; then + echo "[rego-tunnel] WARN: BRIDGE2_UPLINK_IF=$BRIDGE2_UPLINK_IF not found; skipping uplink bridging" + BRIDGE2_UPLINK_IF="" + fi + fi + + if ! ip link show "$BRIDGE2_NAME" &>/dev/null; then + ip link add "$BRIDGE2_NAME" type bridge + fi + ip link set "$BRIDGE2_NAME" up + + # If an uplink interface is provided, move its IPv4 address to the bridge. + # This keeps the container reachable on that network while allowing the VM + # to participate in the same L2 broadcast domain. + if [ -n "$BRIDGE2_UPLINK_IF" ]; then + UPLINK_CIDR="$(ip -o -4 addr show dev "$BRIDGE2_UPLINK_IF" 2>/dev/null | awk '{print $4}' | head -n1)" + UPLINK_DEFAULT_GW="$(ip route show default dev "$BRIDGE2_UPLINK_IF" 2>/dev/null | awk '{print $3}' | head -n1)" + UPLINK_DEFAULT_METRIC="$(ip route show default dev "$BRIDGE2_UPLINK_IF" 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="metric"){print $(i+1); exit}}')" + if [ -z "$BRIDGE2_CIDR" ] && [ -n "$UPLINK_CIDR" ]; then + BRIDGE2_CIDR="$UPLINK_CIDR" + fi + if [ -n "$BRIDGE2_CIDR" ]; then + if [[ "$BRIDGE2_CIDR" != */* ]]; then + BRIDGE2_CIDR="$BRIDGE2_CIDR/24" + fi + ip addr show dev "$BRIDGE2_NAME" | grep -qF "$BRIDGE2_CIDR" || ip addr add "$BRIDGE2_CIDR" dev "$BRIDGE2_NAME" 2>/dev/null || true + fi + # Remove IP from uplink and enslave it into the bridge + ip addr flush dev "$BRIDGE2_UPLINK_IF" 2>/dev/null || true + ip link set "$BRIDGE2_UPLINK_IF" master "$BRIDGE2_NAME" 2>/dev/null || true + ip link set "$BRIDGE2_UPLINK_IF" up 2>/dev/null || true + + # If the uplink carried the container default route, it may have been removed + # by the address flush. Restore it on the bridge. + if [ -n "$UPLINK_DEFAULT_GW" ]; then + if ip route show default 2>/dev/null | grep -q '^default '; then + ip route replace default via "$UPLINK_DEFAULT_GW" dev "$BRIDGE2_NAME" ${UPLINK_DEFAULT_METRIC:+metric "$UPLINK_DEFAULT_METRIC"} 2>/dev/null || true + else + ip route add default via "$UPLINK_DEFAULT_GW" dev "$BRIDGE2_NAME" ${UPLINK_DEFAULT_METRIC:+metric "$UPLINK_DEFAULT_METRIC"} 2>/dev/null || true + fi + fi + fi + + if ! ip link show "$TAP2_NAME" &>/dev/null; then + ip tuntap add "$TAP2_NAME" mode tap + fi + ip link set "$TAP2_NAME" master "$BRIDGE2_NAME" 2>/dev/null || true + ip link set "$TAP2_NAME" up + echo "[rego-tunnel] Second bridge enabled: $BRIDGE2_NAME (tap $TAP2_NAME)${BRIDGE2_UPLINK_IF:+ uplink $BRIDGE2_UPLINK_IF}" + fi +fi + +# Enable IP forwarding +echo 1 > /proc/sys/net/ipv4/ip_forward + +# Setup NAT/masquerade for outbound traffic from VM +iptables -t nat -C POSTROUTING -s "$VM_SUBNET" -o "$WAN_IF" -j MASQUERADE 2>/dev/null || \ + iptables -t nat -A POSTROUTING -s "$VM_SUBNET" -o "$WAN_IF" -j MASQUERADE + +# Ensure forwarding between the VM bridge and outbound interface +iptables -C FORWARD -i "$BRIDGE_NAME" -o "$WAN_IF" -j ACCEPT 2>/dev/null || \ + iptables -A FORWARD -i "$BRIDGE_NAME" -o "$WAN_IF" -j ACCEPT +iptables -C FORWARD -i "$WAN_IF" -o "$BRIDGE_NAME" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || \ + iptables -A FORWARD -i "$WAN_IF" -o "$BRIDGE_NAME" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT + +# Forward traffic destined for VPN networks to VM (TARGET_IP defaults to IBM i) +# The VM will route this through its VPN tunnel +iptables -C FORWARD -d "$TARGET_IP" -j ACCEPT 2>/dev/null || iptables -A FORWARD -d "$TARGET_IP" -j ACCEPT +iptables -C FORWARD -s "$TARGET_IP" -j ACCEPT 2>/dev/null || iptables -A FORWARD -s "$TARGET_IP" -j ACCEPT + +# Route to TARGET_IP through VM +ip route add "$TARGET_IP" via "$VM_NET_IP" 2>/dev/null || true + +echo "Network setup complete" +echo "Bridge: $BRIDGE_NAME = $BRIDGE_CIDR" +echo "TAP: $TAP_NAME attached to $BRIDGE_NAME" +echo "Route: $TARGET_IP via $VM_NET_IP (VM)" +echo "Outbound interface: ${WAN_IF}" + +if [ -n "$BRIDGE2_NAME" ] && [ -n "$TAP2_NAME" ]; then + echo "Bridge2: $BRIDGE2_NAME${BRIDGE2_CIDR:+ = $BRIDGE2_CIDR}" + echo "TAP2: $TAP2_NAME attached to $BRIDGE2_NAME" +fi \ No newline at end of file diff --git a/apps/rego-tunnel/shared/start-dnsmasq.sh b/apps/rego-tunnel/shared/start-dnsmasq.sh new file mode 100644 index 0000000..2b2c3b8 --- /dev/null +++ b/apps/rego-tunnel/shared/start-dnsmasq.sh @@ -0,0 +1,38 @@ +#!/bin/bash +set -euo pipefail + +BRIDGE_NAME="${BRIDGE_NAME:-br-rego-vpn}" +BRIDGE_CIDR="${BRIDGE_CIDR:-100.100.0.1}" +VM_NET_IP="${VM_NET_IP:-100.100.0.2}" +VM_MAC="${VM_MAC:-52:54:00:12:34:56}" + +LEASE_TIME="${LEASE_TIME:-12h}" +DNS_SERVERS="${DNS_SERVERS:-1.1.1.1,8.8.8.8}" + +if [[ "$BRIDGE_CIDR" != */* ]]; then + BRIDGE_CIDR="$BRIDGE_CIDR/24" +fi + +GATEWAY_IP="${BRIDGE_CIDR%%/*}" + +mkdir -p /etc/dnsmasq.d + +cat > /etc/dnsmasq.d/rego.conf < ${VM_NET_IP}" +exec dnsmasq --no-daemon --conf-file=/etc/dnsmasq.d/rego.conf diff --git a/apps/rego-tunnel/shared/start-vm.sh b/apps/rego-tunnel/shared/start-vm.sh new file mode 100644 index 0000000..6ba3031 --- /dev/null +++ b/apps/rego-tunnel/shared/start-vm.sh @@ -0,0 +1,116 @@ +#!/bin/bash +set -euo pipefail + +# If provided, extract ssh.zip to /root/.ssh (not baked into the image) +SSH_ZIP_PATH="/shared/ssh.zip" +SSH_ZIP_DEST="/root/.ssh" + +if [ -f "$SSH_ZIP_PATH" ]; then + mkdir -p "$SSH_ZIP_DEST" + chmod 700 "$SSH_ZIP_DEST" + + echo "[rego-tunnel] Extracting $SSH_ZIP_PATH -> $SSH_ZIP_DEST" + # Exclude editor swap/backup files; overwrite existing. + 7z x -y -aoa -o"$SSH_ZIP_DEST" "$SSH_ZIP_PATH" \ + -x!*.swp -x!*.swo -x!*.swx -x!*~ -x!.DS_Store >/dev/null + + find "$SSH_ZIP_DEST" -type d -exec chmod 700 {} \; + find "$SSH_ZIP_DEST" -type f -exec chmod 600 {} \; +else + echo "[rego-tunnel] No $SSH_ZIP_PATH found; skipping SSH zip extraction" +fi + +# Wait for network setup +sleep 2 + +TAP_NAME="${TAP_NAME:-tap0}" + +# Optional: provide a dedicated 9p export for host app-data (bind-mounted into the container at /shared/app-data) +TSCLIENT_PATH="/hostshare" +TSCLIENT_TAG="${TSCLIENT_TAG:-TSCLIENT}" +SHARED_TAG="${SHARED_TAG:-shared}" + +# Ensure the VM auto-mounts the 9p shares without manual steps. +# This edits the QCOW2 from the outside (idempotent) before QEMU boots. +AUTO_MOUNT_9P="${AUTO_MOUNT_9P:-1}" +if [ "$AUTO_MOUNT_9P" = "1" ]; then + QCOW2_PATH="/vm/linux-vm.qcow2" + NBD_DEV="${NBD_DEV:-/dev/nbd0}" + VMROOT_MNT="/mnt/vmroot" + + if [ -e "$QCOW2_PATH" ] && [ -e "$NBD_DEV" ]; then + echo "[rego-tunnel] Ensuring guest fstab mounts 9p tags ($SHARED_TAG, $TSCLIENT_TAG)" + modprobe nbd max_part=16 >/dev/null 2>&1 || true + qemu-nbd --disconnect "$NBD_DEV" >/dev/null 2>&1 || true + qemu-nbd --connect "$NBD_DEV" "$QCOW2_PATH" + sleep 1 + + # In containers, the kernel may create sysfs partition entries but not + # auto-create the corresponding /dev/nbd0p* nodes. Create them if missing. + base_dev="$(basename "$NBD_DEV")" + for sysfs_dev in /sys/class/block/${base_dev}p*; do + [ -e "$sysfs_dev" ] || continue + part_name="$(basename "$sysfs_dev")" + devnode="/dev/$part_name" + [ -e "$devnode" ] && continue + if [ -r "$sysfs_dev/dev" ]; then + IFS=: read -r major minor < "$sysfs_dev/dev" || true + if [ -n "${major:-}" ] && [ -n "${minor:-}" ]; then + mknod "$devnode" b "$major" "$minor" 2>/dev/null || true + chmod 660 "$devnode" 2>/dev/null || true + fi + fi + done + + mkdir -p "$VMROOT_MNT" + ROOT_PART="" + for part in "${NBD_DEV}"p*; do + [ -e "$part" ] || continue + # Try mount and detect a Linux root by presence of /etc/fstab and /etc/os-release + if mount "$part" "$VMROOT_MNT" >/dev/null 2>&1; then + if [ -d "$VMROOT_MNT/etc" ] && { [ -f "$VMROOT_MNT/etc/os-release" ] || [ -f "$VMROOT_MNT/usr/lib/os-release" ] || [ -f "$VMROOT_MNT/usr/share/os-release" ]; }; then + ROOT_PART="$part" + break + fi + umount "$VMROOT_MNT" >/dev/null 2>&1 || true + fi + done + + if [ -n "$ROOT_PART" ]; then + # already mounted from loop above + mkdir -p "$VMROOT_MNT/shared" "$VMROOT_MNT/hostshare" + + FSTAB="$VMROOT_MNT/etc/fstab" + # Add entries only if missing + grep -qE "^[[:space:]]*${SHARED_TAG}[[:space:]]+" "$FSTAB" || echo "${SHARED_TAG} /shared 9p trans=virtio,version=9p2000.L,msize=262144,_netdev,nofail,x-systemd.automount 0 0" >> "$FSTAB" + grep -qE "^[[:space:]]*${TSCLIENT_TAG}[[:space:]]+" "$FSTAB" || echo "${TSCLIENT_TAG} /hostshare 9p trans=virtio,version=9p2000.L,msize=262144,_netdev,nofail,x-systemd.automount 0 0" >> "$FSTAB" + + umount "$VMROOT_MNT" >/dev/null 2>&1 || true + else + echo "[rego-tunnel] WARN: could not locate guest root partition; skipping auto-mount setup" + lsblk -fp "$NBD_DEV" 2>/dev/null || true + blkid "$NBD_DEV"* 2>/dev/null || true + fi + + qemu-nbd --disconnect "$NBD_DEV" >/dev/null 2>&1 || true + else + echo "[rego-tunnel] WARN: missing $QCOW2_PATH or $NBD_DEV; skipping auto-mount setup" + fi +fi + +exec qemu-system-x86_64 \ + -enable-kvm \ + -cpu host \ + -m ${VM_RAM:-8G} \ + -smp ${VM_CPUS:-4} \ + -hda /vm/linux-vm.qcow2 \ + -fsdev local,id=fsdev0,path=/shared,security_model=none,multidevs=remap \ + -device virtio-9p-pci,fsdev=fsdev0,mount_tag="$SHARED_TAG" \ + -fsdev local,id=fsdev1,path="$TSCLIENT_PATH",security_model=none,multidevs=remap \ + -device virtio-9p-pci,fsdev=fsdev1,mount_tag="$TSCLIENT_TAG" \ + -netdev tap,id=net0,ifname="$TAP_NAME",script=no,downscript=no \ + -device virtio-net-pci,netdev=net0,mac=52:54:00:12:34:56 \ + -vnc :0 \ + -vga virtio \ + -usb \ + -device usb-tablet diff --git a/apps/rego-tunnel/shared/supervisord.conf b/apps/rego-tunnel/shared/supervisord.conf new file mode 100644 index 0000000..749a8af --- /dev/null +++ b/apps/rego-tunnel/shared/supervisord.conf @@ -0,0 +1,46 @@ +[supervisord] +nodaemon=true +logfile=/var/log/supervisord.log + +[program:network-setup] +command=/usr/local/bin/setup-network.sh +autostart=true +autorestart=false +startsecs=0 +priority=1 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:dnsmasq] +command=/usr/local/bin/start-dnsmasq.sh +autostart=true +autorestart=true +priority=5 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:sshd] +command=/usr/sbin/sshd -D +autostart=true +autorestart=true +priority=10 + +[program:qemu] +command=/usr/local/bin/start-vm.sh +autostart=true +autorestart=true +priority=20 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:novnc] +command=/usr/bin/websockify --web /usr/share/novnc 8006 localhost:5900 --auth-plugin BasicHTTPAuth --auth-source alexz:Az@83278327$$@@ +autostart=true +autorestart=true +priority=30