VPN Playbook

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

🇬🇧 English  |  🇨🇳 中文


English

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:

  1. A username + password.
  2. 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-go CLI, 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.sh files in this repo are templates with placeholder values only — fill in your real details locally and keep that change out of git.

Repository layout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.
├── 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:

1
/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

1
2
3
4
5
# 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

1
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:

  1. Copy Cisco/unix/cisco-vpn.sh and Cisco/unix/config.sh wherever you like.
  2. Edit config.sh with your real VPN_HOST, VPN_USER, VPN_PASSWORD and VPN_SECRET.
  3. Make the script executable: chmod +x cisco-vpn.sh

Usage

1
2
3
4
5
6
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:

1
2
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:

1
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

1
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:

  1. Copy Cisco/win/cisco-vpn.bat and Cisco/win/config.bat into the same folder.
  2. Install totp-go: irm https://sh.mawenqian.com/totp-go-install.ps1 | iex
  3. Edit config.bat with your real VPN_HOST, VPN_USER, VPN_PASSWORD and VPN_SECRET — the same variable names used by the unix side’s config.sh.
  4. Optional: put a shortcut to cisco-vpn.bat on your desktop so you can just double-click to connect.

Usage

Double-click cisco-vpn.bat (or run it from a terminal). It will:

  1. Print the current VPN state.
  2. Force-disconnect any existing session.
  3. Load config.bat, generate a fresh TOTP code with totp-go "%VPN_SECRET%", and write a scripted input file straight to %TEMP%\cisco_input.txt from within the batch script (no helper process involved).
  4. 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:

1
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):

1
2
3
4
5
# 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:

  1. Creates an install directory (/opt/openconnect-head or /opt/openconnect-gost, overridable via INSTALL_DIR).
  2. Drops a .env template (host/user/password/TOTP secret + optional local network routing) with chmod 600.
  3. Writes a Dockerfile and docker-compose.yaml that build the proxy + OpenConnect image.

Then:

1
2
3
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.


中文

https://github.com/ma-wenqian/vpn-playbook

概述

一般情况下,能用 Cisco AnyConnect(现称 Cisco Secure Client)的场景,基本都可以用 OpenConnect 替代。从便捷到进阶,本仓库提供了对应的脚本,辅助自动登录学校(或类似配置的)VPN。

目前 Cisco 基本都启用了双重认证:先输入 VPN 地址,然后输入账号、密码,接着输入二次验证码——也就是常见存储在 Google Authenticator、iCloud 钥匙串、Microsoft Authenticator 中的那个 6 位验证码。这个验证码本质上是基于一个固定的 Base32 密钥和当前时间戳计算得到的(TOTP),并没有什么”黑盒”逻辑。因此,只要在绑定 2FA 时提取到这个固定密钥,就可以自己算出验证码,从而实现全程自动登录:

  • Unix 平台:使用 oathtool 工具,OTP=$(oathtool --totp -b "$SECRET")
  • Windows 平台:使用 totp-go 命令行工具,totp-go "$SECRET"

⚠️ 请像保管密码一样保管这个密钥! 任何拿到这个密钥的人都可以无限期地为你的账号生成有效的二次验证码——它不像单次验证码那样会失效。切勿把填入了真实用户名/密码/密钥的配置文件提交到(哪怕是私有的)代码仓库。本仓库中的 config.bat / config.sh 都只是占位模板,请在本地填入真实信息后不要把这部分改动提交到 git。

仓库结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.
├── Cisco/
│ ├── unix/
│ │ ├── cisco-vpn.sh # 基于 oathtool + `vpn -s`(脚本化 stdin)实现 connect/disconnect
│ │ ├── config.sh # 你的 VPN 地址/账号/密码/TOTP 密钥(模板)
│ │ └── install.sh # 一键安装脚本(curl | bash)
│ └── win/
│ ├── cisco-vpn.bat # 双击运行的入口脚本,用 totp-go 生成验证码并直接写入 vpncli.exe 的脚本化输入
│ ├── config.bat # 你的 VPN 地址/账号/密码/TOTP 密钥(模板,变量名与 unix 端一致)
│ ├── icon/ # 桌面快捷方式用到的 connect.ico / disconnect.ico
│ └── install.ps1 # 一键安装脚本,顺带安装 totp-go,还能自动创建桌面快捷方式(irm | iex)
└── Openconnect/
├── openconnect-setup.sh # OpenConnect + microsocks 的 Docker 部署(更轻量)
└── openconnect-gost-setup.sh # OpenConnect + gost 的 Docker 部署(功能更全)

