OIDC Discovery 中的 CORS 问题与 Grafana 自定义请求头

概述

在现代可观测性平台的部署中,与身份提供商(IdP)的安全集成是保障系统可靠性的关键环节。本文档基于 Grafana 的一个实际案例,分析了在 OIDC Discovery 流程中因客户端发送非标准自定义请求头(X-Grafana-Device-Id)而引发的 CORS(跨源资源共享)问题。此类问题直接阻碍了单点登录(SSO)等关键认证功能的配置与运行,是 SRE 在保障系统可用性与可维护性时需要关注的典型集成故障。

核心概念

CORS(跨源资源共享)

CORS 是一种浏览器实施的安全机制,用于控制网页从不同源(域名、协议或端口)请求资源的权限。当浏览器发起跨域请求时,目标服务器必须通过响应头(如 Access-Control-Allow-Origin)明确允许该请求,否则浏览器将阻止响应数据的读取。对于复杂的非简单请求,浏览器会先发送一个 OPTIONS 预检请求,以确认服务器是否允许实际请求。

OIDC Discovery

OIDC(OpenID Connect) Discovery 是一个标准协议(规范定义于 OpenID Connect Discovery 1.0),允许客户端通过一个已知的 URL(通常以 /.well-known/openid-configuration 结尾)自动获取身份提供商的端点信息(如授权、令牌、用户信息等端点)。这极大地简化了 OAuth/OIDC 客户端的配置过程,是实现自动、可靠集成的基石。

实践

问题现象与根因分析

当在 Grafana 中配置 OAuth 认证时,浏览器会向配置的 OIDC Discovery URL 发起请求以自动填充端点信息。根据报告的案例,Grafana 的客户端代码在此请求中附加了一个自定义的 X-Grafana-Device-Id 请求头。

  1. 浏览器预检:由于该自定义头不属于“简单请求”头列表,浏览器会先发送 OPTIONS 预检请求。
  2. 服务器拒绝:身份提供商(如 Zitadel)的 CORS 策略未明确允许 X-Grafana-Device-Id 这个非标准头,因此拒绝了预检请求。
  3. 流程中断:实际的 GET 请求被浏览器拦截,导致 Grafana 无法获取端点信息,OAuth 自动配置失败。

根因:在客户端发起的、需要严格遵守跨域规范的 OIDC Discovery 请求中,附加了一个目标 IdP 无法预知且未被 CORS 策略允许的自定义请求头,破坏了协议的互操作性。

解决方案与规避方法

针对此问题,存在两种主要的解决思路,SRE 可根据环境进行选择:

1. 服务端代理(推荐) 将浏览器直接发起的 OIDC Discovery 请求改为由 Grafana 后端服务器代理。

  • 优势:
    • 绕过 CORS:服务端到服务端的请求不受浏览器同源策略限制,彻底避免 CORS 问题。
    • 前置失败:可以在 Grafana 服务器端提前验证 IdP 端点的可达性(例如,验证 DNS 解析、网络防火墙规则),在用户配置阶段就发现环境问题。
    • 安全性:可避免在浏览器中暴露内部网络结构或 IdP 端点的直接访问路径。
  • 实现方式:这通常需要在 Grafana 的产品层面进行修改,将 GET /.well-known/openid-configuration 的浏览器请求转换为 Grafana 后端向该 URL 发起的服务端请求。

2. 客户端请求头清理 在 Grafana 的客户端代码中,确保在发起 OIDC Discovery 等无需身份验证的发现请求时,不附加任何自定义请求头(特别是 X-Grafana-Device-Id)。

  • 优势:改动范围小,仅影响客户端行为。
  • 注意事项:需要确保所有类似的、面向未知服务的出站请求都已清理,这需要全面的代码审查。Grafana 的 #81957 曾尝试修复此问题,但本案例表明该修复并未覆盖所有场景。

临时规避:如果 IdP 端配置灵活,可以尝试将其 CORS 允许头列表(Access-Control-Allow-Headers)中显式加入 X-Grafana-Device-Id。但这并非理想方案,因为它依赖于对特定客户端非标准行为的迁就,可能引入安全隐忧。

SRE 要点

与可靠性、可观测性的关联

  • 可靠性:身份认证是系统的第一道防线。OIDC Discovery 失败会导致 SSO 整体不可用,影响用户登录,从而直接影响系统核心功能的可用性。一个可靠的 SSO 集成是保障用户体验和系统访问控制的基础。
  • 可观测性:此类问题通常在配置阶段出现,但监控也至关重要。SRE 应监控 Grafana OAuth 登录失败率、OIDC Discovery 请求的 4xx/5xx 错误率,并将其作为身份集成健康度的关键指标(SLI)。设置相应的 SLO(例如,99% 的 OIDC Discovery 请求应能在 5 秒内成功),并将 Error Budget 用于推动对此类互操作性问题的修复。

最佳实践与常见反模式

  • 最佳实践:
    • 服务端优先:对于任何身份协议的发现或初始化步骤,尽可能采用服务端代理或服务端发起请求,避免浏览器 CORS 的复杂性和不可控性。
    • 标准合规:在实现任何标准协议(如 OIDC)时,严格遵守规范,避免添加未在规范中定义或未广泛支持的自定义元素,尤其是会破坏互操作性的请求头。
    • 早期集成测试:在 CI/CD 流程中,集成对目标 IdP 进行端到端的 OIDC 流程测试,而不仅仅是单元测试。将 IdP 视为一个外部依赖。
  • 常见反模式:
    • 假定 IdP 容错性:认为所有 IdP 都会宽泛地配置 CORS 策略以兼容各种客户端,忽略了安全默认策略(默认拒绝)的普遍性。
    • 遗漏客户端请求上下文:未区分“浏览器发起的、受 CORS 限制的请求”和“服务器发起的、不受限的请求”,在错误的上下文中使用了相同的请求头配置。
    • 仅修复已知问题点:如本案例所示,修复了部分出站请求的头清理,但遗漏了特定路径(OIDC Discovery)的请求,导致问题重现。

相关指标(SLI/SLO/SLA)

  • SLI:grafana_oauth_oidc_discovery_request_success_rate(成功获取 OIDC 端点配置的请求比例)。
  • SLO:确保该成功率不低于 99.9%,并监控 Error Budget 的消耗。当 Error Budget 因此类问题被快速消耗时,应触发相应的优先修复工单。
  • 关注指标:浏览器控制台的 CORS 错误计数、Grafana 服务端 /api/login 或相关认证端点返回 5xx 的比率、IdP 端的 CORS 预检拒绝日志。