Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions pkg/oci/remote/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,13 @@ import (

// These enable mocking for unit testing without faking an entire registry.
var (
remoteImage = remote.Image
remoteIndex = remote.Index
remoteGet = remote.Get
remoteWrite = remote.Write
remoteImage = remote.Image
remoteIndex = remote.Index
remoteGet = remote.Get
remoteWrite = remote.Write
remoteHead = remote.Head
remoteWriteLayer = remote.WriteLayer
remotePut = remote.Put
)

// EntityNotFoundError is the error that SignedEntity returns when the
Expand Down
125 changes: 82 additions & 43 deletions pkg/oci/remote/write.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ func WriteSignaturesExperimentalOCI(d name.Digest, se oci.SignedEntity, opts ...
if err != nil {
return err
}
desc, err := remote.Head(ref, o.ROpt...)
desc, err := remoteHead(ref, o.ROpt...)
if err != nil {
return err
}
Expand All @@ -161,7 +161,7 @@ func WriteSignaturesExperimentalOCI(d name.Digest, se oci.SignedEntity, opts ...
return err
}
for _, v := range s {
if err := remote.WriteLayer(d.Repository, v, o.ROpt...); err != nil {
if err := remoteWriteLayer(d.Repository, v, o.ROpt...); err != nil {
return err
}
}
Expand All @@ -176,7 +176,7 @@ func WriteSignaturesExperimentalOCI(d name.Digest, se oci.SignedEntity, opts ...
return err
}
configLayer := static.NewLayer(configBytes, configDesc.MediaType)
if err := remote.WriteLayer(d.Repository, configLayer, o.ROpt...); err != nil {
if err := remoteWriteLayer(d.Repository, configLayer, o.ROpt...); err != nil {
return err
}

Expand Down Expand Up @@ -208,7 +208,7 @@ func WriteSignaturesExperimentalOCI(d name.Digest, se oci.SignedEntity, opts ...
// TODO: use ui.Infof
fmt.Fprintf(os.Stderr, "Uploading signature for [%s] to [%s] with config.mediaType [%s] layers[0].mediaType [%s].\n",
d.String(), targetRef.String(), artifactType, ctypes.SimpleSigningMediaType)
return remote.Put(targetRef, &taggableManifest{raw: b, mediaType: m.MediaType}, o.ROpt...)
return remotePut(targetRef, &taggableManifest{raw: b, mediaType: m.MediaType}, o.ROpt...)
}

type taggableManifest struct {
Expand All @@ -224,15 +224,17 @@ func (taggable taggableManifest) MediaType() (types.MediaType, error) {
return taggable.mediaType, nil
}

func WriteAttestationNewBundleFormat(d name.Digest, bundleBytes []byte, predicateType string, opts ...Option) error {
// WriteReferrer writes a referrer manifest for a given subject digest.
// It uploads the provided layers and creates a manifest that refers to the subject.
func WriteReferrer(d name.Digest, artifactType string, layers []v1.Layer, annotations map[string]string, opts ...Option) error {
o := makeOptions(d.Repository, opts...)

signTarget := d.String()
ref, err := name.ParseReference(signTarget, o.NameOpts...)
if err != nil {
return err
}
desc, err := remote.Head(ref, o.ROpt...)
desc, err := remoteHead(ref, o.ROpt...)
if err != nil {
return err
}
Expand All @@ -247,32 +249,35 @@ func WriteAttestationNewBundleFormat(d name.Digest, bundleBytes []byte, predicat
if err != nil {
return fmt.Errorf("failed to calculate size: %w", err)
}
err = remote.WriteLayer(d.Repository, configLayer, o.ROpt...)
err = remoteWriteLayer(d.Repository, configLayer, o.ROpt...)
if err != nil {
return fmt.Errorf("failed to upload layer: %w", err)
}

// generate bundle media type string
bundleMediaType, err := sgbundle.MediaTypeString("0.3")
if err != nil {
return fmt.Errorf("failed to generate bundle media type string: %w", err)
}

// Write the bundle layer
layer := static.NewLayer(bundleBytes, types.MediaType(bundleMediaType))
blobDigest, err := layer.Digest()
if err != nil {
return fmt.Errorf("failed to calculate digest: %w", err)
}

blobSize, err := layer.Size()
if err != nil {
return fmt.Errorf("failed to calculate size: %w", err)
}
layerDescriptors := make([]v1.Descriptor, len(layers))
for i, layer := range layers {
mediaType, err := layer.MediaType()
if err != nil {
return fmt.Errorf("failed to get media type: %w", err)
}
layerDigest, err := layer.Digest()
if err != nil {
return fmt.Errorf("failed to calculate digest: %w", err)
}
layerSize, err := layer.Size()
if err != nil {
return fmt.Errorf("failed to calculate size: %w", err)
}

err = remote.WriteLayer(d.Repository, layer, o.ROpt...)
if err != nil {
return fmt.Errorf("failed to upload layer: %w", err)
err = remoteWriteLayer(d.Repository, layer, o.ROpt...)
if err != nil {
return fmt.Errorf("failed to upload layer: %w", err)
}
layerDescriptors[i] = v1.Descriptor{
MediaType: mediaType,
Digest: layerDigest,
Size: layerSize,
}
}

// Create a manifest that includes the blob as a layer
Expand All @@ -281,42 +286,76 @@ func WriteAttestationNewBundleFormat(d name.Digest, bundleBytes []byte, predicat
MediaType: types.OCIManifestSchema1,
Config: v1.Descriptor{
MediaType: types.MediaType("application/vnd.oci.empty.v1+json"),
ArtifactType: bundleMediaType,
ArtifactType: artifactType,
Digest: configDigest,
Size: configSize,
},
Layers: []v1.Descriptor{
{
MediaType: types.MediaType(bundleMediaType),
Digest: blobDigest,
Size: blobSize,
},
},
Layers: layerDescriptors,
Subject: &v1.Descriptor{
MediaType: desc.MediaType,
Digest: desc.Digest,
Size: desc.Size,
},
Annotations: map[string]string{
"org.opencontainers.image.created": time.Now().UTC().Format(time.RFC3339),
"dev.sigstore.bundle.content": "dsse-envelope",
"dev.sigstore.bundle.predicateType": predicateType,
},
}, bundleMediaType}
Annotations: annotations,
}, artifactType}

targetRef, err := manifest.targetRef(d.Repository)
if err != nil {
return fmt.Errorf("failed to create target reference: %w", err)
}

if err := remote.Put(targetRef, manifest, o.ROpt...); err != nil {
if err := remotePut(targetRef, manifest, o.ROpt...); err != nil {
return fmt.Errorf("failed to upload manifest: %w", err)
}

return nil
}

// referrerManifest implements Taggable for use in remote.Put.
func WriteAttestationNewBundleFormat(d name.Digest, bundleBytes []byte, predicateType string, opts ...Option) error {
// generate bundle media type string
bundleMediaType, err := sgbundle.MediaTypeString("0.3")
if err != nil {
return fmt.Errorf("failed to generate bundle media type string: %w", err)
}

// Write the bundle layer
layer := static.NewLayer(bundleBytes, types.MediaType(bundleMediaType))

annotations := map[string]string{
"org.opencontainers.image.created": time.Now().UTC().Format(time.RFC3339),
"dev.sigstore.bundle.content": "dsse-envelope",
"dev.sigstore.bundle.predicateType": predicateType,
}

return WriteReferrer(d, bundleMediaType, []v1.Layer{layer}, annotations, opts...)
}

// WriteAttestationsReferrer publishes the attestations attached to the given entity
// into the provided repository using the referrers API.
func WriteAttestationsReferrer(d name.Digest, se oci.SignedEntity, opts ...Option) error {
atts, err := se.Attestations()
if err != nil {
return err
}
layers, err := atts.Layers()
if err != nil {
return err
}

annotations := map[string]string{
"org.opencontainers.image.created": time.Now().UTC().Format(time.RFC3339),
}

// We have to pick an artifactType for the referrer manifest. The attestation
// layers themselves are DSSE envelopes, which wrap in-toto statements.
// For discovery, the artifactType should describe the semantic content (the
// in-toto statement) rather than the wrapper format (the DSSE envelope).
// Using the in-toto media type is the most appropriate and conventional choice,
// as policy engines and other tools will query for attestations using this type.
Comment on lines +349 to +354
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to really think through this (mostly because as mentioned I haven't spent much time with cosign's OCI functions), but I think this is the right answer.

Retrieving an attestation that doesn't use the new protobuf bundles:

$ go run cmd/cosign/main.go download attestation ghcr.io/steiza/actions-oidc-proxy:v1 | jq
{
  "payloadType": "application/vnd.in-toto+json",
  "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2Nvc2lnbi5zaWdzdG9yZS5kZXYvYXR0ZXN0YXRpb24vdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiZ2hjci5pby9zdGVpemEvYWN0aW9ucy1vaWRjLXByb3h5IiwiZGlnZXN0Ijp7InNoYTI1NiI6ImEwNGMyOTk2YzRjNDJhYTUxNzQ5MmJjMGQ4ZTA5Y2EwMDJhNTk5ZDEyZjVlODJiZDUzNzNlZDYzMTNhZjZhNzkifX1dLCJwcmVkaWNhdGUiOnsiRGF0YSI6IntcInBhc3NlZFwiOiB0cnVlfVxuIiwiVGltZXN0YW1wIjoiMjAyNS0wOC0xNVQxODoyMjoyMVoifX0=",
  "signatures": [
    {
      "keyid": "",
      "sig": "MEYCIQD+TJnd3/GuORNfKMMpbviudNGyy+G9qUw0nOtQABEEnAIhAKiTsFRVWdPRSVN/xD3LxGkb/5xPz4f8voN6e16jdePa"
    }
  ]
}

application/vnd.in-toto+json makes sense!

return WriteReferrer(d, ctypes.IntotoPayloadType, layers, annotations, opts...)
}

// referrerManifest implements Taggable for use in remotePut.
// This type also augments the built-in v1.Manifest with an ArtifactType field
// which is part of the OCI 1.1 Image Manifest spec but is unsupported by
// go-containerregistry at this time.
Expand Down
Loading
Loading