Add support for OCI Referrers and fallback (#50)

* Add support for OCI Referrers and fallback
This commit is contained in:
James Carnegie
2024-06-13 16:10:41 +01:00
committed by GitHub
parent e120439035
commit 8ba9656645
15 changed files with 463 additions and 67 deletions

View File

@@ -72,7 +72,7 @@ func attestationManifestFromOCILayout(path string, platform *v1.Platform) (*Atte
continue
}
if mf.Annotations[DockerReferenceDigest] != imageDigest {
if mf.Annotations[att.DockerReferenceDigest] != imageDigest {
continue
}
@@ -195,6 +195,152 @@ func (r *OCILayoutResolver) ImageDigest(ctx context.Context) (string, error) {
return r.Digest, nil
}
type ReferrersResolver struct {
image string
platform *v1.Platform
referrersRepo string
manifests []*AttestationManifest
}
func NewReferrersAttestationResolver(image string, options ...func(*ReferrersResolver) error) (*ReferrersResolver, error) {
res := &ReferrersResolver{
image: image,
}
for _, opt := range options {
err := opt(res)
if err != nil {
return nil, err
}
}
return res, nil
}
func WithReferrersRepo(repo string) func(*ReferrersResolver) error {
return func(r *ReferrersResolver) error {
r.referrersRepo = repo
return nil
}
}
// WithPlatform sets the platform for the resolver (needed for platform specific tag resolution)
func WithPlatform(platform string) func(*ReferrersResolver) error {
return func(r *ReferrersResolver) error {
p, err := ParsePlatform(platform)
if err != nil {
return err
}
r.platform = p
return nil
}
}
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)
}
var referrersSubjectRef name.Digest
if r.referrersRepo != "" {
referrersSubjectRef, err = name.NewDigest(fmt.Sprintf("%s@%s", r.referrersRepo, subjectDigest.String()))
if err != nil {
return fmt.Errorf("failed to create referrers reference: %w", err)
}
} else {
referrersSubjectRef = subjectRef.Context().Digest(subjectDigest.String())
}
referrersIndex, err := remote.Referrers(referrersSubjectRef)
if err != nil {
return fmt.Errorf("failed to get referrers: %w", err)
}
referrersIndexManifest, err := referrersIndex.IndexManifest()
if err != nil {
return fmt.Errorf("failed to get index manifest: %w", err)
}
if len(referrersIndexManifest.Manifests) == 0 {
return errors.New("no referrers found")
}
aManifests := make([]*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] != AttestationManifestType {
continue
}
if manifest.Annotations[att.DockerReferenceDigest] != subjectDigest.String() {
continue
}
attest := &AttestationManifest{
Name: r.image,
Image: attestationImage,
Manifest: manifest,
Descriptor: &m,
Digest: subjectDigest.String(),
Platform: r.platform,
}
aManifests = append(aManifests, attest)
}
if len(aManifests) == 0 {
return errors.New("no attestation manifests found")
}
r.manifests = aManifests
}
return nil
}
func (r *ReferrersResolver) Attestations(ctx context.Context, predicateType string) ([]*att.Envelope, error) {
err := r.resolveAttestations(ctx)
if err != nil {
return nil, fmt.Errorf("failed to resolve attestations: %w", err)
}
var envs []*att.Envelope
for _, attest := range r.manifests {
es, err := ExtractEnvelopes(attest, predicateType)
if err != nil {
return nil, fmt.Errorf("failed to extract envelopes: %w", err)
}
envs = append(envs, es...)
}
return envs, nil
}
func (r *ReferrersResolver) ImageName(ctx context.Context) (string, error) {
return r.image, nil
}
func (r *ReferrersResolver) ImagePlatform() (*v1.Platform, error) {
return r.platform, nil
}
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 len(r.manifests) == 0 {
return "", errors.New("no attestation manifests found")
}
return r.manifests[0].Digest, nil
}
type RegistryResolver struct {
image string
platform *v1.Platform
@@ -352,7 +498,7 @@ func imageDigestForPlatform(ix *v1.IndexManifest, platform *v1.Platform) (string
func attestationDigestForDigest(ix *v1.IndexManifest, imageDigest string, attestType string) (string, error) {
for _, m := range ix.Manifests {
if v, ok := m.Annotations[att.DockerReferenceType]; ok && v == attestType {
if d, ok := m.Annotations[DockerReferenceDigest]; ok && d == imageDigest {
if d, ok := m.Annotations[att.DockerReferenceDigest]; ok && d == imageDigest {
return m.Digest.String(), nil
}
}

View File

@@ -7,6 +7,10 @@ import (
v1 "github.com/google/go-containerregistry/pkg/v1"
)
type AttestationManifests struct {
Manifests []*AttestationManifest
}
type AttestationManifest struct {
// attestation image details
Image v1.Image

View File

@@ -12,18 +12,17 @@ import (
)
const (
DockerReferenceDigest = "vnd.docker.reference.digest"
AttestationManifestType = "attestation-manifest"
InTotoPredicateType = "in-toto.io/predicate-type"
OciReferenceTarget = "org.opencontainers.image.ref.name"
)
type AttestationIndex struct {
type SubjectIndex struct {
Index v1.ImageIndex
Name string
}
func AttestationIndexFromPath(path string) (*AttestationIndex, error) {
func SubjectIndexFromPath(path string) (*SubjectIndex, error) {
wrapperIdx, err := layout.ImageIndexFromPath(path)
if err != nil {
return nil, fmt.Errorf("failed to load image index: %w", err)
@@ -40,13 +39,13 @@ func AttestationIndexFromPath(path string) (*AttestationIndex, error) {
if err != nil {
return nil, fmt.Errorf("failed to extract ImageIndex for digest %s: %w", idxDigest.String(), err)
}
return &AttestationIndex{
return &SubjectIndex{
Index: idx,
Name: imageName,
}, nil
}
func AttestationIndexFromRemote(image string) (*AttestationIndex, error) {
func SubjectIndexFromRemote(image string) (*SubjectIndex, error) {
ref, err := name.ParseReference(image)
if err != nil {
log.Fatalf("Failed to parse image name: %v", err)
@@ -61,7 +60,7 @@ func AttestationIndexFromRemote(image string) (*AttestationIndex, error) {
if err != nil {
return nil, fmt.Errorf("failed to pull image %s: %w", image, err)
}
return &AttestationIndex{
return &SubjectIndex{
Index: idx,
Name: image,
}, nil