* chore!: rename package config -> mapping * feat: add platform filtering support to mapping.yml
196 lines
5.6 KiB
Go
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
|
|
}
|