Files
trivy/pkg/iac/scan/result.go

389 lines
8.4 KiB
Go

package scan
import (
"fmt"
"io/fs"
"path/filepath"
"reflect"
"strconv"
"strings"
"github.com/aquasecurity/trivy/pkg/iac/ignore"
"github.com/aquasecurity/trivy/pkg/iac/severity"
iacTypes "github.com/aquasecurity/trivy/pkg/iac/types"
)
type Status uint8
const (
StatusFailed Status = iota
StatusPassed
StatusIgnored
)
type Result struct {
rule Rule
description string
annotation string
status Status
metadata iacTypes.Metadata
severityOverride *severity.Severity
regoNamespace string
regoRule string
traces []string
fsPath string
renderedCause RenderedCause
}
func (r Result) RegoNamespace() string {
return r.regoNamespace
}
func (r Result) RegoRule() string {
return r.regoRule
}
func (r Result) Severity() severity.Severity {
if r.severityOverride != nil {
return *r.severityOverride
}
return r.Rule().Severity
}
func (r *Result) OverrideSeverity(s severity.Severity) {
r.severityOverride = &s
}
func (r *Result) OverrideDescription(description string) {
r.description = description
}
func (r *Result) OverrideMetadata(metadata iacTypes.Metadata) {
r.metadata = metadata
}
func (r *Result) OverrideStatus(status Status) {
r.status = status
}
func (r *Result) OverrideAnnotation(annotation string) {
r.annotation = annotation
}
func (r *Result) SetRule(ru Rule) {
r.rule = ru
}
func (r Result) Status() Status {
return r.status
}
func (r Result) Rule() Rule {
return r.rule
}
func (r Result) Description() string {
return r.description
}
func (r Result) Annotation() string {
return r.annotation
}
func (r Result) Metadata() iacTypes.Metadata {
return r.metadata
}
func (r Result) Range() iacTypes.Range {
return r.metadata.Range()
}
func (r Result) Traces() []string {
return r.traces
}
type RenderedCause struct {
Raw string
}
func (r *Result) WithRenderedCause(cause RenderedCause) {
r.renderedCause = cause
}
func (r *Result) AbsolutePath(fsRoot string, metadata iacTypes.Metadata) string {
if strings.HasSuffix(fsRoot, ":") {
fsRoot += "/"
}
if metadata.IsUnmanaged() {
return ""
}
rng := metadata.Range()
if rng.GetSourcePrefix() != "" && !strings.HasPrefix(rng.GetSourcePrefix(), ".") {
return rng.GetFilename()
}
return filepath.Join(fsRoot, rng.GetLocalFilename())
}
func (r *Result) RelativePathTo(fsRoot, to string, metadata iacTypes.Metadata) string {
absolute := r.AbsolutePath(fsRoot, metadata)
if strings.HasSuffix(fsRoot, ":") {
fsRoot += "/"
}
if metadata.IsUnmanaged() {
return absolute
}
rng := metadata.Range()
if rng.GetSourcePrefix() != "" && !strings.HasPrefix(rng.GetSourcePrefix(), ".") {
return absolute
}
if !strings.HasPrefix(rng.GetLocalFilename(), strings.TrimSuffix(fsRoot, "/")) {
return absolute
}
relative, err := filepath.Rel(to, rng.GetLocalFilename())
if err != nil {
return absolute
}
return relative
}
type Results []Result
type MetadataProvider interface {
GetMetadata() iacTypes.Metadata
GetRawValue() any
}
func (r *Results) GetPassed() Results {
return r.filterStatus(StatusPassed)
}
func (r *Results) GetIgnored() Results {
return r.filterStatus(StatusIgnored)
}
func (r *Results) GetFailed() Results {
return r.filterStatus(StatusFailed)
}
func (r *Results) filterStatus(status Status) Results {
var filtered Results
if r == nil {
return filtered
}
for _, res := range *r {
if res.Status() == status {
filtered = append(filtered, res)
}
}
return filtered
}
func (r *Results) Add(description string, source any) {
result := Result{
description: description,
}
result.metadata = getMetadataFromSource(source)
if result.metadata.IsExplicit() {
result.annotation = getAnnotation(source)
}
rnge := result.metadata.Range()
result.fsPath = rnge.GetLocalFilename()
*r = append(*r, result)
}
func (r *Results) AddRego(description, namespace, rule string, traces []string, source MetadataProvider) {
result := Result{
description: description,
regoNamespace: namespace,
regoRule: rule,
traces: traces,
}
result.metadata = getMetadataFromSource(source)
if result.metadata.IsExplicit() {
result.annotation = getAnnotation(source)
}
rnge := result.metadata.Range()
result.fsPath = rnge.GetLocalFilename()
*r = append(*r, result)
}
func (r *Results) AddPassed(source any, descriptions ...string) {
res := Result{
description: strings.Join(descriptions, " "),
status: StatusPassed,
}
res.metadata = getMetadataFromSource(source)
rnge := res.metadata.Range()
res.fsPath = rnge.GetLocalFilename()
*r = append(*r, res)
}
func getMetadataFromSource(source any) iacTypes.Metadata {
if provider, ok := source.(MetadataProvider); ok {
return provider.GetMetadata()
}
metaValue := reflect.ValueOf(source)
if metaValue.Kind() == reflect.Ptr {
metaValue = metaValue.Elem()
}
metaVal := metaValue.FieldByName("Metadata")
return metaVal.Interface().(iacTypes.Metadata)
}
func getAnnotation(source any) string {
if provider, ok := source.(MetadataProvider); ok {
return rawToString(provider.GetRawValue())
}
return ""
}
func (r *Results) AddPassedRego(namespace, rule string, traces []string, source any) {
res := Result{
status: StatusPassed,
regoNamespace: namespace,
regoRule: rule,
traces: traces,
}
res.metadata = getMetadataFromSource(source)
rnge := res.metadata.Range()
res.fsPath = rnge.GetLocalFilename()
*r = append(*r, res)
}
func (r *Results) AddIgnored(source any, descriptions ...string) {
res := Result{
description: strings.Join(descriptions, " "),
status: StatusIgnored,
}
res.metadata = getMetadataFromSource(source)
rnge := res.metadata.Range()
res.fsPath = rnge.GetLocalFilename()
*r = append(*r, res)
}
func (r *Results) Ignore(ignoreRules ignore.Rules, ignores map[string]ignore.Ignorer) {
for i, result := range *r {
allIDs := []string{
result.Rule().ID,
strings.ToLower(result.Rule().ID),
result.Rule().LongID(),
result.Rule().AVDID,
strings.ToLower(result.Rule().AVDID),
result.Rule().ShortCode,
}
allIDs = append(allIDs, result.Rule().Aliases...)
if ignoreRules.Ignore(result.Metadata(), allIDs, ignores) {
(*r)[i].OverrideStatus(StatusIgnored)
}
}
}
func (r *Results) SetRule(rule Rule) {
for i := range *r {
(*r)[i].rule = rule
}
}
func (r *Results) SetSourceAndFilesystem(source string, f fs.FS, logicalSource bool) {
for i := range *r {
m := (*r)[i].Metadata()
if m.IsUnmanaged() {
continue
}
rng := m.Range()
newrng := iacTypes.NewRange(rng.GetLocalFilename(), rng.GetStartLine(), rng.GetEndLine(), source, f)
if logicalSource {
newrng = iacTypes.NewRangeWithLogicalSource(rng.GetLocalFilename(), rng.GetStartLine(), rng.GetEndLine(),
source, f)
}
parent := m.Parent()
switch {
case m.IsExplicit():
m = iacTypes.NewExplicitMetadata(newrng, m.Reference())
default:
m = iacTypes.NewMetadata(newrng, m.Reference())
}
if parent != nil {
m.SetParentPtr(parent)
}
(*r)[i].OverrideMetadata(m)
}
}
func rawToString(raw any) string {
if raw == nil {
return ""
}
switch t := raw.(type) {
case int:
return strconv.Itoa(t)
case bool:
return strconv.FormatBool(t)
case float64:
return fmt.Sprintf("%f", t)
case string:
return fmt.Sprintf("%q", t)
case []string:
var items []string
for _, item := range t {
items = append(items, rawToString(item))
}
return fmt.Sprintf("[%s]", strings.Join(items, ", "))
case []int:
var items []string
for _, item := range t {
items = append(items, rawToString(item))
}
return fmt.Sprintf("[%s]", strings.Join(items, ", "))
case []float64:
var items []string
for _, item := range t {
items = append(items, rawToString(item))
}
return fmt.Sprintf("[%s]", strings.Join(items, ", "))
case []bool:
var items []string
for _, item := range t {
items = append(items, rawToString(item))
}
return fmt.Sprintf("[%s]", strings.Join(items, ", "))
default:
return "?"
}
}
type Occurrence struct {
Resource string `json:"resource"`
Filename string `json:"filename"`
StartLine int `json:"start_line"`
EndLine int `json:"end_line"`
}
func (r *Result) Occurrences() []Occurrence {
var occurrences []Occurrence
mod := &r.metadata
for {
mod = mod.Parent()
if mod == nil {
break
}
parentRange := mod.Range()
occurrences = append(occurrences, Occurrence{
Resource: mod.Reference(),
Filename: parentRange.GetFilename(),
StartLine: parentRange.GetStartLine(),
EndLine: parentRange.GetEndLine(),
})
}
return occurrences
}