证明网关自身身份的密钥,和表明某个人可以加入的凭据,都是密钥,相似之处也仅止于此。一个向所有客户端标识一项服务;另一个向该服务标识某位用户。混用两者能在搭建时省一步,却会在出事时花掉一下午,因为两者的恢复方式并不是同一套操作。分开管理在搭建时枯燥乏味,被跳过时却令人难忘。

两种密钥,两个不同的所有者

网关持有一把私钥或一张证书,它唯一的职责是在握手期间证明网关自己的身份。客户端需要与之匹配的公开材料来验证它,永远不需要私密的那一半。正是这种不对称,使得在不必让每个用户都能冒充服务器的前提下,服务器认证成为可能。

用户材料的方向正好相反。客户端私钥、客户端证书或密码表明,正是这个特定的人或设备获得了访问授权。它本来就是会被分发、在设备之间复制、并最终被吊销的。

WireGuard 把这种区分展示得很清楚,因为每个参与者都有一对密钥:服务器的私钥留在服务器上,而你交给用户的配置文件包含他自己的私钥以及服务器的公钥。一份包含服务器私钥的配置不是捷径,而是一场等着被文件共享泄露的沦陷。

决定这种区分的三个问题

问问谁必须能读到这份密钥,一旦暴露攻击者能做什么,以及补救只是吊销一次还是要重建整套服务。

如果用户能读到它,而它证明的是网关身份,那么第二个问题的答案是:攻击者可以向所有客户端冒充你的服务器——这是最糟的结果,因为客户端天生就被设计成信任那个身份。如果它只证明某一位用户,暴露只让攻击者获得该用户对某一个对端的访问权,吊销只是例行操作。如果它是证书颁发机构的密钥,暴露比前两者都糟,因为攻击者可以伪造出每个端点都会信任的身份。

这三个答案推导出存储规则:CA 密钥离线保存或放进保险库,网关密钥留在网关上,用户密钥跟着用户走。任何为了省事把三者放进同一个目录的布局,都是把一项决策变成了意外。

客户端真正需要什么

客户端需要公开证书或对端公钥、它应当信任的 CA 材料或指纹、自己的私钥,以及端点信息。这份清单里没有任何属于网关私密身份的东西。

文档正是泄露发生的地方。一份让你复制服务器目录、再删掉用不着的文件的指南,已经教会了错误的模型,因为删除步骤之后剩下的那个文件,几乎没人会去检查。

吊销单个用户,而不动网关

因为用户身份是单独签发的,所以也可以单独撤销。基于证书的部署会吊销客户端证书并发布更新后的吊销列表;WireGuard 部署则从服务器配置里删掉对应的对端条目。这些操作都不会改动网关自己的密钥。

保存签发记录,否则你不会知道该撤销哪个身份。一张表格就够了,前提是它写明了人、设备、序列号或公钥以及日期,并且在建表的人离开之后有人接着负责它。

下面的命令先查看客户端证书,再通过 OpenSSL 风格的机构吊销它,这正是 easy-rsa 封装的那套流程:

openssl x509 -in client.crt -noout -subject -serial
openssl ca -config openssl.cnf -revoke client.crt
openssl ca -config openssl.cnf -gencrl -out crl.pem

在客户端会检查的每个地方发布新列表,否则吊销只是文件里一条什么也改变不了的备注。如果你的网关接受客户端证书却不查吊销列表,就明确说明这一点,而不要暗示吊销是生效的。

备份也要像密钥一样分开存放

重建归档里合法地包含网关密钥和 CA 材料,因为没有它们,恢复就无法重现这项服务。客户端安装包则绝不能包含这些,两者也不能存在一起。给每个归档贴上标签,标注谁可以持有它,而不是它生成的日期。凌晨两点疲惫的管理员读的正是这个标签。

这种区分也决定了谁可以取回哪一份。管理网关、签发用户身份、读取备份是三种不同的权限,需要其中一种的人并不自动需要另外两种。把这些权限写下来,再核对你的存储实际允许什么,因为目录继承会悄悄授予超出策略本意的东西。

相关文章