Harbor 镜像删除权限验证:Admin 用户删除他人项目镜像(DB 认证模式)实战与实现原理
2026/9/10 1:08:20 网站建设 项目流程

Harbor 镜像删除权限验证:Admin 用户删除他人项目镜像(DB 认证模式)实战与实现原理

【免费下载链接】harborAn open source trusted cloud native registry project that stores, signs, and scans content.项目地址: https://gitcode.com/GitHub_Trending/ha/harbor

导读

本文基于 Harbor 仓库中的系统测试用例 tests/testcases/Group2-image-management/2-23-admin-delete-images.md,完整还原"在本地数据库认证模式(auth_mode: db_auth)下,管理员(admin)删除普通用户所属项目镜像"的验证流程。文章既给出可直接落地的 UI 操作步骤与 Docker CLI 验证命令,也深入 Harbor 源码,剖析删除动作背后的 RBAC 权限判定、仓储(repository)删除、制品(artifact)级联删除等核心链路,帮助你理解"管理员为何能删、删了什么、删完客户端为何拉取失败"这三个关键问题。


一、测试场景背景

1.1 为什么需要验证"管理员删除他人镜像"

Harbor 是开源的企业级云原生制品仓库,支持多租户项目隔离。在真实生产环境中,镜像的所有权往往与项目成员角色绑定,但管理员账号承担平台级运维职责,需要能够管理、清理所有项目下的资源,包括删除普通用户上传的镜像。

该用例的核心断言有两个:

  • 管理员可以删除普通用户项目中的镜像;
  • 镜像被删除后,Docker 客户端拉取该镜像会失败。

这组断言验证的不仅是删除功能本身,更是 Harbor管理员的系统级权限是否能够覆盖项目级资源这一 RBAC 关键行为。

1.2 测试环境前提

原文明确了以下环境约束,缺一不可:

前提说明
Harbor 实例可用测试需要一个正在运行且可访问的 Harbor 实例
认证模式为本地数据库配置项auth_mode必须设置为db_auth,用户数据存储在本地数据库中
装有 Docker CLI 的 Linux 主机需要一台安装了 Docker 客户端的主机用于 push/pull 操作
至少一个非管理员用户用于创建项目并上传镜像,验证"跨用户删除"场景

auth_mode在安装配置模板 make/harbor.yml.tmpl 中定义,用户数据由 Harbor core 服务写入本地 PostgreSQL 数据库(相关 DAO 实现见 src/common/dao/base.go)。当认证模式为db_auth时,用户的创建、登录校验、权限授予全部由 Harbor 自身完成,管理员账号与普通用户账号共存于同一套本地用户体系,这使得"管理员删除普通用户资源"的权限测试成为可能。


二、完整测试步骤(可直接复现)

原文提示:下述步骤中,用户 A 为非管理员用户,"用户 A"和"项目 X"在真实执行时应替换为更有意义的长名称,例如alice用户与demo-images项目。

2.1 准备阶段:普通用户创建项目并上传镜像

  1. 以用户 A(非管理员)身份登录 Harbor UI。此时该用户没有任何项目级资源,先验证其具备创建项目的权限。
  2. 创建项目 X。项目创建者自动成为该项目的项目管理员(Project Admin),拥有项目内的完整管理权限(详见下文 RBAC 分析)。
  3. 在 Docker 客户端上以用户 A 身份登录 Harbor,并向项目 X 推送第一个镜像:
docker login <harbor-host> docker tag <local-image> <harbor-host>/projectX/myimage:v1 docker push <harbor-host>/projectX/myimage:v1
  1. 推送第二个不同名称的镜像到项目 X:
docker tag <local-image> <harbor-host>/projectX/newimage:v1 docker push <harbor-host>/projectX/newimage:v1
  1. 验证镜像可正常拉取
docker pull <harbor-host>/projectX/myimage:v1 docker pull <harbor-host>/projectX/newimage:v1

该步骤用于建立"删除前镜像可用"的基线,从而在删除后形成鲜明的对比断言。

2.2 执行阶段:管理员删除镜像

  1. 在 UI 中退出用户 A 的会话。此步骤确保后续操作完全以管理员身份执行,排除会话复用造成的权限混淆。
  2. 以 admin 用户身份登录 Harbor
  3. 进入项目 X,逐个删除两个镜像projectX/myimage:v1projectX/newimage:v1)。在 UI 中,位置为项目 → 镜像仓库 → 选中镜像 → 删除

2.3 验证阶段:删除后客户端拉取必须失败

  1. 回到 Docker 客户端,以用户 A 身份登录,再次拉取已删除的两个镜像
docker login <harbor-host> docker pull <harbor-host>/projectX/myimage:v1 docker pull <harbor-host>/projectX/newimage:v1

2.4 预期结果

