Add analysis rules for filter suite analyzer

This commit is contained in:
abhisek 2023-02-18 16:37:19 +05:30
parent 2e9b5fb8e0
commit 2ca64478cf
No known key found for this signature in database
GPG Key ID: CB92A4990C02A88F
7 changed files with 105 additions and 28 deletions

View File

@ -110,11 +110,7 @@ To express this policy, multiple filters are needed such as:
```
vulns.critical.exists(p, true) ||
licenses.exists(p,
(p != "MIT") && (p != "Apache-2.0")
) ||
licenses.exists(p, (p != "MIT") && (p != "Apache-2.0")) ||
(scorecard.scores.Maintained == 0)
```

View File

@ -27,7 +27,7 @@ type celFilterAnalyzer struct {
}
func NewCelFilterAnalyzer(fl string, failOnMatch bool) (Analyzer, error) {
evaluator, err := filter.NewEvaluator("single-filter")
evaluator, err := filter.NewEvaluator("single-filter", true)
if err != nil {
return nil, err
}

View File

@ -1,18 +1,26 @@
package analyzer
import (
"fmt"
"os"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/safedep/dry/utils"
"github.com/safedep/vet/gen/filtersuite"
"github.com/safedep/vet/pkg/analyzer/filter"
"github.com/safedep/vet/pkg/models"
)
type celFilterMatchedPackage struct {
pkg *models.Package
filterName string
}
type celFilterSuiteAnalyzer struct {
evaluator filter.Evaluator
suite *filtersuite.FilterSuite
failOnMatch bool
evaluator filter.Evaluator
suite *filtersuite.FilterSuite
failOnMatch bool
matchedPackages map[string]*celFilterMatchedPackage
}
func NewCelFilterSuiteAnalyzer(path string, failOnMatch bool) (Analyzer, error) {
@ -21,7 +29,7 @@ func NewCelFilterSuiteAnalyzer(path string, failOnMatch bool) (Analyzer, error)
return nil, err
}
evaluator, err := filter.NewEvaluator(fs.GetName())
evaluator, err := filter.NewEvaluator(fs.GetName(), true)
if err != nil {
return nil, err
}
@ -34,9 +42,10 @@ func NewCelFilterSuiteAnalyzer(path string, failOnMatch bool) (Analyzer, error)
}
return &celFilterSuiteAnalyzer{
evaluator: evaluator,
suite: fs,
failOnMatch: failOnMatch,
evaluator: evaluator,
suite: fs,
failOnMatch: failOnMatch,
matchedPackages: make(map[string]*celFilterMatchedPackage),
}, nil
}
@ -46,13 +55,68 @@ func (f *celFilterSuiteAnalyzer) Name() string {
func (f *celFilterSuiteAnalyzer) Analyze(manifest *models.PackageManifest,
handler AnalyzerEventHandler) error {
for _, pkg := range manifest.Packages {
res, err := f.evaluator.EvalPackage(pkg)
if err != nil {
continue
}
if res.Matched() {
f.queueMatchedPkg(pkg, res.GetMatchedFilter().Name())
}
}
if f.failOnMatch && (len(f.matchedPackages) > 0) {
handler(&AnalyzerEvent{
Source: f.Name(),
Type: ET_AnalyzerFailOnError,
Manifest: manifest,
Err: fmt.Errorf("failed due to filter suite match on %s", manifest.Path),
})
}
return nil
}
func (f *celFilterSuiteAnalyzer) Finish() error {
f.renderMatchTable()
return nil
}
func (f *celFilterSuiteAnalyzer) renderMatchTable() {
tbl := table.NewWriter()
tbl.SetStyle(table.StyleLight)
tbl.SetOutputMirror(os.Stdout)
tbl.AppendHeader(table.Row{"Ecosystem", "Package", "Latest",
"Filter"})
for _, mp := range f.matchedPackages {
insights := utils.SafelyGetValue(mp.pkg.Insights)
tbl.AppendRow(table.Row{
mp.pkg.PackageDetails.Ecosystem,
fmt.Sprintf("%s@%s", mp.pkg.PackageDetails.Name,
mp.pkg.PackageDetails.Version),
utils.SafelyGetValue(insights.PackageCurrentVersion),
mp.filterName,
})
}
tbl.Render()
}
func (f *celFilterSuiteAnalyzer) queueMatchedPkg(pkg *models.Package,
filterName string) {
if _, ok := f.matchedPackages[pkg.Id()]; ok {
return
}
f.matchedPackages[pkg.Id()] = &celFilterMatchedPackage{
filterName: filterName,
pkg: pkg,
}
}
// To correctly unmarshal a []byte into protobuf message, we must use
// protobuf SDK and not generic JSON / YAML decoder. Since there is no
// officially supported yamlpb, equivalent to jsonpb, we convert YAML

View File

@ -29,7 +29,7 @@ func TestLoadFilterSuiteFromFile(t *testing.T) {
"",
"",
0,
"unknown field \"A\" in FilterSuite",
"unknown field",
},
{
"filter suite does not exists",

View File

@ -37,12 +37,13 @@ type Evaluator interface {
}
type filterEvaluator struct {
name string
env *cel.Env
programs []*filterProgram
name string
env *cel.Env
programs []*filterProgram
ignoreError bool
}
func NewEvaluator(name string) (Evaluator, error) {
func NewEvaluator(name string, ignoreError bool) (Evaluator, error) {
env, err := cel.NewEnv(
cel.Variable(filterInputVarPkg, cel.DynType),
cel.Variable(filterInputVarVulns, cel.DynType),
@ -57,9 +58,10 @@ func NewEvaluator(name string) (Evaluator, error) {
}
return &filterEvaluator{
name: name,
env: env,
programs: []*filterProgram{},
name: name,
env: env,
programs: []*filterProgram{},
ignoreError: ignoreError,
}, nil
}
@ -108,6 +110,12 @@ func (f *filterEvaluator) EvalPackage(pkg *models.Package) (*filterEvaluationRes
})
if err != nil {
logger.Warnf("CEL evaluator error: %s", err.Error())
if f.ignoreError {
continue
}
return nil, err
}
@ -119,7 +127,6 @@ func (f *filterEvaluator) EvalPackage(pkg *models.Package) (*filterEvaluationRes
program: prog,
}, nil
}
}
return &filterEvaluationResult{

View File

@ -8,3 +8,11 @@ type filterEvaluationResult struct {
func (r *filterEvaluationResult) Matched() bool {
return r.match
}
func (r *filterEvaluationResult) GetMatchedFilter() *filterProgram {
if r.program == nil {
return &filterProgram{}
}
return r.program
}

View File

@ -6,15 +6,17 @@ filters:
- name: critical-or-high-vulns
value: |
vulns.critical.exists(p, true) || vulns.high.exists(p, true)
- name: ossf-best-practices
- name: low-popularity
value: |
(scorecard.scores["Maintained"] == 0) ||
(scorecard.scores["Binary-Artifacts"] == 0) ||
(scorecard.scores["Branch-Protection"] == 0) ||
(scorecard.scores["Code-Review"] == 0) ||
(scorecard.scores["Dangerous-Workflow"] == 0)
projects.exists(p, (p.type == "GITHUB") && (p.stars < 10))
- name: risky-oss-licenses
value: |
licenses.exists(p, p == "GPL-2.0") ||
licenses.exists(p, p == "GPL-3.0")
- name: ossf-unmaintained
value: |
scorecard.scores["Maintained"] == 0
- name: ossf-dangerous-workflow
value: |
scorecard.scores["Dangerous-Workflow"] == 0