mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-05 20:40:16 -08:00
389 lines
8.4 KiB
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
|
|
}
|