cosign&harbor2.5.5

cosign 签名镜像(simple sign),签名上传至 harbor,harbor 如何存储签名,手动模拟 cosign 签名过程,镜像签名与 harbor 特性(如复制规则、垃圾清理、tag保留)的适配性

cosign & harbor 实验

install cosign

UTM virtual host | Linux kali 6.0.0-kali3-arm64 #1 SMP Debian 6.0.7-1kali1 (2022-11-07) aarch64 GNU/Linux

wget https://github.com/sigstore/cosign/releases/download/v2.2.2/cosign-linux-arm64
install -m 777 cosign-linux-arm64 /usr/local/bin/cosign
rm cosign-linux-arm64

# verify
cosign version

about harbor

installed harbor v2.5.5, server at Internal Network https://10.120.1.233:8443

Labs

签名镜像并上传签名

# 生成密钥对
cosign generate-key-pair
# 拉取/推送一个未签名的镜像
docker pull 10.120.1.233:8443/zyydebug/test:dev
docker push 10.120.1.233:8443/zyydebug/test:dev

image-20231225115250084

# 签名
cosign sign -key cosign.key 10.120.1.233:8443/zyydebug/test:dev
# 见下图。签名失败了。主机未置信 harbor 根证书。

image-20231225115443383

image-20231225115732611

证书链上选中终端证书上一层的证书,导出,保存为 ca.crt,传到主机上

# 配置 docker 信任证书(而不是直接修改ssl bundle)
mkdir -p '/etc/docker/certs.d/10.120.1.233:8443'
install -m 644 ca.crt '/etc/docker/certs.d/10.120.1.233:8443/ca.crt'

# verify
ls -R /etc/docker/certs.d

# 再次签名,失败; 报错和之前一样
cosign sign -key cosign.key 10.120.1.233:8443/zyydebug/test:dev
# 重载 docker
systemctl daemon-reload; systemctl restart docker
# 再次签名,失败; 报错也和之前一样
cosign sign -key cosign.key 10.120.1.233:8443/zyydebug/test:dev

# 查看帮助文档,没找到配置 registry 证书的地方,但是找到了 registry-insecure 的配置方式
cosign sign --help
# [registry-insecure] 签名,签名成功
cosign sign -key cosign.key --allow-insecure-registry=true 10.120.1.233:8443/zyydebug/test:dev

### 注:经测试,配置证书到 /etc/ssl 内做主机信任(具体配置方式视不同发行版而定)也可以签名成功

image-20231225124853013

image-20231225125416399

注:至此,注意到 -key 会给 warning,下文改用 --key

经查询文档,确认 cosign 支持从 tag 来引用镜像,但是签名是签在 digest 上的。如果 tag 指向的是一个多架构镜像,则实际签名的 digest 并不是任何一个子镜像,而是签在这个 manifest 上。下图即在页面上删除了之前上传的签名,然后给多架构镜像签名的结果

image-20231225163405883

签名文件实际上是以 blob 的形式存储在 storage 层,并以 tag 的形式附加到镜像,所以镜像的 sign 可以作为 image-artifact 的一部分被一起迁移。详见 Cosign Signature Spec

image-20231225163906917

如果对 87aa8 这个 degist 多次重复签名,签名会保存在 _manifest/tags/sha256-87aa8... .sig

image-20231225170217748

以 666fc95 为例,其对应的 blob/sha256/66/…/data 的内容是一个 json 文本。

image-20231225170334443

image-20231225170602227

注意到 layers[*].annotations[dev.sigstore.cosign/bundle]:Payload.logIndex,这个就是 tlog 的序列号,签名时 tlog 将被传到公网。

