OIDC 管理

ACP 可以将用户重定向到符合标准的 OIDC Identity Provider,并在成功登录后创建或更新其平台身份。

初始集成若要尽可能简短,请使用 Form 选项卡。 当返回的用户名 Claim 必须显式选择、Claim 必须重映射、必须查询 UserInfo,或者需要 OIDC 组和额外字段时,请使用 YAML 选项卡。参见 使用 YAML 添加 OIDC

开始之前

准备以下值:

  • Issuer URL,在 ACP 中标记为 Server Provider URL
  • Client ID。
  • Client Secret,在 ACP 中标记为 Client Key
  • 一个具有电子邮件地址的代表性测试账户,该地址尚未被 ACP 本地用户或其他 IDP 使用。
  • 为该测试账户返回的一个稳定的 username Claim。测试过的 YAML 回退使用 preferred_username

向上游 Identity Provider 注册以下精确回调:

https://<ACP_ADDRESS>/dex/callback

ACP 会根据平台地址派生回调地址。表单中不包含 Redirect URI 字段。

Server Provider URL 必须是 Issuer 根地址,而不是 authorization endpoint、token endpoint 或登录页。其 discovery document 通常可在以下路径获取:

<ISSUER_URL>/.well-known/openid-configuration

你可以在一个与 ACP 具有等效可达性的诊断主机上检查 discovery:

curl -fsS 'https://idp.example.com/.well-known/openid-configuration'

对于使用私有 CA 的上游服务,请使用该 CA 独立验证证书链:

curl -fsS --cacert /path/to/oidc-ca.pem \
  'https://idp.example.com/.well-known/openid-configuration'

该外部查询只是初步证据。ACP Connector 创建才是集群侧的 discovery 和可达性检查。

确认返回的 Claims

请上游 Identity 管理员提供一个代表性账户的 ID token 或 UserInfo Claim 名称。Claim 名称区分大小写。ACP Connector 创建会验证 discovery 和客户端可达性,但不能证明真实用户的 token 包含所需的 Claims。

Claim 或设置需要的条件ACP 用途
sub始终需要OIDC 协议使用的稳定上游 subject。它必须是字符串。
name未配置 userNameKeyACP 用户名的默认来源。
userNameKey 选择的 Claim默认 name Claim 缺失或不适用时ACP 用户名的来源。已验证的回退是 preferred_username
email 或由 claimMapping.email 选择的 Claim请求了 email scopeACP 用户身份。它必须是字符串,并且不应与现有 ACP 用户冲突。
email_verified请求了 email scope当前登录流程所需的布尔验证标志。
claimMapping.groups 选择的 Claim启用了 OIDC 组处理用于创建并关联 ACP 组的字符串或组名数组。
claimMapping.phoneclaimMapping.mail 选择的 Claim配置了这些可选的 profile 字段复制到 ACP 用户 mobile 和 mail 字段的字符串值。
claimExtra 中列出的 Claim额外值必须存储到 ACP当其配置类型匹配时,值会复制到 spec.extra。本文档记录了已验证的 string 类型。

在发布前完成一次真实的浏览器登录并检查生成的 ACP 用户。该登录是验证 Claim 可用性、映射、冲突行为和组限制的权威检查。

上游 OIDC 证书验证限制

在本文档验证的 ACP 环境(auth-controller2apollo v4.3.6)中,Connector 创建接受了一个 HTTPS Issuer,其自签名 CA 未被普通客户端信任,并且存储的 Connector 包含 insecureSkipVerify: true。因此,在该实现中 ACP 不会验证上游 OIDC 服务器证书。HTTPS 仍然会加密连接,但 Connector 创建成功并不能证明该证书链已被信任。

如果客户的安全策略要求验证上游服务器证书,则已验证的实现不满足该要求。请在发布前解决产品版本或安全策略要求。

使用 Form 添加 OIDC

  1. 转到 Users > IDP
  2. 单击 Add OIDC
  3. 填写 Basic InfoNameDisplay Name 和可选的 Description
  4. 填写 Server SettingServer Provider URLClient IDClient Key
  5. 对于此已验证的集成流程,请将 Logout URL 留空。
  6. 单击 Add

