-
Notifications
You must be signed in to change notification settings - Fork 3
Description
When multi-arch images are built with Docker BuildKit attestations (provenance + SBOM), the OCI Image Index contains additional entries for each platform image with platform: {os: "unknown", architecture: "unknown"}. Harbor displays these as regular artifact rows in the children list, confusing users.
What these entries are
BuildKit (since v0.11 / Docker 24+) embeds attestation manifests (SLSA provenance + SPDX SBOM) as entries within the OCI Image Index. Each real platform image gets a corresponding attestation entry (~2-87KiB) containing in-toto metadata. These are not runnable images — they are supply-chain metadata.
Each attestation is linked to its platform image via annotations:
{
"annotations": {
"vnd.docker.reference.digest": "sha256:<platform-image-digest>",
"vnd.docker.reference.type": "attestation-manifest"
},
"platform": { "architecture": "unknown", "os": "unknown" }
}Root cause
No filtering exists anywhere in the chain:
- Backend (
src/controller/artifact/abstractor.go):abstractIndexMetadata()stores every OCI Index entry as aReferencewith the platform verbatim — includingunknown/unknown - API: returns all references unfiltered
- Frontend (
artifact-list-tab.component.ts): iterates allres.referencesand renders them
The annotations are already persisted in the artifact_reference.annotations jsonb column but never inspected.
Proposed Solution
Treat attestation manifests as accessories of their parent platform image (similar to cosign signatures), not as index children.
Backend changes
In abstractIndexMetadata (src/controller/artifact/abstractor.go:179), when processing index entries:
- Check if
annotations["vnd.docker.reference.type"] == "attestation-manifest" - If yes, resolve the subject platform artifact via
annotations["vnd.docker.reference.digest"] - Create an
artifact_accessoryrecord linking the attestation to the platform image - Exclude the attestation entry from
art.References(so it won't appear as a child row)
New accessory type needed:
// src/pkg/accessory/model/accessory.go
TypeBuildKitAttestation = "attestation.buildkit"With a new model package at src/pkg/accessory/model/attestation/ following the cosign/notation pattern (hard reference, not independently displayable).
Frontend changes
- Add
ATTESTATION = 'attestation.buildkit'to theAccessoryTypeenum insrc/portal/.../artifact.ts - The attestation will automatically appear in the existing expandable accessory tree (the
<sub-accessories>component) under the platform image — no new columns needed
Result
Before:
| Artifacts | OS/ARCH |
|---|---|
| sha256:b1d2caac | linux/amd64 |
| sha256:d46c76e5 | linux/arm64 |
| sha256:e91e57f4 | unknown/unknown |
| sha256:37c1dde7 | unknown/unknown |
After:
| Artifacts | OS/ARCH |
|---|---|
| sha256:b1d2caac | linux/amd64 |
| ↳ sha256:e91e57f4 | attestation.buildkit |
| sha256:d46c76e5 | linux/arm64 |
| ↳ sha256:37c1dde7 | attestation.buildkit |
Migration consideration
This fix only affects images pushed after the change. For already-pushed images, a display-time fallback could check artifact_reference.annotations for attestation-manifest entries and synthesize virtual accessories, or a one-time migration could create the missing accessory records from existing reference data.
Reproduction
# Build and push multi-arch image with attestations
docker buildx build \
--platform linux/amd64,linux/arm64 \
--provenance=true --sbom=true \
-t <registry>/test/image:v1 --push .Navigate to the image in Harbor UI → expand the manifest index → observe unknown/unknown rows.
API confirms the attestation annotations:
curl -u admin:Harbor12345 \
<registry>/api/v2.0/projects/test/repositories/image/artifacts/<index-digest> \
| jq '.references[] | select(.annotations["vnd.docker.reference.type"] == "attestation-manifest")'