Skip to content

Commit

Permalink
feat: add strict compilation rules to policies being evaluated (#798)
Browse files Browse the repository at this point in the history
Signed-off-by: boranx <boran.seref@gmail.com>
  • Loading branch information
boranx committed Mar 24, 2023
1 parent dfefaf4 commit 3de2c90
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 23 deletions.
9 changes: 9 additions & 0 deletions acceptance.bats
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,15 @@
[ "$status" -eq 0 ]
}

@test "Should fail if strict is set and there are unused variables in the policy" {
run ./conftest test -p examples/strict-rules/policy/ examples/kubernetes/deployment.yaml --strict
[ "$status" -eq 1 ]
[[ "$output" =~ "rego_compile_error: assigned var b unused" ]]
[[ "$output" =~ "rego_compile_error: assigned var x unused" ]]
[[ "$output" =~ "rego_compile_error: assigned var c unused" ]]
[[ "$output" =~ "rego_compile_error: unused argument y" ]]
}

@test "Should fail if an opa function is not defined given capabilities file" {
run ./conftest test examples/kubernetes/deployment.yaml -p examples/kubernetes/policy/ -p examples/capabilities/malicious.rego --capabilities examples/capabilities/capabilities.json
[ "$status" -eq 1 ]
Expand Down
33 changes: 33 additions & 0 deletions examples/strict-rules/policy/lenient.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package main

allow {
input.b == "foo"
a := 1
b := 2
x := {
"a": a,
"b": "bar",
}
c := 3
}

validate(x, y) {
input.test == x
} else := false {
input.test == "foo"
allow
}

test(x, y, z) {
input.test == x
} else {
input.test == y
} else {
input.test == z
}

deny[msg] {
test("foo", "bar", "baz")
validate("foo", "bar")
msg = "deployment objects should have validated"
}
2 changes: 1 addition & 1 deletion internal/commands/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ func pushLayers(ctx context.Context, pusher content.Pusher, policyPath, dataPath
if dataPath != "" {
dataPaths = append(dataPaths, dataPath)
}
engine, err := policy.LoadWithData(policyPaths, dataPaths, "")
engine, err := policy.LoadWithData(policyPaths, dataPaths, "", false)
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",
"capabilities",
"trace",
"strict",
"update",
"junit-hide-message",
}
Expand Down Expand Up @@ -161,6 +162,7 @@ func NewTestCommand(ctx context.Context) *cobra.Command {
cmd.Flags().Bool("all-namespaces", false, "Test policies found in all namespaces")

cmd.Flags().BoolP("trace", "", false, "Enable more verbose trace output for Rego queries")
cmd.Flags().BoolP("strict", "", false, "Enable strict mode for Rego policies")
cmd.Flags().BoolP("combine", "", false, "Combine all config files to be evaluated together")

cmd.Flags().String("ignore", "", "A regex pattern which can be used for ignoring paths")
Expand Down
1 change: 1 addition & 0 deletions internal/commands/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ func NewVerifyCommand(ctx context.Context) *cobra.Command {
cmd.Flags().Bool("no-color", false, "Disable color when printing")
cmd.Flags().Bool("quiet", false, "Disable successful test output")
cmd.Flags().Bool("trace", false, "Enable more verbose trace output for Rego queries")
cmd.Flags().Bool("strict", false, "Enable strict mode for Rego policies")
cmd.Flags().String("report", "", "Shows output for Rego queries as a report with summary. Available options are {full|notes|fails}.")

cmd.Flags().StringP("output", "o", output.OutputStandard, fmt.Sprintf("Output format for conftest results - valid options are: %s", output.Outputs()))
Expand Down
50 changes: 35 additions & 15 deletions policy/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,36 @@ type Engine struct {
docs map[string]string
}

type compilerOptions struct {
strict bool
capabilities *ast.Capabilities
}

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(c compilerOptions) *ast.Compiler {
return ast.NewCompiler().WithEnablePrintStatements(true).WithCapabilities(c.capabilities).WithStrict(c.strict)
}

// Load returns an Engine after loading all of the specified policies.
func Load(policyPaths []string, c *ast.Capabilities) (*Engine, error) {
func Load(policyPaths []string, c compilerOptions) (*Engine, error) {
policies, err := loader.NewFileLoader().WithProcessAnnotation(true).Filtered(policyPaths, func(_ string, info os.FileInfo, depth int) bool {
return !info.IsDir() && !strings.HasSuffix(info.Name(), bundle.RegoExt)
})
Expand All @@ -46,7 +74,7 @@ func Load(policyPaths []string, c *ast.Capabilities) (*Engine, error) {
}

modules := policies.ParsedModules()
compiler := ast.NewCompiler().WithEnablePrintStatements(true).WithCapabilities(c)
compiler := newCompiler(c)
compiler.Compile(modules)
if compiler.Failed() {
return nil, fmt.Errorf("get compiler: %w", compiler.Errors)
Expand All @@ -70,24 +98,16 @@ func Load(policyPaths []string, c *ast.Capabilities) (*Engine, error) {
}

// LoadWithData returns an Engine after loading all of the specified policies and data paths.
func LoadWithData(policyPaths []string, dataPaths []string, capabilities string) (*Engine, error) {
c := ast.CapabilitiesForThisVersion()
if capabilities != "" {
f, err := os.Open(capabilities)
if err != nil {
return nil, fmt.Errorf("capabilities not opened: %w", err)
}
defer f.Close()
c, err = ast.LoadCapabilitiesJSON(f)
if err != nil {
return nil, fmt.Errorf("capabilities not loaded: %w", err)
}
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)
}

engine := &Engine{}
if len(policyPaths) > 0 {
var err error
engine, err = Load(policyPaths, c)
engine, err = Load(policyPaths, compilerOptions)
if err != nil {
return nil, fmt.Errorf("loading policies: %w", err)
}
Expand Down
15 changes: 10 additions & 5 deletions policy/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ func TestException(t *testing.T) {
ctx := context.Background()

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

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

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

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

policies := []string{"../examples/docker/policy"}
engine, err := Load(policies, ast.CapabilitiesForThisVersion())
compilerOptions, _ := newCompilerOptions(false, "")
engine, err := Load(policies, compilerOptions)
if err != nil {
t.Fatalf("loading policies: %v", err)
}
Expand Down
3 changes: 2 additions & 1 deletion runner/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
// Rego policy checks against configuration files.
type TestRunner struct {
Trace bool
Strict bool
Capabilities string
Policy []string
Data []string
Expand Down Expand Up @@ -59,7 +60,7 @@ func (t *TestRunner) Run(ctx context.Context, fileList []string) ([]output.Check
}
}

engine, err := policy.LoadWithData(t.Policy, t.Data, t.Capabilities)
engine, err := policy.LoadWithData(t.Policy, t.Data, t.Capabilities, t.Strict)
if err != nil {
return nil, fmt.Errorf("load: %w", err)
}
Expand Down
3 changes: 2 additions & 1 deletion runner/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type VerifyRunner struct {
Output string
NoColor bool `mapstructure:"no-color"`
Trace bool
Strict bool
Report string
Quiet bool
}
Expand All @@ -33,7 +34,7 @@ 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)
engine, err := policy.LoadWithData(r.Policy, r.Data, r.Capabilities, r.Strict)
if err != nil {
return nil, nil, fmt.Errorf("load: %w", err)
}
Expand Down

0 comments on commit 3de2c90

Please sign in to comment.