giacomocamata/eduvpn-logger
GitHub: giacomocamata/eduvpn-logger
一个为 eduVPN v3(WireGuard)提供统一会话日志记录的 Python 守护进程,通过关联多源数据解决 VPN 会话追踪问题。
Stars: 0 | Forks: 0
# eduvpn-logger
*🇮🇹 [阅读意大利文原文](README.it.md)*
**为 [eduVPN v3](https://www.eduvpn.org/) (WireGuard) 提供统一且关联的会话日志记录。**
## 动机
在一个 eduVPN v3 部署环境中,描述单个 VPN 会话的事实分散在三个独立的日志来源中,且**没有任何单一来源足以**回答在运营和取证上至关重要的“*谁在什么时间、从哪里发起了连接?*”这一问题:
| 来源 | 提供信息 | 位置 |
|---|---|---|
| `vpn-user-portal` | 身份:用户、配置文件、WG 公钥、分配的 VPN IP、传输的字节数 | journald (`-t vpn-user-portal`) |
| **WireGuard** (`wg show`) | 网络端点:WG 公钥 ↔ **公网源 IP:port**;存活状态 | 内部轮询 |
| Apache **ProxyGuard** | 用于 TCP-443 回退会话的公网源 IP:port | 文件 (`proxyguard_start.log`) |
门户记录了进行身份验证的*用户*,但从不记录他们来源的公网地址;而 WireGuard 作为一种没有“连接”概念的无状态协议,虽然知道源端点,但在漫游时会静默重新绑定,且不记录任何日志。
因此,将两者关联起来是一个数据关联问题,而 **WireGuard 公钥**是这三个来源共享的唯一标识符。
`eduvpn-logger` 是一个单文件 Python 守护进程(仅使用标准库),可实时执行此关联操作,并为每个会话事件(`connect`、`roam`、`disconnect`)向日志文件输出**一行结构化的 `key=value` 数据**,同时并行发送至 syslog 以供 SIEM 提取:
```
2026-04-15T09:58:03+02:00 event=connect user=alice profile=staff device=ios conn=soAQTNO...= tunnel_ip4="10.20.0.5" tunnel_ip6="fd00:20::5" src_ip="203.0.113.45" src_port=48049 transport=tcp country="Italy" city="Trieste"
2026-04-21T10:57:22+02:00 event=roam user=alice profile=staff conn=GUUepz8z...= tunnel_ip4="10.20.0.5" src_ip_old="203.0.113.45" src_port_old=45851 src_ip="198.51.100.12" src_port=45851 transport=udp
2026-04-15T09:58:20+02:00 event=disconnect user=alice profile=staff conn=soAQTNO...= bytes_in=227252 bytes_out=49292 src_ip="203.0.113.45" transport=tcp
```
## 设计亮点
该守护进程提取自生产环境(的里雅斯特大学)部署并进行了通用化处理。其设计基于四个值得强调的关键决策:
- **以 WireGuard 公钥作为关联键。** 身份信息(来自门户)和网络端点(来自 WireGuard / ProxyGuard)通过它们共享的唯一稳定标识符进行拼接,因此这种关联在端点漫游和 TCP 回退路径中均能保持有效。
- **内部合成 WireGuard 事件 —— 无需外部日志记录器。** WireGuard 不暴露连接/断开概念,因此必须进行推断。广泛使用的 [`wglogger`](https://codeberg.org/flaruina/wglogger) 通过 conntrack netlink 事件推断它们,但为了将数据流映射回对等节点,最终还是需要查询该守护进程已经轮询的 `wg show` 数据。因此这种依赖是多余的:`eduvpn-logger` 直接从定期快照中重建事件,无需额外安装或维护任何组件。
- **优雅降级。** 每一项数据扩充都是可选且具备故障安全机制的。如果没有 GeoIP 数据库,`country`/`city` 字段会被直接省略;当会话没有门户的 CONNECT 事件时,用户和配置文件信息会通过公钥从门户的 SQLite 数据库(只读)中恢复。门户的 schema 会根据列名自动检测,因此该工具能跨 eduVPN 版本自适应,无需额外配置。
- **对 SIEM 安全的输出。** 源自用户和配置文件的字段在序列化前会进行净化处理,因此来自 IdP 或门户的恶意数值无法破坏日志行格式或伪造虚假的 key=value 键值对。仅反映 NAT 端口重绑定的漫游事件会被抑制,其余事件则按每个对等节点进行速率限制,以避免频繁切换的移动客户端导致 SIEM 被日志洪水淹没。
## WireGuard 事件如何被推导
由于 WireGuard 没有连接的概念,守护进程每隔 `EDUVPN_WG_POLL_SEC` 秒就会从 `wg show` 读取每个对等节点的端点和最后一次握手时间,并据此推导出:
- **connect** —— 某个对等节点在新端点上变为活跃状态(最近有过握手)。该事件会被短暂延迟(`EDUVPN_CONNECT_GRACE_SEC`,默认为 10 秒),并在门户事件或门户数据库将该对等节点归属到某个用户后立即发出,从而确保可追溯的会话永远不会以 `user=-` 的形式被记录。
- **roam** —— 一个活跃对等节点的端点发生改变(受上述限流机制控制)。
- **disconnect** —— 对于 eduVPN-app 会话,直接使用门户自身的 DISCONNECT 事件。对于**导入到通用 WireGuard 客户端**(即非 eduVPN 应用)的 **WireGuard profile**,门户不会发出任何信息,因此一旦握手静默时间达到 `EDUVPN_DISCONNECT_AFTER_SEC`(默认 180 秒 ≈ 3 分钟),系统就会合成断开事件。预计在此类客户端停止连接后约三分钟会出现 `disconnect` 日志行。
与基于 netlink 的日志记录器相比,其代价在于分辨率:检测发生在轮询粒度(默认为 2 秒)上,而非瞬间完成;且短于一个轮询间隔的会话可能会被遗漏。对于 eduVPN 的长生命周期会话而言,这无关紧要;如果需要更精细的粒度,可以调低 `EDUVPN_WG_POLL_SEC`。
重启时,守护进程会核对 `wg show` 报告的仍然活跃的对等节点,从而能够在崩溃后干净地恢复状态。
## 环境要求
- Linux 操作系统,需包含 `systemd`、`journalctl` 和 `wg` 工具(`wireguard-tools`)。
- 基于 WireGuard 的 eduVPN v3 部署环境(`vpn-user-portal`)。
- 包含 ProxyGuard(eduVPN 的 TCP-443 回退方案)的 Apache —— 详见下文。
- Python 3.9+(仅使用标准库)。GeoIP 扩充需要 `maxminddb`。
## 快速开始
```
git clone https://github.com/giacomocamata/eduvpn-logger.git
cd eduvpn-logger
chmod +x install.sh
sudo ./install.sh
```
`install.sh` 是幂等的:它会安装依赖项,将两个脚本复制到 `/usr/local/sbin`,安装并启用 systemd 单元,创建 `/var/log/eduvpn` 目录,并配置 rsyslog 片段。然后它会打印出两个无法被安全自动化的步骤 —— MaxMind GeoIP 许可证申请和 Apache VirtualHost 编辑(两者均见下文)。如需手动安装,请参阅 [手动安装](#manual-install)。
## Apache / ProxyGuard 日志记录
ProxyGuard 通过 TCP/443 隧道传输 WireGuard,因此内核会将这些数据包视为源自 `127.0.0.1`;客户端真实的公网 IP **仅**对 Apache 可见。有两个组件可以恢复该信息(完整片段见 [`examples/apache-proxyguard.conf`](examples/apache-proxyguard.conf)):
1. **START 事件** —— 仅针对 `/proxyguard/` 提升代理日志级别:
LogLevel warn proxy:trace1
随后,Apache 会在隧道建立时向 VirtualHost 的 **ErrorLog** 发送一条 `tunnel running` 追踪行(携带 `[client IP:port]`)。`proxyguard-watcher.py` 会监控该 ErrorLog,并将其重写为紧凑的 `event=start` 行存入 `proxyguard_start.log`,以供守护进程读取。
2. **END 事件** —— 在隧道关闭时记录字节数和持续时间的 `CustomLog`。
应用该代码片段后,通过编辑 `/etc/systemd/system/proxyguard-watcher.service` 中的 `ExecStart`,将监控器指向*你的* VirtualHost ErrorLog(默认为 `/var/log/apache2/error.log`),然后重新加载:
```
apache2ctl configtest && sudo systemctl reload apache2
sudo systemctl enable --now proxyguard-watcher.service
```
## GeoIP(可选)
```
sudo apt install -y python3-maxminddb geoipupdate # Debian/Ubuntu
# 将您的 MaxMind 账户 ID + 许可证密钥放入 /etc/GeoIP.conf,并包含
# EditionIDs GeoLite2-City
sudo geoipupdate -v
```
如果没有数据库,守护进程将照常运行,并仅省略 `country`/`city` 字段。
## 配置
所有配置均通过环境变量(均为可选)进行;可在 systemd 单元中设置覆盖参数。默认值与标准的 Debian eduVPN 安装环境相匹配。
| 变量 | 默认值 | 含义 |
|---|---|---|
| `EDUVPN_LOG` | `/var/log/eduvpn/eduvpn.log` | 统一的输出日志文件 |
| `EDUVPN_PORTAL_DB` | `/var/lib/vpn-user-portal/db.sqlite` | 门户数据库(只读回退) |
| `EDUVPN_PROXYGUARD_START_LOG` | `/var/log/apache2/proxyguard_start.log` | ProxyGuard START 事件 |
| `EDUVPN_GEOIP_DB` | *(自动检测)* | GeoLite2-City.mmdb 的显式路径 |
| `EDUVPN_GEOIP_LANG` | `en` | 首选名称语言,以逗号分隔(例如 `it,en`) |
| `EDUVPN_SYSLOG_IDENT` | `eduvpn-logger` | syslog 程序名称 |
| `EDUVPN_SYSLOG_FACILITY` | `local0` | syslog facility(`local0`..`local7`) |
| `EDUVPN_WG_POLL_SEC` | `2.0` | `wg show` 轮询间隔(秒) |
| `EDUVPN_DISCONNECT_AFTER_SEC` | `180.0` | 合成 disconnect 前的握手静默时间 |
| `EDUVPN_CONNECT_GRACE_SEC` | `10.0` | 发出连接事件前,将其归属至用户的最大等待时间 |
| `EDUVPN_ROAM_MIN_INTERVAL_SEC` | `30.0` | 每个对等节点两次漫游事件之间的最小间隔(限流) |
可选择使用 [`examples/rsyslog-10-eduvpn.conf`](examples/rsyslog-10-eduvpn.conf) 将守护进程的 syslog 路由到专属文件(该文件已由 `install.sh` 自动安装)。
## 输出字段
| 字段 | 事件 | 备注 |
|---|---|---|
| `event` | 全部 | `connect` / `roam` / `disconnect` |
| `user`, `profile` | 全部 | 来自门户或数据库回退(未知时为 `-`) |
| `device` | 已知时 | `android`/`ios`/`windows`/`macos`/`linux` |
| `conn` | 全部 | WireGuard 公钥(关联键) |
| `tunnel_ip4`, `tunnel_ip6` | connect/roam | 分配的 VPN IP |
| `src_ip`, `src_port` | 全部 | 公网源端点 |
| `transport` | 全部 | `udp`(直连) / `tcp`(ProxyGuard) / `unknown` |
| `bytes_in`, `bytes_out` | disconnect | 会话总数据量 |
| `country`, `city` | 当 GeoIP 可用且 IP 为公网时 | |
## 手动安装
```
sudo install -m 0755 eduvpn-logger.py /usr/local/sbin/eduvpn-logger.py
sudo install -m 0755 proxyguard-watcher.py /usr/local/sbin/proxyguard-watcher.py
sudo install -m 0644 systemd/eduvpn-logger.service /etc/systemd/system/
sudo install -m 0644 systemd/proxyguard-watcher.service /etc/systemd/system/
sudo install -m 0644 examples/rsyslog-10-eduvpn.conf /etc/rsyslog.d/10-eduvpn.conf
sudo mkdir -p /var/log/eduvpn
sudo systemctl daemon-reload
sudo systemctl enable --now eduvpn-logger.service
```
然后完成上述的 Apache 和 GeoIP 配置步骤,并启用 `proxyguard-watcher.service`。
## 测试
```
python3 test_eduvpn_logger.py
```
涵盖了纯解析辅助功能(端点/IPv6 分割、key=value、设备标记、ProxyGuard 行解析),不依赖任何外部框架。
## 许可证
MIT —— 详见 [LICENSE](LICENSE)。
标签:ETW劫持, Python, VPN, WireGuard, 无后门, 日志记录, 自动化扫描, 运维监控, 逆向工具