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) || 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) (scorecard.scores.Maintained == 0)
``` ```

View File

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

View File

@ -1,18 +1,26 @@
package analyzer package analyzer
import ( import (
"fmt"
"os" "os"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/safedep/dry/utils" "github.com/safedep/dry/utils"
"github.com/safedep/vet/gen/filtersuite" "github.com/safedep/vet/gen/filtersuite"
"github.com/safedep/vet/pkg/analyzer/filter" "github.com/safedep/vet/pkg/analyzer/filter"
"github.com/safedep/vet/pkg/models" "github.com/safedep/vet/pkg/models"
) )
type celFilterMatchedPackage struct {
pkg *models.Package
filterName string
}
type celFilterSuiteAnalyzer struct { type celFilterSuiteAnalyzer struct {
evaluator filter.Evaluator evaluator filter.Evaluator
suite *filtersuite.FilterSuite suite *filtersuite.FilterSuite
failOnMatch bool failOnMatch bool
matchedPackages map[string]*celFilterMatchedPackage
} }
func NewCelFilterSuiteAnalyzer(path string, failOnMatch bool) (Analyzer, error) { func NewCelFilterSuiteAnalyzer(path string, failOnMatch bool) (Analyzer, error) {
@ -21,7 +29,7 @@ func NewCelFilterSuiteAnalyzer(path string, failOnMatch bool) (Analyzer, error)
return nil, err return nil, err
} }
evaluator, err := filter.NewEvaluator(fs.GetName()) evaluator, err := filter.NewEvaluator(fs.GetName(), true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -37,6 +45,7 @@ func NewCelFilterSuiteAnalyzer(path string, failOnMatch bool) (Analyzer, error)
evaluator: evaluator, evaluator: evaluator,
suite: fs, suite: fs,
failOnMatch: failOnMatch, failOnMatch: failOnMatch,
matchedPackages: make(map[string]*celFilterMatchedPackage),
}, nil }, nil
} }
@ -46,13 +55,68 @@ func (f *celFilterSuiteAnalyzer) Name() string {
func (f *celFilterSuiteAnalyzer) Analyze(manifest *models.PackageManifest, func (f *celFilterSuiteAnalyzer) Analyze(manifest *models.PackageManifest,
handler AnalyzerEventHandler) error { 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 return nil
} }
func (f *celFilterSuiteAnalyzer) Finish() error { func (f *celFilterSuiteAnalyzer) Finish() error {
f.renderMatchTable()
return nil 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 // To correctly unmarshal a []byte into protobuf message, we must use
// protobuf SDK and not generic JSON / YAML decoder. Since there is no // protobuf SDK and not generic JSON / YAML decoder. Since there is no
// officially supported yamlpb, equivalent to jsonpb, we convert YAML // officially supported yamlpb, equivalent to jsonpb, we convert YAML

View File

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

View File

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

View File

@ -8,3 +8,11 @@ type filterEvaluationResult struct {
func (r *filterEvaluationResult) Matched() bool { func (r *filterEvaluationResult) Matched() bool {
return r.match 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 - name: critical-or-high-vulns
value: | value: |
vulns.critical.exists(p, true) || vulns.high.exists(p, true) vulns.critical.exists(p, true) || vulns.high.exists(p, true)
- name: ossf-best-practices - name: low-popularity
value: | value: |
(scorecard.scores["Maintained"] == 0) || projects.exists(p, (p.type == "GITHUB") && (p.stars < 10))
(scorecard.scores["Binary-Artifacts"] == 0) ||
(scorecard.scores["Branch-Protection"] == 0) ||
(scorecard.scores["Code-Review"] == 0) ||
(scorecard.scores["Dangerous-Workflow"] == 0)
- name: risky-oss-licenses - name: risky-oss-licenses
value: | value: |
licenses.exists(p, p == "GPL-2.0") || licenses.exists(p, p == "GPL-2.0") ||
licenses.exists(p, p == "GPL-3.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