在创建过程中,ACP 会从 Issuer URL 读取 OIDC discovery。无效或不可达的 Issuer 会在此阶段被拒绝。当前的创建检查不能证明 Client Key 正确,也不能证明上游 TLS 证书受信任:即使 key 错误,Connector 也可能被创建,随后在浏览器回调时失败;同时,不受信任的自签名证书也可能被接受。因此,身份验证仍需要一次真实登录,而证书合规性必须按上文所述单独评估。

创建完成后,ACP 会将提交的 Client Key 迁移到受管理的 Secret。存储的 Connector 引用该 Secret,而不是在其配置中保留 clientSecret

控制台字段参考

ACP 控制台字段YAML 字段产品行为
Nameidmetadata.name两个字段使用相同的、符合 DNS 规范的值。
Display Namename显示在 ACP IDP 选择器上的名称。
Descriptionmetadata.annotations["cpaas.io/description"]可选的、面向 operator 的描述。
Server Provider URLspec.config.issuer用于 OIDC discovery 的 Issuer 根地址。
Client IDspec.config.clientID在上游注册的客户端标识符。
Client Keyspec.config.clientSecret初始创建时的输入;ACP 会将其存储到受管理的 Secret 中。
Logout URLlogoutUrl保持为空。已验证的运行时未将其用作注销后的重定向。

表单不会暴露 redirectURI、scopes、UserInfo、Claim mappings、组处理或额外 Claims。只有在确认上游响应后,才通过 YAML 配置这些字段。

验证集成

  1. 在没有 ACP 登录状态的情况下启动一个单独的浏览器会话。
  2. 打开 ACP 并选择已配置的 OIDC Display Name
  3. 在上游 Identity Provider 完成身份验证。
  4. 确认浏览器返回到 ACP,而不是 HTTP 500 错误。
  5. Users 中找到生成的用户,并验证其 IDP 来源、email、username 和 active 状态。
  6. 如果由于上游响应不包含默认的 name Claim 而导致 username 为空,请使用下面的 YAML 示例重新创建或更新 Connector,然后再次登录。
  7. 分配一个最小角色,参见 Manage User Roles
  8. 再次登录并确认已分配的 ACP 权限可用。
电子邮件身份冲突

在已验证的 ACP 行为中,通过另一个 OIDC Connector 使用相同的映射 email 登录,会将现有 ACP 用户更新并重新关联到较新的 Connector。它不会创建第二个用户,也不会拒绝该冲突。请使用专用的、不会冲突的账户进行验收测试,并在发布前检查现有 ACP 用户。

Connector 创建、上游身份验证、ACP 用户创建以及 ACP 授权是彼此独立的检查点。请分别验证每个检查点。

使用 YAML 添加 OIDC

当上游响应需要显式 Claim 选择或高级映射时,请使用 Users > IDP > Add OIDC > YAML。下面的完整示例假设上游响应包含 subpreferred_usernameemailemail_verifiedgroupsphone_number,并且测试用户属于 acp-users。请移除真实上游响应不满足的可选映射和组限制。

apiVersion: dex.coreos.com/v1
kind: Connector
id: corporate-oidc
name: Corporate OIDC
type: oidc
metadata:
  name: corporate-oidc
  namespace: cpaas-system
  annotations:
    cpaas.io/description: Corporate OIDC
    # cpaas.io/idp.validation: "false"
  labels:
    cpaas.io/idp.version: v2
spec:
  config:
    issuer: https://idp.example.com
    clientID: <CLIENT_ID>
    clientSecret: <CLIENT_SECRET>
    redirectURI: https://acp.example.com/dex/callback
    scopes:
      - profile
      - email
    getUserInfo: true
    userNameKey: preferred_username
    overrideClaimMapping: true
    claimMapping:
      preferred_username: preferred_username
      email: email
      groups: groups
      phone: phone_number
      mail: email
    claimExtra:
      - field: preferred_username
        type: string
    insecureEnableGroups: true
    allowedGroups:
      - acp-users

在提交前,请替换 Issuer、凭证、ACP 地址、Claim 名称以及允许的组。如果提供方未暴露 UserInfo endpoint,或者所有必需的 Claims 都已包含在 ID token 中,请将 getUserInfo 设为 false。如果不需要组处理,请移除 insecureEnableGroupsallowedGroupsclaimMapping.groups

不要在 scopes 中添加 openid;Connector 会自动将其添加到 authorization request 中。如果上游提供方需要一个特定于该提供方的 scope 来获取组 Claims,请仅在 discovery 和提供方配置确认后再添加该 scope。

