diff --git a/pkg/policy/policy_test.go b/pkg/policy/policy_test.go index 239e637..a6529b6 100644 --- a/pkg/policy/policy_test.go +++ b/pkg/policy/policy_test.go @@ -42,7 +42,7 @@ func TestRegoEvaluator_Evaluate(t *testing.T) { } testCases := []struct { - repo string + policyPath string expectSuccess bool isCanonical bool resolver attestation.Resolver @@ -50,19 +50,19 @@ func TestRegoEvaluator_Evaluate(t *testing.T) { policyID string resolveErrorStr string }{ - {repo: "testdata/policies/allow", expectSuccess: true, resolver: defaultResolver}, - {repo: "testdata/policies/allow", expectSuccess: true, resolver: defaultResolver, policyID: "docker-official-images"}, - {repo: "testdata/policies/allow", resolver: defaultResolver, policyID: "non-existent-policy-id", resolveErrorStr: resolveErrorStr}, - {repo: "testdata/policies/deny", resolver: defaultResolver}, - {repo: "testdata/policies/verify-sig", expectSuccess: true, resolver: defaultResolver}, - {repo: "testdata/policies/wrong-key", resolver: defaultResolver}, - {repo: "testdata/policies/allow-canonical", expectSuccess: true, isCanonical: true, resolver: defaultResolver}, - {repo: "testdata/policies/allow-canonical", resolver: defaultResolver}, - {repo: "testdata/policies/no-rego", resolver: defaultResolver, resolveErrorStr: "no policy file found in policy mapping"}, + {policyPath: "testdata/policies/allow", expectSuccess: true, resolver: defaultResolver}, + {policyPath: "testdata/policies/allow", expectSuccess: true, resolver: defaultResolver, policyID: "docker-official-images"}, + {policyPath: "testdata/policies/allow", resolver: defaultResolver, policyID: "non-existent-policy-id", resolveErrorStr: resolveErrorStr}, + {policyPath: "testdata/policies/deny", resolver: defaultResolver}, + {policyPath: "testdata/policies/verify-sig", expectSuccess: true, resolver: defaultResolver}, + {policyPath: "testdata/policies/wrong-key", resolver: defaultResolver}, + {policyPath: "testdata/policies/allow-canonical", expectSuccess: true, isCanonical: true, resolver: defaultResolver}, + {policyPath: "testdata/policies/allow-canonical", resolver: defaultResolver}, + {policyPath: "testdata/policies/no-rego", resolver: defaultResolver, resolveErrorStr: "no policy file found in policy mapping"}, } for _, tc := range testCases { - t.Run(tc.repo, func(t *testing.T) { + t.Run(tc.policyPath, func(t *testing.T) { input := &policy.Input{ Digest: "sha256:test-digest", PURL: "test-purl", @@ -75,7 +75,7 @@ func TestRegoEvaluator_Evaluate(t *testing.T) { tc.opts = &policy.Options{ LocalTargetsDir: test.CreateTempDir(t, "", "tuf-targets"), PolicyID: tc.policyID, - LocalPolicyDir: tc.repo, + LocalPolicyDir: tc.policyPath, DisableTUF: true, } } diff --git a/pkg/policy/resolver_test.go b/pkg/policy/resolver_test.go new file mode 100644 index 0000000..026bd39 --- /dev/null +++ b/pkg/policy/resolver_test.go @@ -0,0 +1,65 @@ +package policy_test + +import ( + "context" + "testing" + + "github.com/docker/attest/internal/test" + "github.com/docker/attest/pkg/policy" + "github.com/docker/attest/pkg/tuf" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestResolvePolicy(t *testing.T) { + localPolicyPath := "testdata/policies/allow" + tufPolicyPath := "testdata/policies/allow-canonical" + noLocalPolicyPath := "testdata/policies/no-policy" + testPolicyID := "docker-official-images" + testImageName := "localhost:5001/test/repo:tag" + + testCases := []struct { + name string + policyPath string + policyID string + localOverridesTUF bool // if a policy is provided locally, it should override TUF + DisableTUF bool + }{ + {name: "resolve by id (TUF only)", policyID: testPolicyID, DisableTUF: false}, + {name: "resolve by id (local mapping, TUF policy)", policyPath: noLocalPolicyPath, policyID: testPolicyID, DisableTUF: false}, + {name: "resolve by id (local mapping, local policy, no TUF)", policyPath: localPolicyPath, policyID: testPolicyID, DisableTUF: true}, + {name: "resolve by id (local mapping, local policy)", policyPath: localPolicyPath, policyID: testPolicyID, DisableTUF: false, localOverridesTUF: true}, + {name: "resolve by match (TUF only)", DisableTUF: false}, + {name: "resolve by match (local mapping, TUF policy)", policyPath: noLocalPolicyPath, DisableTUF: false}, + {name: "resolve by match (local mapping, local policy, no TUF)", policyPath: localPolicyPath, DisableTUF: true}, + {name: "resolve by match (local mapping, local policy)", policyPath: localPolicyPath, DisableTUF: false, localOverridesTUF: true}, + } + + var tufClient tuf.Downloader + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + opts := &policy.Options{} + tempDir := test.CreateTempDir(t, "", "tuf-dest") + if !tc.DisableTUF { + tufClient = tuf.NewMockTufClient(tufPolicyPath) + } + if tc.policyID != "" { + opts.PolicyID = tc.policyID + } + if tc.policyPath != "" { + opts.LocalPolicyDir = tc.policyPath + } + opts.DisableTUF = tc.DisableTUF + opts.LocalTargetsDir = tempDir + resolver := policy.NewResolver(tufClient, opts) + policy, err := resolver.ResolvePolicy(context.Background(), testImageName) + require.NoError(t, err) + assert.NotNil(t, policy) + if tc.DisableTUF || tc.localOverridesTUF { + assert.Contains(t, policy.URI, localPolicyPath) + } else { + assert.Contains(t, policy.URI, tufPolicyPath) + } + }) + } +} diff --git a/pkg/policy/testdata/policies/no-policy/mapping.yaml b/pkg/policy/testdata/policies/no-policy/mapping.yaml new file mode 100644 index 0000000..6da334a --- /dev/null +++ b/pkg/policy/testdata/policies/no-policy/mapping.yaml @@ -0,0 +1,10 @@ +# map repos to policies +version: v1 +kind: policy-mapping +rules: + - pattern: "^docker[.]io/library/(.*)$" + policy-id: docker-official-images + - pattern: ^localhost:5001/(.*)$ + rewrite: docker.io/library/$1 + - pattern: ^registry[.]local:5000/(.*)$ + rewrite: docker.io/library/$1 diff --git a/pkg/tuf/mock.go b/pkg/tuf/mock.go index ca742a8..f024535 100644 --- a/pkg/tuf/mock.go +++ b/pkg/tuf/mock.go @@ -1,5 +1,42 @@ package tuf +import ( + "io" + "os" + "path/filepath" + + "github.com/docker/attest/internal/util" +) + +type MockTufClient struct { + srcPath string +} + +func NewMockTufClient(srcPath string) *MockTufClient { + if srcPath == "" { + panic("srcPath must be set") + } + return &MockTufClient{ + srcPath: srcPath, + } +} + +func (dc *MockTufClient) DownloadTarget(target string, _ string) (file *TargetFile, err error) { + targetPath := filepath.Join(dc.srcPath, target) + src, err := os.Open(targetPath) + if err != nil { + return nil, err + } + defer src.Close() + + b, err := io.ReadAll(src) + if err != nil { + return nil, err + } + + return &TargetFile{TargetURI: targetPath, Data: b, Digest: util.SHA256Hex(b)}, nil +} + type MockVersionChecker struct { err error }