1. 整体架构
浏览器 → Headlamp → Authentik(登录,返回 id_token)
↓
Headlamp 将 id_token 作为 Bearer Token 发给 k3s kube-apiserver
↓
kube-apiserver 校验 issuer / audience / 签名 → 鉴权(RBAC)
信任链要点:Headlamp 信任 Authentik 并拿到 token,但真正决定登录是否被集群接受的是 kube-apiserver。任何一方的 issuer / client ID / audience 不一致,都会导致 The cluster did not accept your sign-in 错误。
2. Authentik 侧配置
2.1 创建 Provider
后台 → Applications → Providers → Create → 类型选 OAuth2/OpenID Connect。
| 字段 | 值 |
|---|---|
| Name | headlamp |
| Authorization flow | default-provider-authorization-implicit-consent(或显式授权) |
| Client type | Confidential |
| Client ID | 记下来,Headlamp 和 k3s 都要用 |
| Client Secret | 记下来,Headlamp 要用 |
| Redirect URIs | https://<headlamp域名>/oidc-callback(必须与实际访问地址完全一致,含协议/端口/路径) |
| Subject Mode | Based on the User’s Email |
| Signing Key | 自签证书使用 |
| Grant types | 至少包含 authorization_code 和 refresh_token |
| Scope mappings | 包含 openid、profile、email 和 offline_access |
⚠️ 自 authentik 2024.2 起,应用默认只获得 access token。Headlamp 必须请求
offline_access,且Provider 必须关联同名 Scope Mapping,authentik 才会签发 refresh token。否则 token 过期后 Headlamp 会反复报告oauth2: token expired and refresh token is not set,并将集群显示为不健康。


2.2 创建 Application 并绑定 Provider
Applications → Applications → Create:
- Name:
headlamp - Slug:
headlamp - Provider: 选上面创建的 provider
2.3 email_verified 处理(重点)
自 authentik 2025.10 起,默认 scope mapping 中
email_verified固定返回False,不再根据用户邮箱状态自动判断。部分应用要求true才能登录,需通过自定义 Scope Mapping 覆盖。goauthentik.io
创建自定义 Scope Mapping:
路径:Customization → Property Mappings → Create → Scope Mapping
| 字段 | 值 |
|---|---|
| Name | email-verified-override |
| Scope name | 必须是 email(不能自造名字) |
| Description | 任意 |
方案 A:受控用户,硬编码 true(推荐内网/管理员手动开户场景)
return {
"email": request.user.email,
"email_verified": True
}
方案 B:基于用户属性动态判断(Authentik 开放注册时使用)
return {
"email": request.user.email,
"email_verified": request.user.attributes.get("email_verified", False)
}
路径:用户-编辑-高级设置-属性
settings:
locale: ""
email_verified: true
然后点右下角 Save Changes。 token 是登录时签发的,改完用户属性后浏览器里旧的 id_token 依然是 email_verified: false。需要用户退出 Headlamp 重新走一遍 Authentik 登录,然后解码新 token 验证。
⚠️ 方案 B 要求 Authentik 里给用户写入
attributes.email_verified = True。若用户属性从未赋值,token 中仍是false,这本身不会导致 apiserver 拒绝登录,但某些应用侧可能校验该字段。若您用了方案 B 后报”sign-in 被拒绝”,根因几乎不在这一步,而在 issuer / client ID / apiserver 配置,请直接看第 6 节排查表。
将 Mapping 关联到 Provider:
打开 headlamp Provider → Advanced protocol settings → Scope mappings:
- 取消勾选默认的
authentik default OAuth Mapping: OpenID Connect: email - 勾选你刚创建的
email-verified-override

