K3s + Authentik + Headlamp OIDC 集成配置手册

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

字段
Nameheadlamp
Authorization flowdefault-provider-authorization-implicit-consent(或显式授权)
Client typeConfidential
Client ID记下来,Headlamp 和 k3s 都要用
Client Secret记下来,Headlamp 要用
Redirect URIshttps://<headlamp域名>/oidc-callback必须与实际访问地址完全一致,含协议/端口/路径)
Subject ModeBased on the User’s Email
Signing Key自签证书使用
Grant types至少包含 authorization_coderefresh_token
Scope mappings包含 openidprofileemailoffline_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,并将集群显示为不健康。

K3s + Authentik + Headlamp OIDC 集成配置手册
K3s + Authentik + Headlamp OIDC 集成配置手册

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

字段
Nameemail-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
K3s + Authentik + Headlamp OIDC 集成配置手册

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-urlAuthentik discovery JSON 里的 issuer 字段curl -s <discovery_url> \| jq -r .issuer
oidc-client-idAuthentik 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 是否启用 OIDCjournalctl -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)看 audAuthentik 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 是否拿到 envkubectl exec -n headlamp deploy/headlamp -- env \| grep OIDC空 = 注入失败
改 Secret 后是否重启kubectl rollout restart deploy/headlamp -n headlampenv 只在启动时注入
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。按以下顺序处理:

  1. Authentik Provider 添加 offline_access Scope Mapping,并确认允许 refresh_token grant。
  2. Headlamp 的 OIDC_SCOPES 设置为 openid,profile,email,offline_access
  3. 修改 Secret 后执行 kubectl rollout restart;Secret 更新不会自动改变运行中容器的环境变量。
  4. 使用 kubectl exec ... -- sh -c 'echo "$OIDC_SCOPES"' 检查 Pod 进程中的实际值
  5. 清除旧登录会话并重新完成 OIDC 登录。

手动刷新页面时偶尔出现的 context canceledhttp: proxy error: context canceled,通常是浏览器取消了进行中的请求,属于次生现象。应优先处理缺失 refresh token 的错误。

6.4 403 Forbidden(认证成功,授权失败)

  • 检查 ClusterRoleBinding 的 subjects.name 是否与报错里的用户名一致
  • 是否用了 ClusterRoleBinding(集群级资源必须用它)
  • 访问 metrics.k8s.io 需要单独授权

6.5 email_verified 相关

现象原因处理
token 里 email_verified: falseAuthentik 2025.10+ 默认改为 Falsegoauthentik.io按第 2.3 节创建自定义 Scope Mapping
自定义 mapping 未生效Scope name 不是 email / 未在 Provider 里勾选Scope name 必须叫 email;替换默认 mapping
用户属性改了但 token 仍 falsetoken 由 scope mapping 决定,与用户属性字段无关request.user.attributes.get(...) 读取
换成动态 mapping 后登录报”sign-in 被拒绝”几乎与 email_verified 无关按第 6.1 节排查 issuer / client-id / apiserver 配置

7. 关键参数速查表

7.1 三处必须完全一致的参数

参数Authentik 位置Headlamp 位置k3s 位置
Issuer URLDiscovery JSON 里的 issuer 字段issuerURL(Secret 里为 OIDC_ISSUER_URLoidc-issuer-url
Client IDProvider 的 Client IDclientID(Secret 里为 OIDC_CLIENT_IDoidc-client-id
Callback URLProvider 的 Redirect URIscallbackURL

7.2 关键路径

项目路径
k3s 配置/etc/rancher/k3s/config.yaml
Headlamp Discoveryhttps://xxx.xxxxxxx.cn/application/o/headlamp/.well-known/openid-configuration
OpenID Configuration Issuerhttps://xxx.xxxxxxx.cn/application/o/headlamp/
Headlamp Callbackhttps://<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

附录:推荐配置顺序

  1. Authentik:创建 Provider + Application + 自定义 email Scope Mapping
  2. Authentik:把 access token 的 Audience 加上 client_id
  3. k3s:改 config.yaml 加 kube-apiserver-arg,重启,确认 apiserver 进程参数生效
  4. 集群:创建 RBAC(ClusterRoleBinding)
  5. Headlamp:创建 Secret(大写 key)→ Helm 部署 → rollout restart
  6. 浏览器无痕窗口登录验证
  7. 如遇错误,按第 6 节排查表逐项对照
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

给TA打赏
共{{data.count}}人
人已打赏
LinuxOps工具

Gitlab自定义机器人

2025-4-3 11:56:35

Kubernetes云原生

eggo部署K8S - openEuler Kubernetes自动化部署工具完整指南

2025-8-4 16:29:24

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索
本站支持IPv6访问