package scan import ( "fmt" "io/fs" "path/filepath" "reflect" "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 warning bool traces []string fsPath string } 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) IsWarning() bool { return r.warning } 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 } 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, warning: rule == "warn" || strings.HasPrefix(rule, "warn_"), 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().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 fmt.Sprintf("%d", t) case bool: return fmt.Sprintf("%t", 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 }