打开开发者工具,加载内部应用,观察一个请求在页面渲染前如何经过四个检查点。先解析名称,再建立 TCP 连接,然后由 TLS 握手确认服务器是谁,只有在这之后应用才会问你是谁。请把这些当作彼此独立的事件,因为每一个失败时留下的证据不同,负责的团队也不同。事故中的大多数混乱,都来自把 401 当成隧道问题,或者把隧道问题当成密码错误。

检查点一:这是你要对话的那台服务器吗

TLS 握手在发送任何凭据之前就回答了这个问题。你的客户端会依据信任存储校验证书链,并检查证书中的名称是否与你输入的主机一致。如果校验失败,浏览器会显示证书错误,并且根本不会发送 Authorization 头。因此密码提示并不能证明服务器是真的;它只能证明应用作出了响应。

第一条边界就出现在这里。TLS 保护连接一直到它的终止点。如果负载均衡器终止 TLS,并把明文 HTTP 转发给应用,那么加密段就止于负载均衡器,凭据只在第一跳受到保护。

检查点二:401 交换里实际传输的是什么

HTTP Basic 认证把用户标识和密码放在一个头部里发送,按 RFC 7617 所述用 Base64 编码。Authorization: Basic dXNlcjpwYXNz 就是它的形态;任何能读到请求的人都能把编码还原,所以全部保密性都来自下层的传输。curl -v 会同时显示 WWW-Authenticate: Basic 质询和你回送的头部。

那个头部告诉应用是谁在提问。它不告诉网络谁可以连接,本身也不加密任何东西。应用仍要拿凭据去和存储比对,并决定该账户能做什么,那是授权,发生在认证之后。

IPsec 隧道在这条路径上的位置

现在从浏览器向外走。网关到网关的 IPsec 隧道保护两个安全网关之间的选定流量,而不是你的笔记本与应用进程之间的流量。隧道模式下的 ESP 把原始报文封装起来,网关对它加密并认证,内层地址得以保留,远端才能把请求路由到应用。

选择才是让人栽跟头的地方。基于策略的隧道使用流量选择器,通常写成访问控制列表,来决定哪些报文进入隧道。基于路由的隧道则改用虚拟接口和路由表。在 Linux 上,ip xfrm policy 显示选择器,ip xfrm state 显示已建立的安全关联;在防火墙设备上,等价物是隧道的 SA 表加上报文跟踪器。

后果值得直说。一个报文可以待在一条完全健康的隧道里,却仍然携带一个被应用拒绝的 HTTPS 请求;只要路由允许,合法凭据也可以经由一条根本没有隧道的路径到达。

在报告故障的那一层去读它

401 表示应用没有接受凭据。403 表示它接受了身份但拒绝了操作。TLS 错误表示客户端没有接受服务器。没有路由或策略不匹配表示报文从未到达远端网关。四种不同的故障,四个不同的负责人,其中没有一种能靠盯着登录表单诊断出来。

使用每一层产生的证据:浏览器的网络面板用来看状态和头部,openssl s_client -connect host:443 用来看证书链,网关的 SA 计数器和日志用来看隧道流量,应用日志用来看账户裁决。当两层同时报错时,先修下面那层,因为一条死掉的隧道很容易掩盖认证失败,反过来也一样。

把一个服务端到端地画出来

为你正在排查的服务写下五行:隧道连接哪些对端,选择器允许哪些子网和端口,TLS 在哪里终止、证书声明的是哪个身份,使用哪个应用账户和角色,每一层在哪里记日志。把它们放在一页上,因为这张图的意义就是指出一层的责任在哪里结束、下一层从哪里开始。

然后每一层各测一个反例。用错误的密码,确认你拿到的是 401,而不是超时。从选择器之外的子网连接,确认报文没有被送进隧道。出示一张名称错误的证书,确认客户端在发送凭据之前就拒绝。这三个结果比任何方框图都更能说明设计。

最后,有意识地决定每种关系需要哪种保护。在 TLS 之上的 Basic 认证对于网关后面的小型内部工具是合理的选择;它不能替代按用户的授权,也不能替代对 TLS 端点之外流量的保护。隧道保护路径,证书保护那一跳,401 保护账户,而只有最后一项知道你是谁。

相关文章