移动端

  • iOS:无法做到完全自动化,顶多是借助自动填充功能填入验证码。
  • Android:本仓库暂未研究。

Cisco(桌面端,仅命令行)

不要启动 GUI 界面,全部使用命令行实现自动化操作。

Unix(macOS / Linux)

常规桌面版安装后,CLI 路径是:

1
/opt/cisco/secureclient/bin/vpn

(macOS 也可以选择安装 iOS 端的 AnyConnect App,这样还能做到 split tunnel,但功能和稳定性因版本而异。)

前置依赖

1
2
3
4
5
# Debian/Ubuntu
sudo apt install oathtool

# macOS
brew install oath-toolkit

同时需要已经安装好 Cisco AnyConnect/Secure Client(确保 /opt/cisco/secureclient/bin/vpn 存在)。

一键安装

1
curl -fsSL https://sh.mawenqian.com/cisco-unix-install.sh | bash

会把 cisco-vpn.sh 和一份 config.sh 模板放到 /opt/cisco-vpn(可用 INSTALL_DIR=... 覆盖;创建该目录需要 sudo,和下面 OpenConnect 的安装脚本一致),赋予 cisco-vpn.sh 执行权限,并软链接到 ~/.local/bin/ciscovpn。如果 ~/.local/bin 还不在你的 PATH 里,脚本会自动往 .bashrc/.zshrc/.profile(根据 $SHELL 判断用哪个)追加一行 export PATH=...,这样重启终端后 ciscovpn 才真的能直接调用,而不是软链接到一个够不着的地方——追加前会先检查这行是否已存在,所以重复运行安装脚本不会重复添加。以后重新运行这条命令即可更新 cisco-vpn.sh,不会覆盖你的 config.sh

也可以手动安装:

  1. Cisco/unix/cisco-vpn.shCisco/unix/config.sh 拷贝到任意目录。
  2. 编辑 config.sh,填入真实的 VPN_HOSTVPN_USERVPN_PASSWORDVPN_SECRET
  3. 赋予执行权限:chmod +x cisco-vpn.sh

使用

1
2
3
4
5
6
ciscovpn connect      # 先断开已有连接,再登录
ciscovpn disconnect # 断开连接

# 若手动安装且没有创建软链接:
./cisco-vpn.sh connect
./cisco-vpn.sh disconnect

脚本内部会用 oathtool 现算一个 TOTP 验证码,然后把一段脚本化的命令序列,通过管道传给 CLI 的 -s(stdin 脚本化)模式——这和 Windows 那边用 vpncli.exe -s 是同一套机制,因为不管在哪个平台,这个 CLI 都不接受把 connect <host> 直接当命令行参数传入,它只认在自己那个交互式的 VPN> 提示符下输入的命令序列:

1
2
printf 'connect %s\n%s\n%s\n%s\n' "$VPN_HOST" "$VPN_USER" "$VPN_PASSWORD" "$OTP" \
| /opt/cisco/secureclient/bin/vpn -s

全程不落盘——账号密码只存在于这个脚本和 CLI 进程之间的管道里,读取完就没了。

Windows

最新的安装目录是:

1
C:\Program Files (x86)\Cisco\Cisco Secure Client\vpncli.exe

因为 Windows 上没有现成的 oathtool 等价物,所以这里改用 totp-go 生成验证码——一个独立的小型命令行工具,不需要 Python。