$ cosign sign \
--key cosign.key \
--allow-insecure-registry=true \
--tlog-upload=false \
--upload=true \
10.120.1.233:8443/zyydebug/test@sha256:87aa817e17903f32769d257e980555f6382c6ac75964929c654b04d1c0826243
Enter password for private key:
Pushing signature to: 10.120.1.233:8443/zyydebug/test
参数 解释 默认值
–key 签名用私钥|格式:密钥文件路径
–allow-insecure-registry disable repository TLS verify false
–tlog-upload 上传 tlog(需要访问公网,或自定的tlog server) true
–upload 是否推送镜像签名到远程 true
–output-payload 签名算法的输入|格式:文件路径
–output-signature 签名算法的输出|格式:文件路径

验证签名(不上传)

# 选中镜像
IMAGE=10.120.1.233:8443/zyydebug/test@sha256:87aa817e17903f32769d257e980555f6382c6ac75964929c654b04d1c0826243

# 生成密钥对
cosign generate-key-pair

# 签名:签名tlog不上传、签名本地也不上传,输出在本地
cosign sign --key=cosign.key \
--tlog-upload=false \
--upload=false \
--output-signature=signature.dat \
--output-payload=payload.dat $IMAGE
┌──(root㉿kali)-[~/lab_cosign]
└─# cat signature.dat
MEQCIEMEcEbkPx7YihEJBkMZ7vEbwk074j9s409ZN/tbp0jdAiA7NWSXto1QAXo0FYpEE1r+qgNPMcvbL4KDjR/azNA2fg==
┌──(root㉿kali)-[~/lab_cosign]
└─# cat payload.dat | jq
{
"critical": {
"identity": {
"docker-reference": "10.120.1.233:8443/zyydebug/test"
},
"image": {
"docker-manifest-digest": "sha256:87aa817e17903f32769d257e980555f6382c6ac75964929c654b04d1c0826243"
},
"type": "cosign container image signature"
},
"optional": null
}
# 验证签名,并输出签名内容
cosign verify \
--key cosign.pub \
--signature=signature.dat \
--payload=payload.dat \
--insecure-ignore-tlog=true $IMAGE

# Output Example(format pretty):
[
{
"critical": {
"identity": {
"docker-reference": "10.120.1.233:8443/zyydebug/test"
},
"image": {
"docker-manifest-digest": "sha256:87aa817e17903f32769d257e980555f6382c6ac75964929c654b04d1c0826243"
},
"type": "cosign container image signature"
},
"optional": null
}
]

# 验证签名 payload -> signature
cosign verify-blob \
--key cosign.pub \
--signature signature.dat \
--insecure-ignore-tlog=true payload.dat

cosign 从 harbor 下载镜像签名

