Skip to content

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-secretbootstrapPassword
初始状态下能直接用 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 200mustChangePassword=xxx 与 xxx` 均已 401

2. 为什么装机配置设不了密码(证据)

2.1 官方文档核对

对照 Harvester v1.8《Harvester Configuration》与《PXE Boot Installation》, config.yaml 的合法字段里不存在任何 dashboard 管理员密码项:

字段语义
os.passwordOS 用户(rancher)密码,即 SSH 密码
tokenRKE2 集群加入令牌,三节点必须一致
server_urljoin 节点指向的集群 API 地址(VIP)
install.* / system_settings.*分区、网络、VIP、系统设置

2.2 二进制层面验证(不依赖文档)

rootfs.squashfs 抽出 usr/bin/harvester-installer,用 strings 统计关键字出现次数:

关键字出现次数说明
scheme_version1对照组:证明该二进制确实解析装机配置,统计方法有效
admin_password0无此配置能力
admin_token0无此配置能力
server_url0由其他组件处理(不在 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}"                                      # → true

admin 用户对象的关键字段(GET /v3/users?me=true):

字段初始值含义
mustChangePasswordtrue★ 必须改密,否则 API 不可用
enabledtrue账号已启用
usernameadmin
firstLogintrue从未登录过

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 failed

configs/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
.idtoken-9p5vr❌ 只是 token 名,用它请求必然 401
.tokentoken-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"

上面是当时的首次改密实录(引导密码 xxxxxx)。 该值后来因案例 28 被改为 xxx,二次改密方法见 §9。 抄命令时请只抄结构(POST + currentPassword/newPassword),密码值以 configs/credentials.txt50-gen-configs.shDASHBOARD_PW 为准。

changepassword 是 Rancher API 的 collectionAction,规定用 POST; 用 PUT 会得到 405 MethodNotAllow

步骤 6:改密后复核(三项都要查)

复核项期望
新密码能登录xxx 返回非空 token
mustChangePasswordfalse
旧(引导)密码已失效(仍能登录即为异常)

三项全绿,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)
参数默认值说明
$1xxx目标新密码,即 xxxDASHBOARD_PW
VIP192.168.150.200管理 VIP
BOOT_PWadmin"当前密码":首登时是引导密码 xxx二次改密时要传现有 dashboard 密码

★ 注意 50-gen-configs.sh 里有两个密码变量,刻意不同值: PASSWORD='xxx'(写进装机配置 os.password,即 OS/SSHrancher 用户密码) 与 DASHBOARD_PW='xxx'dashboard admin 密码,由本脚本设定)。 原因见 02 案例 28。

依赖:curljq。退出码: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 .tokenchangepassword 必须 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. 浏览器登录验证(最终状态)

URLhttps://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 → 401

7.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.shBOOT_PW 变量语义是"当前可用密码"。 首次部署时它是引导密码 xxx;一旦改过密,就要传现有的 dashboard 密码

bash
# 从 xxx 改成 NewPass@2027
BOOT_PW='xxx' bash scripts/75-set-admin-password.sh 'NewPass@2027'

脚本内部走的是同一条路径(用 BOOT_PW 登录取 .tokenPOST changepasswordcurrentPassword=$BOOT_PW),因此幂等性与三项复核完全一致

改完记得同步三处,否则下次又会出现"文档与实际不符":

要同步的地方怎么改
50-gen-configs.shDASHBOARD_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
mustChangePasswordfalse
/v1/harvester/nodes(业务 API)200
旧密码立即 401
引导密码 xxx401