Skip to content
This repository was archived by the owner on Dec 3, 2024. It is now read-only.

Commit c0510fb

Browse files
authored
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
1 parent 5e16b97 commit c0510fb

File tree

78 files changed

+157
-84
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+157
-84
lines changed

attestation/attestation_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
const ExpectedStatements = 4
1313

1414
func TestExtractAnnotatedStatements(t *testing.T) {
15-
statements, err := attestation.ExtractAnnotatedStatements(test.UnsignedTestImage(".."), intoto.PayloadType)
15+
statements, err := attestation.ExtractAnnotatedStatements(test.UnsignedTestIndex(".."), intoto.PayloadType)
1616
assert.NoError(t, err)
1717
assert.Equalf(t, len(statements), ExpectedStatements, "expected %d statement, got %d", ExpectedStatements, len(statements))
1818
}

attestation/example_attestation_manifest_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func ExampleManifest() {
3131

3232
ref := "docker/image-signer-verifier:latest"
3333

34-
digest, err := v1.NewHash("sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620")
34+
digest, err := v1.NewHash("sha256:7ae6b41655929ad8e1848064874a98ac3f68884996c79907f6525e3045f75390")
3535
if err != nil {
3636
panic(err)
3737
}

attestation/layout.go

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import (
1414
)
1515

1616
// implementation of Resolver that closes over attestations from an oci layout.
17+
18+
var _ Resolver = (*LayoutResolver)(nil)
19+
1720
type LayoutResolver struct {
1821
*Manifest
1922
*oci.ImageSpec
@@ -86,38 +89,49 @@ func (r *LayoutResolver) ImagePlatform(_ context.Context) (*v1.Platform, error)
8689
}
8790

8891
func manifestFromOCILayout(path string, platform *v1.Platform) (*Manifest, error) {
89-
idx, err := layout.ImageIndexFromPath(path)
92+
layoutIndex, err := layout.ImageIndexFromPath(path)
9093
if err != nil {
9194
return nil, err
9295
}
9396

94-
idxm, err := idx.IndexManifest()
97+
layoutIndexManifest, err := layoutIndex.IndexManifest()
9598
if err != nil {
9699
return nil, fmt.Errorf("failed to get digest: %w", err)
97100
}
98101

99-
idxDescriptor := idxm.Manifests[0]
100-
idxDigest := idxDescriptor.Digest
101-
subjectName := idxDescriptor.Annotations[ocispec.AnnotationRefName]
102+
layoutDescriptor := layoutIndexManifest.Manifests[0]
103+
layoutDescriptorDigest := layoutDescriptor.Digest
104+
subjectName := layoutDescriptor.Annotations[ocispec.AnnotationRefName]
102105
if _, err := reference.ParseNamed(subjectName); err != nil {
103106
// try the containerd annotation if the org.opencontainers.image.ref.name is not a full name
104-
subjectName = idxDescriptor.Annotations[containerd.AnnotationImageName]
107+
subjectName = layoutDescriptor.Annotations[containerd.AnnotationImageName]
105108
if _, err := reference.ParseNamed(subjectName); err != nil {
106109
return nil, fmt.Errorf("failed to find subject name in annotations")
107110
}
108111
}
109112

110-
mfs, err := idx.ImageIndex(idxDigest)
113+
// check if digest refers to an image or an index
114+
_, err = layoutIndex.Image(layoutDescriptorDigest)
115+
if err == nil {
116+
return &Manifest{
117+
OriginalLayers: nil,
118+
OriginalDescriptor: nil,
119+
SubjectName: subjectName,
120+
SubjectDescriptor: &layoutDescriptor,
121+
}, nil
122+
}
123+
124+
subjectIndex, err := layoutIndex.ImageIndex(layoutDescriptorDigest)
111125
if err != nil {
112-
return nil, fmt.Errorf("failed to extract ImageIndex for digest %s: %w", idxDigest.String(), err)
126+
return nil, fmt.Errorf("failed to extract ImageIndex for digest %s: %w", layoutDescriptorDigest.String(), err)
113127
}
114-
mfs2, err := mfs.IndexManifest()
128+
subjectIndexManifest, err := subjectIndex.IndexManifest()
115129
if err != nil {
116130
return nil, fmt.Errorf("failed to extract IndexManifest from ImageIndex: %w", err)
117131
}
118132
var subjectDescriptor *v1.Descriptor
119-
for i := range mfs2.Manifests {
120-
manifest := &mfs2.Manifests[i]
133+
for i := range subjectIndexManifest.Manifests {
134+
manifest := &subjectIndexManifest.Manifests[i]
121135
if manifest.Platform != nil {
122136
if manifest.Platform.Equals(*platform) {
123137
subjectDescriptor = manifest
@@ -128,8 +142,8 @@ func manifestFromOCILayout(path string, platform *v1.Platform) (*Manifest, error
128142
if subjectDescriptor == nil {
129143
return nil, fmt.Errorf("platform not found in index")
130144
}
131-
for i := range mfs2.Manifests {
132-
mf := &mfs2.Manifests[i]
145+
for i := range subjectIndexManifest.Manifests {
146+
mf := &subjectIndexManifest.Manifests[i]
133147
if mf.Annotations[DockerReferenceType] != AttestationManifestType {
134148
continue
135149
}
@@ -138,7 +152,7 @@ func manifestFromOCILayout(path string, platform *v1.Platform) (*Manifest, error
138152
continue
139153
}
140154

141-
attestationImage, err := mfs.Image(mf.Digest)
155+
attestationImage, err := subjectIndex.Image(mf.Digest)
142156
if err != nil {
143157
return nil, fmt.Errorf("failed to extract attestation image with digest %s: %w", mf.Digest.String(), err)
144158
}

attestation/layout_test.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package attestation_test
22

33
import (
4+
"context"
45
"path/filepath"
56
"strings"
67
"testing"
@@ -25,7 +26,7 @@ func TestAttestationFromOCILayout(t *testing.T) {
2526
}
2627

2728
opts := &attestation.SigningOptions{}
28-
attIdx, err := oci.IndexFromPath(test.UnsignedTestImage(".."))
29+
attIdx, err := oci.IndexFromPath(test.UnsignedTestIndex(".."))
2930
require.NoError(t, err)
3031
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
3132
require.NoError(t, err)
@@ -74,7 +75,7 @@ func TestSubjectNameAnnotations(t *testing.T) {
7475
ociLayoutPath string
7576
errorStr string
7677
}{
77-
{name: "oci annotation", ociLayoutPath: test.UnsignedTestImage("..")},
78+
{name: "oci annotation", ociLayoutPath: test.UnsignedTestIndex("..")},
7879
{name: "containerd annotation", ociLayoutPath: filepath.Join("..", "test", "testdata", "containerd-subject-layout")},
7980
{name: "missing subject name", ociLayoutPath: filepath.Join("..", "test", "testdata", "missing-subject-layout"), errorStr: "failed to find subject name in annotations"},
8081
}
@@ -93,3 +94,14 @@ func TestSubjectNameAnnotations(t *testing.T) {
9394
})
9495
}
9596
}
97+
98+
func TestImageDetailsFromImageLayout(t *testing.T) {
99+
spec, err := oci.ParseImageSpec(oci.LocalPrefix+test.UnsignedTestImage(".."), oci.WithPlatform("linux/arm64"))
100+
require.NoError(t, err)
101+
resolver, err := policy.CreateImageDetailsResolver(spec)
102+
require.NoError(t, err)
103+
desc, err := resolver.ImageDescriptor(context.Background())
104+
require.NoError(t, err)
105+
digest := desc.Digest.String()
106+
assert.Equal(t, "sha256:7ae6b41655929ad8e1848064874a98ac3f68884996c79907f6525e3045f75390", digest)
107+
}

attestation/mock.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package attestation
33
import (
44
"context"
55

6+
"github.com/docker/attest/internal/test"
67
"github.com/docker/attest/oci"
78
v1 "github.com/google/go-containerregistry/pkg/v1"
89
)
@@ -36,7 +37,7 @@ func (r MockResolver) ImageDescriptor(_ context.Context) (*v1.Descriptor, error)
3637
if r.DescriptorFn != nil {
3738
return r.DescriptorFn()
3839
}
39-
digest, err := v1.NewHash("sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620")
40+
digest, err := v1.NewHash(test.UnsignedLinuxAMD64ImageDigest)
4041
if err != nil {
4142
return nil, err
4243
}

attestation/referrers_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ func TestAttestationReferenceTypes(t *testing.T) {
8989
require.NoError(t, err)
9090

9191
opts := &attestation.SigningOptions{}
92-
attIdx, err := oci.IndexFromPath(test.UnsignedTestImage(".."))
92+
attIdx, err := oci.IndexFromPath(test.UnsignedTestIndex(".."))
9393
require.NoError(t, err)
9494

9595
indexName := fmt.Sprintf("%s/repo:root", u.Host)
@@ -209,7 +209,7 @@ func TestReferencesInDifferentRepo(t *testing.T) {
209209
require.NoError(t, err)
210210

211211
opts := &attestation.SigningOptions{}
212-
attIdx, err := oci.IndexFromPath(test.UnsignedTestImage(".."))
212+
attIdx, err := oci.IndexFromPath(test.UnsignedTestIndex(".."))
213213
require.NoError(t, err)
214214

215215
indexName := fmt.Sprintf("%s/%s:latest", serverURL.Host, repoName)
@@ -233,7 +233,7 @@ func TestReferencesInDifferentRepo(t *testing.T) {
233233
require.NoError(t, err)
234234

235235
opts := &attestation.SigningOptions{}
236-
attIdx, err := oci.IndexFromPath(test.UnsignedTestImage(".."))
236+
attIdx, err := oci.IndexFromPath(test.UnsignedTestIndex(".."))
237237
require.NoError(t, err)
238238

239239
indexName := fmt.Sprintf("%s/%s:latest", serverURL.Host, repoName)
@@ -286,7 +286,7 @@ func TestCorrectArtifactTypeInTagFallback(t *testing.T) {
286286
repoName := "repo"
287287

288288
opts := &attestation.SigningOptions{}
289-
attIdx, err := oci.IndexFromPath(test.UnsignedTestImage(".."))
289+
attIdx, err := oci.IndexFromPath(test.UnsignedTestIndex(".."))
290290
require.NoError(t, err)
291291

292292
indexName := fmt.Sprintf("%s/%s:latest", serverURL.Host, repoName)

attestation/registry_test.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func TestRegistry(t *testing.T) {
2424
require.NoError(t, err)
2525

2626
opts := &attestation.SigningOptions{}
27-
attIdx, err := oci.IndexFromPath(test.UnsignedTestImage(".."))
27+
attIdx, err := oci.IndexFromPath(test.UnsignedTestIndex(".."))
2828
require.NoError(t, err)
2929
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
3030
require.NoError(t, err)
@@ -46,4 +46,14 @@ func TestRegistry(t *testing.T) {
4646
require.NoError(t, err)
4747
digest := desc.Digest.String()
4848
assert.True(t, strings.Contains(digest, "sha256:"))
49+
50+
// resolver also works with platform specific digest
51+
spec, err = oci.ParseImageSpec(fmt.Sprintf("%s@%s", indexName, digest))
52+
require.NoError(t, err)
53+
54+
resolver, err = policy.CreateImageDetailsResolver(spec)
55+
require.NoError(t, err)
56+
desc, err = resolver.ImageDescriptor(ctx)
57+
require.NoError(t, err)
58+
assert.Equal(t, desc.Digest.String(), digest)
4959
}

attestation/sign_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ func TestSimpleStatementSigning(t *testing.T) {
249249
PredicateType: attestation.VSAPredicateType,
250250
},
251251
}
252-
digest, err := v1.NewHash("sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620")
252+
digest, err := v1.NewHash(test.UnsignedLinuxAMD64ImageDigest)
253253
require.NoError(t, err)
254254
subject := &v1.Descriptor{
255255
MediaType: "application/vnd.oci.image.manifest.v1+json",

internal/test/test.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,20 @@ import (
2323
)
2424

2525
const (
26-
UseMockKMS = true
27-
28-
AWSRegion = "us-east-1"
29-
AWSKMSKeyARN = "arn:aws:kms:us-east-1:175142243308:alias/doi-signing" // sandbox
26+
UseMockKMS = true
27+
AWSRegion = "us-east-1"
28+
AWSKMSKeyARN = "arn:aws:kms:us-east-1:175142243308:alias/doi-signing" // sandbox
29+
UnsignedLinuxAMD64ImageDigest = "sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620"
30+
UnsignedLinuxArm64ImageDigest = "sha256:7a76cec943853f9f7105b1976afa1bf7cd5bb6afc4e9d5852dd8da7cf81ae86e"
3031
)
3132

33+
func UnsignedTestIndex(rel ...string) string {
34+
rel = append(rel, "test", "testdata", "unsigned-index")
35+
return filepath.Join(rel...)
36+
}
37+
3238
func UnsignedTestImage(rel ...string) string {
33-
rel = append(rel, "test", "testdata", "unsigned-test-image")
39+
rel = append(rel, "test", "testdata", "unsigned-image")
3440
return filepath.Join(rel...)
3541
}
3642

oci/authn_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
)
1313

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

0 commit comments

Comments
 (0)