┌──(root㉿kali)-[~/lab_cosign/verify]
└─# cosign download signature 10.120.1.233:8443/zyydebug/test:dev | jq
{
"Base64Signature": "MEUCIQDWrW/mlCSB1OqMSYk8QRYnze4KIYvPE6uQyB66/A7qfwIgCmN2DlLfDJ9VXdfv+HEfJC2gxJSoCeEH4Za4dhGsE2E=",
"Payload": "eyJjcml0aWNhbCI6eyJpZGVudGl0eSI6eyJkb2NrZXItcmVmZXJlbmNlIjoiMTAuMTIwLjEuMjMzOjg0NDMvenl5ZGVidWcvdGVzdCJ9LCJpbWFnZSI6eyJkb2NrZXItbWFuaWZlc3QtZGlnZXN0Ijoic2hhMjU2Ojg3YWE4MTdlMTc5MDNmMzI3NjlkMjU3ZTk4MDU1NWY2MzgyYzZhYzc1OTY0OTI5YzY1NGIwNGQxYzA4MjYyNDMifSwidHlwZSI6ImNvc2lnbiBjb250YWluZXIgaW1hZ2Ugc2lnbmF0dXJlIn0sIm9wdGlvbmFsIjpudWxsfQ==",
"Cert": null,
"Chain": null,
"Bundle": null,
"RFC3161Timestamp": null
}
┌──(root㉿kali)-[~/lab_cosign/verify]
└─# cosign download signature 10.120.1.233:8443/zyydebug/test:dev | jq -r ".Payload" | base64 -d | jq
{
"critical": {
"identity": {
"docker-reference": "10.120.1.233:8443/zyydebug/test"
},
"image": {
"docker-manifest-digest": "sha256:87aa817e17903f32769d257e980555f6382c6ac75964929c654b04d1c0826243"
},
"type": "cosign container image signature"
},
"optional": null
}
# harbor nginx log about cosign download signature
[26/Dec/2023:09:19:28 +0000]:10.10.136.50 - "GET /v2/ HTTP/1.1" 401 76 "-" "cosign/v2.2.2 (linux; arm64) go-containerregistry/v0.17.0" 0.006 0.006 .
[26/Dec/2023:09:19:28 +0000]:10.10.136.50 - "GET /service/token?scope=repository%3Azyydebug%2Ftest%3Apull&service=harbor-registry HTTP/1.1" 200 967 "-" "cosign/v2.2.2 (linux; arm64) go-containerregistry/v0.17.0" 0.086 0.086 .
[26/Dec/2023:09:19:28 +0000]:10.10.136.50 - "GET /v2/zyydebug/test/manifests/dev HTTP/1.1" 200 1365 "-" "cosign/v2.2.2 (linux; arm64) go-containerregistry/v0.17.0" 0.221 0.221 .
[26/Dec/2023:09:19:28 +0000]:10.10.136.50 - "GET /v2/zyydebug/test/manifests/sha256-87aa817e17903f32769d257e980555f6382c6ac75964929c654b04d1c0826243.sig HTTP/1.1" 200 558 "-" "cosign/v2.2.2 (linux; arm64) go-containerregistry/v0.17.0" 0.200 0.201 .
[26/Dec/2023:09:19:28 +0000]:10.10.136.50 - "GET /v2/zyydebug/test/blobs/sha256:4549abebd4a503f64d874955e3f80518960bfb1aea6d2e0da4bf4b382ced029f HTTP/1.1" 200 247 "-" "cosign/v2.2.2 (linux; arm64) go-containerregistry/v0.17.0" 0.176 0.176 .

拉取镜像时验证签名

IMAGE=10.120.1.233:8443/zyydebug/test:dev

# 获取镜像 manifest
# harbor API: https://10.120.1.233:8443/v2/zyydebug/test/manifests/dev
crane manifest 10.120.1.233:8443/zyydebug/test:dev | jq | tee manifest.json
# 从镜像 manifest 获取 sha256-digest
image_digest=$(crane manifest 10.120.1.233:8443/zyydebug/test:dev | sha256sum | cut -d' ' -f1)

# 获取签名
# harbor API: https://10.120.1.233:8443/v2/zyydebug/test/manifests/sha256-87aa817e17903f32769d257e980555f6382c6ac75964929c654b04d1c0826243.sig
crane manifest 10.120.1.233:8443/zyydebug/test:sha256-87aa817e17903f32769d257e980555f6382c6ac75964929c654b04d1c0826243.sig | jq | tee signature.json
# pretty print result
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"config": {
"mediaType": "application/vnd.oci.image.config.v1+json",
"size": 233,
"digest": "sha256:f6febe23ffd09309aea4d4a8422f0214ce10849b00b459be4313eac2d36d4678"
},
"layers": [
{
"mediaType": "application/vnd.dev.cosign.simplesigning.v1+json",
"size": 247,
"digest": "sha256:4549abebd4a503f64d874955e3f80518960bfb1aea6d2e0da4bf4b382ced029f",
"annotations": {
"dev.cosignproject.cosign/signature": "MEUCIQDWrW/mlCSB1OqMSYk8QRYnze4KIYvPE6uQyB66/A7qfwIgCmN2DlLfDJ9VXdfv+HEfJC2gxJSoCeEH4Za4dhGsE2E="
}
}
]
}
# 保存当前镜像的签名到变量
download_signature=$(crane manifest 10.120.1.233:8443/zyydebug/test:sha256-87aa817e17903f32769d257e980555f6382c6ac75964929c654b04d1c0826243.sig | jq -r '.layers[0].annotations["dev.cosignproject.cosign/signature"]')

