news 2026/9/10 10:14:25

Harbor 项目级内容信任(Content Trust)实战指南:让未签名镜像无法被拉取

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Harbor 项目级内容信任(Content Trust)实战指南:让未签名镜像无法被拉取

Harbor 项目级内容信任(Content Trust)实战指南:让未签名镜像无法被拉取

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

导读:Harbor 提供了项目级内容信任能力,可在某个项目上强制“只允许拉取已签名镜像”,从而在制品分发链路中落实供应链安全基线。本文以 Harbor 仓库中的项目级内容信任测试用例 9-30-Project-level-content-trust.md 为核心骨架,逐步演示如何创建项目、开启信任策略、分别推送签名与未签名镜像并验证拉取结果;同时结合 Harbor 源码(内容信任中间件、项目元数据定义与对应测试)深入讲解该策略在拉取请求链路上的底层判定逻辑,帮助你既能在真实环境中复现验证,也能理解其实现原理。

一、什么是项目级内容信任

Harbor 的内容信任(Content Trust)机制,本质上是把“镜像是否被签名”作为一个拉取许可条件:当某个项目启用了内容信任策略后,凡是向该项目发起的镜像拉取请求,Harbor 都会先检查目标制品是否带有有效签名,未签名的镜像将被拒绝拉取。

这一能力在 Harbor 中是按项目维度配置的(区别于全局配置),因此不同项目可以采用不同的安全基线:

  • 允许项目随意拉取所有镜像(默认行为,不开启内容信任);
  • 要求镜像必须有Notation签名才能拉取(enable_content_trust);
  • 要求镜像必须有Cosign签名才能拉取(enable_content_trust_cosign);
  • 两者同时开启时,镜像需同时具备两种签名才能拉取。

从源码看,这两项策略分别对应项目元数据中的两个键,定义于 src/pkg/project/models/pro_meta.go:

ProMetaEnableContentTrust = "enable_content_trust" ProMetaEnableContentTrustCosign = "enable_content_trust_cosign"

前端“项目配置 → 策略”页面中的两个开关,也正是读写这两个元数据键,参见 project-policy-config.component.ts 与对应模板 project-policy-config.component.html。

二、验证内容信任所需的环境

原文测试用例(9-30-Project-level-content-trust.md)明确给出了环境前提:

  • 一个正在运行且可访问的 Harbor 实例
  • 一台装有 Docker CLI 的 Linux 主机(作为 Docker 客户端);
  • 若 Harbor 使用了自签名证书,需要先把 CA 根证书拷贝到客户端对应目录:
    • /etc/docker/certs.d/<harbor_ip>/(Docker 拉取镜像时校验用);
    • $HOME/.docker/tls/<harbor_ip>:4443/(Notary 签名服务校验用)。

在后续所有命令中,<harbor_ip>需要替换为 Harbor 的 IP 或 FQDN;项目名建议使用有实际含义、较长一些的名字(避免与常见短名冲突),原文中项目名用a占位,实际使用时建议换成如content-trust-demo这样的名字。

说明:原文基于 Docker Content Trust(Notary)签名方案编写,其签名服务端口为 4443。当前 Harbor 源码中的项目级内容信任中间件已经同时支持CosignNotation两类签名(详见本文第四节),Docker/Notary 方案仍可作为历史验证路径参考。

三、完整验证步骤(核心实操)

按照原文测试步骤,完整的端到端验证流程如下。

第 1 步:登录 UI 并创建项目

用管理员(或具备项目创建权限的用户)登录 Harbor Web 控制台,创建项目。创建完成后先不要开启内容信任,因为我们首先要向该项目推送一个“未签名”镜像作为对照组。

第 2 步:先推送一个未签名镜像

在 Docker 客户端正常(不设置任何签名环境变量)登录 Harbor 后,推送第一个镜像:

docker login <harbor_ip> docker tag nginx:latest <harbor_ip>/<project_a>/nginx:unsigned docker push <harbor_ip>/<project_a>/nginx:unsigned

此时该镜像是未签名的,作为后续对照组使用。

第 3 步:在客户端开启 Docker Content Trust

在推送第二个(签名)镜像前,先设置 Docker Content Trust 相关环境变量:

export DOCKER_CONTENT_TRUST=1 export DOCKER_CONTENT_TRUST_SERVER=https://<harbor_ip>:4443
  • DOCKER_CONTENT_TRUST=1:要求 Docker 客户端在 push/pull 时使用 Notary 签名;
  • DOCKER_CONTENT_TRUST_SERVER:指定 Notary 签名服务地址(Harbor 的 Notary 服务默认监听 4443 端口)。

然后重新登录 Harbor(部分场景下需要重新登录以获取签名权限的凭证):

docker login <harbor_ip>

第 4 步:推送一个已签名镜像

保持上述环境变量生效,推送第二个镜像。Docker 会引导你为签名设置 root key / repository key 的密码,随后将镜像连同签名一起推送到 Harbor:

docker tag nginx:latest <harbor_ip>/<project_a>/nginx:signed docker push <harbor_ip>/<project_a>/nginx:signed

