not-narleeek/pi-vpn
GitHub: not-narleeek/pi-vpn
一款为 CTF 和渗透测试场景设计的 OpenVPN 驱动工具,提供 pi 智能体扩展和独立 TUI 两种模式管理持久化的 VPN 隧道。
Stars: 0 | Forks: 0
# pi-vpn
`pi-vpn` 提供**基于相同逻辑的两个接口**:
- 一个 **pi 扩展** (`extensions/vpn.ts`),将 OpenVPN 暴露为四个 agent 工具 + 一个 `/vpn` 命令 + 一个实时页脚,以及
- 一个**独立终端 UI** (`pi-vpn`),你可以在任何 shell 或 tmux 面板中运行 —— 无需 agent。
两者都为你处理权限、凭证和日志解析,并以**分离的 daemon** 形式运行 tunnel,因此长时间的渗透测试会话不会在进程重启时中断。零运行时依赖。
## 目录
- [功能介绍](#what-it-does)
- [环境要求](#requirements)
- [安装说明](#install)
- [独立 TUI](#standalone-tui)
- [快速开始](#quick-start)
- [架构设计](#architecture)
- [工作原理](#how-it-works)
- [配置说明](#configuration)
- [安全提示](#security-notes)
- [故障排除](#troubleshooting)
- [开源许可](#license)
## 功能介绍
将 agent 指向一个 `.ovpn` 文件(或让它自行查找),它可以:
- 通过一次调用**连接**到 OpenVPN 服务器,仅在配置需要时提示输入凭证,
- 显示一个**实时状态页脚** —— 名称 · 状态 · VPN IP · 经过时间 —— 每秒更新,
- **监控连接**状态以确认成功或显示可读的失败信息(证书无效、认证失败、tun-device 冲突、超时),
- **列出**工作区和常见目录中的 `.ovpn`/`.opvn` 文件,
- **断开连接**,以及
- **重新附加**到上一次会话中仍然保持连接的 tunnel。
该包为 agent 提供了以下工具:
| 工具 | agent 的用途 |
|------|----------------------------|
| `vpn_connect` | 从 `.ovpn` 文件连接(或切换)到 OpenVPN tunnel。仅在用户提供时传入用户名/密码;否则让扩展进行提示。 |
| `vpn_disconnect` | 断开当前的 tunnel。 |
| `vpn_status` | 报告当前状态:名称、状态、VPN IP、设备、PID、经过时间。 |
| `vpn_list` | 发现工作区和常见目录中的 `.ovpn`/`.opvn` 文件。 |
并为你提供了一个 `/vpn` 命令:
```
/vpn interactive status panel (TUI)
/vpn connect connect (prompts for creds if the profile needs them)
/vpn shorthand for connect
/vpn disconnect tear the current tunnel down
/vpn status one-line status notification
/vpn list pick an .ovpn and connect
```
一个实时页脚 (🔒 `htb · ● · 10.10.0.5 · 12:03`) 可以让你一眼看出准备就绪状态。
## 环境要求
| 组件 | 版本 | 说明 |
|-----------|---------|-------|
| **pi** | 任意较新版本 | 加载此包的 agent —— `npm i -g @earendil-works/pi-coding-agent` |
| **OpenVPN** | 2.5+ | `PATH` 中的 `openvpn` 二进制文件。在 Kali/Debian 上:`apt install openvpn`。 |
| **`sudo` / root** | — | `openvpn` 需要 root 权限来创建 tun 设备。参见[权限模型](#how-it-works)。 |
| **`ip` (iproute2)** | — | 用于只读的 tunnel IP 检测;几乎所有 Linux 上均存在。 |
| **Node** | 18+ | 用于 TypeScript 扩展 |
## 安装说明
从三种来源中选择**一种**。`pi install` 默认写入用户设置
(`~/.pi/agent/settings.json`);添加 `-l` 可写入项目本地设置。
### 1. 从 git 安装(推荐 —— 始终最新)
```
pi install git:github.com/not-narleeek/pi-vpn
```
如果需要可重现性,可以固定 tag/commit:
```
pi install git:github.com/not-narleeek/pi-vpn@v0.1.0
```
### 2. 从 npm 安装(一旦发布)
```
pi install npm:pi-vpn
```
### 3. 从本地克隆安装(用于开发)
```
git clone https://github.com/not-narleeek/pi-vpn
cd pi-vpn
npm install # installs TypeScript peer deps for local type-checking
pi install . # or: pi install ./pi-vpn (absolute or relative path)
```
验证是否已加载:
```
pi list # pi-vpn should appear under packages
/vpn status # inside a pi session
```
无需将其提交到设置即可尝试:
```
pi -e git:github.com/not-narleeek/pi-vpn # ephemeral, current run only
```
## 独立 TUI
不想牵扯到 agent?直接运行 `pi-vpn` —— 这是一个键盘驱动、对 tmux 友好的 VPN 管理器,驻留在一个面板中:
```
┌ pi-vpn ─ 14:32:07 ──────────────────────────────────────────────────┐
│🔒 htb ● connected 10.10.14.23 tun0 12:03 pid 4821 │
├──────────────────────────────┬────────────────────────────────────┤
│ Configs │ Log │
│ ▸ ● htb.ovpn │ … Initialization Sequence Compl… │
│ ○ thm.ovpn │ │
├──────────────────────────────┴────────────────────────────────────┤
│ ↑↓/jk select ⏎ connect d disconnect r refresh q quit │
└───────────────────────────────────────────────────────────────────┘
```
```
git clone https://github.com/not-narleeek/pi-vpn && cd pi-vpn
npm install # builds dist/pi-vpn.cjs via the `prepare` script
npm link # optional: puts `pi-vpn` on PATH
pi-vpn # launch the TUI
```
它是一个零依赖的独立打包文件,响应式渲染(在 ≥100 列时并排显示,否则在下方堆叠),在面板大小改变时立即重新渲染,使用 alternate screen,并在退出时进行清理。还有一个用于脚本编写的 CLI:
`pi-vpn connect `, `disconnect`, `status`, `list`。
➡️ **完整的快捷键、标志和 tmux 技巧:[docs/TUI.md](docs/TUI.md)。**
## 快速开始
在 pi 会话中,只要有 `.ovpn` 文件的地方:
```
> connect to the htb vpn using ~/Downloads/lab_norlek.ovpn
# Agent 调用 vpn_connect;如果配置文件需要用户名/密码,且
# 您没有提供它们,扩展会提示您。页脚亮起:
# 🔬 htb · ● · 10.10.14.23 · 0:04
```
不记得文件名?让 agent 帮你找:
```
> list my vpn configs and connect to the tryhackme one
# Agent 运行 vpn_list(扫描 cwd, ~/ctf, ~/Downloads, ~/hackerone, ~/security, ~),
# 然后根据您的选择运行 vpn_connect。
```
即使 pi 退出,tunnel 也会继续运行:
```
> disconnect from the vpn when you're done
# Agent 调用 vpn_disconnect。在此之前,后台守护进程化的 openvpn 会持续运行,
# 即使 pi 退出也是如此 —— 稍后重新打开 pi,它会自动重新附加。
```
## 架构设计
两层结构,没有辅助脚本。(完整深入剖析:
[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)。)
```
┌──────────────────────────────────────────────────────────────────────┐
│ pi (the agent) │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ extensions/vpn.ts ←── this package (TypeScript, stdlib only) │ │
│ │ • 4 tools + /vpn command + interactive status panel + footer │ │
│ │ • privilege mgmt · credential handling · log monitoring │ │
│ └───────────────────┬────────────────────────────────────────────┘ │
└───────────────────────┼────────────────────────────────────────────────┘
│ spawnSync("sudo", ["-n","openvpn", …]) (or openvpn if root)
┌───────────────────────▼────────────────────────────────────────────────┐
│ openvpn --daemon (detached — not a child of pi) │
│ writes pid + log · opens tun/tap · SURVIVES pi quitting │
└────────────────────────────────────────────────────────────────────────┘
```
**为什么没有辅助脚本?** 与 `pi-ghidra`/`pi-caido` 不同,没有任何东西需要
桥接 —— `openvpn` 是一个普通的 CLI。扩展仅负责编排:解析
配置、整理凭证、提升权限、启动 daemon,并
解析返回的日志。这使得整个包只是一个可审计的
TypeScript 文件。
### 文件布局
```
pi-vpn/
├── extensions/
│ └── vpn.ts # pi extension: tools, /vpn command, panel, footer
├── tui/
│ ├── vpn-core.ts # framework-agnostic OpenVPN core (manager, sudo, monitor)
│ └── vpn-tui.ts # standalone TUI renderer + CLI entry (→ dist/pi-vpn.cjs)
├── docs/
│ ├── ARCHITECTURE.md # lifecycle, privilege model, log parsing, reattach
│ └── TUI.md # standalone TUI: keys, flags, tmux tips
├── package.json # pi manifest (pi.extensions) + `bin` + npm metadata
├── tsconfig.json # extension type-checking
├── tsconfig.tui.json # TUI type-checking
├── README.md
├── CHANGELOG.md
└── LICENSE
```
## 工作原理
### 1. Daemon 模型
连接通过以下方式启动:
```
sudo -n openvpn --config --daemon ovpn-pi- \
--writepid /.pid --log /.log \
[--auth-user-pass ]
```
`--daemon` 将 openvpn 从 pi 的进程树中分离。**这是刻意为之且至关重要的**:
CTF / 实验室 VPN 必须比任何单次 agent 运行的时间更长。你
可以通过 `/vpn disconnect`(或通过杀死 pid)显式断开连接。
### 2. 权限模型
`openvpn` 需要 root 权限来操作 tun 设备。扩展绝不会要求超出必要的权限 —— 它会按顺序尝试以下方法,并在第一种有效的方法处停止:
| # | 方法 | 说明 |
|---|--------|-------|
| 1 | pi 已经以 root 身份运行 | 直接调用 `openvpn` |
| 2 | 免密 `sudo -n` | 在专用渗透测试机器上很常见 |
| 3 | `$PI_VPN_SUDO_PASS` | 用于自动化;通过 `sudo -S` 传入 |
| 4 | 会话内 UI 提示 | **仅在内存中**缓存,绝不写入磁盘 |
每次特权调用之前都会重新检查 `sudo -v` 以刷新时间戳。
### 3. 凭证处理
按优先级顺序:
- 显式 `authFile` → 按原样使用。
- 显式 `username`/`password` → 写入一个模式为 `0600` 的临时文件。
- 配置文件包含裸露的 `auth-user-pass`(通过扫描配置检测到)
且未传递凭证 → UI 交互式提示(在没有 UI 的
headless 模式下则会报错)。
临时认证文件会在 `finally` 块中删除,因此即使
连接失败或被取消,它们也会被移除。密码保存在进程作用域的变量中,**绝不**持久化到磁盘。
### 4. 日志监控
启动后,扩展每 500 毫秒轮询一次日志文件,持续最多 60 秒,
仅读取自上次轮询以来的新字节(处理轮转):
- **成功:** `Initialization Sequence Completed` → 同时捕获
`TUN/TAP device opened` 以获取接口名称。
- **失败**(第一个匹配项优先,映射为清晰的信息):
`AUTH_FAILED`, `private key password verification failed`,
`Options error:`, `Exiting due to fatal error`,
`Cannot ioctl TUNSETIFF`, `Inactivity timeout`。
- **进程死亡:** 如果 pid 在成功之前死亡 → 失败并显示匹配到的
原因或日志末尾。
### 5. 重启后重新附加
在 `session_start` 时,扩展会读取 `~/.pi/vpn/state.json`(仅在
连接时持久化)。如果记录的 pid 仍然存活(`kill -0`;对于 root 拥有的 pid,`EPERM`
也算作存活),它会恢复完整的 `connected` 状态 ——
包括 `startTime`,因此经过的时间计数器会从上次中断的地方继续——并
重启页脚计时器。如果 pid 已死,它会清除状态并进入空闲。
这就是让 tunnel 在 agent 重启时感觉持续存在的原因。
### 6. Tunnel-IP 检测
只读(无需 root):首选 `ip -j -4 addr show` (JSON),回退到
文本解析 `ip -4 addr show`,以找到第一个带有 IPv4 地址的 `tun|tap|ppp|ovpn*`
接口。
## 配置说明
全部可选。对于常见情况(交互式 UI + 免密
sudo,或以 root 身份运行 pi)无需任何配置。
| 环境变量 | 默认值 | 用途 |
|---------|---------|---------|
| `PI_VPN_SUDO_PASS` | *(未设置 → 提示)* | 用于非交互式 / 自动化的 sudo 密码。通过 `sudo -S` 传入;仅在内存中缓存。 |
生成的状态(绝不提交,在运行时创建于 `~/.pi/vpn/` 下):
```
state.json # persisted ONLY while connected (name, configPath, pid,
# startTime, dev) — mode 0600, deleted on disconnect
logs/.log # openvpn log for the current/last connection
pid/.pid # openvpn pid file
auth-.txt # TEMP auth-user-pass file (0600), deleted after connect
```
## 安全提示
- **设计上,tunnel 的寿命比 pi 更长。** 这对于长时间
会话来说是全部意义所在,但这意味着被遗忘的 tunnel 会一直保持连接,直到你执行 `/vpn
disconnect` 或杀死 pid。页脚和 `state.json` 会使其保持可见。
- **密码是短暂的。** 保存在进程作用域的变量中;唯一的磁盘
产物是一个 `0600` 认证文件,仅在 `connect` 调用期间存在
并在 `finally` 中删除。
- **`state.json` 不携带任何机密** —— 只有 `name`、`configPath`、`pid`、
`startTime`、`dev`。以 `0600` 模式写入,断开连接时删除。
- **以你的权限运行。** 像所有 pi 包一样,这会执行代码
(`openvpn`、`sudo`、`ip`、`kill`)。正好只有一个源文件需要审计:
`extensions/vpn.ts`。
- **OpenVPN 配置文件**由你负责 —— 在连接前审查 `.ovpn` 文件,尤其是
`redirect-gateway` 和任何内嵌的脚本。
## 故障排除
| 症状 | 解决方法 |
|---------|-----|
| `sudo password required` / `sudo authentication failed` | 以 root 身份运行 pi,为 `openvpn` 添加免密 sudoers 规则,或者 `export PI_VPN_SUDO_PASS=…`。在交互式会话中,你会直接收到提示。 |
| `authentication failed (check VPN credentials)` | 用户名/密码错误。配置文件具有裸露的 `auth-user-pass`;请使用正确的凭证重新连接。 |
| `cannot create tun device` | 另一个 tunnel 已经连接,或者没有权限。先执行 `/vpn disconnect`,或检查 sudo。 |
| `connection timed out` (60 秒) | 服务器不可达,或握手速度极慢。检查位于 `~/.pi/vpn/logs/.log` 的日志。 |
| 页脚显示已连接但没有 IP | Tunnel IP 检测使用 `ip`;确保已安装 iproute2。`dev`/`pid` 仍然会显示。 |
| pi 重启后 Tunnel 消失 | 在 pi 离开时 openvpn 进程死掉了(服务器将你踢下线、重启等)。请重新连接。 |
| `openvpn: command not found` | `apt install openvpn`(或你的发行版的等效命令)。 |
| 想要完全重置 | `/vpn disconnect`,然后 `rm -rf ~/.pi/vpn/` 以清除日志/pid/状态。 |
## 开源许可
[MIT](LICENSE) — 随意使用、修改和分发。感谢注明出处,但不作强制要求。
标签:MITM代理, OpenVPN, TUI, VPN, 网络调试, 自动化, 自动化攻击, 运维工具