From 838b33d6c5f7c994628b1b0708cb0364c6dab5d5 Mon Sep 17 00:00:00 2001 From: alexz Date: Fri, 16 Jan 2026 20:47:20 +0000 Subject: [PATCH] feat(rego-tunnel): Add Dockerfile and build scripts for cisco-vpn image Includes: - Dockerfile for native Cisco Secure Client in Docker - Build scripts (init-vpn.sh, startup-vnc.sh, vpn-connect.sh) - VNC configuration (xstartup, vnc.service) - build.sh for manual image builds - README documenting the architecture Note: cisco-secure-client-full.tar.gz is gitignored (large binary) Copy it from ~/projects/cisco-vpn/build/ before building. Co-Authored-By: Claude Opus 4.5 --- apps/rego-tunnel/build/.gitignore | 3 + apps/rego-tunnel/build/Dockerfile | 104 +++++- apps/rego-tunnel/build/README.md | 41 +++ apps/rego-tunnel/build/build.sh | 21 ++ apps/rego-tunnel/build/scripts/init-vpn.sh | 18 + apps/rego-tunnel/build/scripts/startup-vnc.sh | 33 ++ apps/rego-tunnel/build/scripts/vnc.service | 14 + apps/rego-tunnel/build/scripts/vpn-connect.sh | 346 ++++++++++++++++++ apps/rego-tunnel/build/scripts/xstartup | 21 ++ 9 files changed, 591 insertions(+), 10 deletions(-) create mode 100644 apps/rego-tunnel/build/.gitignore create mode 100644 apps/rego-tunnel/build/README.md create mode 100644 apps/rego-tunnel/build/build.sh create mode 100644 apps/rego-tunnel/build/scripts/init-vpn.sh create mode 100644 apps/rego-tunnel/build/scripts/startup-vnc.sh create mode 100644 apps/rego-tunnel/build/scripts/vnc.service create mode 100644 apps/rego-tunnel/build/scripts/vpn-connect.sh create mode 100644 apps/rego-tunnel/build/scripts/xstartup diff --git a/apps/rego-tunnel/build/.gitignore b/apps/rego-tunnel/build/.gitignore new file mode 100644 index 0000000..7fb7153 --- /dev/null +++ b/apps/rego-tunnel/build/.gitignore @@ -0,0 +1,3 @@ +# Large binary files - not tracked in git +cisco-secure-client-full.tar.gz +*.7z diff --git a/apps/rego-tunnel/build/Dockerfile b/apps/rego-tunnel/build/Dockerfile index f35f897..f487077 100755 --- a/apps/rego-tunnel/build/Dockerfile +++ b/apps/rego-tunnel/build/Dockerfile @@ -1,17 +1,101 @@ -FROM ubuntu:24.04 +FROM ubuntu:22.04 + +LABEL maintainer="alexz" +LABEL description="Cisco Secure Client VPN in Docker with noVNC" +LABEL version="5.1.14.145" ENV DEBIAN_FRONTEND=noninteractive +ENV container=docker -RUN apt-get update && apt-get install -y qemu-system-x86 qemu-utils novnc websockify x11vnc xvfb fluxbox xterm nano oathtool openssh-server supervisor iproute2 bridge-utils iptables nano net-tools p7zip-full dnsmasq && rm -rf /var/lib/apt/lists/* +# VNC/noVNC settings +ENV DISPLAY=:1 +ENV VNC_PORT=5901 +ENV NOVNC_PORT=6080 -# Setup SSH -RUN mkdir /var/run/sshd && echo 'root:vmpassword' | chpasswd && sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config +# Install systemd and dependencies +RUN apt-get update && apt-get install -y \ + systemd \ + systemd-sysv \ + dbus \ + dbus-x11 \ + libgtk-3-0 \ + libglib2.0-0 \ + libstdc++6 \ + iptables \ + libxml2 \ + network-manager \ + zlib1g \ + policykit-1 \ + xdg-utils \ + libwebkit2gtk-4.0-37 \ + # VNC + tigervnc-standalone-server \ + tigervnc-common \ + novnc \ + websockify \ + # Window manager + openbox \ + xterm \ + # Utilities + procps \ + net-tools \ + curl \ + iproute2 \ + iputils-ping \ + nano \ + # Automation tools + xdotool \ + oathtool \ + xclip \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* -WORKDIR /vm +# Remove unnecessary systemd services that cause issues in containers +RUN rm -f /lib/systemd/system/multi-user.target.wants/* \ + /etc/systemd/system/*.wants/* \ + /lib/systemd/system/local-fs.target.wants/* \ + /lib/systemd/system/sockets.target.wants/*udev* \ + /lib/systemd/system/sockets.target.wants/*initctl* \ + /lib/systemd/system/sysinit.target.wants/systemd-tmpfiles-setup* \ + /lib/systemd/system/systemd-update-utmp* -RUN ln -s /shared/supervisord.conf /etc/supervisor/conf.d/supervisord.conf -RUN ln -s /shared/start-vm.sh /usr/local/bin/start-vm.sh -RUN ln -s /shared/setup-network.sh /usr/local/bin/setup-network.sh -RUN ln -s /shared/start-dnsmasq.sh /usr/local/bin/start-dnsmasq.sh +# Copy and extract the FULL Cisco Secure Client installation (VPN + DART + Posture) +COPY cisco-secure-client-full.tar.gz /tmp/ +RUN tar -xzf /tmp/cisco-secure-client-full.tar.gz -C / && rm /tmp/cisco-secure-client-full.tar.gz -CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] +# Enable vpnagentd service +RUN systemctl enable vpnagentd.service + +# Create scripts directory +RUN mkdir -p /opt/scripts + +# Copy scripts +COPY scripts/init-vpn.sh /opt/scripts/ +COPY scripts/startup-vnc.sh /opt/scripts/ +COPY scripts/vpn-connect.sh /opt/scripts/ +RUN chmod +x /opt/scripts/*.sh + +# Setup VNC password (default, can be overridden via mount) +ARG VNC_PASSWORD=cisco123 +RUN mkdir -p /root/.vnc && \ + echo "${VNC_PASSWORD}" | vncpasswd -f > /root/.vnc/passwd && \ + chmod 600 /root/.vnc/passwd + +# VNC xstartup script (can be overridden via mount) +COPY scripts/xstartup /root/.vnc/xstartup +RUN chmod +x /root/.vnc/xstartup + +# Create systemd service for VNC +COPY scripts/vnc.service /lib/systemd/system/vnc.service +RUN systemctl enable vnc.service + +# Create shared directory for mounting scripts +RUN mkdir -p /shared + +VOLUME ["/sys/fs/cgroup"] + +EXPOSE 5901 6080 + +STOPSIGNAL SIGRTMIN+3 + +CMD ["/opt/scripts/init-vpn.sh"] diff --git a/apps/rego-tunnel/build/README.md b/apps/rego-tunnel/build/README.md new file mode 100644 index 0000000..a7b406e --- /dev/null +++ b/apps/rego-tunnel/build/README.md @@ -0,0 +1,41 @@ +# Rego Tunnel - Build Files + +This directory contains the Dockerfile and scripts to build the Cisco VPN Docker image. + +## Files + +- `Dockerfile` - Main Docker image definition +- `cisco-secure-client-full.tar.gz` - Pre-extracted Cisco Secure Client installation +- `build.sh` - Build script to create the Docker image +- `scripts/` - Container scripts: + - `init-vpn.sh` - Container init (unmounts DNS files for VPN, starts systemd) + - `startup-vnc.sh` - VNC/noVNC startup script + - `vpn-connect.sh` - VPN automation script (inside container) + - `xstartup` - VNC session startup + - `vnc.service` - Systemd service for VNC + +## Building + +```bash +cd /etc/runtipi/repos/runtipi/apps/rego-tunnel/build +./build.sh +``` + +To push to registry: +```bash +docker push git.alexzaw.dev/alexz/cisco-vpn:latest +``` + +## Architecture + +1. **init-vpn.sh**: Unmounts Docker's bind-mounted `/etc/resolv.conf` and `/etc/hosts` (required for VPN to modify DNS), then starts systemd +2. **systemd**: Manages vpnagentd (Cisco VPN agent) and vnc (VNC server) services +3. **xstartup**: Runs when VNC session starts - by default launches vpnui, but can be overridden via volume mount +4. **vpn-connect.sh**: Optional automation script for auto-login with TOTP + +## Runtime Mounts + +When running as rego-tunnel app, these are mounted: +- `/shared` - Contains the `cisco-vpn` script with full automation +- `/root/.vnc/xstartup` - Custom xstartup that launches terminal with cisco-vpn script +- `/opt/scripts/init-vpn.sh` - Custom init script with network setup diff --git a/apps/rego-tunnel/build/build.sh b/apps/rego-tunnel/build/build.sh new file mode 100644 index 0000000..069926a --- /dev/null +++ b/apps/rego-tunnel/build/build.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# Build and push the Cisco VPN Docker image +# Run this from the build directory + +set -e + +IMAGE_NAME="${IMAGE_NAME:-git.alexzaw.dev/alexz/cisco-vpn}" +IMAGE_TAG="${IMAGE_TAG:-latest}" + +echo "Building ${IMAGE_NAME}:${IMAGE_TAG}..." + +docker build -t "${IMAGE_NAME}:${IMAGE_TAG}" . + +echo "" +echo "Build complete!" +echo "" +echo "To push to registry:" +echo " docker push ${IMAGE_NAME}:${IMAGE_TAG}" +echo "" +echo "To test locally:" +echo " docker run -d --privileged --cgroupns=host -v /sys/fs/cgroup:/sys/fs/cgroup:rw --cap-add=NET_ADMIN --device=/dev/net/tun -p 6080:6080 ${IMAGE_NAME}:${IMAGE_TAG}" diff --git a/apps/rego-tunnel/build/scripts/init-vpn.sh b/apps/rego-tunnel/build/scripts/init-vpn.sh new file mode 100644 index 0000000..49b9c2e --- /dev/null +++ b/apps/rego-tunnel/build/scripts/init-vpn.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# Cisco VPN Docker Init Script +# Unmounts Docker's read-only bind mounts to allow VPN to modify DNS settings + +# Backup current DNS config +cp /etc/resolv.conf /tmp/resolv.conf.bak 2>/dev/null || true +cp /etc/hosts /tmp/hosts.bak 2>/dev/null || true + +# Unmount Docker's bind mounts (this is the key fix!) +umount /etc/resolv.conf 2>/dev/null || true +umount /etc/hosts 2>/dev/null || true + +# Restore DNS config as regular files +cat /tmp/resolv.conf.bak > /etc/resolv.conf 2>/dev/null || echo "nameserver 8.8.8.8" > /etc/resolv.conf +cat /tmp/hosts.bak > /etc/hosts 2>/dev/null || echo "127.0.0.1 localhost" > /etc/hosts + +# Start systemd +exec /sbin/init diff --git a/apps/rego-tunnel/build/scripts/startup-vnc.sh b/apps/rego-tunnel/build/scripts/startup-vnc.sh new file mode 100644 index 0000000..a1f721f --- /dev/null +++ b/apps/rego-tunnel/build/scripts/startup-vnc.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# VNC and noVNC startup script + +set -e + +export HOME=/root +export USER=root + +# Clean up any existing VNC locks +rm -f /tmp/.X1-lock /tmp/.X11-unix/X1 2>/dev/null || true +rm -rf /tmp/.X*-lock /tmp/.X11-unix/* 2>/dev/null || true + +echo "Starting TigerVNC server on display :1..." +vncserver :1 \ + -geometry 1280x800 \ + -depth 24 \ + -SecurityTypes VncAuth \ + -localhost no + +sleep 2 + +echo "Starting noVNC on port ${NOVNC_PORT:-6080}..." +websockify --web=/usr/share/novnc/ ${NOVNC_PORT:-6080} localhost:5901 & + +echo "" +echo "============================================" +echo "VNC server running on port 5901" +echo "noVNC web interface: http://localhost:${NOVNC_PORT:-6080}/vnc.html" +echo "============================================" +echo "" + +# Keep the script running +tail -f /root/.vnc/*.log diff --git a/apps/rego-tunnel/build/scripts/vnc.service b/apps/rego-tunnel/build/scripts/vnc.service new file mode 100644 index 0000000..2f02518 --- /dev/null +++ b/apps/rego-tunnel/build/scripts/vnc.service @@ -0,0 +1,14 @@ +[Unit] +Description=VNC and noVNC Server +After=network.target vpnagentd.service + +[Service] +Type=simple +ExecStart=/opt/scripts/startup-vnc.sh +Restart=always +RestartSec=5 +Environment=HOME=/root +Environment=USER=root + +[Install] +WantedBy=multi-user.target diff --git a/apps/rego-tunnel/build/scripts/vpn-connect.sh b/apps/rego-tunnel/build/scripts/vpn-connect.sh new file mode 100644 index 0000000..ca74334 --- /dev/null +++ b/apps/rego-tunnel/build/scripts/vpn-connect.sh @@ -0,0 +1,346 @@ +#!/bin/bash +# +# Cisco VPN Automation Script (runs inside container) +# Usage: vpn-connect.sh [-c|--connect] [-d|--disconnect] [-s|--status] [-m|--manual] [--help] +# +# Options: +# -c, --connect Auto-connect to VPN (default if no option) +# -d, --disconnect Disconnect VPN +# -s, --status Show VPN status +# -m, --manual Start UI without auto-login +# --help Show this help message +# + +# Load config from environment or config file +CONFIG_FILE="${VPN_CONFIG_FILE:-/config/vpn.conf}" +if [ -f "$CONFIG_FILE" ]; then + source "$CONFIG_FILE" +fi + +# These can be set via environment variables or config file +EMAIL="${VPN_EMAIL:-}" +PASSWORD="${VPN_PASSWORD:-}" +TOTP_SECRET="${VPN_TOTP_SECRET:-}" +VPN_HOST="${VPN_HOST:-}" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +GRAY='\033[0;90m' +NC='\033[0m' + +# Logging function +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" ;; + *) echo -e "${GRAY}[$timestamp]${NC} $msg" ;; + esac +} + +# Function to get current TOTP +get_totp() { + if [ -n "$TOTP_SECRET" ]; then + oathtool --totp -b "$TOTP_SECRET" + else + log WARN "TOTP_SECRET not set" + echo "" + fi +} + +# Detect VPN tunnel interface +get_vpn_interface() { + ip link show | grep -oP '(cscotun\d+|tun\d+)(?=:.*UP)' | head -1 +} + +# Get VPN 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 +} + +# Check VPN status +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 +} + +# Kill Cisco processes +kill_cisco_processes() { + local kill_agentd="${1:-false}" + log INFO "Stopping Cisco processes..." + + local killed=0 + local my_pid=$$ + + # Kill vpnui + for pid in $(pgrep -x "vpnui" 2>/dev/null); do + if [ "$pid" != "$my_pid" ]; then + log DEBUG "Killing vpnui (PID $pid)" + kill -9 "$pid" 2>/dev/null && ((killed++)) + fi + done + + # Optionally kill vpnagentd + if [ "$kill_agentd" = "true" ]; then + systemctl stop vpnagentd.service 2>/dev/null || true + log DEBUG "Stopped vpnagentd service" + fi + + if [ $killed -eq 0 ]; then + log INFO "No Cisco UI processes were running" + else + log INFO "Stopped $killed process(es)" + sleep 1 + fi +} + +# Disconnect VPN +disconnect_vpn() { + log INFO "Disconnecting Cisco AnyConnect..." + + # Try clean disconnect via CLI + if [ -x /opt/cisco/secureclient/bin/vpn ]; then + echo "disconnect" | /opt/cisco/secureclient/bin/vpn -s 2>/dev/null || true + fi + + # Kill UI processes + kill_cisco_processes "false" + + sleep 2 + + if check_vpn_status; then + log WARN "VPN still appears connected" + return 1 + fi + + log INFO "VPN disconnected" + return 0 +} + +# Auto-login using xdotool +auto_login() { + log INFO "Starting automated login sequence..." + + # Check credentials + if [ -z "$EMAIL" ] || [ -z "$PASSWORD" ]; then + log ERROR "EMAIL and PASSWORD must be set for auto-login" + return 1 + fi + + # 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..." + DISPLAY=:1 xdotool key Return + sleep 5 + + # Type email + log DEBUG "Typing email..." + DISPLAY=:1 xdotool type --delay 50 "$EMAIL" + DISPLAY=:1 xdotool key Return + sleep 5 + + # Type password + log DEBUG "Typing password..." + DISPLAY=:1 xdotool type --delay 50 "$PASSWORD" + DISPLAY=:1 xdotool key Return + sleep 5 + + # Type TOTP if configured + if [ -n "$TOTP_SECRET" ]; then + log DEBUG "Typing TOTP..." + local totp=$(oathtool --totp -b "$TOTP_SECRET") + log DEBUG "TOTP: $totp" + DISPLAY=:1 xdotool type --delay 50 "$totp" + DISPLAY=:1 xdotool key Return + sleep 5 + fi + + # Extra enters for confirmation dialogs + log DEBUG "Sending confirmation enters..." + DISPLAY=:1 xdotool key Return + sleep 2 + DISPLAY=:1 xdotool key Return + sleep 5 + DISPLAY=:1 xdotool key Return + + log INFO "Auto-login sequence completed" +} + +# Start VPN UI +start_vpn_ui() { + local do_auto_login="$1" + log INFO "=== Starting Cisco Secure Client ===" + echo "" + + # Kill existing UI processes + kill_cisco_processes "false" + + # Ensure vpnagentd is running + if ! pgrep -x vpnagentd >/dev/null; then + log INFO "Starting vpnagentd..." + systemctl start vpnagentd.service + log DEBUG "Waiting for vpnagentd to initialize..." + sleep 5 + fi + + # Show credentials if configured + if [ -n "$EMAIL" ]; then + log INFO "Credentials configured:" + echo -e " ${CYAN}Email: $EMAIL${NC}" + echo -e " ${CYAN}Password: ****${NC}" + if [ -n "$TOTP_SECRET" ]; then + local totp=$(get_totp) + echo -e " ${CYAN}TOTP: $totp${NC}" + fi + echo "" + fi + + # Start VPN UI + log INFO "Launching Cisco Secure Client UI..." + export DISPLAY=:1 + 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 + auto_login & + AUTO_LOGIN_PID=$! + log DEBUG "Auto-login started with PID $AUTO_LOGIN_PID" + + # 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" + 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" + + # 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_vpn_ui "$do_auto_login" + return $? + fi + done + else + log INFO "Manual mode - use the noVNC GUI to log in" + log INFO "VPN host: ${VPN_HOST:-not configured}" + return 0 + fi +} + +# Show help +show_help() { + echo -e "${CYAN}Cisco VPN Automation Script${NC}" + echo "" + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " -c, --connect Auto-connect to VPN (default if no option)" + echo " -d, --disconnect Disconnect VPN" + echo " -s, --status Show VPN status" + echo " -m, --manual Start UI without auto-login" + echo " --help Show this help message" + echo "" + echo "Configuration (via environment or /config/vpn.conf):" + echo " VPN_EMAIL Email for SSO login" + echo " VPN_PASSWORD Password for SSO login" + echo " VPN_TOTP_SECRET TOTP secret for 2FA" + echo " VPN_HOST VPN server hostname" +} + +# Parse arguments +ACTION="connect" +while [[ $# -gt 0 ]]; do + case $1 in + -c|--connect) + ACTION="connect" + shift + ;; + -d|--disconnect) + ACTION="disconnect" + shift + ;; + -s|--status) + ACTION="status" + shift + ;; + -m|--manual) + ACTION="manual" + shift + ;; + --help) + show_help + exit 0 + ;; + *) + echo "Unknown option: $1" + show_help + exit 1 + ;; + esac +done + +# Execute action +case $ACTION in + connect) + if [ -z "$EMAIL" ] || [ -z "$PASSWORD" ]; then + log ERROR "VPN_EMAIL and VPN_PASSWORD must be set for auto-connect" + log INFO "Use -m/--manual for manual login, or set credentials in /config/vpn.conf" + exit 1 + fi + start_vpn_ui "true" + ;; + disconnect) + disconnect_vpn + ;; + status) + check_vpn_status + ;; + manual) + start_vpn_ui "false" + ;; +esac diff --git a/apps/rego-tunnel/build/scripts/xstartup b/apps/rego-tunnel/build/scripts/xstartup new file mode 100644 index 0000000..33782ed --- /dev/null +++ b/apps/rego-tunnel/build/scripts/xstartup @@ -0,0 +1,21 @@ +#!/bin/bash +# VNC xstartup - launches window manager and VPN UI + +unset SESSION_MANAGER +unset DBUS_SESSION_BUS_ADDRESS + +export XDG_RUNTIME_DIR=/tmp/runtime-root +mkdir -p $XDG_RUNTIME_DIR +chmod 700 $XDG_RUNTIME_DIR + +# Start dbus session +[ -x /usr/bin/dbus-launch ] && eval $(dbus-launch --sh-syntax --exit-with-session) + +# Start window manager +openbox & +sleep 2 + +# Start Cisco VPN UI +/opt/cisco/secureclient/bin/vpnui & + +wait