From d3bf08c523a68250caa311d29df5c8dc5ad9e966 Mon Sep 17 00:00:00 2001 From: d-chrka Date: Sun, 15 Mar 2026 21:14:29 +0100 Subject: [PATCH] initial commit --- README.md | 107 ++++++++++++++++++++++++++++++++++++++++++++++ vpn-connect.sh | 78 +++++++++++++++++++++++++++++++++ vpn-disconnect.sh | 3 ++ 3 files changed, 188 insertions(+) create mode 100644 README.md create mode 100755 vpn-connect.sh create mode 100755 vpn-disconnect.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..6679672 --- /dev/null +++ b/README.md @@ -0,0 +1,107 @@ +# fastvpn — Automated Sophos SSL VPN Connect + +Fully automated connect to Sophos SSL VPN with TOTP (no dialog, no manual input). + +## How it works + +Sophos SSL VPN uses OpenVPN with certificate + username/password+OTP authentication. +NetworkManager's normal flow requires an interactive KDE dialog which cannot be automated +reliably on Wayland. Instead, `vpn-connect.sh` starts `openvpn` directly with a Unix +management socket and feeds credentials programmatically via `socat`. + +## Prerequisites + +### Packages +```bash +sudo pacman -S openvpn oathtool socat libsecret +``` + +### Sudo rule (no password prompt for openvpn) +```bash +sudo bash -c 'echo "YOUR_USER ALL=(ALL) NOPASSWD: /usr/bin/openvpn" > /etc/sudoers.d/vpn-openvpn && chmod 440 /etc/sudoers.d/vpn-openvpn' +``` + +### Store credentials in keyring (once) +```bash +# VPN password +secret-tool store --label="Sophos VPN password" service sslvpn user YOUR_USER + +# TOTP secret (base32 seed from your authenticator app) +secret-tool store --label="Sophos VPN TOTP" service sslvpn-totp user YOUR_USER +``` + +### Required files +- OpenVPN config: `~/Downloads/sslvpn-fixed.ovpn` (exported from Sophos user portal) +- Client certificate + key in NetworkManager certificate store: + `/home/chk/.local/share/networkmanagement/certificates/nm-openvpn/` + - `sslvpn-fixed-cert.pem` + - `sslvpn-fixed-key.pem` + +> The `.ovpn` file has empty `` and `` blocks — NM stores them separately. +> The scripts reference the NM certificate path directly. + +## Usage + +```bash +# Connect +~/bin/vpn-connect.sh + +# Disconnect +~/bin/vpn-disconnect.sh + +# Check log +sudo cat /tmp/vpn-sophos.log +``` + +## Configuration + +Edit the variables at the top of `vpn-connect.sh`: + +| Variable | Description | +|----------|-------------| +| `VPN_USER` | VPN username | +| `OVPN` | Path to .ovpn config file | +| `DNS_SERVER` | VPN DNS server IP | +| `DNS_SEARCH` | Space-separated search domains | +| `CERT_DIR` | Directory containing cert/key PEM files | + +## Pitfalls & lessons learned + +### `#` in password breaks openvpn management interface +The openvpn management protocol interprets `#` as a comment character. +Passwords containing `#` must be wrapped in double quotes: +``` +password "Auth" "mypassword#123456" +``` +Without quotes, everything after `#` is silently ignored → `AUTH_FAILED`. + +### ydotool / wtype don't work on KDE Wayland +- `ydotool` sends US keycodes — `y`↔`z` swap, `#` becomes `$` on DE layout +- `wtype` requires `zwp_virtual_keyboard` protocol — not supported by KDE Plasma +- `xdotool` works via XWayland but the KDE auth dialog runs natively on Wayland + +### NM passwd-file is ignored with challenge-response-flags=2 +Sophos VPN profiles exported from the user portal set `challenge-response-flags=2` +in the NetworkManager connection. With this flag, NM ignores `--passwd-file` and +waits for its interactive secret agent (KDE dialog). Removing the flag causes +connection timeouts. The only reliable automation path is bypassing NM entirely. + +### OTP timing +The script waits for a fresh 30s TOTP window (>20s remaining) before generating +the OTP to avoid expiry during the TLS handshake. + +### DNS requires routing domains (`~` prefix) +`resolvectl domain tun0 krah-gruppe.de` sets a search domain but does NOT route +DNS queries for that domain to tun0. The `~` prefix is required: +``` +resolvectl domain tun0 ~krah-gruppe.de ~internal.lan ... +``` + +### VPN network icon does not show connected state +Since openvpn is started directly (not via NM), the NetworkManager applet in the +system tray does not reflect the VPN state. Functionally everything works. +To check: `ip link show tun0` or `sudo cat /tmp/vpn-sophos.log`. + +### Account lockout +Sophos locks the account after several failed AUTH attempts. Wait ~5 minutes +before retrying after multiple failures. diff --git a/vpn-connect.sh b/vpn-connect.sh new file mode 100755 index 0000000..56bedb1 --- /dev/null +++ b/vpn-connect.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +# Connect to Sophos SSL VPN via openvpn management interface (same as NM) +set -euo pipefail + +VPN_USER="d-chrka" +OVPN="/home/d-chrka@internal.lan/Downloads/sslvpn-fixed.ovpn" +LOGFILE="/tmp/vpn-sophos.log" +MGMT_SOCK="/tmp/vpn-mgmt-$$.sock" +DNS_SERVER="172.21.20.201" +DNS_SEARCH="krah-gruppe.de internal.lan krah.intranet.de hirsau.seuffer resistec.pri krahicenet.local" +CERT_DIR="/home/chk/.local/share/networkmanagement/certificates/nm-openvpn" + +PW=$(secret-tool lookup service sslvpn user "$VPN_USER") +OTP_SECRET=$(secret-tool lookup service sslvpn-totp user "$VPN_USER") + +# Wait for fresh OTP window (>20s remaining) +while true; do + REMAINING=$(( 30 - ($(date +%s) % 30) )) + [ "$REMAINING" -gt 20 ] && break + echo "Waiting for fresh OTP window (${REMAINING}s remaining)..." + sleep $(( REMAINING + 1 )) +done + +OTP=$(oathtool --totp -b "$OTP_SECRET") +echo "OTP generated, $(( 30 - ($(date +%s) % 30) ))s valid" + +# Up-script: configure DNS once tun is up +UPSCRIPT=$(mktemp /dev/shm/vpn-up-XXXXXX) +cat > "$UPSCRIPT" << EOF +#!/bin/bash +DEV="\$1" +resolvectl dns "\$DEV" $DNS_SERVER +resolvectl domain "\$DEV" ~krah-gruppe.de ~internal.lan ~krah.intranet.de ~hirsau.seuffer ~resistec.pri ~krahicenet.local +resolvectl default-route "\$DEV" no +echo "DNS configured on \$DEV" >> "$LOGFILE" +EOF +chmod +x "$UPSCRIPT" + +echo "Connecting..." +sudo openvpn \ + --config "$OVPN" \ + --remote rcdro1.krah-gruppe.de 8443 udp \ + --cert "$CERT_DIR/sslvpn-fixed-cert.pem" \ + --key "$CERT_DIR/sslvpn-fixed-key.pem" \ + --auth-nocache \ + --management "$MGMT_SOCK" unix \ + --management-query-passwords \ + --auth-retry interact \ + --script-security 2 \ + --up "$UPSCRIPT" \ + --daemon vpn-sophos \ + --log "$LOGFILE" + +# Feed credentials via management interface +sleep 1 +( + printf 'username "Auth" %s\n' "$VPN_USER" + sleep 0.2 + printf 'password "Auth" "%s%s"\n' "$PW" "$OTP" + sleep 2 +) | socat - UNIX-CONNECT:"$MGMT_SOCK" 2>/dev/null || true + +# Wait for tun interface +echo -n "Waiting for tunnel" +for i in $(seq 1 30); do + if ip link show tun0 &>/dev/null; then + echo " connected." + echo "Disconnect: ~/bin/vpn-disconnect.sh" + rm -f "$UPSCRIPT" + exit 0 + fi + echo -n "." + sleep 1 +done + +rm -f "$UPSCRIPT" "$MGMT_SOCK" +echo " failed. Log: sudo cat $LOGFILE" +exit 1 diff --git a/vpn-disconnect.sh b/vpn-disconnect.sh new file mode 100755 index 0000000..c28bff3 --- /dev/null +++ b/vpn-disconnect.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +# Disconnect Sophos SSL VPN +sudo pkill -f "vpn-sophos" 2>/dev/null && echo "VPN disconnected." || echo "VPN was not running."