请通过 ACP 控制台的 YAML 选项卡提交此初始配置。ACP 会将 clientSecret 迁移到受管理的 Secret,存储的 Connector 将改为包含 clientSecretRef。不要使用带有内联 clientSecretkubectl apply:Kubernetes 的 last-applied annotation 可能会在 Connector metadata 中保留提交的 secret。

OIDC 字段完整参考

顶层和 metadata 字段:

字段是否必需产品行为
apiVersion使用 dex.coreos.com/v1
kind使用 Connector
idConnector 标识。保持与 metadata.name 相同。
name显示在 ACP 登录选择器上的显示名称。
type使用 oidc
metadata.nameKubernetes 资源名和 ACP Connector 查询键。
metadata.namespace使用 cpaas-system
metadata.annotations["cpaas.io/description"]ACP 显示的描述。
metadata.annotations["cpaas.io/idp.validation"]仅用于临时排障字符串值 "false" 会在本次创建或变更 spec 的更新中跳过上游连通性验证;格式验证仍会运行。不要将其设为 "true"
metadata.labels["cpaas.io/idp.version"]推荐使用 v2;当前 ACP 在创建时也会添加该 label。

连接和登录字段:

字段是否必需产品行为
issuer用于获取 /.well-known/openid-configuration 的 Issuer 根地址。
clientID在上游注册的客户端标识符。
clientSecret初始创建ACP YAML 页面接受的明文输入。ACP 会将其移除并存储到受管理的 Secret 中。
clientSecretRef存储后的输出ACP 写入的受管理 Secret 引用。不要将其与 clientSecret 一并提交,也不要手动编辑它。
redirectURI在上游注册的精确 ACP 回调:https://<ACP_ADDRESS>/dex/callback
scopes例如 profileemail 之类的附加 scope。ACP 会自动添加 openid
getUserInfo设为 true 时,请求 UserInfo endpoint,并在映射前合并返回的 Claims。请确认提供方支持该 endpoint。
userNameKey作为 ACP 用户名的 Claim,替代默认的 name;已验证的回退是 preferred_username

Claim 和组字段:

字段是否必需产品行为
overrideClaimMapping强制自定义映射时当标准 Claim 也存在,但 ACP 必须使用下面选择的 Claim 时,设为 true
claimMapping.preferred_username用作首选用户名的 Claim。
claimMapping.email重映射 email 时用作 ACP 主身份的 Claim。发布前请检查冲突。
claimMapping.groups启用组处理时包含字符串或组名数组的 Claim。这个嵌套字段是运行时的组映射。
claimMapping.phone复制到 ACP 用户 mobile 字段的字符串 Claim。
claimMapping.mail复制到 ACP 用户 mail 字段的字符串 Claim。
claimExtra[].field复制到 ACP 用户 spec.extra map 的 Claim。
claimExtra[].typeclaimExtra[].field 一起使用对于本文档中的已验证流程,请使用 string。当 Claim 缺失或类型不同时,请移除该条目。
insecureEnableGroups需要组处理时设为 true,以便读取已映射的组 Claim,并在登录时创建或关联 ACP 组。
allowedGroups当该字段非空时,只有至少一个映射组匹配时才允许登录。ACP 仅保留匹配的允许组。

如果 scopes 中包含 email,则所选 email Claim 必须是字符串,并且合并后的响应中 email_verified 必须是 Boolean。任一值缺失都会导致登录失败。

临时绕过验证

仅当必须在上游服务临时不可达时暂存 Connector,才使用 cpaas.io/idp.validation: "false"。ACP 仍会检查配置结构,但会跳过 discovery 和上游连通性检查。因此,即使浏览器登录无法工作,该 Connector 也可能存在。

admission webhook 会从已存储的 Connector 中移除该 annotation,并将跳过标记复制到受管理的 credential Secret 中。后续在没有该 annotation 的 spec 变更型 Connector 更新将执行正常的 Connector 验证。要为未来的 Client Key 变更恢复验证,请通过不带该 annotation 的 Connector 更新凭证;不要直接编辑受管理的 Secret。不要使用 cpaas.io/idp.validation: "true";正常验证时请省略该 annotation。

在发布前,请完成一次正常验证的创建或真实的纠正性更新,然后执行一次真实的浏览器登录并检查生成的 ACP 用户。仅 metadata 更新不能证明正常验证已运行。

