Add support for OCI Referrers and fallback (#50)
* Add support for OCI Referrers and fallback
This commit is contained in:
150
pkg/oci/oci.go
150
pkg/oci/oci.go
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user