第17章 以攻击样本验收整条工具链
安全声明要能被证伪
“我们使用了 JWT”“我们支持 OIDC”“我们做了审批”都不是足够具体的验收结论。可验证的声明应写成:错误 issuer 的签名令牌被拒绝;代理受众令牌不能直达业务;Alice 不能通过修改 tenant 读取 Beta 订单;旧审批在用户撤权并重新登录后仍不可用;十个并发执行只产生一次副作用。
本书测试按这些行为组织。TestSignedHTTPBoundaries 覆盖凭据约束,TestTenantScopeAndUntrustedToolInput 覆盖对象边界与恶意输入,TestApprovalBindingsAndReplay 覆盖审批参数和身份绑定,TestApprovalExpiryRevocationAndConcurrency 覆盖过期、撤权与并发,TestKeyRotationAndDependencyOutage 覆盖键集变化与故障关闭,TestTicketIsolationAndApproval 覆盖第二类业务对象。
成熟身份路径另由 TestRealKeycloakWorkload 运行。它没有在无容器时假装通过,而是显式跳过。CI 必须启动真实 Keycloak,验证 discovery、JWKS、服务身份、audience、真实到期和代理对象策略。这使“本地教学签发可控”与“外部产品真实接入”各自有证据。
攻击前提需要写清
有些测试直接调用测试私钥签出错误声明,模拟错误配置或签发方被滥用;另一些只修改客户端请求,模拟普通攻击者。二者证明的边界不同。前者检验业务层是否有独立约束,后者检验调用者能否越权。报告里应保留攻击者能力,不要把拥有签发私钥的测试解释成普通用户就能伪造令牌。
失败案例是一个团队只测所有无效请求返回非 200。实际上后端地址写错,全部请求都超时,安全测试仍然“通过”。本书每组实验都包含合法成功路径,确保失败来自目标约束。审批重放实验还恢复对象状态,证明拒绝不是偶然状态冲突。这样的对照能够防止大量虚假安全结论。
测试也要避免把实现细节镜像一遍。若测试只调用 allowedScope 并断言返回 false,它可能遗漏 HTTP handler 从未调用这个函数。真实 HTTP、真实签名、真实状态修改和审计响应,更接近系统实际行为。race detector 帮助发现内存竞争,但仍需业务断言。
从实验到交付证据
本项目构建器从同一章源文生成网页、EPUB、离线 HTML、搜索索引与代码附录。完整性校验逐章比较源文渲染结果、网页正文与 EPUB 正文,并校验实验包每个文件的哈希。EPUBCheck 5.4 使用警告即失败模式。浏览器验收覆盖浅色默认、低饱和深色、共享主题键、搜索、字号、无脚本阅读及全部章节的 320/390 像素布局。
这些检查证明出版完整性和阅读功能,不评价每句话的事实正确性。事实来源由官方标准与产品文档列出,实验结论由运行记录给出。Release 附件应直接使用同次成功 CI 的 artifact,并与 Pages 下载计算相同 SHA-256,避免本地重构建造成版本漂移。
练习与范围限制
执行 go test -race -v ./lab,保存测试名与实际结果,不保存访问令牌。再列出尚未覆盖的生产能力:真实用户 PKCE 登录、数据库事务、支付渠道幂等、跨进程缓存、分布式撤权栅栏、审批人身份和不可变审计。这些缺口不是实验失败,而是上线范围需要新增的证据。
练习:挑选三个你最重要的安全声明,分别设计一个合法对照、一个攻击样本、一个依赖故障和一个并发时序。要求每个样本能说明失败原因,并避免仅因环境不可用而“成功拒绝”。
运行记录要避免扩大数据暴露
持续集成日志是共享工件,不适合输出完整访问令牌、私钥或客户数据。真实 Keycloak 测试只记录 issuer、audience、服务 subject 与到期时刻,不记录 bearer。调试失败时,也应优先输出状态码和错误类别。若某个上游错误响应可能包含机密,保存前需要额外脱敏。
测试环境使用公开开发凭据与隔离网络,降低了泄漏后果,但不能因此形成生产调试习惯。代码中的日志策略应从一开始使用字段白名单;后续换成真实身份平台时,才不会无意把秘密送进 CI。证据文档记录版本、运行链接和哈希,足以证明产物来源,无需复制凭据。
还要区分一次通过与持续保证。依赖升级、身份配置变化、对象模型扩展和代理新工具都可能改变边界。工作流 paths 应覆盖真正参与构建和实验的文件,包括 realm、模块锁、打包说明和代码;交付记录本身不应反复触发发布。这样才能让通过的证据与实际发布输入一致。