Skip to content

Commit

Permalink
1. support skip resource with comment.
Browse files Browse the repository at this point in the history
2. skipped resource violations in output.
  • Loading branch information
patilpankaj212 authored and Yusuf Kanchwala committed Jan 15, 2021
1 parent 9ac1b32 commit f0cbb79
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 35 deletions.
47 changes: 40 additions & 7 deletions pkg/iac-providers/kubernetes/v1/normalize.go
Expand Up @@ -28,7 +28,11 @@ import (
"gopkg.in/yaml.v3"
)

const terrascanSkip = "terrascanSkip"
const (
terrascanSkip = "terrascanSkip"
terrascanSkipRule = "rule"
terrascanSkipComment = "comment"
)

var (
errUnsupportedDoc = fmt.Errorf("unsupported document type")
Expand Down Expand Up @@ -146,7 +150,7 @@ func (k *K8sV1) Normalize(doc *utils.IacDocument) (*output.ResourceConfig, error
return &resourceConfig, nil
}

func readSkipRulesFromAnnotations(annotations map[string]interface{}, resourceID string) []string {
func readSkipRulesFromAnnotations(annotations map[string]interface{}, resourceID string) []output.SkipRule {

var skipRulesFromAnnotations interface{}
var ok bool
Expand All @@ -155,18 +159,47 @@ func readSkipRulesFromAnnotations(annotations map[string]interface{}, resourceID
return nil
}

skipRules := make([]string, 0)
skipRules := make([]output.SkipRule, 0)
if rules, ok := skipRulesFromAnnotations.([]interface{}); ok {
for _, rule := range rules {
if value, ok := rule.(string); ok {
skipRules = append(skipRules, value)
if value, ok := rule.(map[string]interface{}); ok {
skipRule := getSkipRuleObject(value)
if skipRule != nil {
skipRules = append(skipRules, *skipRule)
}
} else {
zap.S().Debugf("each rule in %s must be of string type", terrascanSkip)
zap.S().Debugf("each rule in %s must be of map type", terrascanSkip)
}
}
} else {
zap.S().Debugf("%s must be an array of rules to skip", terrascanSkip)
zap.S().Debugf("%s must be an array of {rule: ruleID, comment: reason for skipping}", terrascanSkip)
}

return skipRules
}

func getSkipRuleObject(m map[string]interface{}) *output.SkipRule {
var skipRule output.SkipRule
var rule, comment interface{}
var ok bool

// get rule, if rule not found return nil
if rule, ok = m[terrascanSkipRule]; ok {
if _, ok = rule.(string); ok {
skipRule.Rule = rule.(string)
} else {
return nil
}
} else {
return nil
}

// get comment
if comment, ok = m[terrascanSkipComment]; ok {
if _, ok = comment.(string); ok {
skipRule.Comment = comment.(string)
}
}

return &skipRule
}
8 changes: 7 additions & 1 deletion pkg/iac-providers/output/types.go
Expand Up @@ -27,7 +27,13 @@ type ResourceConfig struct {
// SkipRules will hold the rules to be skipped for the resource.
// Each iac provider should append the rules to be skipped for a resource,
// while extracting resource from the iac files
SkipRules []string `json:"skip_rules"`
SkipRules []SkipRule `json:"skip_rules" yaml:"skip_rules"`
}

// SkipRule struct will hold the skipped rule and any comment for the skipped rule
type SkipRule struct {
Rule string `json:"rule"`
Comment string `json:"comment"`
}

// AllResourceConfigs is a list/slice of resource configs present in IaC
Expand Down
39 changes: 23 additions & 16 deletions pkg/policy/opa/engine.go
Expand Up @@ -285,7 +285,7 @@ func (e *Engine) Release() error {
}

// reportViolation Add a violation for a given resource
func (e *Engine) reportViolation(regoData *RegoData, resource *output.ResourceConfig) {
func (e *Engine) reportViolation(regoData *RegoData, resource *output.ResourceConfig, isSkipped bool, skipComment string) {
violation := results.Violation{
RuleName: regoData.Metadata.Name,
Description: regoData.Metadata.Description,
Expand All @@ -301,20 +301,24 @@ func (e *Engine) reportViolation(regoData *RegoData, resource *output.ResourceCo
LineNumber: resource.Line,
}

severity := regoData.Metadata.Severity
if strings.ToLower(severity) == "high" {
e.results.ViolationStore.Summary.HighCount++
} else if strings.ToLower(severity) == "medium" {
e.results.ViolationStore.Summary.MediumCount++
} else if strings.ToLower(severity) == "low" {
e.results.ViolationStore.Summary.LowCount++
if !isSkipped {
severity := regoData.Metadata.Severity
if strings.ToLower(severity) == "high" {
e.results.ViolationStore.Summary.HighCount++
} else if strings.ToLower(severity) == "medium" {
e.results.ViolationStore.Summary.MediumCount++
} else if strings.ToLower(severity) == "low" {
e.results.ViolationStore.Summary.LowCount++
} else {
zap.S().Warn("invalid severity found in rule definition",
zap.String("rule id", violation.RuleID), zap.String("severity", severity))
}
e.results.ViolationStore.AddResult(&violation, false)
e.results.ViolationStore.Summary.ViolatedPolicies++
} else {
zap.S().Warn("invalid severity found in rule definition",
zap.String("rule id", violation.RuleID), zap.String("severity", severity))
violation.Comment = skipComment
e.results.ViolationStore.AddResult(&violation, true)
}
e.results.ViolationStore.Summary.ViolatedPolicies++

e.results.ViolationStore.AddResult(&violation)
}

// Evaluate Executes compiled OPA queries against the input JSON data
Expand Down Expand Up @@ -378,16 +382,19 @@ func (e *Engine) Evaluate(engineInput policy.EngineInput) (policy.EngineOutput,
continue
}

// do no report violations if rule is skipped for resource
// add to skipped violations if rule is skipped for resource
if len(resource.SkipRules) > 0 {
found := false
var skipComment string
for _, rule := range resource.SkipRules {
if strings.EqualFold(k, rule) {
if strings.EqualFold(k, rule.Rule) {
found = true
skipComment = rule.Comment
break
}
}
if found {
e.reportViolation(e.regoDataMap[k], resource, true, skipComment)
zap.S().Debugf("rule: %s skipped for resource: %s", k, resource.Name)
continue
}
Expand All @@ -401,7 +408,7 @@ func (e *Engine) Evaluate(engineInput policy.EngineInput) (policy.EngineOutput,
zap.S().Debug("violation found for rule with rego", zap.String("rego", string("\n")+string(e.regoDataMap[k].RawRego)+string("\n")))

// Report the violation
e.reportViolation(e.regoDataMap[k], resource)
e.reportViolation(e.regoDataMap[k], resource, false, "")
}
}

Expand Down
5 changes: 3 additions & 2 deletions pkg/policy/types.go
Expand Up @@ -32,7 +32,8 @@ func (me EngineOutput) AsViolationStore() results.ViolationStore {
return results.ViolationStore{}
}
return results.ViolationStore{
Violations: me.Violations,
Summary: me.Summary,
Violations: me.Violations,
SkippedViolations: me.SkippedViolations,
Summary: me.Summary,
}
}
4 changes: 2 additions & 2 deletions pkg/results/interface.go
Expand Up @@ -2,6 +2,6 @@ package results

// Store manages the storage and export of results information
type Store interface {
AddResult(violation *Violation)
GetResults() []*Violation
AddResult(violation *Violation, isSkipped bool)
GetResults(isSkipped bool) []*Violation
}
18 changes: 14 additions & 4 deletions pkg/results/store.go
Expand Up @@ -19,16 +19,26 @@ package results
// NewViolationStore returns a new violation store
func NewViolationStore() *ViolationStore {
return &ViolationStore{
Violations: []*Violation{},
Violations: []*Violation{},
SkippedViolations: []*Violation{},
}
}

// AddResult Adds individual violations into the violation store
func (s *ViolationStore) AddResult(violation *Violation) {
s.Violations = append(s.Violations, violation)
// when skip is true, violations are added to skipped violations
func (s *ViolationStore) AddResult(violation *Violation, isSkipped bool) {
if isSkipped {
s.SkippedViolations = append(s.SkippedViolations, violation)
} else {
s.Violations = append(s.Violations, violation)
}
}

// GetResults Retrieves all violations from the violation store
func (s *ViolationStore) GetResults() []*Violation {
// when skip is true, it returns only the skipped violations
func (s *ViolationStore) GetResults(isSkipped bool) []*Violation {
if isSkipped {
return s.SkippedViolations
}
return s.Violations
}
7 changes: 5 additions & 2 deletions pkg/results/types.go
Expand Up @@ -29,6 +29,7 @@ type Violation struct {
Category string `json:"category" yaml:"category" xml:"category,attr"`
RuleFile string `json:"-" yaml:"-" xml:"-"`
RuleData interface{} `json:"-" yaml:"-" xml:"-"`
Comment string `json:"skip_comment,omitempty" yaml:"skip_comment,omitempty" xml:"skip_comment,omitempty"`
ResourceName string `json:"resource_name" yaml:"resource_name" xml:"resource_name,attr"`
ResourceType string `json:"resource_type" yaml:"resource_type" xml:"resource_type,attr"`
ResourceData interface{} `json:"-" yaml:"-" xml:"-"`
Expand All @@ -38,8 +39,9 @@ type Violation struct {

// ViolationStore Storage area for violation data
type ViolationStore struct {
Violations []*Violation `json:"violations" yaml:"violations" xml:"violations>violation"`
Summary ScanSummary `json:"scan_summary" yaml:"scan_summary" xml:"scan_summary"`
Violations []*Violation `json:"violations" yaml:"violations" xml:"violations>violation"`
SkippedViolations []*Violation `json:"skipped_violations" yaml:"skipped_violations" xml:"skipped_violations>violation"`
Summary ScanSummary `json:"scan_summary" yaml:"scan_summary" xml:"scan_summary"`
}

// ScanSummary will hold the default scan summary data
Expand All @@ -59,6 +61,7 @@ type ScanSummary struct {
func (vs ViolationStore) Add(extra ViolationStore) ViolationStore {
// Just concatenate the slices, since order shouldn't be important
vs.Violations = append(vs.Violations, extra.Violations...)
vs.SkippedViolations = append(vs.SkippedViolations, extra.SkippedViolations...)

// Add the scan summary
vs.Summary.LowCount += extra.Summary.LowCount
Expand Down
2 changes: 1 addition & 1 deletion pkg/termcolor/writer_test.go
Expand Up @@ -26,7 +26,7 @@ func buildStore() *results.ViolationStore {
ResourceType: "resource type",
File: "file",
LineNumber: 1,
})
}, false)

return res
}
Expand Down

0 comments on commit f0cbb79

Please sign in to comment.