3. k3s apiserver OIDC 配置
3.1 编辑配置文件
编辑 /etc/rancher/k3s/config.yaml(不存在则创建):
kube-apiserver-arg:
- "oidc-issuer-url=https://xxxx.xxxxxxx.cn/application/o/headlamp/"
- "oidc-client-id=<你的client-id>"
- "oidc-username-claim=email"
- "oidc-groups-claim=groups"
# 可选:
# - "oidc-username-prefix=oidc:"
# - "oidc-groups-prefix=oidc:"
# - "oidc-ca-file=/path/to/ca.pem" # Authentik 用自签证书时必须配置
三个关键字段的核对规则:
| 参数 | 必须等于 | 核对方式 |
|---|---|---|
oidc-issuer-url | Authentik discovery JSON 里的 issuer 字段 | curl -s <discovery_url> \| jq -r .issuer |
oidc-client-id | Authentik Provider 的 Client ID,且必须出现在 token 的 aud 中 | 解码 token 查看 aud |
oidc-username-claim | 用于映射 K8s 用户名的 claim,通常 email | 登录后看 token 里 email |
💡 多 server 节点:HA 模式下每台 server 节点的 config.yaml 都必须配置同样的 OIDC 参数。
3.2 重启 k3s 并验证
systemctl restart k3s
journalctl -u k3s -f # 观察启动日志
3.3 Authentik access token 的 aud 坑
kube-apiserver 校验 token 时要求自己的 --oidc-client-id 出现在 token 的 aud 列表里。Authentik 默认的 access token 可能不带正确的 audience,需要在 Provider 的 Advanced protocol settings 里把你的 client_id 加进 Access token 的 Audience 列表。
4. Headlamp Helm 部署
4.1 创建 Secret
kubectl create secret generic oidc -n headlamp \
--from-literal=OIDC_CLIENT_ID="<你的client-id>" \
--from-literal=OIDC_CLIENT_SECRET="<你的client-secret>" \
--from-literal=OIDC_ISSUER_URL="https://auth.xxxxxxx.cn/application/o/headlamp/" \
--from-literal=OIDC_SCOPES="openid,profile,email,offline_access"
⚠️ Key 名必须是大写环境变量风格(
OIDC_CLIENT_ID而非clientID),这是externalSecret模式的约定。Key 拼错会导致 env 静默失败、Pod 照常 Running 但 OIDC 不生效。
4.2 values.yaml
config:
oidc:
secret:
create: false # 必须关,避免覆盖外部 Secret
externalSecret:
enabled: true
name: oidc
hasScopes: true # Secret 里有 OIDC_SCOPES 时开启
callbackURL: "https://<headlamp域名>/oidc-callback"
# usePKCE: true # 建议开启
4.3 部署 / 升级
helm repo add headlamp https://headlamp-k8s.github.io/headlamp/
helm repo update
helm upgrade headlamp headlamp/headlamp \
--reset-values \
-f values.yaml \
-n headlamp --create-namespace
# e️nv 是启动时注入的,改过 Secret 必须重启
kubectl rollout restart deploy/headlamp -n headlamp
kubectl rollout status deploy/headlamp -n headlamp
# 确认新 Pod 进程实际加载了新 scope;只检查 Secret 本身不够
kubectl exec -n headlamp deploy/headlamp -- \
sh -c 'echo "$OIDC_SCOPES"'
# 预期:openid,profile,email,offline_access
修改 OIDC scopes 后,旧浏览器会话不会自动获得 refresh token。必须退出 Headlamp 并重新登录;如果页面已无法正常退出,清除 Headlamp 域名的 Cookie/站点数据,或使用无痕窗口重新登录。
4.4 验证后端已启用 OIDC
# 确认 Pod 拿到了环境变量
kubectl exec -n headlamp deploy/headlamp -- env | grep OIDC
# 访问 OIDC 端点应返回 302 跳转到 Authentik
curl -sI https://<headlamp域名>/oidc
5. RBAC 授权
即使认证通过,没有 RBAC 仍会 403(例如报 nodes.metrics.k8s.io is forbidden: User "xxx@163.com" cannot list resource "nodes"...)。
核心原则:subjects.name 必须与报错信息里显示的用户名逐字符一致。若在 k3s 配置里加了 oidc-username-prefix=oidc:,报错里用户名会带前缀,subject 也要带上。
5.1 管理员用户
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: oidc-admin
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: User
name: xxxx@xx.com # 与报错信息一致
apiGroup: rbac.authorization.k8s.io
5.2 推荐做法:按组授权
依赖 token 里的 groups claim(前提:k3s 配了 oidc-groups-claim=groups 且 Authentik 用户属于该组):
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: oidc-admin-group
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: Group
name: k8s-admins # Authentik 里的组名
apiGroup: rbac.authorization.k8s.io
5.3 补充 metrics.k8s.io 权限(普通只读用户常见坑)
内置 view/edit 角色默认不包含 metrics.k8s.io 的权限:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: metrics-viewer
rules:
- apiGroups: ["metrics.k8s.io"]
resources: ["nodes", "pods"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: zhoumx-metrics
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: metrics-viewer
subjects:
- kind: User
name: zhoumx2024@163.com
apiGroup: rbac.authorization.k8s.io
5.4 验证
# 管理员侧模拟验证
kubectl auth can-i list nodes.metrics.k8s.io --as xxxxxx@xxx.com
# 或用 token 直连 apiserver
kubectl --token=<id_token> --server=https://<k3s>:6443 get pods
6. 常见错误排查表
6.1 The cluster did not accept your sign-in
出现此错误说明 Headlamp 登录已成功,但 apiserver 拒绝 token。与 email_verified 映射无关——那一层校验在应用侧,不在 apiserver 侧。请按下面顺序排查:
| 检查点 | 命令/方法 | 常见原因 |
|---|---|---|
| apiserver 是否启用 OIDC | journalctl -u k3s -f查看是否有oidc相关日志 | config.yaml 没配 / 没重启 / 缩进错 |
| issuer URL 是否一致 | curl -s <discovery_url> \| jq -r .issuer 与 config.yaml 对比 | 末尾斜杠差异 / 用了 .well-known/openid-configuration 完整 URL |
| token aud 是否包含 apiserver 的 client-id | 解码 token(jwt.io / echo <payload> \| base64 -d)看 aud | Authentik Provider 未把 client_id 加进 access token 的 Audience |
| client-id 是否一致 | Authentik Provider Client ID vs k3s oidc-client-id vs Headlamp clientID | 三处必须完全相同 |
| 回调地址 | Authentik Provider 的 Redirect URIs vs Headlamp 实际访问地址 | 协议/域名/端口/路径任一不同即失败 |
| token 是否过期 | 解码看 exp | 重新登录 |
| 自签证书 | journalctl -u k3s 看 apiserver 日志 | 未配 oidc-ca-file |
6.2 Headlamp 不弹 OIDC 登录
| 检查点 | 命令 | 说明 |
|---|---|---|
| Secret key 名 | kubectl get secret oidc -n headlamp -o jsonpath='{.data}' \| jq keys | 必须是 OIDC_CLIENT_ID 等大写形式 |
| Pod 是否拿到 env | kubectl exec -n headlamp deploy/headlamp -- env \| grep OIDC | 空 = 注入失败 |
| 改 Secret 后是否重启 | kubectl rollout restart deploy/headlamp -n headlamp | env 只在启动时注入 |
| Helm values 是否生效 | helm get values <release> -n <ns> | 缩进错 / 旧值残留 |
secret.create 是否关闭 | 同上 | 为 true 会创建空 Secret 覆盖 |
6.3 Cluster main is not healthy / refresh token is not set
典型日志:
refreshing token: oauth2: token expired and refresh token is not set
这通常不是 Gateway 或 kube-apiserver 健康问题,而是 OIDC token 已过期且当前会话没有 refresh token。按以下顺序处理:
- Authentik Provider 添加
offline_accessScope Mapping,并确认允许refresh_tokengrant。 - Headlamp 的
OIDC_SCOPES设置为openid,profile,email,offline_access。 - 修改 Secret 后执行
kubectl rollout restart;Secret 更新不会自动改变运行中容器的环境变量。 - 使用
kubectl exec ... -- sh -c 'echo "$OIDC_SCOPES"'检查 Pod 进程中的实际值。 - 清除旧登录会话并重新完成 OIDC 登录。
手动刷新页面时偶尔出现的 context canceled 或 http: proxy error: context canceled,通常是浏览器取消了进行中的请求,属于次生现象。应优先处理缺失 refresh token 的错误。
6.4 403 Forbidden(认证成功,授权失败)
- 检查 ClusterRoleBinding 的
subjects.name是否与报错里的用户名一致 - 是否用了
ClusterRoleBinding(集群级资源必须用它) - 访问
metrics.k8s.io需要单独授权
6.5 email_verified 相关
| 现象 | 原因 | 处理 |
|---|---|---|
token 里 email_verified: false | Authentik 2025.10+ 默认改为 Falsegoauthentik.io | 按第 2.3 节创建自定义 Scope Mapping |
| 自定义 mapping 未生效 | Scope name 不是 email / 未在 Provider 里勾选 | Scope name 必须叫 email;替换默认 mapping |
| 用户属性改了但 token 仍 false | token 由 scope mapping 决定,与用户属性字段无关 | 用 request.user.attributes.get(...) 读取 |
| 换成动态 mapping 后登录报”sign-in 被拒绝” | 几乎与 email_verified 无关 | 按第 6.1 节排查 issuer / client-id / apiserver 配置 |
7. 关键参数速查表
7.1 三处必须完全一致的参数
| 参数 | Authentik 位置 | Headlamp 位置 | k3s 位置 |
|---|---|---|---|
| Issuer URL | Discovery JSON 里的 issuer 字段 | issuerURL(Secret 里为 OIDC_ISSUER_URL) | oidc-issuer-url |
| Client ID | Provider 的 Client ID | clientID(Secret 里为 OIDC_CLIENT_ID) | oidc-client-id |
| Callback URL | Provider 的 Redirect URIs | callbackURL | — |
7.2 关键路径
| 项目 | 路径 |
|---|---|
| k3s 配置 | /etc/rancher/k3s/config.yaml |
| Headlamp Discovery | https://xxx.xxxxxxx.cn/application/o/headlamp/.well-known/openid-configuration |
| OpenID Configuration Issuer | https://xxx.xxxxxxx.cn/application/o/headlamp/ |
| Headlamp Callback | https://<headlamp域名>/oidc-callback |
| k3s kubeconfig | /etc/rancher/k3s/k3s.yaml |
7.3 常用命令
# k3s
journalctl -u k3s -f
systemctl restart k3s
# Headlamp
kubectl exec -n headlamp deploy/headlamp -- env | grep OIDC
kubectl rollout restart deploy/headlamp -n headlamp
helm get values headlamp -n headlamp
helm get manifest headlamp -n headlamp | grep -i oidc
# 验证 token
curl -sI https://<headlamp>/oidc # 应返回 200
kubectl --token=<id_token> --server=https://<k3s>:6443 get pods
# RBAC 验证
kubectl auth can-i list nodes.metrics.k8s.io --as xxxxxxxx@xx.com
附录:推荐配置顺序
- Authentik:创建 Provider + Application + 自定义 email Scope Mapping
- Authentik:把 access token 的 Audience 加上 client_id
- k3s:改 config.yaml 加
kube-apiserver-arg,重启,确认 apiserver 进程参数生效 - 集群:创建 RBAC(ClusterRoleBinding)
- Headlamp:创建 Secret(大写 key)→ Helm 部署 → rollout restart
- 浏览器无痕窗口登录验证
- 如遇错误,按第 6 节排查表逐项对照
