Harbor 项目公开性实战指南:DB 模式下非管理员创建项目与公共/私有项目权限验证
2026/9/10 23:54:12 网站建设 项目流程

Harbor 项目公开性实战指南:DB 模式下非管理员创建项目与公共/私有项目权限验证

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

本篇技术指南围绕 Harbor 测试用例 2-01-DB-user-create-project.md 展开,深入讲解在本地数据库认证(DB 模式,auth_mode=db_auth)下,非管理员用户如何创建项目、公开项目(Public)与私有项目(Private)的可见性差异,以及通过 Docker CLI 完成登录、推送与拉取镜像的完整验证链路。读完本文,你将掌握 Harbor 项目公开性元数据(metadata.public)的底层实现、项目创建权限限制配置(project_creation_restriction)的作用,以及公共项目列表过滤的源码级原理,可直接复现该测试场景并迁移到生产环境的项目权限规划中。

一、测试场景概览:验证什么、为什么重要

该用例是 Harbor 镜像管理测试组(Group2-image-management)的第一个用例,编号 2-01,属于DB 模式(DB Mode)系列。它要验证的核心命题是:

当 Harbor 使用本地数据库管理用户(auth_mode=db_auth)时,普通非管理员用户是否能够创建项目,以及项目公开性(publicity)切换后,其他非管理员用户能否在公共项目列表中看到该项目,并能否拉取其镜像。

这一命题直接关系到多租户场景下项目隔离与共享的基本体验。配套用例还包括 2-02-DB-user-push-multiple-images.md(同一项目推送多镜像)、2-03-DB-user-push-multiple-projects.md(多项目推送)、2-04-DB-user-view-projects.md(查看项目)等,本用例是其中验证项目创建与公开性语义的基础。

二、测试环境与前置条件

原用例对环境的要求如下,这也是复现实验的最低门槛:

  • Harbor 实例已启动并可用(运行中的核心服务、Registry、数据库组件齐全);
  • 认证模式为本地数据库auth_mode设置为db_auth,用户数据存储在 Harbor 本地数据库中,即用户由 Harbor 自行管理,不依赖 LDAP/OIDC 等外部认证源;
  • 一台安装了 Docker CLI 的 Linux 主机(作为 Docker 客户端,用于docker login/docker push/docker pull);
  • Harbor 中至少存在两个非管理员用户(下文记为用户 A 和用户 B)。

原用例特别注明:示例中的用户 A、B 以及项目 X、Y 应替换为更长、更有实际含义的名称,便于在真实环境与日志中区分,避免使用单字母命名造成混淆。

三、逐步测试流程:完整复现 12 个步骤

以下步骤完整继承原用例,并结合 Harbor Web 界面(Portal)与 Docker CLI 的实际操作展开说明。

角色设定:用户 A、B 均为非管理员;项目 X 与项目 Y 为待创建项目。

3.1 用户 A 创建两个公开性不同的项目

  1. 以用户 A(非管理员)身份登录 Harbor UI。使用浏览器访问 Harbor Portal,输入用户 A 的凭据完成登录。
  2. 创建项目 X,公开性(publicity)保持关闭(默认)。在项目创建界面中,项目访问级别默认即为私有(Private)。从源码实现看,创建项目时若未显式指定公开性,服务端会将其置为false(见下文源码解析),因此"默认关闭"即默认创建私有项目。
  3. 再创建项目 Y,公开性(publicity)设为开启(Public)。在创建界面勾选公开访问级别,使项目 Y 对未登录用户及其他用户可见。

3.2 用户 B 从另一个浏览器验证公共项目列表

  1. 保持用户 A 处于登录状态,在另一个浏览器中(或隐身窗口)以用户 B(非管理员)登录 Harbor UI
  2. 用户 B 查看自己的公共项目(Public Projects)列表,预期结果
    • 项目Y(公开)出现在列表中;
    • 项目X(私有)不出现在列表中。

这一步验证了"公共项目对全体用户可见、私有项目对外部用户不可见"的基本隔离语义。

