Unify functions for use in sign & verify --vsa (#71)
* Use receivers for manifest functions * Move SaveImage/SaveIndex from image-signing-verifier * Ignore test fixtures in coverage * Add AddImagesToIndex function
This commit is contained in:
2
codecov.yml
Normal file
2
codecov.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
ignore:
|
||||
- "internal/test"
|
||||
@@ -32,22 +32,30 @@ func ExampleSign_remote() {
|
||||
|
||||
// load image index with unsigned attestation-manifests
|
||||
ref := "docker/image-signer-verifier:latest"
|
||||
att, err := oci.SubjectIndexFromRemote(ref)
|
||||
attIdx, err := oci.IndexFromRemote(ref)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// example for local image index
|
||||
// path := "/myimage"
|
||||
// att, err := oci.AttestationIndexFromLocal(path)
|
||||
// attIdx, err = oci.IndexFromPath(path)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
|
||||
// sign attestations
|
||||
signedImageIndex, err := attest.Sign(context.Background(), att.Index, signer, opts)
|
||||
// sign all attestations in an image index
|
||||
signedManifests, err := attest.SignStatements(context.Background(), attIdx.Index, signer, opts)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
signedIndex := attIdx.Index
|
||||
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// push image index with signed attestation-manifests
|
||||
err = mirror.PushIndexToRegistry(signedImageIndex, ref)
|
||||
err = mirror.PushIndexToRegistry(signedIndex, ref)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -55,10 +63,10 @@ func ExampleSign_remote() {
|
||||
path := "/myimage"
|
||||
idx := v1.ImageIndex(empty.Index)
|
||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||
Add: signedImageIndex,
|
||||
Add: signedIndex,
|
||||
Descriptor: v1.Descriptor{
|
||||
Annotations: map[string]string{
|
||||
oci.OciReferenceTarget: att.Name,
|
||||
oci.OciReferenceTarget: attIdx.Name,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -2,246 +2,25 @@ package attest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/attest/pkg/attestation"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/empty"
|
||||
"github.com/google/go-containerregistry/pkg/v1/match"
|
||||
"github.com/google/go-containerregistry/pkg/v1/mutate"
|
||||
"github.com/google/go-containerregistry/pkg/v1/partial"
|
||||
"github.com/google/go-containerregistry/pkg/v1/static"
|
||||
"github.com/google/go-containerregistry/pkg/v1/types"
|
||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||
"github.com/secure-systems-lab/go-securesystemslib/dsse"
|
||||
)
|
||||
|
||||
func Sign(ctx context.Context, idx v1.ImageIndex, signer dsse.SignerVerifier, opts *attestation.SigningOptions) (v1.ImageIndex, error) {
|
||||
images, err := SignedAttestationImages(ctx, idx, signer, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign attestation images: %w", err)
|
||||
}
|
||||
for _, image := range images {
|
||||
idx, err = addImageToIndex(idx, image.Image, image.Descriptor, image.AttestationManifest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to add signed layers to index: %w", err)
|
||||
}
|
||||
}
|
||||
return idx, nil
|
||||
}
|
||||
|
||||
func SignedAttestationImages(ctx context.Context, idx v1.ImageIndex, signer dsse.SignerVerifier, opts *attestation.SigningOptions) ([]*attestation.SignedAttestationImage, error) {
|
||||
// this is only relevant if there are (unsigned) in-toto statements
|
||||
func SignStatements(ctx context.Context, idx v1.ImageIndex, signer dsse.SignerVerifier, opts *attestation.SigningOptions) ([]*attestation.AttestationManifest, error) {
|
||||
// extract attestation manifests from index
|
||||
attestationManifests, err := attestation.GetAttestationManifestsFromIndex(idx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get attestation manifests: %w", err)
|
||||
return nil, fmt.Errorf("failed to load attestation manifests from index: %w", err)
|
||||
}
|
||||
if len(attestationManifests) == 0 {
|
||||
return nil, fmt.Errorf("no attestation manifests found")
|
||||
}
|
||||
images := []*attestation.SignedAttestationImage{}
|
||||
// sign every attestation layer in each manifest
|
||||
for _, manifest := range attestationManifests {
|
||||
newImg, newDescriptor, err := SignLayersAndAddToImage(ctx, manifest.Attestation.Layers, manifest, signer, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to add signed layers to image: %w", err)
|
||||
}
|
||||
images = append(images, &attestation.SignedAttestationImage{
|
||||
Image: newImg,
|
||||
Descriptor: newDescriptor,
|
||||
AttestationManifest: manifest,
|
||||
})
|
||||
}
|
||||
return images, nil
|
||||
}
|
||||
|
||||
func AddAttestation(ctx context.Context, idx v1.ImageIndex, statement *intoto.Statement, signer dsse.SignerVerifier) (v1.ImageIndex, error) {
|
||||
if len(statement.Subject) == 0 {
|
||||
return nil, fmt.Errorf("statement has no subjects")
|
||||
}
|
||||
|
||||
subjectDigests := make(map[string]bool)
|
||||
for _, subject := range statement.Subject {
|
||||
subjectDigest := fmt.Sprintf("sha256:%s", subject.Digest["sha256"])
|
||||
subjectDigests[subjectDigest] = true
|
||||
}
|
||||
|
||||
attestationManifests, err := attestation.GetAttestationManifestsFromIndex(idx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get attestation manifests: %w", err)
|
||||
}
|
||||
updatedIndex := false
|
||||
for _, manifest := range attestationManifests {
|
||||
if subjectDigests[manifest.Annotations[attestation.DockerReferenceDigest]] {
|
||||
attestationLayers := []attestation.AttestationLayer{
|
||||
{
|
||||
Statement: statement,
|
||||
MediaType: types.MediaType(intoto.PayloadType),
|
||||
Annotations: map[string]string{
|
||||
oci.InTotoPredicateType: statement.PredicateType,
|
||||
},
|
||||
},
|
||||
}
|
||||
// hard-coding replace to false here, because if it's true we will remove any unsigned statements, even unrelated ones
|
||||
newImg, newDec, err := SignLayersAndAddToImage(ctx, attestationLayers, manifest, signer, &attestation.SigningOptions{Replace: false})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to add signed layers to image: %w", err)
|
||||
}
|
||||
idx, err = addImageToIndex(idx, newImg, newDec, manifest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to add attestation image to index: %w", err)
|
||||
}
|
||||
updatedIndex = true
|
||||
}
|
||||
}
|
||||
if !updatedIndex {
|
||||
return nil, fmt.Errorf("no attestation manifest found for statement")
|
||||
}
|
||||
return idx, nil
|
||||
}
|
||||
|
||||
func SignLayersAndAddToImage(
|
||||
ctx context.Context,
|
||||
attestationLayers []attestation.AttestationLayer,
|
||||
manifest attestation.AttestationManifest,
|
||||
signer dsse.SignerVerifier,
|
||||
opts *attestation.SigningOptions) (v1.Image, *v1.Descriptor, error) {
|
||||
|
||||
signedLayers, err := signLayers(ctx, attestationLayers, signer, opts)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to sign attestations: %w", err)
|
||||
}
|
||||
|
||||
newImg, err := addSignedLayers(signedLayers, manifest, opts)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to add signed layers: %w", err)
|
||||
}
|
||||
if !opts.SkipSubject {
|
||||
newImg = mutate.Subject(newImg, *manifest.SubjectDescriptor).(v1.Image)
|
||||
}
|
||||
newDesc, err := partial.Descriptor(newImg)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get descriptor: %w", err)
|
||||
}
|
||||
cf, err := manifest.Attestation.Image.ConfigFile()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get config file: %w", err)
|
||||
}
|
||||
newDesc.Platform = cf.Platform()
|
||||
if newDesc.Platform == nil {
|
||||
newDesc.Platform = &v1.Platform{
|
||||
Architecture: "unknown",
|
||||
OS: "unknown",
|
||||
}
|
||||
}
|
||||
newDesc.MediaType = manifest.MediaType
|
||||
newDesc.Annotations = manifest.Annotations
|
||||
|
||||
return newImg, newDesc, nil
|
||||
}
|
||||
|
||||
func addImageToIndex(
|
||||
idx v1.ImageIndex,
|
||||
img v1.Image,
|
||||
desc *v1.Descriptor,
|
||||
manifest attestation.AttestationManifest,
|
||||
) (v1.ImageIndex, error) {
|
||||
|
||||
idx = mutate.RemoveManifests(idx, match.Digests(manifest.Digest))
|
||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||
Add: img,
|
||||
Descriptor: *desc,
|
||||
})
|
||||
return idx, nil
|
||||
}
|
||||
|
||||
// signLayers signs each intoto attestation layer with the given signer
|
||||
func signLayers(ctx context.Context, layers []attestation.AttestationLayer, signer dsse.SignerVerifier, opts *attestation.SigningOptions) ([]mutate.Addendum, error) {
|
||||
var signedLayers []mutate.Addendum
|
||||
for _, layer := range layers {
|
||||
// only sign intoto layers
|
||||
if layer.MediaType != types.MediaType(intoto.PayloadType) {
|
||||
continue
|
||||
}
|
||||
// mark attestation as experimental
|
||||
layer.Annotations[InTotoReferenceLifecycleStage] = LifecycleStageExperimental
|
||||
|
||||
// sign the statement
|
||||
env, err := signInTotoStatement(ctx, layer.Statement, signer, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign statement: %w", err)
|
||||
}
|
||||
|
||||
mediaType, err := attestation.DSSEMediaType(layer.Statement.PredicateType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get DSSE media type: %w", err)
|
||||
}
|
||||
data, err := json.Marshal(env)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal envelope: %w", err)
|
||||
}
|
||||
newLayer := static.NewLayer(data, types.MediaType(mediaType))
|
||||
withAnnotations := mutate.Addendum{
|
||||
Layer: newLayer,
|
||||
Annotations: layer.Annotations,
|
||||
}
|
||||
signedLayers = append(signedLayers, withAnnotations)
|
||||
}
|
||||
return signedLayers, nil
|
||||
}
|
||||
|
||||
func signInTotoStatement(ctx context.Context, statement *intoto.Statement, signer dsse.SignerVerifier, opts *attestation.SigningOptions) (*attestation.Envelope, error) {
|
||||
payload, err := json.Marshal(statement)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal statement: %w", err)
|
||||
}
|
||||
env, err := attestation.SignDSSE(ctx, payload, signer, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign statement: %w", err)
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
|
||||
// addSignedLayers adds signed layers to a new or existing attestation image
|
||||
func addSignedLayers(signedLayers []mutate.Addendum, manifest attestation.AttestationManifest, opts *attestation.SigningOptions) (v1.Image, error) {
|
||||
withAnnotations := func(img v1.Image) v1.Image {
|
||||
// this is handy when dealing with referrers
|
||||
return mutate.Annotations(img, map[string]string{
|
||||
attestation.DockerReferenceType: attestation.AttestationManifestType,
|
||||
attestation.DockerReferenceDigest: manifest.SubjectDescriptor.Digest.String(),
|
||||
}).(v1.Image)
|
||||
}
|
||||
var err error
|
||||
if opts.Replace {
|
||||
// create a new attestation image with only signed layers
|
||||
newImg := empty.Image
|
||||
newImg = mutate.MediaType(newImg, manifest.MediaType)
|
||||
newImg = mutate.ConfigMediaType(newImg, "application/vnd.oci.image.config.v1+json")
|
||||
for _, layer := range signedLayers {
|
||||
newImg, err = mutate.Append(newImg, layer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to append signed layer: %w", err)
|
||||
}
|
||||
}
|
||||
// add any existing unsigned (non-intoto) layers to the new image
|
||||
for _, layer := range manifest.Attestation.Layers {
|
||||
if layer.MediaType != types.MediaType(intoto.PayloadType) {
|
||||
newImg, err = mutate.AppendLayers(newImg, layer.Layer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to append unsigned layer: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return withAnnotations(newImg), nil
|
||||
}
|
||||
// Add signed layers to the existing image
|
||||
for _, layer := range signedLayers {
|
||||
manifest.Attestation.Image, err = mutate.Append(manifest.Attestation.Image, layer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to append layer: %w", err)
|
||||
manifest.AddAttestation(ctx, signer, layer.Statement, opts)
|
||||
}
|
||||
}
|
||||
return withAnnotations(manifest.Attestation.Image), nil
|
||||
return attestationManifests, nil
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package attest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
@@ -41,7 +40,6 @@ func TestSignVerifyOCILayout(t *testing.T) {
|
||||
expectedAttestations int
|
||||
replace bool
|
||||
}{
|
||||
|
||||
{"signed replaced", UnsignedTestImage, 0, 4, true},
|
||||
{"without replace", UnsignedTestImage, 4, 4, false},
|
||||
// image without provenance doesn't fail
|
||||
@@ -57,11 +55,13 @@ func TestSignVerifyOCILayout(t *testing.T) {
|
||||
opts := &attestation.SigningOptions{
|
||||
Replace: tc.replace,
|
||||
}
|
||||
attIdx, err := oci.SubjectIndexFromPath(tc.TestImage)
|
||||
attIdx, err := oci.IndexFromPath(tc.TestImage)
|
||||
require.NoError(t, err)
|
||||
signedIndex, err := Sign(ctx, attIdx.Index, signer, opts)
|
||||
signedManifests, err := SignStatements(ctx, attIdx.Index, signer, opts)
|
||||
require.NoError(t, err)
|
||||
signedIndex := attIdx.Index
|
||||
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests)
|
||||
require.NoError(t, err)
|
||||
|
||||
// output signed attestations
|
||||
idx := v1.ImageIndex(empty.Index)
|
||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||
@@ -88,8 +88,8 @@ func TestSignVerifyOCILayout(t *testing.T) {
|
||||
allEnvelopes = append(allEnvelopes, statements...)
|
||||
|
||||
for _, stmt := range statements {
|
||||
assert.Equalf(t, predicate, stmt.Annotations[oci.InTotoPredicateType], "expected predicate-type annotation to be set to %s, got %s", predicate, stmt.Annotations[oci.InTotoPredicateType])
|
||||
assert.Equalf(t, LifecycleStageExperimental, stmt.Annotations[InTotoReferenceLifecycleStage], "expected reference lifecycle stage annotation to be set to %s, got %s", LifecycleStageExperimental, stmt.Annotations[InTotoReferenceLifecycleStage])
|
||||
assert.Equalf(t, predicate, stmt.Annotations[attestation.InTotoPredicateType], "expected predicate-type annotation to be set to %s, got %s", predicate, stmt.Annotations[attestation.InTotoPredicateType])
|
||||
assert.Equalf(t, attestation.LifecycleStageExperimental, stmt.Annotations[attestation.InTotoReferenceLifecycleStage], "expected reference lifecycle stage annotation to be set to %s, got %s", attestation.LifecycleStageExperimental, stmt.Annotations[attestation.InTotoReferenceLifecycleStage])
|
||||
}
|
||||
}
|
||||
assert.Equalf(t, tc.expectedAttestations, len(allEnvelopes), "expected %d attestations, got %d", tc.expectedAttestations, len(allEnvelopes))
|
||||
@@ -100,70 +100,6 @@ func TestSignVerifyOCILayout(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddAttestation(t *testing.T) {
|
||||
ctx, signer := test.Setup(t)
|
||||
|
||||
expectedAttestations := 2
|
||||
expectedStatements := 4
|
||||
|
||||
outputLayout := test.CreateTempDir(t, "", TestTempDir)
|
||||
attIdx, err := oci.SubjectIndexFromPath(UnsignedTestImage)
|
||||
require.NoError(t, err)
|
||||
|
||||
statementToAdd := &intoto.Statement{
|
||||
StatementHeader: intoto.StatementHeader{
|
||||
PredicateType: attestation.VSAPredicateType,
|
||||
Type: intoto.StatementInTotoV01,
|
||||
Subject: []intoto.Subject{
|
||||
{
|
||||
Name: attIdx.Name,
|
||||
Digest: map[string]string{
|
||||
"sha256": "da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: attIdx.Name,
|
||||
Digest: map[string]string{
|
||||
"sha256": "7a76cec943853f9f7105b1976afa1bf7cd5bb6afc4e9d5852dd8da7cf81ae86e",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
signedIndex, err := AddAttestation(ctx, attIdx.Index, statementToAdd, signer)
|
||||
require.NoError(t, err)
|
||||
|
||||
// output signed attestations
|
||||
idx := v1.ImageIndex(empty.Index)
|
||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||
Add: signedIndex,
|
||||
Descriptor: v1.Descriptor{
|
||||
Annotations: map[string]string{
|
||||
oci.OciReferenceTarget: attIdx.Name,
|
||||
},
|
||||
},
|
||||
})
|
||||
_, err = layout.Write(outputLayout, idx)
|
||||
require.NoError(t, err)
|
||||
|
||||
var allEnvelopes []*test.AnnotatedStatement
|
||||
mt, _ := attestation.DSSEMediaType(attestation.VSAPredicateType)
|
||||
statements, err := test.ExtractAnnotatedStatements(outputLayout, mt)
|
||||
require.NoError(t, err)
|
||||
allEnvelopes = append(allEnvelopes, statements...)
|
||||
|
||||
for _, stmt := range statements {
|
||||
assert.Equalf(t, attestation.VSAPredicateType, stmt.Annotations[oci.InTotoPredicateType], "expected predicate-type annotation to be set to %s, got %s", attestation.VSAPredicateType, stmt.Annotations[oci.InTotoPredicateType])
|
||||
assert.Equalf(t, LifecycleStageExperimental, stmt.Annotations[InTotoReferenceLifecycleStage], "expected reference lifecycle stage annotation to be set to %s, got %s", LifecycleStageExperimental, stmt.Annotations[InTotoReferenceLifecycleStage])
|
||||
}
|
||||
assert.Equalf(t, expectedAttestations, len(allEnvelopes), "expected %d attestations, got %d", expectedAttestations, len(allEnvelopes))
|
||||
statements, err = test.ExtractAnnotatedStatements(outputLayout, intoto.PayloadType)
|
||||
fmt.Printf("statements: %+v\n", statements)
|
||||
require.NoError(t, err)
|
||||
assert.Equalf(t, expectedStatements, len(statements), "expected %d statement, got %d", expectedStatements, len(statements))
|
||||
}
|
||||
|
||||
func TestAddSignedLayerAnnotations(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
@@ -176,33 +112,29 @@ func TestAddSignedLayerAnnotations(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
data := []byte("signed")
|
||||
signedLayer := static.NewLayer(data, types.MediaType(intoto.PayloadType))
|
||||
signedLayers := []mutate.Addendum{
|
||||
{
|
||||
Layer: signedLayer,
|
||||
Annotations: map[string]string{"test": "test"},
|
||||
},
|
||||
}
|
||||
data = []byte("test")
|
||||
testLayer := static.NewLayer(data, types.MediaType(intoto.PayloadType))
|
||||
mediaType := types.OCIManifestSchema1
|
||||
opts := &attestation.SigningOptions{
|
||||
Replace: tc.replace,
|
||||
}
|
||||
manifest := attestation.AttestationManifest{
|
||||
originalLayer := &attestation.AttestationLayer{
|
||||
Layer: testLayer,
|
||||
Statement: &intoto.Statement{},
|
||||
Annotations: map[string]string{"test": "test"},
|
||||
}
|
||||
|
||||
manifest := &attestation.AttestationManifest{
|
||||
MediaType: mediaType,
|
||||
Attestation: attestation.AttestationImage{
|
||||
Attestation: &attestation.AttestationImage{
|
||||
Image: empty.Image,
|
||||
Layers: []attestation.AttestationLayer{
|
||||
{
|
||||
Layer: testLayer,
|
||||
Statement: &intoto.Statement{},
|
||||
},
|
||||
Layers: []*attestation.AttestationLayer{
|
||||
originalLayer,
|
||||
},
|
||||
},
|
||||
SubjectDescriptor: &v1.Descriptor{},
|
||||
}
|
||||
newImg, err := addSignedLayers(signedLayers, manifest, opts)
|
||||
err := manifest.AddOrReplaceLayer(originalLayer, opts)
|
||||
newImg := manifest.Attestation.Image
|
||||
require.NoError(t, err)
|
||||
mf, _ := newImg.RawManifest()
|
||||
type Annotations struct {
|
||||
|
||||
@@ -7,11 +7,6 @@ import (
|
||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||
)
|
||||
|
||||
const (
|
||||
InTotoReferenceLifecycleStage = "vnd.docker.lifecycle-stage"
|
||||
LifecycleStageExperimental = "experimental"
|
||||
)
|
||||
|
||||
type Outcome string
|
||||
|
||||
const (
|
||||
|
||||
@@ -80,10 +80,13 @@ func TestVSA(t *testing.T) {
|
||||
opts := &attestation.SigningOptions{
|
||||
Replace: true,
|
||||
}
|
||||
attIdx, err := oci.SubjectIndexFromPath(UnsignedTestImage)
|
||||
assert.NoError(t, err)
|
||||
signedIndex, err := Sign(ctx, attIdx.Index, signer, opts)
|
||||
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
||||
assert.NoError(t, err)
|
||||
signedManifests, err := SignStatements(ctx, attIdx.Index, signer, opts)
|
||||
require.NoError(t, err)
|
||||
signedIndex := attIdx.Index
|
||||
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests)
|
||||
require.NoError(t, err)
|
||||
|
||||
// output signed attestations
|
||||
idx := v1.ImageIndex(empty.Index)
|
||||
@@ -136,10 +139,13 @@ func TestVerificationFailure(t *testing.T) {
|
||||
opts := &attestation.SigningOptions{
|
||||
Replace: true,
|
||||
}
|
||||
attIdx, err := oci.SubjectIndexFromPath(UnsignedTestImage)
|
||||
assert.NoError(t, err)
|
||||
signedIndex, err := Sign(ctx, attIdx.Index, signer, opts)
|
||||
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
||||
assert.NoError(t, err)
|
||||
signedManifests, err := SignStatements(ctx, attIdx.Index, signer, opts)
|
||||
require.NoError(t, err)
|
||||
signedIndex := attIdx.Index
|
||||
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests)
|
||||
require.NoError(t, err)
|
||||
|
||||
// output signed attestations
|
||||
idx := v1.ImageIndex(empty.Index)
|
||||
@@ -201,7 +207,7 @@ func TestSignVerifyNoTL(t *testing.T) {
|
||||
{name: "no tl", signTL: false, policyDir: PassPolicyDir, success: false},
|
||||
}
|
||||
|
||||
attIdx, err := oci.SubjectIndexFromPath(UnsignedTestImage)
|
||||
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, tc := range testCases {
|
||||
@@ -211,9 +217,11 @@ func TestSignVerifyNoTL(t *testing.T) {
|
||||
SkipTL: tc.signTL,
|
||||
}
|
||||
|
||||
signedIndex, err := Sign(ctx, attIdx.Index, signer, opts)
|
||||
assert.NoError(t, err)
|
||||
|
||||
signedManifests, err := SignStatements(ctx, attIdx.Index, signer, opts)
|
||||
require.NoError(t, err)
|
||||
signedIndex := attIdx.Index
|
||||
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests)
|
||||
require.NoError(t, err)
|
||||
// output signed attestations
|
||||
idx := v1.ImageIndex(empty.Index)
|
||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
package attestation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/empty"
|
||||
"github.com/google/go-containerregistry/pkg/v1/match"
|
||||
"github.com/google/go-containerregistry/pkg/v1/mutate"
|
||||
"github.com/google/go-containerregistry/pkg/v1/partial"
|
||||
"github.com/google/go-containerregistry/pkg/v1/static"
|
||||
"github.com/google/go-containerregistry/pkg/v1/types"
|
||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||
"github.com/secure-systems-lab/go-securesystemslib/dsse"
|
||||
)
|
||||
|
||||
// GetAttestationManifestsFromIndex extracts all attestation manifests from an index
|
||||
func GetAttestationManifestsFromIndex(index v1.ImageIndex) ([]AttestationManifest, error) {
|
||||
func GetAttestationManifestsFromIndex(index v1.ImageIndex) ([]*AttestationManifest, error) {
|
||||
idx, err := index.IndexManifest()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract IndexManifest from ImageIndex: %w", err)
|
||||
@@ -22,9 +28,8 @@ func GetAttestationManifestsFromIndex(index v1.ImageIndex) ([]AttestationManifes
|
||||
subjects[subject.Digest.String()] = &subject
|
||||
}
|
||||
|
||||
var attestationManifests []AttestationManifest
|
||||
var attestationManifests []*AttestationManifest
|
||||
for _, manifest := range idx.Manifests {
|
||||
|
||||
if manifest.Annotations[DockerReferenceType] == AttestationManifestType {
|
||||
subject := subjects[manifest.Annotations[DockerReferenceDigest]]
|
||||
if subject == nil {
|
||||
@@ -39,10 +44,10 @@ func GetAttestationManifestsFromIndex(index v1.ImageIndex) ([]AttestationManifes
|
||||
return nil, fmt.Errorf("failed to get attestations from image: %w", err)
|
||||
}
|
||||
attestationManifests = append(attestationManifests,
|
||||
AttestationManifest{
|
||||
Descriptor: manifest,
|
||||
&AttestationManifest{
|
||||
Descriptor: &manifest,
|
||||
SubjectDescriptor: subject,
|
||||
Attestation: AttestationImage{
|
||||
Attestation: &AttestationImage{
|
||||
Layers: attestationLayers,
|
||||
Image: attestationImage},
|
||||
MediaType: manifest.MediaType,
|
||||
@@ -54,12 +59,12 @@ func GetAttestationManifestsFromIndex(index v1.ImageIndex) ([]AttestationManifes
|
||||
}
|
||||
|
||||
// GetAttestationsFromImage extracts all attestation layers from an image
|
||||
func GetAttestationsFromImage(image v1.Image) ([]AttestationLayer, error) {
|
||||
func GetAttestationsFromImage(image v1.Image) ([]*AttestationLayer, error) {
|
||||
layers, err := image.Layers()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract layers from image: %w", err)
|
||||
}
|
||||
var attestationLayers []AttestationLayer
|
||||
var attestationLayers []*AttestationLayer
|
||||
for _, layer := range layers {
|
||||
// parse layer blob as json
|
||||
r, err := layer.Uncompressed()
|
||||
@@ -85,7 +90,162 @@ func GetAttestationsFromImage(image v1.Image) ([]AttestationLayer, error) {
|
||||
return nil, fmt.Errorf("failed to decode statement layer contents: %w", err)
|
||||
}
|
||||
}
|
||||
attestationLayers = append(attestationLayers, AttestationLayer{Layer: layer, MediaType: mt, Statement: stmt, Annotations: ann})
|
||||
attestationLayers = append(attestationLayers, &AttestationLayer{Layer: layer, MediaType: mt, Statement: stmt, Annotations: ann})
|
||||
}
|
||||
return attestationLayers, nil
|
||||
}
|
||||
|
||||
func (manifest *AttestationManifest) AddAttestation(ctx context.Context, signer dsse.SignerVerifier, statement *intoto.Statement, opts *SigningOptions) error {
|
||||
layer, err := createSignedImageLayer(ctx, statement, signer, opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create signed layer: %w", err)
|
||||
}
|
||||
newImg, newDesc, err := addLayerToImage(manifest, layer, opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add signed layers to image: %w", err)
|
||||
}
|
||||
manifest.Attestation.Image = newImg
|
||||
manifest.Descriptor = newDesc
|
||||
return nil
|
||||
}
|
||||
|
||||
func createSignedImageLayer(ctx context.Context, statement *intoto.Statement, signer dsse.SignerVerifier, opts *SigningOptions) (*AttestationLayer, error) {
|
||||
|
||||
// sign the statement
|
||||
env, err := SignInTotoStatement(ctx, statement, signer, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign statement: %w", err)
|
||||
}
|
||||
|
||||
mediaType, err := DSSEMediaType(statement.PredicateType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get DSSE media type: %w", err)
|
||||
}
|
||||
data, err := json.Marshal(env)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal envelope: %w", err)
|
||||
}
|
||||
return &AttestationLayer{
|
||||
Statement: statement,
|
||||
MediaType: types.MediaType(intoto.PayloadType),
|
||||
Annotations: map[string]string{
|
||||
InTotoPredicateType: statement.PredicateType,
|
||||
InTotoReferenceLifecycleStage: LifecycleStageExperimental,
|
||||
},
|
||||
Layer: static.NewLayer(data, types.MediaType(mediaType)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func SignInTotoStatement(ctx context.Context, statement *intoto.Statement, signer dsse.SignerVerifier, opts *SigningOptions) (*Envelope, error) {
|
||||
payload, err := json.Marshal(statement)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal statement: %w", err)
|
||||
}
|
||||
env, err := SignDSSE(ctx, payload, signer, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign statement: %w", err)
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
|
||||
func addLayerToImage(
|
||||
manifest *AttestationManifest,
|
||||
layer *AttestationLayer,
|
||||
opts *SigningOptions) (v1.Image, *v1.Descriptor, error) {
|
||||
|
||||
err := manifest.AddOrReplaceLayer(layer, opts)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to add signed layers: %w", err)
|
||||
}
|
||||
newImg := manifest.Attestation.Image
|
||||
if !opts.SkipSubject {
|
||||
newImg = mutate.Subject(newImg, *manifest.SubjectDescriptor).(v1.Image)
|
||||
}
|
||||
newDesc, err := partial.Descriptor(newImg)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get descriptor: %w", err)
|
||||
}
|
||||
cf, err := manifest.Attestation.Image.ConfigFile()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get config file: %w", err)
|
||||
}
|
||||
newDesc.Platform = cf.Platform()
|
||||
if newDesc.Platform == nil {
|
||||
newDesc.Platform = &v1.Platform{
|
||||
Architecture: "unknown",
|
||||
OS: "unknown",
|
||||
}
|
||||
}
|
||||
newDesc.MediaType = manifest.MediaType
|
||||
newDesc.Annotations = manifest.Annotations
|
||||
return newImg, newDesc, nil
|
||||
}
|
||||
|
||||
// AddOrReplaceLayer adds signed layers to a new or existing attestation image
|
||||
// NOTE: the pointers attestation.AttestationLayer.Statement are compared when replacing,
|
||||
// so make sure you are signing a layer extracted from the original attestation-manifest image!
|
||||
func (manifest *AttestationManifest) AddOrReplaceLayer(signedLayer *AttestationLayer, opts *SigningOptions) error {
|
||||
var err error
|
||||
// always create a new image from all the layers
|
||||
newImg := empty.Image
|
||||
newImg = mutate.Annotations(newImg, map[string]string{
|
||||
DockerReferenceType: AttestationManifestType,
|
||||
DockerReferenceDigest: manifest.SubjectDescriptor.Digest.String(),
|
||||
}).(v1.Image)
|
||||
|
||||
newImg = mutate.MediaType(newImg, manifest.MediaType)
|
||||
newImg = mutate.ConfigMediaType(newImg, "application/vnd.oci.image.config.v1+json")
|
||||
add := mutate.Addendum{
|
||||
Layer: signedLayer.Layer,
|
||||
Annotations: signedLayer.Annotations,
|
||||
}
|
||||
newImg, err = mutate.Append(newImg, add)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add signed layer to image: %w", err)
|
||||
}
|
||||
layers := make([]*AttestationLayer, 0)
|
||||
for _, layer := range manifest.Attestation.Layers {
|
||||
if layer.Statement == signedLayer.Statement && opts.Replace {
|
||||
continue
|
||||
}
|
||||
add := mutate.Addendum{
|
||||
Layer: layer.Layer,
|
||||
Annotations: layer.Annotations,
|
||||
}
|
||||
newImg, err = mutate.Append(newImg, add)
|
||||
layers = append(layers, layer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add layer to image: %w", err)
|
||||
}
|
||||
}
|
||||
manifest.Attestation.Layers = append(layers, signedLayer)
|
||||
manifest.Attestation.Image = newImg
|
||||
return nil
|
||||
}
|
||||
|
||||
func AddImageToIndex(
|
||||
idx v1.ImageIndex,
|
||||
manifest *AttestationManifest,
|
||||
) (v1.ImageIndex, error) {
|
||||
idx = mutate.RemoveManifests(idx, match.Digests(manifest.Digest))
|
||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||
Add: manifest.Attestation.Image,
|
||||
Descriptor: *manifest.Descriptor,
|
||||
})
|
||||
return idx, nil
|
||||
}
|
||||
|
||||
func AddImagesToIndex(
|
||||
idx v1.ImageIndex,
|
||||
manifests []*AttestationManifest,
|
||||
) (v1.ImageIndex, error) {
|
||||
for _, manifest := range manifests {
|
||||
var err error
|
||||
idx, err = AddImageToIndex(idx, manifest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to add image to index: %w", err)
|
||||
}
|
||||
}
|
||||
return idx, nil
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ func TestAttestationReferenceTypes(t *testing.T) {
|
||||
Replace: true,
|
||||
SkipSubject: tc.skipSubject,
|
||||
}
|
||||
attIdx, err := oci.SubjectIndexFromPath(UnsignedTestImage)
|
||||
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
||||
require.NoError(t, err)
|
||||
|
||||
indexName := fmt.Sprintf("%s/repo:root", u.Host)
|
||||
@@ -108,15 +108,18 @@ func TestAttestationReferenceTypes(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
repo := fmt.Sprintf("%s/referrers", ru.Host)
|
||||
tc.referrersRepo = repo
|
||||
images, err := attest.SignedAttestationImages(ctx, attIdx.Index, signer, opts)
|
||||
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
|
||||
require.NoError(t, err)
|
||||
err = mirror.PushIndexToRegistry(attIdx.Index, indexName)
|
||||
for _, img := range images {
|
||||
err = mirror.PushImageToRegistry(img.Image, fmt.Sprintf("%s:tag-does-not-matter", repo))
|
||||
for _, img := range signedManifests {
|
||||
err = mirror.PushImageToRegistry(img.Attestation.Image, fmt.Sprintf("%s:tag-does-not-matter", repo))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
} else {
|
||||
signedIndex, err := attest.Sign(ctx, attIdx.Index, signer, opts)
|
||||
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
|
||||
require.NoError(t, err)
|
||||
signedIndex := attIdx.Index
|
||||
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests)
|
||||
require.NoError(t, err)
|
||||
err = mirror.PushIndexToRegistry(signedIndex, indexName)
|
||||
require.NoError(t, err)
|
||||
@@ -215,20 +218,20 @@ func TestReferencesInDifferentRepo(t *testing.T) {
|
||||
Replace: true,
|
||||
SkipTL: true,
|
||||
}
|
||||
attIdx, err := oci.SubjectIndexFromPath(UnsignedTestImage)
|
||||
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
||||
require.NoError(t, err)
|
||||
|
||||
indexName := fmt.Sprintf("%s/%s:latest", serverUrl.Host, repoName)
|
||||
err = mirror.PushIndexToRegistry(attIdx.Index, indexName)
|
||||
require.NoError(t, err)
|
||||
|
||||
signedImages, err := attest.SignedAttestationImages(ctx, attIdx.Index, signer, opts)
|
||||
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
|
||||
require.NoError(t, err)
|
||||
|
||||
// push signed attestation image to the ref server
|
||||
for _, img := range signedImages {
|
||||
for _, img := range signedManifests {
|
||||
// push references using subject-digest.att convention
|
||||
err = mirror.PushImageToRegistry(img.Image, fmt.Sprintf("%s/%s:tag-does-not-matter", refServerUrl.Host, repoName))
|
||||
err = mirror.PushImageToRegistry(img.Attestation.Image, fmt.Sprintf("%s/%s:tag-does-not-matter", refServerUrl.Host, repoName))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
mfs2, err := attIdx.Index.IndexManifest()
|
||||
|
||||
@@ -12,12 +12,15 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
DockerReferenceType = "vnd.docker.reference.type"
|
||||
AttestationManifestType = "attestation-manifest"
|
||||
DockerReferenceDigest = "vnd.docker.reference.digest"
|
||||
DockerDsseExtKind = "application/vnd.docker.attestation-verification.v1+json"
|
||||
RekorTlExtKind = "Rekor"
|
||||
OCIDescriptorDSSEMediaType = ociv1.MediaTypeDescriptor + "+dsse"
|
||||
DockerReferenceType = "vnd.docker.reference.type"
|
||||
AttestationManifestType = "attestation-manifest"
|
||||
InTotoPredicateType = "in-toto.io/predicate-type"
|
||||
DockerReferenceDigest = "vnd.docker.reference.digest"
|
||||
DockerDsseExtKind = "application/vnd.docker.attestation-verification.v1+json"
|
||||
RekorTlExtKind = "Rekor"
|
||||
OCIDescriptorDSSEMediaType = ociv1.MediaTypeDescriptor + "+dsse"
|
||||
InTotoReferenceLifecycleStage = "vnd.docker.lifecycle-stage"
|
||||
LifecycleStageExperimental = "experimental"
|
||||
)
|
||||
|
||||
var base64Encoding = base64.StdEncoding.Strict()
|
||||
@@ -30,19 +33,19 @@ type AttestationLayer struct {
|
||||
}
|
||||
|
||||
type AttestationImage struct {
|
||||
Layers []AttestationLayer
|
||||
Layers []*AttestationLayer
|
||||
Image v1.Image
|
||||
}
|
||||
|
||||
type SignedAttestationImage struct {
|
||||
Image v1.Image
|
||||
Descriptor *v1.Descriptor
|
||||
AttestationManifest AttestationManifest
|
||||
AttestationManifest *AttestationManifest
|
||||
}
|
||||
|
||||
type AttestationManifest struct {
|
||||
Descriptor v1.Descriptor
|
||||
Attestation AttestationImage
|
||||
Descriptor *v1.Descriptor
|
||||
Attestation *AttestationImage
|
||||
MediaType types.MediaType
|
||||
Annotations map[string]string
|
||||
Digest v1.Hash
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
func TestRegistryAuth(t *testing.T) {
|
||||
UnsignedTestImage := filepath.Join("..", "..", "test", "testdata", "unsigned-test-image")
|
||||
|
||||
attIdx, err := oci.SubjectIndexFromPath(UnsignedTestImage)
|
||||
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
||||
require.NoError(t, err)
|
||||
// test cases for ecr, gcr and dockerhub
|
||||
testCases := []struct {
|
||||
@@ -27,7 +27,7 @@ func TestRegistryAuth(t *testing.T) {
|
||||
t.Run(tc.Image, func(t *testing.T) {
|
||||
err := mirror.PushIndexToRegistry(attIdx.Index, tc.Image)
|
||||
require.NoError(t, err)
|
||||
_, err = oci.SubjectIndexFromRemote(tc.Image)
|
||||
_, err = oci.IndexFromRemote(tc.Image)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/attest/pkg/attestation"
|
||||
att "github.com/docker/attest/pkg/attestation"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/layout"
|
||||
@@ -50,7 +51,7 @@ func (r *OCILayoutResolver) Attestations(ctx context.Context, predicateType stri
|
||||
var envs []*att.Envelope
|
||||
manifest := r.AttestationManifest.Manifest
|
||||
for i, l := range manifest.Layers {
|
||||
if l.Annotations[InTotoPredicateType] != predicateType {
|
||||
if l.Annotations[attestation.InTotoPredicateType] != predicateType {
|
||||
continue
|
||||
}
|
||||
layer := layers[i]
|
||||
@@ -121,7 +122,7 @@ func attestationManifestFromOCILayout(path string, platform *v1.Platform) (*Atte
|
||||
}
|
||||
}
|
||||
for _, mf := range mfs2.Manifests {
|
||||
if mf.Annotations[att.DockerReferenceType] != AttestationManifestType {
|
||||
if mf.Annotations[att.DockerReferenceType] != attestation.AttestationManifestType {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ func ExtractEnvelopes(ia *AttestationManifest, predicateType string) ([]*att.Env
|
||||
for i, l := range manifest.Layers {
|
||||
if (strings.HasPrefix(string(l.MediaType), "application/vnd.in-toto.")) &&
|
||||
strings.HasSuffix(string(l.MediaType), "+dsse") &&
|
||||
l.Annotations[InTotoPredicateType] == predicateType {
|
||||
l.Annotations[att.InTotoPredicateType] == predicateType {
|
||||
reader, err := layers[i].Uncompressed()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get layer contents: %w", err)
|
||||
|
||||
@@ -79,7 +79,7 @@ func (r *ReferrersResolver) resolveAttestations(ctx context.Context) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get manifest: %w", err)
|
||||
}
|
||||
if manifest.Annotations[att.DockerReferenceType] != AttestationManifestType {
|
||||
if manifest.Annotations[att.DockerReferenceType] != att.AttestationManifestType {
|
||||
continue
|
||||
}
|
||||
if manifest.Annotations[att.DockerReferenceDigest] != subjectDigest {
|
||||
|
||||
@@ -29,9 +29,12 @@ func TestRegistry(t *testing.T) {
|
||||
Replace: true,
|
||||
SkipSubject: true,
|
||||
}
|
||||
attIdx, err := oci.SubjectIndexFromPath(oci.UnsignedTestImage)
|
||||
attIdx, err := oci.IndexFromPath(oci.UnsignedTestImage)
|
||||
require.NoError(t, err)
|
||||
signedIndex, err := attest.Sign(ctx, attIdx.Index, signer, opts)
|
||||
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
|
||||
require.NoError(t, err)
|
||||
signedIndex := attIdx.Index
|
||||
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests)
|
||||
require.NoError(t, err)
|
||||
|
||||
indexName := fmt.Sprintf("%s/repo:root", u.Host)
|
||||
|
||||
@@ -11,17 +11,15 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
AttestationManifestType = "attestation-manifest"
|
||||
InTotoPredicateType = "in-toto.io/predicate-type"
|
||||
OciReferenceTarget = "org.opencontainers.image.ref.name"
|
||||
LocalPrefix = "oci://"
|
||||
RegistryPrefix = "docker://"
|
||||
OCI SourceType = "OCI"
|
||||
Docker SourceType = "Docker"
|
||||
OciReferenceTarget = "org.opencontainers.image.ref.name"
|
||||
LocalPrefix = "oci://"
|
||||
RegistryPrefix = "docker://"
|
||||
OCI SourceType = "OCI"
|
||||
Docker SourceType = "Docker"
|
||||
)
|
||||
|
||||
type SourceType string
|
||||
type SubjectIndex struct {
|
||||
type NamedIndex struct {
|
||||
Index v1.ImageIndex
|
||||
Name string
|
||||
}
|
||||
@@ -42,7 +40,7 @@ type ImageSpec struct {
|
||||
Platform *v1.Platform
|
||||
}
|
||||
|
||||
func SubjectIndexFromPath(path string) (*SubjectIndex, error) {
|
||||
func IndexFromPath(path string) (*NamedIndex, error) {
|
||||
wrapperIdx, err := layout.ImageIndexFromPath(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load image index: %w", err)
|
||||
@@ -59,13 +57,13 @@ func SubjectIndexFromPath(path string) (*SubjectIndex, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract ImageIndex for digest %s: %w", idxDigest.String(), err)
|
||||
}
|
||||
return &SubjectIndex{
|
||||
return &NamedIndex{
|
||||
Index: idx,
|
||||
Name: imageName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func SubjectIndexFromRemote(image string) (*SubjectIndex, error) {
|
||||
func IndexFromRemote(image string) (*NamedIndex, error) {
|
||||
ref, err := name.ParseReference(image)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse image reference %s: %w", image, err)
|
||||
@@ -76,17 +74,17 @@ func SubjectIndexFromRemote(image string) (*SubjectIndex, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to pull image %s: %w", image, err)
|
||||
}
|
||||
return &SubjectIndex{
|
||||
return &NamedIndex{
|
||||
Index: idx,
|
||||
Name: image,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func LoadSubjectIndex(input *ImageSpec) (*SubjectIndex, error) {
|
||||
func LoadIndex(input *ImageSpec) (*NamedIndex, error) {
|
||||
if input.Type == OCI {
|
||||
return SubjectIndexFromPath(input.Identifier)
|
||||
return IndexFromPath(input.Identifier)
|
||||
} else {
|
||||
return SubjectIndexFromRemote(input.Identifier)
|
||||
return IndexFromRemote(input.Identifier)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user