主题
03 · Harvester Dashboard 凭据与首登认证(v1.8.2 实测)
本文解决一个具体问题:Harvester ISO 无人值守装完后,Dashboard 的 admin 密码从哪来、 为什么"密码正确却 401"、如何让部署全程真正无人值守。 全部结论均为 2026-09-06 在该 3 节点集群上的实测结果,配套脚本
deploy/scripts/75-set-admin-password.sh。
1. 结论速览(TL;DR)
| 问题 | 实测答案 |
|---|---|
| 装机配置能设 dashboard 密码吗? | 不能。v1.8.2 的 harvester-installer 里没有 admin_password/admin_token 字段(二进制字符串统计为 0 次) |
| 全新集群的初始凭据是什么? | 用户 admin / 密码 xxx,来自 cattle-system/bootstrap-secret 的 bootstrapPassword |
| 初始状态下能直接用 API 吗? | 不能。mustChangePassword=xxx 时登录虽返回 201 并发 token,但该 token 对 /v1、/v3、/apis` 全部 401 |
| 怎么破? | 先调 POST /v3/users?action=changepassword 改密,之后一切正常 |
配置里的 os.password 是什么? | OS / SSH(rancher 用户)密码,与 dashboard 无关 |
配置里的 token 是什么? | RKE2 集群加入令牌(node02/03 join 用),不是 Dashboard API token |
| 自动化怎么做? | 75-set-admin-password.sh(幂等),已挂进 65-deploy-all.sh 在整体验证前执行 |
| dashboard 密码与 SSH 密码相同吗? | ★ 故意不同:dashboard xxx vs SSH/OS xxx。曾同值,导致把 SSH 密码当 dashboard 密码而反复登录失败(xxx 案例 28) |
浏览器 401 但 curl 能登录? | 多为客户端问题:输入法把 @ 打成全角 @、自动填充了旧密码、Cookie 残留 → 先用无痕窗口重试 |
| 当前线上生效的是哪个? | \1xxx\2(2026-09-06 10:15 复核:登录 201、/v3/users?me=true 200、mustChangePassword=xxx 与 xxx` 均已 401) |
2. 为什么装机配置设不了密码(证据)
2.1 官方文档核对
对照 Harvester v1.8《Harvester Configuration》与《PXE Boot Installation》, config.yaml 的合法字段里不存在任何 dashboard 管理员密码项:
| 字段 | 语义 |
|---|---|
os.password | OS 用户(rancher)密码,即 SSH 密码 |
token | RKE2 集群加入令牌,三节点必须一致 |
server_url | join 节点指向的集群 API 地址(VIP) |
install.* / system_settings.* | 分区、网络、VIP、系统设置 |
2.2 二进制层面验证(不依赖文档)
从 rootfs.squashfs 抽出 usr/bin/harvester-installer,用 strings 统计关键字出现次数:
| 关键字 | 出现次数 | 说明 |
|---|---|---|
scheme_version | 1 | ★ 对照组:证明该二进制确实解析装机配置,统计方法有效 |
admin_password | 0 | 无此配置能力 |
admin_token | 0 | 无此配置能力 |
server_url | 0 | 由其他组件处理(不在 installer 内) |
💡 方法要点:做"不存在"的证明时必须设对照组。若只统计
admin_password=xxx 无法排除"抽错了文件/统计方法失效";scheme_version=1` 恰好排除了这种可能。
2.3 结论
dashboard 密码只能在集群起来之后由外部设置。这不是配置写错,而是产品形态决定的: Harvester 的认证栈复用 Rancher 的 first-login 引导流程。
3. 全新集群的初始认证状态(实测)
bash
K='sudo /var/lib/rancher/rke2/bin/kubectl --kubeconfig /etc/rancher/rke2/rke2.yaml'
ssh rancher@192.168.150.11 "$K -n cattle-system get secret bootstrap-secret \
-o jsonpath={.data.bootstrapPassword} | base64 -d" # → admin
ssh rancher@192.168.150.11 "$K get settings.management.cattle.io first-login \
-o jsonpath={.value}" # → trueadmin 用户对象的关键字段(GET /v3/users?me=true):
| 字段 | 初始值 | 含义 |
|---|---|---|
mustChangePassword | true | ★ 必须改密,否则 API 不可用 |
enabled | true | 账号已启用 |
username | admin | — |
firstLogin | true | 从未登录过 |
4. ★ "密码正确却 401" 的完整排错过程
这是本次最容易走弯路的一段,按实际顺序复盘(每一步都是实测)。
步骤 1:按凭据文件登录 → 401
bash
curl -sk -X POST https://192.168.150.200/v3-public/localProviders/local?action=login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"xxx","ttl":3600000}'
# → HTTP 401 authentication failedconfigs/credentials.txt 里明明写着这个密码 → 说明该密码当时并未被设进集群 (它只是装机配置里的 os.password,见 02 案例 22)。
步骤 2:换引导密码 → 登录"成功",但埋着雷
bash
… -d '{"username":"admin","password":"admin","ttl":3600000}'
# → HTTP 201 Created
{
"id": "token-9p5vr",
"token": "token-9p5vr:frxb6…",
"mustChangePassword": true
}注意是 201(不是 200),且响应体里带 `mustChangePassword: xxx
步骤 3:拿 token 调业务 API → 全部 401
bash
T='token-9p5vr:frxb6…'
curl -sk -H "Authorization: Bearer $T" https://192.168.150.200/v3/users?me=true
# → 401 {"baseType":"error","code":"Unauthorized","message":"must authenticate"}/v1、/v3、/apis 无一例外。这就是"`mustChangePassword=xxx 期间 token 不可用"。
步骤 4:坑一 —— .id 不是 Bearer 值
登录响应里有两个像 token 的字段:
| 字段 | 样例 | 能否当 Bearer |
|---|---|---|
.id | token-9p5vr | ❌ 只是 token 名,用它请求必然 401 |
.token | token-9p5vr:frxb6… | ✅ 真正的凭据,格式是 <名>:<密钥> |
脚本里统一用 jq -r '.token // empty' 提取,不要用 .id。
步骤 5:坑二 —— changepassword 必须 POST
bash
# ❌ PUT → 405 MethodNotAllow
curl -sk -X PUT -H "Authorization: Bearer $T" "$API/v3/users?action=changepassword" …
# ✅ POST → 200
curl -sk -X POST -H "Authorization: Bearer $T" -H 'Content-Type: application/json' \
--data '{"currentPassword":"admin","newPassword":"xxx"}' \
"$API/v3/users?action=changepassword"上面是当时的首次改密实录(引导密码
xxx→xxx)。 该值后来因案例 28 被改为xxx,二次改密方法见 §9。 抄命令时请只抄结构(POST +currentPassword/newPassword),密码值以configs/credentials.txt与50-gen-configs.sh的DASHBOARD_PW为准。
changepassword 是 Rancher API 的 collectionAction,规定用 POST; 用 PUT 会得到 405 MethodNotAllow。
步骤 6:改密后复核(三项都要查)
| 复核项 | 期望 |
|---|---|
| 新密码能登录 | xxx 返回非空 token |
mustChangePassword | false |
| 旧(引导)密码 | 已失效(仍能登录即为异常) |
三项全绿,UI 与 API 才真正可用。
5. 修复:75-set-admin-password.sh(幂等)
5.1 逻辑
① curl $API/ping 探活 → 不可达则 exit 1
② login admin <新密码> → 成功即"已是目标值",短路 exit 0(★ 幂等)
③ login admin <引导密码> → 失败则报错,并打印"如何手工核对 bootstrap-secret"
④ POST /v3/users?action=changepassword → 非 200 打印响应前 300 字节并 exit 1
⑤ 复核三项:新密码可登录 / mustChangePassword / 旧密码是否失效
⑥ 打印最终凭据行5.2 用法与可调参数
bash
bash scripts/75-set-admin-password.sh # 默认新密码 xxx
bash scripts/75-set-admin-password.sh 'MyNewPass@2026' # 指定新密码(第 1 个位置参数)
VIP=192.168.150.100 bash scripts/75-set-admin-password.sh # 覆盖 VIP
BOOT_PW=<当前密码> bash scripts/75-set-admin-password.sh <新密码> # ★ 二次改密(见 §9)| 参数 | 默认值 | 说明 |
|---|---|---|
$1 | xxx | 目标新密码,即 xxx 的 DASHBOARD_PW |
VIP | 192.168.150.200 | 管理 VIP |
BOOT_PW | admin | "当前密码":首登时是引导密码 xxx;二次改密时要传现有 dashboard 密码 |
★ 注意
50-gen-configs.sh里有两个密码变量,刻意不同值:PASSWORD='xxx'(写进装机配置os.password,即 OS/SSH 的rancher用户密码) 与DASHBOARD_PW='xxx'(dashboard admin 密码,由本脚本设定)。 原因见02案例 28。
依赖:curl、jq。退出码:0 = 成功或已幂等短路; 1 = 不可达 / 两种密码都登不上 / 改密失败 / 改密后仍登不上。
临时文件用 mktemp -d + trap … EXIT 清理,密码不落盘残留。
5.3 实测输出
首次执行(真的改密):
已用引导密码登录, 正在改密...
新密码登录: OK mustChangePassword: xxx 引导密码: xxx
dashboard: https://192.168.150.200/ user: admin password: xxx重复执行(幂等短路,退出码 0):
admin 密码已是目标值, 无需修改(登录验证通过)
dashboard: https://192.168.150.200/ user: admin password: xxx上面两次输出中的密码值随
xxx默认值变化:本轮部署时曾是xxx, 后因案例 28 改为xxx并重新执行了改密。
5.4 集成点:挂在整体验证之前
65-deploy-all.sh 末尾:
bash
# dashboard admin 密码无法由装机配置设定(v1.8.2 的 harvester-installer 里没有 admin_password
# 字段), 全新集群初始是 admin/admin 且 mustChangePassword=xxx 此时 API token 对 /v1 /v3
# 全部 401。故在这里自动完成首登改密, 详见 75-set-admin-password.sh 头部说明。
log "=== set dashboard admin password =xxx"
bash scripts/75-set-admin-password.sh \
|| log "WARN: 设定 admin 密码失败, 可稍后手工重跑: bash scripts/75-set-admin-password.sh"
log "=== final verification ==="
bash scripts/70-verify.sh| 设计决策 | 理由 |
|---|---|
放在 70-verify.sh 之前 | 只有改密完成后 dashboard 才真正可用,这样"验证通过"≈"可以登录使用" |
| 失败只 WARN 不中断 | 改密失败不影响集群健康(节点/etcd/存储均正常),属可事后单独修复的收尾动作 |
| 幂等短路 | 允许随时重跑 65-deploy-all.sh 或单独重跑本脚本,不会把已改好的密码又改一遍 |
6. 凭据文件纠错(02 案例 22 的落地)
6.1 修改前 vs 修改后
| 行 | 修改前(错误) | 修改后(正确) |
|---|---|---|
| 密码归属 | xxxpassword : xxx + 说明"由 75-set-admin-password.sh 设定;实测登录 201、`mustChangePassword=xxx 200、旧密码立即 401" | |
| 两类密码分离 | 同一个值既当 SSH 密码又当 dashboard 密码 | ★ 拆成两个不同值并显式写明"dashboard 密码与 ssh 密码不是同一个(故意分开)",避免误用(02 案例 28) |
| token 归属 | `token : xxx | **`rke2 token: xxx + "这是 RKE2 集群加入令牌,不是 dashboard API token;API token 需在 UI 的 Account & API Keys 创建" |
| 首登流程 | 无 | 新增 首登说明 与 引导密码xxxadmin/admin 来源、mustChangePassword 期间 token 全 401、.id vs .token、changepassword 必须 POST、引导密码已失效 |
| 浏览器 401 | 无 | 新增排障提示:浏览器 401 而 API 正常多为客户端问题(全角 @、自动填充旧密码、Cookie 残留)→ 先用无痕窗口重试 |
| 节点角色 | nodes : harvester-01 (create) / harvester-02,03 (join) | 追加"三节点均已由 harvester-promote-node-controller 自动提升为 control-plane,etcd(etcd 成员 3/3,具备 HA quorum);提升期间会短暂显示 Ready,SchedulingDisabled" |
| SSH 密码 | 混在同一行,语义不清 | ssh : rancher@…11..13 (key: …, password: xxx os.password` 就是 SSH 密码 |
6.2 同步修掉生成器
只改现存文件不够——50-gen-configs.sh 会在下次部署时重新覆盖它。 所以生成器的凭据块也一并重写(见 deploy/scripts/50-gen-configs.sh 第 91 行起), 新增 首登说明 与 rke2 token 两段。
实测结果:configs/credentials.txt(权限 600)当前内容:
Harvester v1.8.2 cluster credentials (generated 2026-09-05 20:36:29; 密码状态更新于 2026-09-06)
dashboard : https://192.168.150.200/ (user: admin)
password : xxx <-- 2026-09-06 重置(原值 xxx 已失效)
由 scripts/75-set-admin-password.sh 设定(65-deploy-all.sh 会自动调用)。
改密方式: 先用旧密码登录取 token, 再 POST /v3/users?action=changepassword
{currentPassword,newPassword}; 实测返回 200, 新密码登录 201 +
mustChangePassword=xxx + /v1/harvester/nodes 200, 旧密码立即 401。
注意: dashboard 密码与下面的 ssh 密码**不是同一个**(故意分开, 见下)。
若浏览器登录报 401 而 API 登录正常, 多为客户端问题: 输入法把 @ 打成
全角 @、自动填充了旧密码、Cookie 残留 —— 先用无痕窗口重试。
引导密码 : xxx 已失效(401)。
首登说明 : 装机配置**不支持** dashboard 密码字段(v1.8.2 harvester-installer 内无
admin_password/admin_token 字符串), 全新集群初始为 admin/admin …
另注: 登录响应中 .id 只是 token 名, 可用 Bearer 是 .token 字段(name:secret);
changepassword 为 collectionAction, 必须 POST(PUT 会 405)。
rke2 token: xxx
这是 RKE2 集群加入令牌(node02/03 join 用), **不是** dashboard API token。
ssh : rancher@192.168.150.11..13 (key: …/id_rsa, password: xxx
nodes : harvester-01 (create) / harvester-02,03 (join)
三节点均已由 harvester-promote-node-controller 自动提升为 control-plane,etcd
iface : enp1s0 os disk: /dev/vda data disk: /dev/vdb⚠️ 已知残留:
logs/deploy-all.log末尾仍是旧版凭据块(08:44 那次运行由旧生成器输出), 下次部署会自动刷新。看凭据请以configs/credentials.txt为准,不要看日志尾部。
7. 浏览器登录验证(最终状态)
| 项 | 值 |
|---|---|
| URL | https://192.168.150.200/ |
| 证书 | 自签,浏览器需点"继续前往" |
| 用户名 | admin |
| 密码 | xxx |
| 结果 | ✅ 直接进入 Dashboard,不再弹强制改密页 |
命令行等价验证(可作为自动化断言,2026-09-06 10:15 实测):
bash
curl -sk -o /dev/null -w '%{http_code}\n' https://192.168.150.200/ping # 200
bash scripts/75-set-admin-password.sh; echo "rc=$?" # rc=0(短路)
# 三态判定:哪个密码当前有效
API=https://192.168.150.200
for pw in 'xxx' 'xxx' 'admin'; do
printf '%-18s -> %s\n' "$pw" "$(curl -sk -m 10 -o /dev/null -w '%{http_code}' \
-X POST "$API/v3-public/localProviders/local?action=login" \
-H 'Content-Type: application/json' \
-d "$(jq -n --arg u admin --arg p "$pw" '{username:$u,password:$p,ttl:3600000}')")"
done
# 实测:xxx → 201 ;xxx → 401 ;admin → 4017.1 ★ 浏览器 401 但 API 登录正常 → 是客户端问题
实测遇到过的三个原因(按出现频率):
| 原因 | 特征 | 处置 |
|---|---|---|
输入法把 @ 打成全角 @ | 密码看起来一模一样 | 切英文输入法重打;或粘贴 |
| 浏览器自动填充了旧密码 | 换密码后立刻出现 | 清空该站点的已存密码后重试 |
| Cookie / 会话残留 | 之前 401 过,之后一直 401 | ★ 先用无痕窗口重试——这是最快的判别手段 |
判别口径:curl 能拿到 201 而浏览器 401 ⇒ 服务端凭据是对的,问题在客户端。 反之若 curl 也 401,才是密码/账号问题,走 §9 改密。
8. FAQ
Q1:能不能在装机前就把 dashboard 密码设好? 不能(§2 已证明)。只能装完后调 API,这就是 75 号脚本存在的唯一理由。
Q2:`mustChangePassword=xxx 时,UI 能用吗? UI 会强制跳到改密页;API 一律 401。所以自动化流程必须先改密。
Q3:改密会影响 SSH 登录吗? 不会。dashboard 密码与 xxx(SSH 密码)是两套独立凭据, 本方案只是把它们设成同一个值便于记忆。
Q4:怎么拿到长期可用的 API token? 改密后在 UI 的 Account & API Keys 创建;或用 /v3/token 接口。 装机配置里的 token 与它毫无关系(那是 RKE2 join token)。
Q5:bootstrap-secret 里的密码一定是 xxx 吗? 本次实测是。但不要硬编码假设——75 号脚本支持 BOOT_PW 覆盖, 且登录失败时会打印核对 bootstrap-secret 的完整命令。
Q6:脚本重复执行会不会把密码改坏? 不会。步骤 ② 先用目标密码尝试登录,成功就短路退出(实测 rc=0)。
Q7:为什么 dashboard 密码和 SSH 密码要设成不同的值? 因为同值会诱导误用。本次实测:两者都曾是 xxx,使用者把 SSH 密码 当成 dashboard 密码反复登录失败,浪费了大量排查时间(02 案例 28)。 拆成 xxx(dashboard)与 xxx(SSH/OS)后, "登录失败"立刻能定位到"用错了哪一类密码"。
Q8:浏览器一直 401,但脚本说密码是对的? 见 §7.1——先用无痕窗口重试,排除全角 @、自动填充、Cookie 残留三类客户端问题。
Q9:引导密码 xxx 已失效,还能再改密码吗? 能,见 §9。BOOT_PW 的语义是"当前密码",不是字面上的引导密码。
9. 二次改密(引导密码已失效后)
75-set-admin-password.sh 的 BOOT_PW 变量语义是"当前可用密码"。 首次部署时它是引导密码 xxx;一旦改过密,就要传现有的 dashboard 密码:
bash
# 从 xxx 改成 NewPass@2027
BOOT_PW='xxx' bash scripts/75-set-admin-password.sh 'NewPass@2027'脚本内部走的是同一条路径(用 BOOT_PW 登录取 .token → POST changepassword 带 currentPassword=$BOOT_PW),因此幂等性与三项复核完全一致。
改完记得同步三处,否则下次又会出现"文档与实际不符":
| 要同步的地方 | 怎么改 |
|---|---|
50-gen-configs.sh 的 DASHBOARD_PW | 改成新值(否则下次部署又设回旧值) |
configs/credentials.txt | 重跑 50-gen-configs.sh 会自动重写;或手工更新并保留 600 权限 |
| 本目录文档中的凭据段 | README.md §6、deploy/README.md §10 |
验证(改密后必须三项全过):
bash
API=https://192.168.150.200
T=$(curl -sk -X POST "$API/v3-public/localProviders/local?action=login" \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"NewPass@2027","ttl":3600000}' | jq -r '.token // empty')
[ -n "$T" ] && echo "① 新密码登录 OK"
curl -sk -o /dev/null -w '② /v3/users?me=true -> %{http_code}\n' \
-H "Authorization: Bearer $T" "$API/v3/users?me=true" # 期望 200
curl -sk -o /dev/null -w '③ 旧密码 -> %{http_code}\n' -X POST \
"$API/v3-public/localProviders/local?action=login" -H 'Content-Type: application/json' \
-d '{"username":"admin","password":"xxx","ttl":3600000}' # 期望 401本轮实测记录(2026-09-06,从 xxx 改为 xxx):
| 检查 | 结果 |
|---|---|
changepassword 返回 | 200 |
| 新密码登录 | 201 |
mustChangePassword | false |
/v1/harvester/nodes(业务 API) | 200 |
| 旧密码 | 立即 401 |
引导密码 xxx | 401 |