Files
attest/policy/resolver.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

196 lines
5.6 KiB
Go

package policy
import (
"context"
"fmt"
"os"
"path"
"path/filepath"
"github.com/distribution/reference"
"github.com/docker/attest/internal/util"
"github.com/docker/attest/mapping"
"github.com/docker/attest/tuf"
v1 "github.com/google/go-containerregistry/pkg/v1"
)
type Resolver struct {
tufClient tuf.Downloader
opts *Options
}
func NewResolver(tufClient tuf.Downloader, opts *Options) *Resolver {
return &Resolver{
tufClient: tufClient,
opts: opts,
}
}
func (r *Resolver) ResolvePolicy(_ context.Context, imageName string, platform *v1.Platform) (*Policy, error) {
p, err := r.resolvePolicyByID()
if err != nil {
return nil, fmt.Errorf("failed to resolve policy by id: %w", err)
}
if p != nil {
return p, nil
}
imageName, err = normalizeImageName(imageName)
if err != nil {
return nil, fmt.Errorf("failed to parse image name: %w", err)
}
localMappings, err := mapping.LoadLocalMappings(r.opts.LocalPolicyDir)
if err != nil {
return nil, fmt.Errorf("failed to load local policy mappings: %w", err)
}
match, err := localMappings.FindPolicyMatch(imageName, platform)
if err != nil {
return nil, err
}
if match.MatchType == mapping.MatchTypePolicy {
return r.resolveLocalPolicy(match.Policy, imageName, match.MatchedName)
}
if !r.opts.DisableTUF {
tufMappings, err := mapping.LoadTUFMappings(r.tufClient, r.opts.LocalTargetsDir)
if err != nil {
return nil, fmt.Errorf("failed to load tuf policy mappings as fallback: %w", err)
}
// it's a mirror of a tuf policy
if match.MatchType == mapping.MatchTypeMatchNoPolicy {
for _, mapping := range tufMappings.Policies {
if mapping.ID == match.Rule.PolicyID {
return r.resolveTUFPolicy(mapping, imageName, match.MatchedName)
}
}
}
// try to resolve a tuf policy directly
match, err = tufMappings.FindPolicyMatch(imageName, platform)
if err != nil {
return nil, err
}
if match.MatchType == mapping.MatchTypePolicy {
return r.resolveTUFPolicy(match.Policy, imageName, match.MatchedName)
}
}
return nil, nil
}
func (r *Resolver) resolveLocalPolicy(mapping *mapping.PolicyMapping, imageName string, matchedName string) (*Policy, error) {
if r.opts.LocalPolicyDir == "" {
return nil, fmt.Errorf("local policy dir not set")
}
var URI string
var digest map[string]string
files := make([]*File, 0, len(mapping.Files))
for _, f := range mapping.Files {
filename := f.Path
filePath := path.Join(r.opts.LocalPolicyDir, filename)
fileContents, err := os.ReadFile(filePath)
if err != nil {
return nil, fmt.Errorf("failed to read policy file %s: %w", filename, err)
}
files = append(files, &File{
Path: filename,
Content: fileContents,
})
// if the file is a policy file, store the URI and digest
if filepath.Ext(filename) == ".rego" {
// TODO: support multiple rego files, need some way to identify the main policy file
if URI != "" {
return nil, fmt.Errorf("multiple policy files found in policy mapping")
}
URI = filePath
digest = map[string]string{"sha256": util.SHA256Hex(fileContents)}
}
}
if URI == "" {
return nil, fmt.Errorf("no policy file found in policy mapping")
}
policy := &Policy{
InputFiles: files,
Mapping: mapping,
URI: URI,
Digest: digest,
}
if imageName != matchedName {
policy.ResolvedName = matchedName
}
return policy, nil
}
func (r *Resolver) resolveTUFPolicy(mapping *mapping.PolicyMapping, imageName string, matchedName string) (*Policy, error) {
var URI string
var digest map[string]string
files := make([]*File, 0, len(mapping.Files))
for _, f := range mapping.Files {
filename := f.Path
file, err := r.tufClient.DownloadTarget(filename, filepath.Join(r.opts.LocalTargetsDir, filename))
if err != nil {
return nil, fmt.Errorf("failed to download policy file %s: %w", filename, err)
}
files = append(files, &File{
Path: filename,
Content: file.Data,
})
// if the file is a policy file, store the URI and digest
if filepath.Ext(filename) == ".rego" {
// TODO: support multiple rego files, need some way to identify the main policy file
if URI != "" {
return nil, fmt.Errorf("multiple policy files found in policy mapping")
}
URI = file.TargetURI
digest = map[string]string{"sha256": file.Digest}
}
}
if URI == "" {
return nil, fmt.Errorf("no policy file found in policy mapping")
}
policy := &Policy{
InputFiles: files,
Mapping: mapping,
URI: URI,
Digest: digest,
}
if imageName != matchedName {
policy.ResolvedName = matchedName
}
return policy, nil
}
func (r *Resolver) resolvePolicyByID() (*Policy, error) {
if r.opts.PolicyID != "" {
localMappings, err := mapping.LoadLocalMappings(r.opts.LocalPolicyDir)
if err != nil {
return nil, fmt.Errorf("failed to load local policy mappings: %w", err)
}
if localMappings != nil {
policy := localMappings.Policies[r.opts.PolicyID]
if policy != nil {
return r.resolveLocalPolicy(policy, "", "")
}
}
if !r.opts.DisableTUF {
tufMappings, err := mapping.LoadTUFMappings(r.tufClient, r.opts.LocalTargetsDir)
if err != nil {
return nil, fmt.Errorf("failed to load tuf policy mappings by id: %w", err)
}
policy := tufMappings.Policies[r.opts.PolicyID]
if policy != nil {
return r.resolveTUFPolicy(policy, "", "")
}
}
return nil, fmt.Errorf("policy with id %s not found", r.opts.PolicyID)
}
return nil, nil
}
func normalizeImageName(imageName string) (string, error) {
named, err := reference.ParseNormalizedNamed(imageName)
if err != nil {
return "", fmt.Errorf("failed to parse image name: %w", err)
}
return named.Name(), nil
}