Support referrers using digest, not just tag (#55)
* Support referrers using digest, not just tag * ParseRef and switch on type * Call DigestStr instead of String
This commit is contained in:
@@ -13,7 +13,9 @@ import (
|
||||
"github.com/docker/attest/pkg/mirror"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
"github.com/docker/attest/pkg/policy"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
"github.com/google/go-containerregistry/pkg/registry"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -33,6 +35,7 @@ func TestAttestationReferenceTypes(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
server *httptest.Server
|
||||
skipSubject bool
|
||||
useDigest bool
|
||||
}{
|
||||
{
|
||||
server: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
|
||||
@@ -44,6 +47,10 @@ func TestAttestationReferenceTypes(t *testing.T) {
|
||||
server: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
|
||||
skipSubject: true,
|
||||
},
|
||||
{
|
||||
server: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
|
||||
useDigest: true,
|
||||
},
|
||||
} {
|
||||
s := tc.server
|
||||
defer s.Close()
|
||||
@@ -65,7 +72,18 @@ func TestAttestationReferenceTypes(t *testing.T) {
|
||||
|
||||
for _, platform := range platforms {
|
||||
// can eval policy in the normal way
|
||||
resolver, err := oci.NewRegistryAttestationResolver(indexName, platform)
|
||||
ref := indexName
|
||||
if tc.useDigest {
|
||||
options := oci.WithOptions(ctx, nil)
|
||||
subjectRef, err := name.ParseReference(indexName)
|
||||
require.NoError(t, err)
|
||||
desc, err := remote.Index(subjectRef, options...)
|
||||
require.NoError(t, err)
|
||||
idxDigest, err := desc.Digest()
|
||||
require.NoError(t, err)
|
||||
ref = fmt.Sprintf("%s/repo@%s", u.Host, idxDigest.String())
|
||||
}
|
||||
resolver, err := oci.NewRegistryAttestationResolver(ref, platform)
|
||||
require.NoError(t, err)
|
||||
|
||||
policyOpts := &policy.PolicyOptions{
|
||||
@@ -74,9 +92,22 @@ func TestAttestationReferenceTypes(t *testing.T) {
|
||||
results, err := attest.Verify(ctx, policyOpts, resolver)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, attest.OutcomeSuccess, results.Outcome)
|
||||
|
||||
if !tc.skipSubject {
|
||||
// can evaluate policy using referrers
|
||||
referrersResolver, err := oci.NewReferrersAttestationResolver(indexName, oci.WithPlatform(platform))
|
||||
if tc.useDigest {
|
||||
p, err := oci.ParsePlatform(platform)
|
||||
require.NoError(t, err)
|
||||
options := oci.WithOptions(ctx, p)
|
||||
subjectRef, err := name.ParseReference(indexName)
|
||||
require.NoError(t, err)
|
||||
desc, err := remote.Image(subjectRef, options...)
|
||||
require.NoError(t, err)
|
||||
subjectDigest, err := desc.Digest()
|
||||
require.NoError(t, err)
|
||||
ref = fmt.Sprintf("%s/repo@%s", u.Host, subjectDigest.String())
|
||||
}
|
||||
referrersResolver, err := oci.NewReferrersAttestationResolver(ref, oci.WithPlatform(platform))
|
||||
require.NoError(t, err)
|
||||
|
||||
results, err = attest.Verify(ctx, policyOpts, referrersResolver)
|
||||
@@ -137,7 +168,7 @@ func TestReferencesInDifferentRepo(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
for _, mf := range mfs2.Manifests {
|
||||
//skip signed/unsigned attestations
|
||||
if mf.Annotations[attestation.DockerReferenceType] == "attestation-manifest" {
|
||||
if mf.Annotations[attestation.DockerReferenceType] == attestation.AttestationManifestType {
|
||||
continue
|
||||
}
|
||||
// can evaluate policy using referrers in a different repo
|
||||
|
||||
@@ -198,6 +198,7 @@ func (r *OCILayoutResolver) ImageDigest(ctx context.Context) (string, error) {
|
||||
type ReferrersResolver struct {
|
||||
image string
|
||||
platform *v1.Platform
|
||||
digest string
|
||||
referrersRepo string
|
||||
manifests []*AttestationManifest
|
||||
}
|
||||
@@ -236,27 +237,20 @@ func WithPlatform(platform string) func(*ReferrersResolver) error {
|
||||
|
||||
func (r *ReferrersResolver) resolveAttestations(ctx context.Context) error {
|
||||
if r.manifests == nil {
|
||||
options := withOptions(ctx, r.platform)
|
||||
subjectRef, err := name.ParseReference(r.image)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse reference: %w", err)
|
||||
}
|
||||
desc, err := remote.Image(subjectRef, options...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get image manifest: %w", err)
|
||||
}
|
||||
subjectDigest, err := desc.Digest()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get image digest: %w", err)
|
||||
}
|
||||
subjectDigest, err := r.ImageDigest(ctx)
|
||||
|
||||
var referrersSubjectRef name.Digest
|
||||
if r.referrersRepo != "" {
|
||||
referrersSubjectRef, err = name.NewDigest(fmt.Sprintf("%s@%s", r.referrersRepo, subjectDigest.String()))
|
||||
referrersSubjectRef, err = name.NewDigest(fmt.Sprintf("%s@%s", r.referrersRepo, subjectDigest))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create referrers reference: %w", err)
|
||||
}
|
||||
} else {
|
||||
referrersSubjectRef = subjectRef.Context().Digest(subjectDigest.String())
|
||||
referrersSubjectRef = subjectRef.Context().Digest(subjectDigest)
|
||||
}
|
||||
referrersIndex, err := remote.Referrers(referrersSubjectRef)
|
||||
if err != nil {
|
||||
@@ -284,7 +278,7 @@ func (r *ReferrersResolver) resolveAttestations(ctx context.Context) error {
|
||||
if manifest.Annotations[att.DockerReferenceType] != AttestationManifestType {
|
||||
continue
|
||||
}
|
||||
if manifest.Annotations[att.DockerReferenceDigest] != subjectDigest.String() {
|
||||
if manifest.Annotations[att.DockerReferenceDigest] != subjectDigest {
|
||||
continue
|
||||
}
|
||||
attest := &AttestationManifest{
|
||||
@@ -292,7 +286,7 @@ func (r *ReferrersResolver) resolveAttestations(ctx context.Context) error {
|
||||
Image: attestationImage,
|
||||
Manifest: manifest,
|
||||
Descriptor: &m,
|
||||
Digest: subjectDigest.String(),
|
||||
Digest: subjectDigest,
|
||||
Platform: r.platform,
|
||||
}
|
||||
aManifests = append(aManifests, attest)
|
||||
@@ -331,14 +325,30 @@ func (r *ReferrersResolver) ImagePlatform() (*v1.Platform, error) {
|
||||
}
|
||||
|
||||
func (r *ReferrersResolver) ImageDigest(ctx context.Context) (string, error) {
|
||||
err := r.resolveAttestations(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to resolve attestations: %w", err)
|
||||
if r.digest == "" {
|
||||
subjectRef, err := name.ParseReference(r.image)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse reference: %w", err)
|
||||
}
|
||||
switch t := subjectRef.(type) {
|
||||
case name.Digest:
|
||||
r.digest = t.DigestStr()
|
||||
case name.Tag:
|
||||
options := WithOptions(ctx, r.platform)
|
||||
desc, err := remote.Image(t, options...)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get image manifest: %w", err)
|
||||
}
|
||||
subjectDigest, err := desc.Digest()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get image digest: %w", err)
|
||||
}
|
||||
r.digest = subjectDigest.String()
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported reference type: %T", t)
|
||||
}
|
||||
}
|
||||
if len(r.manifests) == 0 {
|
||||
return "", errors.New("no attestation manifests found")
|
||||
}
|
||||
return r.manifests[0].Digest, nil
|
||||
return r.digest, nil
|
||||
}
|
||||
|
||||
type RegistryResolver struct {
|
||||
@@ -390,20 +400,20 @@ func (r *RegistryResolver) Attestations(ctx context.Context, predicateType strin
|
||||
|
||||
func FetchAttestationManifest(ctx context.Context, image string, platform *v1.Platform) (*AttestationManifest, error) {
|
||||
// we want to get to the image index, so ignoring platform for now
|
||||
options := withOptions(ctx, nil)
|
||||
options := WithOptions(ctx, nil)
|
||||
ref, err := name.ParseReference(image)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse reference: %w", err)
|
||||
}
|
||||
desc, err := remote.Index(ref, options...)
|
||||
index, err := remote.Index(ref, options...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to obtain index manifest: %w", err)
|
||||
return nil, fmt.Errorf("failed to get index: %w", err)
|
||||
}
|
||||
ix, err := desc.IndexManifest()
|
||||
indexManifest, err := index.IndexManifest()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to obtain index manifest: %w", err)
|
||||
return nil, fmt.Errorf("failed to get index manifest: %w", err)
|
||||
}
|
||||
digest, err := imageDigestForPlatform(ix, platform)
|
||||
digest, err := imageDigestForPlatform(indexManifest, platform)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to obtain image for platform: %w", err)
|
||||
}
|
||||
@@ -412,7 +422,7 @@ func FetchAttestationManifest(ctx context.Context, image string, platform *v1.Pl
|
||||
return nil, fmt.Errorf("failed to parse attestation reference: %w", err)
|
||||
}
|
||||
|
||||
attestationDigest, err := attestationDigestForDigest(ix, digest, "attestation-manifest")
|
||||
attestationDigest, err := attestationDigestForDigest(indexManifest, digest, "attestation-manifest")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to obtain attestation for image: %w", err)
|
||||
}
|
||||
@@ -444,7 +454,7 @@ func FetchAttestationManifest(ctx context.Context, image string, platform *v1.Pl
|
||||
return attest, nil
|
||||
}
|
||||
|
||||
func withOptions(ctx context.Context, platform *v1.Platform) []remote.Option {
|
||||
func WithOptions(ctx context.Context, platform *v1.Platform) []remote.Option {
|
||||
// prepare options
|
||||
options := []remote.Option{remote.WithAuthFromKeychain(authn.DefaultKeychain), remote.WithTransport(HttpTransport()), remote.WithContext(ctx)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user