Single attestation when creating VSA

This commit is contained in:
James Carnegie
2024-07-07 22:34:34 +01:00
parent a4c3bd07fe
commit c3ece3f02d
17 changed files with 433 additions and 188 deletions

View File

@@ -29,15 +29,15 @@ func GetAttestationManifestsFromIndex(index v1.ImageIndex) ([]*AttestationManife
}
var attestationManifests []*AttestationManifest
for _, manifest := range idx.Manifests {
if manifest.Annotations[DockerReferenceType] == AttestationManifestType {
subject := subjects[manifest.Annotations[DockerReferenceDigest]]
for _, desc := range idx.Manifests {
if desc.Annotations[DockerReferenceType] == AttestationManifestType {
subject := subjects[desc.Annotations[DockerReferenceDigest]]
if subject == nil {
return nil, fmt.Errorf("failed to find subject for attestation manifest: %w", err)
}
attestationImage, err := index.Image(manifest.Digest)
attestationImage, err := index.Image(desc.Digest)
if err != nil {
return nil, fmt.Errorf("failed to extract attestation image with digest %s: %w", manifest.Digest.String(), err)
return nil, fmt.Errorf("failed to extract attestation image with digest %s: %w", desc.Digest.String(), err)
}
attestationLayers, err := GetAttestationsFromImage(attestationImage)
if err != nil {
@@ -45,14 +45,11 @@ func GetAttestationManifestsFromIndex(index v1.ImageIndex) ([]*AttestationManife
}
attestationManifests = append(attestationManifests,
&AttestationManifest{
Descriptor: &manifest,
SubjectDescriptor: subject,
Attestation: &AttestationImage{
Layers: attestationLayers,
Image: attestationImage},
MediaType: manifest.MediaType,
Annotations: manifest.Annotations,
Digest: manifest.Digest})
OriginalDescriptor: &desc,
SubjectDescriptor: subject,
AttestationImage: &AttestationImage{
Layers: attestationLayers,
Descriptor: &desc}})
}
}
return attestationManifests, nil
@@ -90,7 +87,7 @@ 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, Statement: stmt, Annotations: ann})
}
return attestationLayers, nil
}
@@ -100,13 +97,7 @@ func (manifest *AttestationManifest) AddAttestation(ctx context.Context, signer
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
return addLayerToImage(manifest, layer, opts)
}
func createSignedImageLayer(ctx context.Context, statement *intoto.Statement, signer dsse.SignerVerifier, opts *SigningOptions) (*AttestationLayer, error) {
@@ -127,7 +118,6 @@ func createSignedImageLayer(ctx context.Context, statement *intoto.Statement, si
}
return &AttestationLayer{
Statement: statement,
MediaType: types.MediaType(intoto.PayloadType),
Annotations: map[string]string{
InTotoPredicateType: statement.PredicateType,
InTotoReferenceLifecycleStage: LifecycleStageExperimental,
@@ -151,35 +141,27 @@ func SignInTotoStatement(ctx context.Context, statement *intoto.Statement, signe
func addLayerToImage(
manifest *AttestationManifest,
layer *AttestationLayer,
opts *SigningOptions) (v1.Image, *v1.Descriptor, error) {
opts *SigningOptions) error {
err := manifest.AddOrReplaceLayer(layer, opts)
if err != nil {
return nil, nil, fmt.Errorf("failed to add signed layers: %w", err)
return fmt.Errorf("failed to add signed layers: %w", err)
}
newImg := manifest.Attestation.Image
if !opts.SkipSubject {
newImg = mutate.Subject(newImg, *manifest.SubjectDescriptor).(v1.Image)
manifest.AttestationImage.Image = mutate.Subject(manifest.AttestationImage.Image, *manifest.SubjectDescriptor).(v1.Image)
}
newDesc, err := partial.Descriptor(newImg)
newDesc, err := partial.Descriptor(manifest.AttestationImage.Image)
if err != nil {
return nil, nil, fmt.Errorf("failed to get descriptor: %w", err)
return 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 = &v1.Platform{
Architecture: "unknown",
OS: "unknown",
}
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
newDesc.MediaType = manifest.OriginalDescriptor.MediaType
newDesc.Annotations = manifest.OriginalDescriptor.Annotations
manifest.AttestationImage.Descriptor = newDesc
return nil
}
// AddOrReplaceLayer adds signed layers to a new or existing attestation image
@@ -189,12 +171,13 @@ func (manifest *AttestationManifest) AddOrReplaceLayer(signedLayer *AttestationL
var err error
// always create a new image from all the layers
newImg := empty.Image
//TODO: maybe we don't need these anymore
newImg = mutate.Annotations(newImg, map[string]string{
DockerReferenceType: AttestationManifestType,
DockerReferenceDigest: manifest.SubjectDescriptor.Digest.String(),
}).(v1.Image)
newImg = mutate.MediaType(newImg, manifest.MediaType)
newImg = mutate.MediaType(newImg, manifest.OriginalDescriptor.MediaType)
newImg = mutate.ConfigMediaType(newImg, "application/vnd.oci.image.config.v1+json")
add := mutate.Addendum{
Layer: signedLayer.Layer,
@@ -204,23 +187,25 @@ func (manifest *AttestationManifest) AddOrReplaceLayer(signedLayer *AttestationL
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 {
newLayers := make([]*AttestationLayer, 0)
for _, existingLayer := range manifest.AttestationImage.Layers {
// if we're replacing, then we don't add it back in
if existingLayer.Statement == signedLayer.Statement && opts.Replace {
continue
}
// add original layer back in
add := mutate.Addendum{
Layer: layer.Layer,
Annotations: layer.Annotations,
Layer: existingLayer.Layer,
Annotations: existingLayer.Annotations,
}
newImg, err = mutate.Append(newImg, add)
layers = append(layers, layer)
newLayers = append(newLayers, existingLayer)
if err != nil {
return fmt.Errorf("failed to add layer to image: %w", err)
}
}
manifest.Attestation.Layers = append(layers, signedLayer)
manifest.Attestation.Image = newImg
manifest.AttestationImage.Layers = append(newLayers, signedLayer)
manifest.AttestationImage.Image = newImg
return nil
}
@@ -228,10 +213,10 @@ func AddImageToIndex(
idx v1.ImageIndex,
manifest *AttestationManifest,
) (v1.ImageIndex, error) {
idx = mutate.RemoveManifests(idx, match.Digests(manifest.Digest))
idx = mutate.RemoveManifests(idx, match.Digests(manifest.OriginalDescriptor.Digest))
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
Add: manifest.Attestation.Image,
Descriptor: *manifest.Descriptor,
Add: manifest.AttestationImage.Image,
Descriptor: *manifest.AttestationImage.Descriptor,
})
return idx, nil
}

View File

@@ -104,6 +104,7 @@ func TestAttestationReferenceTypes(t *testing.T) {
opts := &attestation.SigningOptions{
Replace: true,
SkipSubject: tc.skipSubject,
SkipTL: true,
}
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
require.NoError(t, err)
@@ -121,7 +122,7 @@ func TestAttestationReferenceTypes(t *testing.T) {
err = mirror.PushIndexToRegistry(attIdx.Index, indexName)
require.NoError(t, err)
for _, img := range signedManifests {
err = mirror.PushImageToRegistry(img.Attestation.Image, fmt.Sprintf("%s:tag-does-not-matter", repo))
err = mirror.PushImageToRegistry(img.AttestationImage.Image, fmt.Sprintf("%s:tag-does-not-matter", repo))
require.NoError(t, err)
}
} else {
@@ -213,10 +214,34 @@ func TestReferencesInDifferentRepo(t *testing.T) {
refServer: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
},
} {
t.Run(tc.name, func(t *testing.T) {
server := tc.server
defer server.Close()
serverUrl, err := url.Parse(server.URL)
server := tc.server
defer server.Close()
serverUrl, err := url.Parse(server.URL)
require.NoError(t, err)
refServer := tc.refServer
defer refServer.Close()
refServerUrl, err := url.Parse(refServer.URL)
require.NoError(t, err)
opts := &attestation.SigningOptions{
Replace: true,
SkipTL: true,
}
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)
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
require.NoError(t, err)
// push signed attestation image to the ref server
for _, img := range signedManifests {
// push references using subject-digest.att convention
err = mirror.PushImageToRegistry(img.AttestationImage.Image, fmt.Sprintf("%s/%s:tag-does-not-matter", refServerUrl.Host, repoName))
require.NoError(t, err)
refServer := tc.refServer
@@ -241,7 +266,7 @@ func TestReferencesInDifferentRepo(t *testing.T) {
// push signed attestation image to the ref server
for _, img := range signedManifests {
// push references using subject-digest.att convention
err = mirror.PushImageToRegistry(img.Attestation.Image, fmt.Sprintf("%s/%s:tag-does-not-matter", refServerUrl.Host, repoName))
err = mirror.PushImageToRegistry(img.AttestationImage.Image, fmt.Sprintf("%s/%s:tag-does-not-matter", refServerUrl.Host, repoName))
require.NoError(t, err)
}
mfs2, err := attIdx.Index.IndexManifest()
@@ -262,6 +287,6 @@ func TestReferencesInDifferentRepo(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, attest.OutcomeSuccess, results.Outcome)
}
})
}
}
}

View File

@@ -5,7 +5,6 @@ import (
"fmt"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/types"
intoto "github.com/in-toto/in-toto-golang/in_toto"
v02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
@@ -28,27 +27,23 @@ var base64Encoding = base64.StdEncoding.Strict()
type AttestationLayer struct {
Statement *intoto.Statement
Layer v1.Layer
MediaType types.MediaType
Annotations map[string]string
}
type AttestationImage struct {
Layers []*AttestationLayer
Image v1.Image
}
type SignedAttestationImage struct {
Image v1.Image
Descriptor *v1.Descriptor
AttestationManifest *AttestationManifest
Descriptor *v1.Descriptor
Layers []*AttestationLayer
Image v1.Image
}
type AttestationManifest struct {
Descriptor *v1.Descriptor
Attestation *AttestationImage
MediaType types.MediaType
Annotations map[string]string
Digest v1.Hash
OriginalDescriptor *v1.Descriptor
// kept up to date during signing
AttestationImage *AttestationImage
// details of subect image
SubjectName string
SubjectDescriptor *v1.Descriptor
}