Skip to content

Commit

Permalink
engine: Refactor to allow for Rego version to be specified
Browse files Browse the repository at this point in the history
Signed-off-by: James Alseth <[email protected]>
  • Loading branch information
jalseth committed Feb 9, 2025
1 parent 19c82bc commit cc245b1
Show file tree
Hide file tree
Showing 11 changed files with 200 additions and 82 deletions.
9 changes: 8 additions & 1 deletion internal/commands/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,14 @@ func pushLayers(ctx context.Context, pusher content.Pusher, policyPath, dataPath
if dataPath != "" {
dataPaths = append(dataPaths, dataPath)
}
engine, err := policy.LoadWithData(policyPaths, dataPaths, "", false)
capabilities, err := policy.LoadCapabilities("")
if err != nil {
return nil, fmt.Errorf("load capabilities: %w", err)
}
opts := policy.CompilerOptions{
Capabilities: capabilities,
}
engine, err := policy.LoadWithData(policyPaths, dataPaths, opts)
if err != nil {
return nil, fmt.Errorf("load: %w", err)
}
Expand Down
2 changes: 2 additions & 0 deletions internal/commands/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ func NewTestCommand(ctx context.Context) *cobra.Command {
"policy",
"proto-file-dirs",
"capabilities",
"require-rego-v1",
"trace",
"strict",
"show-builtin-errors",
Expand Down Expand Up @@ -176,6 +177,7 @@ func NewTestCommand(ctx context.Context) *cobra.Command {
cmd.Flags().String("ignore", "", "A regex pattern which can be used for ignoring paths")
cmd.Flags().String("parser", "", fmt.Sprintf("Parser to use to parse the configurations. Valid parsers: %s", parser.Parsers()))
cmd.Flags().String("capabilities", "", "Path to JSON file that can restrict opa functionality against a given policy. Default: all operations allowed")
cmd.Flags().Bool("require-rego-v1", false, "Whether to require Rego policies be V1 compatible")

cmd.Flags().StringP("output", "o", output.OutputStandard, fmt.Sprintf("Output format for conftest results - valid options are: %s", output.Outputs()))
cmd.Flags().Bool("junit-hide-message", false, "Do not include the violation message in the JUnit test name")
Expand Down
2 changes: 2 additions & 0 deletions internal/commands/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ func NewVerifyCommand(ctx context.Context) *cobra.Command {
"quiet",
"junit-hide-message",
"capabilities",
"require-rego-v1",
"strict",
"proto-file-dirs",
"show-builtin-errors",
Expand Down Expand Up @@ -144,6 +145,7 @@ func NewVerifyCommand(ctx context.Context) *cobra.Command {
cmd.Flags().Bool("junit-hide-message", false, "Do not include the violation message in the JUnit test name")

cmd.Flags().String("capabilities", "", "Path to JSON file that can restrict opa functionality against a given policy. Default: all operations allowed")
cmd.Flags().Bool("require-rego-v1", false, "Whether to require Rego policies be V1 compatible")
cmd.Flags().StringSliceP("data", "d", []string{}, "A list of paths from which data for the rego policies will be recursively loaded")
cmd.Flags().StringSliceP("policy", "p", []string{"policy"}, "Path to the Rego policy files directory")

Expand Down
61 changes: 29 additions & 32 deletions policy/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,42 +34,43 @@ type Engine struct {
docs map[string]string
}

type compilerOptions struct {
strict bool
capabilities *ast.Capabilities
// CompilerOptions defines the options for the Rego compiler.
type CompilerOptions struct {
Strict bool
RegoV1 bool
Capabilities *ast.Capabilities
}

var (
warningRegex = regexp.MustCompile("^warn(_[a-zA-Z0-9]+)*$")
failureRegex = regexp.MustCompile("^(deny|violation)(_[a-zA-Z0-9]+)*$")
)

func newCompilerOptions(strict bool, capabilities string) (compilerOptions, error) {
c := ast.CapabilitiesForThisVersion()
if capabilities != "" {
f, err := os.Open(capabilities)
if err != nil {
return compilerOptions{}, fmt.Errorf("capabilities not opened: %w", err)
}
defer f.Close()
c, err = ast.LoadCapabilitiesJSON(f)
if err != nil {
return compilerOptions{}, fmt.Errorf("capabilities not loaded: %w", err)
}
}
return compilerOptions{
strict: strict,
capabilities: c,
}, nil
func newCompiler(opts CompilerOptions) *ast.Compiler {
return ast.NewCompiler().
WithEnablePrintStatements(true).
WithCapabilities(opts.Capabilities).
WithStrict(opts.Strict)
}

func newCompiler(c compilerOptions) *ast.Compiler {
return ast.NewCompiler().WithEnablePrintStatements(true).WithCapabilities(c.capabilities).WithStrict(c.strict)
// LoadCapabilities loads Rego JSON capabilities given a path. If no path is supplied, the default
// capabilities are returned.
func LoadCapabilities(path string) (*ast.Capabilities, error) {
if path == "" {
return ast.CapabilitiesForThisVersion(), nil
}
return ast.LoadCapabilitiesFile(path)
}

// Load returns an Engine after loading all of the specified policies.
func Load(policyPaths []string, c compilerOptions) (*Engine, error) {
policies, err := loader.NewFileLoader().WithProcessAnnotation(true).Filtered(policyPaths, func(_ string, info os.FileInfo, _ int) bool {
func Load(policyPaths []string, opts CompilerOptions) (*Engine, error) {
regoVer := ast.RegoV0
if opts.RegoV1 {
regoVer = ast.RegoV1
}

l := loader.NewFileLoader().WithProcessAnnotation(true).WithRegoVersion(regoVer)
policies, err := l.Filtered(policyPaths, func(_ string, info os.FileInfo, _ int) bool {
return !info.IsDir() && !strings.HasSuffix(info.Name(), bundle.RegoExt)
})

Expand All @@ -80,7 +81,7 @@ func Load(policyPaths []string, c compilerOptions) (*Engine, error) {
}

modules := policies.ParsedModules()
compiler := newCompiler(c)
compiler := newCompiler(opts)
compiler.Compile(modules)
if compiler.Failed() {
return nil, fmt.Errorf("get compiler: %w", compiler.Errors)
Expand Down Expand Up @@ -108,15 +109,11 @@ func Load(policyPaths []string, c compilerOptions) (*Engine, error) {
}

// LoadWithData returns an Engine after loading all of the specified policies and data paths.
func LoadWithData(policyPaths []string, dataPaths []string, capabilities string, strict bool) (*Engine, error) {
compilerOptions, err := newCompilerOptions(strict, capabilities)
if err != nil {
return nil, fmt.Errorf("get compiler options: %w", err)
}

func LoadWithData(policyPaths []string, dataPaths []string, opts CompilerOptions) (*Engine, error) {
engine := &Engine{}
if len(policyPaths) > 0 {
engine, err = Load(policyPaths, compilerOptions)
var err error
engine, err = Load(policyPaths, opts)
if err != nil {
return nil, fmt.Errorf("loading policies: %w", err)
}
Expand Down
115 changes: 68 additions & 47 deletions policy/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,18 @@ import (
"github.com/open-policy-agent/opa/loader"
)

func testOptions(t *testing.T) CompilerOptions {
t.Helper()
return CompilerOptions{
Capabilities: ast.CapabilitiesForThisVersion(),
}
}

func TestException(t *testing.T) {
ctx := context.Background()

policies := []string{"../examples/exceptions/policy"}
compilerOptions, _ := newCompilerOptions(false, "")
engine, err := Load(policies, compilerOptions)
engine, err := Load(policies, testOptions(t))
if err != nil {
t.Fatalf("loading policies: %v", err)
}
Expand Down Expand Up @@ -59,8 +65,7 @@ func TestTracing(t *testing.T) {
ctx := context.Background()

policies := []string{"../examples/kubernetes/policy"}
compilerOptions, _ := newCompilerOptions(false, "")
engine, err := Load(policies, compilerOptions)
engine, err := Load(policies, testOptions(t))
if err != nil {
t.Fatalf("loading policies: %v", err)
}
Expand Down Expand Up @@ -89,8 +94,7 @@ func TestTracing(t *testing.T) {
ctx := context.Background()

policies := []string{"../examples/kubernetes/policy"}
compilerOptions, _ := newCompilerOptions(false, "")
engine, err := Load(policies, compilerOptions)
engine, err := Load(policies, testOptions(t))
if err != nil {
t.Fatalf("loading policies: %v", err)
}
Expand Down Expand Up @@ -119,8 +123,7 @@ func TestMultifileYaml(t *testing.T) {
ctx := context.Background()

policies := []string{"../examples/kubernetes/policy"}
compilerOptions, _ := newCompilerOptions(false, "")
engine, err := Load(policies, compilerOptions)
engine, err := Load(policies, testOptions(t))
if err != nil {
t.Fatalf("loading policies: %v", err)
}
Expand Down Expand Up @@ -166,8 +169,7 @@ func TestDockerfile(t *testing.T) {
ctx := context.Background()

policies := []string{"../examples/docker/policy"}
compilerOptions, _ := newCompilerOptions(false, "")
engine, err := Load(policies, compilerOptions)
engine, err := Load(policies, testOptions(t))
if err != nil {
t.Fatalf("loading policies: %v", err)
}
Expand Down Expand Up @@ -379,32 +381,10 @@ func TestProblematicIf(t *testing.T) {
}

func TestLoadWithData(t *testing.T) {
tmpDir := t.TempDir()

inaccessibleCapabilitiesPath := filepath.Join(tmpDir, "capabilities")
err := os.WriteFile(inaccessibleCapabilitiesPath, []byte(""), 0o000)
if err != nil {
t.Fatalf("failed to write empty policy file: %v", err)
}

t.Cleanup(func() {
err := os.Chmod(inaccessibleCapabilitiesPath, 0o600)
if err != nil {
t.Fatalf("failed to restore capabilities file permissions: %v", err)
}
})

invalidCapabilitiesPath := filepath.Join(tmpDir, "invalid-capabilities")
err = os.WriteFile(invalidCapabilitiesPath, []byte("invalid json"), 0o600)
if err != nil {
t.Fatalf("failed to write invalid capabilities file: %v", err)
}

testCases := []struct {
desc string
policyPaths []string
dataPaths []string
capabilities string
strict bool
wantPolicies bool
wantDocs bool
Expand Down Expand Up @@ -441,25 +421,15 @@ func TestLoadWithData(t *testing.T) {
dataPaths: []string{"nonexistent/data.yaml"},
wantErr: true,
},
{
desc: "Inaccessible capabilities file",
policyPaths: []string{"../examples/kubernetes/policy"},
dataPaths: []string{"../examples/kubernetes/service.yaml"},
capabilities: inaccessibleCapabilitiesPath,
wantErr: true,
},
{
desc: "Invalid capabilities file",
policyPaths: []string{"../examples/kubernetes/policy"},
dataPaths: []string{"../examples/kubernetes/service.yaml"},
capabilities: invalidCapabilitiesPath,
wantErr: true,
},
}

for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
engine, err := LoadWithData(tc.policyPaths, tc.dataPaths, tc.capabilities, tc.strict)
opts := CompilerOptions{
Strict: tc.strict,
Capabilities: ast.CapabilitiesForThisVersion(),
}
engine, err := LoadWithData(tc.policyPaths, tc.dataPaths, opts)
if tc.wantErr {
if err == nil {
t.Fatal("expected error but got none")
Expand Down Expand Up @@ -502,6 +472,57 @@ func TestLoadWithData(t *testing.T) {
}
}

func TestLoadCapabilities(t *testing.T) {
tmpDir := t.TempDir()

inaccessibleCapabilitiesPath := filepath.Join(tmpDir, "capabilities")
err := os.WriteFile(inaccessibleCapabilitiesPath, []byte(""), 0o000)
if err != nil {
t.Fatalf("failed to write empty policy file: %v", err)
}

t.Cleanup(func() {
err := os.Chmod(inaccessibleCapabilitiesPath, 0o600)
if err != nil {
t.Fatalf("failed to restore capabilities file permissions: %v", err)
}
})

invalidCapabilitiesPath := filepath.Join(tmpDir, "invalid-capabilities")
err = os.WriteFile(invalidCapabilitiesPath, []byte("invalid json"), 0o600)
if err != nil {
t.Fatalf("failed to write invalid capabilities file: %v", err)
}

tests := []struct {
desc string
path string
wantErr bool
}{
{
desc: "Inaccessible capabilities file",
path: inaccessibleCapabilitiesPath,
wantErr: true,
},
{
desc: "Invalid capabilities file",
path: invalidCapabilitiesPath,
wantErr: true,
},
{
desc: "No path does not error",
},
}
for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) {
_, err := LoadCapabilities(tc.path)
if gotErr := err != nil; gotErr != tc.wantErr {
t.Errorf("LoadCapabilities(%s) error = %v, want %v", tc.path, gotErr, tc.wantErr)
}
})
}
}

func TestNamespaces(t *testing.T) {
tests := []struct {
name string
Expand Down
12 changes: 11 additions & 1 deletion runner/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type TestRunner struct {
Trace bool
Strict bool
Capabilities string
RegoV1 bool `mapstructure:"require-rego-v1"`
Policy []string
Data []string
Update []string
Expand Down Expand Up @@ -62,7 +63,16 @@ func (t *TestRunner) Run(ctx context.Context, fileList []string) ([]output.Check
}
}

engine, err := policy.LoadWithData(t.Policy, t.Data, t.Capabilities, t.Strict)
capabilities, err := policy.LoadCapabilities(t.Capabilities)
if err != nil {
return nil, fmt.Errorf("load capabilities: %w", err)
}
opts := policy.CompilerOptions{
Strict: t.Strict,
RegoV1: t.RegoV1,
Capabilities: capabilities,
}
engine, err := policy.LoadWithData(t.Policy, t.Data, opts)
if err != nil {
return nil, fmt.Errorf("load: %w", err)
}
Expand Down
12 changes: 11 additions & 1 deletion runner/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
// Rego policy unit-tests.
type VerifyRunner struct {
Capabilities string
RegoV1 bool `mapstructure:"require-rego-v1"`
Policy []string
Data []string
Output string
Expand All @@ -35,7 +36,16 @@ const (

// Run executes the Rego tests for the given policies.
func (r *VerifyRunner) Run(ctx context.Context) ([]output.CheckResult, []*tester.Result, error) {
engine, err := policy.LoadWithData(r.Policy, r.Data, r.Capabilities, r.Strict)
capabilities, err := policy.LoadCapabilities(r.Capabilities)
if err != nil {
return nil, nil, fmt.Errorf("load capabilities: %w", err)
}
opts := policy.CompilerOptions{
Strict: r.Strict,
RegoV1: r.RegoV1,
Capabilities: capabilities,
}
engine, err := policy.LoadWithData(r.Policy, r.Data, opts)
if err != nil {
return nil, nil, fmt.Errorf("load: %w", err)
}
Expand Down
Empty file added tests/rego-version/data.yaml
Empty file.
6 changes: 6 additions & 0 deletions tests/rego-version/policy/v0.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package main

deny[msg] {
input.bar == "baz"
msg := "foo"
}
6 changes: 6 additions & 0 deletions tests/rego-version/policy/v1.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package main

deny contains msg if {
input.bar == "baz"
msg := "foo"
}
Loading

0 comments on commit cc245b1

Please sign in to comment.