Single attestation when creating VSA
This commit is contained in:
64
internal/test/mocks.go
Normal file
64
internal/test/mocks.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/attest/pkg/attestation"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
"github.com/docker/attest/pkg/signerverifier"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/secure-systems-lab/go-securesystemslib/dsse"
|
||||
)
|
||||
|
||||
type MockResolver struct {
|
||||
Envs []*attestation.Envelope
|
||||
}
|
||||
|
||||
func (r MockResolver) Attestations(ctx context.Context, mediaType string) ([]*attestation.Envelope, error) {
|
||||
return r.Envs, nil
|
||||
}
|
||||
|
||||
func (r MockResolver) ImageName(ctx context.Context) (string, error) {
|
||||
return "library/alpine:latest", nil
|
||||
}
|
||||
|
||||
func (r MockResolver) ImageDescriptor(ctx context.Context) (*v1.Descriptor, error) {
|
||||
digest, err := v1.NewHash("sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &v1.Descriptor{
|
||||
Digest: digest,
|
||||
Size: 1234,
|
||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func (r MockResolver) ImagePlatform(ctx context.Context) (*v1.Platform, error) {
|
||||
return oci.ParsePlatform("linux/amd64")
|
||||
}
|
||||
|
||||
type MockRegistryResolver struct {
|
||||
Subject *v1.Descriptor
|
||||
ImageNameStr string
|
||||
*MockResolver
|
||||
}
|
||||
|
||||
func (r *MockRegistryResolver) ImageDescriptor(ctx context.Context) (*v1.Descriptor, error) {
|
||||
return r.Subject, nil
|
||||
}
|
||||
|
||||
func (r *MockRegistryResolver) ImageName(ctx context.Context) (string, error) {
|
||||
return r.ImageNameStr, nil
|
||||
}
|
||||
|
||||
func GetMockSigner(ctx context.Context) (dsse.SignerVerifier, error) {
|
||||
priv, err := os.ReadFile(filepath.Join("..", "..", "test", "testdata", "test-signing-key.pem"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return signerverifier.LoadKeyPair(priv)
|
||||
}
|
||||
@@ -18,8 +18,11 @@ func SignStatements(ctx context.Context, idx v1.ImageIndex, signer dsse.SignerVe
|
||||
}
|
||||
// sign every attestation layer in each manifest
|
||||
for _, manifest := range attestationManifests {
|
||||
for _, layer := range manifest.Attestation.Layers {
|
||||
manifest.AddAttestation(ctx, signer, layer.Statement, opts)
|
||||
for _, layer := range manifest.AttestationImage.Layers {
|
||||
err = manifest.AddAttestation(ctx, signer, layer.Statement, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign attestation layer %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return attestationManifests, nil
|
||||
|
||||
@@ -125,8 +125,10 @@ func TestAddSignedLayerAnnotations(t *testing.T) {
|
||||
}
|
||||
|
||||
manifest := &attestation.AttestationManifest{
|
||||
MediaType: mediaType,
|
||||
Attestation: &attestation.AttestationImage{
|
||||
OriginalDescriptor: &v1.Descriptor{
|
||||
MediaType: mediaType,
|
||||
},
|
||||
AttestationImage: &attestation.AttestationImage{
|
||||
Image: empty.Image,
|
||||
Layers: []*attestation.AttestationLayer{
|
||||
originalLayer,
|
||||
@@ -135,7 +137,7 @@ func TestAddSignedLayerAnnotations(t *testing.T) {
|
||||
SubjectDescriptor: &v1.Descriptor{},
|
||||
}
|
||||
err := manifest.AddOrReplaceLayer(originalLayer, opts)
|
||||
newImg := manifest.Attestation.Image
|
||||
newImg := manifest.AttestationImage.Image
|
||||
require.NoError(t, err)
|
||||
mf, _ := newImg.RawManifest()
|
||||
type Annotations struct {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/attest/pkg/policy"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||
)
|
||||
|
||||
@@ -27,9 +28,10 @@ func (o Outcome) StringForVSA() (string, error) {
|
||||
}
|
||||
|
||||
type VerificationResult struct {
|
||||
Outcome Outcome
|
||||
Policy *policy.Policy
|
||||
Input *policy.PolicyInput
|
||||
VSA *intoto.Statement
|
||||
Violations []policy.Violation
|
||||
Outcome Outcome
|
||||
Policy *policy.Policy
|
||||
Input *policy.PolicyInput
|
||||
VSA *intoto.Statement
|
||||
Violations []policy.Violation
|
||||
SubjectDescriptor *v1.Descriptor
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/docker/attest/pkg/config"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
"github.com/docker/attest/pkg/policy"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||
)
|
||||
|
||||
@@ -60,7 +61,7 @@ func Verify(ctx context.Context, src *oci.ImageSpec, opts *policy.PolicyOptions)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func ToPolicyResult(p *policy.Policy, input *policy.PolicyInput, result *policy.Result) (*VerificationResult, error) {
|
||||
func toVerificationResult(p *policy.Policy, input *policy.PolicyInput, result *policy.Result) (*VerificationResult, error) {
|
||||
dgst, err := oci.SplitDigest(input.Digest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to split digest: %w", err)
|
||||
@@ -112,10 +113,11 @@ func ToPolicyResult(p *policy.Policy, input *policy.PolicyInput, result *policy.
|
||||
}
|
||||
|
||||
func VerifyAttestations(ctx context.Context, resolver oci.AttestationResolver, pctx *policy.Policy) (*VerificationResult, error) {
|
||||
digest, err := resolver.ImageDigest(ctx)
|
||||
desc, err := resolver.ImageDescriptor(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get image digest: %w", err)
|
||||
return nil, fmt.Errorf("failed to get image descriptor: %w", err)
|
||||
}
|
||||
digest := desc.Digest.String()
|
||||
name, err := resolver.ImageName(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get image name: %w", err)
|
||||
@@ -155,5 +157,27 @@ func VerifyAttestations(ctx context.Context, resolver oci.AttestationResolver, p
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("policy evaluation failed: %w", err)
|
||||
}
|
||||
return ToPolicyResult(pctx, input, result)
|
||||
verificationResult, err := toVerificationResult(pctx, input, result)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert to policy result: %w", err)
|
||||
}
|
||||
verificationResult.SubjectDescriptor = desc
|
||||
return verificationResult, nil
|
||||
}
|
||||
|
||||
func NewAttestationManifest(subject *v1.Descriptor) (*attestation.AttestationManifest, error) {
|
||||
subjectDigest := subject.Digest.String()
|
||||
subject.Annotations = map[string]string{
|
||||
"vnd.docker.reference.digest": subjectDigest,
|
||||
"vnd.docker.reference.type": "attestation-manifest"}
|
||||
|
||||
return &attestation.AttestationManifest{
|
||||
OriginalDescriptor: &v1.Descriptor{
|
||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
||||
},
|
||||
AttestationImage: &attestation.AttestationImage{
|
||||
Layers: []*attestation.AttestationLayer{},
|
||||
},
|
||||
SubjectDescriptor: subject,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,14 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/docker/attest/internal/embed"
|
||||
"github.com/docker/attest/pkg/attestation"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
"github.com/docker/attest/pkg/tuf"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/empty"
|
||||
"github.com/google/go-containerregistry/pkg/v1/layout"
|
||||
"github.com/google/go-containerregistry/pkg/v1/mutate"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
)
|
||||
|
||||
@@ -73,3 +75,90 @@ func SaveIndexAsOCILayout(image v1.ImageIndex, path string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SaveIndex(outputs []*oci.ImageSpec, index v1.ImageIndex, indexName string) error {
|
||||
// split output by comma and write or push each one
|
||||
for _, output := range outputs {
|
||||
if output.Type == oci.OCI {
|
||||
idx := v1.ImageIndex(empty.Index)
|
||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||
Add: index,
|
||||
Descriptor: v1.Descriptor{
|
||||
Annotations: map[string]string{
|
||||
oci.OciReferenceTarget: indexName,
|
||||
},
|
||||
},
|
||||
})
|
||||
err := SaveIndexAsOCILayout(idx, output.Identifier)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write signed image: %w", err)
|
||||
}
|
||||
} else {
|
||||
err := PushIndexToRegistry(index, output.Identifier)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to push signed image: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SaveImage(output *oci.ImageSpec, image v1.Image, imageName string) error {
|
||||
if output.Type == oci.OCI {
|
||||
idx := v1.ImageIndex(empty.Index)
|
||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||
Add: image,
|
||||
Descriptor: v1.Descriptor{
|
||||
Annotations: map[string]string{
|
||||
oci.OciReferenceTarget: imageName,
|
||||
},
|
||||
},
|
||||
})
|
||||
err := SaveIndexAsOCILayout(idx, output.Identifier)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write signed image: %w", err)
|
||||
}
|
||||
} else {
|
||||
err := PushImageToRegistry(image, output.Identifier)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to push signed image: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SaveReferrers(manifest *attestation.AttestationManifest, outputs []*oci.ImageSpec) error {
|
||||
for _, output := range outputs {
|
||||
if output.Type == oci.OCI {
|
||||
continue
|
||||
}
|
||||
ociManifest, err := manifest.AttestationImage.Image.Manifest()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get manifest: %w", err)
|
||||
}
|
||||
refDigest := ociManifest.Annotations[attestation.DockerReferenceDigest]
|
||||
if refDigest == "" {
|
||||
return fmt.Errorf("no digest found in manifest")
|
||||
}
|
||||
hash, err := v1.NewHash(refDigest)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse digest: %w", err)
|
||||
}
|
||||
// so that we use the same tag each time to reduce number of tags (tags aren't needed for referrers but we must push one)
|
||||
attOut, err := oci.ReplaceTagInSpec(output, hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//otherwise we end up with the detected platform, though I'm not sure it matters
|
||||
attOut.Platform = &v1.Platform{
|
||||
OS: "unknown",
|
||||
Architecture: "unknown",
|
||||
}
|
||||
|
||||
err = SaveImage(attOut, manifest.AttestationImage.Image, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to push image: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
|
||||
// implementation of AttestationResolver that closes over attestations from an oci layout
|
||||
type OCILayoutResolver struct {
|
||||
*AttestationManifest
|
||||
*attestation.AttestationManifest
|
||||
*ImageSpec
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func NewOCILayoutAttestationResolver(src *ImageSpec) (*OCILayoutResolver, error)
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *OCILayoutResolver) fetchAttestationManifest() (*AttestationManifest, error) {
|
||||
func (r *OCILayoutResolver) fetchAttestationManifest() (*attestation.AttestationManifest, error) {
|
||||
if r.AttestationManifest == nil {
|
||||
m, err := attestationManifestFromOCILayout(r.Identifier, r.ImageSpec.Platform)
|
||||
if err != nil {
|
||||
@@ -43,13 +43,16 @@ func (r *OCILayoutResolver) fetchAttestationManifest() (*AttestationManifest, er
|
||||
}
|
||||
|
||||
func (r *OCILayoutResolver) Attestations(ctx context.Context, predicateType string) ([]*att.Envelope, error) {
|
||||
attestationImage := r.AttestationManifest.Image
|
||||
layers, err := attestationImage.Layers()
|
||||
attestationImage := r.AttestationManifest.AttestationImage
|
||||
layers, err := attestationImage.Image.Layers()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract layers from attestation image: %w", err)
|
||||
}
|
||||
var envs []*att.Envelope
|
||||
manifest := r.AttestationManifest.Manifest
|
||||
manifest, err := r.AttestationManifest.AttestationImage.Image.Manifest()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get manifest: %w", err)
|
||||
}
|
||||
for i, l := range manifest.Layers {
|
||||
if l.Annotations[attestation.InTotoPredicateType] != predicateType {
|
||||
continue
|
||||
@@ -81,18 +84,18 @@ func (r *OCILayoutResolver) Attestations(ctx context.Context, predicateType stri
|
||||
}
|
||||
|
||||
func (r *OCILayoutResolver) ImageName(ctx context.Context) (string, error) {
|
||||
return r.Name, nil
|
||||
return r.SubjectName, nil
|
||||
}
|
||||
|
||||
func (r *OCILayoutResolver) ImageDigest(ctx context.Context) (string, error) {
|
||||
return r.Digest, nil
|
||||
func (r *OCILayoutResolver) ImageDescriptor(ctx context.Context) (*v1.Descriptor, error) {
|
||||
return r.SubjectDescriptor, nil
|
||||
}
|
||||
|
||||
func (r *OCILayoutResolver) ImagePlatform(ctx context.Context) (*v1.Platform, error) {
|
||||
return r.ImageSpec.Platform, nil
|
||||
}
|
||||
|
||||
func attestationManifestFromOCILayout(path string, platform *v1.Platform) (*AttestationManifest, error) {
|
||||
func attestationManifestFromOCILayout(path string, platform *v1.Platform) (*attestation.AttestationManifest, error) {
|
||||
idx, err := layout.ImageIndexFromPath(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -115,10 +118,11 @@ func attestationManifestFromOCILayout(path string, platform *v1.Platform) (*Atte
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract IndexManifest from ImageIndex: %w", err)
|
||||
}
|
||||
var imageDigest string
|
||||
var subjectDescriptor *v1.Descriptor
|
||||
for _, mf := range mfs2.Manifests {
|
||||
if mf.Platform.Equals(*platform) {
|
||||
imageDigest = mf.Digest.String()
|
||||
subjectDescriptor = &mf
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, mf := range mfs2.Manifests {
|
||||
@@ -126,7 +130,7 @@ func attestationManifestFromOCILayout(path string, platform *v1.Platform) (*Atte
|
||||
continue
|
||||
}
|
||||
|
||||
if mf.Annotations[att.DockerReferenceDigest] != imageDigest {
|
||||
if mf.Annotations[att.DockerReferenceDigest] != subjectDescriptor.Digest.String() {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -134,17 +138,11 @@ func attestationManifestFromOCILayout(path string, platform *v1.Platform) (*Atte
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract attestation image with digest %s: %w", mf.Digest.String(), err)
|
||||
}
|
||||
manifest, err := attestationImage.Manifest()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get manifest: %w", err)
|
||||
}
|
||||
attest := &AttestationManifest{
|
||||
Name: name,
|
||||
Image: attestationImage,
|
||||
Manifest: manifest,
|
||||
Descriptor: &mf,
|
||||
Digest: imageDigest,
|
||||
Platform: platform,
|
||||
attest := &attestation.AttestationManifest{
|
||||
AttestationImage: &att.AttestationImage{Image: attestationImage},
|
||||
OriginalDescriptor: &mf,
|
||||
SubjectName: name,
|
||||
SubjectDescriptor: subjectDescriptor,
|
||||
}
|
||||
return attest, nil
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/distribution/reference"
|
||||
"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/remote"
|
||||
@@ -46,11 +47,13 @@ func WithOptions(ctx context.Context, platform *v1.Platform) []remote.Option {
|
||||
return options
|
||||
}
|
||||
|
||||
func ExtractEnvelopes(ia *AttestationManifest, predicateType string) ([]*att.Envelope, error) {
|
||||
manifest := ia.Manifest
|
||||
image := ia.Image
|
||||
func ExtractEnvelopes(ia *attestation.AttestationManifest, predicateType string) ([]*att.Envelope, error) {
|
||||
manifest, err := ia.AttestationImage.Image.Manifest()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get manifest: %w", err)
|
||||
}
|
||||
var envs []*att.Envelope
|
||||
layers, err := image.Layers()
|
||||
layers, err := ia.AttestationImage.Image.Layers()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get layers: %w", err)
|
||||
}
|
||||
@@ -75,13 +78,13 @@ func ExtractEnvelopes(ia *AttestationManifest, predicateType string) ([]*att.Env
|
||||
return envs, nil
|
||||
}
|
||||
|
||||
func imageDigestForPlatform(ix *v1.IndexManifest, platform *v1.Platform) (string, error) {
|
||||
func imageDescriptor(ix *v1.IndexManifest, platform *v1.Platform) (*v1.Descriptor, error) {
|
||||
for _, m := range ix.Manifests {
|
||||
if (m.MediaType == ocispec.MediaTypeImageManifest || m.MediaType == "application/vnd.docker.distribution.manifest.v2+json") && m.Platform.Equals(*platform) {
|
||||
return m.Digest.String(), nil
|
||||
return &m, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New(fmt.Sprintf("no image found for platform %v", platform))
|
||||
return nil, errors.New(fmt.Sprintf("no image found for platform %v", platform))
|
||||
}
|
||||
|
||||
func attestationDigestForDigest(ix *v1.IndexManifest, imageDigest string, attestType string) (string, error) {
|
||||
@@ -147,3 +150,27 @@ func SplitDigest(digest string) (common.DigestSet, error) {
|
||||
parts[0]: parts[1],
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ReplaceTagInSpec(src *ImageSpec, digest v1.Hash) (*ImageSpec, error) {
|
||||
newName, err := replaceTag(src.Identifier, digest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse repo name: %w", err)
|
||||
}
|
||||
return &ImageSpec{
|
||||
Identifier: newName,
|
||||
Type: src.Type,
|
||||
Platform: src.Platform,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// so that the index tag is replaced with a tag unique to the image digest and doesn't overwrite it
|
||||
func replaceTag(image string, digest v1.Hash) (string, error) {
|
||||
if strings.HasPrefix(image, LocalPrefix) {
|
||||
return image, nil
|
||||
}
|
||||
notag, err := WithoutTag(image)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
return fmt.Sprintf("%s:%s-%s.att", notag, digest.Algorithm, digest.Hex), nil
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/layout"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -75,14 +76,16 @@ func TestImageDigestForPlatform(t *testing.T) {
|
||||
|
||||
p, err := ParsePlatform("linux/amd64")
|
||||
assert.NoError(t, err)
|
||||
digest, err := imageDigestForPlatform(mfs2, p)
|
||||
desc, err := imageDescriptor(mfs2, p)
|
||||
assert.NoError(t, err)
|
||||
digest := desc.Digest.String()
|
||||
assert.Equal(t, "sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620", digest)
|
||||
|
||||
p, err = ParsePlatform("linux/arm64")
|
||||
assert.NoError(t, err)
|
||||
digest, err = imageDigestForPlatform(mfs2, p)
|
||||
desc, err = imageDescriptor(mfs2, p)
|
||||
assert.NoError(t, err)
|
||||
digest = desc.Digest.String()
|
||||
assert.Equal(t, "sha256:7a76cec943853f9f7105b1976afa1bf7cd5bb6afc4e9d5852dd8da7cf81ae86e", digest)
|
||||
}
|
||||
|
||||
@@ -106,3 +109,31 @@ func TestWithoutTag(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplaceTag(t *testing.T) {
|
||||
tc := []struct {
|
||||
name string
|
||||
expected string
|
||||
}{
|
||||
{name: "image:tag", expected: "index.docker.io/library/image:sha256-digest.att"},
|
||||
{name: "image", expected: "index.docker.io/library/image:sha256-digest.att"},
|
||||
{name: "image:sha256-digest.att", expected: "index.docker.io/library/image:sha256-digest.att"},
|
||||
{name: "docker://image:tag", expected: "docker://index.docker.io/library/image:sha256-digest.att"},
|
||||
{name: "image@sha256:166710df254975d4a6c4c407c315951c22753dcaa829e020a3fd5d18fff70dd2", expected: "index.docker.io/library/image:sha256-digest.att"},
|
||||
{name: "oci://foobar", expected: "oci://foobar"},
|
||||
{name: "docker://image@sha256:166710df254975d4a6c4c407c315951c22753dcaa829e020a3fd5d18fff70dd2", expected: "docker://index.docker.io/library/image:sha256-digest.att"},
|
||||
{name: "docker://127.0.0.1:36555/repo:latest", expected: "docker://127.0.0.1:36555/repo:sha256-digest.att"},
|
||||
}
|
||||
|
||||
digest := v1.Hash{
|
||||
Algorithm: "sha256",
|
||||
Hex: "digest",
|
||||
}
|
||||
for _, c := range tc {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
replaced, err := replaceTag(c.name, digest)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, c.expected, replaced)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/attest/pkg/attestation"
|
||||
att "github.com/docker/attest/pkg/attestation"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
@@ -13,7 +14,7 @@ import (
|
||||
type ReferrersResolver struct {
|
||||
digest string
|
||||
referrersRepo string
|
||||
manifests []*AttestationManifest
|
||||
manifests []*attestation.AttestationManifest
|
||||
*RegistryImageDetailsResolver
|
||||
}
|
||||
|
||||
@@ -43,7 +44,11 @@ func (r *ReferrersResolver) resolveAttestations(ctx context.Context) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse reference: %w", err)
|
||||
}
|
||||
subjectDigest, err := r.ImageDigest(ctx)
|
||||
desc, err := r.ImageDescriptor(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get descriptor: %w", err)
|
||||
}
|
||||
subjectDigest := desc.Digest.String()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get digest: %w", err)
|
||||
}
|
||||
@@ -56,6 +61,7 @@ func (r *ReferrersResolver) resolveAttestations(ctx context.Context) error {
|
||||
} else {
|
||||
referrersSubjectRef = subjectRef.Context().Digest(subjectDigest)
|
||||
}
|
||||
// TODO - search for in-toto artifact type
|
||||
referrersIndex, err := remote.Referrers(referrersSubjectRef)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get referrers: %w", err)
|
||||
@@ -67,31 +73,18 @@ func (r *ReferrersResolver) resolveAttestations(ctx context.Context) error {
|
||||
if len(referrersIndexManifest.Manifests) == 0 {
|
||||
return errors.New("no referrers found")
|
||||
}
|
||||
aManifests := make([]*AttestationManifest, 0)
|
||||
aManifests := make([]*attestation.AttestationManifest, 0)
|
||||
for _, m := range referrersIndexManifest.Manifests {
|
||||
|
||||
remoteRef := referrersSubjectRef.Context().Digest(m.Digest.String())
|
||||
attestationImage, err := remote.Image(remoteRef)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get referred image: %w", err)
|
||||
}
|
||||
manifest, err := attestationImage.Manifest()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get manifest: %w", err)
|
||||
}
|
||||
if manifest.Annotations[att.DockerReferenceType] != att.AttestationManifestType {
|
||||
continue
|
||||
}
|
||||
if manifest.Annotations[att.DockerReferenceDigest] != subjectDigest {
|
||||
continue
|
||||
}
|
||||
attest := &AttestationManifest{
|
||||
Name: r.Identifier,
|
||||
Image: attestationImage,
|
||||
Manifest: manifest,
|
||||
Descriptor: &m,
|
||||
Digest: subjectDigest,
|
||||
Platform: r.Platform,
|
||||
attest := &attestation.AttestationManifest{
|
||||
SubjectName: r.Identifier,
|
||||
AttestationImage: &attestation.AttestationImage{Image: attestationImage},
|
||||
OriginalDescriptor: &m,
|
||||
SubjectDescriptor: desc,
|
||||
}
|
||||
aManifests = append(aManifests, attest)
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ package oci
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/attest/pkg/attestation"
|
||||
att "github.com/docker/attest/pkg/attestation"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
@@ -13,12 +13,12 @@ import (
|
||||
|
||||
type RegistryResolver struct {
|
||||
*RegistryImageDetailsResolver
|
||||
*AttestationManifest
|
||||
*attestation.AttestationManifest
|
||||
}
|
||||
|
||||
type RegistryImageDetailsResolver struct {
|
||||
*ImageSpec
|
||||
digest string
|
||||
descriptor *v1.Descriptor
|
||||
}
|
||||
|
||||
func NewRegistryImageDetailsResolver(src *ImageSpec) (*RegistryImageDetailsResolver, error) {
|
||||
@@ -41,24 +41,36 @@ func (r *RegistryImageDetailsResolver) ImagePlatform(ctx context.Context) (*v1.P
|
||||
return r.Platform, nil
|
||||
}
|
||||
|
||||
func (r *RegistryImageDetailsResolver) ImageDigest(ctx context.Context) (string, error) {
|
||||
if r.digest == "" {
|
||||
func (r *RegistryImageDetailsResolver) ImageDescriptor(ctx context.Context) (*v1.Descriptor, error) {
|
||||
if r.descriptor == nil {
|
||||
subjectRef, err := name.ParseReference(r.Identifier)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse reference: %w", err)
|
||||
return nil, fmt.Errorf("failed to parse reference: %w", err)
|
||||
}
|
||||
options := WithOptions(ctx, r.Platform)
|
||||
desc, err := remote.Image(subjectRef, options...)
|
||||
image, err := remote.Image(subjectRef, options...)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get image manifest: %w", err)
|
||||
return nil, fmt.Errorf("failed to get image manifest: %w", err)
|
||||
}
|
||||
subjectDigest, err := desc.Digest()
|
||||
digest, err := image.Digest()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get image digest: %w", err)
|
||||
return nil, fmt.Errorf("failed to get image digest: %w", err)
|
||||
}
|
||||
size, err := image.Size()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get image size: %w", err)
|
||||
}
|
||||
mediaType, err := image.MediaType()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get image media type: %w", err)
|
||||
}
|
||||
r.descriptor = &v1.Descriptor{
|
||||
Digest: digest,
|
||||
Size: size,
|
||||
MediaType: mediaType,
|
||||
}
|
||||
r.digest = subjectDigest.String()
|
||||
}
|
||||
return r.digest, nil
|
||||
return r.descriptor, nil
|
||||
}
|
||||
|
||||
func (r *RegistryResolver) Attestations(ctx context.Context, predicateType string) ([]*att.Envelope, error) {
|
||||
@@ -72,7 +84,7 @@ func (r *RegistryResolver) Attestations(ctx context.Context, predicateType strin
|
||||
return ExtractEnvelopes(r.AttestationManifest, predicateType)
|
||||
}
|
||||
|
||||
func FetchAttestationManifest(ctx context.Context, image string, platform *v1.Platform) (*AttestationManifest, error) {
|
||||
func FetchAttestationManifest(ctx context.Context, image string, platform *v1.Platform) (*attestation.AttestationManifest, error) {
|
||||
// we want to get to the image index, so ignoring platform for now
|
||||
options := WithOptions(ctx, nil)
|
||||
ref, err := name.ParseReference(image)
|
||||
@@ -87,10 +99,12 @@ func FetchAttestationManifest(ctx context.Context, image string, platform *v1.Pl
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get index manifest: %w", err)
|
||||
}
|
||||
digest, err := imageDigestForPlatform(indexManifest, platform)
|
||||
subjectDescriptor, err := imageDescriptor(indexManifest, platform)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to obtain image for platform: %w", err)
|
||||
}
|
||||
|
||||
digest := subjectDescriptor.Digest.String()
|
||||
ref, err = name.ParseReference(fmt.Sprintf("%s@%s", ref.Context().Name(), digest))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse attestation reference: %w", err)
|
||||
@@ -108,22 +122,16 @@ func FetchAttestationManifest(ctx context.Context, image string, platform *v1.Pl
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get attestation: %w", err)
|
||||
}
|
||||
manifest := new(v1.Manifest)
|
||||
err = json.Unmarshal(remoteDescriptor.Manifest, manifest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal attestation: %w", err)
|
||||
}
|
||||
attestationImage, err := remoteDescriptor.Image()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get attestation image: %w", err)
|
||||
}
|
||||
attest := &AttestationManifest{
|
||||
Name: image,
|
||||
Image: attestationImage,
|
||||
Manifest: manifest,
|
||||
Descriptor: &remoteDescriptor.Descriptor,
|
||||
Digest: digest,
|
||||
Platform: platform,
|
||||
|
||||
attest := &attestation.AttestationManifest{
|
||||
AttestationImage: &att.AttestationImage{Image: attestationImage},
|
||||
OriginalDescriptor: &remoteDescriptor.Descriptor,
|
||||
SubjectName: image,
|
||||
SubjectDescriptor: subjectDescriptor,
|
||||
}
|
||||
return attest, nil
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@ func TestRegistry(t *testing.T) {
|
||||
|
||||
resolver, err := policy.CreateImageDetailsResolver(spec)
|
||||
require.NoError(t, err)
|
||||
digest, err := resolver.ImageDigest(ctx)
|
||||
desc, err := resolver.ImageDescriptor(ctx)
|
||||
require.NoError(t, err)
|
||||
digest := desc.Digest.String()
|
||||
assert.True(t, strings.Contains(digest, "sha256:"))
|
||||
}
|
||||
|
||||
@@ -7,21 +7,6 @@ import (
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
)
|
||||
|
||||
type AttestationManifests struct {
|
||||
Manifests []*AttestationManifest
|
||||
}
|
||||
|
||||
type AttestationManifest struct {
|
||||
// attestation image details
|
||||
Image v1.Image
|
||||
Manifest *v1.Manifest
|
||||
Descriptor *v1.Descriptor
|
||||
// details of subect image
|
||||
Name string
|
||||
Digest string
|
||||
Platform *v1.Platform
|
||||
}
|
||||
|
||||
type AttestationResolver interface {
|
||||
ImageDetailsResolver
|
||||
Attestations(ctx context.Context, mediaType string) ([]*att.Envelope, error)
|
||||
@@ -30,7 +15,7 @@ type AttestationResolver interface {
|
||||
type ImageDetailsResolver interface {
|
||||
ImageName(ctx context.Context) (string, error)
|
||||
ImagePlatform(ctx context.Context) (*v1.Platform, error)
|
||||
ImageDigest(ctx context.Context) (string, error)
|
||||
ImageDescriptor(ctx context.Context) (*v1.Descriptor, error)
|
||||
}
|
||||
|
||||
type MockResolver struct {
|
||||
@@ -45,8 +30,17 @@ func (r MockResolver) ImageName(ctx context.Context) (string, error) {
|
||||
return "library/alpine:latest", nil
|
||||
}
|
||||
|
||||
func (r MockResolver) ImageDigest(ctx context.Context) (string, error) {
|
||||
return "sha256:test-digest", nil
|
||||
func (r MockResolver) ImageDescriptor(ctx context.Context) (*v1.Descriptor, error) {
|
||||
digest, err := v1.NewHash("sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &v1.Descriptor{
|
||||
Digest: digest,
|
||||
Size: 1234,
|
||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func (r MockResolver) ImagePlatform(ctx context.Context) (*v1.Platform, error) {
|
||||
|
||||
@@ -20,7 +20,11 @@ k2s4SO3XbQ2GG2alm289SUUpmBAuVxvT8muYQ8HC/QzixzyTACTXsBDjQg==
|
||||
// to run locally, we need to impersonate the GCP service account
|
||||
// gcloud auth application-default login --impersonate-service-account attest-kms-test@attest-kms-test.iam.gserviceaccount.com
|
||||
|
||||
<<<<<<< HEAD
|
||||
func TestGCPKMS_Signer(t *testing.T) {
|
||||
=======
|
||||
func _TestGCPKMS_Signer(t *testing.T) {
|
||||
>>>>>>> 56ef672 (Single attestation when creating VSA)
|
||||
// create a new signer
|
||||
ctx := context.Background()
|
||||
ref := "projects/attest-kms-test/locations/us-west1/keyRings/attest-kms-test/cryptoKeys/test-signing-key/cryptoKeyVersions/1"
|
||||
|
||||
Reference in New Issue
Block a user