共享子域名,两个入口:谁能看到哪些病历?
子域名决定品牌,经过验证的成员关系决定数据。无论请求来自办公网地址还是 VPN,都执行同一套租户检查。
诊所 A 和诊所 B 共用一个代码库、两个子域名,以及来自办公室与 VPN 的混合访客。网络团队往往把两个入口分别配置,这没问题,只要应用不这么做。下面的检查属于应用,无论请求从哪扇门进来,行为都必须一致。
第一:主机名只负责指定租户
把请求的主机名解析到管理员维护的映射上,凡是未知的一律拒绝。使用框架校验过的主机值,而不是原始请求头,这样伪造的 Host 无法注入查询键。匹配成功只是告诉代码该加载哪个租户的配置和品牌。它并不说明调用方能否看到该租户的数据。把第二个问题放进另一个函数,用另一个入参。
一个共享的辅助函数如果从一次主机名查询里同时返回上下文和权限,那就是缺陷的开端。
第二:成员关系来自会话
查询已认证用户的活跃成员关系,并与第一次检查得到的租户比较。同时属于两家诊所的用户可以切换,但切换应当是在请求上下文中记录的显式操作,而不是从 URL 推断出来的。只属于一家诊所却请求另一家的用户会被拒绝,下载和 API 调用同样如此。
测试否定情形:改动病历标识符和租户参数。只要任一改动仍返回数据,作用域就是从请求里读的,而不是从已验证的成员关系里读的。
Cookie 作用域和单点登录决定账号可以在哪里呈现。它们不决定该账号能读什么。
第三:网络证明不了人的身份
VPN 网关认证的是隧道,公开允许列表认证的是地址段。两者都不能识别一个人。NAT 把许多用户压缩成一个源地址,办公室地址段对楼里所有人也是同样效果。把两者都当作传输层事实,把授权留在应用里,或者放在明确声明身份、并据此接受评审的集成里。
DNS 是第三个传输层事实。私有答案需要私有路由,公开前端需要受支持的源站路径。它们都不携带权限。
贴在墙上的一句话:网络决定请求怎么到达,应用决定它能读什么。
第四:在两个入口上都跑一遍矩阵
用受控账号分别测试 A 访问 A、B 访问 B、A 访问 B、一个未知主机名,以及一个没有任何成员关系的已认证账号。第四和第五种情况必须失败并保持关闭,绝不能回退到某个默认租户。同一条矩阵也要走 VPN 入口跑一遍,每个账号一个会话罐:
curl -s -o /dev/null -w '%{http_code}' -b jar.txt https://clinic-b.example.com/records/42
curl -s -o /dev/null -w '%{http_code}' -b jar.txt https://clinic-a.example.com/records/42
然后改一条允许列表条目并重跑。结果如果发生移动,说明某项网络控制在代替应用做授权决定。
前端每改一次就重跑矩阵,因为绕过检查的第二条代码路径是这条边界被侵蚀最常见的方式。预期结果很无聊:每个入口给出同样的答案。