前置依赖:PATH 中要有 totp-go(下面的一键安装脚本会在检测不到时自动帮你安装;手动安装的话执行 irm https://sh.mawenqian.com/totp-go-install.ps1 | iex)。

一键安装

1
irm https://sh.mawenqian.com/cisco-win-install.ps1 | iex

会把 cisco-vpn.bat 和一份 config.bat 模板放到 %USERPROFILE%\.local\opt\cisco-vpn(可用 -InstallDir ... 覆盖),在 totp-go 不在 PATH 中时自动安装它,然后询问是否要在桌面创建 “Connect VPN” / “Disconnect VPN” 快捷方式——选择创建之后就再也不用碰终端了。以后重新运行安装脚本只会刷新 cisco-vpn.bat,不会动你的 config.bat

快捷方式图标:默认使用仓库自带的 Cisco/win/icon/connect.icoCisco/win/icon/disconnect.ico(两个快捷方式各用各的)。想用自己的图标,传入 -ConnectIconUrl <url> / -DisconnectIconUrl <url> 指向一个 .ico 文件即可——快捷方式的图标必须是本地的 .ico/.exe/.dll 文件(Windows 本身不支持直接用远程 URL 做图标),所以提供的 URL 会先下载到安装目录,再指向本地文件。

也可以手动安装:

  1. Cisco/win/cisco-vpn.batCisco/win/config.bat 放到同一个目录。
  2. 安装 totp-goirm https://sh.mawenqian.com/totp-go-install.ps1 | iex
  3. 编辑 config.bat,填入真实的 VPN_HOSTVPN_USERVPN_PASSWORDVPN_SECRET——变量名和 unix 端的 config.sh 完全一致。
  4. 可选:把 cisco-vpn.bat 的快捷方式放到桌面,以后双击即可自动连接。

使用

双击运行 cisco-vpn.bat(或在终端里直接执行),它会依次:

  1. 打印当前 VPN 状态;
  2. 强制断开已有连接;
  3. 加载 config.bat,用 totp-go "%VPN_SECRET%" 现算一个 TOTP 验证码,直接在批处理脚本内把脚本化的登录输入写入 %TEMP%\cisco_input.txt(不再经过任何辅助进程);
  4. 把这个文件通过管道传给 vpncli.exe -s(脚本模式)完成登录,然后删除临时文件。

一键断开:上面的一键安装已经自动创建了这个快捷方式;也可以自己创建一个,目标设置为:

1
C:\Windows\System32\cmd.exe /c "C:\Program Files (x86)\Cisco\Cisco Secure Client\vpncli.exe" disconnect & timeout /nobreak /t 3

以后想换图标:右键快捷方式 → 属性 → 更改图标。

OpenConnect(Docker 化,SOCKS5 代理)

因为默认的 Cisco 客户端不支持 split tunnel 模式,且与其他 VPN/代理协作不友好,所以可以用兼容 AnyConnect 协议的 OpenConnect 替代。直接裸机运行的方案可以参考 Mark4551124015/HKU-VPN

这里提供的是一个 Docker 化的部署方式,并映射出一个 1080 端口的 SOCKS5 代理,可以直接使用,或作为其他 VPN 做代理链的一环,比如访问 ChatGPT / Claude 等服务。

提供了两种方案,二选一即可:

脚本 代理后端 说明
openconnect-setup.sh microsocks 更轻量,只做 SOCKS5
openconnect-gost-setup.sh gost 更重一些,支持更多协议/代理链能力

脚本引用的镜像构建自 ma-wenqian/dockerfiles/openconnect

一键安装(Linux/macOS):

1
2
3
4
5
# 更轻量: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

每个脚本都会:

  1. 创建安装目录(默认 /opt/openconnect-head/opt/openconnect-gost,可通过 INSTALL_DIR 环境变量覆盖);
  2. 生成一个 .env 模板(VPN 地址/账号/密码/TOTP 密钥 + 可选的本地网段路由),并设置权限为 chmod 600
  3. 写入对应的 Dockerfiledocker-compose.yaml,用于构建代理 + OpenConnect 镜像。

然后:

1
2
3
cd /opt/openconnect-head   # 或 /opt/openconnect-gost
$EDITOR .env # 填入真实的 VPN 信息
docker compose up -d --build

容器内置了断线自动重连逻辑(--force-dpd=30 + 重启循环),并配置了健康检查(通过隧道 ping 1.1.1.1)。

如果希望局域网内的其他设备也能访问这个代理(而不仅仅是运行 Docker 的这台机器),需要在 .env 中设置 LOCAL_NETWORK / LOCAL_GATEWAY;如果只在本机使用,保持默认即可忽略这两项。