Files
attest/policy/policy.go
James Carnegie 4a70e5ae36 Add platform filtering support to mapping.yml (#167)
* chore!: rename package config -> mapping
* feat: add platform filtering support to mapping.yml
2024-09-18 21:11:55 +01:00

97 lines
3.1 KiB
Go

package policy
import (
"context"
"fmt"
"github.com/distribution/reference"
"github.com/docker/attest/attestation"
"github.com/docker/attest/mapping"
"github.com/docker/attest/oci"
intoto "github.com/in-toto/in-toto-golang/in_toto"
"github.com/package-url/packageurl-go"
)
func CreateImageDetailsResolver(imageSource *oci.ImageSpec) (oci.ImageDetailsResolver, error) {
switch imageSource.Type {
case oci.OCI:
return attestation.NewOCILayoutResolver(imageSource)
case oci.Docker:
return oci.NewRegistryImageDetailsResolver(imageSource)
}
return nil, fmt.Errorf("unsupported image source type: %s", imageSource.Type)
}
func CreateAttestationResolver(resolver oci.ImageDetailsResolver, policyMapping *mapping.PolicyMapping) (attestation.Resolver, error) {
if policyMapping.Attestations != nil {
if policyMapping.Attestations.Style == mapping.AttestationStyleAttached {
switch resolver := resolver.(type) {
case *oci.RegistryImageDetailsResolver:
return attestation.NewRegistryResolver(resolver)
case *attestation.LayoutResolver:
return resolver, nil
default:
return nil, fmt.Errorf("unsupported image details resolver type: %T", resolver)
}
}
if policyMapping.Attestations.Repo != "" {
return attestation.NewReferrersResolver(resolver, attestation.WithReferrersRepo(policyMapping.Attestations.Repo))
}
}
return attestation.NewReferrersResolver(resolver)
}
// VerifySubject verifies if any of the given subject PURLs matches the image name and platform from resolver.
// Tags are not taken into account when attempting to match because sometimes the user may not have specified a tag, and maybe there
// isn't a purl subject with that particular tag (because of post build tagging?).
func VerifySubject(ctx context.Context, subject []intoto.Subject, resolver attestation.Resolver) error {
img, err := resolver.ImageName(ctx)
if err != nil {
return err
}
inputName, err := reference.ParseNormalizedNamed(img)
if err != nil {
return err
}
descriptor, err := resolver.ImageDescriptor(ctx)
if err != nil {
return err
}
platform, err := resolver.ImagePlatform(ctx)
if err != nil {
return err
}
for _, sub := range subject {
if sub.Digest[descriptor.Digest.Algorithm] != descriptor.Digest.Hex {
continue
}
purl, err := packageurl.FromString(sub.Name)
if err != nil {
continue
}
if purl.Type != packageurl.TypeDocker {
continue
}
if purl.Qualifiers.Map()["platform"] != platform.String() {
continue
}
// ensure reference is normalized before comparing
withNamespace := purl.Name
if purl.Namespace != "" {
withNamespace = purl.Namespace + "/" + purl.Name
}
subjectName, err := reference.ParseNormalizedNamed(withNamespace)
if err != nil {
continue
}
// this assumes that domain is part of the package URL (some say it should be a qualifier)
// buildkit puts the domain in the name, e.g. pkg:docker/ecr.io/foobar/alpine@latest?platform=linux%2Famd64
if inputName.Name() == subjectName.Name() {
// found a match
return nil
}
}
return fmt.Errorf("no matching subject found for image: %s", img)
}