Skip to content

Commit

Permalink
policy: add Agent.Validate() method
Browse files Browse the repository at this point in the history
Add a method to validate the policy rules without actually evaluating
them against a particular input. For OPA, this validation amounts to a
syntax check of the specified rules.

This is intended for use by the forthcoming policy management API
implementation.

Signed-off-by: Sergei Trofimov <[email protected]>
  • Loading branch information
setrofim committed Jul 19, 2023
1 parent 1cedbf1 commit d7cc2bf
Show file tree
Hide file tree
Showing 13 changed files with 153 additions and 5 deletions.
9 changes: 9 additions & 0 deletions policy/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,15 @@ func (o *Agent) Evaluate(
}
}

// Validate performs basic validation of the provided policy rules, returning
// an error if it fails. the nature of the validation performed is
// backend-specific, however it would typically amount to a syntax check.
// Successful validation does not guarantee that the policy will execute
// correctly againt actual inputs.
func (o *Agent) Validate(ctx context.Context, policyRules string) error {
return o.Backend.Validate(ctx, policyRules)
}

func (o *Agent) GetBackend() IBackend {
return o.Backend
}
Expand Down
1 change: 1 addition & 0 deletions policy/iagent.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ type IAgent interface {
evidence *proto.EvidenceContext,
endorsements []string,
) (*ear.Appraisal, error)
Validate(ctx context.Context, policyRules string) error
Close()
}
1 change: 1 addition & 0 deletions policy/ibackend.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ type IBackend interface {
evidence map[string]interface{},
endorsements []string,
) (map[string]interface{}, error)
Validate(ctx context.Context, policy string) error
Close()
}
14 changes: 14 additions & 0 deletions policy/mocks/mock_ibackend.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 14 additions & 1 deletion policy/opa.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (o *OPA) Evaluate(
rego := rego.New(
rego.Package("policy"),
rego.Module("opa.rego", preambleText),
rego.Module("policy.rego", string(policy)),
rego.Module("policy.rego", policy),
rego.Input(input),
rego.Query("outcome"),
rego.Dump(log.Writer()),
Expand All @@ -78,6 +78,19 @@ func (o *OPA) Evaluate(
return resultUpdate, nil
}

func (o *OPA) Validate(ctx context.Context, policy string) error {
rego := rego.New(
rego.Package("policy"),
rego.Module("opa.rego", preambleText),
rego.Module("policy.rego", policy),
rego.Query("outcome"),
rego.Dump(log.NamedWriter("opa", log.DebugLevel)),
)

_, err := rego.Compile(ctx)
return err
}

func (o *OPA) Close() {
}

Expand Down
47 changes: 43 additions & 4 deletions policy/opa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type TestResult struct {
Outcome *ear.AttestationResult `json:"outcome"`
}

type TestVector struct {
type EvaluateTestVector struct {
Title string `json:"title"`
Scheme string `json:"scheme"`
ResultPath string `json:"result"`
Expand All @@ -30,7 +30,7 @@ type TestVector struct {
Expected TestResult `json:"expected"`
}

func (o TestVector) Run(t *testing.T, ctx context.Context, pa *OPA) {
func (o EvaluateTestVector) Run(t *testing.T, ctx context.Context, pa *OPA) {
resultMap, err := jsonFileToResultMap(o.ResultPath)
require.NoError(t, err)

Expand All @@ -54,6 +54,24 @@ func (o TestVector) Run(t *testing.T, ctx context.Context, pa *OPA) {
assert.Equal(t, expected, res)
}

type ValidateTestVector struct {
Title string `json:"title"`
PolicyPath string `json:"policy"`
Error string `json:"error"`
}

func (o ValidateTestVector) Run(t *testing.T, ctx context.Context, pa *OPA) {
policy, err := os.ReadFile(o.PolicyPath)
require.NoError(t, err)

err = pa.Validate(ctx, string(policy))
if o.Error == "" {
assert.NoError(t, err)
} else {
assert.EqualError(t, err, o.Error)
}
}

func Test_OPA_GetName(t *testing.T) {
pa, err := NewOPA(nil)
require.NoError(t, err)
Expand All @@ -63,7 +81,7 @@ func Test_OPA_GetName(t *testing.T) {
}

func Test_OPA_Evaluate(t *testing.T) {
bytes, err := os.ReadFile("test/vectors.json")
bytes, err := os.ReadFile("test/evaluate-vectors.json")
require.NoError(t, err)

ctx := context.Background()
Expand All @@ -72,7 +90,7 @@ func Test_OPA_Evaluate(t *testing.T) {
require.NoError(t, err)
defer pa.Close()

var vectors []TestVector
var vectors []EvaluateTestVector

err = json.Unmarshal(bytes, &vectors)
require.NoError(t, err)
Expand All @@ -94,6 +112,27 @@ func Test_OPA_Evaluate(t *testing.T) {

}

func Test_OPA_Validate(t *testing.T) {
bytes, err := os.ReadFile("test/validate-vectors.json")
require.NoError(t, err)

ctx := context.Background()

pa, err := NewOPA(nil)
require.NoError(t, err)
defer pa.Close()

var vectors []ValidateTestVector

err = json.Unmarshal(bytes, &vectors)
require.NoError(t, err)

for _, v := range vectors {
fmt.Printf("running %q\n", v.Title)
v.Run(t, ctx, pa)
}
}

func jsonFileToMap(path string) (map[string]interface{}, error) {
bytes, err := os.ReadFile(path)
if err != nil {
Expand Down
File renamed without changes.
Empty file added policy/test/policies/null.rego
Empty file.
3 changes: 3 additions & 0 deletions policy/test/policies/simple.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package policy

executables = APPROVED_RT
3 changes: 3 additions & 0 deletions policy/test/policies/undefined.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package policy

executables = ok
37 changes: 37 additions & 0 deletions policy/test/validate-vectors.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[
{
"title": "malformed policy",
"policy": "test/policies/malformed.rego",
"error": "1 error occurred: 1 error occurred: policy.rego:1: rego_parse_error: unexpected : token\n\tbad_rule:;;\n\t ^"
},
{
"title": "bad policy",
"policy": "test/policies/bad.rego",
"error": "1 error occurred: policy.rego:6: rego_unsafe_var_error: var y is unsafe"
},
{
"title": "empty policy",
"policy": "test/policies/empty.rego",
"error": null
},
{
"title": "empty string",
"policy": "test/policies/null.rego",
"error": "1 error occurred: policy.rego:0: rego_parse_error: empty module"
},
{
"title": "undefined variable",
"policy": "test/policies/undefined.rego",
"error": "1 error occurred: policy.rego:3: rego_unsafe_var_error: var ok is unsafe"
},
{
"title": "valid simple",
"policy": "test/policies/simple.rego",
"error": null
},
{
"title": "valid complex",
"policy": "test/policies/sw-up-to-dateness.rego",
"error": null
}
]
14 changes: 14 additions & 0 deletions vts/policymanager/mocks/iagent.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions vts/policymanager/mocks/ibackend.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit d7cc2bf

Please sign in to comment.