步骤预期结果
步骤 8管理员可以成功删除项目 X 中的两个镜像
步骤 9Docker 客户端拉取已删除镜像时应报错

拉取失败的直接原因是镜像的 manifest 已从 Harbor 存储中删除,Registry 返回manifest unknown之类的错误,Docker CLI 因此无法解析镜像内容。


三、源码深潜:管理员为什么能删除?

3.1 删除接口的权限门禁

镜像删除在 API 层对应DELETE /projects/{project_name}/repositories/{repository_name},实现在 src/server/v2.0/handler/repository.go 的DeleteRepository

func (r *repositoryAPI) DeleteRepository(ctx context.Context, params operation.DeleteRepositoryParams) middleware.Responder { if err := r.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionDelete, rbac.ResourceRepository); err != nil { return r.SendError(ctx, err) } repository, err := r.repoCtl.GetByName(ctx, fmt.Sprintf("%s/%s", params.ProjectName, params.RepositoryName)) ... if err := r.repoCtl.Delete(ctx, repository.RepositoryID); err != nil { return r.SendError(ctx, err) } // fire event notification.AddEvent(ctx, &metadata.DeleteRepositoryEventMetadata{...}) return operation.NewDeleteRepositoryOK() }

关键点在RequireProjectAccess:删除操作要求调用者对rbac.ResourceRepository资源拥有rbac.ActionDelete动作。权限枚举定义见 src/common/rbac/const.go(ActionDelete = Action("delete"))与 src/common/rbac/const.go(ResourceRepository)。

系统管理员的特殊地位:Harbor 的权限上下文中,系统管理员(secCtx.IsSysAdmin())在权限评估时被直接放行,无需逐项匹配项目级角色策略。因此 admin 用户对任意项目内的repository:delete动作天然具备权限——这正是用例中"管理员能删除普通用户项目镜像"的权限层依据。

3.2 项目管理员的权限清单(对照组)

用例同时要求用户 A 在创建项目后获得项目管理员角色。查看 src/common/rbac/const.go 中项目管理员(Project Admin)的策略声明,可以看到它被授予了:

{Resource: ResourceRepository, Action: ActionRead}, {Resource: ResourceRepository, Action: ActionUpdate}, {Resource: ResourceRepository, Action: ActionDelete}, {Resource: ResourceRepository, Action: ActionList}, {Resource: ResourceRepository, Action: ActionPull}, {Resource: ResourceRepository, Action: ActionPush},

也就是说:普通用户在"自己创建的项目"内同样拥有镜像删除权限;而管理员则在此基础上拥有跨项目、跨用户的全局删除能力。两者叠加,构成了本例完整的权限对照:用户 A 能删自己的镜像(但删不了别人的),admin 能删所有人的镜像。


四、源码深潜:删除镜像时后端到底做了什么?

4.1 仓储控制器:先清制品,再删记录

API 层在权限校验通过后,调用repository.Ctl.Delete(src/controller/repository/controller.go):

