* chore!: rename package config -> mapping * feat: add platform filtering support to mapping.yml
139 lines
4.0 KiB
Go
139 lines
4.0 KiB
Go
package mapping
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
|
|
"github.com/docker/attest/tuf"
|
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
|
"sigs.k8s.io/yaml"
|
|
)
|
|
|
|
const (
|
|
MappingFilename = "mapping.yaml"
|
|
)
|
|
|
|
func validateMappingsFile(mappings *policyMappingsFile) error {
|
|
var validationErrors []error
|
|
if mappings.Kind != "policy-mapping" {
|
|
validationErrors = append(validationErrors, fmt.Errorf("file is not of kind policy-mapping: %s", mappings.Kind))
|
|
}
|
|
if mappings.Version != "v1" {
|
|
validationErrors = append(validationErrors, fmt.Errorf("unsupported policy mapping file version: %s", mappings.Version))
|
|
}
|
|
for _, rule := range mappings.Rules {
|
|
if rule.Pattern == "" {
|
|
validationErrors = append(validationErrors, fmt.Errorf("rule missing pattern: %s", rule))
|
|
}
|
|
if rule.PolicyID == "" && rule.Replacement == "" {
|
|
validationErrors = append(validationErrors, fmt.Errorf("rule must have policy-id or replacement: %s", rule))
|
|
}
|
|
if rule.PolicyID != "" && rule.Replacement != "" {
|
|
validationErrors = append(validationErrors, fmt.Errorf("rule cannot have both policy-id and replacement: %s", rule))
|
|
}
|
|
if rule.Platforms != nil {
|
|
for _, platform := range rule.Platforms {
|
|
if platform == "" {
|
|
validationErrors = append(validationErrors, fmt.Errorf("rule has empty platform: %s", rule))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for _, policy := range mappings.Policies {
|
|
if policy.ID == "" {
|
|
validationErrors = append(validationErrors, fmt.Errorf("policy missing id: %s", policy.ID))
|
|
}
|
|
if len(policy.Files) == 0 {
|
|
validationErrors = append(validationErrors, fmt.Errorf("policy missing files: %v", policy))
|
|
}
|
|
for _, file := range policy.Files {
|
|
if file.Path == "" {
|
|
validationErrors = append(validationErrors, fmt.Errorf("file missing path: %s", file))
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(validationErrors) > 0 {
|
|
return errors.Join(validationErrors...)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func parsePolicyMappingsFile(data []byte) (*PolicyMappings, error) {
|
|
mappings := &policyMappingsFile{}
|
|
err := yaml.Unmarshal(data, mappings)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal policy mapping file: %w", err)
|
|
}
|
|
err = validateMappingsFile(mappings)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid policy mapping file: %w", err)
|
|
}
|
|
return expandMappingFile(mappings)
|
|
}
|
|
|
|
func LoadLocalMappings(configDir string) (*PolicyMappings, error) {
|
|
if configDir == "" {
|
|
return nil, nil
|
|
}
|
|
path := filepath.Join(configDir, MappingFilename)
|
|
mappingFile, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read local policy mapping file %s: %w", path, err)
|
|
}
|
|
return parsePolicyMappingsFile(mappingFile)
|
|
}
|
|
|
|
func LoadTUFMappings(tufClient tuf.Downloader, localTargetsDir string) (*PolicyMappings, error) {
|
|
if tufClient == nil {
|
|
return nil, fmt.Errorf("tuf client not set")
|
|
}
|
|
filename := MappingFilename
|
|
file, err := tufClient.DownloadTarget(filename, filepath.Join(localTargetsDir, filename))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to download policy mapping file %s: %w", filename, err)
|
|
}
|
|
return parsePolicyMappingsFile(file.Data)
|
|
}
|
|
|
|
func expandMappingFile(mappingFile *policyMappingsFile) (*PolicyMappings, error) {
|
|
policies := make(map[string]*PolicyMapping)
|
|
for _, policy := range mappingFile.Policies {
|
|
policies[policy.ID] = policy
|
|
}
|
|
|
|
var rules []*PolicyRule
|
|
for _, rule := range mappingFile.Rules {
|
|
patternRegex, err := regexp.Compile(rule.Pattern)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
platforms := make([]*v1.Platform, 0, len(rule.Platforms))
|
|
for _, platform := range rule.Platforms {
|
|
parsedPlatform, err := v1.ParsePlatform(platform)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse platform %s: %w", platform, err)
|
|
}
|
|
platforms = append(platforms, parsedPlatform)
|
|
}
|
|
|
|
rules = append(rules, &PolicyRule{
|
|
Pattern: patternRegex,
|
|
PolicyID: rule.PolicyID,
|
|
Replacement: rule.Replacement,
|
|
Platforms: platforms,
|
|
})
|
|
}
|
|
|
|
return &PolicyMappings{
|
|
Version: mappingFile.Version,
|
|
Kind: mappingFile.Kind,
|
|
Policies: policies,
|
|
Rules: rules,
|
|
}, nil
|
|
}
|