ACP 管理且无效的字段

存储的 YAML 可能包含 controller 添加的默认值,例如 issuerAlias: ""、顶层 groupsKey: groupsinsecureSkipVerify: true

  • 通过 claimMapping.groups 配置组映射。当前运行时不会将 controller 添加的顶层 groupsKey 用作组 Claim 选择器。
  • 不要将 insecureSkipVerify: false 作为证书信任控制手段。正如 开始之前 所述,已验证的运行时不会验证上游证书。
  • 除非某个产品特定的集成流程明确要求,否则请不要在已验证流程中使用 issuerAlias

登录、更新和删除行为

  • OIDC 用户会在成功的浏览器登录后创建或更新。OIDC 不提供类似 LDAP 的手动或计划性用户同步。
  • 在更新用户名映射后,请启动新的浏览器会话并再次登录,以验证更新后的用户。
  • 通过 ACP 删除 Connector 而不进行清理,会保留源用户并将其标记为无效。
  • 选择 Clean up IDP users and User Groups 会删除生成的源用户以及与 Connector 一起管理的 Secret。

请使用 ACP 控制台操作或 ACP product API 进行删除。直接使用 kubectl 删除底层 Connector custom resource 会绕过已测试的 ACP 用户失效和清理行为。

注销行为

在已验证的当前流程中,ACP 注销会结束 ACP 会话,并将浏览器返回到 ACP IDP 选择器。再次选择同一个 OIDC 条目会复用仍然有效的上游会话,并在不再次提示凭证的情况下完成登录。因此,ACP 注销并不能证明上游会话已经结束。

经过测试的流程没有将配置的 Logout URL 用作浏览器重定向。请将该字段留空,并在客户安全要求需要时单独验证上游会话终止。

按阶段排查

症状已证实阶段下一步检查
ACP 在创建期间拒绝 Connector。discovery 或集群侧可达性未完成。确认 Server Provider URL 是 Issuer 根地址,从与 ACP 等效的网络路径获取其 discovery document,然后重试创建。
Connector 创建成功,但回调返回 HTTP 500,错误为 unauthorized_clientInvalid client secretdiscovery 和上游授权页正常;token endpoint 上的客户端认证失败。更正 Client Key,然后在新的浏览器会话中重复完整登录。仅创建并不能验证该值。
登录成功,但 ACP 用户名为空。ACP 接受了上游身份并创建了用户。确认 preferred_username 存在,通过 YAML 设置 userNameKey: preferred_username,然后再次登录。
登录失败,并提示缺少 email 或 email_verified授权和 token 交换已完成,但所需 Claims 缺失或类型错误。检查 ID token 和 UserInfo 响应,更正 scopesclaimMapping.email,然后重复登录。
登录失败,并提示 user not a member of allowed groups已读取映射的组 Claim,但没有任何值与 allowedGroups 匹配。确认组值的大小写完全正确,然后更正上游成员关系、claimMapping.groupsallowedGroups
登录成功,但没有关联任何 ACP 组。身份验证和用户映射完成,但没有得到可用的组结果。确认 insecureEnableGroups: true,检查映射的组 Claim 类型和值,并验证 claimMapping.groups
配置的额外 Claim 未出现在 ACP 用户中。登录完成,但 Claim 缺失或与配置的类型不匹配。检查合并后的 ID token 和 UserInfo Claims;对于本文档,请仅对字符串 Claim 使用 type: string
测试登录后,现有用户的 IDP 来源或映射发生变化。回调映射到了一个已经被 ACP 用户使用的 email。停止发布,选择一个不会冲突的测试账户,然后在重试前检查现有 ACP 用户。
ACP 注销后返回 IDP 选择器,但再次选择 OIDC 时无需额外提示即可登录。ACP 会话已结束,而上游会话仍然有效。将 ACP 和上游注销视为两个独立的验收检查;不要依赖未经验证的 Logout URL 字段。
使用 kubectl 删除 Connector custom resource 后,用户仍保持 active。绕过了 ACP product deletion 流程。如有需要,先恢复测试 Connector,然后使用 ACP 控制台操作删除,并选择所需的清理选项。

如需查看回调和用户映射证据,请检查当前 ACP 登录组件日志:

kubectl -n cpaas-system logs deployment/apollo --since=10m