Merge pull request #3957 from deitch/push-with-sboms

push sboms as well
This commit is contained in:
Avi Deitcher 2023-11-20 01:59:22 -08:00 committed by GitHub
commit 54bd9073c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 141 additions and 31 deletions

View File

@ -7,7 +7,7 @@ import (
namepkg "github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/validate"
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/registry"
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/util"
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
log "github.com/sirupsen/logrus"
)
@ -57,10 +57,26 @@ func (p *Provider) Push(name string, withManifest bool) error {
if err != nil {
return fmt.Errorf("could not get digest for index %s: %v", name, err)
}
manifest, err := ii.IndexManifest()
if err != nil {
return fmt.Errorf("could not read images in index: %v", err)
}
// get the existing image, if any
desc, err := remote.Get(ref, remoteOptions...)
if err == nil && desc != nil && dig == desc.Digest {
fmt.Printf("%s index already available on remote registry, skipping push", name)
return nil
if err == nil && desc != nil {
if dig == desc.Digest {
fmt.Printf("%s index already available on remote registry, skipping push", name)
return nil
}
// we have a different index, need to cross-reference and only override relevant stuff
remoteIndex, err := desc.ImageIndex()
if err == nil && remoteIndex != nil {
ii, err = util.AppendIndex(ii, remoteIndex)
if err != nil {
return fmt.Errorf("could not append remote index to local index: %v", err)
}
}
}
log.Debugf("pushing index %s", name)
// this is an index, so we not only want to write the index, but tags for each arch-specific image in it
@ -68,10 +84,6 @@ func (p *Provider) Push(name string, withManifest bool) error {
return err
}
fmt.Printf("Pushed index %s\n", name)
manifest, err := ii.IndexManifest()
if err != nil {
return fmt.Errorf("successfully pushed index, but could not read images in index: %v", err)
}
log.Debugf("pushing individual images in the index %s", name)
for _, m := range manifest.Manifests {
if m.Platform == nil || m.Platform.Architecture == "" {
@ -113,17 +125,5 @@ func (p *Provider) Push(name string, withManifest bool) error {
return fmt.Errorf("name %s unknown in cache", name)
}
if !withManifest {
return nil
}
// Even though we may have pushed the index, we want to be sure that we have an index that includes every architecture on the registry,
// not just those that were in our local cache. So we call PushManifest to push an index that includes all arch-specific images
// already in the registry.
fmt.Printf("Pushing index based on all arch-specific images in registry %s\n", name)
_, _, err = registry.PushManifest(name, options...)
if err != nil {
return err
}
return nil
}

View File

@ -15,16 +15,12 @@ import (
"github.com/google/go-containerregistry/pkg/v1/tarball"
intoto "github.com/in-toto/in-toto-golang/in_toto"
lktspec "github.com/linuxkit/linuxkit/src/cmd/linuxkit/spec"
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/util"
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
)
const (
annotationDockerReferenceType = "vnd.docker.reference.type"
annotationAttestationManifest = "attestation-manifest"
annotationDockerReferenceDigest = "vnd.docker.reference.digest"
annotationInTotoPredicateType = "in-toto.io/predicate-type"
annotationSPDXDoc = "https://spdx.dev/Document"
inTotoJsonMediaType = "application/vnd.in-toto+json"
inTotoJsonMediaType = "application/vnd.in-toto+json"
)
// ImageSource a source for an image in the OCI distribution cache.
@ -143,8 +139,8 @@ func (c ImageSource) SBoMs() ([]io.ReadCloser, error) {
desc := descs[0]
annotations := map[string]string{
annotationDockerReferenceType: annotationAttestationManifest,
annotationDockerReferenceDigest: desc.Digest.String(),
util.AnnotationDockerReferenceType: util.AnnotationAttestationManifest,
util.AnnotationDockerReferenceDigest: desc.Digest.String(),
}
descs, err = partial.FindManifests(index, matchAllAnnotations(annotations))
if err != nil {
@ -183,7 +179,7 @@ func (c ImageSource) SBoMs() ([]io.ReadCloser, error) {
var readers []io.ReadCloser
for i, layer := range manifest.Layers {
annotations := layer.Annotations
if annotations[annotationInTotoPredicateType] != annotationSPDXDoc || layer.MediaType != inTotoJsonMediaType {
if annotations[util.AnnotationInTotoPredicateType] != util.AnnotationSPDXDoc || layer.MediaType != inTotoJsonMediaType {
continue
}
// get the actual blob of the layer
@ -201,7 +197,7 @@ func (c ImageSource) SBoMs() ([]io.ReadCloser, error) {
if err := json.Unmarshal(buf.Bytes(), &stmt); err != nil {
return nil, err
}
if stmt.PredicateType != annotationSPDXDoc {
if stmt.PredicateType != util.AnnotationSPDXDoc {
return nil, fmt.Errorf("unexpected predicate type %s", stmt.PredicateType)
}
sbom := stmt.Predicate

View File

@ -20,6 +20,7 @@ import (
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/types"
lktspec "github.com/linuxkit/linuxkit/src/cmd/linuxkit/spec"
lktutil "github.com/linuxkit/linuxkit/src/cmd/linuxkit/util"
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
log "github.com/sirupsen/logrus"
)
@ -320,7 +321,7 @@ func (p *Provider) IndexWrite(ref *reference.Spec, descriptors ...v1.Descriptor)
appliedManifests[m.Digest] = true
continue
}
value, ok := m.Annotations[annotationDockerReferenceDigest]
value, ok := m.Annotations[lktutil.AnnotationDockerReferenceDigest]
if !ok {
manifest.Manifests = append(manifest.Manifests, m)
appliedManifests[m.Digest] = true

View File

@ -9,6 +9,7 @@ import (
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/util"
log "github.com/sirupsen/logrus"
)
@ -30,6 +31,7 @@ func PushManifest(img string, options ...remote.Option) (hash string, length int
if err != nil {
return hash, length, fmt.Errorf("parsing %s: %w", img, err)
}
adds := make([]mutate.IndexAddendum, 0, len(platformsToSearchForIndex))
for i, platform := range platformsToSearchForIndex {
osArchArr := strings.Split(platform, "/")
@ -70,6 +72,20 @@ func PushManifest(img string, options ...remote.Option) (hash string, length int
// add the desc to the index we will push
index := mutate.AppendManifests(empty.Index, adds...)
// base index with which we are working
// get the existing index, if any
desc, err := remote.Get(baseRef, options...)
if err == nil && desc != nil {
ii, err := desc.ImageIndex()
if err != nil {
return hash, length, fmt.Errorf("could not get index for existing reference %s: %w", img, err)
}
index, err = util.AppendIndex(index, ii)
if err != nil {
return hash, length, fmt.Errorf("could not append existing index for %s: %w", img, err)
}
}
size, err := index.Size()
if err != nil {
return hash, length, fmt.Errorf("getting index size: %w", err)

View File

@ -0,0 +1,97 @@
package util
import (
"fmt"
v1 "github.com/google/go-containerregistry/pkg/v1"
)
const (
AnnotationDockerReferenceDigest = "vnd.docker.reference.digest"
AnnotationDockerReferenceType = "vnd.docker.reference.type"
AnnotationAttestationManifest = "attestation-manifest"
AnnotationInTotoPredicateType = "in-toto.io/predicate-type"
AnnotationSPDXDoc = "https://spdx.dev/Document"
)
// AppendIndex appends the elements of secondary ImageIndex into primary ImageIndex,
// returning the updated primary ImageIndex.
// In the case of conflicts, the primary ImageIndex wins.
// For example, if both have a manifest for a specific platform, then use the one from primary.
// The append is aware of the buildkit-style attestations, and will keep any attestations that point to a valid
// manifest in the list, discarding any that do not.
func AppendIndex(primary, secondary v1.ImageIndex) (v1.ImageIndex, error) {
primaryManifest, err := primary.IndexManifest()
if err != nil {
return nil, err
}
secondaryManifest, err := secondary.IndexManifest()
if err != nil {
return nil, err
}
// figure out what already is in the index, and what should be overwritten
// what should be checked in the existing index:
// 1. platform - if it is in remote index but not in local, add to local
// 2. attestation - after all platforms, does it point to something in the updated index?
// If not, remove
// make a map of all the digests already in the index, so we can know what is there
var (
manifestMap = map[v1.Hash]bool{}
platformMap = map[string]bool{}
)
for _, m := range primaryManifest.Manifests {
if m.Platform == nil || m.Platform.Architecture == "" {
continue
}
platformKey := fmt.Sprintf("%s/%s/%s", m.Platform.Architecture, m.Platform.OS, m.Platform.Variant)
manifestMap[m.Digest] = true
platformMap[platformKey] = true
}
for _, m := range secondaryManifest.Manifests {
// ignore any of those without a platform for this run (we will deal witb attestations in a second pass)
if m.Platform == nil || m.Platform.Architecture == "" || (m.Platform.Architecture == "unknown" && m.Platform.OS == "unknown") {
continue
}
platformKey := fmt.Sprintf("%s/%s/%s", m.Platform.Architecture, m.Platform.OS, m.Platform.Variant)
// primary wins if we already have this platform covered
if _, ok := platformMap[platformKey]; ok {
continue
}
if _, ok := manifestMap[m.Digest]; ok {
// we already have this one, so we can skip it
continue
}
primaryManifest.Manifests = append(primaryManifest.Manifests, m)
manifestMap[m.Digest] = true
}
// now we have assured that all of the images in the remote index are in the local index
// or overridden by matching local ones
// next we have to make sure that any sboms already on the remote index are still valid
// we either add them to the local index, or remove them if they are no longer valid
// we assume the ones in the local index are valid because they would have been generated now
for _, m := range secondaryManifest.Manifests {
if m.Platform == nil || m.Platform.Architecture != "unknown" || m.Platform.OS == "unknown" || m.Annotations == nil || m.Annotations[AnnotationDockerReferenceDigest] == "" {
continue
}
// if we already have this one, we are good
if _, ok := manifestMap[m.Digest]; ok {
continue
}
// the hash to which this attestation points
hash := m.Annotations[AnnotationDockerReferenceDigest]
dig, err := v1.NewHash(hash)
if err != nil {
return nil, fmt.Errorf("could not parse hash %s: %v", hash, err)
}
// if this points at something not in the local index, do not bother adding it
if _, ok := manifestMap[dig]; !ok {
continue
}
primaryManifest.Manifests = append(primaryManifest.Manifests, m)
manifestMap[m.Digest] = true
}
return primary, nil
}