Skip to content

Commit

Permalink
Enhancement in printing results for regoval command and re-organizing…
Browse files Browse the repository at this point in the history
… Rego policies

Signed-off-by: Santosh <ksantosh@intelops.dev>
  • Loading branch information
santoshkal committed May 17, 2024
1 parent a4e242c commit a9d4e75
Show file tree
Hide file tree
Showing 19 changed files with 367 additions and 85 deletions.
65 changes: 33 additions & 32 deletions pkg/validate/printresults.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,56 +8,57 @@ import (
"github.com/fatih/color"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/open-policy-agent/opa/rego"
log "github.com/sirupsen/logrus"
)

func PrintResults(result rego.ResultSet) error {
// PrintResults prints the evaluation results along with the metadata
func PrintResults(result rego.ResultSet, metas []*regoMetadata) error {
// Create the table
t := table.NewWriter()
t.SetOutputMirror(os.Stdout)
t.AppendHeader(table.Row{"Policy", "Status", "Description"})
t.AppendHeader(table.Row{"Policy Name", "Status", "Description", "Severity", "Benchmark", "Category"})

var policyError error
var desc string

// Match policy metadata outside the loop
matchedKey, meta, err := MatchPolicyMetadata(metas, result)
if err != nil {
return fmt.Errorf("error matching key and metadata name: %v", err)
}

for _, r := range result {
if len(r.Expressions) > 0 {
keys := r.Expressions[0].Value.(map[string]interface{})
for key, value := range keys {
switch v := value.(type) {
case []interface{}:
// Check if the slice is not empty
if len(v) > 0 {
// If it's not empty, take the first element
desc = fmt.Sprintf("%v", v[0])
}
case string:
// If value is a string, assign it to desc
desc = v
}

// Perform type assertion to check if value is a slice
if slice, ok := value.([]interface{}); ok {
// Check if the slice is empty
if len(slice) > 0 {
t.AppendRow(table.Row{key, color.New(color.FgGreen).Sprint("passed"), desc})
} else {
t.AppendRow(table.Row{key, color.New(color.FgRed).Sprint("failed"), "NA"})
policyError = errors.New("policy evaluation failed: " + key)
}
} else {
// Handle other types of values (non-slice)
if value != nil {
t.AppendRow(table.Row{key, color.New(color.FgGreen).Sprint("passed"), desc})
// Construct rows using the matched metadata
if key == matchedKey {
var status string
if policies, ok := value.([]interface{}); ok {
// Check if the slice is empty
if len(policies) > 0 {
status = color.New(color.FgGreen).Sprint("passed")
} else {
status = color.New(color.FgRed).Sprint("failed")
policyError = errors.New("policy evaluation failed: " + key)
}
} else {
t.AppendRow(table.Row{key, color.New(color.FgRed).Sprint("failed"), "NA"})
policyError = errors.New("policy evaluation failed: " + key)
// Handle other types of values (non-slice)
if value != nil {
status = color.New(color.FgGreen).Sprint("passed")
} else {
status = color.New(color.FgRed).Sprint("failed")
policyError = errors.New("policy evaluation failed: " + key)
}
}
t.AppendRow([]interface{}{key, status, meta.Description, meta.Severity, meta.Benchmark, meta.Category})
}
}
} else {
log.Error("No policies passed")
fmt.Println("No policies passed")
policyError = errors.New("no policies passed")
}
}

// Render the table
t.Render()

return policyError
Expand Down
77 changes: 77 additions & 0 deletions pkg/validate/regometa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package validate

import (
"encoding/json"
"fmt"
"os"
"path/filepath"

"github.com/open-policy-agent/opa/rego"
)

func FetchRegoMetadata(policyDir, metaExt, regoExt string) ([]string, []string, error) {
var metaFiles []string
var regoFiles []string

err := filepath.Walk(policyDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}

if !info.IsDir() {
if filepath.Ext(info.Name()) == metaExt {
metaFiles = append(metaFiles, path)
} else if filepath.Ext(info.Name()) == regoExt {
regoFiles = append(regoFiles, path)
}
}

return nil
})

if len(regoFiles) == 0 {
return nil, nil, fmt.Errorf("no Rego policy file found in the directory: %s", policyDir)
}

return metaFiles, regoFiles, err
}

// LoadRegoMetadata loads the contents of the metadata files into a slice of pointers to RegoMeta structs
func LoadRegoMetadata(filePaths []string) ([]*regoMetadata, error) {
var metas []*regoMetadata

for _, path := range filePaths {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()

var meta regoMetadata
err = json.NewDecoder(file).Decode(&meta)
if err != nil {
return nil, err
}

metas = append(metas, &meta)
}

return metas, nil
}

// MatchPolicyMetadata matches the RegoMeta policy names with the Rego evaluation results and returns the matched key
func MatchPolicyMetadata(metas []*regoMetadata, results rego.ResultSet) (string, *regoMetadata, error) {
for _, r := range results {
if len(r.Expressions) > 0 {
keys := r.Expressions[0].Value.(map[string]interface{})
for key := range keys {
for _, meta := range metas {
if key == meta.PolicyName {
return key, meta, nil
}
}
}
}
}
return "", nil, fmt.Errorf("no matching policy name found")
}
16 changes: 16 additions & 0 deletions pkg/validate/regometadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package validate

type regoMetadata struct {
Name string `json:"name"`
PolicyName string `json:"policy_name"`
PolicyFile string `json:"policy_file"`
Severity string `json:"severity"`
Description string `json:"description"`
Benchmark string `json:"benchmark"`
Category string `json:"category"`
}

const (
metaExt = ".json"
policyExt = ".rego"
)
25 changes: 20 additions & 5 deletions pkg/validate/regoval.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,23 @@ import (
log "github.com/sirupsen/logrus"
)

func ValidateWithRego(inputContent string, regoPolicy string) error {
func ValidateWithRego(inputContent string, regoPolicyPath string) error {
// read input is a file
jsonData, err := parser.ProcessInput(inputContent)
if err != nil {
log.Errorf("Error reading input content file: %v", err)
}

k8sPolicy, err := utils.ReadFile(regoPolicy)
metaFiles, regoPolicy, err := FetchRegoMetadata(regoPolicyPath, metaExt, policyExt)
if err != nil {
return err
}
var regoFile string
for _, v := range regoPolicy {
regoFile = v
}

k8sPolicy, err := utils.ReadFile(regoFile)
if err != nil {
log.WithError(err).Error("Error reading the policy file")
return err
Expand All @@ -40,7 +49,7 @@ func ValidateWithRego(inputContent string, regoPolicy string) error {
// Create regoQuery for evaluation
regoQuery := rego.New(
rego.Query("data."+pkg),
rego.Module(regoPolicy, string(k8sPolicy)),
rego.Module(regoFile, string(k8sPolicy)),
rego.Input(commands),
)

Expand All @@ -51,8 +60,14 @@ func ValidateWithRego(inputContent string, regoPolicy string) error {
return err
}

if err := PrintResults(rs); err != nil {
return fmt.Errorf("error evaluating rego results fron policy %s: %v", regoPolicy, err)
// Load metadata from JSON files
metas, err := LoadRegoMetadata(metaFiles)
if err != nil {
return fmt.Errorf("error loading policy metadata: %v", err)
}

if err := PrintResults(rs, metas); err != nil {
return fmt.Errorf("error evaluating rego results for %s: %v", regoPolicyPath, err)
}
return nil
}
110 changes: 67 additions & 43 deletions pkg/validate/validatedockerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,66 +13,90 @@ import (
log "github.com/sirupsen/logrus"
)

// ValidateDockerfileUsingRego validates a Dockerfile using Rego.
// ValidateDockerfileUsingRego validates a Dockerfile using Rego.
func ValidateDockerfile(dockerfileContent string, regoPolicyPath string) error {
dockerPolicy, err := utils.ReadFile(regoPolicyPath)
metaFiles, regoPolicy, err := FetchRegoMetadata(regoPolicyPath, metaExt, policyExt)
if err != nil {
return fmt.Errorf("error reading the policy file %v: %v", regoPolicyPath, err)
return err
}

pkg, err := utils.ExtractPackageName(dockerPolicy)
// Load metadata from JSON files
metas, err := LoadRegoMetadata(metaFiles)
if err != nil {
return fmt.Errorf("errr fetching package name from polcy %v: %v", dockerPolicy, err)
return fmt.Errorf("error loading policy metadata: %v", err)
}

// Prepare Rego input data
dockerfileInstructions := parser.ParseDockerfileContent(dockerfileContent)
// Declare a slice to store the results of each policy evaluation
var allResults []rego.ResultSet

jsonData, err := json.Marshal(dockerfileInstructions)
if err != nil {
return fmt.Errorf("error marshalling %v, to JSON: %v", dockerfileInstructions, err)
}
for _, regoFile := range regoPolicy {
dockerPolicy, err := utils.ReadFile(regoFile)
if err != nil {
return fmt.Errorf("error reading the policy file %v: %v", regoPolicy, err)
}

policyName := filepath.Base(regoPolicyPath)
pkg, err := utils.ExtractPackageName(dockerPolicy)
if err != nil {
return fmt.Errorf("errr fetching package name from polcy %v: %v", dockerPolicy, err)
}

var commands []map[string]string
err = json.Unmarshal([]byte(jsonData), &commands)
if err != nil {
return fmt.Errorf("error converting JSON to map: %v", err)
}
// Prepare Rego input data
dockerfileInstructions := parser.ParseDockerfileContent(dockerfileContent)

ctx := context.Background()
jsonData, err := json.Marshal(dockerfileInstructions)
if err != nil {
return fmt.Errorf("error marshalling %v, to JSON: %v", dockerfileInstructions, err)
}

compiler, err := ast.CompileModules(map[string]string{
policyName: string(dockerPolicy),
})
if err != nil {
log.Fatal(err)
return fmt.Errorf("failed to compile rego policy: %w", err)
}
// Create regoQuery for evaluation
regoQuery := rego.New(
rego.Query("data."+pkg),
rego.Compiler(compiler),
rego.Input(commands),
)

// Evaluate the Rego query
rs, err := regoQuery.Eval(ctx)
if err != nil {
switch err := err.(type) {
case ast.Errors:
for _, e := range err {
fmt.Printf("code: %v", e.Code)
fmt.Printf("row: %v", e.Location.Row)
fmt.Printf("filename: %v", e.Location.File)
policyName := filepath.Base(regoFile)

var commands []map[string]string
err = json.Unmarshal([]byte(jsonData), &commands)
if err != nil {
return fmt.Errorf("error converting JSON to map: %v", err)
}

ctx := context.Background()

compiler, err := ast.CompileModules(map[string]string{
policyName: string(dockerPolicy),
})
if err != nil {
log.Fatal(err)
return fmt.Errorf("failed to compile rego policy: %w", err)
}
// Create regoQuery for evaluation
regoQuery := rego.New(
rego.Query("data."+pkg),
rego.Compiler(compiler),
rego.Input(commands),
)

// Evaluate the Rego query
rs, err := regoQuery.Eval(ctx)
if err != nil {
switch err := err.(type) {
case ast.Errors:
for _, e := range err {
fmt.Printf("code: %v", e.Code)
fmt.Printf("row: %v", e.Location.Row)
fmt.Printf("filename: %v", e.Location.File)
}
log.Fatal("Error evaluating query:", err)
}
log.Fatal("Error evaluating query:", err)
}

// Store the results in the slice
allResults = append(allResults, rs)
}

if err := PrintResults(rs); err != nil {
return fmt.Errorf("error evaluating rego results for %s: %v", regoPolicyPath, err)
// Print all results accumulated from each policy evaluation
for _, rs := range allResults {
if err := PrintResults(rs, metas); err != nil {
return fmt.Errorf("error evaluating rego results for %s: %v", regoPolicyPath, err)
}
}

return nil
}
Loading

0 comments on commit a9d4e75

Please sign in to comment.