身份平台高可用、升级、迁移与退出:把信任当作可核销资产
某团队把身份平台从一台虚拟机改成三副本,发布单显示“高可用改造完成”。半年后数据库故障,三个副本同时失去 realm、client 和 session 状态;人工恢复数据库时又发现签名私钥只存在旧节点本地卷,恢复出的平台虽然健康,却无法验证旧 token,也不能安全延续新签发。副本数增加了,却没有改变真正的故障域。
另一场迁移把所有应用的 issuer 在一个晚上切向新平台。网页登录看似正常,但 CLI 仍持有旧 refresh token,合作方缓存着旧 SAML metadata,SCIM 新旧连接器同时写入用户,资源 API 只接受旧 audience。团队回退 DNS 后又让新平台签发的会话成为孤儿。身份迁移不是换一个登录页面,而是迁移签发权、验证权、账号写入权、会话和审计责任。
先盘点状态、信任和外部依赖
运行资产至少包括租户或 realm、用户与组、客户端注册、redirect URI、scope 与声明映射、上游 IdP、下游资源、SAML metadata、SCIM 凭证、签名和加密密钥、浏览器 session、refresh grant、管理员账号、审计流、模板与自定义扩展。每项标明权威来源、存储位置、复制方式、恢复点、恢复时间、负责人和销毁证据。
assets:
- name: signing-key
authority: kms-keyring
consumers: [issuer, resource-apis]
rotation: dual-publish
backup: key-provider-policy
restore_test: verify-old-and-new-token
- name: scim-write-authority
authority: workforce-directory
consumers: [target-saas]
rotation: single-writer-cutover
restore_test: create-disable-reconcile依赖图要把数据库、缓存或 session store、KMS/HSM、DNS、负载均衡、反向代理、邮件/短信、MFA、外部目录、上游社交或企业 IdP、日志平台和时间同步单独画出。每个依赖定义失败时是 fail closed、只允许已有会话、禁止新登录、暂停 SCIM,还是进入管理员紧急模式。不能用一句“依赖由云厂商保障”替代业务降级语义。
高可用目标从业务旅程推导:已有会话访问、交互登录、token refresh、机器身份换取 token、管理员恢复、离职停用、密钥发布和审计导出可以有不同 RTO/RPO。身份平台健康端点返回 200,只能证明进程局部存活,不能证明这些旅程可用。
数据库、缓存与会话决定副本是否真的可替换
数据库承载的配置和身份状态需要跨故障域复制、明确一致性与故障转移。连接池总量按所有副本和后台任务计算,防止扩容把数据库连接先耗尽。自动切换后验证写入权是否唯一、复制是否追平、旧主是否隔离;双主冲突对 client、key 或 session 的影响远大于普通展示数据冲突。
缓存可能保存会话、登录事务、授权结果、JWKS 或速率状态。先确认它是可丢失缓存还是权威状态:可重建数据允许失效后回源,登录事务和服务端 session 丢失则会造成回调失败或全员退出。跨区域同步还会引入延迟和脑裂,不能为了“无感”把不可合并 session 当作普通键复制。
kill one application replica
expected: existing and new sessions continue through another replica
block session store
expected: new login fails closed with stable reason; no unsigned local fallback
promote database replica
expected: one writable primary; client/key/config writes remain consistent
restore old node network
expected: fenced old primary cannot accept identity-state writes正向实验通过轮换负载均衡目标验证任意副本可服务,并比较 session、state、nonce 与 refresh 状态。反向实验在隔离环境阻断 session store、制造数据库只读和连接耗尽,预期系统留下可区分证据并执行既定降级。不得在生产临时启用本地内存 session 来掩盖共享存储故障。
签名密钥和协议 metadata 需要双轨轮换
密钥生命周期包含生成、保护、发布公钥、启用签发、验证旧 token、停止签发、撤下公钥和销毁私钥。新私钥不应通过镜像、配置仓库或普通备份四处复制;优先使用受控 KMS/HSM 或严格保护的密钥库。资源端只获得公钥,签发服务使用受限身份访问私钥操作。
OIDC/OAuth 轮换先发布新 JWK,再让签发器使用新 kid,保留旧公钥直到所有旧 token 和缓存窗口结束。资源端遇到未知 kid 可以受控刷新,但不能关闭签名校验。SAML metadata 同理,在合作方能力允许时并列新旧验证证书;先证明对方已导入新证书,再切换签名,最后撤下旧证书。
ISSUER=https://id.example.invalid/realms/workforce
curl -fsS "$ISSUER/.well-known/openid-configuration" > discovery.json
JWKS_URI=$(jq -r .jwks_uri discovery.json)
curl -fsS "$JWKS_URI" | jq -r '.keys[] | [.kid,.kty,.use] | @tsv'
# 轮换观察脚本只列公钥标识,不导出私钥。
# 测试应覆盖:旧 token 仍可验证、新 token 使用新 kid、过渡窗结束后旧 token 被拒绝。反向实验让测试资源端缓存旧 JWKS,再使用新 kid token,预期触发一次限速刷新后成功;使用不存在的 kid 应稳定拒绝且不形成下载风暴。密钥泄露演练与常规轮换不同:需要立即停止旧签发、评估已发 token、缩短接受面、撤销 grant/session,并向所有依赖方传播事件。
外部目录、MFA 与通知通道要有独立故障语义
企业身份平台往往把认证链延伸到 LDAP/AD、上游 IdP、邮件、短信、硬件认证器、风险引擎和密钥服务。它们不应被一个笼统的“第三方依赖失败”覆盖。目录连接超时可能阻止新用户认证,却不一定需要终止已验证会话;MFA 服务不可用时,高风险操作应停止,不能静默降级为单因素;邮件失败应让邀请和恢复流程进入可重试队列,不能让已创建但无人掌握的管理员账号长期存活。
每个连接器设置连接、读取和整体请求超时,限制并发与重试,并用熔断阻止故障扩散。重试只针对可恢复错误,带指数退避、随机抖动和总预算;无效凭证、策略拒绝和配置错误立即失败。平台恢复后按优先级排空队列,离职停用和高风险撤销优先于低风险属性更新,避免恢复流量把目录或 SaaS API 再次压垮。
dependencies:
workforce_directory:
timeout: ${DIRECTORY_TIMEOUT}
retry_budget: ${DIRECTORY_RETRY_BUDGET}
failure_mode: deny-new-authentication
mfa_provider:
timeout: ${MFA_TIMEOUT}
failure_mode: deny-step-up
notification_sink:
queue: identity-notifications
failure_mode: persist-and-alert正向实验在隔离环境分别完成目录查询、MFA challenge、恢复邮件投递和 KMS 签名,并保存依赖请求 ID、延迟和平台决策。反向实验依次制造 DNS 失败、TLS 不受信、超时、429、凭证拒绝和响应格式错误,预期 reason code、熔断状态与降级行为彼此可区分。若所有情况最后都表现为“用户名或密码错误”,既妨碍值班判断,也可能诱导用户反复提交凭证。
外部依赖的管理员恢复同样要避免环形依赖:身份平台管理员不能只能通过同一个已故障的上游 IdP 登录,KMS 解封不能只依赖由该平台签发的机器 token,告警接收人也不能只存在于不可用目录。紧急路径定期演练、双人取用、使用后轮换,并通过独立审计通道留证;它提供恢复入口,不提供绕过业务授权的永久后门。
容量要覆盖峰值、批处理和故障恢复积压
身份系统容量不只看登录 TPS。模型还包括 authorization、token exchange、refresh、logout、JWKS 与 metadata 读取、管理员 API、MFA、外部目录查询、SCIM 全量和增量同步、审计导出。密钥轮换、开工高峰、移动端同时刷新、目录恢复后的重放队列以及区域故障转移,会制造比日常均值更陡的峰值。
每层设置容量预算与停止条件:入口并发、应用线程、数据库连接与写 IOPS、session 缓存内存、KMS 调用、外部 IdP 超时、邮件/MFA 限流、SCIM 429 退避和审计队列。指标标签避免用户和组高基数。扩容评审同时估算副本、数据库、缓存、跨区流量、日志、KMS 与 SaaS API 消耗;动态配额和费用只从当前官方入口取得。
steady state -> normal login + refresh + incremental provisioning
planned peak -> shift start + batch provisioning + key publication
dependency outage -> bounded retries, no retry storm
recovery surge -> queues drain under rate limits without starving login
regional failover -> surviving capacity handles critical journeys压测使用隔离租户和不可用于生产的测试主体,逐阶增加流量并记录 p50/p95/p99、错误分类、数据库饱和、队列年龄和外部限流。停止条件必须在压测前确定。清理时撤销测试 grant、删除账号和组、恢复限流与告警阈值,并核销临时容量。
备份只有经过恢复和业务验证才有意义
一致性备份覆盖数据库、加密所需密钥引用、媒体或模板、自定义扩展、外部配置清单和协议资产。导出 realm 或租户 JSON 往往不能替代数据库级恢复,也可能不含密码、会话、密钥或第三方连接秘密。密钥材料是否可备份、如何恢复必须遵守所用 KMS/HSM 的模型,不能把明文私钥塞进普通归档。
1. 冻结备份批次 ID,记录数据库位点、配置提交和密钥版本。
2. 在隔离网络恢复到新实例,禁止连接生产邮件、SCIM 和 webhook。
3. 验证管理员紧急入口与审计,不复用生产管理员 cookie。
4. 验证授权码 + PKCE、refresh、资源端验签和 logout。
5. 验证 SAML、外部目录、SCIM 创建与停用的受控替身。
6. 记录可恢复资产、缺失项、RPO/RTO 和销毁隔离恢复环境的证据。恢复实验必须防止“恢复副本向真实下游写数据”。隔离 DNS、邮件、短信、webhook 和 SCIM,使用 sink 或测试租户。正向证据是从空环境恢复后关键旅程成立;反向实验删除一个必要密钥引用或阻断数据库迁移,预期恢复门禁阻止切流,而不是启动一个只能重新登录、无法验证旧 token 的半成品。
管理员 break-glass 账号独立于主要联邦链,使用强认证、双人取用和全程审计;它不是日常超级管理员。演练后轮换凭证并确认告警触发。恢复 runbook 也要在主要文档平台不可用时可获得,但副本不得包含可直接使用的秘密。
升级是一组协议和状态契约测试
升级前锁定候选版本,阅读目标产品当前升级指南与支持矩阵,盘点数据库迁移、缓存格式、插件/主题、API、代理配置和外部连接器。数据库 schema 一旦前向迁移,回滚应用镜像可能无法读取,必须事先确定可逆点、备份恢复成本和停止窗口。滚动发布不天然等于无损升级。
候选环境从生产配置的脱敏副本或声明式清单构建,执行协议契约:Discovery issuer、authorization code + PKCE、refresh rotation、logout、JWKS 轮换、资源 audience/claim、SAML 签名、SCIM 创建/更新/停用、管理员登录与审计导出。每条保存成功和故障证据,健康检查只是其中一项。
$checks = @(
"discovery-issuer",
"authorization-code-pkce",
"refresh-and-replay",
"jwks-rotation",
"resource-audience",
"scim-disable",
"audit-export"
)
foreach ($check in $checks) {
Write-Host "RUN $check with isolated test identities"
# 调用项目自己的契约测试;失败即停止推进,不自动跳过。
}灰度按租户、客户端或流量边界推进,先选择可回退的低风险对象。新旧节点若共享数据库,要确认混合版本兼容;若不兼容,就使用蓝绿环境与受控数据切换。反向实验包含旧插件加载失败、外部 IdP 超时、未知 claim 和 schema 迁移中断。回滚不仅恢复镜像,还要恢复数据库、配置、密钥发布和代理路由的一致状态。
双联邦迁移先分离签发权、验证权和写入权
跨平台迁移至少有三条轨道:新平台开始签发,资源端开始验证新 issuer/metadata,目录供给切换写入权。三者不能在一个不可观察的 DNS 切换里同时发生。先建立新 issuer 和客户端,资源端在有限迁移窗显式接受新旧两个可区分 issuer;再按客户端或租户迁移登录,最后迁移 SCIM 单写权。
trusted_issuers:
- issuer: https://old-id.example.invalid/
audience: orders-api
expires_after_migration: true
- issuer: https://new-id.example.invalid/
audience: orders-api
status: candidate
provisioning:
writer: old-scim
candidate: new-scim-readonly-reconcile双接受不等于共享私钥。新旧平台使用不同 issuer、client、密钥和审计身份,资源端按 issuer 选择对应 JWKS 与 claim 映射。比较 subject 稳定性、组/角色、认证上下文、MFA、session 与 logout 语义。旧 refresh token 通常不能安全搬到新平台,应设计重新认证窗口,而不是复制不可验证的会话状态。
SCIM 阶段先让新连接器只读核对或写入隔离目标,比较 externalId、唯一属性、组成员和停用。切换时冻结变更、追平队列、关闭旧 writer、启用新 writer,再执行创建/更新/停用反向验证。两个 writer 同时无条件管理同一对象会造成账号复活和组漂移,是迁移红线。
回滚必须在切换前定义权威侧和数据方向
每个批次记录当前权威平台、可接受 issuer、SCIM writer、DNS/代理路由、客户端配置和撤回动作。回滚点分为“尚未产生新状态”和“新平台已经产生用户、grant、session 或审计”。后者不能只把流量切回旧平台,还要处理新增状态和双轨期间的撤销传播。
正向迁移实验选一个隔离客户端:在新平台登录、刷新、访问资源、退出,再由新 SCIM writer 创建并停用测试用户。反向实验撤掉新 issuer 信任、制造错误 audience、让新 SCIM 写入失败,预期批次停止且旧客户端不受影响。未达到判定标准时不扩大批次。
回滚清单至少回答:新平台签发的 token 谁拒绝或撤销;旧平台是否仍可安全写目录;已迁移用户是否需要重新认证;SAML metadata 是否恢复;审计如何合并;新客户端 secret 和管理员账号是否撤销。只恢复 DNS 会留下两套仍可签发的身份入口。
退出要证明旧信任、凭证和数据已经消失
停止容器或取消订阅只是终止服务,不是安全退出。客户端与资源端必须删除旧 issuer、JWKS、SAML metadata、redirect、证书和代理路由;旧 refresh grant、session、SCIM token、client secret、机器身份和管理员账号全部撤销。DNS、邮件/MFA、webhook、日志导出与外部目录连接解除后,还要观察是否有遗留调用。
用户、组、属性、审计和法定保留数据按策略迁移、归档或销毁。数据库、备份、对象存储、缓存快照、日志副本和密钥副本逐项核销;加密删除只有在确认所有密钥副本和恢复副本都受控时才成立。供应商导出、数据驻留、删除证明和合同结束由负责人留档。
exit evidence
[ ] no client redirects to old platform
[ ] no resource trusts old issuer or metadata
[ ] old sessions, grants, SCIM and client credentials revoked
[ ] old signing keys cannot sign; public-key grace window ended
[ ] DNS, proxy, webhook, mail/MFA and audit routes removed
[ ] users, groups, audit and retained data reconciled
[ ] databases, backups, objects, logs and key copies disposed
[ ] monitoring shows no residual calls during observation window最后执行反向证明:用旧 token 访问资源、用旧 client secret 换 token、用旧 SCIM token 查询或写入、访问旧管理入口和旧 DNS。预期都是可解释的拒绝或不可达,且不会触发隐藏回退。退出记录由身份平台、应用、基础设施、安全、数据治理和采购共同签字,因为任何一方遗留的信任都可能让旧平台继续成为攻击入口。
长期运行时,每季度从资产台账抽取客户端、密钥、管理员、外部连接器和 SCIM writer 做核对,并安排数据库故障、密钥轮换、恢复与迁移演练。架构成熟度不体现在部署了多少副本,而体现在每条信任能否被定位、替换、回滚,并在生命周期结束时被证明已经失效。
