Add platform filtering support to mapping.yml (#167)
* chore!: rename package config -> mapping * feat: add platform filtering support to mapping.yml
This commit is contained in:
138
mapping/mapping.go
Normal file
138
mapping/mapping.go
Normal file
@@ -0,0 +1,138 @@
|
||||
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
|
||||
}
|
||||
82
mapping/mapping_test.go
Normal file
82
mapping/mapping_test.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func newMapping() *policyMappingsFile {
|
||||
return &policyMappingsFile{
|
||||
Version: "v1",
|
||||
Kind: "policy-mapping",
|
||||
Policies: []*PolicyMapping{
|
||||
{
|
||||
ID: "docker-official-images",
|
||||
Files: []PolicyMappingFile{
|
||||
{
|
||||
Path: "docker.io/library/alpine",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Rules: []*policyRuleFile{
|
||||
{
|
||||
Pattern: "docker.io/library/alpine",
|
||||
PolicyID: "docker-official-images",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestMappingsFileValidation(t *testing.T) {
|
||||
mappings := newMapping()
|
||||
err := validateMappingsFile(mappings)
|
||||
require.NoError(t, err)
|
||||
|
||||
mappings = newMapping()
|
||||
mappings.Kind = "not-policy-mapping"
|
||||
err = validateMappingsFile(mappings)
|
||||
require.ErrorContains(t, err, "file is not of kind policy-mapping: not-policy-mapping")
|
||||
|
||||
mappings = newMapping()
|
||||
mappings.Version = "v2"
|
||||
err = validateMappingsFile(mappings)
|
||||
require.ErrorContains(t, err, "unsupported policy mapping file version: v2")
|
||||
|
||||
mappings = newMapping()
|
||||
mappings.Rules[0].Pattern = ""
|
||||
err = validateMappingsFile(mappings)
|
||||
require.ErrorContains(t, err, "rule missing pattern")
|
||||
|
||||
mappings = newMapping()
|
||||
mappings.Rules[0].PolicyID = ""
|
||||
err = validateMappingsFile(mappings)
|
||||
require.ErrorContains(t, err, "rule must have policy-id or replacement")
|
||||
|
||||
mappings = newMapping()
|
||||
mappings.Rules[0].PolicyID = "docker-official-images"
|
||||
mappings.Rules[0].Replacement = "docker.io/library/alpine"
|
||||
err = validateMappingsFile(mappings)
|
||||
require.ErrorContains(t, err, "rule cannot have both policy-id and replacement")
|
||||
|
||||
mappings = newMapping()
|
||||
mappings.Policies[0].ID = ""
|
||||
err = validateMappingsFile(mappings)
|
||||
require.ErrorContains(t, err, "policy missing id")
|
||||
|
||||
mappings = newMapping()
|
||||
mappings.Policies[0].Files = nil
|
||||
err = validateMappingsFile(mappings)
|
||||
require.ErrorContains(t, err, "policy missing files")
|
||||
|
||||
mappings = newMapping()
|
||||
mappings.Policies[0].Files[0].Path = ""
|
||||
err = validateMappingsFile(mappings)
|
||||
require.ErrorContains(t, err, "file missing path")
|
||||
|
||||
// multiple errors
|
||||
mappings.Policies[0].ID = ""
|
||||
err = validateMappingsFile(mappings)
|
||||
require.ErrorContains(t, err, "policy missing id: \nfile missing path: {}")
|
||||
}
|
||||
80
mapping/match.go
Normal file
80
mapping/match.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
)
|
||||
|
||||
type matchType string
|
||||
|
||||
const (
|
||||
MatchTypePolicy matchType = "policy"
|
||||
MatchTypeMatchNoPolicy matchType = "match_no_policy"
|
||||
MatchTypeNoMatch matchType = "no_match"
|
||||
)
|
||||
|
||||
type PolicyMatch struct {
|
||||
MatchType matchType
|
||||
Policy *PolicyMapping
|
||||
Rule *PolicyRule
|
||||
MatchedName string
|
||||
}
|
||||
|
||||
func (mappings *PolicyMappings) FindPolicyMatch(imageName string, platform *v1.Platform) (*PolicyMatch, error) {
|
||||
if mappings == nil {
|
||||
return &PolicyMatch{MatchType: MatchTypeNoMatch, MatchedName: imageName}, nil
|
||||
}
|
||||
return mappings.findPolicyMatchImpl(imageName, platform, make(map[*PolicyRule]bool))
|
||||
}
|
||||
|
||||
func (mappings *PolicyMappings) findPolicyMatchImpl(imageName string, platform *v1.Platform, matched map[*PolicyRule]bool) (*PolicyMatch, error) {
|
||||
for _, rule := range mappings.Rules {
|
||||
if !rule.matchesPlatform(platform) {
|
||||
continue
|
||||
}
|
||||
if rule.Pattern.MatchString(imageName) {
|
||||
switch {
|
||||
case rule.PolicyID == "" && rule.Replacement == "":
|
||||
return nil, fmt.Errorf("rule %s has neither policy-id nor rewrite", rule.Pattern)
|
||||
case rule.PolicyID != "" && rule.Replacement != "":
|
||||
return nil, fmt.Errorf("rule %s has both policy-id and rewrite", rule.Pattern)
|
||||
case rule.PolicyID != "":
|
||||
policy := mappings.Policies[rule.PolicyID]
|
||||
if policy != nil {
|
||||
return &PolicyMatch{
|
||||
MatchType: MatchTypePolicy,
|
||||
Policy: policy,
|
||||
Rule: rule,
|
||||
MatchedName: imageName,
|
||||
}, nil
|
||||
}
|
||||
return &PolicyMatch{
|
||||
MatchType: MatchTypeMatchNoPolicy,
|
||||
Rule: rule,
|
||||
MatchedName: imageName,
|
||||
}, nil
|
||||
case rule.Replacement != "":
|
||||
if matched[rule] {
|
||||
return nil, fmt.Errorf("rewrite loop detected")
|
||||
}
|
||||
matched[rule] = true
|
||||
imageName = rule.Pattern.ReplaceAllString(imageName, rule.Replacement)
|
||||
return mappings.findPolicyMatchImpl(imageName, platform, matched)
|
||||
}
|
||||
}
|
||||
}
|
||||
return &PolicyMatch{MatchType: MatchTypeNoMatch}, nil
|
||||
}
|
||||
|
||||
func (rule *PolicyRule) matchesPlatform(platform *v1.Platform) bool {
|
||||
if len(rule.Platforms) == 0 {
|
||||
return true
|
||||
}
|
||||
for i := range rule.Platforms {
|
||||
if rule.Platforms[i].Equals(*platform) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
200
mapping/match_test.go
Normal file
200
mapping/match_test.go
Normal file
@@ -0,0 +1,200 @@
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFindPolicyMatch(t *testing.T) {
|
||||
defaultPlatform, err := v1.ParsePlatform("linux/amd64")
|
||||
require.NoError(t, err)
|
||||
testCases := []struct {
|
||||
name string
|
||||
imageName string
|
||||
mappingDir string
|
||||
expectError bool
|
||||
expectLoadingError bool
|
||||
expectedMatchType matchType
|
||||
expectedPolicyID string
|
||||
expectedImageName string
|
||||
platform string
|
||||
}{
|
||||
{
|
||||
name: "alpine",
|
||||
mappingDir: "doi",
|
||||
imageName: "docker.io/library/alpine",
|
||||
|
||||
expectedMatchType: MatchTypePolicy,
|
||||
expectedPolicyID: "docker-official-images",
|
||||
expectedImageName: "docker.io/library/alpine",
|
||||
},
|
||||
{
|
||||
name: "no match",
|
||||
mappingDir: "doi",
|
||||
imageName: "docker.io/something/else",
|
||||
|
||||
expectedMatchType: MatchTypeNoMatch,
|
||||
},
|
||||
{
|
||||
name: "match, no policy",
|
||||
mappingDir: "local",
|
||||
imageName: "docker.io/library/alpine",
|
||||
|
||||
expectedMatchType: MatchTypeMatchNoPolicy,
|
||||
expectedImageName: "docker.io/library/alpine",
|
||||
},
|
||||
{
|
||||
name: "simple rewrite",
|
||||
mappingDir: "simple-rewrite",
|
||||
imageName: "mycoolmirror.org/library/alpine",
|
||||
|
||||
expectedMatchType: MatchTypePolicy,
|
||||
expectedPolicyID: "docker-official-images",
|
||||
expectedImageName: "docker.io/library/alpine",
|
||||
},
|
||||
{
|
||||
name: "rewrite no match",
|
||||
mappingDir: "rewrite-to-no-match",
|
||||
imageName: "mycoolmirror.org/library/alpine",
|
||||
|
||||
expectedMatchType: MatchTypeNoMatch,
|
||||
},
|
||||
{
|
||||
name: "rewrite to match, no policy",
|
||||
mappingDir: "rewrite-to-local",
|
||||
imageName: "mycoolmirror.org/library/alpine",
|
||||
|
||||
expectedMatchType: MatchTypeMatchNoPolicy,
|
||||
expectedImageName: "docker.io/library/alpine",
|
||||
},
|
||||
{
|
||||
name: "multiple rewrites",
|
||||
mappingDir: "rewrite-multiple",
|
||||
imageName: "myevencoolermirror.org/library/alpine",
|
||||
|
||||
expectedMatchType: MatchTypePolicy,
|
||||
expectedPolicyID: "docker-official-images",
|
||||
expectedImageName: "docker.io/library/alpine",
|
||||
},
|
||||
{
|
||||
name: "rewrite loop",
|
||||
mappingDir: "rewrite-loop",
|
||||
imageName: "yin/alpine",
|
||||
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "alpine with platform",
|
||||
mappingDir: "doi",
|
||||
imageName: "docker.io/library/alpine",
|
||||
platform: "linux/amd64",
|
||||
expectedMatchType: MatchTypePolicy,
|
||||
expectedPolicyID: "docker-official-images",
|
||||
expectedImageName: "docker.io/library/alpine",
|
||||
},
|
||||
{
|
||||
name: "alpine with platform",
|
||||
mappingDir: "doi-platform",
|
||||
imageName: "docker.io/library/alpine",
|
||||
platform: "linux/amd64",
|
||||
expectedMatchType: MatchTypePolicy,
|
||||
expectedPolicyID: "docker-official-images",
|
||||
expectedImageName: "docker.io/library/alpine",
|
||||
},
|
||||
{
|
||||
name: "alpine with no matching platform",
|
||||
mappingDir: "doi-platform",
|
||||
imageName: "docker.io/library/alpine",
|
||||
platform: "linux/arm64",
|
||||
expectedMatchType: MatchTypeNoMatch,
|
||||
expectedPolicyID: "docker-official-images",
|
||||
},
|
||||
{
|
||||
name: "alpine with platform",
|
||||
mappingDir: "doi-platform",
|
||||
imageName: "docker.io/library/alpine",
|
||||
platform: "linux/amd64",
|
||||
expectedMatchType: MatchTypePolicy,
|
||||
expectedPolicyID: "docker-official-images",
|
||||
expectedImageName: "docker.io/library/alpine",
|
||||
},
|
||||
{
|
||||
name: "alpine with invalid platform in mapping",
|
||||
mappingDir: "doi-platform-broken",
|
||||
imageName: "docker.io/library/alpine",
|
||||
platform: "linux/amd64",
|
||||
expectLoadingError: true,
|
||||
},
|
||||
{
|
||||
name: "firefox with > 1 platforms in policy",
|
||||
mappingDir: "doi-platform",
|
||||
imageName: "docker.io/mozilla/firefox",
|
||||
platform: "linux/arm64",
|
||||
expectedMatchType: MatchTypePolicy,
|
||||
expectedPolicyID: "docker-official-images",
|
||||
expectedImageName: "docker.io/mozilla/firefox",
|
||||
},
|
||||
{
|
||||
name: "firefox with > 1 platforms in policy (no match)",
|
||||
mappingDir: "doi-platform",
|
||||
imageName: "docker.io/mozilla/firefox",
|
||||
platform: "macOs/arm64",
|
||||
expectedMatchType: MatchTypeNoMatch,
|
||||
expectedPolicyID: "docker-official-images",
|
||||
},
|
||||
{
|
||||
name: "rewrite and platform",
|
||||
mappingDir: "doi-platform",
|
||||
imageName: "mycoolmirror.org/library/alpine",
|
||||
platform: "linux/amd64",
|
||||
expectedMatchType: MatchTypePolicy,
|
||||
expectedPolicyID: "docker-official-images",
|
||||
expectedImageName: "docker.io/library/alpine",
|
||||
},
|
||||
{
|
||||
name: "rewrite and platform mismatch",
|
||||
mappingDir: "doi-platform",
|
||||
imageName: "mycoolmirror.org/library/alpine",
|
||||
platform: "macOs/amd64",
|
||||
expectedMatchType: MatchTypeNoMatch,
|
||||
expectedPolicyID: "docker-official-images",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
mappings, err := LoadLocalMappings(filepath.Join("testdata", "mappings", tc.mappingDir))
|
||||
if tc.expectLoadingError {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
platform := defaultPlatform
|
||||
if tc.platform != "" {
|
||||
platform, err = v1.ParsePlatform(tc.platform)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
match, err := mappings.FindPolicyMatch(tc.imageName, platform)
|
||||
if tc.expectError {
|
||||
require.Error(t, err)
|
||||
// TODO: check error matches expected error message
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedMatchType, match.MatchType)
|
||||
if match.MatchType == MatchTypePolicy {
|
||||
if assert.NotNil(t, match.Policy) {
|
||||
assert.Equal(t, tc.expectedPolicyID, match.Policy.ID)
|
||||
}
|
||||
}
|
||||
if match.MatchType == MatchTypeMatchNoPolicy || match.MatchType == MatchTypePolicy {
|
||||
assert.Equal(t, tc.expectedImageName, match.MatchedName)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
11
mapping/testdata/mappings/doi-platform-broken/mapping.yaml
vendored
Normal file
11
mapping/testdata/mappings/doi-platform-broken/mapping.yaml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
version: v1
|
||||
kind: policy-mapping
|
||||
policies:
|
||||
- id: docker-official-images
|
||||
description: Docker Official Images
|
||||
files:
|
||||
- path: doi/policy.rego
|
||||
rules:
|
||||
- pattern: "^docker[.]io/library/(.*)$"
|
||||
platforms: ["linux/amd64/broken/platform/spec/1.0:foobar"]
|
||||
policy-id: docker-official-images
|
||||
17
mapping/testdata/mappings/doi-platform/mapping.yaml
vendored
Normal file
17
mapping/testdata/mappings/doi-platform/mapping.yaml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
version: v1
|
||||
kind: policy-mapping
|
||||
policies:
|
||||
- id: docker-official-images
|
||||
description: Docker Official Images
|
||||
files:
|
||||
- path: doi/policy.rego
|
||||
rules:
|
||||
- pattern: "^docker[.]io/library/(.*)$"
|
||||
platforms: ["linux/amd64"]
|
||||
policy-id: docker-official-images
|
||||
- pattern: "^docker.io/mozilla/(.*)$"
|
||||
platforms: ["linux/amd64", "linux/arm64"]
|
||||
policy-id: docker-official-images
|
||||
- pattern: "^mycoolmirror[.]org/library/(.*)$"
|
||||
platforms: ["linux/amd64"]
|
||||
rewrite: "docker.io/library/$1"
|
||||
10
mapping/testdata/mappings/doi/mapping.yaml
vendored
Normal file
10
mapping/testdata/mappings/doi/mapping.yaml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
version: v1
|
||||
kind: policy-mapping
|
||||
policies:
|
||||
- id: docker-official-images
|
||||
description: Docker Official Images
|
||||
files:
|
||||
- path: doi/policy.rego
|
||||
rules:
|
||||
- pattern: "^docker[.]io/library/(.*)$"
|
||||
policy-id: docker-official-images
|
||||
10
mapping/testdata/mappings/local/mapping.yaml
vendored
Normal file
10
mapping/testdata/mappings/local/mapping.yaml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
version: v1
|
||||
kind: policy-mapping
|
||||
policies:
|
||||
- id: local-policy
|
||||
description: Local Policy
|
||||
files:
|
||||
- path: local-policy.rego
|
||||
rules:
|
||||
- pattern: "^docker[.]io/library/(.*)$"
|
||||
policy-id: docker-official-images # note this policy does not exist in this file
|
||||
13
mapping/testdata/mappings/rewrite-invalid/mapping.yaml
vendored
Normal file
13
mapping/testdata/mappings/rewrite-invalid/mapping.yaml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
version: v1
|
||||
kind: policy-mapping
|
||||
policies:
|
||||
- id: docker-official-images
|
||||
description: Docker Official Images
|
||||
files:
|
||||
- path: doi/policy.rego
|
||||
rules:
|
||||
- pattern: "^docker[.]io/library/(.*)$"
|
||||
policy-id: docker-official-images
|
||||
- pattern: "^mycoolmirror[.]org/library/(.*)$"
|
||||
rewrite: "docker.io/library/$1"
|
||||
policy-id: docker-official-images # invalid to specify both rewrite and policy-id
|
||||
14
mapping/testdata/mappings/rewrite-loop/mapping.yaml
vendored
Normal file
14
mapping/testdata/mappings/rewrite-loop/mapping.yaml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
version: v1
|
||||
kind: policy-mapping
|
||||
policies:
|
||||
- id: docker-official-images
|
||||
description: Docker Official Images
|
||||
files:
|
||||
- path: doi/policy.rego
|
||||
rules:
|
||||
- pattern: "^docker[.]io/library/(.*)$"
|
||||
policy-id: docker-official-images
|
||||
- pattern: "^yin/(.*)$"
|
||||
rewrite: "yang/$1"
|
||||
- pattern: "^yang/(.*)$"
|
||||
rewrite: "yin/$1"
|
||||
14
mapping/testdata/mappings/rewrite-multiple/mapping.yaml
vendored
Normal file
14
mapping/testdata/mappings/rewrite-multiple/mapping.yaml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
version: v1
|
||||
kind: policy-mapping
|
||||
policies:
|
||||
- id: docker-official-images
|
||||
description: Docker Official Images
|
||||
files:
|
||||
- path: doi/policy.rego
|
||||
rules:
|
||||
- pattern: "^docker[.]io/library/(.*)$"
|
||||
policy-id: docker-official-images
|
||||
- pattern: "^mycoolmirror[.]org/library/(.*)$"
|
||||
rewrite: "docker.io/library/$1"
|
||||
- pattern: "^myevencoolermirror[.]org/library/(.*)$"
|
||||
rewrite: "mycoolmirror.org/library/$1"
|
||||
12
mapping/testdata/mappings/rewrite-to-local/mapping.yaml
vendored
Normal file
12
mapping/testdata/mappings/rewrite-to-local/mapping.yaml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
version: v1
|
||||
kind: policy-mapping
|
||||
policies:
|
||||
- id: local-policy
|
||||
description: Local Policy
|
||||
files:
|
||||
- path: local-policy.rego
|
||||
rules:
|
||||
- pattern: "^docker[.]io/library/(.*)$"
|
||||
policy-id: docker-official-images # note this policy does not exist in this file
|
||||
- pattern: "^mycoolmirror[.]org/library/(.*)$"
|
||||
rewrite: "docker.io/library/$1"
|
||||
12
mapping/testdata/mappings/rewrite-to-no-match/mapping.yaml
vendored
Normal file
12
mapping/testdata/mappings/rewrite-to-no-match/mapping.yaml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
version: v1
|
||||
kind: policy-mapping
|
||||
policies:
|
||||
- id: docker-official-images
|
||||
description: Docker Official Images
|
||||
files:
|
||||
- path: doi/policy.rego
|
||||
rules:
|
||||
- pattern: "^docker[.]io/library/(.*)$"
|
||||
policy-id: docker-official-images
|
||||
- pattern: "^mycoolmirror[.]org/library/(.*)$"
|
||||
rewrite: "badredirect.org/$1" # no matching rule for this rewrite
|
||||
12
mapping/testdata/mappings/simple-rewrite/mapping.yaml
vendored
Normal file
12
mapping/testdata/mappings/simple-rewrite/mapping.yaml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
version: v1
|
||||
kind: policy-mapping
|
||||
policies:
|
||||
- id: docker-official-images
|
||||
description: Docker Official Images
|
||||
files:
|
||||
- path: doi/policy.rego
|
||||
rules:
|
||||
- pattern: "^docker[.]io/library/(.*)$"
|
||||
policy-id: docker-official-images
|
||||
- pattern: "^mycoolmirror[.]org/library/(.*)$"
|
||||
rewrite: "docker.io/library/$1"
|
||||
58
mapping/types.go
Normal file
58
mapping/types.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
)
|
||||
|
||||
type policyMappingsFile struct {
|
||||
Version string `json:"version"`
|
||||
Kind string `json:"kind"`
|
||||
Policies []*PolicyMapping `json:"policies"`
|
||||
Rules []*policyRuleFile `json:"rules"`
|
||||
}
|
||||
|
||||
type policyRuleFile struct {
|
||||
Pattern string `json:"pattern"`
|
||||
Platforms []string `json:"platforms"`
|
||||
PolicyID string `json:"policy-id"`
|
||||
Replacement string `json:"rewrite"`
|
||||
}
|
||||
|
||||
type PolicyMappings struct {
|
||||
Version string
|
||||
Kind string
|
||||
Policies map[string]*PolicyMapping
|
||||
Rules []*PolicyRule
|
||||
}
|
||||
|
||||
type AttestationStyle string
|
||||
|
||||
const (
|
||||
AttestationStyleAttached AttestationStyle = "attached"
|
||||
AttestationStyleReferrers AttestationStyle = "referrers"
|
||||
)
|
||||
|
||||
type PolicyMapping struct {
|
||||
ID string `json:"id"`
|
||||
Description string `json:"description"`
|
||||
Files []PolicyMappingFile `json:"files"`
|
||||
Attestations *AttestationConfig `json:"attestations"`
|
||||
}
|
||||
|
||||
type AttestationConfig struct {
|
||||
Style AttestationStyle `json:"style"`
|
||||
Repo string `json:"repo"`
|
||||
}
|
||||
|
||||
type PolicyMappingFile struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
type PolicyRule struct {
|
||||
Pattern *regexp.Regexp
|
||||
PolicyID string
|
||||
Replacement string
|
||||
Platforms []*v1.Platform
|
||||
}
|
||||
Reference in New Issue
Block a user