1 Commits

Author SHA1 Message Date
James Carnegie
c0510fb76c Support images as well as indexes in ImageDetailResolvers (#183)
* build: Generate test data for unsigned and no provenance image indexes
* feat: Add function to build index without SBOM or provenance for linux/amd64 platform
* feat: add build_image function to build image without SBOM or provenance for linux/amd64
* feat: Rename NO_SBOM_NO_PROVENANCE_INDEX_DIR to UNSIGNED_IMAGE_DIR
* feat: support images in details resolvers
2024-09-30 20:53:13 +01:00
78 changed files with 159 additions and 86 deletions

View File

@@ -12,7 +12,7 @@ import (
const ExpectedStatements = 4 const ExpectedStatements = 4
func TestExtractAnnotatedStatements(t *testing.T) { func TestExtractAnnotatedStatements(t *testing.T) {
statements, err := attestation.ExtractAnnotatedStatements(test.UnsignedTestImage(".."), intoto.PayloadType) statements, err := attestation.ExtractAnnotatedStatements(test.UnsignedTestIndex(".."), intoto.PayloadType)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equalf(t, len(statements), ExpectedStatements, "expected %d statement, got %d", ExpectedStatements, len(statements)) assert.Equalf(t, len(statements), ExpectedStatements, "expected %d statement, got %d", ExpectedStatements, len(statements))
} }

View File

@@ -31,7 +31,7 @@ func ExampleManifest() {
ref := "docker/image-signer-verifier:latest" ref := "docker/image-signer-verifier:latest"
digest, err := v1.NewHash("sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620") digest, err := v1.NewHash("sha256:7ae6b41655929ad8e1848064874a98ac3f68884996c79907f6525e3045f75390")
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -14,6 +14,9 @@ import (
) )
// implementation of Resolver that closes over attestations from an oci layout. // implementation of Resolver that closes over attestations from an oci layout.
var _ Resolver = (*LayoutResolver)(nil)
type LayoutResolver struct { type LayoutResolver struct {
*Manifest *Manifest
*oci.ImageSpec *oci.ImageSpec
@@ -86,38 +89,49 @@ func (r *LayoutResolver) ImagePlatform(_ context.Context) (*v1.Platform, error)
} }
func manifestFromOCILayout(path string, platform *v1.Platform) (*Manifest, error) { func manifestFromOCILayout(path string, platform *v1.Platform) (*Manifest, error) {
idx, err := layout.ImageIndexFromPath(path) layoutIndex, err := layout.ImageIndexFromPath(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
idxm, err := idx.IndexManifest() layoutIndexManifest, err := layoutIndex.IndexManifest()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get digest: %w", err) return nil, fmt.Errorf("failed to get digest: %w", err)
} }
idxDescriptor := idxm.Manifests[0] layoutDescriptor := layoutIndexManifest.Manifests[0]
idxDigest := idxDescriptor.Digest layoutDescriptorDigest := layoutDescriptor.Digest
subjectName := idxDescriptor.Annotations[ocispec.AnnotationRefName] subjectName := layoutDescriptor.Annotations[ocispec.AnnotationRefName]
if _, err := reference.ParseNamed(subjectName); err != nil { if _, err := reference.ParseNamed(subjectName); err != nil {
// try the containerd annotation if the org.opencontainers.image.ref.name is not a full name // try the containerd annotation if the org.opencontainers.image.ref.name is not a full name
subjectName = idxDescriptor.Annotations[containerd.AnnotationImageName] subjectName = layoutDescriptor.Annotations[containerd.AnnotationImageName]
if _, err := reference.ParseNamed(subjectName); err != nil { if _, err := reference.ParseNamed(subjectName); err != nil {
return nil, fmt.Errorf("failed to find subject name in annotations") return nil, fmt.Errorf("failed to find subject name in annotations")
} }
} }
mfs, err := idx.ImageIndex(idxDigest) // check if digest refers to an image or an index
if err != nil { _, err = layoutIndex.Image(layoutDescriptorDigest)
return nil, fmt.Errorf("failed to extract ImageIndex for digest %s: %w", idxDigest.String(), err) if err == nil {
return &Manifest{
OriginalLayers: nil,
OriginalDescriptor: nil,
SubjectName: subjectName,
SubjectDescriptor: &layoutDescriptor,
}, nil
} }
mfs2, err := mfs.IndexManifest()
subjectIndex, err := layoutIndex.ImageIndex(layoutDescriptorDigest)
if err != nil {
return nil, fmt.Errorf("failed to extract ImageIndex for digest %s: %w", layoutDescriptorDigest.String(), err)
}
subjectIndexManifest, err := subjectIndex.IndexManifest()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to extract IndexManifest from ImageIndex: %w", err) return nil, fmt.Errorf("failed to extract IndexManifest from ImageIndex: %w", err)
} }
var subjectDescriptor *v1.Descriptor var subjectDescriptor *v1.Descriptor
for i := range mfs2.Manifests { for i := range subjectIndexManifest.Manifests {
manifest := &mfs2.Manifests[i] manifest := &subjectIndexManifest.Manifests[i]
if manifest.Platform != nil { if manifest.Platform != nil {
if manifest.Platform.Equals(*platform) { if manifest.Platform.Equals(*platform) {
subjectDescriptor = manifest subjectDescriptor = manifest
@@ -128,8 +142,8 @@ func manifestFromOCILayout(path string, platform *v1.Platform) (*Manifest, error
if subjectDescriptor == nil { if subjectDescriptor == nil {
return nil, fmt.Errorf("platform not found in index") return nil, fmt.Errorf("platform not found in index")
} }
for i := range mfs2.Manifests { for i := range subjectIndexManifest.Manifests {
mf := &mfs2.Manifests[i] mf := &subjectIndexManifest.Manifests[i]
if mf.Annotations[DockerReferenceType] != AttestationManifestType { if mf.Annotations[DockerReferenceType] != AttestationManifestType {
continue continue
} }
@@ -138,7 +152,7 @@ func manifestFromOCILayout(path string, platform *v1.Platform) (*Manifest, error
continue continue
} }
attestationImage, err := mfs.Image(mf.Digest) attestationImage, err := subjectIndex.Image(mf.Digest)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to extract attestation image with digest %s: %w", mf.Digest.String(), err) return nil, fmt.Errorf("failed to extract attestation image with digest %s: %w", mf.Digest.String(), err)
} }

View File

@@ -1,6 +1,7 @@
package attestation_test package attestation_test
import ( import (
"context"
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
@@ -25,7 +26,7 @@ func TestAttestationFromOCILayout(t *testing.T) {
} }
opts := &attestation.SigningOptions{} opts := &attestation.SigningOptions{}
attIdx, err := oci.IndexFromPath(test.UnsignedTestImage("..")) attIdx, err := oci.IndexFromPath(test.UnsignedTestIndex(".."))
require.NoError(t, err) require.NoError(t, err)
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts) signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
require.NoError(t, err) require.NoError(t, err)
@@ -74,7 +75,7 @@ func TestSubjectNameAnnotations(t *testing.T) {
ociLayoutPath string ociLayoutPath string
errorStr string errorStr string
}{ }{
{name: "oci annotation", ociLayoutPath: test.UnsignedTestImage("..")}, {name: "oci annotation", ociLayoutPath: test.UnsignedTestIndex("..")},
{name: "containerd annotation", ociLayoutPath: filepath.Join("..", "test", "testdata", "containerd-subject-layout")}, {name: "containerd annotation", ociLayoutPath: filepath.Join("..", "test", "testdata", "containerd-subject-layout")},
{name: "missing subject name", ociLayoutPath: filepath.Join("..", "test", "testdata", "missing-subject-layout"), errorStr: "failed to find subject name in annotations"}, {name: "missing subject name", ociLayoutPath: filepath.Join("..", "test", "testdata", "missing-subject-layout"), errorStr: "failed to find subject name in annotations"},
} }
@@ -93,3 +94,14 @@ func TestSubjectNameAnnotations(t *testing.T) {
}) })
} }
} }
func TestImageDetailsFromImageLayout(t *testing.T) {
spec, err := oci.ParseImageSpec(oci.LocalPrefix+test.UnsignedTestImage(".."), oci.WithPlatform("linux/arm64"))
require.NoError(t, err)
resolver, err := policy.CreateImageDetailsResolver(spec)
require.NoError(t, err)
desc, err := resolver.ImageDescriptor(context.Background())
require.NoError(t, err)
digest := desc.Digest.String()
assert.Equal(t, "sha256:7ae6b41655929ad8e1848064874a98ac3f68884996c79907f6525e3045f75390", digest)
}

