From 6e42331c0b21326cdcbf7b6b10e51a5842ae1ef4 Mon Sep 17 00:00:00 2001 From: Ilya Dmitrichenko Date: Thu, 28 Sep 2023 06:06:31 +0100 Subject: [PATCH] Fix a few bugs with attestation summary and use it in `tape view` --- attest/types/types.go | 21 ++++++++++++++++++++- oci/artefact.go | 30 +++++++++++++++--------------- tape/app/view.go | 19 +++++++++++++++++-- 3 files changed, 52 insertions(+), 18 deletions(-) diff --git a/attest/types/types.go b/attest/types/types.go index f82bea2..c7a3aad 100644 --- a/attest/types/types.go +++ b/attest/types/types.go @@ -234,7 +234,26 @@ func (s Subjects) Export() []toto.Subject { func (s Subjects) MarshalJSON() ([]byte, error) { return json.Marshal(s.Export()) } -//func (s *Subjects) UnmarshalJSON(data []byte) error { return json.Unmarshal(data, s) } +func (s *Subjects) UnmarshalJSON(data []byte) error { + subjects := []toto.Subject{} + if err := json.Unmarshal(data, &subjects); err != nil { + return err + } + if len(subjects) == 0 { + return fmt.Errorf("invalid subject: zero entries") + } + for i := range subjects { + digestValue, ok := subjects[i].Digest["sha256"] + if !ok { + return fmt.Errorf("invalid subject: missing sha256 digest") + } + *s = append(*s, Subject{ + Name: subjects[i].Name, + Digest: digest.SHA256(digestValue), + }) + } + return nil +} func MakePathCheckSummaryCollection(entries ...PathChecker) (*PathCheckSummaryCollection, error) { numEntries := len(entries) diff --git a/oci/artefact.go b/oci/artefact.go index bdd7745..9afb840 100644 --- a/oci/artefact.go +++ b/oci/artefact.go @@ -36,7 +36,7 @@ const ( ContentInterpreterAnnotation = mediaTypePrefix + ".content-interpreter.v1alpha1" ContentInterpreterKubectlApply = mediaTypePrefix + ".kubectl-apply.v1alpha1.tar+gzip" - AttestationsSummaryAnnotations = mediaTypePrefix + ".attestations-summary.v1alpha1" + AttestationsSummaryAnnotation = mediaTypePrefix + ".attestations-summary.v1alpha1" // TODO: content interpreter invocation with an image @@ -101,7 +101,7 @@ func (c *Client) FetchFromIndexOrImage(ctx context.Context, imageIndex ImageInde continue } - info, err := newArtifcatInfoFromLayerDescriptor(image, layerDescriptor) + info, err := newArtifcatInfoFromLayerDescriptor(image, layerDescriptor, manifest.Annotations) if err != nil { return nil, nil, err } @@ -131,7 +131,7 @@ func (c *Client) FetchFromIndexOrImage(ctx context.Context, imageIndex ImageInde return nil, nil, fmt.Errorf("media type mismatch between manifest and layer: %s != %s", manifestDescriptor.MediaType, layerDescriptor.MediaType) } - info, err := newArtifcatInfoFromLayerDescriptor(image, layerDescriptor) + info, err := newArtifcatInfoFromLayerDescriptor(image, layerDescriptor, manifest.Annotations) if err != nil { return nil, nil, err } @@ -143,17 +143,17 @@ func (c *Client) FetchFromIndexOrImage(ctx context.Context, imageIndex ImageInde } func (c *Client) GetSingleArtefact(ctx context.Context, ref string) (*ArtefactInfo, error) { - image, layers, err := c.getFlatArtefactLayers(ctx, ref) + image, layers, annotations, err := c.getFlatArtefactLayers(ctx, ref) if err != nil { return nil, err } if len(layers) != 1 { return nil, fmt.Errorf("multiple layers found in image %q", ref) } - return newArtifcatInfoFromLayerDescriptor(image, layers[0]) + return newArtifcatInfoFromLayerDescriptor(image, layers[0], annotations) } -func newArtifcatInfoFromLayerDescriptor(image Image, layerDecriptor Descriptor) (*ArtefactInfo, error) { +func newArtifcatInfoFromLayerDescriptor(image Image, layerDecriptor Descriptor, annotations map[string]string) (*ArtefactInfo, error) { layer, err := image.LayerByDigest(layerDecriptor.Digest) if err != nil { return nil, fmt.Errorf("fetching artefact image failed: %w", err) @@ -166,41 +166,41 @@ func newArtifcatInfoFromLayerDescriptor(image Image, layerDecriptor Descriptor) info := &ArtefactInfo{ ReadCloser: blob, MediaType: layerDecriptor.MediaType, - Annotations: layerDecriptor.Annotations, + Annotations: annotations, Digest: layerDecriptor.Digest.String(), } return info, nil } -func (c *Client) getFlatArtefactLayers(ctx context.Context, ref string) (Image, []Descriptor, error) { +func (c *Client) getFlatArtefactLayers(ctx context.Context, ref string) (Image, []Descriptor, map[string]string, error) { imageIndex, indexManifest, image, err := c.GetIndexOrImage(ctx, ref) if err != nil { - return nil, nil, err + return nil, nil, nil, err } var manifest *Manifest if indexManifest != nil { if len(indexManifest.Manifests) != 1 { - return nil, nil, fmt.Errorf("multiple manifests found in image %q", ref) + return nil, nil, nil, fmt.Errorf("multiple manifests found in image %q", ref) } image, manifest, err = c.getImage(ctx, imageIndex, indexManifest.Manifests[0].Digest) if err != nil { - return nil, nil, err + return nil, nil, nil, err } } else { manifest, err = image.Manifest() if err != nil { - return nil, nil, fmt.Errorf("failed to get manifest for %q: %w", ref, err) + return nil, nil, nil, fmt.Errorf("failed to get manifest for %q: %w", ref, err) } } if len(manifest.Layers) < 1 { - return nil, nil, fmt.Errorf("no layers found in image %q", ref) + return nil, nil, nil, fmt.Errorf("no layers found in image %q", ref) } - return image, manifest.Layers, nil + return image, manifest.Layers, manifest.Annotations, nil } func (c *Client) getImage(ctx context.Context, imageIndex ImageIndex, digest Hash) (Image, *Manifest, error) { @@ -307,7 +307,7 @@ func (c *Client) PushArtefact(ctx context.Context, destinationRef, sourceDir str if err != nil { return "", err } - attestAnnotations[AttestationsSummaryAnnotations] = summary + attestAnnotations[AttestationsSummaryAnnotation] = summary attest := mutate.Annotations( mutate.ConfigMediaType( diff --git a/tape/app/view.go b/tape/app/view.go index efb0609..0076355 100644 --- a/tape/app/view.go +++ b/tape/app/view.go @@ -11,7 +11,7 @@ import ( toto "github.com/in-toto/in-toto-golang/in_toto" - //attestTypes "github.com/docker/labs-brown-tape/attest/types" + attestTypes "github.com/docker/labs-brown-tape/attest/types" "github.com/docker/labs-brown-tape/oci" ) @@ -29,7 +29,8 @@ type artefactInfo struct { Content rawManifest[oci.Manifest] `json:"content"` Attest rawManifest[oci.Manifest] `json:"attest"` } `json:"rawManifests"` - Attestations []toto.Statement `json:"attestations"` + Attestations []toto.Statement `json:"attestations"` + AttestationsSummary *attestTypes.SummaryAnnotation `json:"attestationsSummary,omitempty"` } type rawManifest[T oci.Manifest | oci.IndexManifest] struct { @@ -96,6 +97,14 @@ func (c *TapeViewCommand) CollectInfo(ctx context.Context, client *oci.Client) ( switch info.MediaType { case oci.ContentMediaType: case oci.AttestMediaType: + if annotation, ok := info.Annotations[oci.AttestationsSummaryAnnotation]; ok { + summary, err := attestTypes.UnmarshalSummaryAnnotation(annotation) + if err != nil { + return nil, err + } + artefactInfo.AttestationsSummary = summary + } + gr, err := gzip.NewReader(info) if err != nil { return nil, err @@ -149,6 +158,12 @@ func (c *TapeViewCommand) PrintInfo(ctx context.Context, outputInfo *artefactInf fmt.Printf(" %s %s\n", outputInfo.RawManifests.Attest.Digest, outputInfo.RawManifests.Attest.Manifest.Config.MediaType) + if outputInfo.AttestationsSummary != nil { + fmt.Printf(" Attestations Summary:\n") + fmt.Printf(" Number of Statements: %v\n", outputInfo.AttestationsSummary.NumStamentes) + fmt.Printf(" Predicate Types: %v\n", outputInfo.AttestationsSummary.PredicateTypes) + fmt.Printf(" Subjects: %v\n", outputInfo.AttestationsSummary.Subjects) + } } return nil }