先读 EAP 方法:VPN 能用哪种口令哈希
VPN 验证的是秘密本身,而不是网页哈希。先读方法,再核对存储里到底有什么,然后才能承诺同一批口令继续可用。
很多团队都有能用的网页登录,但同一个表里的口令 VPN 一个都不认。网页应用比对的是它自己算出来的哈希,而隧道要客户端以服务器能校验的方式证明自己握有某个秘密。这是对存储材料的两套不同要求。多数这类项目卡住的地方,正是这两者之间的落差。
先读方法,再看表结构
在看任何表结构之前,先写下所选方法需要什么证明。挑战-应答方法需要服务器能据此算出预期应答的材料,实际就是口令本身或某种兼容的派生形式。在被保护的隧道里携带口令的方法,需要校验方拿到口令本身。读方法文档时要找校验方的要求,而不是客户端配置,因为客户端那一侧通常容易。
现代 Web 框架生成的加盐哈希两样都给不了。它只验证某一次具体比对,无法还原出它所来自的秘密。
挑战-应答用不了加盐哈希
MSCHAPv2 是挑战-应答交换,服务器永远拿不到口令,也就无法比对某个存储字符串。它只能用手里已有的材料推导预期应答,兼容的存储形式因此狭窄得厉害。带每用户盐的 bcrypt 或 Argon2 值单向到彻底排除了这种交换。下面这张表从存储形式出发,而不是从方法名出发,因为决定答案的是存储形式。
| What the store holds | What that material can verify | What has to change |
|---|---|---|
| A bcrypt or Argon2 value with a per-user salt | nothing challenge-response can use, and not the password either | re-enrol the users, or place a verifying service in front |
| An unsalted NT-hash | MSCHAPv2, and inner PAP through a TLS tunnel | nothing, as long as that store is the verifier |
| A client certificate with a revocation list | EAP-TLS only | move enrolment and revocation to the PKI owner |
strongSwan 的 SQL 插件文档 为配置和凭据定义了它自己的一套表结构,而项目至今仍把该插件标为实验性。它不会去适配某个应用的用户表,也不会去猜某个存储值出自哪套哈希方案。所以现实的选择只有两个:迁移到网关能用的存储,或在现有存储前面放一个能做这次交换的认证服务。除此之外的东西,都是内藏隐藏假设的连接器。
RADIUS 挪的是活,不是要求
把 EAP 交给 RADIUS 服务器,改变的是校验发生的位置,通常还能改善审计线索。它并不能从单向哈希里还原口令,所以认证服务仍然需要与它所终结的方法兼容的材料。团队往往是在把 RADIUS 这条路搭完、又在另一端撞上同一堵墙之后,才明白这一点。
如果 RADIUS 服务器能读到存放兼容凭据的目录,方案就成立,VPN 网关也保持简单。如果读不到,诚实的答案是改注册流程,而不是再加一个连接器。发放与吊销属于拥有身份的一方,所以一份没有受支持同步流程的复制表,是第二套凭据生命周期,而不是单点登录。只要有人只在一处改了口令,它就开始漂移;而这种漂移是无声的,直到两个系统里都存在的账号在两边表现不一致。
把读目录的账号限制在只读这一件事上,并让它与网关自身配置所用的账号分开。共用一个管理账号,会把只读设计变成大得多的爆炸半径。
账号应该放在哪
材料问题定下来之后,还剩两种架构。网关可以自持一个小凭据库,只由它读取;也可以交给一个站在企业目录前面的认证服务。
前者更容易想清楚,对用户却更糟,因为他最终要多维护一套口令。后者只保留一个身份,却多了一个依赖:现在得先有这个服务在线,任何人才能连上。两种都不算错,但这个选择应该记在网关配置旁边,否则下一个工程师会默认选了另一种。无论选哪种,下面这两条命令就是改动之前先用来看清现状的。
mysql -N -e "SELECT id, username FROM vpn_users WHERE active = 1 LIMIT 5" vpn
swanctl --list-conns账号认证也不等于授权。组成员关系、设备状态和网络权限,在用户证明了所需身份之后,仍然需要显式映射。一个认证做得完美却什么都不授权的连接,是策略的结果,不是方法的缺陷。