View File

@@ -3,6 +3,7 @@ package attestation
import ( import (
"context" "context"
"github.com/docker/attest/internal/test"
"github.com/docker/attest/oci" "github.com/docker/attest/oci"
v1 "github.com/google/go-containerregistry/pkg/v1" v1 "github.com/google/go-containerregistry/pkg/v1"
) )
@@ -36,7 +37,7 @@ func (r MockResolver) ImageDescriptor(_ context.Context) (*v1.Descriptor, error)
if r.DescriptorFn != nil { if r.DescriptorFn != nil {
return r.DescriptorFn() return r.DescriptorFn()
} }
digest, err := v1.NewHash("sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620") digest, err := v1.NewHash(test.UnsignedLinuxAMD64ImageDigest)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -89,7 +89,7 @@ func TestAttestationReferenceTypes(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
opts := &attestation.SigningOptions{} opts := &attestation.SigningOptions{}
attIdx, err := oci.IndexFromPath(test.UnsignedTestImage("..")) attIdx, err := oci.IndexFromPath(test.UnsignedTestIndex(".."))
require.NoError(t, err) require.NoError(t, err)
indexName := fmt.Sprintf("%s/repo:root", u.Host) indexName := fmt.Sprintf("%s/repo:root", u.Host)
@@ -209,7 +209,7 @@ func TestReferencesInDifferentRepo(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
opts := &attestation.SigningOptions{} opts := &attestation.SigningOptions{}
attIdx, err := oci.IndexFromPath(test.UnsignedTestImage("..")) attIdx, err := oci.IndexFromPath(test.UnsignedTestIndex(".."))
require.NoError(t, err) require.NoError(t, err)
indexName := fmt.Sprintf("%s/%s:latest", serverURL.Host, repoName) indexName := fmt.Sprintf("%s/%s:latest", serverURL.Host, repoName)
@@ -233,7 +233,7 @@ func TestReferencesInDifferentRepo(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
opts := &attestation.SigningOptions{} opts := &attestation.SigningOptions{}
attIdx, err := oci.IndexFromPath(test.UnsignedTestImage("..")) attIdx, err := oci.IndexFromPath(test.UnsignedTestIndex(".."))
require.NoError(t, err) require.NoError(t, err)
indexName := fmt.Sprintf("%s/%s:latest", serverURL.Host, repoName) indexName := fmt.Sprintf("%s/%s:latest", serverURL.Host, repoName)
@@ -286,7 +286,7 @@ func TestCorrectArtifactTypeInTagFallback(t *testing.T) {
repoName := "repo" repoName := "repo"
opts := &attestation.SigningOptions{} opts := &attestation.SigningOptions{}
attIdx, err := oci.IndexFromPath(test.UnsignedTestImage("..")) attIdx, err := oci.IndexFromPath(test.UnsignedTestIndex(".."))
require.NoError(t, err) require.NoError(t, err)
indexName := fmt.Sprintf("%s/%s:latest", serverURL.Host, repoName) indexName := fmt.Sprintf("%s/%s:latest", serverURL.Host, repoName)

View File

@@ -24,7 +24,7 @@ func TestRegistry(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
opts := &attestation.SigningOptions{} opts := &attestation.SigningOptions{}
attIdx, err := oci.IndexFromPath(test.UnsignedTestImage("..")) attIdx, err := oci.IndexFromPath(test.UnsignedTestIndex(".."))
require.NoError(t, err) require.NoError(t, err)
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts) signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
require.NoError(t, err) require.NoError(t, err)
@@ -46,4 +46,14 @@ func TestRegistry(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
digest := desc.Digest.String() digest := desc.Digest.String()
assert.True(t, strings.Contains(digest, "sha256:")) assert.True(t, strings.Contains(digest, "sha256:"))
// resolver also works with platform specific digest
spec, err = oci.ParseImageSpec(fmt.Sprintf("%s@%s", indexName, digest))
require.NoError(t, err)
resolver, err = policy.CreateImageDetailsResolver(spec)
require.NoError(t, err)
desc, err = resolver.ImageDescriptor(ctx)
require.NoError(t, err)
assert.Equal(t, desc.Digest.String(), digest)
} }

View File

@@ -249,7 +249,7 @@ func TestSimpleStatementSigning(t *testing.T) {
PredicateType: attestation.VSAPredicateType, PredicateType: attestation.VSAPredicateType,
}, },
} }
digest, err := v1.NewHash("sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620") digest, err := v1.NewHash(test.UnsignedLinuxAMD64ImageDigest)
require.NoError(t, err) require.NoError(t, err)
subject := &v1.Descriptor{ subject := &v1.Descriptor{
MediaType: "application/vnd.oci.image.manifest.v1+json", MediaType: "application/vnd.oci.image.manifest.v1+json",

View File

@@ -23,14 +23,20 @@ import (
) )
const ( const (
UseMockKMS = true UseMockKMS = true
AWSRegion = "us-east-1"
AWSRegion = "us-east-1" AWSKMSKeyARN = "arn:aws:kms:us-east-1:175142243308:alias/doi-signing" // sandbox
AWSKMSKeyARN = "arn:aws:kms:us-east-1:175142243308:alias/doi-signing" // sandbox UnsignedLinuxAMD64ImageDigest = "sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620"
UnsignedLinuxArm64ImageDigest = "sha256:7a76cec943853f9f7105b1976afa1bf7cd5bb6afc4e9d5852dd8da7cf81ae86e"
) )
func UnsignedTestIndex(rel ...string) string {
rel = append(rel, "test", "testdata", "unsigned-index")
return filepath.Join(rel...)
}
func UnsignedTestImage(rel ...string) string { func UnsignedTestImage(rel ...string) string {
rel = append(rel, "test", "testdata", "unsigned-test-image") rel = append(rel, "test", "testdata", "unsigned-image")
return filepath.Join(rel...) return filepath.Join(rel...)
} }

View File

@@ -12,7 +12,7 @@ import (
) )
func TestRegistryAuth(t *testing.T) { func TestRegistryAuth(t *testing.T) {
attIdx, err := oci.IndexFromPath(test.UnsignedTestImage("..")) attIdx, err := oci.IndexFromPath(test.UnsignedTestIndex(".."))
require.NoError(t, err) require.NoError(t, err)
// test cases for ecr, gcr and dockerhub // test cases for ecr, gcr and dockerhub
testCases := []struct { testCases := []struct {

View File

@@ -67,7 +67,7 @@ func TestRefToPurl(t *testing.T) {
// Test fix for https://github.com/docker/secure-artifacts-team-issues/issues/202 // Test fix for https://github.com/docker/secure-artifacts-team-issues/issues/202
func TestImageDigestForPlatform(t *testing.T) { func TestImageDigestForPlatform(t *testing.T) {
idx, err := layout.ImageIndexFromPath(test.UnsignedTestImage("..")) idx, err := layout.ImageIndexFromPath(test.UnsignedTestIndex(".."))
assert.NoError(t, err) assert.NoError(t, err)
idxm, err := idx.IndexManifest() idxm, err := idx.IndexManifest()
@@ -86,14 +86,14 @@ func TestImageDigestForPlatform(t *testing.T) {
desc, err := oci.ImageDescriptor(mfs2, p) desc, err := oci.ImageDescriptor(mfs2, p)
assert.NoError(t, err) assert.NoError(t, err)
digest := desc.Digest.String() digest := desc.Digest.String()
assert.Equal(t, "sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620", digest) assert.Equal(t, test.UnsignedLinuxAMD64ImageDigest, digest)
p, err = oci.ParsePlatform("linux/arm64") p, err = oci.ParsePlatform("linux/arm64")
assert.NoError(t, err) assert.NoError(t, err)
desc, err = oci.ImageDescriptor(mfs2, p) desc, err = oci.ImageDescriptor(mfs2, p)
assert.NoError(t, err) assert.NoError(t, err)
digest = desc.Digest.String() digest = desc.Digest.String()
assert.Equal(t, "sha256:7a76cec943853f9f7105b1976afa1bf7cd5bb6afc4e9d5852dd8da7cf81ae86e", digest) assert.Equal(t, test.UnsignedLinuxArm64ImageDigest, digest)
} }
func TestWithoutTag(t *testing.T) { func TestWithoutTag(t *testing.T) {

View File

@@ -18,7 +18,7 @@ import (
func TestSavingIndex(t *testing.T) { func TestSavingIndex(t *testing.T) {
outputLayout := test.CreateTempDir(t, "", "mirror-test") outputLayout := test.CreateTempDir(t, "", "mirror-test")
attIdx, err := oci.IndexFromPath(test.UnsignedTestImage("..")) attIdx, err := oci.IndexFromPath(test.UnsignedTestIndex(".."))
require.NoError(t, err) require.NoError(t, err)
ctx := context.Background() ctx := context.Background()
@@ -73,7 +73,7 @@ func TestSavingReferrers(t *testing.T) {
}, },
} }
digest, err := v1.NewHash("sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620") digest, err := v1.NewHash(test.UnsignedLinuxAMD64ImageDigest)
require.NoError(t, err) require.NoError(t, err)
subject := &v1.Descriptor{ subject := &v1.Descriptor{
MediaType: "application/vnd.oci.image.manifest.v1+json", MediaType: "application/vnd.oci.image.manifest.v1+json",

View File

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
"github.com/docker/attest/attestation" "github.com/docker/attest/attestation"
@@ -357,6 +358,7 @@ func TestVerifySubject(t *testing.T) {
}, },
} }
digestHex := strings.TrimPrefix(test.UnsignedLinuxAMD64ImageDigest, "sha256:")
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
defaultResolver.Image = tc.img defaultResolver.Image = tc.img
@@ -365,7 +367,7 @@ func TestVerifySubject(t *testing.T) {
return &v1.Platform{Architecture: "amd64", OS: "linux"}, nil return &v1.Platform{Architecture: "amd64", OS: "linux"}, nil
} }
// digest from mock resolver // digest from mock resolver
tc.subject[0].Digest = map[string]string{"sha256": "da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620"} tc.subject[0].Digest = map[string]string{"sha256": digestHex}
if tc.digest != "" { if tc.digest != "" {
tc.subject[0].Digest = map[string]string{"sha256": tc.digest} tc.subject[0].Digest = map[string]string{"sha256": tc.digest}
} }
@@ -381,7 +383,7 @@ func TestVerifySubject(t *testing.T) {
subject := []intoto.Subject{ subject := []intoto.Subject{
{ {
Name: "pkg:docker/alpine@latest?platform=linux%2Famd64", Name: "pkg:docker/alpine@latest?platform=linux%2Famd64",
Digest: map[string]string{"sha256": "da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620"}, Digest: map[string]string{"sha256": digestHex},
}, },
} }

View File

@@ -10,36 +10,45 @@ function check_command () {
function cleanup_testdata () { function cleanup_testdata () {
echo "Cleaning up existing testdata..." echo "Cleaning up existing testdata..."
rm -rf "${TESTDATA_PATH:?}/${UNSIGNED_INDEX_DIR:?}"
rm -rf "${TESTDATA_PATH:?}/${NO_PROVENANCE_INDEX_DIR:?}"
rm -rf "${TESTDATA_PATH:?}/${UNSIGNED_IMAGE_DIR:?}" rm -rf "${TESTDATA_PATH:?}/${UNSIGNED_IMAGE_DIR:?}"
rm -rf "${TESTDATA_PATH:?}/${NO_PROVENANCE_IMAGE_DIR:?}"
} }
function build_unsigned_image () { function build_unsigned_index () {
echo "Building $UNSIGNED_INDEX_DIR..."
docker buildx build "$TEST_INDEX_DOCKERFILE_PATH" --sbom true --provenance true --platform linux/amd64,linux/arm64 \
--output type=oci,tar=false,name="$TEST_INDEX_REPO:$TEST_INDEX_TAG",dest="$TESTDATA_PATH/$UNSIGNED_INDEX_DIR"
}
function build_no_provenance_index () {
echo "Building unsigned $NO_PROVENANCE_INDEX_DIR..."
docker buildx build "$TEST_INDEX_DOCKERFILE_PATH" --sbom true --provenance false --platform linux/amd64,linux/arm64 \
--output type=oci,tar=false,name="$TEST_INDEX_REPO:$TEST_INDEX_TAG",dest="$TESTDATA_PATH/$NO_PROVENANCE_INDEX_DIR"
}
function build_image () {
echo "Building $UNSIGNED_IMAGE_DIR..." echo "Building $UNSIGNED_IMAGE_DIR..."
docker buildx build "$TEST_IMAGE_DOCKERFILE_PATH" --sbom true --provenance true --platform linux/amd64,linux/arm64 \ docker buildx build "$TEST_INDEX_DOCKERFILE_PATH" --sbom false --provenance false --platform linux/amd64 \
--output type=oci,tar=false,name="$TEST_IMAGE_REPO:$TEST_IMAGE_TAG",dest="$TESTDATA_PATH/$UNSIGNED_IMAGE_DIR" --output type=oci,tar=false,name="$TEST_INDEX_REPO:$TEST_INDEX_TAG",dest="$TESTDATA_PATH/$UNSIGNED_IMAGE_DIR"
}
function build_no_provenance_image () {
echo "Building unsigned $NO_PROVENANCE_IMAGE_DIR..."
docker buildx build "$TEST_IMAGE_DOCKERFILE_PATH" --sbom true --provenance false --platform linux/amd64,linux/arm64 \
--output type=oci,tar=false,name="$TEST_IMAGE_REPO:$TEST_IMAGE_TAG",dest="$TESTDATA_PATH/$NO_PROVENANCE_IMAGE_DIR"
} }
# Check required commands # Check required commands
check_command docker check_command docker
TESTDATA_PATH="../test/testdata" TESTDATA_PATH="../test/testdata"
TEST_IMAGE_DOCKERFILE_PATH="../test" TEST_INDEX_DOCKERFILE_PATH="../test"
TEST_IMAGE_REPO="test-image" TEST_INDEX_REPO="test-image"
TEST_IMAGE_TAG="test" TEST_INDEX_TAG="test"
UNSIGNED_IMAGE_DIR="unsigned-test-image" UNSIGNED_INDEX_DIR="unsigned-index"
NO_PROVENANCE_IMAGE_DIR="no-provenance-image" NO_PROVENANCE_INDEX_DIR="no-provenance-index"
UNSIGNED_IMAGE_DIR="unsigned-image"
ATTESTATION_PAYLOADTYPE="application/vnd.in-toto+json" ATTESTATION_PAYLOADTYPE="application/vnd.in-toto+json"
# Run steps # Run steps
cleanup_testdata cleanup_testdata
build_unsigned_image build_unsigned_index
build_no_provenance_image build_no_provenance_index
build_image
echo "Process completed successfully." echo "Process completed successfully."

View File

@@ -15,7 +15,7 @@ import (
) )
var ( var (
NoProvenanceImage = filepath.Join("test", "testdata", "no-provenance-image") NoProvenanceImage = filepath.Join("test", "testdata", "no-provenance-index")
PassPolicyDir = filepath.Join("test", "testdata", "local-policy-pass") PassPolicyDir = filepath.Join("test", "testdata", "local-policy-pass")
PassMirrorPolicyDir = filepath.Join("test", "testdata", "local-policy-mirror") PassMirrorPolicyDir = filepath.Join("test", "testdata", "local-policy-mirror")
PassNoTLPolicyDir = filepath.Join("test", "testdata", "local-policy-no-tl") PassNoTLPolicyDir = filepath.Join("test", "testdata", "local-policy-no-tl")
@@ -35,8 +35,8 @@ func TestSignVerifyOCILayout(t *testing.T) {
expectedAttestations int expectedAttestations int
replace bool replace bool
}{ }{
{"signed replaced", test.UnsignedTestImage(), 0, 4, true}, {"signed replaced", test.UnsignedTestIndex(), 0, 4, true},
{"without replace", test.UnsignedTestImage(), 4, 4, false}, {"without replace", test.UnsignedTestIndex(), 4, 4, false},
// image without provenance doesn't fail // image without provenance doesn't fail
{"no provenance (replace)", NoProvenanceImage, 0, 2, true}, {"no provenance (replace)", NoProvenanceImage, 0, 2, true},
{"no provenance (no replace)", NoProvenanceImage, 2, 2, false}, {"no provenance (no replace)", NoProvenanceImage, 2, 2, false},

View File

@@ -1 +0,0 @@
{"architecture":"unknown","os":"unknown","config":{},"rootfs":{"type":"layers","diff_ids":["sha256:da5651e8877b960aa30f32f317fbeba28f5e06f1ce4d3895b3b8770140280a2e"]}}

View File

@@ -1 +0,0 @@
{"architecture":"unknown","os":"unknown","config":{},"rootfs":{"type":"layers","diff_ids":["sha256:a9646604f9522bf59d203a86ac5c2354a573ea041b8846409c4fc0f8c4a70850"]}}

View File

@@ -1 +0,0 @@
{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:1effe3a77c594e579388dc4553dbbe762e4457a099ab8b706e67f5f9fc934701","size":1607,"annotations":{"org.opencontainers.image.created":"2024-04-29T10:23:48Z","org.opencontainers.image.ref.name":"docker.io/library/test-image:test"}}]}

View File

@@ -3,13 +3,13 @@
"mediaType": "application/vnd.oci.image.manifest.v1+json", "mediaType": "application/vnd.oci.image.manifest.v1+json",
"config": { "config": {
"mediaType": "application/vnd.oci.image.config.v1+json", "mediaType": "application/vnd.oci.image.config.v1+json",
"digest": "sha256:b6ef78de3633e45d1c08019fbabb4464fabd6dd32e82c67ea2b2a3c4e8bacdf5", "digest": "sha256:bb0ed50656ccdb2eb114407de579554426777d6dc0e4206a6f746afb4ee5237e",
"size": 167 "size": 167
}, },
"layers": [ "layers": [
{ {
"mediaType": "application/vnd.in-toto+json", "mediaType": "application/vnd.in-toto+json",
"digest": "sha256:a9646604f9522bf59d203a86ac5c2354a573ea041b8846409c4fc0f8c4a70850", "digest": "sha256:618f1e2f903648dde23cc38dc0ed7eed83d5394a6902bb7bfae8fa707c2e5c33",
"size": 946, "size": 946,
"annotations": { "annotations": {
"in-toto.io/predicate-type": "https://spdx.dev/Document" "in-toto.io/predicate-type": "https://spdx.dev/Document"

View File

@@ -3,13 +3,13 @@
"mediaType": "application/vnd.oci.image.manifest.v1+json", "mediaType": "application/vnd.oci.image.manifest.v1+json",
"config": { "config": {
"mediaType": "application/vnd.oci.image.config.v1+json", "mediaType": "application/vnd.oci.image.config.v1+json",
"digest": "sha256:2e82727457f04f320b643cb6e13bcbafb8e0dc0adc0443f1a25666f9518c5071", "digest": "sha256:816b20ea86474dcfb2906ffaf4410262dfcb0d49fdfb60698775f7bc10aad7fb",
"size": 167 "size": 167
}, },
"layers": [ "layers": [
{ {
"mediaType": "application/vnd.in-toto+json", "mediaType": "application/vnd.in-toto+json",
"digest": "sha256:da5651e8877b960aa30f32f317fbeba28f5e06f1ce4d3895b3b8770140280a2e", "digest": "sha256:f0dac65dd0ff6a656c419c654ac672c38029a3f1a4b4acce062bd2f5a923ffae",
"size": 946, "size": 946,
"annotations": { "annotations": {
"in-toto.io/predicate-type": "https://spdx.dev/Document" "in-toto.io/predicate-type": "https://spdx.dev/Document"

View File

@@ -4,7 +4,7 @@
"manifests": [ "manifests": [
{ {
"mediaType": "application/vnd.oci.image.manifest.v1+json", "mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620", "digest": "sha256:7ae6b41655929ad8e1848064874a98ac3f68884996c79907f6525e3045f75390",
"size": 476, "size": 476,
"platform": { "platform": {
"architecture": "amd64", "architecture": "amd64",
@@ -13,7 +13,7 @@
}, },
{ {
"mediaType": "application/vnd.oci.image.manifest.v1+json", "mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:7a76cec943853f9f7105b1976afa1bf7cd5bb6afc4e9d5852dd8da7cf81ae86e", "digest": "sha256:52f7a760b9322aa1af76d998763868b7d1bfec2331a2574a438ef44c92c0c46d",
"size": 476, "size": 476,
"platform": { "platform": {
"architecture": "arm64", "architecture": "arm64",
@@ -22,10 +22,10 @@
}, },
{ {
"mediaType": "application/vnd.oci.image.manifest.v1+json", "mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:2aaebbb079957470e7c0adddbb054b2b4c01f717d408efba753da2bf6e8905da", "digest": "sha256:059eea09507d0f904b8892ee59fcd3ddec1a637fc40fb7c83c432c6ff27e2f91",
"size": 558, "size": 558,
"annotations": { "annotations": {
"vnd.docker.reference.digest": "sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620", "vnd.docker.reference.digest": "sha256:7ae6b41655929ad8e1848064874a98ac3f68884996c79907f6525e3045f75390",
"vnd.docker.reference.type": "attestation-manifest" "vnd.docker.reference.type": "attestation-manifest"
}, },
"platform": { "platform": {
@@ -35,10 +35,10 @@
}, },
{ {
"mediaType": "application/vnd.oci.image.manifest.v1+json", "mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:9b009d6b84b1ed941070b3f919823446286a674ad669d0baa8ab2c358aeb3a82", "digest": "sha256:0b1ee0f360b073d2f76ceed15a63e291659fbcc6c3caf3be39e437d8344b520e",
"size": 558, "size": 558,
"annotations": { "annotations": {
"vnd.docker.reference.digest": "sha256:7a76cec943853f9f7105b1976afa1bf7cd5bb6afc4e9d5852dd8da7cf81ae86e", "vnd.docker.reference.digest": "sha256:52f7a760b9322aa1af76d998763868b7d1bfec2331a2574a438ef44c92c0c46d",
"vnd.docker.reference.type": "attestation-manifest" "vnd.docker.reference.type": "attestation-manifest"
}, },
"platform": { "platform": {

View File

@@ -0,0 +1 @@
{"architecture":"amd64","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"WorkingDir":"/"},"created":"2024-09-27T16:10:13.292759474Z","history":[{"created":"2024-09-27T16:10:13.292759474Z","created_by":"COPY /tmp/hello.txt / # buildkit","comment":"buildkit.dockerfile.v0"}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:81a78ab7aa0b72d665a9c203b4c30f0423e434b789ed95b2d418e60a1b726470"]}}

View File

@@ -3,13 +3,13 @@
"mediaType": "application/vnd.oci.image.manifest.v1+json", "mediaType": "application/vnd.oci.image.manifest.v1+json",
"config": { "config": {
"mediaType": "application/vnd.oci.image.config.v1+json", "mediaType": "application/vnd.oci.image.config.v1+json",
"digest": "sha256:d85d624a324422194b43cccd975b5752cf0acaedd668bb525fcd40c3587cc460", "digest": "sha256:c0bd7799c46e00830b4d7cb8c1f622d14aae81643a90be5ec38c9be4bdd70f6c",
"size": 453 "size": 438
}, },
"layers": [ "layers": [
{ {
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:97a548f8d65d9ab617f608dd621f59e0d43a3b346f34c34eb58da31f00a9b0ad", "digest": "sha256:07d9a868932bd092fa0a4c4df943785a7ba9cee12dbf446d02488319a5fbf336",
"size": 116 "size": 116
} }
] ]

View File

@@ -1 +1 @@
{"_type":"https://in-toto.io/Statement/v0.1","predicateType":"https://spdx.dev/Document","subject":[{"name":"pkg:docker/test-image@test?platform=linux%2Famd64","digest":{"sha256":"da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620"}}],"predicate":{"spdxVersion":"SPDX-2.3","dataLicense":"CC0-1.0","SPDXID":"SPDXRef-DOCUMENT","name":"sbom","documentNamespace":"https://anchore.com/syft/dir/sbom-6d900ae6-587d-4695-9c01-511801a85b65","creationInfo":{"licenseListVersion":"3.23","creators":["Organization: Anchore, Inc","Tool: syft-v0.105.0","Tool: buildkit-v0.12.4"],"created":"2024-03-08T16:42:30Z"},"packages":[{"name":"sbom","SPDXID":"SPDXRef-DocumentRoot-Directory-sbom","supplier":"NOASSERTION","downloadLocation":"NOASSERTION","filesAnalyzed":false,"primaryPackagePurpose":"FILE"}],"relationships":[{"spdxElementId":"SPDXRef-DOCUMENT","relatedSpdxElement":"SPDXRef-DocumentRoot-Directory-sbom","relationshipType":"DESCRIBES"}]}} {"_type":"https://in-toto.io/Statement/v0.1","predicateType":"https://spdx.dev/Document","subject":[{"name":"pkg:docker/test-image@test?platform=linux%2Famd64","digest":{"sha256":"7ae6b41655929ad8e1848064874a98ac3f68884996c79907f6525e3045f75390"}}],"predicate":{"spdxVersion":"SPDX-2.3","dataLicense":"CC0-1.0","SPDXID":"SPDXRef-DOCUMENT","name":"sbom","documentNamespace":"https://anchore.com/syft/dir/sbom-b92d7d2e-7ffe-4d0a-8194-9af68e80e169","creationInfo":{"licenseListVersion":"3.23","creators":["Organization: Anchore, Inc","Tool: syft-v0.105.0","Tool: buildkit-v0.15.2"],"created":"2024-09-27T16:10:21Z"},"packages":[{"name":"sbom","SPDXID":"SPDXRef-DocumentRoot-Directory-sbom","supplier":"NOASSERTION","downloadLocation":"NOASSERTION","filesAnalyzed":false,"primaryPackagePurpose":"FILE"}],"relationships":[{"spdxElementId":"SPDXRef-DOCUMENT","relatedSpdxElement":"SPDXRef-DocumentRoot-Directory-sbom","relationshipType":"DESCRIBES"}]}}

View File

@@ -3,13 +3,13 @@
"mediaType": "application/vnd.oci.image.manifest.v1+json", "mediaType": "application/vnd.oci.image.manifest.v1+json",
"config": { "config": {
"mediaType": "application/vnd.oci.image.config.v1+json", "mediaType": "application/vnd.oci.image.config.v1+json",
"digest": "sha256:1c70b3e7c3a57801501ec127aa6c918c390c373294ec4fc48f2c6fe703fcc6fe", "digest": "sha256:363133d587b90ff7a21f7b32a96be8422c6799683f0e1e6d71de5c03a82ab35e",
"size": 453 "size": 438
}, },
"layers": [ "layers": [
{ {
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:97a548f8d65d9ab617f608dd621f59e0d43a3b346f34c34eb58da31f00a9b0ad", "digest": "sha256:07d9a868932bd092fa0a4c4df943785a7ba9cee12dbf446d02488319a5fbf336",
"size": 116 "size": 116
} }
] ]

View File

@@ -0,0 +1 @@
{"architecture":"unknown","os":"unknown","config":{},"rootfs":{"type":"layers","diff_ids":["sha256:f0dac65dd0ff6a656c419c654ac672c38029a3f1a4b4acce062bd2f5a923ffae"]}}

View File

@@ -0,0 +1 @@
{"architecture":"unknown","os":"unknown","config":{},"rootfs":{"type":"layers","diff_ids":["sha256:618f1e2f903648dde23cc38dc0ed7eed83d5394a6902bb7bfae8fa707c2e5c33"]}}

View File

@@ -0,0 +1 @@
{"architecture":"arm64","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"WorkingDir":"/"},"created":"2024-09-27T16:10:13.292759474Z","history":[{"created":"2024-09-27T16:10:13.292759474Z","created_by":"COPY /tmp/hello.txt / # buildkit","comment":"buildkit.dockerfile.v0"}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:81a78ab7aa0b72d665a9c203b4c30f0423e434b789ed95b2d418e60a1b726470"]}}

View File

@@ -1 +1 @@
{"_type":"https://in-toto.io/Statement/v0.1","predicateType":"https://spdx.dev/Document","subject":[{"name":"pkg:docker/test-image@test?platform=linux%2Farm64","digest":{"sha256":"7a76cec943853f9f7105b1976afa1bf7cd5bb6afc4e9d5852dd8da7cf81ae86e"}}],"predicate":{"spdxVersion":"SPDX-2.3","dataLicense":"CC0-1.0","SPDXID":"SPDXRef-DOCUMENT","name":"sbom","documentNamespace":"https://anchore.com/syft/dir/sbom-6d900ae6-587d-4695-9c01-511801a85b65","creationInfo":{"licenseListVersion":"3.23","creators":["Organization: Anchore, Inc","Tool: syft-v0.105.0","Tool: buildkit-v0.12.4"],"created":"2024-03-08T16:42:30Z"},"packages":[{"name":"sbom","SPDXID":"SPDXRef-DocumentRoot-Directory-sbom","supplier":"NOASSERTION","downloadLocation":"NOASSERTION","filesAnalyzed":false,"primaryPackagePurpose":"FILE"}],"relationships":[{"spdxElementId":"SPDXRef-DOCUMENT","relatedSpdxElement":"SPDXRef-DocumentRoot-Directory-sbom","relationshipType":"DESCRIBES"}]}} {"_type":"https://in-toto.io/Statement/v0.1","predicateType":"https://spdx.dev/Document","subject":[{"name":"pkg:docker/test-image@test?platform=linux%2Farm64","digest":{"sha256":"52f7a760b9322aa1af76d998763868b7d1bfec2331a2574a438ef44c92c0c46d"}}],"predicate":{"spdxVersion":"SPDX-2.3","dataLicense":"CC0-1.0","SPDXID":"SPDXRef-DOCUMENT","name":"sbom","documentNamespace":"https://anchore.com/syft/dir/sbom-b92d7d2e-7ffe-4d0a-8194-9af68e80e169","creationInfo":{"licenseListVersion":"3.23","creators":["Organization: Anchore, Inc","Tool: syft-v0.105.0","Tool: buildkit-v0.15.2"],"created":"2024-09-27T16:10:21Z"},"packages":[{"name":"sbom","SPDXID":"SPDXRef-DocumentRoot-Directory-sbom","supplier":"NOASSERTION","downloadLocation":"NOASSERTION","filesAnalyzed":false,"primaryPackagePurpose":"FILE"}],"relationships":[{"spdxElementId":"SPDXRef-DOCUMENT","relatedSpdxElement":"SPDXRef-DocumentRoot-Directory-sbom","relationshipType":"DESCRIBES"}]}}

View File

@@ -0,0 +1 @@
{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:1e3839ac14fba8c5e4db574df2046ce21a9e012e4030305cea97ad3f07f81a4a","size":1607,"annotations":{"org.opencontainers.image.created":"2024-09-27T20:22:06Z","org.opencontainers.image.ref.name":"docker.io/library/test-image:test"}}]}

View File

@@ -0,0 +1 @@
{"architecture":"amd64","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"WorkingDir":"/"},"created":"2024-09-27T16:10:13.292759474Z","history":[{"created":"2024-09-27T16:10:13.292759474Z","created_by":"COPY /tmp/hello.txt / # buildkit","comment":"buildkit.dockerfile.v0"}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:81a78ab7aa0b72d665a9c203b4c30f0423e434b789ed95b2d418e60a1b726470"]}}

View File

@@ -0,0 +1,16 @@
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"config": {
"mediaType": "application/vnd.oci.image.config.v1+json",
"digest": "sha256:363133d587b90ff7a21f7b32a96be8422c6799683f0e1e6d71de5c03a82ab35e",
"size": 438
},
"layers": [
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:07d9a868932bd092fa0a4c4df943785a7ba9cee12dbf446d02488319a5fbf336",
"size": 116
}
]
}

View File

@@ -0,0 +1 @@
{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:7ae6b41655929ad8e1848064874a98ac3f68884996c79907f6525e3045f75390","size":476,"annotations":{"org.opencontainers.image.created":"2024-09-27T20:22:07Z","org.opencontainers.image.ref.name":"docker.io/library/test-image:test"},"platform":{"architecture":"amd64","os":"linux"}}]}

View File

@@ -0,0 +1 @@
{"imageLayoutVersion":"1.0.0"}

View File

@@ -1 +0,0 @@
{"architecture":"amd64","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"WorkingDir":"/","OnBuild":null},"created":"2024-03-08T16:42:30.065465358Z","history":[{"created":"2024-03-08T16:42:30.065465358Z","created_by":"COPY /tmp/hello.txt / # buildkit","comment":"buildkit.dockerfile.v0"}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:b842af8c2f1451ffc802ae4139819eaea8441223357642548d8a25ab5c52cff7"]}}

View File

@@ -1 +0,0 @@
{"architecture":"arm64","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"WorkingDir":"/","OnBuild":null},"created":"2024-03-08T16:42:30.065465358Z","history":[{"created":"2024-03-08T16:42:30.065465358Z","created_by":"COPY /tmp/hello.txt / # buildkit","comment":"buildkit.dockerfile.v0"}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:b842af8c2f1451ffc802ae4139819eaea8441223357642548d8a25ab5c52cff7"]}}

View File

@@ -80,7 +80,7 @@ func TestVSA(t *testing.T) {
opts := &attestation.SigningOptions{ opts := &attestation.SigningOptions{
TransparencyLog: tlog.GetMockTL(), TransparencyLog: tlog.GetMockTL(),
} }
attIdx, err := oci.IndexFromPath(test.UnsignedTestImage()) attIdx, err := oci.IndexFromPath(test.UnsignedTestIndex())
assert.NoError(t, err) assert.NoError(t, err)
signedManifests, err := SignStatements(ctx, attIdx.Index, signer, opts) signedManifests, err := SignStatements(ctx, attIdx.Index, signer, opts)
require.NoError(t, err) require.NoError(t, err)
@@ -106,7 +106,7 @@ func TestVSA(t *testing.T) {
assert.Empty(t, results.Violations) assert.Empty(t, results.Violations)
if assert.NotNil(t, results.Input) { if assert.NotNil(t, results.Input) {
assert.Equal(t, "sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620", results.Input.Digest) assert.Equal(t, test.UnsignedLinuxAMD64ImageDigest, results.Input.Digest)
assert.NotNil(t, results.Input.Tag) assert.NotNil(t, results.Input.Tag)
} }
@@ -135,7 +135,7 @@ func TestVerificationFailure(t *testing.T) {
opts := &attestation.SigningOptions{ opts := &attestation.SigningOptions{
TransparencyLog: tlog.GetMockTL(), TransparencyLog: tlog.GetMockTL(),
} }
attIdx, err := oci.IndexFromPath(test.UnsignedTestImage()) attIdx, err := oci.IndexFromPath(test.UnsignedTestIndex())
assert.NoError(t, err) assert.NoError(t, err)
signedManifests, err := SignStatements(ctx, attIdx.Index, signer, opts) signedManifests, err := SignStatements(ctx, attIdx.Index, signer, opts)
require.NoError(t, err) require.NoError(t, err)
@@ -216,7 +216,7 @@ func TestSignVerify(t *testing.T) {
{name: "mirror with verification", signTL: false, policyDir: LocalKeysPolicy, imageName: "mirror.org/library/test-image:test"}, {name: "mirror with verification", signTL: false, policyDir: LocalKeysPolicy, imageName: "mirror.org/library/test-image:test"},
} }
attIdx, err := oci.IndexFromPath(test.UnsignedTestImage()) attIdx, err := oci.IndexFromPath(test.UnsignedTestIndex())
assert.NoError(t, err) assert.NoError(t, err)
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {