Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions examples/excludes/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@



resource "null_resource" "exception-name" {}

resource "null_resource" "invalid-name" {}

resource "invalid_type" "valid_name" {}
28 changes: 28 additions & 0 deletions examples/excludes/policy/deny.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package main

exceptions = {"exception-name"}

deny_name[result] {
input.resource[_][name]
contains(name, "-")
msg := sprintf("Resource Name '%s' contains dashes", [name])
result := {
"msg": msg,
"resource-name": name,
}
}

deny_resource_type[msg] {
input.resource[type]
type == "invalid_type"
msg := sprintf("Resource Type '%s' is invalid", [type])
}

exclude_name[attrs] {
exceptions[name]
attrs := [{"resource-name": name}]
}

exception[rules] {
rules := ["resource_type"]
}
35 changes: 35 additions & 0 deletions examples/excludes2/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: mydep
spec:
template:
spec:
containers:
- name: web
image: nginx
ports:
- containerPort: 8080
securityContext:
runAsNonRoot: false
- name: host-agent
image: host-agent
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: not-mydep
spec:
template:
spec:
containers:
- name: web
image: nginx
ports:
- containerPort: 8080
securityContext:
runAsNonRoot: true
- name: host-agent
image: host-agent
securityContext:
runAsNonRoot: true
29 changes: 29 additions & 0 deletions examples/excludes2/policy/deny.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package main

deny_root[result] {
input.kind == "Deployment"
c = input.spec.template.spec.containers[_]
not c.securityContext.runAsNonRoot

# key "msg" is required to be set.
result := {
"container": c.name,
"deployment": input.metadata.name,
"msg": sprintf("container %s in deployment %s doesn't set runAsNonRoot", [c.name, input.metadata.name]),
}
}

root_exceptions = [{"deployment": "mydep", "containers": ["host-agent"]}]

# Here the exception I want to be able to express is "mydep can run host-agent as root".
# But not web as root
exclude_root[attrs] {
deployment := input.metadata.name
container := input.spec.template.spec.containers[_].name
exception := root_exceptions[_]

deployment == exception.deployment
container == exception.containers[_]

attrs = [{"container": container, "deployment": deployment}]
}
1 change: 1 addition & 0 deletions output/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ type CheckResult struct {
Warnings []Result `json:"warnings,omitempty"`
Failures []Result `json:"failures,omitempty"`
Exceptions []Result `json:"exceptions,omitempty"`
Excludes []Result `json:"excludes,omitempty"`
Queries []QueryResult `json:"queries,omitempty"`
}

Expand Down
16 changes: 14 additions & 2 deletions output/standard.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func (s *Standard) Output(results []CheckResult) error {

var totalFailures int
var totalExceptions int
var totalExclusions int
var totalWarnings int
var totalSuccesses int
var totalSkipped int
Expand Down Expand Up @@ -89,14 +90,19 @@ func (s *Standard) Output(results []CheckResult) error {
}
}

for _, exclude := range result.Excludes {
fmt.Fprintln(s.Writer, colorizer.Colorize("EXCL", aurora.BlueFg), indicator, namespace, exclude.Message)
}

totalFailures += len(result.Failures)
totalExceptions += len(result.Exceptions)
totalWarnings += len(result.Warnings)
totalExclusions += len(result.Excludes)
totalSkipped += len(result.Skipped)
totalSuccesses += result.Successes
}

totalTests := totalFailures + totalExceptions + totalWarnings + totalSuccesses + totalSkipped
totalTests := totalFailures + totalExceptions + totalWarnings + totalSuccesses + totalExclusions + totalSkipped

var pluralSuffixTests string
if totalTests != 1 {
Expand All @@ -118,12 +124,18 @@ func (s *Standard) Output(results []CheckResult) error {
pluralSuffixExceptions = "s"
}

outputText := fmt.Sprintf("%v test%s, %v passed, %v warning%s, %v failure%s, %v exception%s",
var pluralSuffixExclusions string
if totalExclusions != 1 {
pluralSuffixExclusions = "s"
}

outputText := fmt.Sprintf("%v test%s, %v passed, %v warning%s, %v failure%s, %v exception%s, %v exclusion%s",
totalTests, pluralSuffixTests,
totalSuccesses,
totalWarnings, pluralSuffixWarnings,
totalFailures, pluralSuffixFailures,
totalExceptions, pluralSuffixExceptions,
totalExclusions, pluralSuffixExclusions,
)

if s.ShowSkipped {
Expand Down
29 changes: 29 additions & 0 deletions policy/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package policy
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
Expand Down Expand Up @@ -137,6 +138,7 @@ func (e *Engine) Check(ctx context.Context, configs map[string]interface{}, name
checkResult.Failures = append(checkResult.Failures, result.Failures...)
checkResult.Warnings = append(checkResult.Warnings, result.Warnings...)
checkResult.Exceptions = append(checkResult.Exceptions, result.Exceptions...)
checkResult.Excludes = append(checkResult.Excludes, result.Excludes...)
checkResult.Queries = append(checkResult.Queries, result.Queries...)
}
checkResults = append(checkResults, checkResult)
Expand Down Expand Up @@ -233,6 +235,7 @@ func (e *Engine) Runtime() *ast.Term {
func (e *Engine) check(ctx context.Context, path string, config interface{}, namespace string) (output.CheckResult, error) {
var rules []string
var ruleCount int

for _, module := range e.Modules() {
currentNamespace := strings.Replace(module.Package.Path.String(), "data.", "", 1)
if currentNamespace != namespace {
Expand Down Expand Up @@ -298,6 +301,7 @@ func (e *Engine) check(ctx context.Context, path string, config interface{}, nam

var failures []output.Result
var warnings []output.Result
var excludes []output.Result
for _, ruleResult := range ruleQueryResult.Results {

// Exceptions have already been accounted for in the exception query so
Expand All @@ -311,6 +315,30 @@ func (e *Engine) check(ctx context.Context, path string, config interface{}, nam
continue
}

result, err := json.Marshal(ruleResult.Metadata)
if err != nil {
return output.CheckResult{}, fmt.Errorf("json marshal: %w", err)
}

// If we have a non-null metadata response, then we are eligible to exclude the policy.
// Otherwise we can just skip & process the policy violation
if string(result) != "null" {
localExcludeQuery := fmt.Sprintf("data.%s.exclude_%s[_][_] = %s", namespace, removeRulePrefix(rule), result)
localExcludeQueryResult, err := e.query(ctx, config, localExcludeQuery)
if err != nil {
return output.CheckResult{}, fmt.Errorf("query exception: %w", err)
}

// If the query was a failure, let's have a look & see if an exception was written for it.
if len(localExcludeQueryResult.Results) > 0 {
// append an exception & continue
localExcludeResult := localExcludeQueryResult.Results[0]
localExcludeResult.Message = localExcludeQuery
excludes = append(excludes, localExcludeResult)
continue
}

}
if isFailure(rule) {
failures = append(failures, ruleResult)
} else {
Expand All @@ -321,6 +349,7 @@ func (e *Engine) check(ctx context.Context, path string, config interface{}, nam
checkResult.Failures = append(checkResult.Failures, failures...)
checkResult.Warnings = append(checkResult.Warnings, warnings...)
checkResult.Exceptions = append(checkResult.Exceptions, exceptions...)
checkResult.Excludes = append(checkResult.Excludes, excludes...)

checkResult.Queries = append(checkResult.Queries, exceptionQueryResult)
checkResult.Queries = append(checkResult.Queries, ruleQueryResult)
Expand Down