diff --git a/apps/cistech-tunnel/shared/host-routing.sh b/apps/cistech-tunnel/shared/host-routing.sh index 5bf2031..ce57328 100644 --- a/apps/cistech-tunnel/shared/host-routing.sh +++ b/apps/cistech-tunnel/shared/host-routing.sh @@ -93,8 +93,8 @@ remove_all() { done # Remove masquerade rule (using nft) - local handle - handle=$(nft -a list chain ip nat POSTROUTING 2>/dev/null | grep "saddr $LAN_SUBNET.*oifname.*$BRIDGE_NAME.*masquerade" | grep -oP 'handle \K\d+' | head -1) + local handle="" + handle=$(nft -a list chain ip nat POSTROUTING 2>/dev/null | grep "saddr $LAN_SUBNET.*oifname.*$BRIDGE_NAME.*masquerade" | grep -oP 'handle \K\d+' | head -1 || true) if [ -n "$handle" ]; then nft delete rule ip nat POSTROUTING handle "$handle" 2>/dev/null || true fi diff --git a/apps/cistech-tunnel/shared/openconnect-vpn b/apps/cistech-tunnel/shared/openconnect-vpn index 0ed8cb0..7bee434 100755 --- a/apps/cistech-tunnel/shared/openconnect-vpn +++ b/apps/cistech-tunnel/shared/openconnect-vpn @@ -1,16 +1,24 @@ #!/bin/bash -# OpenConnect-SSO VPN Connection Script -# Usage: ./openconnect-vpn [-c|--connect] [-d|--disconnect] [-s|--status] [--setup-keyring] [--help] +# Cistech VPN Connection Script with OpenConnect-SSO +# Usage: ./openconnect-vpn [-c|--connect] [-d|--disconnect] [-m|--menu] [-r|--routes] [-s|--status] [--help] +# +# Options: +# -c, --connect Connect to VPN and exit +# -d, --disconnect Disconnect VPN and exit +# -m, --menu Skip auto-connect, show menu directly +# -r, --routes Show routing table and exit +# -s, --status Show VPN status and exit +# --help Show this help message # Credentials from environment variables (set by runtipi) VPN_EMAIL="${VPN_EMAIL:-}" VPN_PASSWORD="${VPN_PASSWORD:-}" VPN_TOTP_SECRET="${VPN_TOTP_SECRET:-}" VPN_HOST="${VPN_HOST:-}" -TARGET_IP="${TARGET_IP:-}" +TARGET_IP="${TARGET_IP:-10.3.1.0}" VPN_INTERFACE="${VPN_INTERFACE:-tun0}" -IBMI_HOST="10.3.1.201" +CONTAINER_NETWORK="172.30.0.0/24" # Log directory LOG_DIR="/var/log/openconnect-vpn" @@ -35,15 +43,15 @@ NC='\033[0m' print_banner() { echo -e "${CYAN}========================================${NC}" - echo -e "${CYAN} OpenConnect-SSO VPN Connection ${NC}" + echo -e "${CYAN} Cistech VPN Connection Script ${NC}" echo -e "${CYAN}========================================${NC}" echo "" } # Flags +SKIP_AUTO_CONNECT=false DO_CONNECT=false DO_DISCONNECT=false -DO_SETUP_KEYRING=false # Logging log() { @@ -61,6 +69,7 @@ log() { WARN) echo -e "${GRAY}[$timestamp_short]${NC} ${YELLOW}[WARN]${NC} $msg" ;; ERROR) echo -e "${GRAY}[$timestamp_short]${NC} ${RED}[ERROR]${NC} $msg" ;; DEBUG) echo -e "${GRAY}[$timestamp_short]${NC} ${CYAN}[DEBUG]${NC} $msg" ;; + CMD) echo -e "${GRAY}[$timestamp_short]${NC} ${GRAY}[CMD]${NC} $msg" ;; *) echo -e "${GRAY}[$timestamp_short]${NC} $msg" ;; esac } @@ -68,7 +77,7 @@ log() { run_cmd() { local desc="$1" shift - log DEBUG "$desc: $*" + log CMD "$desc: $*" output=$("$@" 2>&1) local rc=$? if [ -n "$output" ]; then @@ -79,12 +88,35 @@ run_cmd() { return $rc } -# Fetch server certificate fingerprint from VPN host +show_help() { + echo -e "${CYAN}Cistech VPN Connection Script${NC}" + echo "" + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " -c, --connect Connect to VPN and exit" + echo " -d, --disconnect Disconnect VPN and exit" + echo " -m, --menu Skip auto-connect, show menu directly" + echo " -r, --routes Show routing table and exit" + echo " -s, --status Show VPN status and exit" + echo " --help Show this help message" + echo "" + echo "Menu Options:" + echo " 1 - Connect VPN" + echo " 2 - Disconnect VPN" + echo " 3 - Show VPN status" + echo " 4 - Setup IP forwarding" + echo " 5 - Test connection" + echo " 6 - Show network status" + echo " 7 - Show routing table" + echo " 8 - Show live TOTP" + echo " q - Quit" +} + +# Fetch server certificate fingerprint get_server_cert() { local host="$1" - # Strip protocol and path to get just hostname:port local server=$(echo "$host" | sed -E 's|^https?://||; s|/.*$||') - # Add default port if not specified [[ "$server" != *:* ]] && server="${server}:443" log DEBUG "Fetching server certificate from $server..." @@ -103,7 +135,7 @@ get_server_cert() { fi } -# Setup keyring with credentials for openconnect-sso +# Setup keyring with credentials setup_keyring() { log INFO "Setting up keyring credentials..." @@ -116,19 +148,16 @@ setup_keyring() { import keyring from keyrings.alt.file import PlaintextKeyring -# Use plaintext keyring (works without desktop environment) keyring.set_keyring(PlaintextKeyring()) email = "$VPN_EMAIL" password = "$VPN_PASSWORD" totp_secret = "$VPN_TOTP_SECRET" -# Store password if password: keyring.set_password('openconnect-sso', email, password) print(f"Password stored in keyring for {email}") -# Store TOTP secret if totp_secret: keyring.set_password('openconnect-sso', f'totp/{email}', totp_secret.upper()) print(f"TOTP secret stored in keyring for {email}") @@ -144,27 +173,19 @@ PYTHON fi } -show_help() { - echo -e "${CYAN}OpenConnect-SSO VPN Connection Script${NC}" +get_totp() { + oathtool --totp -b "$VPN_TOTP_SECRET" +} + +show_totp() { + log INFO "Starting live TOTP display (Ctrl+C to stop)" echo "" - echo "Usage: $0 [OPTIONS]" - echo "" - echo "Options:" - echo " -c, --connect Connect to VPN" - echo " -d, --disconnect Disconnect from VPN" - echo " -s, --status Show VPN status" - echo " -r, --routes Show routing table" - echo " --setup-keyring Setup keyring credentials only" - echo " --help Show this help message" - echo "" - echo "Environment Variables:" - echo " VPN_EMAIL Email/username for SSO" - echo " VPN_PASSWORD Password for SSO" - echo " VPN_TOTP_SECRET TOTP secret for 2FA" - echo " VPN_HOST VPN server hostname" - echo " TARGET_IP Target IP for connectivity test" - echo " VPN_INTERFACE TUN interface name (default: tun0)" - echo " IBM_HOST IBM i Host" + 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 } get_vpn_interface() { @@ -295,7 +316,7 @@ disconnect_vpn() { } setup_forwarding() { - log INFO "Setting up IP forwarding rules..." + log INFO "Setting up IP forwarding rules for $TARGET_IP..." local vpn_iface=$(get_vpn_interface) if [ -z "$vpn_iface" ]; then @@ -310,19 +331,42 @@ setup_forwarding() { log DEBUG "VPN IP: $vpn_ip" log DEBUG "Container IP: $container_ip" + # Enable IP forwarding run_cmd "Enabling IP forwarding" sysctl -w net.ipv4.ip_forward=1 - # NAT masquerade for container network - if ! iptables -t nat -C POSTROUTING -o "$vpn_iface" -j MASQUERADE 2>/dev/null; then - run_cmd "Adding NAT masquerade" iptables -t nat -A POSTROUTING -o "$vpn_iface" -j MASQUERADE + # NAT masquerade for container network going through VPN + if ! iptables -t nat -C POSTROUTING -s "$CONTAINER_NETWORK" -o "$vpn_iface" -j MASQUERADE 2>/dev/null; then + run_cmd "Adding NAT masquerade for container network -> VPN" iptables -t nat -A POSTROUTING -s "$CONTAINER_NETWORK" -o "$vpn_iface" -j MASQUERADE + else + log DEBUG "NAT masquerade for container network already exists" fi + # Forward rules at position 1 + iptables -D FORWARD -d "$TARGET_IP" -j ACCEPT 2>/dev/null || true + iptables -D FORWARD -s "$TARGET_IP" -j ACCEPT 2>/dev/null || true + iptables -D FORWARD -s "$CONTAINER_NETWORK" -j ACCEPT 2>/dev/null || true + iptables -D FORWARD -d "$CONTAINER_NETWORK" -j ACCEPT 2>/dev/null || true + + run_cmd "Inserting forward rule (to container network)" iptables -I FORWARD 1 -d "$CONTAINER_NETWORK" -j ACCEPT + run_cmd "Inserting forward rule (from container network)" iptables -I FORWARD 1 -s "$CONTAINER_NETWORK" -j ACCEPT + run_cmd "Inserting forward rule (from target)" iptables -I FORWARD 1 -s "$TARGET_IP" -j ACCEPT + run_cmd "Inserting forward rule (to target)" iptables -I FORWARD 1 -d "$TARGET_IP" -j ACCEPT + log INFO "Forwarding rules configured" + echo "" - # Trigger host routing service restart if available - if [ -d /runtime ]; then - touch /runtime/restart-routing 2>/dev/null || true + # Trigger host routing service restart + log INFO "Triggering host routing service restart..." + touch /runtime/restart-routing 2>/dev/null || true + sleep 2 + if [ ! -f /runtime/restart-routing ]; then + log INFO "Host routing service restarted" + else + log WARN "Host watcher may not be running (trigger file still exists)" fi + + log INFO "Routing configured for $TARGET_IP through VPN tunnel" + echo "" } connect_vpn() { @@ -341,6 +385,13 @@ connect_vpn() { # Setup keyring credentials setup_keyring + # Fetch server certificate + log INFO "Fetching server certificate..." + local servercert=$(get_server_cert "$VPN_HOST") + + log INFO "Connecting to: $VPN_HOST" + log DEBUG "Interface: $VPN_INTERFACE" + # Build openconnect-sso command local sso_args=() sso_args+=("-s" "$VPN_HOST") @@ -349,12 +400,9 @@ connect_vpn() { sso_args+=("-u" "$VPN_EMAIL") fi + # Use hidden browser for automated login sso_args+=("--browser-display-mode" "hidden") - # Fetch server certificate - log INFO "Fetching server certificate..." - local servercert=$(get_server_cert "$VPN_HOST") - # Build openconnect args local oc_args=() oc_args+=("--protocol=anyconnect") @@ -366,12 +414,9 @@ connect_vpn() { log DEBUG "Server cert: $servercert" fi - log INFO "Connecting to: $VPN_HOST" - log DEBUG "Interface: $VPN_INTERFACE" - # Launch openconnect-sso log INFO "Launching openconnect-sso..." - openconnect-sso "${sso_args[@]}" -- /usr/sbin/openconnect "${oc_args[@]}" & + openconnect-sso "${sso_args[@]}" -- sudo /usr/sbin/openconnect "${oc_args[@]}" & OC_PID=$! disown $OC_PID log DEBUG "openconnect-sso started with PID $OC_PID" @@ -399,34 +444,95 @@ connect_vpn() { log DEBUG " Interface: $vpn_iface" log DEBUG " VPN IP: $vpn_ip" + # Wait for routes to stabilize + log DEBUG "Waiting for routes to stabilize..." sleep 3 + + # Setup forwarding setup_forwarding - # Test connection if TARGET_IP is set - if [[ -n "$IBMI_HOST" ]]; then - log INFO "Testing connection to $IBMI_HOST..." - if ping -c 2 -W 3 "$IBMI_HOST" &>/dev/null; then + # Test connection + if [[ -n "$TARGET_IP" ]]; then + 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}" + log WARN "Connection test: ${RED}FAILED${NC} (may need manual route on host)" fi fi + # Disable screen blanking + xset s off 2>/dev/null || true + xset -dpms 2>/dev/null || true + xset s noblank 2>/dev/null || true + log DEBUG "Screen blanking disabled" + log INFO "VPN setup complete" + + # Start watchdog in background + start_watchdog & + WATCHDOG_PID=$! + log DEBUG "Watchdog started with PID $WATCHDOG_PID" + return 0 } +# Watchdog - monitors VPN and reconnects if dropped +start_watchdog() { + log INFO "Starting VPN watchdog (check every 60s, keepalive ping every 5min)..." + + local check_interval=60 + local keepalive_interval=300 + local last_keepalive=0 + local reconnect_attempts=0 + local max_reconnect_attempts=3 + + while true; do + sleep $check_interval + + local now=$(date +%s) + local vpn_iface=$(get_vpn_interface) + + if [ -n "$vpn_iface" ]; then + # VPN is up + reconnect_attempts=0 + + # Keepalive ping every 5 minutes + if [ $((now - last_keepalive)) -ge $keepalive_interval ]; then + if [[ -n "$TARGET_IP" ]] && ping -c 1 -W 5 "$TARGET_IP" &>/dev/null; then + log DEBUG "Keepalive ping to $TARGET_IP successful" + else + log WARN "Keepalive ping to $TARGET_IP failed (VPN may be degraded)" + fi + last_keepalive=$now + fi + else + # VPN is down + ((reconnect_attempts++)) + log WARN "VPN disconnected! Reconnect attempt $reconnect_attempts/$max_reconnect_attempts..." + + if [ $reconnect_attempts -le $max_reconnect_attempts ]; then + connect_vpn + else + log ERROR "Max reconnect attempts reached. Manual intervention required." + sleep 300 + reconnect_attempts=0 + fi + fi + done +} + # Main menu main_menu() { echo -e "${GREEN}Options:${NC}" echo -e " ${CYAN}1${NC} - Connect VPN" echo -e " ${CYAN}2${NC} - Disconnect VPN" echo -e " ${CYAN}3${NC} - Show VPN status" - echo -e " ${CYAN}4${NC} - Setup IP forwarding" - echo -e " ${CYAN}5${NC} - Test connection" + echo -e " ${CYAN}4${NC} - Setup IP forwarding only" + echo -e " ${CYAN}5${NC} - Test connection to $TARGET_IP" echo -e " ${CYAN}6${NC} - Show network status" echo -e " ${CYAN}7${NC} - Show routing table" - echo -e " ${CYAN}8${NC} - Setup keyring" + echo -e " ${CYAN}8${NC} - Show live TOTP" echo -e " ${CYAN}q${NC} - Quit" echo "" } @@ -442,6 +548,10 @@ parse_args() { DO_DISCONNECT=true shift ;; + -m|--menu) + SKIP_AUTO_CONNECT=true + shift + ;; -s|--status) print_banner check_vpn_status @@ -453,10 +563,6 @@ parse_args() { show_routes exit 0 ;; - --setup-keyring) - DO_SETUP_KEYRING=true - shift - ;; --help) show_help exit 0 @@ -468,6 +574,11 @@ parse_args() { ;; esac done + + if [ "$DO_CONNECT" = "true" ] && [ "$DO_DISCONNECT" = "true" ]; then + echo "Error: --connect and --disconnect are mutually exclusive" + exit 1 + fi } # Main @@ -476,7 +587,8 @@ cleanup_old_logs echo "" >> "$(get_log_file)" echo "========================================" >> "$(get_log_file)" -log INFO "openconnect-vpn script started" +log INFO "=== Starting OpenConnect-SSO VPN ===" +echo "" log DEBUG "VPN_EMAIL=$VPN_EMAIL" log DEBUG "VPN_HOST=$VPN_HOST" log DEBUG "TARGET_IP=$TARGET_IP" @@ -484,11 +596,6 @@ log DEBUG "VPN_TOTP_SECRET is $([ -n "$VPN_TOTP_SECRET" ] && echo 'set' || echo print_banner -if [ "$DO_SETUP_KEYRING" = "true" ]; then - setup_keyring - exit $? -fi - if [ "$DO_DISCONNECT" = "true" ]; then disconnect_vpn exit $? @@ -499,7 +606,21 @@ if [ "$DO_CONNECT" = "true" ]; then exit $? fi -# Interactive menu mode +# Auto-connect logic (unless -m flag) +if [ "$SKIP_AUTO_CONNECT" = "true" ]; then + log INFO "Menu mode - skipping auto-connect" +elif check_vpn_status; then + echo "" + log INFO "VPN already connected. Setting up forwarding..." + setup_forwarding +else + echo "" + log INFO "Auto-starting VPN connection..." + echo "" + connect_vpn +fi + +# Interactive menu loop while true; do echo "" main_menu @@ -514,15 +635,15 @@ while true; do 2) disconnect_vpn ;; 3) check_vpn_status ;; 4) setup_forwarding ;; - 5) if [[ -n "$IBMI_HOST" ]]; then - log INFO "Testing connection to $IBMI_HOST..." - ping -c 3 "$IBMI_HOST" && log INFO "Connection test: ${GREEN}SUCCESS${NC}" || log ERROR "Connection test: ${RED}FAILED${NC}" + 5) if [[ -n "$TARGET_IP" ]]; then + log INFO "Testing connection to $TARGET_IP..." + ping -c 3 "$TARGET_IP" && log INFO "Connection test: ${GREEN}SUCCESS${NC}" || log ERROR "Connection test: ${RED}FAILED${NC}" else - log WARN "IBMI_HOST not set" + log WARN "TARGET_IP not set" fi ;; 6) show_network_status ;; 7) show_routes ;; - 8) setup_keyring ;; + 8) show_totp ;; q|Q) log INFO "Goodbye!"; exit 0 ;; *) ;; esac