第 5 步:在项目配置页开启“项目级内容信任”

回到 Harbor UI,进入项目a配置(Configuration)页面,开启项目级内容信任(Content Trust)开关并保存。

此时项目a的策略变为:任何来自该项目的镜像拉取请求,都必须携带有效签名

第 6 步:拉取第一个(未签名)镜像 —— 应失败

docker pull <harbor_ip>/<project_a>/nginx:unsigned

第 7 步:拉取第二个(已签名)镜像 —— 应成功

docker pull <harbor_ip>/<project_a>/nginx:signed

预期结果

步骤操作预期结果
第 6 步拉取首次推送的未签名镜像拉取失败,Harbor 拒绝未签名镜像
第 7 步拉取第二次推送的已签名镜像拉取成功

两组结果对比即可证明:项目级内容信任策略只拦截“未签名”制品,而不会影响“已签名”制品的正常拉取。

四、底层原理:拉取链路上的内容信任中间件

项目级内容信任在 Harbor 中是通过注册在 Registry API 路由上的中间件实现的。在 src/server/registry/route.go 中可以看到,针对清单获取的GETHEAD请求:

root.NewRoute(). Method(http.MethodGet). Path("/*/manifests/:reference"). Middleware(metric.InjectOpIDMiddleware(metric.ManifestOperationID)). Middleware(repoproxy.ManifestMiddleware()). Middleware(contenttrust.ContentTrust()). Middleware(vulnerable.Middleware()). HandlerFunc(getManifest)

docker pull解析镜像清单(manifest)时,会先经过contenttrust.ContentTrust()中间件做签名校验。中间件核心逻辑位于 src/server/middleware/contenttrust/contentrust.go:

// If signature policy enabled, it has to at least have one signature. if pro.ContentTrustCosignEnabled() { if err := signatureChecking(ctx, r, af, pro.ProjectID, model.TypeCosignSignature); err != nil { if errors.IsErr(err, errors.PROJECTPOLICYVIOLATION) { return errors.New(nil).WithCode(errors.PROJECTPOLICYVIOLATION).WithMessage("The image is not signed by cosign.") } return err } } if pro.ContentTrustEnabled() { if err := signatureChecking(ctx, r, af, pro.ProjectID, model.TypeNotationSignature); err != nil { if errors.IsErr(err, errors.PROJECTPOLICYVIOLATION) { return errors.New(nil).WithCode(errors.PROJECTPOLICYVIOLATION).WithMessage("The image is not signed by notation.") } return err } }

其判定流程可以概括为四步:

  1. 取出制品信息并定位项目:从请求上下文中取得lib.ArtifactInfo(仓库名、引用等),再通过project.Ctl.GetByName拿到项目对象;
  2. 读取项目信任策略:根据项目的enable_content_trust_cosign/enable_content_trust元数据决定要检查哪种签名(Cosign 与 Notation 分别独立判定);
  3. 获取制品及其配件(Accessories):通过artifact.Ctl.GetByReference读取制品,并携带WithAccessory: true选项取回附属对象列表;
  4. 在 Accessories 中查找签名:若制品没有任何 Accessories,或所有 Accessories 的类型都不匹配要求的签名类型(model.TypeCosignSignature/model.TypeNotationSignature),则返回策略违规错误PROJECTPOLICYVIOLATION,请求被拒绝。

也就是说,签名在 Harbor 的数据模型中是作为制品的Accessory(附属物)存在的:Cosign 签名(cosign镜像,类型TypeCosignSignature)与 Notation 签名(类型TypeNotationSignature)会以子制品的形式挂接在目标镜像上,中间件通过检查这些附属物来确定“该镜像是否已被签名”。

五、中间件测试用例:验证判定行为

Harbor 为内容信任中间件编写了完备的单元测试,位于 src/server/middleware/contenttrust/contentrust_test.go。这些测试直观地印证了上文总结的判定行为:

测试用例场景预期状态码
TestContentTrustDisabled项目未开启 Cosign 内容信任200 OK
TestAuthenticatedUserPulling开启信任策略但制品无签名配件412 Precondition Failed
TestUnAuthenticatedUserPulling匿名用户拉取无签名镜像412 Precondition Failed
TestCosignPulling携带User-Agent: cosign的客户端拉取(签名推送前置动作)200 OK
TestScannerPulling扫描器(scanner)拉取镜像200 OK
TestCosignSignaturePulling制品带 Cosign 签名配件200 OK
TestNotationSignaturePulling制品带 Notation 签名配件200 OK
TestBothSignaturePulling同时开启两类策略且两种签名都存在200 OK

其中“412 Precondition Failed(前提条件不满足)”正是 Harbor 拒绝未签名镜像时返回给客户端的语义化状态码,与你执行docker pull时看到的失败相互对应。

测试中项目元数据的构造方式也印证了配置键与行为的映射关系:

suite.project = &proModels.Project{ Name: "library", Metadata: map[string]string{ proModels.ProMetaEnableContentTrustCosign: "true", }, }

