Amazon ECR Pull Through Cache Referrer 自动同步实测:让 cosign 在缓存镜像上直接验签¶
Lab 信息
- 难度: ⭐⭐ 中级
- 预估时间: 30 分钟
- 预估费用: < $0.01(ECR 存储 KB 级)
- Region: us-east-1
- 最后验证: 2026-04-18
背景¶
2026 年 4 月 17 日,Amazon ECR 为 Pull Through Cache(PTC)新增了一个看似低调但意义重大的能力:自动发现并同步上游仓库的 OCI Referrer(镜像签名、SBOM、attestation)。
之前的坑是这样:你的 EKS 集群用 PTC 从 public.ecr.aws 或 ghcr.io 拉镜像,签名验证策略(比如 Kyverno、Sigstore Policy Controller)想用 OCI 1.1 的 Referrers API 查签名 → ECR 只看本地 repo,返回空 → 验证失败。你必须写一个 workaround:先连 upstream 手动拉签名,再 push 到 ECR,或者绕开 PTC 直接连上游。
现在 ECR 让这一步 "消失" 了。用户侧只需要调 ListImageReferrers,ECR 的 service-linked role 会帮你去 upstream 把 referrer 拉回来缓存到私有 repo。从此 cosign verify 可以直接指向 ECR 私有 URL,端到端签名验证走通。
本文用 cosign + OCI Public 实测这个能力,覆盖核心功能、边界条件、CloudTrail 审计链,以及与非 PTC repo 的对比。
前置条件¶
- AWS 账号(需要
ecr:*、ecr-public:*、cloudtrail:LookupEvents) - AWS CLI v2 ≥ 2.34(含
ecr list-image-referrers命令) - Docker ≥ 20
- cosign v2.x(v3 CLI 变化较大,本文用 v2.4.1)
- oras(可选,用于验证 upstream referrer)
核心概念¶
| 项 | 说明 |
|---|---|
| OCI Referrer | OCI Distribution Spec 1.1 引入的机制,用 subject 字段把一个 artifact(签名/SBOM/attestation)挂到另一个 artifact(subject image)上 |
| Referrers API | Registry 提供 GET /v2/{name}/referrers/{digest} 端点,列出所有以该 digest 为 subject 的 artifact |
ECR ListImageReferrers API |
ECR 的等价物;同时也是触发 PTC 自动同步的唯一入口 |
| 同步模型 | Lazy pull on API call:docker pull 不同步,ListImageReferrers 才触发 |
| 缓存窗口 | Referrer 6 小时(镜像本身是 24 小时) |
| IAM 权限 | ecr:BatchGetImage(不是新 action) |
| 支持 upstream | 所有 PTC upstream:ECR Public / K8s / Quay / Docker Hub / Azure CR / GitHub CR / GitLab CR / Chainguard / ECR |
| Region | 除 China + GovCloud 外,所有支持 ECR PTC 的 Region |
| 新异常 | UnableToListUpstreamImageReferrersException(主要 Secrets Manager 凭据问题) |
关键差异表:
| 场景 | PTC repo ListImageReferrers | 普通 private repo ListImageReferrers |
|---|---|---|
| 有 upstream referrer | ✅ 返回(首次拉取,后续走 6h 缓存) | ❌ 返回空(只查本地) |
| 无 upstream referrer | ✅ 返回空,不报错 | ✅ 返回空 |
| IAM action | ecr:BatchGetImage |
ecr:BatchGetImage |
动手实践¶
Step 1: 创建 ECR Public PTC 规则¶
REGION=us-east-1
PROFILE=weichaol-testenv2-awswhatsnewtest
ACCOUNT=595842667825
aws ecr create-pull-through-cache-rule \
--ecr-repository-prefix ecr-public \
--upstream-registry-url public.ecr.aws \
--upstream-registry ecr-public \
--region $REGION --profile $PROFILE
输出:
{
"ecrRepositoryPrefix": "ecr-public",
"upstreamRegistryUrl": "public.ecr.aws",
"createdAt": "2026-04-18T03:56:23.728Z",
"registryId": "595842667825"
}
ECR Public 不需要凭据,这是最简单的 upstream。
Step 2: 在 upstream 准备一个带 cosign 签名的镜像¶
为了确定性地验证功能(而不是依赖某个第三方镜像碰巧有签名),我们自己做一个 upstream 镜像。
2.1 创建 ECR Public repo 并 push 一个 alpine 镜像:
# 创建 ECR Public repo(获得 public.ecr.aws/<alias>/<repo> URI)
aws ecr-public create-repository \
--repository-name archie-referrer-test \
--region us-east-1 --profile $PROFILE
# → uri: public.ecr.aws/l1j0r8q7/archie-referrer-test(你的 alias 会不同)
UPSTREAM=public.ecr.aws/l1j0r8q7/archie-referrer-test
# 构建一个简单镜像
cat > Dockerfile <<'EOF'
FROM public.ecr.aws/docker/library/alpine:3.19
LABEL test=ecr-ptc-referrers
EOF
docker build -t $UPSTREAM:v1 .
# Push 到 ECR Public
aws ecr-public get-login-password --region us-east-1 --profile $PROFILE | \
docker login --username AWS --password-stdin public.ecr.aws
docker push $UPSTREAM:v1
# → Digest: sha256:dcda66a37dd711038d97a31244940909aa4946fe4bda4a528f8f77034f73415b
2.2 用 cosign 签名(OCI 1.1 referrer 模式):
export COSIGN_PASSWORD=""
cosign generate-key-pair # 生成 cosign.key + cosign.pub
export COSIGN_EXPERIMENTAL=1 # cosign v2 的 OCI 1.1 模式仍在 experimental
DIGEST=sha256:dcda66a37dd711038d97a31244940909aa4946fe4bda4a528f8f77034f73415b
cosign sign \
--key cosign.key \
--tlog-upload=false \
--yes \
--registry-referrers-mode oci-1-1 \
$UPSTREAM@$DIGEST
输出关键行:
Pushing signature to: public.ecr.aws/l1j0r8q7/archie-referrer-test
Uploading signature for [...@sha256:dcda66a3...] to [...@sha256:3bc7fe5f...]
with config.mediaType [application/vnd.dev.cosign.artifact.sig.v1+json]
签名被作为一个独立 manifest(digest sha256:3bc7fe5f...)推送,并通过 subject 字段指向 sha256:dcda66a3...。
2.3 确认 upstream 确实有这个 referrer:
public.ecr.aws/l1j0r8q7/archie-referrer-test@sha256:dcda66a3...
└── application/vnd.dev.cosign.artifact.sig.v1+json
└── sha256:3bc7fe5f...
Step 3: 通过 PTC 拉镜像(观察 referrer 是否随 pull 同步)¶
PRIVATE=$ACCOUNT.dkr.ecr.$REGION.amazonaws.com/ecr-public/l1j0r8q7/archie-referrer-test
# 登录私有 ECR
aws ecr get-login-password --region $REGION --profile $PROFILE | \
docker login --username AWS --password-stdin $ACCOUNT.dkr.ecr.$REGION.amazonaws.com
# 第一次 pull,触发 PTC 创建 repo + 缓存镜像
docker pull $PRIVATE:v1
拉取成功后查看私有 repo 的 images:
aws ecr describe-images \
--repository-name ecr-public/l1j0r8q7/archie-referrer-test \
--region $REGION --profile $PROFILE
结果只有 3 个 image 对象(index + 2 个 child manifest),没有 referrer 被下载:
{
"imageDetails": [
{"imageDigest": "sha256:52163a1aaf8978...", "imageManifestMediaType": "application/vnd.oci.image.manifest.v1+json"},
{"imageDigest": "sha256:59a3cff5c2f4ff...", "imageManifestMediaType": "application/vnd.oci.image.manifest.v1+json"},
{"imageDigest": "sha256:dcda66a37dd711...", "imageTags": ["v1"], "imageManifestMediaType": "application/vnd.oci.image.index.v1+json"}
]
}
这是预期行为:docker pull 不触发 referrer 同步。官方文档明确:同步的触发点是 ListImageReferrers API 调用。
Step 4: 核心验证 — 首次 ListImageReferrers 触发自动同步¶
aws ecr list-image-referrers \
--repository-name ecr-public/l1j0r8q7/archie-referrer-test \
--subject-id imageDigest=$DIGEST \
--region $REGION --profile $PROFILE
第一次调用就返回了上游的签名:
{
"referrers": [
{
"digest": "sha256:3bc7fe5fb93a2fd5d3ded192dd009758786382d464ff2a669f2d39d22de9f052",
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"artifactType": "application/vnd.dev.cosign.artifact.sig.v1+json",
"size": 725
}
]
}
Digest sha256:3bc7fe5f... 与 Step 2.2 上游签名完全一致。ECR 在这次 API 调用时,用 service-linked role 到 public.ecr.aws 查了 referrer,拉下来,缓存到私有 repo,并返回给你。
Step 5: cosign verify 直接指向 ECR 私有 URL¶
这是读者真正关心的场景:部署时用 ECR 私有 URL,能不能直接验签?
# cosign v2 的 OCI 1.1 模式需要显式开启
export COSIGN_EXPERIMENTAL=1
cosign verify \
--key cosign.pub \
--insecure-ignore-tlog=true \
--experimental-oci11 \
$PRIVATE@$DIGEST
输出:
Verification for 595842667825.dkr.ecr.us-east-1.amazonaws.com/ecr-public/l1j0r8q7/archie-referrer-test@sha256:dcda66a3... --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- The signatures were verified against the specified public key
[{"critical":{"identity":{"docker-reference":"public.ecr.aws/l1j0r8q7/archie-referrer-test"},
"image":{"docker-manifest-digest":"sha256:dcda66a3..."},
"type":"cosign container image signature"},"optional":null}]
验证通过。 注意 docker-reference 字段仍然是 upstream 的 URL(签名创建时记录的),这符合 cosign 的设计 — 它只比对 digest,不强制 URL 一致。
Step 6: 边界测试 — upstream 没有 referrer 的镜像¶
# 通过 PTC 拉一个完全没签名的镜像(hello-world)
docker pull $ACCOUNT.dkr.ecr.$REGION.amazonaws.com/ecr-public/docker/library/hello-world:latest
HELLO_DIGEST=$(aws ecr describe-images \
--repository-name ecr-public/docker/library/hello-world \
--query 'imageDetails[?imageManifestMediaType==`application/vnd.oci.image.index.v1+json`]|[0].imageDigest' \
--output text --region $REGION --profile $PROFILE)
aws ecr list-image-referrers \
--repository-name ecr-public/docker/library/hello-world \
--subject-id imageDigest=$HELLO_DIGEST \
--region $REGION --profile $PROFILE
输出:
无 referrer 的镜像返回空数组,不报错。这保证了调用方可以无脑调 API,不用先判断镜像是否有签名。
Step 7: 对比 — 同 digest 在非 PTC repo 上的行为¶
创建一个普通 private repo,直接 push 同一个镜像:
aws ecr create-repository --repository-name archie-plain-referrer-test \
--region $REGION --profile $PROFILE
docker tag $UPSTREAM:v1 $ACCOUNT.dkr.ecr.$REGION.amazonaws.com/archie-plain-referrer-test:v1
docker push $ACCOUNT.dkr.ecr.$REGION.amazonaws.com/archie-plain-referrer-test:v1
# 同 digest: sha256:dcda66a37dd711...
aws ecr list-image-referrers \
--repository-name archie-plain-referrer-test \
--subject-id imageDigest=$DIGEST \
--region $REGION --profile $PROFILE
输出:
同样的 digest,非 PTC repo 不会去 upstream 查,返回空。 这个对比直接证实了 "PTC + ListImageReferrers" 是新能力的触发条件。
Step 8: 从 CloudTrail 看自动同步的服务内部调用链¶
START=$(date -u -d "15 minutes ago" +"%Y-%m-%dT%H:%M:%SZ")
END=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
aws cloudtrail lookup-events \
--start-time "$START" --end-time "$END" \
--lookup-attributes AttributeKey=EventName,AttributeValue=ListImageReferrers \
--region $REGION --profile $PROFILE
在事件里你会看到两类 ListImageReferrers:
① 用户发起(accessKey 是 AKIA...,userAgent 是 aws-cli):
eventName: ListImageReferrers
userIdentity.type: IAMUser
userIdentity.userName: awswhatsnewtest
userAgent: aws-cli/2.34.29 ...
sourceIPAddress: <your-IP>
② ECR 服务内部发起(accessKey 是 ASIA... 临时会话,关键字段全指向 ECR 自己):
eventName: ListImageReferrers
userIdentity.invokedBy: ecr.amazonaws.com
userIdentity.sessionContext: { creationDate, mfaAuthenticated: false }
userAgent: ecr.amazonaws.com
sourceIPAddress: ecr.amazonaws.com
在同一时间窗口,你还会看到 ECR 服务自动发起的 BatchGetImage、GetDownloadUrlForLayer、PutImage(把 referrer manifest 写入私有 repo)— 它们都带 invokedBy: ecr.amazonaws.com。
这是 PTC 自动同步行为的完整审计链,对合规场景非常有价值。
测试结果¶
| # | 测试场景 | 结果 | 关键数据 | 备注 |
|---|---|---|---|---|
| T1 | 创建 ECR Public PTC rule | ✅ 成功 | ecrRepositoryPrefix=ecr-public |
|
| T2 | docker pull 不同步 referrer | ✅ 符合预期 | describe-images 无 referrer | API 触发模型 |
| T3 | 首次 list-image-referrers 同步 cosign 签名 | ✅ | digest sha256:3bc7...,size 725 B |
核心功能 |
| T4 | cosign verify 指向 ECR 私有 URL | ✅ 通过 | 需要 --experimental-oci11 |
端到端验证 |
| T5 | 二次 list-image-referrers 走缓存 | ✅ | 返回一致,耗时 ~1.6s | 6h 缓存窗口 |
| T6 | upstream 无 referrer 的镜像 | ✅ 空数组不报错 | {"referrers": []} |
优雅降级 |
| T7 | 非 PTC repo 同 digest | ✅ 空数组 | 与 PTC repo 形成对比 | 功能边界清晰 |
| T8 | CloudTrail 审计链 | ✅ | 2 类事件(用户 + ECR 服务) | invokedBy=ecr.amazonaws.com(实测发现,官方未明示) |
踩坑记录¶
踩坑 1:docker pull 不触发 referrer 同步
直觉上以为 "pull 镜像 = 同步完整供应链 artifact",实际不是。Referrer 只有在 ListImageReferrers API 被调用时才同步。
对读者的影响:如果你只用 docker pull + 运行镜像,signature/SBOM 不会自动出现在私有 repo(因此也不占存储)。但如果你用 Kyverno / Sigstore Policy Controller / Admission Webhook 验证签名,它们会调 Referrers API,这时同步自动发生。
→ 已查文档确认。官方 pull-through-cache.html:"Calling the ListImageReferrers API to a pull through cache created repository returns the OCI-compliant referrer artifacts to the private cache."
踩坑 2:cosign v3 的 --tlog-upload=false 不能单独用
cosign v3 把 transparency log 配置移到了 --signing-config 文件里,直接用 --tlog-upload=false 会报错:
解决:离线签名(不打 Rekor)的最简办法是用 cosign v2.4.1;或者给 v3 提供自定义 signing-config。
踩坑 3:cosign verify 默认走 legacy .sig tag,访问 ECR 时 404
不加 --experimental-oci11 时,cosign 会尝试访问 sha256-<digest>.sig 这种老格式 tag:
.sig tag。
解决:export COSIGN_EXPERIMENTAL=1 && cosign verify --experimental-oci11 ...
→ 实测发现,cosign 工具默认行为问题,与 ECR 无关。
观察 1:Referrer 同步延迟可忽略
实测首次 list-image-referrers 返回耗时 ~1 秒(包含 ECR 从 upstream 拉 referrer manifest),二次调用在 6h 缓存窗口内,差异不明显,因为 cosign bundle 只有几百字节。对于大型 SBOM(可能 MB 级),首次延迟会更显著 — 建议在生产场景预热(pre-fetch)关键镜像的 referrer。
→ 实测数据,官方未记录性能基线。
费用明细¶
| 资源 | 单价 | 用量 | 费用 |
|---|---|---|---|
| ECR 私有存储 | $0.10/GB/月 | ~10 MB(3 repos) | < $0.001/月 |
| ECR Public 存储 | 免费(50GB/月配额) | <10 MB | $0 |
| ECR API 调用 | 前 100 万免费 | ~80 次 | $0 |
| 数据传输(ECR Public → ECR 私有,同 Region) | 免费 | <50 MB | $0 |
| 合计 | < $0.01 |
Referrer artifact 本身(cosign bundle ~1KB,SLSA provenance ~1KB,SBOM 通常几十 KB 到 MB)按 ECR 存储正常计费,无额外费用类别。
清理资源¶
REGION=us-east-1
PROFILE=weichaol-testenv2-awswhatsnewtest
# 1. 删除 PTC rule(rule 删除不会清掉已缓存的 repo,要单独删)
aws ecr delete-pull-through-cache-rule \
--ecr-repository-prefix ecr-public \
--region $REGION --profile $PROFILE
# 2. 删除 PTC 自动创建的私有 repo(用 --force 连同镜像一起)
aws ecr delete-repository --force \
--repository-name ecr-public/l1j0r8q7/archie-referrer-test \
--region $REGION --profile $PROFILE
aws ecr delete-repository --force \
--repository-name ecr-public/docker/library/hello-world \
--region $REGION --profile $PROFILE
# 3. 删除对比用的普通 repo
aws ecr delete-repository --force \
--repository-name archie-plain-referrer-test \
--region $REGION --profile $PROFILE
# 4. 删除 ECR Public 上的 upstream repo
aws ecr-public delete-repository --force \
--repository-name archie-referrer-test \
--region us-east-1 --profile $PROFILE
务必清理
虽然费用微乎其微,ECR Public repo 长期留存会在你的 account alias 下公开可见,建议清理。
场景化建议¶
| 场景 | 建议 |
|---|---|
| Kyverno / Sigstore Policy Controller 验签 | ✅ 直接升级到支持 Referrers API 的版本,无需改 workflow |
| EKS Pod 准入控制 | ✅ Admission webhook 内调 ListImageReferrers 即可,不需要先拉 upstream |
| 大型 SBOM/attestation | ⚠️ 首次调用会有秒级延迟,生产场景建议预热关键镜像 |
| 跨账号 ECR PTC + referrer | ✅ 同样适用,依赖 IAM role 跨账号访问 |
| Chainguard / GHCR 私有镜像 | ✅ 同样适用,需要正确配置 Secrets Manager 凭据 |
| 不想让 referrer 占用私有 repo 存储 | ⛔ 目前没有 opt-out 选项,调了 API 就会缓存 |
一句话总结¶
ECR PTC 现在把 OCI 1.1 Referrer 的 "upstream 寻址" 对用户透明化了:你调 ListImageReferrers,ECR 就帮你跑一趟 upstream 把签名/SBOM/attestation 拉回来。cosign verify、Kyverno 策略、Admission Webhook 这些签名验证工作流,可以第一次在 ECR PTC 模式下无 workaround 地运行。