diff --git a/policy/builtin_module.go b/policy/builtin_module.go new file mode 100644 index 000000000..c04364c53 --- /dev/null +++ b/policy/builtin_module.go @@ -0,0 +1,30 @@ +package policy + +import "github.com/open-policy-agent/opa/v1/ast" + +const builtinPolicyModuleFilename = "builtin/buildx_defaults.rego" + +const builtinPolicyModule = `package docker + +docker_github_builder(image, repo) if { + image.hasProvenance + some sig in image.signatures + valid_docker_github_builder_signature(sig, repo) +} + +valid_docker_github_builder_signature(sig, repo) if { + sig.kind == "docker-github-builder" + sig.type == "bundle-v0.3" + sig.signer.certificateIssuer == "CN=sigstore-intermediate,O=sigstore.dev" + sig.signer.issuer == "https://token.actions.githubusercontent.com" + sig.signer.sourceRepositoryURI == sprintf("https://github.com/%s", [repo]) + sig.signer.runnerEnvironment == "github-hosted" + count(sig.timestamps) > 0 +} +` + +func builtinPolicyModuleAST() (*ast.Module, error) { + return ast.ParseModuleWithOpts(builtinPolicyModuleFilename, builtinPolicyModule, ast.ParserOptions{ + RegoVersion: ast.RegoV1, + }) +} diff --git a/policy/tester.go b/policy/tester.go index 90ed59b1d..17477f339 100644 --- a/policy/tester.go +++ b/policy/tester.go @@ -195,6 +195,13 @@ func loadPolicyModules(root fs.StatFS, filename string) (map[string]*ast.Module, modules := map[string]*ast.Module{ filepath.ToSlash(policyFile): mod, } + builtinMod, err := builtinPolicyModuleAST() + if err != nil { + return nil, nil, errors.Wrapf(err, "parse builtin policy module %s", builtinPolicyModuleFilename) + } + if _, ok := modules[builtinPolicyModuleFilename]; !ok { + modules[builtinPolicyModuleFilename] = builtinMod + } files := []File{ { Filename: filepath.ToSlash(policyFile), diff --git a/policy/validate.go b/policy/validate.go index 5383939d3..7f3b1b73a 100644 --- a/policy/validate.go +++ b/policy/validate.go @@ -201,6 +201,7 @@ func (p *Policy) CheckPolicy(ctx context.Context, req *policysession.CheckPolicy opts = append(opts, f.impl(st)) } + opts = append(opts, rego.Module(builtinPolicyModuleFilename, builtinPolicyModule)) for _, file := range p.opt.Files { opts = append(opts, rego.Module(file.Filename, string(file.Data))) } diff --git a/tests/policy_test.go b/tests/policy_test.go index b477aefdb..4374d57ec 100644 --- a/tests/policy_test.go +++ b/tests/policy_test.go @@ -12,6 +12,7 @@ var policyTestTests = []func(t *testing.T, sb integration.Sandbox){ testPolicyTestRunFilter, testPolicyTestFailMissingInput, testPolicyTestNestedPath, + testPolicyTestDockerGitHubBuilder, } func testPolicyTestRunFilter(t *testing.T, sb integration.Sandbox) { @@ -182,3 +183,82 @@ test_allowlisted_repo if { require.NoError(t, err, string(out)) require.Contains(t, string(out), "test_allowlisted_repo: PASS") } + +func testPolicyTestDockerGitHubBuilder(t *testing.T, sb integration.Sandbox) { + skipNoCompatBuildKit(t, sb, ">= 0.26.0-0", "policy input requires BuildKit v0.26.0+") + dir := tmpdir( + t, + fstest.CreateFile("policy.rego", []byte(` +package docker + +default allow = false + +allow if docker_github_builder(input.image, "org/repo") + +decision := {"allow": allow} +`), 0600), + fstest.CreateFile("policy_test.rego", []byte(` +package docker + +test_docker_github_builder if { + result := data.docker.decision with input as { + "image": { + "hasProvenance": true, + "signatures": [{ + "kind": "docker-github-builder", + "type": "bundle-v0.3", + "signer": { + "certificateIssuer": "CN=sigstore-intermediate,O=sigstore.dev", + "issuer": "https://token.actions.githubusercontent.com", + "sourceRepositoryURI": "https://github.com/org/repo", + "runnerEnvironment": "github-hosted" + }, + "timestamps": [{ + "type": "tlog", + "uri": "https://example.com/tlog", + "timestamp": "2024-01-01T00:00:00Z" + }] + }] + } + } + result.allow +} + +test_docker_github_builder_denied if { + result := data.docker.decision with input as { + "image": { + "hasProvenance": true, + "signatures": [{ + "kind": "docker-github-builder", + "type": "bundle-v0.3", + "signer": { + "certificateIssuer": "CN=sigstore-intermediate,O=sigstore.dev", + "issuer": "https://token.actions.githubusercontent.com", + "sourceRepositoryURI": "https://github.com/other/repo", + "runnerEnvironment": "github-hosted" + }, + "timestamps": [{ + "type": "tlog", + "uri": "https://example.com/tlog", + "timestamp": "2024-01-01T00:00:00Z" + }] + }] + } + } + not result.allow +} +`), 0600), + ) + + cmd := buildxCmd(sb, withDir(dir), withArgs( + "policy", + "test", + "--filename", + "policy", + ".", + )) + out, err := cmd.CombinedOutput() + require.NoError(t, err, string(out)) + require.Contains(t, string(out), "test_docker_github_builder: PASS") + require.Contains(t, string(out), "test_docker_github_builder_denied: PASS") +}