哪些“拉取”会被放行

值得注意的是,中间件并不会一刀切地拦截所有请求。在 src/server/middleware/util/util.go 的SkipPolicyChecking中,以下三类场景会被显式跳过策略检查:

  1. 扫描器拉取:扫描器使用v2token安全上下文且具备ActionScannerPull权限时可绕过,保证漏洞扫描不受签名策略影响;
  2. Cosign / Notation 签名工具拉取:签名工具(User-Agent 含cosignnotation)且具备ActionPush权限时,可以在推送签名之前先拉取目标 manifest——这是“先签名后推送”流程的必要前提;
  3. 直接拉取签名附属物本身:当请求的制品本身就是 Cosign / Notation 签名(Accessory 类型匹配)时直接放行。

这解释了内容信任策略在“放行合法工具”与“拦截普通未签名拉取”之间如何取得平衡,也是你在排查“为什么签名流程没被拦截、普通 pull 却被拦下”时最需要关注的分支逻辑。

六、实际使用建议与故障排查

使用建议

  • 先用对照组验证策略:按本文步骤先推“未签名 + 已签名”两个镜像再开启策略,能最快确认策略生效,避免把镜像损坏、认证失败等其他问题误判为内容信任拦截;
  • 结合 Cosign / Notation 使用:当前 Harbor 更推荐使用 Cosign(icons/cosign.png 对应工具生态)与 Notation(icons/notation.png)方案,二者均可通过 Harbor UI 的“内容信任-Cosign / 内容信任-Notation”两个开关分别开启,并支持同时启用双签名策略;
  • 注意扫描器与签名工具的绕行路径:若你观察到某些客户端(扫描器、签名工具)在策略开启后仍能拉取,这属于SkipPolicyChecking的设计行为,而非策略失效。

常见问题排查

现象可能原因处理方式
未签名镜像在开启策略后仍能拉取策略未保存成功,或该请求命中绕行分支到项目“配置”页确认开关状态;检查请求方是否属于扫描器/签名工具
已签名镜像无法拉取签名类型与策略不匹配(例如只签了 Cosign 却开启了 Notation 策略)核对策略开关与镜像实际签名类型;双策略场景需同时具备两种签名
docker pull报 412 相关错误Harbor 判定镜像未签名使用docker trust signcosign signnotation sign完成签名后重试
自签名证书环境下签名/拉取异常证书未导入客户端信任目录将 CA 证书分别放入/etc/docker/certs.d/<harbor_ip>/$HOME/.docker/tls/<harbor_ip>:4443/

七、小结

项目级内容信任是 Harbor 将“镜像签名”与“拉取许可”直接挂钩的安全策略:开启后,未签名镜像的拉取请求会在 Registry manifest 路由上被 contenttrust 中间件 拦截并返回失败,而已签名镜像不受影响。本文从测试用例 9-30-Project-level-content-trust.md 出发,给出了完整的可复现验证流程,并从 contentrust.go、route.go、util.go 与 contentrust_test.go 等源码中还原了其判定链路,帮助你既能在生产环境安全落地“仅允许拉取已签名镜像”的策略,也能在出现异常时快速定位到具体的拦截分支。

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

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 10:10:49

CANN/ge算子属性设置接口

aclgrphSetOpAttr 【免费下载链接】ge GE&#xff08;Graph Engine&#xff09;是面向昇腾的图编译器和执行器&#xff0c;提供了计算图优化、多流并行、内存复用和模型下沉等技术手段&#xff0c;加速模型执行效率&#xff0c;减少模型内存占用。 GE 提供对 PyTorch、TensorFl…

作者头像 李华
网站建设 2026/9/10 10:08:15

CANN/ge类型转换API文档

CreateFrom 【免费下载链接】ge GE&#xff08;Graph Engine&#xff09;是面向昇腾的图编译器和执行器&#xff0c;提供了计算图优化、多流并行、内存复用和模型下沉等技术手段&#xff0c;加速模型执行效率&#xff0c;减少模型内存占用。 GE 提供对 PyTorch、TensorFlow 前端…

作者头像 李华
网站建设 2026/9/10 10:07:38

Wand-Enhancer 完整指南:5 步免费解锁 Wand 专业版的本地补丁

Wand-Enhancer 完整指南&#xff1a;5 步免费解锁 Wand 专业版的本地补丁 【免费下载链接】Wand-Enhancer Advanced UX and interoperability extension for Wand (WeMod) app 项目地址: https://gitcode.com/GitHub_Trending/we/Wand-Enhancer Wand 客户端一更新&#…

作者头像 李华
网站建设 2026/9/10 10:07:19

GE图引擎:aclGetTensorDescName API

aclGetTensorDescName 【免费下载链接】ge GE&#xff08;Graph Engine&#xff09;是面向昇腾的图编译器和执行器&#xff0c;提供了计算图优化、多流并行、内存复用和模型下沉等技术手段&#xff0c;加速模型执行效率&#xff0c;减少模型内存占用。 GE 提供对 PyTorch、Tens…

作者头像 李华