# 生成 payload(流程展示)
# --- json --- 填入 docker-reference 的值;填入 docker-manifest-digest 的值 $image_digest
{
"critical": {
"identity": {
"docker-reference": "10.120.1.233:8443/zyydebug/test"
},
"image": {
"docker-manifest-digest": "sha256:87aa817e17903f32769d257e980555f6382c6ac75964929c654b04d1c0826243"
},
"type": "cosign container image signature"
},
"optional": null
}
# --- jq -c --- short json in one line --- make sure that no \n at end; that is the payload for `cosign verify-blob`
# --- base64 -w 0 --- do base64 encode; that is the accurate input for cosign's signature algorithm. useless for the lab.

# 获取已经生成好的 payload(方便版)
payload=$(cosign download signature 10.120.1.233:8443/zyydebug/test:dev | jq -r ".Payload" | base64 -d)

# cosign 验签
echo -n $download_signature > sign.dat
echo -n $payload > payload.dat
cosign verify-blob \
--key cosign.pub \
--signature sign.dat \
--insecure-ignore-tlog=true payload.dat

┌──(root㉿kali)-[~/lab_cosign/verify]
└─# download_signature=$(crane manifest 10.120.1.233:8443/zyydebug/test:sha256-87aa817e17903f32769d257e980555f6382c6ac75964929c654b04d1c0826243.sig | jq -r '.layers[0].annotations["dev.cosignproject.cosign/signature"]')
payload=$(cosign download signature 10.120.1.233:8443/zyydebug/test:dev | jq -r ".Payload" | base64 -d)
echo -n $download_signature > sign.dat
echo -n $payload > payload.dat
cosign verify-blob \
--key cosign.pub \
--signature sign.dat \
--insecure-ignore-tlog=true payload.dat
WARNING: Skipping tlog verification is an insecure practice that lacks of transparency and auditability verification for the blob.
Verified OK

harbor镜像复制,是否会携带签名一起转移

测试环境

# build.sh
cat > Dockerfile <<'EOF'
from scratch
add file /file
EOF
echo "hello world" > file
docker build -t 10.120.1.233:8443/zyydebug/signature:v1 .
docker push 10.120.1.233:8443/zyydebug/signature:v1
bash -x build.sh
cosign generate-key-pair
cosign sign --key=cosign.key \
--tlog-upload=false \
--upload=true 10.120.1.233:8443/zyydebug/signature:v1

测试结果

测试 签名是否复制
pull-base
push-base
manual-docker-pull-push
manual-crane-cp
manual-skopeo-cp

补充说明:对于未复制签名的情况,把签名一起复制了就好了。例如

crane --insecure cp \
10.120.1.233:8443/zyydebug/signature:sha256-70263dea0fafa899bfc9aa37fbf3a669e48dd15a277f7eaee821afa61eebf570.sig \
10.120.1.233:8443/zyydebug-crane/signature:sha256-70263dea0fafa899bfc9aa37fbf3a669e48dd15a277f7eaee821afa61eebf570.sig

harbor GC测试

  • 删签名,不删镜像,签名 blob 会被 GC 么:会
  • 删签名,再删除镜像artifact,镜像 blob 和签名 blob 会被 GC 么:会
  • 不删签名,直接删镜像,镜像 blob 和签名 blob 会被 GC 么:会

image-20231226201910769

Q&A

Q: harbor 是仅存储 cosign 签名还是也会尝试解析、验证签名?

A: harbor 既没有签名的公钥或私钥,也没相关的证书链,无从解析签名。

考虑到签名在 repository 中的存储形式,不验证它,是非常合理的。

虽然没有具体明确的证据,但是我认为 harbor 不会尝试解析 cosign 签名。

Q: 签名以 tag 的形式链接到 image-artifact,那么它会被 tag 保留策略删除么?

测了,签名不会被 tag 保留策略删除。但是暂时未找到签名tag和普通tag在数据库中或源代码处理上有什么不一样。

参考文档