Add function for parsing DOI definition files (#172)
Add a Rego builtin called `attest.internals.parse_library_definition` for parsing the DOI definition files in https://github.com/docker-library/official-images/tree/master/library. This will allow us to verify DOI provenance fields against these files which are the source of truth for DOI images. This function just defers to https://github.com/docker-library/bashbrew/blob/master/manifest/rfc2822.go.
This commit is contained in:
107
policy/rego.go
107
policy/rego.go
@@ -1,12 +1,14 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker-library/bashbrew/manifest"
|
||||
"github.com/docker/attest/attestation"
|
||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
@@ -149,6 +151,12 @@ var attestDecl = &ast.Builtin{
|
||||
Nondeterministic: true,
|
||||
}
|
||||
|
||||
var internalParseLibraryDefinitionDecl = &ast.Builtin{
|
||||
Name: "attest.internals.parse_library_definition",
|
||||
Decl: types.NewFunction(types.Args(types.S), dynamicObj),
|
||||
Nondeterministic: false,
|
||||
}
|
||||
|
||||
func wrapFunctionResult(value *ast.Term, err error) (*ast.Term, error) {
|
||||
var terms [][2]*ast.Term
|
||||
if err != nil {
|
||||
@@ -174,28 +182,50 @@ func handleErrors2(f func(rCtx rego.BuiltinContext, a, b *ast.Term) (*ast.Term,
|
||||
|
||||
func RegoFunctions(regoOpts *RegoFnOpts) []*tester.Builtin {
|
||||
return []*tester.Builtin{
|
||||
{
|
||||
Decl: verifyDecl,
|
||||
Func: rego.Function2(
|
||||
®o.Function{
|
||||
Name: verifyDecl.Name,
|
||||
Decl: verifyDecl.Decl,
|
||||
Memoize: true,
|
||||
Nondeterministic: verifyDecl.Nondeterministic,
|
||||
},
|
||||
handleErrors2(regoOpts.verifyInTotoEnvelope)),
|
||||
},
|
||||
{
|
||||
Decl: attestDecl,
|
||||
Func: rego.Function1(
|
||||
®o.Function{
|
||||
Name: attestDecl.Name,
|
||||
Decl: attestDecl.Decl,
|
||||
Memoize: true,
|
||||
Nondeterministic: attestDecl.Nondeterministic,
|
||||
},
|
||||
handleErrors1(regoOpts.fetchInTotoAttestations)),
|
||||
},
|
||||
builtin2(verifyDecl, regoOpts.verifyInTotoEnvelope),
|
||||
builtin1(attestDecl, regoOpts.fetchInTotoAttestations),
|
||||
builtin1(internalParseLibraryDefinitionDecl, regoOpts.internalParseLibraryDefinition),
|
||||
}
|
||||
}
|
||||
|
||||
func builtin1(decl *ast.Builtin, f rego.Builtin1) *tester.Builtin {
|
||||
return &tester.Builtin{
|
||||
Decl: decl,
|
||||
Func: rego.Function1(
|
||||
®o.Function{
|
||||
Name: decl.Name,
|
||||
Decl: decl.Decl,
|
||||
Memoize: true,
|
||||
Nondeterministic: decl.Nondeterministic,
|
||||
},
|
||||
handleErrors1(f)),
|
||||
}
|
||||
}
|
||||
|
||||
func builtin2(decl *ast.Builtin, f rego.Builtin2) *tester.Builtin {
|
||||
return &tester.Builtin{
|
||||
Decl: decl,
|
||||
Func: rego.Function2(
|
||||
®o.Function{
|
||||
Name: decl.Name,
|
||||
Decl: decl.Decl,
|
||||
Memoize: true,
|
||||
Nondeterministic: decl.Nondeterministic,
|
||||
},
|
||||
handleErrors2(f)),
|
||||
}
|
||||
}
|
||||
|
||||
type RegoFnOpts struct {
|
||||
attestationResolver attestation.Resolver
|
||||
attestationVerifier attestation.Verifier
|
||||
}
|
||||
|
||||
// this is exported for testing here and in clients of the library.
|
||||
func NewRegoFunctionOptions(resolver attestation.Resolver, verifier attestation.Verifier) *RegoFnOpts {
|
||||
return &RegoFnOpts{
|
||||
attestationResolver: resolver,
|
||||
attestationVerifier: verifier,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,19 +259,6 @@ func (regoOpts *RegoFnOpts) fetchInTotoAttestations(rCtx rego.BuiltinContext, pr
|
||||
return set, nil
|
||||
}
|
||||
|
||||
type RegoFnOpts struct {
|
||||
attestationResolver attestation.Resolver
|
||||
attestationVerifier attestation.Verifier
|
||||
}
|
||||
|
||||
// this is exported for testing here and in clients of the library.
|
||||
func NewRegoFunctionOptions(resolver attestation.Resolver, verifier attestation.Verifier) *RegoFnOpts {
|
||||
return &RegoFnOpts{
|
||||
attestationResolver: resolver,
|
||||
attestationVerifier: verifier,
|
||||
}
|
||||
}
|
||||
|
||||
// because we don't control the signature here (blame rego)
|
||||
// nolint:gocritic
|
||||
func (regoOpts *RegoFnOpts) verifyInTotoEnvelope(rCtx rego.BuiltinContext, envTerm, optsTerm *ast.Term) (*ast.Term, error) {
|
||||
@@ -285,6 +302,26 @@ func (regoOpts *RegoFnOpts) verifyInTotoEnvelope(rCtx rego.BuiltinContext, envTe
|
||||
return ast.NewTerm(value), nil
|
||||
}
|
||||
|
||||
// because we don't control the signature here (blame rego)
|
||||
// nolint:gocritic
|
||||
func (regoOpts *RegoFnOpts) internalParseLibraryDefinition(_ rego.BuiltinContext, definitionTerm *ast.Term) (*ast.Term, error) {
|
||||
definitionStr, ok := definitionTerm.Value.(ast.String)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("predicateTypeTerm is not a string")
|
||||
}
|
||||
definition := string(definitionStr)
|
||||
defBuffer := bytes.NewBufferString(definition)
|
||||
parsed, err := manifest.Parse2822(defBuffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
value, err := ast.InterfaceToValue(parsed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ast.NewTerm(value), nil
|
||||
}
|
||||
|
||||
func loadYAML(path string, bs []byte) (interface{}, error) {
|
||||
var x interface{}
|
||||
bs, err := yaml.YAMLToJSON(bs)
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func TestPolicy(t *testing.T) {
|
||||
paths := []string{"testdata/policies/test"}
|
||||
paths := []string{"testdata/policies/test/fetch"}
|
||||
modules, store, err := tester.Load(paths, nil)
|
||||
require.NoError(t, err)
|
||||
resolver := &NullAttestationResolver{}
|
||||
@@ -35,6 +35,30 @@ func TestPolicy(t *testing.T) {
|
||||
assert.True(t, resolver.called)
|
||||
}
|
||||
|
||||
func TestPolicyDefParse(t *testing.T) {
|
||||
paths := []string{"testdata/policies/test/def_parse"}
|
||||
modules, store, err := tester.Load(paths, nil)
|
||||
require.NoError(t, err)
|
||||
resolver := &NullAttestationResolver{}
|
||||
|
||||
opts := NewRegoFunctionOptions(resolver, nil)
|
||||
ctx := context.Background()
|
||||
ch, err := tester.NewRunner().
|
||||
SetStore(store).
|
||||
AddCustomBuiltins(RegoFunctions(opts)).
|
||||
CapturePrintOutput(true).
|
||||
RaiseBuiltinErrors(true).
|
||||
EnableTracing(true).
|
||||
SetModules(modules).
|
||||
RunTests(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
results := buffer(ch)
|
||||
t.Log(string(results[0].Output))
|
||||
assert.Equalf(t, 1, len(results), "expected 1 results, got %d", len(results))
|
||||
assert.Truef(t, results[0].Pass(), "expected result 1 to pass, got %v", results[0].Location)
|
||||
}
|
||||
|
||||
func buffer[T any](ch chan T) []T {
|
||||
var out []T
|
||||
for v := range ch {
|
||||
|
||||
18
policy/testdata/policies/test/def_parse/def_parse_test.rego
vendored
Normal file
18
policy/testdata/policies/test/def_parse/def_parse_test.rego
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
package def_parse_test
|
||||
|
||||
import rego.v1
|
||||
|
||||
test_parse_library_definition if {
|
||||
def := `Maintainers: me <me@example.com> (@me)
|
||||
GitRepo: blah
|
||||
|
||||
Tags: 1, 2, 3
|
||||
GitCommit: fa105cb3c26c8f0e87d7dbb1bf5293691ac2f688
|
||||
File: Dockerfile.foo`
|
||||
result := attest.internals.parse_library_definition(def)
|
||||
definition := result.value
|
||||
definition.Entries[0].GitRepo == "blah"
|
||||
definition.Entries[0].GitCommit == "fa105cb3c26c8f0e87d7dbb1bf5293691ac2f688"
|
||||
definition.Entries[0].Tags == ["1", "2", "3"]
|
||||
definition.Entries[0].File == "Dockerfile.foo"
|
||||
}
|
||||
9
policy/testdata/policies/test/fetch/fetch_test.rego
vendored
Normal file
9
policy/testdata/policies/test/fetch/fetch_test.rego
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
package attest_test
|
||||
|
||||
import rego.v1
|
||||
|
||||
import data.attest
|
||||
|
||||
test_sucess if {
|
||||
attest.success
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package attest
|
||||
|
||||
import rego.v1
|
||||
|
||||
test_sucess if {
|
||||
success
|
||||
}
|
||||
Reference in New Issue
Block a user