Harbor 用户生命周期实战:管理员在本地数据库(DB)模式下创建、删除与重建用户的完整验证指南
【免费下载链接】harborAn open source trusted cloud native registry project that stores, signs, and scans content.项目地址: https://gitcode.com/GitHub_Trending/ha/harbor
导读
本文以 Harbor 官方用户管理测试用例 1-09 Admin User Create, Delete and Recreate a User (DB Mode) 为核心,系统讲解在 Harbor 使用本地数据库(DB 模式)认证时,管理员如何通过 UI 创建用户、删除用户,并利用相同用户名重新创建用户的全过程。你将掌握用户生命周期管理的完整操作步骤、docker login命令行验证方法,以及 Harbor 用户存储与"软删除 + 用户名改写"机制的源码级实现原理,可用于功能测试、验收与故障排查。
一、测试背景:为什么要在 DB 模式下验证用户管理
Harbor 支持多种认证模式,其中本地数据库模式(DB mode)表示用户账号由 Harbor 自身维护,登录时直接与 Harbor 数据库中的harbor_user表比对。在 src/common/const.go 中,该模式被定义为常量DBAuth = "db_auth"。
本测试用例的目标是验证在 DB 模式下,管理员能否完整走通创建用户 → 用户登录与使用 → 删除用户 → 会话失效 → 重建同名用户的闭环流程,重点确认以下行为:
- 新创建的用户可以正常登录 UI 与 Docker CLI;
- 管理员删除用户后,该用户既有的 Web 会话立即失效(跳转回登录页),也无法再通过
docker login认证; - 删除是可逆复用的——相同用户名可以再次被创建,但新建用户是全新的账号,不再拥有被删用户的任何项目;
- 审计日志中会以"特殊编号"形式残留被删用户的信息,便于追溯。
该用例对应的完整测试步骤与预期结果记录于 tests/testcases/Group1-user-management/1-09-admin-create-delete-user.md。
二、测试环境准备
2.1 前提条件
执行本用例需要满足以下环境要求:
| 前提 | 说明 |
|---|---|
| 运行中的 Harbor 实例 | 已安装并可通过浏览器访问 Harbor UI |
| DB 认证模式 | Harbor 配置为auth_mode: db_auth(本地数据库认证) |
| Linux 主机 | 已安装 Docker CLI(Docker 客户端)用于命令行登录验证 |
| 管理员账号 | 使用内置的admin账号(数据库种子数据中user_id=1的系统管理员) |
2.2 DB 模式确认方式
DB 模式在 Harbor 部署配置文件harbor.yml中通过auth_mode字段指定:
auth_mode: db_auth部署时由make/prepare根据 harbor.yml.tmpl 模板生成实际配置。注意,该模式的创建权限还受self_registration(自助注册)开关影响:在 src/server/v2.0/handler/user.go 的requireCreatable逻辑中,创建用户时若开启了自助注册,普通登录用户也可在 UI 上注册;而管理员创建用户始终被允许(requireCreatable中当self_registration=false时仅校验系统级Create/User权限)。
内置
admin账号与anonymous账号由数据库初始迁移脚本 make/migrations/postgresql/0001_initial_schema.up.sql 写入,其中admin的sysadmin_flag=true,user_id=1,且在用户列表中默认被隐藏(src/pkg/user/manager.go 通过user_id__gt = 1过滤默认管理员)。
三、完整测试步骤(15 步实操)
以下步骤为原测试用例的完整操作序列,建议在测试时严格按序执行。
Step 1:管理员登录 UI
使用内置管理员账号登录 Harbor Web 控制台。登录后进入顶部导航的Administration → Users(系统管理 → 用户)页面,可查看、创建、删除本地用户。
Step 2:管理员创建用户
在 Users 管理页面点击New User,填写用户名(Username)、邮箱(Email)、全名(Realname)、备注(Comment)与密码(Password),提交创建。
密码必须满足 Harbor 的密码强度策略:长度 8~128 位,且同时包含至少 1 个大写字母、1 个小写字母与 1 个数字。该规则实现在 src/server/v2.0/handler/user.go 的requireValidSecret函数中,任何不满足要求的密码都会在创建或改密时被拒绝:
if len(in) >= 8 && len(in) <= 128 && hasLower.MatchString(in) && hasUpper.MatchString(in) && hasNumber.MatchString(in) { return nil }同时,用户名与全名不能包含非法字符(,"~#%$,见 src/common/const.go 的IllegalCharsInUsername),用户名、邮箱不能为空,邮箱需符合格式校验,备注长度不超过 30 个字符(validateUserProfile)。
Step 3:在另一个浏览器登录新用户
使用与管理员不同的浏览器(模拟独立的会话环境,避免共享 Cookie),用 Step 2 创建的账号密码登录 Harbor UI,应能成功登录。
Step 4:新用户查看自己的账户设置
登录后点击右上角用户名进入Account Settings(账户设置),应能看到管理员在 Step 2 填写的邮箱、全名等信息与创建时的输入一致。
Step 5:新用户在 UI 中创建两个项目
新用户通过New Project创建两个项目(项目名需唯一)。普通用户创建项目时需要满足配额与命名规则要求,创建成功后项目所有者即为该用户。
Step 6:Docker CLI 验证用户可登录
在装有 Docker CLI 的 Linux 主机上执行:
docker login <harbor_host>输入 Step 2 创建的用户名与密码。若 Harbor 未配置 HTTPS,需在 Docker 守护进程的/etc/docker/daemon.json中将 Harbor 地址加入insecure-registries。登录成功即代表该用户具备向 Harbor 推送/拉取镜像的凭证能力。
Step 7:管理员删除用户
回到管理员的浏览器,在Administration → Users列表中找到刚创建的用户,执行删除操作。
Step 8:验证被删用户的会话被强制登出
在 Step 3 使用的新用户浏览器中,点击页面上的任意链接(触发一次新的 HTTP 请求)。此时该用户的会话应立即失效,页面被重定向到登录页。
原理说明:Harbor 的认证安全上下文(src/common/security 下的local.SecurityContext)在每次请求时都会基于当前会话对应的用户记录构建。用户被删除后,其数据库记录被标记为deleted=true且用户名/邮箱被改写,后续请求无法再以该用户身份通过认证校验,因此会话表现为被强制登出。这也解释了为什么用例特别强调"点击任意链接"——需要触发一次真实请求才能观察到跳转。
Step 9:Docker CLI 验证被删用户无法登录
回到 Linux 主机,再次执行:
docker login <harbor_host>使用被删除的用户名登录,应提示认证失败(unauthorized: authentication required),确认删除后该用户在客户端侧也无法继续使用。
Step 10:管理员重建同名用户
回到管理员浏览器,在New User中使用与被删除用户完全相同的用户名再次创建用户(密码、邮箱等可重新设置)。此步骤应成功——这正是 Harbor 软删除机制设计的关键点(详见下文源码剖析)。
Step 11:在另一个浏览器登录重建用户
使用新的浏览器(或清除原浏览器 Cookie)以重建的用户登录,应成功登录。
Step 12:重建用户查看账户设置
进入Account Settings,确认看到的是本次重建时填写的资料,而不是被删除用户的旧资料。
Step 13:重建用户查看"我的项目"
进入My Projects页面,应看到项目列表为空——虽然用户名相同,但被删用户在 Step 5 创建的两个项目不再属于新账号。
原理说明:项目通过owner_id(外键)关联harbor_user.user_id(见 make/migrations/postgresql/0001_initial_schema.up.sql)。重建用户的user_id是数据库自增的新值,与被删用户的user_id不同,因此历史项目不会自动转移给新账号。同时,删除用户时 Harbor 会先清理该用户在project_member表中的成员记录(见下文 Controller 层代码),确保成员关系一并解除。
Step 14:Docker CLI 验证重建用户可登录
执行:
docker login <harbor_host>使用重建的用户名密码登录,应成功,确认同名重建后账号完整可用。
Step 15:管理员在日志中确认特殊用户编号
管理员从 UI 的Logs(日志)页面查看审计日志。由于被删除用户曾在 Step 5 创建过两个项目,日志中应出现两条项目创建记录,且记录关联的操作用户显示为带有特殊编号的形式(即被删用户的用户名被改写后残留的用户名#userID格式,详见下文)。
注意:日志显示的具体格式取决于审计记录对用户名的抓取时机,核心验证点是"能看到带编号的用户标识,而不是普通用户名"。
四、预期结果汇总
| 步骤 | 预期结果 |
|---|---|
| Step 3 | 新用户成功登录 UI |
| Step 4 | 新用户看到的账户设置与管理员录入一致 |
| Step 5 | 新用户成功创建两个项目 |
| Step 6 | Docker CLI 登录成功 |
| Step 8 | 被删用户点击任意链接后跳转登录页,会话失效 |
| Step 9 | Docker CLI 登录失败 |
| Step 10 | 管理员可重建同名用户 |
| Step 11~13 | 重建用户可登录,但不再拥有原用户的项目 |
| Step 14 | Docker CLI 登录成功 |
| Step 15 | 日志中出现带特殊用户编号的项目创建记录 |
五、源码级原理剖析:软删除与同名重建的关键机制
5.1 用户数据模型
用户实体定义在 src/common/models/user.go,核心字段包括UserID、Username、Email、Password、PasswordVersion、Salt、Deleted、SysAdminFlag等。
对应的数据库表harbor_user由初始迁移脚本 make/migrations/postgresql/0001_initial_schema.up.sql 创建,其关键设计是:
create table harbor_user ( user_id SERIAL PRIMARY KEY NOT NULL, username varchar(255), email varchar(255), ... deleted boolean DEFAULT false NOT NULL, ... UNIQUE (username), UNIQUE (email) );其中username与email均建有UNIQUE 唯一约束。这意味着同一时刻数据库中不允许存在两条相同的用户名——要让"删除后重建同名用户"成为可能,删除操作就不能简单地物理删除记录,否则唯一约束本身并不妨碍重建(删除后约束自然释放);真正的难点在于 Harbor 采用了软删除策略,删除的记录仍然留在表中。
5.2 删除用户的实现:软删除 + 用户名改写
Manager 层的删除实现位于 src/pkg/user/manager.go:
func (m *manager) Delete(ctx context.Context, id int) error { u, err := m.Get(ctx, id) if err != nil { return err } u.Username = lib.Truncate(u.Username, fmt.Sprintf("#%d", u.UserID), 255) u.Email = lib.Truncate(u.Email, fmt.Sprintf("#%d", u.UserID), 255) u.Deleted = true return m.dao.Update(ctx, u, "username", "email", "deleted") }关键点如下:
- 不是物理删除:删除只把
deleted标记置为true,保留记录用于审计追溯; - 改写用户名与邮箱:将用户名改写为
原名#userID(邮箱同理),从而释放username/email的唯一约束,使得后续可以用相同用户名新建记录而不产生唯一键冲突——这正是 Step 10 重建成功的底层原因; - DAO 层更新:通过 src/pkg/user/dao/dao.go 的
Update仅更新指定列。
需要说明的是,DAO 层接口中虽然也定义了物理删除Delete(ctx, userID)(src/pkg/user/dao/dao.go),但 Manager 层面向用户删除流程使用的是上述软删除路径。
此外,若启用了 GDPR 合规设置,Harbor 还提供DeleteGDPR实现(src/pkg/user/manager.go),它进一步将用户名、邮箱、全名替换为crc32校验和#userID形式,以抹除个人可识别信息(PII)。
5.3 Controller 层的级联清理
删除请求最终进入 src/controller/user/controller.go 的Delete,它会在标记删除之前完成级联清理:
- 通过
memberMgr.DeleteMemberByUserID删除该用户在所有项目中的成员关系(这正是 Step 13 中"新用户不再拥有项目"的辅助原因之一); - 在 OIDC 认证模式下,同步删除该用户的 OIDC 元数据;
- 若配置了 GDPR 审计日志,则触发 GDPR 审计任务;
- 最后根据
gdprSetting.DeleteUser决定走DeleteGDPR还是普通Delete。
5.4 API 层的权限与合法性校验
V2.0 REST API 的创建与删除处理器位于 src/server/v2.0/handler/user.go:
- 创建用户
CreateUser(L80-L104):先经requireCreatable校验必须是 DB 认证模式且具备系统级创建权限,再校验密码强度与用户资料合法性,最后调用ctl.Create落库; - 删除用户
DeleteUser(L183-L193):经requireDeletable(L394-L402)校验——管理员不能删除自己,也不能删除user_id=1的内置管理员,否则返回403 Forbidden。
5.5 创建用户的密码加密
创建用户时,Manager 层通过injectPasswd(src/pkg/user/manager.go)为密码生成随机盐值,并使用PBKDF2-HMAC-SHA256算法(高迭代次数)加密存储,同时记录password_version;登录校验MatchLocalPassword(src/pkg/user/manager.go)是版本感知的,历史版本使用 SHA1/SHA256 哈希的存量用户仍可正常认证。
六、日志验证的补充说明(Step 15)
Step 15 是容易被忽略的审计重点:管理员在 Dashboard 的日志中应看到两条项目创建记录(对应被删用户在 Step 5 创建的两个项目),且记录中操作用户显示为带编号的特殊形式。
这与 5.2 节描述的软删除机制直接相关:被删用户的用户名在删除时已被改写为用户名#userID格式,项目创建日志(在删除前写入)保留的是当时的用户名信息。通过该特殊编号可以追溯到被删用户的user_id,这是 Harbor 软删除保留记录用于审计的直接体现。
七、常见问题排查(实战补充)
原用例的 "Possible Problems" 为 None,但基于源码约束,实际执行中可能出现以下情况,供排查参考:
| 现象 | 原因 | 处理 |
|---|---|---|
| 创建用户提示密码不合法 | 密码不满足 8~128 位且含大小写字母与数字 | 按 requireValidSecret 规则修改密码 |
| 删除时提示 "User cannot be deleted" | 试图删除自己或内置admin(user_id=1) | 换用其他管理员账号执行删除 |
| 非 DB 模式下创建用户被拒 | requireCreatable限制仅 DB 模式允许创建本地用户 | 在 LDAP/OIDC 模式下改用认证目录管理用户 |
| 重建用户后看不到旧项目 | owner_id关联的是旧user_id,且删除时已清理项目成员关系 | 属于预期行为,将旧项目授权给新账号需重新添加成员 |
| 登录被强制登出 | 用户记录已标记deleted=true | 属于预期行为,管理员可重建同名账号 |
八、结语
通过本用例可以确认,Harbor 在 DB 模式下提供了完整、安全的用户生命周期管理能力:创建时强制密码强度与字段合法性校验,删除时采用"软删除 + 用户名/邮箱改写"策略释放唯一约束,从而允许同名用户重建,同时保证旧项目归属与成员关系不随用户名转移,且被删用户的所有会话立即失效。掌握这套流程与背后的 src/pkg/user 源码实现,对于 Harbor 的用户验收测试、日常运维与审计追溯都极具实战价值。
延伸阅读:用户管理相关的更多测试用例可参考 tests/testcases/Group1-user-management 目录;用户相关 REST API 定义见 api/v2.0/swagger.yaml 中/users相关章节;用户管理 API 的实现入口为 src/server/v2.0/handler/user.go。
【免费下载链接】harborAn open source trusted cloud native registry project that stores, signs, and scans content.项目地址: https://gitcode.com/GitHub_Trending/ha/harbor
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考