在容器镜像的分发链条中,镜像从构建到最终运行要经过网络传输、镜像仓库等多个环节,任何一个环节都可能存在被篡改的风险。Notary 正是为了解决这个问题而生的工具,它基于 TUF(The Update Framework)协议实现了一套完整的信任模型,可以对镜像进行数字签名和完整性校验。Harbor 私有仓库的镜像签名能力就是通过集成 Notary 组件实现的。本文将围绕 Notary 服务的架构原理、部署步骤和客户端使用三个方面展开,帮助你搭建一套可用的镜像签名环境。

一、Notary 的架构原理与信任模型
Notary 服务由两个核心进程组成:notary-server 和 notary-signer。前者面向客户端提供 API 接口,负责接收签名请求、存储签名元数据;后者持有签名私钥,负责实际的签名运算。两者之间通过 gRPC 通信,并且使用 TLS 证书进行双向认证,这种拆分设计让私钥可以隔离在独立的进程中,即使 server 被攻破,私钥也不会直接泄露。
元数据存储方面,Notary 默认使用 MySQL 或 PostgreSQL 保存 TUF 元数据文件。TUF 协议定义了多角色密钥体系:root 密钥是整个信任链的根,targets 密钥负责对具体目标(镜像 tag)签名,snapshot 密钥对文件列表快照签名,timestamp 密钥则保证元数据的时效性。客户端拉取镜像时会校验这套信任链,任何一环签名不合法都会导致验签失败。
与 Docker 集成时,客户端通过环境变量 DOCKER_CONTENT_TRUST=1 开启内容信任,Docker 会自动与 Notary 服务交互完成签名与验签,开发者无需手动操作元数据文件。理解这套机制对后续排查签名失败问题很有帮助。
二、使用 Docker Compose 部署 Notary 服务
部署 Notary 最便捷的方式是直接使用官方提供的 docker-compose 编排文件。如果同时使用 Harbor,也可以在安装 Harbor 时勾选 notary 组件,安装器会自动完成整合。下面以独立部署为例说明关键步骤。
首先准备证书。Notary 需要 server 与 signer 之间的通信证书,以及对外提供服务的 TLS 证书。可以用 openssl 快速生成一套测试证书:
# 生成 CA 私钥 openssl genrsa -out ca-key.pem 4096 # 生成 CA 证书 openssl req -x509 -new -nodes -key ca-key.pem -sha256 -days 1024 -out ca.pem -subj "/CN=notary-ca" # 生成 server 私钥并签发证书 openssl genrsa -out server-key.pem 4096 openssl req -new -key server-key.pem -out server.csr -subj "/CN=notary-server" openssl x509 -req -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -days 1024
证书准备好后,配置 server 端。server 配置文件 server-config.json 中需要指定服务端口、TLS 证书路径和数据库连接信息:
{
"server": {
"addr": ":4443",
"tls_key_file": "/certs/server-key.pem",
"tls_cert_file": "/certs/server-cert.pem",
"client_ca_file": ""
},
"trust_service": {
"type": "remote",
"hostname": "notary-signer",
"port": "7899",
"tls_ca_file": "/certs/ca.pem",
"key_algorithm": "ecdsa"
},
"storage": {
"backend": "mysql",
"db_url": "server@tcp(mysql:3306)/notaryserver?parseTime=True"
}
}
signer 端配置类似,主要区别在于 storage 指向 notarysigner 数据库,并且需要配置自身的服务证书供 server 校验。数据库需要提前创建对应的库和用户,并导入 Notary 源码中的初始化 SQL 脚本(scripts/mysql/ 目录下的 schemas)。
最后通过 docker-compose 启动:
git clone https://github.com/theupdateframework/notary.git cd notary docker-compose up -d docker-compose ps # 确认 server、signer、mysql 均为运行状态
服务启动后可以用 curl https://127.0.0.1:4443/v2/ -k 做简单探测,返回正常说明服务已就绪。
三、客户端签名与验签实战
服务端部署完成后,客户端需要配置信任环境。将 CA 证书复制到 /etc/docker/certs.d/notary服务器域名:4443/ 目录下(文件命名为 ca.crt),这样 Docker 与 Notary 通信时才能校验服务端身份。
签名推送镜像的流程如下:
export DOCKER_CONTENT_TRUST=1 export DOCKER_CONTENT_TRUST_SERVER=https://notary.ippipp.com:4443 docker login registry.ippipp.com docker tag nginx:latest registry.ippipp.com/library/nginx:1.25 docker push registry.ippipp.com/library/nginx:1.25 # 首次推送时 Notary 会要求设置 root 密码与 repository 密码 # root 密码用于保护根密钥(默认存放在 ~/.docker/trust/private) # repository 密码用于保护当前仓库的签名密钥
首次推送时客户端会在本地生成 root 密钥并存放在 ~/.docker/trust 目录,务必妥善备份该目录,root 密钥一旦丢失就只能重建信任链。拉取验证则在开启内容信任后自动进行:
export DOCKER_CONTENT_TRUST=1 export DOCKER_CONTENT_TRUST_SERVER=https://notary.ippipp.com:4443 docker pull registry.ippipp.com/library/nginx:1.25 # 若镜像未签名或签名校验失败,将直接报错拒绝拉取
也可以使用 notary 命令行工具独立查询签名状态:notary -s https://notary.ippipp.com:4443 list registry.ippipp.com/library/nginx,该命令会列出当前仓库各 tag 的签名信息,方便在 CI 流水线中做校验。
四、常见问题与排查思路
部署过程中最容易遇到的是证书问题。典型报错如 x509: certificate signed by unknown authority,说明客户端没有导入 CA 证书,或者证书的 CN/SAN 与访问地址不一致。排查时先用 openssl 检查证书内容: openssl x509 -in server-cert.pem -text -noout,确认 Subject Alternative Name 是否覆盖了实际访问的域名或 IP。
第二类常见问题是数据库未初始化导致 server 启动即退出,查看容器日志 docker logs notary-server 会看到表不存在的错误,需要确认初始化 SQL 已经执行。第三类是首次签名时密钥管理报错,例如忘记了之前设置的 root 密码,此时可以删除 ~/.docker/trust/private 下的密钥文件重新初始化,但已有仓库的签名记录会作废。
还有一个容易踩坑的点:Harbor 集成场景下,Notary 的端口通常映射为 4443,客户端必须通过 DOCKER_CONTENT_TRUST_SERVER 显式指向该端口,否则 Docker 默认去找 443 端口导致连接失败。把这些细节提前确认好,签名环境基本可以一次搭建成功。
总结
Notary 通过 TUF 信任模型为镜像分发提供了签名与校验能力,server 与 signer 分离的架构保证了私钥安全,配合 Docker 的内容信任机制,客户端只需设置环境变量即可实现透明的签名与验签。部署时的关键点集中在证书体系配置、数据库初始化和密钥备份三处,尤其是 root 密钥的备份不可忽视。在供应链安全日益重要的当下,为私有仓库启用镜像签名是投入产出比很高的一项加固措施。
Notary服务部署Harbor镜像签名Docker Content Trust修改时间:2026-09-09 01:12:52