BuildKit 镜像 Attestation 存储格式详解从 OCI Artifact 到 in-toto 语句的完整剖析【免费下载链接】buildkitconcurrent, cache-efficient, and Dockerfile-agnostic builder toolkit项目地址: https://gitcode.com/GitHub_Trending/bu/buildkitBuildKit 在构建产物上创建并挂载 attestation证明为供应链安全提供 SBOM、SLSA Provenance、构建日志等可信信息。本文以 docs/attestations/attestation-storage.md 为骨架深入剖析 attestation 在镜像中的存储格式OCI Artifact 存储与 legacy manifest 格式、image index → attestation manifest → attestation blob三级对象结构、贯穿其中的关键 annotation 约定并结合仓库源码exporter/containerimage/writer.go、exporter/attestation/make.go 等验证每一条存储规则的底层实现。读完本文你将能徒手解析任意 BuildKit 产出的镜像 attestation理解oci-artifact导出选项的切换逻辑并掌握 80 MiB 大小限制等边界行为。背景BuildKit 的 Attestation 是什么Attestation 是附着在构建产物上的、来自构建过程的元数据可用于证明产物的来源与内容。常见的 attestation 类型包括SBOMSoftware Bill of Materials构建过程中使用的软件清单通常以 SPDX 格式编码SLSA Provenance描述构建过程与供应链来源的可验证记录SLSA Provenance 规范构建日志等其他过程信息。在 BuildKit 中attestation 的生成、传递与存储是一条完整链路frontend如 Dockerfile frontend生成 attestation 文件 → 通过exporter.Attestation结构传递给 exporter → 由 exporter 组装为 OCI 对象写入镜像。本文聚焦链路末端这些 attestation 最终以什么格式、什么结构、什么 annotation 被存储到镜像中。核心结论先行BuildKit 默认将 attestation 存储为 OCI artifactOCI media types 启用时并以manifest对象形式挂载在image index上若设置镜像导出选项oci-artifactfalse则回退到 legacy attestation image manifest 格式。存储架构总览三级对象结构BuildKit 的 attestation 存储遵循 OCI 规范由三个层级组成Image Index根镜像索引最顶层的 OCI image index同时引用“可运行镜像 manifest”和“attestation manifest”两类描述符Attestation Manifest证明清单一个独立的 OCI image manifest通过artifactType、subject、config、layers四个关键字段组织证明Attestation Blob证明数据体manifest 的每个 layer 中承载的实际 in-toto statement JSON。其中每个 attestation manifest 可以包含多个 attestation blob且一个 manifest 内的全部 attestation 都作用于同一个平台 manifest。这意味着多平台构建时每个平台的 attestation 会被分别组织为各自独立的 attestation manifest。Attestation Manifest证明的载体字段语义与两种存储模式Attestation manifest 附着在根 image index 下作为一个独立的 OCI image manifest。它的全部属性遵循标准 OCI / Docker manifest 规范。两种存储模式的差异集中在config与artifactType字段上字段OCI Artifact 模式默认Legacy 模式oci-artifactfalseartifactTypeapplication/vnd.docker.attestation.manifest.v1json不设置subject指向目标镜像 manifest 的 descriptor不设置configOCI 空 JSON descriptorapplication/vnd.oci.empty.v1json指向一个合法的 image configlayers每个 layer 承载一个 attestation blob同左在 OCI artifact 模式下manifest 的subject描述符直接指向目标镜像 manifestdigest、size、mediaType三要素齐全。源码中该逻辑位于 exporter/containerimage/writer.goif ociArtifact { mfst.ArtifactType attestationManifestArtifactType mfst.Subject ocispecs.Descriptor{ Digest: target.Digest, Size: target.Size, MediaType: target.MediaType, } }其中常量attestationManifestArtifactType application/vnd.docker.attestation.manifest.v1json定义于同文件 exporter/containerimage/writer.go。config使用 OCI 空 JSON descriptorsha256:44136fa355b3...size 为 2data 为e30即ocispecs.DescriptorEmptyJSON。在 legacy 模式下ociArtifact为 falseconfig指向一个合法的 image config由attestationsConfig()函数生成exporter/containerimage/writer.go其Architecture/OS固定为intotoPlatform即unknown/unknownRootFS.DiffIDs逐一对应每个 attestation layer 的LabelUncompressed注解。该 config不包含任何 attestation 专属信息仅为了兼容性而存在消费者应直接忽略其内容。layers 与 mediaType 约定manifest 的每个layers条目承载一个 attestation blob。layer 的mediaType依 blob 内容设定当前唯一支持的值是application/vnd.in-totojson表示一个 in-toto attestation blob。源码中以intoto.PayloadType作为 layer 的 mediaTypeexporter/containerimage/writer.godesc : ocispecs.Descriptor{ MediaType: intoto.PayloadType, Digest: digest, Size: int64(len(data)), Annotations: map[string]string{ labels.LabelUncompressed: digest.String(), in-toto.io/predicate-type: statement.PredicateType, }, }对于未知的 mediaType规范要求忽略该 layer这是向前兼容的关键设计——未来新增 attestation 类型不会破坏旧消费者。层描述符上的in-toto.io/predicate-type注解为帮助消费方在无需拉取全部内容的情况下快速定位目标 attestation每个 layer 描述符可以携带如下注解in-toto.io/predicate-type当 layer 是 in-toto attestation 时当前唯一支持的场景设置其值与 attestation 内部predicateType字段完全相同。消费方如 source/containerimage/source.go、solver/llbsolver/history.go、vendor 中的 moby/policy-helpers/image/resolve.go正是通过读取该注解来筛选特定 predicate 类型的证明从而避免拉取无关 blob。Attestation Blobin-toto Statement 数据体每个 layer 的内容是一个依赖mediaType的 blob。对于application/vnd.in-totojsonblob 内容是完整的 in-toto attestation statement结构如下{ _type: https://in-toto.io/Statement/v1, subject: [ { name: NAME, digest: {ALGORITHM: HEX_VALUE} }, ... ], predicateType: URI, predicate: { ... } }关键约束statement 的subject必须与 Attestation Manifest Descriptor 中描述的目标 manifest 的 digest 一致或者是目标 manifest 内部某个对象的 digest。这建立了证明与产物之间的可验证绑定关系。80 MiB 大小限制BuildKit 在将 attestation 文件包装为 in-toto statement 之前将每次从构建结果中读取的 attestation 文件限制为 80 MiB以保护 exporter 免受 frontend 提供的超大 attestation 文件的无界读取。该限制的源码实现位于 exporter/attestation/make.goconst maxAttestationBytes int64 80 20读取路径使用io.LimitedReader实现“读到 limit1 字节即停”的检测逻辑exporter/attestation/make.gofunc readAllLimited(r io.Reader, name string, limit int64) ([]byte, error) { limited : io.LimitedReader{R: r, N: limit 1} dt, err : io.ReadAll(limited) ... if limited.N 0 { return nil, errors.Errorf(%s exceeds %d bytes, name, limit) } return dt, nil }即若读取后limited.N 0说明文件长度超过 80 MiB直接报错。解包unbundle方向同样施加了该限制exporter/attestation/unbundle.go。这一约束的实际意义在 docs/attestations/sbom.md 中有直观说明大型 SPDX JSON 文档含详细的 file、package、relationship 元数据容易逼近该上限。Attestation Manifest Descriptor挂载与遍历约定Attestation manifest 通过根 image index 的manifests键挂载位置在所有原始可运行 manifest 之后。其描述符遵循标准 OCI / Docker manifest descriptor 规范并额外附加两类关键信息防误拉取platform设为unknown/unknown为防止容器运行时意外拉取或运行 attestation manifest 所描述的镜像其platform属性被强制设置为platform: { architecture: unknown, os: unknown }这一设计使得支持平台过滤的运行时如 containerd、Docker在按平台选择镜像时天然跳过这些 manifest。辅助索引遍历两个vnd.docker.reference.*注解描述符上会设置以下注解用于帮助遍历 image index、建立 attestation manifest 与目标镜像 manifest 的关联vnd.docker.reference.type描述 artifact 类型固定为attestation-manifest。若该值为其他任意值整个 manifest 应被忽略。vnd.docker.reference.digest包含该 attestation manifest 所指向的、image index 中目标对象的 digest。可用于为选中的镜像 manifest 反查匹配的 attestation manifest。这两个注解的常量定义位于 util/attestation/types.goDockerAnnotationReferenceType vnd.docker.reference.type DockerAnnotationReferenceDigest vnd.docker.reference.digest DockerAnnotationReferenceTypeDefault attestation-manifest写入逻辑见 exporter/containerimage/writer.go返回的 attestation manifest 描述符同时携带DockerAnnotationReferenceType值为attestation-manifest与DockerAnnotationReferenceDigest值为目标 digest 字符串。oci-artifact导出选项与默认值文档明确当 OCI media types 启用时BuildKit 默认将 attestation 存储为 OCI artifact设置oci-artifactfalse才回退到 legacy 格式。该选项在源码中定义于 exporter/containerimage/exptypes/keys.goOptKeyOCIArtifact ImageExporterOptKey oci-artifact解析逻辑在 exporter/containerimage/opts.goOCIArtifactEnabled()并通过 opts.go 的Validate()强制校验一个冲突条件if c.OCIArtifactEnabled() !c.OCITypesEnabled() { return errors.New(exporter option \oci-artifacttrue\ conflicts with \oci-mediatypesfalse\) }即oci-artifacttrue与oci-mediatypesfalse互斥——legacy Docker media types 与 OCI artifact 语义无法共存。同理oci-mediatypesfalse还会与compressionzstd等仅支持 OCI media types 的压缩类型冲突exporter/containerimage/opts.go。在 buildctl 或 Dockerfile frontend 中可通过 exporter 参数传入例如buildctl build \ --exporterimage \ --exporter-opt nameexample.com/app:latest \ --exporter-opt oci-artifactfalse构建工具链层面还可通过--opt attest等参数启用 SBOM/Provenance 生成oci-artifact控制的是导出阶段的存储格式。实战示例解析一个 SBOM Attestation下面用文档中的完整示例演示三级结构的实际形态。该示例为一个附加了 SBOM attestation 的linux/amd64镜像。第一步Image Indexsha256:94acc2ca70c4...索引定义了两个描述符AMD64 镜像sha256:23678f31..及其对应的 attestation manifestsha256:02cb9aa7..{ mediaType: application/vnd.oci.image.index.v1json, schemaVersion: 2, manifests: [ { mediaType: application/vnd.oci.image.manifest.v1json, digest: sha256:23678f31b3b3586c4fb318aecfe64a96a1f0916ba8faf9b2be2abee63fa9e827, size: 1234, platform: { architecture: amd64, os: linux } }, { mediaType: application/vnd.oci.image.manifest.v1json, digest: sha256:02cb9aa7600e73fcf41ee9f0f19cc03122b2d8be43d41ce4b21335118f5dd943, size: 1234, annotations: { vnd.docker.reference.digest: sha256:23678f31b3b3586c4fb318aecfe64a96a1f0916ba8faf9b2be2abee63fa9e827, vnd.docker.reference.type: attestation-manifest }, platform: { architecture: unknown, os: unknown } } ] }注意观察attestation manifest 描述符的platform为unknown/unknownannotations 中的vnd.docker.reference.digest精确指向第一个可运行镜像描述符的 digest——这正是上一节所述“防误拉取 辅助遍历”两大约定的直接体现。第二步Attestation Manifestsha256:02cb9aa7...该 manifest 包含一个 in-toto attestation其 predicate 为https://spdx.dev/Document表明这是镜像的 SBOM{ mediaType: application/vnd.oci.image.manifest.v1json, schemaVersion: 2, artifactType: application/vnd.docker.attestation.manifest.v1json, config: { mediaType: application/vnd.oci.empty.v1json, digest: sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a, size: 2, data: e30 }, layers: [ { mediaType: application/vnd.in-totojson, digest: sha256:133ae3f9bcc385295b66c2d83b28c25a9f294ce20954d5cf922dda860429734a, size: 1234, annotations: { in-toto.io/predicate-type: https://spdx.dev/Document } } ], subject: { mediaType: application/vnd.oci.image.manifest.v1json, digest: sha256:23678f31b3b3586c4fb318aecfe64a96a1f0916ba8faf9b2be2abee63fa9e827, size: 1234 } }关键点逐一对应前文规则artifactType为 attestation manifest 专用值config是 OCI 空 JSONlayers只有一个application/vnd.in-totojson层并带in-toto.io/predicate-type注解subject.digest与 image index 中第一个描述符的 digest 完全一致。第三步Layer 内容SBOM 数据体layer digest 为sha256:1ea07d5e55eb...示例中与 manifest 内层描述符 digest 不同的展示口径不影响结构理解其内容是包装为 in-toto statement 的 SPDX SBOM{ _type: https://in-toto.io/Statement/v1, predicateType: https://spdx.dev/Document, subject: [ { name: _, digest: { sha256: 23678f31b3b3586c4fb318aecfe64a96a1f0916ba8faf9b2be2abee63fa9e827 } } ], predicate: { SPDXID: SPDXRef-DOCUMENT, spdxVersion: SPDX-2.2, ... } }predicateTypehttps://spdx.dev/Document与 manifest 层描述符上的in-toto.io/predicate-type注解一致——这使消费方仅凭 manifest 元数据即可完成筛选无需下载任何 blob。subject[0].digest.sha256再次指向目标镜像 manifest完成证明与产物的绑定。消费侧视角如何遍历与校验理解了存储格式后消费方运行时、策略引擎、审计工具的遍历算法可以归纳为三步遍历根 image index 的manifests筛选annotations[vnd.docker.reference.type] attestation-manifest的描述符其他值一律忽略通过vnd.docker.reference.digest匹配目标镜像 digest定位对应的 attestation manifest读取 attestation manifest按需利用各 layer 描述符的in-toto.io/predicate-type注解跳过无关层仅拉取目标 predicate 类型的 blob解析 blob 内容为 in-toto statement校验subject与目标镜像 digest 一致再按predicateType解析predicate如 SPDX SBOM、SLSA Provenance。仓库中的测试用例印证了这一消费路径。例如 client/client_export_metadata_test.go 断言导出的 attestation manifest 的ArtifactType恰为application/vnd.docker.attestation.manifest.v1jsonfrontend/dockerfile/dockerfile_provenance_test.go 验证vnd.docker.reference.digest等于镜像 digest、vnd.docker.reference.type为attestation-manifestclient/client_export_metadata_test.go 与 client/compatibility_test.go 则检查 layer 注解in-toto.io/predicate-type是否为 SLSA/SPDX predicate 类型。这些测试同时充当了格式规范的“可执行文档”。常见问题与边界行为未知 mediaType 的 layer 如何处理忽略。这是格式演进的前向兼容设计未来新增 attestation 类型不影响旧消费者。oci-artifacttrue与oci-mediatypesfalse能否同时使用不能exporter/containerimage/opts.go 会直接返回配置冲突错误。Legacy 模式下为何还要写一个 image config仅为兼容性——早期工具链期望 manifest 携带合法 config其内容不含 attestation 信息应被忽略。attestation 文件超过 80 MiB 会怎样导出失败并报exceeds 83886080 bytes类错误这是有意为之的防护性限制exporter/attestation/make.go。一个 manifest 能放多个 attestation 吗能。多个 blob 共享同一个平台 manifest 与同一个subject各自以独立 layer 呈现。延伸阅读SBOM 生成与格式约定SPDX 文档如何生成、80 MiB 限制的实际影响SLSA Provenance 定义 与 SLSA 定义Provenance 谓词的字段语义Attestation 协议说明SBOM 在 frontend 与 exporter 之间的传递协议核心实现exporter/containerimage/writer.goattestation manifest 写入、exporter/attestation/make.goblob 读取与 80 MiB 限制、util/attestation/types.goannotation 常量消费侧参考vendor 中 moby/policy-helpers/image/resolve.go 展示了如何基于本格式在策略验证中解析 attestation。【免费下载链接】buildkitconcurrent, cache-efficient, and Dockerfile-agnostic builder toolkit项目地址: https://gitcode.com/GitHub_Trending/bu/buildkit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考