3.3 切换公开性后重新验证

  1. 用户 A 在项目中修改公开性:将项目 X 的公开性改为开启(Public),将项目 Y 的公开性改为关闭(Private)。
  2. 用户 B 刷新公共项目列表,预期结果
    • 项目X(现已公开)出现在列表中;
    • 项目Y(现已私有)不再出现在列表中。

这一步验证了公开性切换是即时生效的,公共项目列表会随项目公开性变化实时刷新。

3.4 Docker CLI 侧的推送与拉取验证

  1. 在 Docker 客户端主机上,使用docker login <harbor_host>以用户 A 身份登录 Harbor。其中<harbor_host>替换为 Harbor 实际地址(含端口,如reg.example.com:443)。

  2. 用户 A 分别向项目 X 和项目 Y 推送镜像

    # 先为本地镜像打上指向项目 X 的标签 docker tag <local-image>:<tag> <harbor_host>/x/<local-image>:<tag> docker push <harbor_host>/x/<local-image>:<tag> # 再打上指向项目 Y 的标签并推送 docker tag <local-image>:<tag> <harbor_host>/y/<local-image>:<tag> docker push <harbor_host>/y/<local-image>:<tag>
  3. 用户 A 在浏览器中确认两个镜像均已成功推送到项目 X 和项目 Y 的仓库列表。

  4. 在 Docker 客户端主机上,使用docker login <harbor_host>以用户 B 身份登录(同一主机或另一台均可)。

  5. 用户 B 尝试拉取项目 X 与项目 Y 中的镜像

    docker pull <harbor_host>/x/<local-image>:<tag> docker pull <harbor_host>/y/<local-image>:<tag>

3.5 预期结果汇总

步骤操作预期结果
Step 5用户 B 查看公共项目列表项目 Y 在列表中,项目 X 不在列表中
Step 7切换公开性后刷新列表项目 X 在列表中,项目 Y 不在列表中
Step 9、10用户 A 推送镜像镜像成功推送到项目 X 和项目 Y,UI 可见
Step 11、12用户 B 拉取镜像项目 X 的镜像可拉取,项目 Y 的镜像不可拉取

其中 Step 12 的判定需要特别注意:此时项目 X 已切换为公开,因此用户 B 可以匿名拉取其镜像;项目 Y 已切换为私有,用户 B 并非其成员,因此拉取会被拒绝。这正是公开性(Public/Private)在镜像分发层面的最终体现。

四、源码级原理(一):项目公开性如何存储与判定

原用例中的"公开性(publicity)"在 Harbor 源码中对应项目元数据键public

4.1 元数据键与模型定义

在项目模型 src/pkg/project/models/pro_meta.go 中定义了元数据常量:

ProMetaPublic = "public"

而项目实体 src/pkg/project/models/project.go 中同时定义了语义常量:

