revert(cistech-tunnel): restore to working state at 5d54ed6
Some checks failed
Test / test (push) Has been cancelled
Some checks failed
Test / test (push) Has been cancelled
- Removed build/ folder - Restored source/ folder with original Dockerfile and entrypoint.sh - Reverted config files to original working state - Cleaned up shared/ to only contain host routing scripts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2
apps/cistech-tunnel/build/.gitignore
vendored
2
apps/cistech-tunnel/build/.gitignore
vendored
@@ -1,2 +0,0 @@
|
|||||||
# Large binary files - track tar.gz but not 7z
|
|
||||||
*.7z
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
FROM ubuntu:22.04
|
|
||||||
|
|
||||||
LABEL maintainer="alexz"
|
|
||||||
LABEL description="OpenConnect SSO VPN in Docker with noVNC"
|
|
||||||
LABEL version="1.0.0"
|
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
|
||||||
|
|
||||||
# VNC/noVNC settings
|
|
||||||
ENV DISPLAY=:1
|
|
||||||
ENV VNC_PORT=5901
|
|
||||||
ENV NOVNC_PORT=6092
|
|
||||||
|
|
||||||
# Python/Playwright settings
|
|
||||||
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
|
|
||||||
ENV VIRTUAL_ENV=/opt/venv
|
|
||||||
ENV PATH=/opt/venv/bin:$PATH
|
|
||||||
|
|
||||||
# Install dependencies (no systemd)
|
|
||||||
RUN apt-get update && apt-get install -y \
|
|
||||||
dbus \
|
|
||||||
dbus-x11 \
|
|
||||||
libgtk-3-0 \
|
|
||||||
libglib2.0-0 \
|
|
||||||
libstdc++6 \
|
|
||||||
iptables \
|
|
||||||
libxml2 \
|
|
||||||
zlib1g \
|
|
||||||
policykit-1 \
|
|
||||||
xdg-utils \
|
|
||||||
libwebkit2gtk-4.0-37 \
|
|
||||||
tigervnc-standalone-server \
|
|
||||||
tigervnc-common \
|
|
||||||
novnc \
|
|
||||||
websockify \
|
|
||||||
openbox \
|
|
||||||
xterm \
|
|
||||||
procps \
|
|
||||||
net-tools \
|
|
||||||
curl \
|
|
||||||
iproute2 \
|
|
||||||
iputils-ping \
|
|
||||||
nano \
|
|
||||||
x11vnc \
|
|
||||||
xvfb \
|
|
||||||
fluxbox \
|
|
||||||
xdotool \
|
|
||||||
oathtool \
|
|
||||||
openconnect \
|
|
||||||
python3 \
|
|
||||||
python3-pip \
|
|
||||||
python3-venv \
|
|
||||||
vpnc-scripts \
|
|
||||||
libasound2 \
|
|
||||||
libnss3 \
|
|
||||||
libatk1.0-0 \
|
|
||||||
libatk-bridge2.0-0 \
|
|
||||||
libxcomposite1 \
|
|
||||||
libxrandr2 \
|
|
||||||
libgbm1 \
|
|
||||||
libxdamage1 \
|
|
||||||
libpango-1.0-0 \
|
|
||||||
fonts-liberation \
|
|
||||||
&& apt-get clean \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Install openconnect-sso with playwright
|
|
||||||
RUN python3 -m venv "$VIRTUAL_ENV" && \
|
|
||||||
pip install --no-cache-dir openconnect-sso[full] playwright keyring keyrings.alt && \
|
|
||||||
python -m playwright install --with-deps chromium
|
|
||||||
|
|
||||||
RUN mkdir -p /opt/scripts /shared
|
|
||||||
|
|
||||||
# Copy entrypoint script
|
|
||||||
COPY scripts/entrypoint.sh /opt/scripts/
|
|
||||||
RUN chmod +x /opt/scripts/entrypoint.sh
|
|
||||||
|
|
||||||
EXPOSE 5901 6092
|
|
||||||
|
|
||||||
CMD ["/opt/scripts/entrypoint.sh"]
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
# Rego Tunnel - Build Files
|
|
||||||
|
|
||||||
This directory contains the Dockerfile and scripts to build the Cisco VPN Docker image.
|
|
||||||
|
|
||||||
## Files
|
|
||||||
|
|
||||||
- `Dockerfile` - Docker image definition (Ubuntu 22.04 + Cisco Secure Client + noVNC)
|
|
||||||
- `cisco-secure-client-full.tar.gz` - Pre-extracted Cisco Secure Client 5.1.14.145
|
|
||||||
- `build.sh` - Build and push script
|
|
||||||
- `scripts/entrypoint.sh` - Container entrypoint (starts systemd)
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /etc/runtipi/repos/runtipi/apps/rego-tunnel/build
|
|
||||||
./build.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
This builds and pushes to `git.alexzaw.dev/alexz/cisco-vpn:latest`
|
|
||||||
|
|
||||||
To build without pushing:
|
|
||||||
```bash
|
|
||||||
docker build -t git.alexzaw.dev/alexz/cisco-vpn:latest .
|
|
||||||
```
|
|
||||||
|
|
||||||
## What's in the image
|
|
||||||
|
|
||||||
The Dockerfile creates an image with:
|
|
||||||
- Ubuntu 22.04 with systemd
|
|
||||||
- Cisco Secure Client 5.1.14.145 (VPN, DART, Posture modules)
|
|
||||||
- TigerVNC server + noVNC (web-based VNC)
|
|
||||||
- Tools: xdotool, oathtool (for TOTP), xclip, openbox
|
|
||||||
|
|
||||||
### Systemd services (baked in)
|
|
||||||
- `vpnagentd.service` - Cisco VPN agent
|
|
||||||
- `vnc.service` - VNC server + noVNC websockify
|
|
||||||
|
|
||||||
### Scripts (baked in via base64 in Dockerfile)
|
|
||||||
- `/opt/scripts/startup-vnc.sh` - Starts VNC server and noVNC
|
|
||||||
- `/opt/scripts/entrypoint.sh` - Container entrypoint
|
|
||||||
|
|
||||||
## Runtime mounts (from shared/)
|
|
||||||
|
|
||||||
When running as rego-tunnel app, these are mounted from `shared/`:
|
|
||||||
- `/shared/cisco-vpn` - Main VPN automation script
|
|
||||||
- `/shared/xstartup` → `/root/.vnc/xstartup` - VNC session startup
|
|
||||||
|
|
||||||
## Ports
|
|
||||||
|
|
||||||
- `5901` - VNC server
|
|
||||||
- `6080` - noVNC web interface
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Build and push the Cistech VPN Docker image
|
|
||||||
# Run this from the build directory
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
IMAGE_NAME="${IMAGE_NAME:-git.alexzaw.dev/alexz/cistech-vpn}"
|
|
||||||
IMAGE_TAG="${IMAGE_TAG:-latest}"
|
|
||||||
|
|
||||||
echo "Building ${IMAGE_NAME}:${IMAGE_TAG}..."
|
|
||||||
|
|
||||||
docker build "$@" -t "${IMAGE_NAME}:${IMAGE_TAG}" .
|
|
||||||
docker push "${IMAGE_NAME}:${IMAGE_TAG}"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Build complete!"
|
|
||||||
echo ""
|
|
||||||
echo "To test locally:"
|
|
||||||
echo " docker run -d --privileged --cap-add=NET_ADMIN --device=/dev/net/tun -p 5901:5901 -p 6092:6092 ${IMAGE_NAME}:${IMAGE_TAG}"
|
|
||||||
echo ""
|
|
||||||
echo "Then connect via VNC to localhost:5901 or open noVNC at http://localhost:6092/vnc.html"
|
|
||||||
echo ""
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Entrypoint: VNC password setup + DNS fix + direct VNC start (no systemd)
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
export HOME='/root'
|
|
||||||
export USER='root'
|
|
||||||
export DISPLAY=:1
|
|
||||||
|
|
||||||
# Setup TigerVNC password file from env var (passed by runtipi)
|
|
||||||
if [ -n "${VNC_PASSWORD:-}" ]; then
|
|
||||||
mkdir -p /root/.vnc
|
|
||||||
printf '%s\n%s\n' "$VNC_PASSWORD" "$VNC_PASSWORD" | vncpasswd -f > /root/.vnc/passwd
|
|
||||||
chmod 600 /root/.vnc/passwd
|
|
||||||
fi
|
|
||||||
|
|
||||||
# DNS fix
|
|
||||||
cp /etc/resolv.conf /tmp/resolv.conf.bak 2>/dev/null || true
|
|
||||||
cp /etc/hosts /tmp/hosts.bak 2>/dev/null || true
|
|
||||||
umount /etc/resolv.conf 2>/dev/null || true
|
|
||||||
umount /etc/hosts 2>/dev/null || true
|
|
||||||
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
|
|
||||||
|
|
||||||
# Enable IP forwarding
|
|
||||||
echo 1 > /proc/sys/net/ipv4/ip_forward
|
|
||||||
echo "[entrypoint] IP forwarding enabled"
|
|
||||||
|
|
||||||
# Clean up stale VNC locks
|
|
||||||
rm -f /tmp/.X1-lock /tmp/.X11-unix/X1 2>/dev/null || true
|
|
||||||
|
|
||||||
# Start TigerVNC server
|
|
||||||
echo "[entrypoint] Starting TigerVNC server on display :1..."
|
|
||||||
vncserver :1 -geometry 1280x800 -depth 24 -SecurityTypes VncAuth -localhost no
|
|
||||||
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
# Start noVNC via websockify
|
|
||||||
echo "[entrypoint] Starting noVNC on port ${NOVNC_PORT:-6092}..."
|
|
||||||
websockify --web=/usr/share/novnc/ ${NOVNC_PORT:-6092} localhost:5901 &
|
|
||||||
|
|
||||||
echo "[entrypoint] VNC ready. Use noVNC at http://localhost:${NOVNC_PORT:-6092}/vnc.html"
|
|
||||||
|
|
||||||
# Keep container running and tail logs
|
|
||||||
tail -f /root/.vnc/*.log 2>/dev/null || tail -f /dev/null
|
|
||||||
@@ -4,13 +4,13 @@
|
|||||||
"available": true,
|
"available": true,
|
||||||
"short_desc": "Cistech VPN client container with noVNC.",
|
"short_desc": "Cistech VPN client container with noVNC.",
|
||||||
"author": "alexz",
|
"author": "alexz",
|
||||||
"port": 6092,
|
"port": 6902,
|
||||||
"categories": [
|
"categories": [
|
||||||
"utilities",
|
"utilities",
|
||||||
"network"
|
"network"
|
||||||
],
|
],
|
||||||
"description": "OpenConnect-SSO VPN running in an isolated namespace with noVNC for first-time SSO reconnects.",
|
"description": "OpenConnect-SSO VPN running in an isolated namespace with noVNC for first-time SSO reconnects.",
|
||||||
"tipi_version": 4,
|
"tipi_version": 1,
|
||||||
"version": "latest",
|
"version": "latest",
|
||||||
"source": "local",
|
"source": "local",
|
||||||
"exposable": true,
|
"exposable": true,
|
||||||
@@ -48,13 +48,6 @@
|
|||||||
"type": "password",
|
"type": "password",
|
||||||
"env_variable": "VNC_PASSWORD",
|
"env_variable": "VNC_PASSWORD",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Target IP",
|
|
||||||
"type": "text",
|
|
||||||
"env_variable": "TARGET_IP",
|
|
||||||
"required": false,
|
|
||||||
"hint": "IP address to route through VPN (for host routing)"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"supported_architectures": [
|
"supported_architectures": [
|
||||||
|
|||||||
@@ -4,29 +4,24 @@
|
|||||||
{
|
{
|
||||||
"name": "cistech-tunnel",
|
"name": "cistech-tunnel",
|
||||||
"image": "git.alexzaw.dev/alexz/cistech-vpn:latest",
|
"image": "git.alexzaw.dev/alexz/cistech-vpn:latest",
|
||||||
|
"isMain": true,
|
||||||
|
"internalPort": 6902,
|
||||||
|
"privileged": true,
|
||||||
|
"capAdd": ["NET_ADMIN"],
|
||||||
|
"devices": ["/dev/net/tun"],
|
||||||
"environment": [
|
"environment": [
|
||||||
{ "key": "OC_URL", "value": "${OC_URL}" },
|
{ "key": "OC_URL", "value": "${OC_URL}" },
|
||||||
{ "key": "OC_USER", "value": "${OC_USER}" },
|
{ "key": "OC_USER", "value": "${OC_USER}" },
|
||||||
{ "key": "OC_PASSWORD", "value": "${OC_PASSWORD}" },
|
{ "key": "OC_PASSWORD", "value": "${OC_PASSWORD}" },
|
||||||
{ "key": "OC_TOTP_SECRET", "value": "${OC_TOTP_SECRET}" },
|
{ "key": "OC_TOTP_SECRET", "value": "${OC_TOTP_SECRET}" },
|
||||||
{ "key": "VNC_PASSWORD", "value": "${VNC_PASSWORD}" },
|
{ "key": "VNC_PASSWORD", "value": "${VNC_PASSWORD}" },
|
||||||
{ "key": "NOVNC_PORT", "value": "6092" },
|
{ "key": "NOVNC_PORT", "value": "6902" }
|
||||||
{ "key": "TZ", "value": "${TZ}" },
|
|
||||||
{ "key": "TARGET_IP", "value": "${TARGET_IP}" }
|
|
||||||
],
|
],
|
||||||
"internalPort": 6092,
|
|
||||||
"volumes": [
|
"volumes": [
|
||||||
{ "hostPath": "${APP_DATA_DIR}/config", "containerPath": "/config", "readOnly": false },
|
{ "hostPath": "${APP_DATA_DIR}/data", "containerPath": "/root" },
|
||||||
{ "hostPath": "${APP_DATA_DIR}", "containerPath": "/runtime", "readOnly": false },
|
{ "hostPath": "${APP_DATA_DIR}", "containerPath": "/runtime" },
|
||||||
{ "hostPath": "/etc/runtipi/repos/runtipi/apps/cistech-tunnel/shared", "containerPath": "/shared", "readOnly": false },
|
{ "hostPath": "/etc/runtipi/repos/runtipi/apps/cistech-tunnel/shared", "containerPath": "/shared" }
|
||||||
{ "hostPath": "/etc/runtipi/repos/runtipi/apps/cistech-tunnel/shared/xstartup", "containerPath": "/root/.vnc/xstartup", "readOnly": true }
|
]
|
||||||
],
|
|
||||||
"stopGracePeriod": "30s",
|
|
||||||
"devices": ["/dev/net/tun"],
|
|
||||||
"privileged": true,
|
|
||||||
"capAdd": ["NET_ADMIN"],
|
|
||||||
"isMain": true,
|
|
||||||
"extraLabels": { "runtipi.managed": true }
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
cistech-tunnel:
|
cistech-tunnel:
|
||||||
image: git.alexzaw.dev/alexz/cistech-vpn:latest
|
image: cistech-vpn:latest
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
cistech-tunnel_runtipi_network:
|
cistech-tunnel_runtipi_network:
|
||||||
@@ -9,32 +9,20 @@ services:
|
|||||||
gw_priority: 1
|
gw_priority: 1
|
||||||
environment:
|
environment:
|
||||||
OC_URL: ${OC_URL}
|
OC_URL: ${OC_URL}
|
||||||
|
OC_SERVERCERT: ${OC_SERVERCERT}
|
||||||
OC_USER: ${OC_USER}
|
OC_USER: ${OC_USER}
|
||||||
OC_PASSWORD: ${OC_PASSWORD}
|
|
||||||
OC_TOTP_SECRET: ${OC_TOTP_SECRET}
|
|
||||||
VNC_PASSWORD: ${VNC_PASSWORD}
|
VNC_PASSWORD: ${VNC_PASSWORD}
|
||||||
NOVNC_PORT: "6092"
|
NOVNC_PORT: "6902"
|
||||||
TZ: ${TZ}
|
|
||||||
TARGET_IP: ${TARGET_IP}
|
|
||||||
ports:
|
ports:
|
||||||
- ${APP_PORT}:6092
|
- ${APP_PORT}:6902
|
||||||
volumes:
|
volumes:
|
||||||
- ${APP_DATA_DIR}/config:/config
|
- ${APP_DATA_DIR}/data:/root
|
||||||
- ${APP_DATA_DIR}:/runtime
|
|
||||||
- /etc/runtipi/repos/runtipi/apps/cistech-tunnel/shared:/shared
|
|
||||||
- /etc/runtipi/repos/runtipi/apps/cistech-tunnel/shared/xstartup:/root/.vnc/xstartup:ro
|
|
||||||
devices:
|
|
||||||
- /dev/net/tun
|
|
||||||
privileged: true
|
|
||||||
cap_add:
|
|
||||||
- NET_ADMIN
|
|
||||||
stop_grace_period: 30s
|
|
||||||
labels:
|
labels:
|
||||||
generated: true
|
generated: true
|
||||||
traefik.enable: true
|
traefik.enable: true
|
||||||
traefik.docker.network: runtipi_tipi_main_network
|
traefik.docker.network: runtipi_tipi_main_network
|
||||||
traefik.http.middlewares.cistech-tunnel-runtipi-web-redirect.redirectscheme.scheme: https
|
traefik.http.middlewares.cistech-tunnel-runtipi-web-redirect.redirectscheme.scheme: https
|
||||||
traefik.http.services.cistech-tunnel-runtipi.loadbalancer.server.port: "6092"
|
traefik.http.services.cistech-tunnel-runtipi.loadbalancer.server.port: "6902"
|
||||||
traefik.http.routers.cistech-tunnel-runtipi-insecure.rule: Host(`${APP_DOMAIN}`)
|
traefik.http.routers.cistech-tunnel-runtipi-insecure.rule: Host(`${APP_DOMAIN}`)
|
||||||
traefik.http.routers.cistech-tunnel-runtipi-insecure.entrypoints: web
|
traefik.http.routers.cistech-tunnel-runtipi-insecure.entrypoints: web
|
||||||
traefik.http.routers.cistech-tunnel-runtipi-insecure.service: cistech-tunnel-runtipi
|
traefik.http.routers.cistech-tunnel-runtipi-insecure.service: cistech-tunnel-runtipi
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
#
|
#
|
||||||
# Host routing script for cistech-tunnel
|
# Host routing script for cistech-tunnel
|
||||||
# Routes TARGET_IP through the VPN container
|
# Routes target subnets through the VPN container
|
||||||
#
|
#
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
ACTION="${1:-start}"
|
ACTION="${1:-start}"
|
||||||
|
|
||||||
# Fixed configuration (we assigned these)
|
# Fixed configuration
|
||||||
CONTAINER_IP="172.30.0.10"
|
CONTAINER_IP="172.30.0.10"
|
||||||
BRIDGE_NAME="br-vpn-static"
|
BRIDGE_NAME="br-vpn-static"
|
||||||
TARGET_IP="${TARGET_IP:-}"
|
TARGET_SUBNETS="10.3.1.0/24 10.255.255.0/24"
|
||||||
LAN_SUBNET="192.168.0.0/23"
|
LAN_SUBNET="192.168.0.0/23"
|
||||||
LAN_INTERFACES="eth0 eth1 wlan0"
|
LAN_INTERFACES="eth0 eth1 wlan0"
|
||||||
LOG_FILE="/var/log/cistech-routing.log"
|
LOG_FILE="/var/log/cistech-routing.log"
|
||||||
@@ -25,12 +25,10 @@ get_lan_interface() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
remove_routes() {
|
remove_routes() {
|
||||||
log "Removing stale routes for $TARGET_IP..."
|
log "Removing stale routes..."
|
||||||
|
for subnet in $TARGET_SUBNETS; do
|
||||||
# Remove any existing route to TARGET_IP
|
ip route del "$subnet" 2>/dev/null || true
|
||||||
ip route del "$TARGET_IP" 2>/dev/null || true
|
done
|
||||||
ip route del "$TARGET_IP/32" 2>/dev/null || true
|
|
||||||
|
|
||||||
log "Stale routes removed"
|
log "Stale routes removed"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,35 +39,35 @@ apply_routes() {
|
|||||||
log "Applying host routing rules..."
|
log "Applying host routing rules..."
|
||||||
log " Container IP: $CONTAINER_IP"
|
log " Container IP: $CONTAINER_IP"
|
||||||
log " Bridge: $BRIDGE_NAME"
|
log " Bridge: $BRIDGE_NAME"
|
||||||
log " Target IP: $TARGET_IP"
|
log " Target subnets: $TARGET_SUBNETS"
|
||||||
log " LAN interface: ${lan_if:-unknown}"
|
log " LAN interface: ${lan_if:-unknown}"
|
||||||
|
|
||||||
# Enable IP forwarding
|
# Enable IP forwarding
|
||||||
echo 1 > /proc/sys/net/ipv4/ip_forward
|
echo 1 > /proc/sys/net/ipv4/ip_forward
|
||||||
log "IP forwarding enabled"
|
log "IP forwarding enabled"
|
||||||
|
|
||||||
# Add route to TARGET_IP via container
|
# Add routes to target subnets via container
|
||||||
ip route replace "$TARGET_IP/32" via "$CONTAINER_IP" dev "$BRIDGE_NAME"
|
for subnet in $TARGET_SUBNETS; do
|
||||||
log "Route added: $TARGET_IP via $CONTAINER_IP dev $BRIDGE_NAME"
|
ip route replace "$subnet" via "$CONTAINER_IP" dev "$BRIDGE_NAME"
|
||||||
|
log "Route added: $subnet via $CONTAINER_IP dev $BRIDGE_NAME"
|
||||||
|
done
|
||||||
|
|
||||||
# Allow forwarding in DOCKER-USER chain for all LAN interfaces
|
# Allow forwarding in DOCKER-USER chain for all LAN interfaces
|
||||||
for lan_if in $LAN_INTERFACES; do
|
for lan_if in $LAN_INTERFACES; do
|
||||||
# Check if interface exists
|
|
||||||
if ip link show "$lan_if" &>/dev/null; then
|
if ip link show "$lan_if" &>/dev/null; then
|
||||||
# Allow traffic from LAN to container for TARGET_IP
|
# Allow traffic from LAN to container bridge
|
||||||
iptables -C DOCKER-USER -i "$lan_if" -o "$BRIDGE_NAME" -d "$TARGET_IP" -j ACCEPT 2>/dev/null || \
|
iptables -C DOCKER-USER -i "$lan_if" -o "$BRIDGE_NAME" -j ACCEPT 2>/dev/null || \
|
||||||
iptables -I DOCKER-USER 1 -i "$lan_if" -o "$BRIDGE_NAME" -d "$TARGET_IP" -j ACCEPT
|
iptables -I DOCKER-USER 1 -i "$lan_if" -o "$BRIDGE_NAME" -j ACCEPT
|
||||||
|
|
||||||
# Allow return traffic
|
# Allow return traffic
|
||||||
iptables -C DOCKER-USER -i "$BRIDGE_NAME" -o "$lan_if" -s "$TARGET_IP" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || \
|
iptables -C DOCKER-USER -i "$BRIDGE_NAME" -o "$lan_if" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || \
|
||||||
iptables -I DOCKER-USER 1 -i "$BRIDGE_NAME" -o "$lan_if" -s "$TARGET_IP" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
iptables -I DOCKER-USER 1 -i "$BRIDGE_NAME" -o "$lan_if" -m state --state RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
|
||||||
log "DOCKER-USER iptables rules added for $lan_if <-> $BRIDGE_NAME"
|
log "DOCKER-USER iptables rules added for $lan_if <-> $BRIDGE_NAME"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Masquerade traffic from LAN subnet to VPN bridge (so return traffic routes correctly)
|
# Masquerade traffic from LAN subnet to VPN bridge (using nft)
|
||||||
# Use nft since iptables-nft backend doesn't support iptables -t nat commands
|
|
||||||
if ! nft list chain ip nat POSTROUTING 2>/dev/null | grep -q "saddr $LAN_SUBNET.*oifname.*$BRIDGE_NAME.*masquerade"; then
|
if ! nft list chain ip nat POSTROUTING 2>/dev/null | grep -q "saddr $LAN_SUBNET.*oifname.*$BRIDGE_NAME.*masquerade"; then
|
||||||
nft add rule ip nat POSTROUTING ip saddr "$LAN_SUBNET" oifname "$BRIDGE_NAME" counter masquerade
|
nft add rule ip nat POSTROUTING ip saddr "$LAN_SUBNET" oifname "$BRIDGE_NAME" counter masquerade
|
||||||
log "NAT masquerade rule added for $LAN_SUBNET -> $BRIDGE_NAME"
|
log "NAT masquerade rule added for $LAN_SUBNET -> $BRIDGE_NAME"
|
||||||
@@ -77,19 +75,21 @@ apply_routes() {
|
|||||||
log "NAT masquerade rule already exists for $LAN_SUBNET -> $BRIDGE_NAME"
|
log "NAT masquerade rule already exists for $LAN_SUBNET -> $BRIDGE_NAME"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "OK: Host routing applied - $TARGET_IP via $CONTAINER_IP ($BRIDGE_NAME)"
|
log "OK: Host routing applied"
|
||||||
}
|
}
|
||||||
|
|
||||||
remove_all() {
|
remove_all() {
|
||||||
log "Removing all routing rules..."
|
log "Removing all routing rules..."
|
||||||
|
|
||||||
# Remove route
|
# Remove routes
|
||||||
ip route del "$TARGET_IP/32" via "$CONTAINER_IP" dev "$BRIDGE_NAME" 2>/dev/null || true
|
for subnet in $TARGET_SUBNETS; do
|
||||||
|
ip route del "$subnet" via "$CONTAINER_IP" dev "$BRIDGE_NAME" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
# Remove iptables rules for all LAN interfaces
|
# Remove iptables rules for all LAN interfaces
|
||||||
for lan_if in $LAN_INTERFACES; do
|
for lan_if in $LAN_INTERFACES; do
|
||||||
iptables -D DOCKER-USER -i "$lan_if" -o "$BRIDGE_NAME" -d "$TARGET_IP" -j ACCEPT 2>/dev/null || true
|
iptables -D DOCKER-USER -i "$lan_if" -o "$BRIDGE_NAME" -j ACCEPT 2>/dev/null || true
|
||||||
iptables -D DOCKER-USER -i "$BRIDGE_NAME" -o "$lan_if" -s "$TARGET_IP" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true
|
iptables -D DOCKER-USER -i "$BRIDGE_NAME" -o "$lan_if" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true
|
||||||
done
|
done
|
||||||
|
|
||||||
# Remove masquerade rule (using nft)
|
# Remove masquerade rule (using nft)
|
||||||
|
|||||||
@@ -1,55 +1,68 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
#
|
#
|
||||||
# Install host-side systemd services for rego-tunnel
|
# Install host-side systemd services for cistech-tunnel
|
||||||
# Run this ONCE on the host after installing the app in Runtipi
|
# Run this ONCE on the host after app install
|
||||||
#
|
#
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
APP_DATA_DIR="/etc/runtipi/app-data/runtipi/rego-tunnel"
|
APP_DATA_DIR="/etc/runtipi/app-data/runtipi/cistech-tunnel"
|
||||||
|
|
||||||
echo "Installing rego-tunnel host services..."
|
echo "Installing cistech-tunnel host services..."
|
||||||
|
|
||||||
# Create the path watcher unit
|
# Create app-data directory for trigger file
|
||||||
cat << 'EOF' | sudo tee /etc/systemd/system/rego-routing-watcher.path
|
sudo mkdir -p "$APP_DATA_DIR"
|
||||||
|
|
||||||
|
# Create the path unit (watches for trigger file)
|
||||||
|
sudo tee /etc/systemd/system/cistech-routing-watcher.path > /dev/null << EOF
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Watch for rego-tunnel routing trigger
|
Description=Watch for cistech-tunnel routing trigger
|
||||||
|
|
||||||
[Path]
|
[Path]
|
||||||
PathExists=/etc/runtipi/app-data/runtipi/rego-tunnel/restart-routing
|
PathExists=$APP_DATA_DIR/restart-routing
|
||||||
Unit=rego-routing-watcher.service
|
Unit=cistech-routing-watcher.service
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Create the service unit
|
# Create the service unit (applies routes when triggered)
|
||||||
cat << EOF | sudo tee /etc/systemd/system/rego-routing-watcher.service
|
sudo tee /etc/systemd/system/cistech-routing-watcher.service > /dev/null << EOF
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Apply rego-tunnel routing rules
|
Description=Apply cistech-tunnel routing rules
|
||||||
After=docker.service
|
After=docker.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
ExecStart=${SCRIPT_DIR}/host-routing.sh restart
|
ExecStart=$SCRIPT_DIR/host-routing.sh restart
|
||||||
ExecStartPost=/bin/rm -f ${APP_DATA_DIR}/restart-routing
|
ExecStartPost=/bin/rm -f $APP_DATA_DIR/restart-routing
|
||||||
ExecStartPost=/bin/bash -c 'echo "trigger cleared at \$(date)" >> ${APP_DATA_DIR}/watcher.log'
|
ExecStartPost=/bin/bash -c 'echo "trigger cleared at \$(date)" >> $APP_DATA_DIR/watcher.log'
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Make host-routing.sh executable
|
# Make host-routing.sh executable
|
||||||
sudo chmod +x "${SCRIPT_DIR}/host-routing.sh"
|
chmod +x "$SCRIPT_DIR/host-routing.sh"
|
||||||
|
|
||||||
# Reload systemd and enable the watcher
|
# Reload systemd and enable the watcher
|
||||||
sudo systemctl daemon-reload
|
sudo systemctl daemon-reload
|
||||||
sudo systemctl enable --now rego-routing-watcher.path
|
sudo systemctl enable cistech-routing-watcher.path
|
||||||
|
sudo systemctl start cistech-routing-watcher.path
|
||||||
|
|
||||||
|
# Disable the old boot-only service if it exists
|
||||||
|
if systemctl is-enabled cistech-routing.service &>/dev/null; then
|
||||||
|
echo "Disabling old cistech-routing.service (replaced by watcher)..."
|
||||||
|
sudo systemctl stop cistech-routing.service 2>/dev/null || true
|
||||||
|
sudo systemctl disable cistech-routing.service 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Apply routes now
|
||||||
|
echo "Applying initial routes..."
|
||||||
|
sudo "$SCRIPT_DIR/host-routing.sh" start
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Done! Services installed:"
|
echo "Done! Watcher installed and routes applied."
|
||||||
echo " - rego-routing-watcher.path (watches for trigger file)"
|
|
||||||
echo " - rego-routing-watcher.service (applies routing rules)"
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "To check status:"
|
echo "To trigger route refresh from container:"
|
||||||
echo " systemctl status rego-routing-watcher.path"
|
echo " touch /runtime/restart-routing"
|
||||||
echo ""
|
echo ""
|
||||||
echo "To manually trigger routing:"
|
echo "To check watcher status:"
|
||||||
echo " touch ${APP_DATA_DIR}/restart-routing"
|
echo " systemctl status cistech-routing-watcher.path"
|
||||||
|
|||||||
@@ -1,209 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Cistech VPN Connection Script with OpenConnect SSO
|
|
||||||
# Uses config.toml for configuration, openconnect-sso handles auto-connect
|
|
||||||
|
|
||||||
# Credentials from environment variables
|
|
||||||
OC_URL="${OC_URL:-}"
|
|
||||||
OC_USER="${OC_USER:-}"
|
|
||||||
OC_PASSWORD="${OC_PASSWORD:-}"
|
|
||||||
OC_TOTP_SECRET="${OC_TOTP_SECRET:-}"
|
|
||||||
OC_INTERFACE="${OC_INTERFACE:-tun0}"
|
|
||||||
|
|
||||||
CONFIG_DIR="$HOME/.config/openconnect-sso"
|
|
||||||
CONFIG_FILE="$CONFIG_DIR/config.toml"
|
|
||||||
|
|
||||||
# Colors
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
CYAN='\033[0;36m'
|
|
||||||
GRAY='\033[0;90m'
|
|
||||||
NC='\033[0m'
|
|
||||||
|
|
||||||
print_banner() {
|
|
||||||
echo -e "${CYAN}========================================${NC}"
|
|
||||||
echo -e "${CYAN} Cistech VPN Connection Script ${NC}"
|
|
||||||
echo -e "${CYAN}========================================${NC}"
|
|
||||||
echo ""
|
|
||||||
}
|
|
||||||
|
|
||||||
log() {
|
|
||||||
local level="$1"
|
|
||||||
local msg="$2"
|
|
||||||
local ts=$(date '+%H:%M:%S')
|
|
||||||
case $level in
|
|
||||||
INFO) echo -e "${GRAY}[$ts]${NC} ${GREEN}[INFO]${NC} $msg" ;;
|
|
||||||
WARN) echo -e "${GRAY}[$ts]${NC} ${YELLOW}[WARN]${NC} $msg" ;;
|
|
||||||
ERROR) echo -e "${GRAY}[$ts]${NC} ${RED}[ERROR]${NC} $msg" ;;
|
|
||||||
DEBUG) echo -e "${GRAY}[$ts]${NC} ${CYAN}[DEBUG]${NC} $msg" ;;
|
|
||||||
*) echo -e "${GRAY}[$ts]${NC} $msg" ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
get_totp() {
|
|
||||||
[ -n "$OC_TOTP_SECRET" ] && oathtool --totp -b "$OC_TOTP_SECRET"
|
|
||||||
}
|
|
||||||
|
|
||||||
get_vpn_interface() {
|
|
||||||
ip link show "$OC_INTERFACE" 2>/dev/null | grep -q "UP" && echo "$OC_INTERFACE"
|
|
||||||
}
|
|
||||||
|
|
||||||
get_vpn_ip() {
|
|
||||||
local iface=$(get_vpn_interface)
|
|
||||||
[ -n "$iface" ] && ip addr show "$iface" 2>/dev/null | grep -oP 'inet \K[\d.]+' | head -1
|
|
||||||
}
|
|
||||||
|
|
||||||
check_vpn_status() {
|
|
||||||
local vpn_iface=$(get_vpn_interface)
|
|
||||||
if [ -n "$vpn_iface" ]; then
|
|
||||||
log INFO "VPN: ${GREEN}CONNECTED${NC} ($(get_vpn_ip))"
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
log WARN "VPN: ${RED}NOT CONNECTED${NC}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_keyring() {
|
|
||||||
if [[ -n "$OC_TOTP_SECRET" && -n "$OC_USER" ]]; then
|
|
||||||
python3 -c "
|
|
||||||
import keyring
|
|
||||||
keyring.set_password('openconnect-sso', 'totp/$OC_USER', '$OC_TOTP_SECRET'.upper())
|
|
||||||
" 2>/dev/null && log INFO "TOTP stored in keyring"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_config() {
|
|
||||||
mkdir -p "$CONFIG_DIR"
|
|
||||||
|
|
||||||
# Determine browser display mode
|
|
||||||
local browser_mode="hidden"
|
|
||||||
[ -z "$OC_USER" ] && browser_mode="shown"
|
|
||||||
|
|
||||||
cat > "$CONFIG_FILE" << EOF
|
|
||||||
[openconnect]
|
|
||||||
server = "$OC_URL"
|
|
||||||
interface = "$OC_INTERFACE"
|
|
||||||
|
|
||||||
[sso]
|
|
||||||
browser_display_mode = "$browser_mode"
|
|
||||||
EOF
|
|
||||||
|
|
||||||
[ -n "$OC_USER" ] && echo "user = \"$OC_USER\"" >> "$CONFIG_FILE"
|
|
||||||
|
|
||||||
log INFO "Config written to $CONFIG_FILE"
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_forwarding() {
|
|
||||||
log INFO "Setting up IP forwarding..."
|
|
||||||
local vpn_iface=$(get_vpn_interface)
|
|
||||||
if [ -z "$vpn_iface" ]; then
|
|
||||||
log ERROR "No VPN interface found!"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
sysctl -w net.ipv4.ip_forward=1 >/dev/null
|
|
||||||
iptables -t nat -C POSTROUTING -o "$vpn_iface" -j MASQUERADE 2>/dev/null || \
|
|
||||||
iptables -t nat -A POSTROUTING -o "$vpn_iface" -j MASQUERADE
|
|
||||||
touch /runtime/restart-routing 2>/dev/null
|
|
||||||
log INFO "Forwarding configured"
|
|
||||||
}
|
|
||||||
|
|
||||||
kill_vpn() {
|
|
||||||
log INFO "Stopping VPN..."
|
|
||||||
pkill -f "openconnect" 2>/dev/null
|
|
||||||
sleep 1
|
|
||||||
}
|
|
||||||
|
|
||||||
start_vpn() {
|
|
||||||
log INFO "Starting OpenConnect SSO..."
|
|
||||||
|
|
||||||
setup_keyring
|
|
||||||
setup_config
|
|
||||||
|
|
||||||
log INFO "Credentials:"
|
|
||||||
echo -e " ${CYAN}URL: $OC_URL${NC}"
|
|
||||||
echo -e " ${CYAN}User: $OC_USER${NC}"
|
|
||||||
[ -n "$OC_TOTP_SECRET" ] && echo -e " ${CYAN}TOTP: $(get_totp)${NC}"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Run openconnect-sso (it handles reconnection)
|
|
||||||
if [[ -n "$OC_PASSWORD" ]]; then
|
|
||||||
echo "$OC_PASSWORD" | openconnect-sso
|
|
||||||
else
|
|
||||||
echo "" | openconnect-sso
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
show_totp() {
|
|
||||||
[ -z "$OC_TOTP_SECRET" ] && { log ERROR "No TOTP secret"; return 1; }
|
|
||||||
log INFO "Live TOTP (Ctrl+C to stop)"
|
|
||||||
while true; do
|
|
||||||
echo -ne "\r TOTP: ${GREEN}$(get_totp)${NC} (${YELLOW}$((30 - $(date +%s) % 30))s${NC}) "
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
main_menu() {
|
|
||||||
echo -e "${GREEN}Options:${NC}"
|
|
||||||
echo -e " ${CYAN}1${NC} - Start VPN"
|
|
||||||
echo -e " ${CYAN}2${NC} - Stop VPN"
|
|
||||||
echo -e " ${CYAN}3${NC} - Show TOTP"
|
|
||||||
echo -e " ${CYAN}4${NC} - Setup forwarding"
|
|
||||||
echo -e " ${CYAN}5${NC} - Check status"
|
|
||||||
echo -e " ${CYAN}6${NC} - Show routes"
|
|
||||||
echo -e " ${CYAN}q${NC} - Quit"
|
|
||||||
echo ""
|
|
||||||
}
|
|
||||||
|
|
||||||
# Parse args
|
|
||||||
for arg in "$@"; do
|
|
||||||
case $arg in
|
|
||||||
-m|--menu) SKIP_AUTO=true ;;
|
|
||||||
-s|--status) check_vpn_status; exit 0 ;;
|
|
||||||
--help) echo "Usage: $0 [-m|--menu] [-s|--status]"; exit 0 ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
# Main
|
|
||||||
print_banner
|
|
||||||
log INFO "openconnect-vpn started"
|
|
||||||
|
|
||||||
if [ "${SKIP_AUTO:-false}" = "true" ]; then
|
|
||||||
log INFO "Menu mode - skipping auto-connect"
|
|
||||||
elif check_vpn_status; then
|
|
||||||
setup_forwarding
|
|
||||||
else
|
|
||||||
log INFO "Auto-starting VPN..."
|
|
||||||
start_vpn &
|
|
||||||
VPN_PID=$!
|
|
||||||
|
|
||||||
# Wait for connection
|
|
||||||
for i in {1..60}; do
|
|
||||||
sleep 2
|
|
||||||
if [ -n "$(get_vpn_interface)" ]; then
|
|
||||||
log INFO "VPN connected!"
|
|
||||||
setup_forwarding
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Menu loop
|
|
||||||
while true; do
|
|
||||||
echo ""
|
|
||||||
main_menu
|
|
||||||
echo -ne "${CYAN}Choice: ${NC}"
|
|
||||||
read -r choice
|
|
||||||
echo ""
|
|
||||||
[[ -z "${choice// }" ]] && continue
|
|
||||||
case $choice in
|
|
||||||
1) start_vpn ;;
|
|
||||||
2) kill_vpn ;;
|
|
||||||
3) show_totp ;;
|
|
||||||
4) setup_forwarding ;;
|
|
||||||
5) check_vpn_status ;;
|
|
||||||
6) ip -4 route show ;;
|
|
||||||
q|Q) log INFO "Goodbye!"; exit 0 ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
@@ -1,21 +1,21 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
#
|
#
|
||||||
# Uninstall host-side systemd services for rego-tunnel
|
# Uninstall host-side systemd services for cistech-tunnel
|
||||||
#
|
#
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
echo "Removing rego-tunnel host services..."
|
echo "Removing cistech-tunnel host services..."
|
||||||
|
|
||||||
# Stop and disable the watcher
|
# Stop and disable the watcher
|
||||||
sudo systemctl stop rego-routing-watcher.path 2>/dev/null || true
|
sudo systemctl stop cistech-routing-watcher.path 2>/dev/null || true
|
||||||
sudo systemctl disable rego-routing-watcher.path 2>/dev/null || true
|
sudo systemctl disable cistech-routing-watcher.path 2>/dev/null || true
|
||||||
|
|
||||||
# Remove routing rules
|
# Remove routing rules
|
||||||
/etc/runtipi/repos/runtipi/apps/rego-tunnel/shared/host-routing.sh stop 2>/dev/null || true
|
/etc/runtipi/repos/runtipi/apps/cistech-tunnel/shared/host-routing.sh stop 2>/dev/null || true
|
||||||
|
|
||||||
# Remove systemd units
|
# Remove systemd units
|
||||||
sudo rm -f /etc/systemd/system/rego-routing-watcher.path
|
sudo rm -f /etc/systemd/system/cistech-routing-watcher.path
|
||||||
sudo rm -f /etc/systemd/system/rego-routing-watcher.service
|
sudo rm -f /etc/systemd/system/cistech-routing-watcher.service
|
||||||
|
|
||||||
# Reload systemd
|
# Reload systemd
|
||||||
sudo systemctl daemon-reload
|
sudo systemctl daemon-reload
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# VNC xstartup - launches terminal with openconnect-vpn script
|
|
||||||
|
|
||||||
unset SESSION_MANAGER
|
|
||||||
unset DBUS_SESSION_BUS_ADDRESS
|
|
||||||
|
|
||||||
# Import environment variables from container (PID 1)
|
|
||||||
# Systemd services don't inherit Docker env vars, so we source them here
|
|
||||||
while IFS= read -r -d '' line; do
|
|
||||||
export "$line"
|
|
||||||
done < /proc/1/environ
|
|
||||||
|
|
||||||
export XDG_RUNTIME_DIR=/tmp/runtime-root
|
|
||||||
mkdir -p $XDG_RUNTIME_DIR
|
|
||||||
chmod 700 $XDG_RUNTIME_DIR
|
|
||||||
|
|
||||||
# GPU/WebKit workarounds for browser
|
|
||||||
export GDK_BACKEND=x11
|
|
||||||
export WEBKIT_DISABLE_DMABUF_RENDERER=1
|
|
||||||
|
|
||||||
# Start dbus session
|
|
||||||
[ -x /usr/bin/dbus-launch ] && eval $(dbus-launch --sh-syntax --exit-with-session)
|
|
||||||
|
|
||||||
# Start window manager
|
|
||||||
openbox &
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
# Disable screen blanking and power saving
|
|
||||||
xset s off 2>/dev/null || true
|
|
||||||
xset -dpms 2>/dev/null || true
|
|
||||||
xset s noblank 2>/dev/null || true
|
|
||||||
|
|
||||||
# Make script executable and launch in terminal
|
|
||||||
chmod +x /shared/openconnect-vpn 2>/dev/null || true
|
|
||||||
xterm -fa 'Monospace' -fs 11 -bg black -fg white -geometry 130x45+10+10 \
|
|
||||||
-title "Cistech VPN Terminal" \
|
|
||||||
-e "bash -c '/shared/openconnect-vpn; exec bash'" &
|
|
||||||
|
|
||||||
wait
|
|
||||||
43
apps/cistech-tunnel/source/Dockerfile
Executable file
43
apps/cistech-tunnel/source/Dockerfile
Executable file
@@ -0,0 +1,43 @@
|
|||||||
|
FROM ubuntu:24.04
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
|
PLAYWRIGHT_BROWSERS_PATH=/ms-playwright \
|
||||||
|
VIRTUAL_ENV=/opt/venv \
|
||||||
|
PATH=/opt/venv/bin:$PATH \
|
||||||
|
QTWEBENGINE_DISABLE_SANDBOX=1 \
|
||||||
|
QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox --disable-gpu"
|
||||||
|
# Credentials come from environment variables at runtime:
|
||||||
|
# OC_URL, OC_SERVERCERT, OC_USER, OC_TOTP_SECRET, VNC_PASSWORD
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
openconnect iproute2 iptables ca-certificates \
|
||||||
|
python3 python3-pip python3-venv \
|
||||||
|
vpnc-scripts curl wget openssh-client \
|
||||||
|
x11vnc xvfb fluxbox novnc websockify xterm nano oathtool \
|
||||||
|
xauth libnss3 libatk1.0-0 libatk-bridge2.0-0 \
|
||||||
|
libx11-6 libx11-xcb1 libxcomposite1 libxrandr2 libgbm1 libxdamage1 \
|
||||||
|
libpango-1.0-0 fonts-liberation \
|
||||||
|
libegl1 libgl1 libopengl0 libdbus-1-3 libglib2.0-0 \
|
||||||
|
libxkbcommon0 libxkbcommon-x11-0 \
|
||||||
|
libxcb1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render0 libxcb-render-util0 libxcb-shm0 libxcb-xfixes0 libxcb-xinerama0 libxcb-randr0 libxcb-glx0 \
|
||||||
|
sudo && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN apt-get update && (apt-get install -y libasound2t64 || apt-get install -y libasound2) && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Python venv + Playwright + openconnect-sso
|
||||||
|
RUN python3 -m venv "$VIRTUAL_ENV"
|
||||||
|
RUN pip install --no-cache-dir openconnect-sso playwright keyring keyrings.alt && \
|
||||||
|
python -m playwright install --with-deps chromium
|
||||||
|
|
||||||
|
# Cloudflared (amd64)
|
||||||
|
RUN arch=$(dpkg --print-architecture) && \
|
||||||
|
if [ "$arch" = "amd64" ]; then \
|
||||||
|
curl -fsSL https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb -o /tmp/cloudflared.deb && \
|
||||||
|
apt-get update && apt-get install -y /tmp/cloudflared.deb && rm -f /tmp/cloudflared.deb ; \
|
||||||
|
else \
|
||||||
|
echo "Install cloudflared manually for arch=$arch" && exit 1 ; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
COPY entrypoint.sh /entrypoint.sh
|
||||||
|
RUN chmod +x /entrypoint.sh
|
||||||
|
EXPOSE 6901
|
||||||
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
224
apps/cistech-tunnel/source/entrypoint.sh
Executable file
224
apps/cistech-tunnel/source/entrypoint.sh
Executable file
@@ -0,0 +1,224 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
: "${OC_URL:?OC_URL required}"
|
||||||
|
|
||||||
|
# Auto-fetch server certificate pin if not provided
|
||||||
|
get_server_cert_pin() {
|
||||||
|
local url="$1"
|
||||||
|
local host=$(echo "$url" | sed -E 's|https?://([^/:]+).*|\1|')
|
||||||
|
local port=443
|
||||||
|
|
||||||
|
echo "Fetching certificate pin from $host:$port..." >&2
|
||||||
|
|
||||||
|
# Get certificate and compute pin-sha256
|
||||||
|
local pin=$(echo | openssl s_client -connect "$host:$port" -servername "$host" 2>/dev/null | \
|
||||||
|
openssl x509 -pubkey -noout 2>/dev/null | \
|
||||||
|
openssl pkey -pubin -outform DER 2>/dev/null | \
|
||||||
|
openssl dgst -sha256 -binary | \
|
||||||
|
base64)
|
||||||
|
|
||||||
|
if [[ -n "$pin" ]]; then
|
||||||
|
echo "pin-sha256:$pin"
|
||||||
|
else
|
||||||
|
echo "ERROR: Failed to fetch certificate from $host" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get or fetch OC_SERVERCERT
|
||||||
|
if [[ -z "${OC_SERVERCERT:-}" ]]; then
|
||||||
|
OC_SERVERCERT=$(get_server_cert_pin "$OC_URL")
|
||||||
|
echo "Auto-detected server cert: $OC_SERVERCERT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
NOVNC_PORT="${NOVNC_PORT:-6901}"
|
||||||
|
VNC_PASSWORD="${VNC_PASSWORD:-changeme}"
|
||||||
|
DISPLAY_ADDR="${DISPLAY:-:1}"
|
||||||
|
OC_INTERFACE="${OC_INTERFACE:-tun0}"
|
||||||
|
OC_USER="${OC_USER:-}"
|
||||||
|
OC_PASSWORD="${OC_PASSWORD:-}"
|
||||||
|
OC_TOTP_SECRET="${OC_TOTP_SECRET:-}"
|
||||||
|
|
||||||
|
# Default to hidden browser if OC_USER is set
|
||||||
|
if [[ -n "$OC_USER" ]]; then
|
||||||
|
OC_SSO_ARGS_DEFAULT="--browser-display-mode hidden -u $OC_USER"
|
||||||
|
else
|
||||||
|
OC_SSO_ARGS_DEFAULT="--browser-display-mode shown"
|
||||||
|
fi
|
||||||
|
|
||||||
|
CLOUDFLARED_MODE="${CLOUDFLARED_MODE:-off}" # off|token|config
|
||||||
|
CLOUDFLARED_TOKEN="${CLOUDFLARED_TOKEN:-}"
|
||||||
|
SSH_TUNNEL_ENABLE="${SSH_TUNNEL_ENABLE:-0}"
|
||||||
|
SSH_DEST="${SSH_DEST:-zawa@10.3.1.201}"
|
||||||
|
SSH_FORWARDS="${SSH_FORWARDS:-0.0.0.0:8090:localhost:8090}"
|
||||||
|
|
||||||
|
pids=()
|
||||||
|
|
||||||
|
# Setup keyring with TOTP secret if provided
|
||||||
|
setup_keyring() {
|
||||||
|
if [[ -n "$OC_TOTP_SECRET" && -n "$OC_USER" ]]; then
|
||||||
|
python3 -c "
|
||||||
|
import keyring
|
||||||
|
keyring.set_password('openconnect-sso', 'totp/$OC_USER', '$OC_TOTP_SECRET'.upper())
|
||||||
|
print('TOTP secret stored in keyring for $OC_USER')
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create vpn_connect command in PATH and save environment
|
||||||
|
create_vpn_command() {
|
||||||
|
# Save environment variables to a file
|
||||||
|
cat > /etc/vpn.env << ENVFILE
|
||||||
|
export OC_URL="$OC_URL"
|
||||||
|
export OC_SERVERCERT="$OC_SERVERCERT"
|
||||||
|
export OC_INTERFACE="$OC_INTERFACE"
|
||||||
|
export OC_USER="$OC_USER"
|
||||||
|
export OC_PASSWORD="$OC_PASSWORD"
|
||||||
|
export OC_SSO_ARGS_DEFAULT="$OC_SSO_ARGS_DEFAULT"
|
||||||
|
export OC_SSO_ARGS="${OC_SSO_ARGS:-$OC_SSO_ARGS_DEFAULT}"
|
||||||
|
export OC_AUTHGROUP="${OC_AUTHGROUP:-}"
|
||||||
|
export OC_USERAGENT="${OC_USERAGENT:-}"
|
||||||
|
export OC_EXTRA_ARGS="${OC_EXTRA_ARGS:-}"
|
||||||
|
export OC_TOTP_SECRET="$OC_TOTP_SECRET"
|
||||||
|
export DISPLAY=":1"
|
||||||
|
ENVFILE
|
||||||
|
|
||||||
|
# Build openconnect command
|
||||||
|
OPENCONNECT_CMD="/usr/sbin/openconnect --protocol=anyconnect --servercert $OC_SERVERCERT --interface $OC_INTERFACE --script /usr/share/vpnc-scripts/vpnc-script"
|
||||||
|
[[ -n "${OC_AUTHGROUP:-}" ]] && OPENCONNECT_CMD+=" --authgroup $OC_AUTHGROUP"
|
||||||
|
[[ -n "${OC_USERAGENT:-}" ]] && OPENCONNECT_CMD+=" --useragent $OC_USERAGENT"
|
||||||
|
[[ -n "${OC_EXTRA_ARGS:-}" ]] && OPENCONNECT_CMD+=" ${OC_EXTRA_ARGS}"
|
||||||
|
echo "export OPENCONNECT_CMD=\"$OPENCONNECT_CMD\"" >> /etc/vpn.env
|
||||||
|
|
||||||
|
cat > /usr/local/bin/vpn_connect << 'VPNCMD'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
source /etc/vpn.env
|
||||||
|
echo "[$(date)] Starting VPN connection..."
|
||||||
|
|
||||||
|
# Set password for openconnect
|
||||||
|
export OPENCONNECT_PASSWORD="$OC_PASSWORD"
|
||||||
|
|
||||||
|
# openconnect-sso reads TOTP from keyring automatically
|
||||||
|
# Pass password via stdin for SSO form if needed
|
||||||
|
if [[ -n "$OC_USER" && -n "$OC_PASSWORD" ]]; then
|
||||||
|
echo "$OC_PASSWORD" | openconnect-sso -s "$OC_URL" ${OC_SSO_ARGS:-$OC_SSO_ARGS_DEFAULT} -- $OPENCONNECT_CMD
|
||||||
|
elif [[ -n "$OC_USER" ]]; then
|
||||||
|
echo "" | openconnect-sso -s "$OC_URL" ${OC_SSO_ARGS:-$OC_SSO_ARGS_DEFAULT} -- $OPENCONNECT_CMD
|
||||||
|
else
|
||||||
|
openconnect-sso -s "$OC_URL" ${OC_SSO_ARGS:-$OC_SSO_ARGS_DEFAULT} -- $OPENCONNECT_CMD
|
||||||
|
fi
|
||||||
|
VPNCMD
|
||||||
|
chmod +x /usr/local/bin/vpn_connect
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create VPN runner script that keeps shell open
|
||||||
|
create_vpn_script() {
|
||||||
|
cat > /tmp/vpn-runner.sh << 'VPNSCRIPT'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
cd /root
|
||||||
|
|
||||||
|
echo "============================================"
|
||||||
|
echo " Cistech VPN Container"
|
||||||
|
echo "============================================"
|
||||||
|
echo ""
|
||||||
|
echo "Commands:"
|
||||||
|
echo " vpn_connect - Start/restart VPN connection"
|
||||||
|
echo " Ctrl+C - Stop auto-reconnect and drop to shell"
|
||||||
|
echo ""
|
||||||
|
echo "Starting VPN with auto-reconnect..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
vpn_connect
|
||||||
|
echo ""
|
||||||
|
echo "[$(date)] VPN disconnected. Reconnecting in 10 seconds..."
|
||||||
|
echo "(Press Ctrl+C to stop auto-reconnect)"
|
||||||
|
sleep 10
|
||||||
|
done
|
||||||
|
VPNSCRIPT
|
||||||
|
chmod +x /tmp/vpn-runner.sh
|
||||||
|
}
|
||||||
|
|
||||||
|
start_gui() {
|
||||||
|
mkdir -p /root/.vnc
|
||||||
|
x11vnc -storepasswd "$VNC_PASSWORD" /root/.vnc/pass >/dev/null 2>&1 || true
|
||||||
|
rm -f /tmp/.X1-lock /tmp/.X11-unix/X1 2>/dev/null || true
|
||||||
|
Xvfb "$DISPLAY_ADDR" -screen 0 ${XVFB_WxHxD:-1280x800x24} +extension RANDR &
|
||||||
|
pids+=($!)
|
||||||
|
sleep 0.5
|
||||||
|
export DISPLAY="$DISPLAY_ADDR"
|
||||||
|
fluxbox >/tmp/fluxbox.log 2>&1 &
|
||||||
|
pids+=($!)
|
||||||
|
x11vnc -display "$DISPLAY_ADDR" -rfbauth /root/.vnc/pass -forever -shared -rfbport 5900 -quiet &
|
||||||
|
pids+=($!)
|
||||||
|
websockify --web=/usr/share/novnc/ 0.0.0.0:"$NOVNC_PORT" localhost:5900 >/tmp/websockify.log 2>&1 &
|
||||||
|
pids+=($!)
|
||||||
|
}
|
||||||
|
|
||||||
|
start_vpn_terminal() {
|
||||||
|
# Start xterm with VPN script
|
||||||
|
sleep 1
|
||||||
|
xterm -fa 'Monospace' -fs 11 -bg black -fg white -geometry 120x35+50+50 \
|
||||||
|
-T "Cistech VPN" -e /tmp/vpn-runner.sh &
|
||||||
|
pids+=($!)
|
||||||
|
}
|
||||||
|
|
||||||
|
start_cloudflared() {
|
||||||
|
case "$CLOUDFLARED_MODE" in
|
||||||
|
token)
|
||||||
|
[ -n "$CLOUDFLARED_TOKEN" ] && cloudflared tunnel run --token "$CLOUDFLARED_TOKEN" >/tmp/cloudflared.log 2>&1 &
|
||||||
|
pids+=($!)
|
||||||
|
;;
|
||||||
|
config)
|
||||||
|
cloudflared --no-autoupdate --config /etc/cloudflared/config.yml tunnel run >/tmp/cloudflared.log 2>&1 &
|
||||||
|
pids+=($!)
|
||||||
|
;;
|
||||||
|
off|*)
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
start_ssh_tunnel() {
|
||||||
|
[ "$SSH_TUNNEL_ENABLE" = "1" ] || return 0
|
||||||
|
IFS=',' read -ra LINES <<< "$SSH_FORWARDS"
|
||||||
|
args=(-N -o StrictHostKeyChecking=no -o ServerAliveInterval=60)
|
||||||
|
for m in "${LINES[@]}"; do args+=(-L "$m"); done
|
||||||
|
ssh "${args[@]}" "$SSH_DEST" &
|
||||||
|
pids+=($!)
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_nat() {
|
||||||
|
(
|
||||||
|
for i in {1..60}; do
|
||||||
|
if ip link show "$OC_INTERFACE" >/dev/null 2>&1; then
|
||||||
|
sysctl -w net.ipv4.ip_forward=1 >/dev/null
|
||||||
|
iptables -t nat -C POSTROUTING -o "$OC_INTERFACE" -j MASQUERADE 2>/dev/null || \
|
||||||
|
iptables -t nat -A POSTROUTING -o "$OC_INTERFACE" -j MASQUERADE
|
||||||
|
echo "NAT enabled on $OC_INTERFACE"
|
||||||
|
|
||||||
|
# Trigger host routing service
|
||||||
|
if [ -d /runtime ]; then
|
||||||
|
touch /runtime/restart-routing
|
||||||
|
echo "Host routing trigger sent"
|
||||||
|
fi
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
) &
|
||||||
|
}
|
||||||
|
|
||||||
|
trap 'kill 0' INT TERM
|
||||||
|
|
||||||
|
# Always start GUI now
|
||||||
|
setup_keyring
|
||||||
|
create_vpn_command
|
||||||
|
create_vpn_script
|
||||||
|
start_gui
|
||||||
|
start_vpn_terminal
|
||||||
|
setup_nat
|
||||||
|
start_cloudflared
|
||||||
|
start_ssh_tunnel
|
||||||
|
|
||||||
|
wait
|
||||||
Reference in New Issue
Block a user