func (c *controller) Delete(ctx context.Context, id int64) error { candidates, err := c.artCtl.List(ctx, &q.Query{ Keywords: map[string]any{"RepositoryID": id}, }, nil) ... for len(candidates) > 0 { artifacts := candidates candidates = nil for _, artifact := range artifacts { parents, err := c.artMgr.ListReferences(...) if err != nil { return err } if len(parents) > 0 { // 制品仍被其他制品引用,放入下一轮再试 candidates = append(candidates, artifact) continue } if err = c.artCtl.Delete(ctx, artifact.ID); err != nil { return err } } } return c.repoMgr.Delete(ctx, id) }

该实现有两个值得注意的工程细节:

  • 先删制品、后删仓储记录:一个 repository 下可能挂多个 artifact(不同 tag 指向的 manifest 等)。必须等所有制品删除成功后,才删除repository表记录,避免残留孤儿数据。
  • 引用感知的循环删除:若某个制品仍被其他制品引用(例如被其他 manifest 引用),当前轮次会跳过它并放入candidates重新排队,下一轮再尝试——这种"让位重试"机制保证了被依赖的制品不会被误删(这一点与下面的深度删除逻辑配合)。

4.2 制品控制器:事务内的深度级联删除

真正的物理删除发生在制品控制器,artifact.Ctl.Delete(src/controller/artifact/controller.go)在一个 ORM 事务内调用deleteDeeply

func (c *controller) Delete(ctx context.Context, id int64) error { accs, err := c.accessoryMgr.List(ctx, q.New(q.KeyWords{"ArtifactID": id})) ... return orm.WithTransaction(func(ctx context.Context) error { return c.deleteDeeply(ctx, id, true, len(accs) > 0) })(orm.SetTransactionOpNameToContext(ctx, "tx-delete-artifact-delete")) }

deleteDeeply(src/controller/artifact/controller.go 起)处理了完整的依赖链:

  • 若制品已被其他制品引用(parents非空),根制品删除会返回ViolateForeignKeyConstraintCode错误并中止;非根制品则跳过,避免级联破坏。
  • 依次删除制品上的accessory(如 cosign 签名、SBOM 等附加物,仅硬引用类型被删除)与child artifact 引用,再递归处理子制品。
  • 整个过程包在事务中,任何一步失败都会整体回滚,保证删除的原子性。

正是这条"仓储 → 制品 → 深度级联"的链路,使得 UI 上的一次删除操作能够干净地移除镜像在 Harbor 元数据库中的全部记录。

4.3 删除后的联动:审计与通知事件

回到 API 层,删除成功后还会通过notification.AddEvent发布DeleteRepositoryEventMetadata事件(见 src/server/v2.0/handler/repository.go)。这意味着:

  • 配置了Webhook的项目会收到仓库删除通知,外部系统(如 CI/CD 流水线、工单系统)可以据此联动;
  • 审计日志(audit log)会记录该删除动作,便于事后追溯"谁在何时删除了哪个镜像"。

4.4 单测对删除链路的锁定

Harbor 为仓储删除逻辑提供了单元测试 src/controller/repository/controller_test.go:

func (c *controllerTestSuite) TestDelete() { art := &artifact.Artifact{} art.ID = 1 mock.OnAnything(c.argMgr, "ListReferences").Return(nil, nil) mock.OnAnything(c.artCtl, "List").Return([]*artifact.Artifact{art}, nil) mock.OnAnything(c.artCtl, "Delete").Return(nil) c.repoMgr.On("Delete", mock.Anything, mock.Anything).Return(nil) err := c.ctl.Delete(nil, 1) c.Require().Nil(err) }

该测试覆盖了"先遍历删除制品、再删除仓储记录"的核心路径,与2-23用例在功能层相互印证:前者保证后端删除逻辑的正确性,后者保证端到端(UI + Docker CLI)行为符合预期。


五、删除后拉取失败的机制解读

用例的最终断言是:删除后docker pull必须失败。其背后机制为:

  1. Harbor 使用 OCI Distribution 规范与 Docker Registry 交互,镜像通过 manifest digest 寻址。
  2. 删除操作同时清理了 Harbor 元数据库中的 repository/artifact/tag 记录,并触发对 Registry 存储层 blob/manifest 的清理(由 GC 流程协调)。
  3. 客户端再次docker pull时,Registry 无法找到对应 manifest,返回manifest unknown一类错误,Docker CLI 随即终止并报错。

从用户视角看,这正是"删除立即生效"的体现。需要注意的是,存储层物理空间的回收通常由后续的垃圾回收(GC)任务完成,元数据删除与磁盘空间释放之间存在时间差,这不影响本文用例的判定(用例验证的是客户端可访问性,而非磁盘空间)。


六、实操建议与注意事项

基于源码链路与用例流程,在实际运维中可注意以下几点:

  1. 权限最小化:普通项目管理员(Project Admin)已具备本项目的镜像删除权限(见 src/common/rbac/const.go),日常清理建议由项目管理员完成;admin 账号的全局删除能力应保留给跨项目治理、下线清理等场景。
  2. 删除前确认引用关系:若镜像被其他制品引用(如被其他 manifest 引用),删除会被后端引用检查拦截或推迟(见 src/controller/repository/controller.go),这是保护机制而非故障。
  3. 关注 Webhook 与审计:生产环境建议为关键项目配置删除事件通知,并开启审计日志,使每一次管理员删除操作都可追溯。
  4. 配合 GC 规划空间回收:删除操作只保证镜像不可访问,若需要立即回收磁盘空间,应关注 Harbor 的 GC 任务执行情况(相关任务框架见 src/jobservice/job/impl)。
  5. 多模式认证差异:本用例针对db_auth本地认证模式验证。若使用 LDAP/OIDC 等外部认证,管理员判定逻辑由 src/common/security 下的安全上下文实现,建议在对应认证模式下单独回归测试。

总结

2-23 Admin User Delete Images (DB Mode)这一用例看似只是一个 UI 手工测试,实则覆盖了 Harbor 权限模型与删除链路的多个核心设计:系统管理员跨项目删除权限、项目管理员角色策略、仓储-制品两级删除与深度级联清理、删除事件的发布。通过 src/server/v2.0/handler/repository.go、src/controller/repository/controller.go 与 src/controller/artifact/controller.go 的源码印证,你可以清晰地回答"管理员为什么能删、删了什么、删完客户端为何拉取失败",并在此基础上规划自己的镜像清理与权限治理策略。

【免费下载链接】harborAn open source trusted cloud native registry project that stores, signs, and scans content.项目地址: https://gitcode.com/GitHub_Trending/ha/harbor

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询