const ( // ProjectTable is the table name for project ProjectTable = "project" // ProjectPublic means project is public ProjectPublic = "public" // ProjectPrivate means project is private ProjectPrivate = "private" )

可见"公开/私有"并非project主表的独立列,而是存放在项目的metadata(元数据 Map)中。项目实体的Metadata map[string]string字段承载这些键值对。

4.2 公开性判定逻辑:IsPublic()

src/pkg/project/models/project.go 提供了判定方法:

// IsPublic ... func (p *Project) IsPublic() bool { public, exist := p.GetMetadata(ProMetaPublic) if !exist { return false } return isTrue(public) }

关键语义:只要public元数据不存在(从未设置),一律按私有(false)处理。这与 UI 创建项目时"公开性默认关闭"的行为完全一致。

4.3 创建与更新时的服务端校验

在项目 API 处理器 src/server/v2.0/handler/project.go 中,CreateProjectpublic元数据做了三段式处理:

// accept the "public" property to make replication work well with old versions(<=1.2.0) if req.Public != nil && req.Metadata.Public == "" { req.Metadata.Public = strconv.FormatBool(*req.Public) } // populate public metadata as false if it isn't set if req.Metadata.Public == "" { req.Metadata.Public = strconv.FormatBool(false) } // validate metadata.public value, should only be "true" or "false" if p := req.Metadata.Public; p != "" { if p != "true" && p != "false" { return a.SendError(ctx, errors.BadRequestError(nil).WithMessagef("metadata.public should only be 'true' or 'false', but got: '%s'", p)) } }

这段代码说明三件事:

  1. 向后兼容:兼容旧版本(≤1.2.0)复制(replication)请求中携带顶层public字段的写法,将其转换为元数据形式;
  2. 默认值兜底:未设置时强制填充为"false",从服务端保证了"默认私有";
  3. 入参校验public只能取字符串"true""false",非法值直接返回 400 Bad Request。

更新项目时(UpdateProject,见 src/server/v2.0/handler/project.go),处理器同样将请求中的元数据通过JSONCopy合并进项目实体,从而允许项目所有者切换公开性。

五、源码级原理(二):非管理员为何能创建项目

原用例成立的前提是"非管理员可以创建项目"。这在 Harbor 中由一个系统配置项控制:project_creation_restriction(项目创建限制)

5.1 配置取值与默认行为

系统配置键定义在 src/common/const.go,可取值为:

配置值常量含义
everyoneProCrtRestrEveryone所有用户均可创建项目(默认)
adminonlyProCrtRestrAdmOnly仅系统管理员可创建项目

src/common/const.go 中的定义:

ProCrtRestrEveryone = "everyone" ProCrtRestrAdmOnly = "adminonly"

判断逻辑位于 src/lib/config/userconfig.go:

// OnlyAdminCreateProject returns the flag to restrict that only sys admin can create project func OnlyAdminCreateProject(ctx context.Context) (bool, error) { return DefaultMgr().Get(ctx, common.ProjectCreationRestriction).GetString() == common.ProCrtRestrAdmOnly, nil }

5.2 CreateProject 的权限检查链

CreateProject 的权限检查顺序为:

if err := a.RequireAuthenticated(ctx); err != nil { ... } onlyAdmin, err := config.OnlyAdminCreateProject(ctx) ... if onlyAdmin && !(a.isSysAdmin(ctx, rbac.ActionCreate) || secCtx.IsSolutionUser()) { log.Errorf("Only sys admin can create project") return a.SendError(ctx, errors.ForbiddenError(nil).WithMessage("Only system admin can create project")) }

即:在默认配置everyone下,任何已通过认证(authenticated)的非管理员用户都可以创建项目;只有管理员在系统配置中将project_creation_restriction切换为adminonly后,非管理员才会收到 403 Forbidden。原用例的 Step 1/2 正是基于默认配置展开的。

此外,创建项目的所有者(Owner)被设定为发起请求的用户本人(见 src/server/v2.0/handler/project.go),通过userCtl.GetByName查询当前用户名对应的用户 ID 作为OwnerID

六、源码级原理(三):公共项目列表如何过滤

原用例 Step 5、7 的核心是"用户 B 的公共项目列表中能看到公开项目、看不到私有项目"。这一行为在ListProjects处理器中实现。

6.1 非管理员视角:公共项目 + 自己参与的项目

src/server/v2.0/handler/project.go 中,对已认证但非系统管理员的请求,查询被改写为"本人成员身份 + 公共项目":

if ok && secCtx.IsAuthenticated() { if !a.isSysAdmin(ctx, rbac.ActionList) && !secCtx.IsSolutionUser() { // authenticated but not system admin or solution user, // return public projects and projects that the user is member of if l, ok := secCtx.(*local.SecurityContext); ok { currentUser := l.User() member := &project.MemberQuery{ UserID: currentUser.UserID, GroupIDs: currentUser.GroupIDs, } // not filter by public or filter by the public with true, // so also return public projects for the member if public, ok := query.Keywords["public"]; !ok || lib.ToBool(public) { member.WithPublic = true } query.Keywords["member"] = member } ... } }

MemberQuery.WithPublic字段在 src/pkg/project/models/member.go 中定义为WithPublic bool // include the public projects for the member,表示在按成员查询时同时包含公共项目

  • 当用户未指定public过滤条件,或显式请求public=true时,WithPublic=true,结果 = 本人参与的项目 + 全部公共项目;
  • 这正是用户 B 能看到公开的项目 Y、却看不到私有的项目 X 的根源:项目 X 既非公共项目,用户 B 也不是其成员

6.2 匿名视角与私有查询的兜底

同一处理器还处理了其他视角(src/server/v2.0/handler/project.go):

} else { if params.Public != nil && !*params.Public { // anonymous want to query private projects return empty projects directly return operation.NewListProjectsOK().WithXTotalCount(0).WithPayload([]*models.Project{}) } // force to return public projects for anonymous query.Keywords["public"] = true }

匿名用户只能看到公共项目;若匿名用户显式请求私有项目列表,则直接返回空结果。公共项目因此具备了"无需登录即可拉取镜像"的能力,这正是 Step 12 中用户 B 能拉取公开项目 X 镜像的原因之一(实际拉取时还受 Registry 的匿名访问策略支持)。

6.3 DAO 层的支撑验证

在 src/pkg/project/dao/dao_test.go 中提供了TestListByPublic测试,对按公开性过滤的查询逻辑进行了覆盖;其中 dao_test.go 展示了组合查询的典型用法:

projects, err := suite.dao.List(orm.Context(), q.New(q.KeyWords{"member": &models.MemberQuery{UserID: 1, Role: common.RoleGuest, WithPublic: true}}))

即以"用户 ID + 角色 + 包含公共项目"为关键词组合查询,与ListProjects处理器的行为一一对应。

七、实操要点与边界条件

7.1 Docker 客户端连通性前提

docker login/push/pull依赖 Harbor 对外暴露的 Registry 端口(通常是 HTTPS 443 或配置的其他端口),且客户端需信任 Harbor 的 CA 证书(自签名证书场景需将 Harbor 根证书加入 Docker 客户端的信任链)。本用例默认环境已具备这些前提。

7.2 命名建议

原用例明确要求将用户 A/B 与项目 X/Y 替换为有意义的名称。建议在实测中:

  • 用户:alicebob
  • 项目:alice-private-repoalice-public-repo
  • 镜像:使用nginx:latestbusybox:latest等轻量公共镜像打标。

7.3 判定要点重述

  • UI 侧:公共项目列表 = 公开项目 + 本人参与的项目;私有且未参与的项目不出现;
  • Registry 侧:公开项目允许非成员拉取;私有项目仅成员(含项目内角色)可拉取;
  • 切换即时性:公开性切换无需重启 Harbor,刷新页面即可看到列表变化。

7.4 与 LDAP 模式的对照

同目录下还有 2-11-LDAP-user-create-project.md 等 LDAP 系列用例(2-11 至 2-16)。两者验证的功能矩阵完全一致(创建项目、推多镜像、推多项目、查看项目、删镜像、删项目),唯一区别是用户来源:LDAP 模式下用户由外部目录服务管理。本文的源码原理对两种模式同样适用,因为项目创建、公开性与成员关系的处理逻辑与认证来源无关。

八、总结

通过完整复现 2-01-DB-user-create-project.md 的 12 个步骤,我们验证了 Harbor 在 DB 模式下的一条完整权限链路:

  1. 创建:默认配置下任何已认证用户均可创建项目(project_creation_restriction=everyone);
  2. 公开性:项目通过元数据metadata.public"true"/"false")表达公开/私有,未设置时服务端强制为私有;
  3. 可见性:非管理员的项目列表 = 本人参与的项目 + 公共项目(MemberQuery.WithPublic),私有项目对其他用户完全不可见;
  4. 镜像访问:公开项目支持非成员拉取,私有项目仅成员可拉取。

这套机制构成了 Harbor 多租户场景下"共享与隔离"的基石,无论是搭建内部镜像仓库还是对外提供公共镜像服务,理解metadata.publicproject_creation_restriction的实现细节,都能帮助你更精准地规划项目权限策略。

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

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

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

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

立即咨询