https://github.com/ma-wenqian/vpn-playbook
A complete guide and toolkit for automating login to Cisco AnyConnect / Cisco Secure Client VPNs that use TOTP-based two-factor authentication, plus a Dockerized OpenConnect setup for split-tunnel / proxy-chain use.
Tags: HKU, Cisco AnyConnect, Cisco Secure Client, OpenConnect, 2FA, TOTP, VPN Guide
Overview
Most institutional VPNs built on Cisco AnyConnect / Cisco Secure Client (this repo was written against HKU’s vpn2fa.hku.hk, but the approach generalizes to any similarly-configured gateway) require two factors to log in:
- A username + password.
- A 6-digit TOTP code, usually the same code you’d read off Google Authenticator, Authy, iCloud Keychain, or Microsoft Authenticator.
That TOTP code is just HMAC-SHA1(secret, current_time_step) truncated to 6 digits — there is nothing magic about the authenticator app. Once you have extracted the Base32 secret used to seed your authenticator (usually shown as text or a QR code during 2FA enrollment), you can compute the same code yourself and drive the whole login non-interactively:
- On Unix (macOS/Linux):
oathtool --totp -b "$SECRET" - On Windows: the
totp-goCLI,totp-go "$SECRET"
⚠️ Protect this secret like a password. Anyone who has it can generate valid 2FA codes for your account indefinitely — it does not rotate or expire the way a single OTP code does. Never commit a config file that contains your real secret, password, or username to a public (or even private, shared) repository. The
config.bat/config.shfiles in this repo are templates with placeholder values only — fill in your real details locally and keep that change out of git.
Repository layout
.
├── Cisco/
│ ├── unix/
│ │ ├── cisco-vpn.sh # connect/disconnect via oathtool + `vpn -s` scripted stdin
│ │ ├── config.sh # your VPN host/username/password/TOTP secret (template)
│ │ └── install.sh # one-line installer (curl | bash)
│ └── win/
│ ├── cisco-vpn.bat # double-click entry point; calls totp-go and writes the scripted vpncli.exe input directly
│ ├── config.bat # your VPN host/username/password/TOTP secret (template, same variable names as unix)
│ ├── icon/ # connect.ico / disconnect.ico used by desktop shortcuts
│ └── install.ps1 # one-line installer, also installs totp-go and offers desktop shortcuts (irm | iex)
└── Openconnect/
├── openconnect-setup.sh # OpenConnect + microsocks Docker setup (lighter)
└── openconnect-gost-setup.sh # OpenConnect + gost Docker setup (more proxy features)
Mobile
- iOS: no full automation is possible; at most you can auto-fill the TOTP code via the Passwords/Authenticator autofill integration.
- Android: not investigated in this repo.
Cisco AnyConnect / Secure Client (desktop, CLI-only)
Don’t launch the GUI client — everything below drives the command-line interface so the login can be scripted.
macOS / Linux
On a standard desktop install, the CLI lives at:
/opt/cisco/secureclient/bin/vpn
(macOS users can alternatively install the iOS-style AnyConnect client, which also supports split tunneling — see the Chinese section below for the caveat.)
Prerequisites
# Debian/Ubuntu
sudo apt install oathtool
# macOS
brew install oath-toolkit
You also need the Cisco AnyConnect/Secure Client already installed (so /opt/cisco/secureclient/bin/vpn exists).
Quick install
curl -fsSL https://sh.mawenqian.com/cisco-unix-install.sh | bash
This drops cisco-vpn.sh and a config.sh template into /opt/cisco-vpn (override with INSTALL_DIR=...; needs sudo to create the directory, matching the OpenConnect installers below), makes cisco-vpn.sh executable, and symlinks it to ~/.local/bin/ciscovpn. If ~/.local/bin isn’t already on your PATH, it appends an export PATH=... line to your .bashrc/.zshrc/.profile (whichever matches $SHELL) so ciscovpn actually works after you restart your shell, instead of silently symlinking somewhere unreachable — it checks for that exact line first, so re-running the installer never duplicates it. Re-running it later refreshes cisco-vpn.sh without touching your config.sh, so it doubles as an update mechanism.
Or install manually:
- Copy
Cisco/unix/cisco-vpn.shandCisco/unix/config.shwherever you like. - Edit
config.shwith your realVPN_HOST,VPN_USER,VPN_PASSWORDandVPN_SECRET. - Make the script executable:
chmod +x cisco-vpn.sh
Usage
ciscovpn connect # disconnect any existing session, then log in
ciscovpn disconnect # disconnect
# or, if you installed manually / skipped the symlink:
./cisco-vpn.sh connect
./cisco-vpn.sh disconnect
Under the hood, cisco-vpn.sh generates a fresh TOTP code with oathtool and pipes a scripted command sequence into the CLI’s -s (stdin-scripted) mode — the same mechanism the Windows side uses via vpncli.exe -s, since the CLI doesn’t accept connect <host> as a plain command-line argument on either platform; it only reacts to that sequence typed at its own interactive VPN> prompt:
printf 'connect %s\n%s\n%s\n%s\n' "$VPN_HOST" "$VPN_USER" "$VPN_PASSWORD" "$OTP" \
| /opt/cisco/secureclient/bin/vpn -s
Nothing touches disk — the credentials only exist in the pipe between this script and the CLI process for the moment it takes to read them.
Windows
The current install path for Cisco Secure Client is:
C:\Program Files (x86)\Cisco\Cisco Secure Client\vpncli.exe
There’s no direct Windows equivalent of oathtool, so this repo generates TOTP codes with totp-go instead — a small standalone CLI, no Python required.
Prerequisites: totp-go on PATH (the installer below fetches it for you if it’s missing; manually it’s irm https://sh.mawenqian.com/totp-go-install.ps1 | iex).
Quick install
irm https://sh.mawenqian.com/cisco-win-install.ps1 | iex
This drops cisco-vpn.bat and a config.bat template into %USERPROFILE%\.local\opt\cisco-vpn (override with -InstallDir ...), installs totp-go if it isn’t already on PATH, and asks whether to create “Connect VPN” / “Disconnect VPN” shortcuts on your Desktop — say yes and you never have to touch a terminal again. Re-running the installer later refreshes cisco-vpn.bat without touching your config.bat.
Shortcut icons: Cisco/win/icon/connect.ico and Cisco/win/icon/disconnect.ico are used by default (fetched from the repo, one per shortcut). To use your own instead, pass -ConnectIconUrl <url> / -DisconnectIconUrl <url> pointing at a .ico file — a shortcut’s icon has to be a local .ico/.exe/.dll file (Windows has no notion of a remote icon URL), so a supplied URL is downloaded once into the install folder and pointed at locally.
Or install manually:
- Copy
Cisco/win/cisco-vpn.batandCisco/win/config.batinto the same folder. - Install
totp-go:irm https://sh.mawenqian.com/totp-go-install.ps1 | iex - Edit
config.batwith your realVPN_HOST,VPN_USER,VPN_PASSWORDandVPN_SECRET— the same variable names used by the unix side’sconfig.sh. - Optional: put a shortcut to
cisco-vpn.baton your desktop so you can just double-click to connect.
Usage
Double-click cisco-vpn.bat (or run it from a terminal). It will:
- Print the current VPN state.
- Force-disconnect any existing session.
- Load
config.bat, generate a fresh TOTP code withtotp-go "%VPN_SECRET%", and write a scripted input file straight to%TEMP%\cisco_input.txtfrom within the batch script (no helper process involved). - Pipe that file into
vpncli.exe -s(scripted mode) to complete the login, then delete the temp file.
One-click disconnect: the Quick Install shortcut above already creates this, or make one yourself with this target:
C:\Windows\System32\cmd.exe /c "C:\Program Files (x86)\Cisco\Cisco Secure Client\vpncli.exe" disconnect & timeout /nobreak /t 3
To change either shortcut’s icon later: right-click it → Properties → Change Icon.
OpenConnect (Docker, SOCKS5 proxy)
The stock Cisco client doesn’t support split tunneling and doesn’t play well alongside other VPNs/proxies. OpenConnect is protocol-compatible with AnyConnect and works better for this. See Mark4551124015/HKU-VPN for a bare-metal reference setup.
This repo instead provides a Dockerized OpenConnect container that exposes a local SOCKS5 proxy on port 1080 — point any app (or chain another VPN/proxy through it) at socks5://127.0.0.1:1080, e.g. to reach services like ChatGPT/Claude from a network where OpenConnect is the only way out.
Two variants are provided — pick one:
| Script | Proxy backend | Notes |
|---|---|---|
openconnect-setup.sh | microsocks | Lighter weight, SOCKS5 only |
openconnect-gost-setup.sh | gost | Heavier, supports more protocols/chaining features |
Container images referenced by these scripts are built from ma-wenqian/dockerfiles/openconnect.
Quick install (Linux/macOS):
# lighter option: OpenConnect + microsocks
curl -fsSL https://sh.mawenqian.com/openconnect-setup.sh | bash
# OpenConnect + gost
curl -fsSL https://sh.mawenqian.com/openconnect-gost-setup.sh | bash
Each script:
- Creates an install directory (
/opt/openconnect-heador/opt/openconnect-gost, overridable viaINSTALL_DIR). - Drops a
.envtemplate (host/user/password/TOTP secret + optional local network routing) withchmod 600. - Writes a
Dockerfileanddocker-compose.yamlthat build the proxy + OpenConnect image.
Then:
cd /opt/openconnect-head # or /opt/openconnect-gost
$EDITOR .env # fill in your real VPN details
docker compose up -d --build
The container reconnects automatically if the OpenConnect session drops (--force-dpd=30 + a restart loop), and exposes a health check that pings 1.1.1.1 through the tunnel.
If you want other devices on your LAN to reach the proxy (not just the Docker host), set LOCAL_NETWORK / LOCAL_GATEWAY in .env. If it’s only used from the host machine, you can leave those at their defaults.