mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-27 19:17:47 +01:00
refactor: remove allowlist in GetSummary of scan controller (#14836)
Signed-off-by: He Weiwei <hweiwei@vmware.com>
This commit is contained in:
parent
1a3335edc5
commit
0c315d8aee
@ -17,8 +17,6 @@ package preheat
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/lib/config"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"strings"
|
||||
|
||||
tk "github.com/docker/distribution/registry/auth/token"
|
||||
@ -29,8 +27,10 @@ import (
|
||||
"github.com/goharbor/harbor/src/controller/tag"
|
||||
"github.com/goharbor/harbor/src/core/service/token"
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
"github.com/goharbor/harbor/src/lib/config"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/lib/selector"
|
||||
"github.com/goharbor/harbor/src/pkg/label/model"
|
||||
@ -40,8 +40,6 @@ import (
|
||||
"github.com/goharbor/harbor/src/pkg/p2p/preheat/models/provider"
|
||||
"github.com/goharbor/harbor/src/pkg/p2p/preheat/policy"
|
||||
pr "github.com/goharbor/harbor/src/pkg/p2p/preheat/provider"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/report"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/vuln"
|
||||
"github.com/goharbor/harbor/src/pkg/task"
|
||||
)
|
||||
@ -483,8 +481,7 @@ func (de *defaultEnforcer) startTask(ctx context.Context, executionID int64, can
|
||||
|
||||
// getVulnerabilitySev gets the severity code value for the given artifact with allowlist option set
|
||||
func (de *defaultEnforcer) getVulnerabilitySev(ctx context.Context, p *models.Project, art *artifact.Artifact) (uint, error) {
|
||||
al := p.CVEAllowlist.CVESet()
|
||||
r, err := de.scanCtl.GetSummary(ctx, art, []string{v1.MimeTypeNativeReport, v1.MimeTypeGenericVulnerabilityReport}, report.WithCVEAllowlist(&al))
|
||||
vulnerable, err := de.scanCtl.GetVulnerable(ctx, art, p.CVEAllowlist.CVESet())
|
||||
if err != nil {
|
||||
if errors.IsNotFoundErr(err) {
|
||||
// no vulnerability report
|
||||
@ -494,25 +491,17 @@ func (de *defaultEnforcer) getVulnerabilitySev(ctx context.Context, p *models.Pr
|
||||
return defaultSeverityCode, errors.Wrap(err, "get vulnerability severity")
|
||||
}
|
||||
|
||||
// Severity is based on the native report format or the generic vulnerability report format.
|
||||
// In case no supported report format, treat as same to the no report scenario
|
||||
sum, ok := r[v1.MimeTypeNativeReport]
|
||||
if !ok {
|
||||
// check if a report with MimeTypeGenericVulnerabilityReport is present.
|
||||
// return the default severity code only if it does not exist
|
||||
sum, ok = r[v1.MimeTypeGenericVulnerabilityReport]
|
||||
if !ok {
|
||||
return defaultSeverityCode, nil
|
||||
}
|
||||
|
||||
if !vulnerable.IsScanSuccess() {
|
||||
// scan status may running or error
|
||||
return defaultSeverityCode, nil
|
||||
}
|
||||
|
||||
sm, ok := sum.(*vuln.NativeReportSummary)
|
||||
if !ok {
|
||||
return defaultSeverityCode, errors.New("malformed native summary report")
|
||||
// no vulnerability found
|
||||
if vulnerable.Severity == nil {
|
||||
return (uint)(vuln.None.Code()), nil
|
||||
}
|
||||
|
||||
return (uint)(sm.Severity.Code()), nil
|
||||
return (uint)(vulnerable.Severity.Code()), nil
|
||||
}
|
||||
|
||||
// toCandidates converts the artifacts to filtering candidates
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
car "github.com/goharbor/harbor/src/controller/artifact"
|
||||
"github.com/goharbor/harbor/src/controller/scan"
|
||||
"github.com/goharbor/harbor/src/controller/tag"
|
||||
"github.com/goharbor/harbor/src/lib/selector"
|
||||
models2 "github.com/goharbor/harbor/src/pkg/allowlist/models"
|
||||
@ -33,12 +34,11 @@ import (
|
||||
pr "github.com/goharbor/harbor/src/pkg/p2p/preheat/models/provider"
|
||||
"github.com/goharbor/harbor/src/pkg/p2p/preheat/provider"
|
||||
"github.com/goharbor/harbor/src/pkg/p2p/preheat/provider/auth"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/vuln"
|
||||
ta "github.com/goharbor/harbor/src/pkg/tag/model/tag"
|
||||
"github.com/goharbor/harbor/src/testing/controller/artifact"
|
||||
"github.com/goharbor/harbor/src/testing/controller/project"
|
||||
"github.com/goharbor/harbor/src/testing/controller/scan"
|
||||
scantesting "github.com/goharbor/harbor/src/testing/controller/scan"
|
||||
"github.com/goharbor/harbor/src/testing/mock"
|
||||
"github.com/goharbor/harbor/src/testing/pkg/p2p/preheat/instance"
|
||||
"github.com/goharbor/harbor/src/testing/pkg/p2p/preheat/policy"
|
||||
@ -104,13 +104,13 @@ func (suite *EnforcerTestSuite) SetupSuite() {
|
||||
mock.AnythingOfType("*artifact.Option"),
|
||||
).Return(mockArtifacts(), nil)
|
||||
|
||||
fakeScanCtl := &scan.Controller{}
|
||||
fakeScanCtl.On("GetSummary",
|
||||
low := vuln.Low
|
||||
fakeScanCtl := &scantesting.Controller{}
|
||||
fakeScanCtl.On("GetVulnerable",
|
||||
context.TODO(),
|
||||
mock.AnythingOfType("*artifact.Artifact"),
|
||||
[]string{v1.MimeTypeNativeReport, v1.MimeTypeGenericVulnerabilityReport},
|
||||
mock.AnythingOfType("report.Option"),
|
||||
).Return(mockVulnerabilitySummary(), nil)
|
||||
mock.AnythingOfType("models.CVESet"),
|
||||
).Return(&scan.Vulnerable{Severity: &low, ScanStatus: "Success"}, nil)
|
||||
|
||||
fakeProCtl := &project.Controller{}
|
||||
fakeProCtl.On("Get",
|
||||
@ -304,16 +304,3 @@ func mockArtifacts() []*car.Artifact {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// mock vulnerability summary
|
||||
func mockVulnerabilitySummary() map[string]interface{} {
|
||||
// skip all unused properties
|
||||
return map[string]interface{}{
|
||||
v1.MimeTypeNativeReport: &vuln.NativeReportSummary{
|
||||
Severity: vuln.Low,
|
||||
},
|
||||
v1.MimeTypeGenericVulnerabilityReport: &vuln.NativeReportSummary{
|
||||
Severity: vuln.Low,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/lib/config"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@ -28,10 +28,12 @@ import (
|
||||
sc "github.com/goharbor/harbor/src/controller/scanner"
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
"github.com/goharbor/harbor/src/lib/config"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
allowlist "github.com/goharbor/harbor/src/pkg/allowlist/models"
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
sca "github.com/goharbor/harbor/src/pkg/scan"
|
||||
@ -533,7 +535,7 @@ func (bc *basicController) GetReport(ctx context.Context, artifact *ar.Artifact,
|
||||
}
|
||||
|
||||
// GetSummary ...
|
||||
func (bc *basicController) GetSummary(ctx context.Context, artifact *ar.Artifact, mimeTypes []string, options ...report.Option) (map[string]interface{}, error) {
|
||||
func (bc *basicController) GetSummary(ctx context.Context, artifact *ar.Artifact, mimeTypes []string) (map[string]interface{}, error) {
|
||||
if artifact == nil {
|
||||
return nil, errors.New("no way to get report summaries for nil artifact")
|
||||
}
|
||||
@ -546,7 +548,7 @@ func (bc *basicController) GetSummary(ctx context.Context, artifact *ar.Artifact
|
||||
|
||||
summaries := make(map[string]interface{}, len(rps))
|
||||
for _, rp := range rps {
|
||||
sum, err := report.GenerateSummary(rp, options...)
|
||||
sum, err := report.GenerateSummary(rp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -699,6 +701,85 @@ func (bc *basicController) DeleteReports(ctx context.Context, digests ...string)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bc *basicController) GetVulnerable(ctx context.Context, artifact *ar.Artifact, allowlist allowlist.CVESet) (*Vulnerable, error) {
|
||||
if artifact == nil {
|
||||
return nil, errors.New("no way to get vulnerable for nil artifact")
|
||||
}
|
||||
|
||||
var (
|
||||
mimeType string
|
||||
reports []*scan.Report
|
||||
)
|
||||
for _, m := range []string{v1.MimeTypeNativeReport, v1.MimeTypeGenericVulnerabilityReport} {
|
||||
rps, err := bc.GetReport(ctx, artifact, []string{m})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(rps) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
mimeType = m
|
||||
reports = rps
|
||||
break
|
||||
}
|
||||
|
||||
if len(reports) == 0 {
|
||||
return nil, errors.NotFoundError(nil).WithMessage("report not found")
|
||||
}
|
||||
|
||||
scanStatus := reports[0].Status
|
||||
for _, report := range reports {
|
||||
scanStatus = vuln.MergeScanStatus(scanStatus, report.Status)
|
||||
}
|
||||
|
||||
vulnerable := &Vulnerable{
|
||||
ScanStatus: scanStatus,
|
||||
}
|
||||
|
||||
if !vulnerable.IsScanSuccess() {
|
||||
return vulnerable, nil
|
||||
}
|
||||
|
||||
raw, err := report.Reports(reports).ResolveData(mimeType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rp, ok := raw.(*vuln.Report)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("type mismatch: expect *vuln.Report but got %s", reflect.TypeOf(raw).String())
|
||||
}
|
||||
|
||||
if vuls := rp.GetVulnerabilityItemList().Items(); len(vuls) > 0 {
|
||||
vulnerable.VulnerabilitiesCount = len(vuls)
|
||||
|
||||
var severity vuln.Severity
|
||||
|
||||
for _, v := range vuls {
|
||||
if allowlist.Contains(v.ID) {
|
||||
// Append the by passed CVEs specified in the allowlist
|
||||
vulnerable.CVEBypassed = append(vulnerable.CVEBypassed, v.ID)
|
||||
|
||||
vulnerable.VulnerabilitiesCount--
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if severity == "" || v.Severity.Code() > severity.Code() {
|
||||
severity = v.Severity
|
||||
}
|
||||
}
|
||||
|
||||
if severity != "" {
|
||||
vulnerable.Severity = &severity
|
||||
}
|
||||
}
|
||||
|
||||
return vulnerable, nil
|
||||
}
|
||||
|
||||
// makeRobotAccount creates a robot account based on the arguments for scanning.
|
||||
func (bc *basicController) makeRobotAccount(ctx context.Context, projectID int64, repository string, registration *scanner.Registration) (*robot.Robot, error) {
|
||||
// Use uuid as name to avoid duplicated entries.
|
||||
|
@ -18,14 +18,27 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/goharbor/harbor/src/controller/artifact"
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
allowlist "github.com/goharbor/harbor/src/pkg/allowlist/models"
|
||||
sca "github.com/goharbor/harbor/src/pkg/scan"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scan"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/report"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/vuln"
|
||||
)
|
||||
|
||||
// Vulnerable ...
|
||||
type Vulnerable struct {
|
||||
VulnerabilitiesCount int
|
||||
ScanStatus string
|
||||
Severity *vuln.Severity
|
||||
CVEBypassed []string
|
||||
}
|
||||
|
||||
// IsScanSuccess returns true when the artifact scanned success
|
||||
func (v *Vulnerable) IsScanSuccess() bool {
|
||||
return v.ScanStatus == job.SuccessStatus.String()
|
||||
}
|
||||
|
||||
// Controller provides the related operations for triggering scan.
|
||||
// TODO: Here the artifact object is reused the v1 one which is sent to the adapter,
|
||||
// it should be pointed to the general artifact object in future once it's ready.
|
||||
type Controller interface {
|
||||
// Scan the given artifact
|
||||
//
|
||||
@ -56,12 +69,11 @@ type Controller interface {
|
||||
// ctx context.Context : the context for this method
|
||||
// artifact *artifact.Artifact : the scanned artifact
|
||||
// mimeTypes []string : the mime types of the reports
|
||||
// options ...report.Option : optional report options, specify if needed
|
||||
//
|
||||
// Returns:
|
||||
// map[string]interface{} : report summaries indexed by mime types
|
||||
// error : non nil error if any errors occurred
|
||||
GetSummary(ctx context.Context, artifact *artifact.Artifact, mimeTypes []string, options ...report.Option) (map[string]interface{}, error)
|
||||
GetSummary(ctx context.Context, artifact *artifact.Artifact, mimeTypes []string) (map[string]interface{}, error)
|
||||
|
||||
// Get the scan log for the specified artifact with the given digest
|
||||
//
|
||||
@ -103,4 +115,15 @@ type Controller interface {
|
||||
// Returns:
|
||||
// error : non nil error if any errors occurred
|
||||
ScanAll(ctx context.Context, trigger string, async bool) (int64, error)
|
||||
|
||||
// GetVulnerable returns the vulnerable of the artifact for the allowlist
|
||||
//
|
||||
// Arguments:
|
||||
// ctx context.Context : the context for this method
|
||||
// artifact *artifact.Artifact : artifact to be scanned
|
||||
//
|
||||
// Returns
|
||||
// *Vulnerable : the vulnerable
|
||||
// error : non nil error if any errors occurred
|
||||
GetVulnerable(ctx context.Context, artifact *artifact.Artifact, allowlist allowlist.CVESet) (*Vulnerable, error)
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ package report
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scan"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/vuln"
|
||||
)
|
||||
@ -53,3 +54,36 @@ func MergeNativeReport(r1, r2 interface{}) (interface{}, error) {
|
||||
|
||||
return nr1.Merge(nr2), nil
|
||||
}
|
||||
|
||||
// Reports slice of scan.Reports pointer
|
||||
type Reports []*scan.Report
|
||||
|
||||
// ResolveData resolve the data from the reports and merge them together
|
||||
func (l Reports) ResolveData(mimeType string) (interface{}, error) {
|
||||
var result interface{}
|
||||
|
||||
for _, rp := range l {
|
||||
// Resolve scan report data only when it is ready and its mime type equal the given one
|
||||
if len(rp.Report) == 0 || rp.MimeType != mimeType {
|
||||
continue
|
||||
}
|
||||
|
||||
vrp, err := ResolveData(rp.MimeType, []byte(rp.Report), WithArtifactDigest(rp.Digest))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
result = vrp
|
||||
} else {
|
||||
r, err := Merge(rp.MimeType, result, vrp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = r
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ import (
|
||||
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
models2 "github.com/goharbor/harbor/src/pkg/allowlist/models"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scan"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/vuln"
|
||||
@ -29,20 +28,11 @@ import (
|
||||
type Options struct {
|
||||
// If it is set, the returned report will contains artifact digest for the vulnerabilities
|
||||
ArtifactDigest string
|
||||
// If it is set, the returned summary will not count the CVEs in the list in.
|
||||
CVEAllowlist models2.CVESet
|
||||
}
|
||||
|
||||
// Option for getting the report w/ summary with func template way.
|
||||
type Option func(options *Options)
|
||||
|
||||
// WithCVEAllowlist is an option of setting CVE allowlist.
|
||||
func WithCVEAllowlist(set *models2.CVESet) Option {
|
||||
return func(options *Options) {
|
||||
options.CVEAllowlist = *set
|
||||
}
|
||||
}
|
||||
|
||||
// WithArtifactDigest is an option of setting artifact digest
|
||||
func WithArtifactDigest(artifactDigest string) Option {
|
||||
return func(options *Options) {
|
||||
@ -107,11 +97,6 @@ type SummaryGenerator func(r *scan.Report, options ...Option) (interface{}, erro
|
||||
|
||||
// GenerateNativeSummary generates the report summary for the native report.
|
||||
func GenerateNativeSummary(r *scan.Report, options ...Option) (interface{}, error) {
|
||||
ops := &Options{}
|
||||
for _, op := range options {
|
||||
op(ops)
|
||||
}
|
||||
|
||||
sum := &vuln.NativeReportSummary{}
|
||||
sum.ReportID = r.UUID
|
||||
sum.StartTime = r.StartTime
|
||||
@ -120,9 +105,6 @@ func GenerateNativeSummary(r *scan.Report, options ...Option) (interface{}, erro
|
||||
if sum.Duration < 0 {
|
||||
sum.Duration = 0
|
||||
}
|
||||
if len(ops.CVEAllowlist) > 0 {
|
||||
sum.CVEBypassed = make([]string, 0)
|
||||
}
|
||||
|
||||
sum.ScanStatus = job.ErrorStatus.String()
|
||||
if job.Status(r.Status).Code() != -1 {
|
||||
@ -157,7 +139,7 @@ func GenerateNativeSummary(r *scan.Report, options ...Option) (interface{}, erro
|
||||
sum.Severity = rp.Severity
|
||||
sum.Scanner = rp.Scanner
|
||||
|
||||
sum.UpdateSeveritySummaryAndByPassed(rp.GetVulnerabilityItemList(), ops.CVEAllowlist)
|
||||
sum.UpdateSeveritySummary(rp.GetVulnerabilityItemList())
|
||||
|
||||
return sum, nil
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
models2 "github.com/goharbor/harbor/src/pkg/allowlist/models"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scan"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/vuln"
|
||||
@ -103,23 +102,6 @@ func (suite *SummaryTestSuite) TestSummaryGenerateSummaryNoOptions() {
|
||||
suite.Equal("0.1.0", nativeSummary.Scanner.Version)
|
||||
}
|
||||
|
||||
// TestSummaryGenerateSummaryWithOptions ...
|
||||
func (suite *SummaryTestSuite) TestSummaryGenerateSummaryWithOptions() {
|
||||
cveSet := make(models2.CVESet)
|
||||
cveSet["2019-0980-0909"] = struct{}{}
|
||||
|
||||
summaries, err := GenerateSummary(suite.r, WithCVEAllowlist(&cveSet))
|
||||
require.NoError(suite.T(), err)
|
||||
require.NotNil(suite.T(), summaries)
|
||||
|
||||
nativeSummary, ok := summaries.(*vuln.NativeReportSummary)
|
||||
require.Equal(suite.T(), true, ok)
|
||||
|
||||
suite.Equal(vuln.Medium, nativeSummary.Severity)
|
||||
suite.Equal(1, len(nativeSummary.CVEBypassed))
|
||||
suite.Equal(1, nativeSummary.Summary.Total)
|
||||
}
|
||||
|
||||
// TestSummaryGenerateSummaryWrongMime ...
|
||||
func (suite *SummaryTestSuite) TestSummaryGenerateSummaryWrongMime() {
|
||||
suite.r.MimeType = "wrong-mime"
|
||||
|
@ -18,7 +18,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
models2 "github.com/goharbor/harbor/src/pkg/allowlist/models"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
)
|
||||
|
||||
@ -149,27 +148,19 @@ func (l *VulnerabilityItemList) Add(items ...*VulnerabilityItem) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetSeveritySummaryAndByPassed returns the Severity Summary and ByPassed by allowlist for the l
|
||||
func (l *VulnerabilityItemList) GetSeveritySummaryAndByPassed(allowlist models2.CVESet) (Severity, *VulnerabilitySummary, []string) {
|
||||
// GetSeveritySummary returns the severity and summary of l
|
||||
func (l *VulnerabilityItemList) GetSeveritySummary() (Severity, *VulnerabilitySummary) {
|
||||
if l == nil {
|
||||
return Severity(""), nil
|
||||
}
|
||||
|
||||
sum := &VulnerabilitySummary{
|
||||
Total: len(l.Items()),
|
||||
Summary: make(SeveritySummary),
|
||||
}
|
||||
|
||||
var bypassed []string
|
||||
|
||||
severity := None
|
||||
for _, v := range l.Items() {
|
||||
if len(allowlist) > 0 && allowlist.Contains(v.ID) {
|
||||
// If allowlist is set, then check if we need to bypass it
|
||||
// Reduce the total
|
||||
sum.Total--
|
||||
// Append the by passed CVEs specified in the allowlist
|
||||
bypassed = append(bypassed, v.ID)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if num, ok := sum.Summary[v.Severity]; ok {
|
||||
sum.Summary[v.Severity] = num + 1
|
||||
} else {
|
||||
@ -180,14 +171,13 @@ func (l *VulnerabilityItemList) GetSeveritySummaryAndByPassed(allowlist models2.
|
||||
if v.Severity.Code() > severity.Code() {
|
||||
severity = v.Severity
|
||||
}
|
||||
|
||||
// If the CVE item has a fixable version
|
||||
if len(v.FixVersion) > 0 {
|
||||
sum.Fixable++
|
||||
}
|
||||
}
|
||||
|
||||
return severity, sum, bypassed
|
||||
return severity, sum
|
||||
}
|
||||
|
||||
// VulnerabilityItem represents one found vulnerability
|
||||
|
@ -19,7 +19,6 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
models2 "github.com/goharbor/harbor/src/pkg/allowlist/models"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -80,7 +79,7 @@ func TestReportMarshalJSON(t *testing.T) {
|
||||
assert.Contains(string(b), "vulnerabilities")
|
||||
}
|
||||
|
||||
func TestGetSummarySeverityAndByPassed(t *testing.T) {
|
||||
func TestGetSummarySeverity(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
vul1 := &VulnerabilityItem{
|
||||
@ -102,34 +101,14 @@ func TestGetSummarySeverityAndByPassed(t *testing.T) {
|
||||
l := VulnerabilityItemList{}
|
||||
l.Add(vul1, vul2, vul3)
|
||||
|
||||
{
|
||||
s := SeveritySummary{
|
||||
Low: 2,
|
||||
Medium: 1,
|
||||
}
|
||||
|
||||
severity, sum, byPassed := l.GetSeveritySummaryAndByPassed(models2.CVESet{})
|
||||
assert.Equal(3, sum.Total)
|
||||
assert.Equal(1, sum.Fixable)
|
||||
assert.Equal(s, sum.Summary)
|
||||
assert.Equal(Medium, severity)
|
||||
assert.Empty(byPassed)
|
||||
s := SeveritySummary{
|
||||
Low: 2,
|
||||
Medium: 1,
|
||||
}
|
||||
|
||||
{
|
||||
s := SeveritySummary{
|
||||
Low: 2,
|
||||
}
|
||||
|
||||
cveSet := models2.CVESet{}
|
||||
cveSet.Add("cve3")
|
||||
|
||||
severity, sum, byPassed := l.GetSeveritySummaryAndByPassed(cveSet)
|
||||
assert.Equal(2, sum.Total)
|
||||
assert.Equal(1, sum.Fixable)
|
||||
assert.Equal(s, sum.Summary)
|
||||
assert.Equal(Low, severity)
|
||||
assert.NotEmpty(byPassed)
|
||||
assert.Equal([]string{"cve3"}, byPassed)
|
||||
}
|
||||
severity, sum := l.GetSeveritySummary()
|
||||
assert.Equal(Medium, severity)
|
||||
assert.Equal(3, sum.Total)
|
||||
assert.Equal(1, sum.Fixable)
|
||||
assert.Equal(s, sum.Summary)
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
models2 "github.com/goharbor/harbor/src/pkg/allowlist/models"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
)
|
||||
|
||||
@ -39,30 +38,12 @@ type NativeReportSummary struct {
|
||||
TotalCount int `json:"-"`
|
||||
CompleteCount int `json:"-"`
|
||||
VulnerabilityItemList *VulnerabilityItemList `json:"-"`
|
||||
CVESet models2.CVESet `json:"-"`
|
||||
}
|
||||
|
||||
// UpdateSeveritySummaryAndByPassed update the Severity, Summary and CVEBypassed of the sum from l and s
|
||||
func (sum *NativeReportSummary) UpdateSeveritySummaryAndByPassed(l *VulnerabilityItemList, s models2.CVESet) {
|
||||
// UpdateSeveritySummary update the Severity, Summary of the sum from l
|
||||
func (sum *NativeReportSummary) UpdateSeveritySummary(l *VulnerabilityItemList) {
|
||||
sum.VulnerabilityItemList = l
|
||||
sum.CVESet = s
|
||||
|
||||
if l == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var severity Severity
|
||||
severity, sum.Summary, sum.CVEBypassed = l.GetSeveritySummaryAndByPassed(s)
|
||||
|
||||
if len(s) > 0 {
|
||||
// Override the overall severity of the filtered list if needed.
|
||||
sum.Severity = severity
|
||||
}
|
||||
}
|
||||
|
||||
// IsSuccessStatus returns true when the scan status is success
|
||||
func (sum *NativeReportSummary) IsSuccessStatus() bool {
|
||||
return sum.ScanStatus == job.SuccessStatus.String()
|
||||
sum.Severity, sum.Summary = l.GetSeveritySummary()
|
||||
}
|
||||
|
||||
// Merge ...
|
||||
@ -79,17 +60,17 @@ func (sum *NativeReportSummary) Merge(another *NativeReportSummary) *NativeRepor
|
||||
} else {
|
||||
r.Scanner = another.Scanner
|
||||
}
|
||||
|
||||
r.TotalCount = sum.TotalCount + another.TotalCount
|
||||
r.CompleteCount = sum.CompleteCount + another.CompleteCount
|
||||
r.CompletePercent = r.CompleteCount * 100 / r.TotalCount
|
||||
r.ReportID = mergeReportID(sum.ReportID, another.ReportID)
|
||||
r.Severity = mergeSeverity(sum.Severity, another.Severity)
|
||||
r.ScanStatus = mergeScanStatus(sum.ScanStatus, another.ScanStatus)
|
||||
r.ScanStatus = MergeScanStatus(sum.ScanStatus, another.ScanStatus)
|
||||
|
||||
r.UpdateSeveritySummaryAndByPassed(
|
||||
NewVulnerabilityItemList(sum.VulnerabilityItemList, another.VulnerabilityItemList),
|
||||
models2.NewCVESet(sum.CVESet, another.CVESet),
|
||||
)
|
||||
if r.ScanStatus != job.RunningStatus.String() {
|
||||
l := NewVulnerabilityItemList(sum.VulnerabilityItemList, another.VulnerabilityItemList)
|
||||
r.UpdateSeveritySummary(l)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ func TestMergeNativeReportSummary(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
errorStatus := job.ErrorStatus.String()
|
||||
runningStatus := job.RunningStatus.String()
|
||||
successStatus := job.SuccessStatus.String()
|
||||
|
||||
v1 := VulnerabilitySummary{
|
||||
Total: 1,
|
||||
@ -42,82 +43,135 @@ func TestMergeNativeReportSummary(t *testing.T) {
|
||||
})
|
||||
|
||||
{
|
||||
n1 := NativeReportSummary{
|
||||
ScanStatus: runningStatus,
|
||||
Severity: Low,
|
||||
TotalCount: 1,
|
||||
Summary: &v1,
|
||||
VulnerabilityItemList: l,
|
||||
}
|
||||
|
||||
r := n1.Merge(&NativeReportSummary{
|
||||
ScanStatus: errorStatus,
|
||||
Severity: Severity(""),
|
||||
TotalCount: 1,
|
||||
})
|
||||
|
||||
assert.Equal(runningStatus, r.ScanStatus)
|
||||
assert.Equal(Low, r.Severity)
|
||||
assert.Equal(v1, *r.Summary)
|
||||
}
|
||||
|
||||
{
|
||||
n1 := NativeReportSummary{
|
||||
// running && running
|
||||
n1 := &NativeReportSummary{
|
||||
ScanStatus: runningStatus,
|
||||
Severity: Severity(""),
|
||||
TotalCount: 1,
|
||||
}
|
||||
|
||||
r := n1.Merge(&NativeReportSummary{
|
||||
ScanStatus: errorStatus,
|
||||
Severity: Severity(""),
|
||||
ScanStatus: runningStatus,
|
||||
TotalCount: 1,
|
||||
})
|
||||
|
||||
assert.Equal(runningStatus, r.ScanStatus)
|
||||
assert.Equal(Severity(""), r.Severity)
|
||||
assert.Nil(r.Summary)
|
||||
}
|
||||
|
||||
{
|
||||
n1 := &NativeReportSummary{
|
||||
ScanStatus: errorStatus,
|
||||
Severity: Severity(""),
|
||||
// running && success
|
||||
n1 := NativeReportSummary{
|
||||
ScanStatus: runningStatus,
|
||||
TotalCount: 1,
|
||||
}
|
||||
|
||||
r := n1.Merge(&NativeReportSummary{
|
||||
ScanStatus: runningStatus,
|
||||
ScanStatus: successStatus,
|
||||
Severity: Low,
|
||||
TotalCount: 1,
|
||||
CompleteCount: 1,
|
||||
Summary: &v1,
|
||||
VulnerabilityItemList: l,
|
||||
})
|
||||
|
||||
assert.Equal(runningStatus, r.ScanStatus)
|
||||
assert.Equal(Low, r.Severity)
|
||||
assert.Equal(v1, *r.Summary)
|
||||
assert.Nil(r.Summary)
|
||||
}
|
||||
|
||||
{
|
||||
n1 := &NativeReportSummary{
|
||||
ScanStatus: runningStatus,
|
||||
// running && error
|
||||
n1 := NativeReportSummary{
|
||||
ScanStatus: runningStatus,
|
||||
TotalCount: 1,
|
||||
}
|
||||
|
||||
r := n1.Merge(&NativeReportSummary{
|
||||
ScanStatus: errorStatus,
|
||||
TotalCount: 1,
|
||||
})
|
||||
|
||||
assert.Equal(runningStatus, r.ScanStatus)
|
||||
assert.Nil(r.Summary)
|
||||
}
|
||||
|
||||
{
|
||||
// success && success
|
||||
n1 := NativeReportSummary{
|
||||
ScanStatus: successStatus,
|
||||
Severity: Low,
|
||||
TotalCount: 1,
|
||||
CompleteCount: 1,
|
||||
Summary: &v1,
|
||||
VulnerabilityItemList: l,
|
||||
}
|
||||
|
||||
l2 := &VulnerabilityItemList{}
|
||||
l2.Add(&VulnerabilityItem{
|
||||
ID: "cve-id-high",
|
||||
Package: "openssl-libs-high",
|
||||
Version: "1:1.1.1g-11.el8",
|
||||
Severity: High,
|
||||
FixVersion: "1:1.1.1g-12.el8_3",
|
||||
})
|
||||
|
||||
r := n1.Merge(&NativeReportSummary{
|
||||
ScanStatus: successStatus,
|
||||
Severity: High,
|
||||
TotalCount: 1,
|
||||
CompleteCount: 1,
|
||||
Summary: &VulnerabilitySummary{
|
||||
Total: 1,
|
||||
Fixable: 1,
|
||||
Summary: map[Severity]int{High: 1},
|
||||
},
|
||||
VulnerabilityItemList: l2,
|
||||
})
|
||||
|
||||
assert.Equal(successStatus, r.ScanStatus)
|
||||
assert.Equal(High, r.Severity)
|
||||
assert.Equal(VulnerabilitySummary{
|
||||
Total: 2,
|
||||
Fixable: 2,
|
||||
Summary: map[Severity]int{Low: 1, High: 1},
|
||||
}, *r.Summary)
|
||||
assert.Equal(100, r.CompletePercent)
|
||||
}
|
||||
|
||||
{
|
||||
// success && error
|
||||
n1 := NativeReportSummary{
|
||||
ScanStatus: successStatus,
|
||||
Severity: Low,
|
||||
TotalCount: 1,
|
||||
CompleteCount: 1,
|
||||
Summary: &v1,
|
||||
VulnerabilityItemList: l,
|
||||
}
|
||||
|
||||
r := n1.Merge(&NativeReportSummary{
|
||||
ScanStatus: runningStatus,
|
||||
Severity: Low,
|
||||
TotalCount: 1,
|
||||
Summary: &v1,
|
||||
VulnerabilityItemList: l,
|
||||
ScanStatus: errorStatus,
|
||||
TotalCount: 1,
|
||||
})
|
||||
|
||||
assert.Equal(runningStatus, r.ScanStatus)
|
||||
assert.Equal(successStatus, r.ScanStatus)
|
||||
assert.Equal(Low, r.Severity)
|
||||
assert.Equal(v1, *r.Summary)
|
||||
assert.Equal(50, r.CompletePercent)
|
||||
}
|
||||
|
||||
{
|
||||
// error && error
|
||||
n1 := NativeReportSummary{
|
||||
ScanStatus: errorStatus,
|
||||
TotalCount: 1,
|
||||
}
|
||||
|
||||
r := n1.Merge(&NativeReportSummary{
|
||||
ScanStatus: errorStatus,
|
||||
TotalCount: 1,
|
||||
})
|
||||
|
||||
assert.Equal(errorStatus, r.ScanStatus)
|
||||
assert.Nil(r.Summary)
|
||||
}
|
||||
}
|
||||
|
@ -79,12 +79,14 @@ func mergeSeverity(s1, s2 Severity) Severity {
|
||||
return s2
|
||||
}
|
||||
|
||||
func mergeScanStatus(s1, s2 string) string {
|
||||
// MergeScanStatus ...
|
||||
func MergeScanStatus(s1, s2 string) string {
|
||||
j1, j2 := job.Status(s1), job.Status(s2)
|
||||
|
||||
if j1 == job.RunningStatus || j2 == job.RunningStatus {
|
||||
return job.RunningStatus.String()
|
||||
} else if j1 == job.SuccessStatus || j2 == job.SuccessStatus {
|
||||
// the scan status of the image index will be treated as a success when one of its children is success
|
||||
return job.SuccessStatus.String()
|
||||
}
|
||||
|
||||
|
@ -91,7 +91,7 @@ func Test_mergeScanStatus(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := mergeScanStatus(tt.args.s1, tt.args.s2); got != tt.want {
|
||||
if got := MergeScanStatus(tt.args.s1, tt.args.s2); got != tt.want {
|
||||
t.Errorf("mergeScanStatus() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
|
@ -25,8 +25,6 @@ import (
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/report"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/vuln"
|
||||
"github.com/goharbor/harbor/src/server/middleware"
|
||||
"github.com/goharbor/harbor/src/server/middleware/util"
|
||||
@ -92,28 +90,20 @@ func Middleware() func(http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
allowlist := proj.CVEAllowlist.CVESet()
|
||||
summaries, err := scanController.GetSummary(ctx, art, []string{v1.MimeTypeNativeReport, v1.MimeTypeGenericVulnerabilityReport}, report.WithCVEAllowlist(&allowlist))
|
||||
if err != nil {
|
||||
logger.Errorf("get vulnerability summary of the artifact %s@%s failed, error: %v", art.RepositoryName, art.Digest, err)
|
||||
return err
|
||||
}
|
||||
|
||||
projectSeverity := vuln.ParseSeverityVersion3(proj.Severity())
|
||||
|
||||
rawSummary, ok := summaries[v1.MimeTypeNativeReport]
|
||||
if !ok {
|
||||
rawSummary, ok = summaries[v1.MimeTypeGenericVulnerabilityReport]
|
||||
if !ok {
|
||||
vulnerable, err := scanController.GetVulnerable(ctx, art, allowlist)
|
||||
if err != nil {
|
||||
if errors.IsNotFoundErr(err) {
|
||||
// No report yet?
|
||||
msg := fmt.Sprintf(`current image without vulnerability scanning cannot be pulled due to configured policy in 'Prevent images with vulnerability severity of "%s" or higher from running.' `+
|
||||
`To continue with pull, please contact your project administrator for help.`, projectSeverity)
|
||||
return errors.New(nil).WithCode(errors.PROJECTPOLICYVIOLATION).WithMessage(msg)
|
||||
}
|
||||
}
|
||||
|
||||
summary, ok := rawSummary.(*vuln.NativeReportSummary)
|
||||
if !ok {
|
||||
return fmt.Errorf("report summary is invalid")
|
||||
logger.Errorf("get vulnerability summary of the artifact %s@%s failed, error: %v", art.RepositoryName, art.Digest, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if art.IsImageIndex() {
|
||||
@ -128,36 +118,27 @@ func Middleware() func(http.Handler) http.Handler {
|
||||
}
|
||||
}
|
||||
|
||||
if !summary.IsSuccessStatus() {
|
||||
if !vulnerable.IsScanSuccess() {
|
||||
msg := fmt.Sprintf(`current image with "%s" status of vulnerability scanning cannot be pulled due to configured policy in 'Prevent images with vulnerability severity of "%s" or higher from running.' `+
|
||||
`To continue with pull, please contact your project administrator for help.`, summary.ScanStatus, projectSeverity)
|
||||
`To continue with pull, please contact your project administrator for help.`, vulnerable.ScanStatus, projectSeverity)
|
||||
return errors.New(nil).WithCode(errors.PROJECTPOLICYVIOLATION).WithMessage(msg)
|
||||
}
|
||||
|
||||
if summary.Summary == nil || summary.Summary.Total == 0 {
|
||||
// No vulnerabilities found in the artifact, skip the checking
|
||||
// See https://github.com/goharbor/harbor/issues/11210 to get more details
|
||||
logger.Debugf("no vulnerabilities found in artifact %s@%s, skip the vulnerability prevention checking", art.RepositoryName, art.Digest)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do judgement
|
||||
if summary.Severity.Code() >= projectSeverity.Code() {
|
||||
if vulnerable.Severity != nil && vulnerable.Severity.Code() >= projectSeverity.Code() {
|
||||
thing := "vulnerability"
|
||||
if summary.Summary.Total > 1 {
|
||||
if vulnerable.VulnerabilitiesCount > 1 {
|
||||
thing = "vulnerabilities"
|
||||
}
|
||||
msg := fmt.Sprintf(`current image with %d %s cannot be pulled due to configured policy in 'Prevent images with vulnerability severity of "%s" or higher from running.' `+
|
||||
`To continue with pull, please contact your project administrator to exempt matched vulnerabilities through configuring the CVE allowlist.`,
|
||||
summary.Summary.Total, thing, projectSeverity)
|
||||
vulnerable.VulnerabilitiesCount, thing, projectSeverity)
|
||||
return errors.New(nil).WithCode(errors.PROJECTPOLICYVIOLATION).WithMessage(msg)
|
||||
}
|
||||
|
||||
// Print scannerPull CVE list
|
||||
if len(summary.CVEBypassed) > 0 {
|
||||
for _, cve := range summary.CVEBypassed {
|
||||
logger.Infof("Vulnerable policy check: bypassed CVE %s", cve)
|
||||
}
|
||||
for _, cve := range vulnerable.CVEBypassed {
|
||||
logger.Infof("Vulnerable policy check: bypassed CVE %s", cve)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -28,7 +28,7 @@ import (
|
||||
"github.com/goharbor/harbor/src/controller/project"
|
||||
"github.com/goharbor/harbor/src/controller/scan"
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/vuln"
|
||||
securitytesting "github.com/goharbor/harbor/src/testing/common/security"
|
||||
artifacttesting "github.com/goharbor/harbor/src/testing/controller/artifact"
|
||||
@ -222,7 +222,7 @@ func (suite *MiddlewareTestSuite) TestArtifactNotScanned() {
|
||||
mock.OnAnything(suite.artifactController, "GetByReference").Return(suite.artifact, nil)
|
||||
mock.OnAnything(suite.projectController, "Get").Return(suite.project, nil)
|
||||
mock.OnAnything(suite.checker, "IsScannable").Return(true, nil)
|
||||
mock.OnAnything(suite.scanController, "GetSummary").Return(nil, nil)
|
||||
mock.OnAnything(suite.scanController, "GetVulnerable").Return(nil, errors.NotFoundError(nil))
|
||||
|
||||
req := suite.makeRequest()
|
||||
rr := httptest.NewRecorder()
|
||||
@ -235,12 +235,7 @@ func (suite *MiddlewareTestSuite) TestArtifactScanFailed() {
|
||||
mock.OnAnything(suite.artifactController, "GetByReference").Return(suite.artifact, nil)
|
||||
mock.OnAnything(suite.projectController, "Get").Return(suite.project, nil)
|
||||
mock.OnAnything(suite.checker, "IsScannable").Return(true, nil)
|
||||
mock.OnAnything(suite.scanController, "GetSummary").Return(map[string]interface{}{
|
||||
v1.MimeTypeNativeReport: &vuln.NativeReportSummary{
|
||||
ScanStatus: "Error",
|
||||
CVEBypassed: []string{"cve-2020"},
|
||||
},
|
||||
}, nil)
|
||||
mock.OnAnything(suite.scanController, "GetVulnerable").Return(&scan.Vulnerable{ScanStatus: "Error"}, nil)
|
||||
|
||||
req := suite.makeRequest()
|
||||
rr := httptest.NewRecorder()
|
||||
@ -249,26 +244,11 @@ func (suite *MiddlewareTestSuite) TestArtifactScanFailed() {
|
||||
suite.Equal(rr.Code, http.StatusPreconditionFailed)
|
||||
}
|
||||
|
||||
func (suite *MiddlewareTestSuite) TestGetSummaryFailed() {
|
||||
func (suite *MiddlewareTestSuite) TestGetVulnerableFailed() {
|
||||
mock.OnAnything(suite.artifactController, "GetByReference").Return(suite.artifact, nil)
|
||||
mock.OnAnything(suite.projectController, "Get").Return(suite.project, nil)
|
||||
mock.OnAnything(suite.checker, "IsScannable").Return(true, nil)
|
||||
mock.OnAnything(suite.scanController, "GetSummary").Return(nil, fmt.Errorf("error"))
|
||||
|
||||
req := suite.makeRequest()
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
Middleware()(suite.next).ServeHTTP(rr, req)
|
||||
suite.Equal(rr.Code, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
func (suite *MiddlewareTestSuite) TestBadSummary() {
|
||||
mock.OnAnything(suite.artifactController, "GetByReference").Return(suite.artifact, nil)
|
||||
mock.OnAnything(suite.projectController, "Get").Return(suite.project, nil)
|
||||
mock.OnAnything(suite.checker, "IsScannable").Return(true, nil)
|
||||
mock.OnAnything(suite.scanController, "GetSummary").Return(map[string]interface{}{
|
||||
v1.MimeTypeNativeReport: "bad report",
|
||||
}, nil)
|
||||
mock.OnAnything(suite.scanController, "GetVulnerable").Return(nil, fmt.Errorf("error"))
|
||||
|
||||
req := suite.makeRequest()
|
||||
rr := httptest.NewRecorder()
|
||||
@ -281,32 +261,9 @@ func (suite *MiddlewareTestSuite) TestNoVulnerabilities() {
|
||||
mock.OnAnything(suite.artifactController, "GetByReference").Return(suite.artifact, nil)
|
||||
mock.OnAnything(suite.projectController, "Get").Return(suite.project, nil)
|
||||
mock.OnAnything(suite.checker, "IsScannable").Return(true, nil)
|
||||
mock.OnAnything(suite.scanController, "GetSummary").Return(map[string]interface{}{
|
||||
v1.MimeTypeNativeReport: &vuln.NativeReportSummary{
|
||||
ScanStatus: "Success",
|
||||
Severity: vuln.Unknown,
|
||||
CVEBypassed: []string{"cve-2020"},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
req := suite.makeRequest()
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
Middleware()(suite.next).ServeHTTP(rr, req)
|
||||
suite.Equal(rr.Code, http.StatusOK)
|
||||
}
|
||||
|
||||
func (suite *MiddlewareTestSuite) TestTotalVulnerabilitiesIsZero() {
|
||||
mock.OnAnything(suite.artifactController, "GetByReference").Return(suite.artifact, nil)
|
||||
mock.OnAnything(suite.projectController, "Get").Return(suite.project, nil)
|
||||
mock.OnAnything(suite.checker, "IsScannable").Return(true, nil)
|
||||
mock.OnAnything(suite.scanController, "GetSummary").Return(map[string]interface{}{
|
||||
v1.MimeTypeNativeReport: &vuln.NativeReportSummary{
|
||||
ScanStatus: "Success",
|
||||
Severity: vuln.Unknown,
|
||||
Summary: &vuln.VulnerabilitySummary{Total: 0},
|
||||
CVEBypassed: []string{"cve-2020"},
|
||||
},
|
||||
mock.OnAnything(suite.scanController, "GetVulnerable").Return(&scan.Vulnerable{
|
||||
ScanStatus: "Success",
|
||||
CVEBypassed: []string{"cve-2020"},
|
||||
}, nil)
|
||||
|
||||
req := suite.makeRequest()
|
||||
@ -317,16 +274,15 @@ func (suite *MiddlewareTestSuite) TestTotalVulnerabilitiesIsZero() {
|
||||
}
|
||||
|
||||
func (suite *MiddlewareTestSuite) TestAllowed() {
|
||||
low := vuln.Low
|
||||
mock.OnAnything(suite.artifactController, "GetByReference").Return(suite.artifact, nil)
|
||||
mock.OnAnything(suite.projectController, "Get").Return(suite.project, nil)
|
||||
mock.OnAnything(suite.checker, "IsScannable").Return(true, nil)
|
||||
mock.OnAnything(suite.scanController, "GetSummary").Return(map[string]interface{}{
|
||||
v1.MimeTypeNativeReport: &vuln.NativeReportSummary{
|
||||
ScanStatus: "Success",
|
||||
Severity: vuln.Low,
|
||||
Summary: &vuln.VulnerabilitySummary{Total: 1},
|
||||
CVEBypassed: []string{"cve-2020"},
|
||||
},
|
||||
mock.OnAnything(suite.scanController, "GetVulnerable").Return(&scan.Vulnerable{
|
||||
ScanStatus: "Success",
|
||||
Severity: &low,
|
||||
VulnerabilitiesCount: 1,
|
||||
CVEBypassed: []string{"cve-2020"},
|
||||
}, nil)
|
||||
|
||||
req := suite.makeRequest()
|
||||
@ -341,14 +297,14 @@ func (suite *MiddlewareTestSuite) TestPrevented() {
|
||||
mock.OnAnything(suite.projectController, "Get").Return(suite.project, nil)
|
||||
mock.OnAnything(suite.checker, "IsScannable").Return(true, nil)
|
||||
|
||||
critical := vuln.Critical
|
||||
|
||||
{
|
||||
// only one vulnerability
|
||||
mock.OnAnything(suite.scanController, "GetSummary").Return(map[string]interface{}{
|
||||
v1.MimeTypeNativeReport: &vuln.NativeReportSummary{
|
||||
ScanStatus: "Success",
|
||||
Severity: vuln.Critical,
|
||||
Summary: &vuln.VulnerabilitySummary{Total: 1},
|
||||
},
|
||||
mock.OnAnything(suite.scanController, "GetVulnerable").Return(&scan.Vulnerable{
|
||||
ScanStatus: "Success",
|
||||
Severity: &critical,
|
||||
VulnerabilitiesCount: 1,
|
||||
}, nil).Once()
|
||||
|
||||
req := suite.makeRequest()
|
||||
@ -362,12 +318,10 @@ func (suite *MiddlewareTestSuite) TestPrevented() {
|
||||
|
||||
{
|
||||
// multiple vulnerabilities
|
||||
mock.OnAnything(suite.scanController, "GetSummary").Return(map[string]interface{}{
|
||||
v1.MimeTypeNativeReport: &vuln.NativeReportSummary{
|
||||
ScanStatus: "Success",
|
||||
Severity: vuln.Critical,
|
||||
Summary: &vuln.VulnerabilitySummary{Total: 2},
|
||||
},
|
||||
mock.OnAnything(suite.scanController, "GetVulnerable").Return(&scan.Vulnerable{
|
||||
ScanStatus: "Success",
|
||||
Severity: &critical,
|
||||
VulnerabilitiesCount: 2,
|
||||
}, nil).Once()
|
||||
|
||||
req := suite.makeRequest()
|
||||
@ -381,14 +335,15 @@ func (suite *MiddlewareTestSuite) TestPrevented() {
|
||||
}
|
||||
|
||||
func (suite *MiddlewareTestSuite) TestArtifactIsImageIndex() {
|
||||
critical := vuln.Critical
|
||||
|
||||
suite.artifact.ManifestMediaType = manifestlist.MediaTypeManifestList
|
||||
mock.OnAnything(suite.artifactController, "GetByReference").Return(suite.artifact, nil)
|
||||
mock.OnAnything(suite.projectController, "Get").Return(suite.project, nil)
|
||||
mock.OnAnything(suite.checker, "IsScannable").Return(true, nil)
|
||||
mock.OnAnything(suite.scanController, "GetSummary").Return(map[string]interface{}{
|
||||
v1.MimeTypeNativeReport: &vuln.NativeReportSummary{
|
||||
Severity: vuln.Critical,
|
||||
},
|
||||
mock.OnAnything(suite.scanController, "GetVulnerable").Return(&scan.Vulnerable{
|
||||
ScanStatus: "Success",
|
||||
Severity: &critical,
|
||||
}, nil)
|
||||
|
||||
req := suite.makeRequest()
|
||||
|
@ -351,28 +351,17 @@ func (a *artifactAPI) GetVulnerabilitiesAddition(ctx context.Context, params ope
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
for _, rp := range reports {
|
||||
// Resolve scan report data only when it is ready
|
||||
if len(rp.Report) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
vrp, err := report.ResolveData(rp.MimeType, []byte(rp.Report), report.WithArtifactDigest(rp.Digest))
|
||||
if err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
if v, ok := vulnerabilities[rp.MimeType]; ok {
|
||||
r, err := report.Merge(rp.MimeType, v, vrp)
|
||||
if err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
vulnerabilities[rp.MimeType] = r
|
||||
} else {
|
||||
vulnerabilities[rp.MimeType] = vrp
|
||||
}
|
||||
vrp, err := report.Reports(reports).ResolveData(mimeType)
|
||||
if err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
if vrp == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
vulnerabilities[mimeType] = vrp
|
||||
|
||||
if len(vulnerabilities) != 0 {
|
||||
break
|
||||
}
|
||||
|
@ -11,9 +11,9 @@ import (
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
pkgscan "github.com/goharbor/harbor/src/pkg/scan"
|
||||
models "github.com/goharbor/harbor/src/pkg/allowlist/models"
|
||||
|
||||
report "github.com/goharbor/harbor/src/pkg/scan/report"
|
||||
pkgscan "github.com/goharbor/harbor/src/pkg/scan"
|
||||
|
||||
scan "github.com/goharbor/harbor/src/controller/scan"
|
||||
)
|
||||
@ -90,20 +90,13 @@ func (_m *Controller) GetScanLog(ctx context.Context, uuid string) ([]byte, erro
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetSummary provides a mock function with given fields: ctx, _a1, mimeTypes, options
|
||||
func (_m *Controller) GetSummary(ctx context.Context, _a1 *artifact.Artifact, mimeTypes []string, options ...report.Option) (map[string]interface{}, error) {
|
||||
_va := make([]interface{}, len(options))
|
||||
for _i := range options {
|
||||
_va[_i] = options[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, ctx, _a1, mimeTypes)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
// GetSummary provides a mock function with given fields: ctx, _a1, mimeTypes
|
||||
func (_m *Controller) GetSummary(ctx context.Context, _a1 *artifact.Artifact, mimeTypes []string) (map[string]interface{}, error) {
|
||||
ret := _m.Called(ctx, _a1, mimeTypes)
|
||||
|
||||
var r0 map[string]interface{}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, []string, ...report.Option) map[string]interface{}); ok {
|
||||
r0 = rf(ctx, _a1, mimeTypes, options...)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, []string) map[string]interface{}); ok {
|
||||
r0 = rf(ctx, _a1, mimeTypes)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(map[string]interface{})
|
||||
@ -111,8 +104,31 @@ func (_m *Controller) GetSummary(ctx context.Context, _a1 *artifact.Artifact, mi
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *artifact.Artifact, []string, ...report.Option) error); ok {
|
||||
r1 = rf(ctx, _a1, mimeTypes, options...)
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *artifact.Artifact, []string) error); ok {
|
||||
r1 = rf(ctx, _a1, mimeTypes)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetVulnerable provides a mock function with given fields: ctx, _a1, allowlist
|
||||
func (_m *Controller) GetVulnerable(ctx context.Context, _a1 *artifact.Artifact, allowlist models.CVESet) (*scan.Vulnerable, error) {
|
||||
ret := _m.Called(ctx, _a1, allowlist)
|
||||
|
||||
var r0 *scan.Vulnerable
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, models.CVESet) *scan.Vulnerable); ok {
|
||||
r0 = rf(ctx, _a1, allowlist)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*scan.Vulnerable)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *artifact.Artifact, models.CVESet) error); ok {
|
||||
r1 = rf(ctx, _a1, allowlist)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
@ -162,13 +178,13 @@ func (_m *Controller) ScanAll(ctx context.Context, trigger string, async bool) (
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// UpdateReport provides a mock function with given fields: ctx, _a1
|
||||
func (_m *Controller) UpdateReport(ctx context.Context, _a1 *pkgscan.CheckInReport) error {
|
||||
ret := _m.Called(ctx, _a1)
|
||||
// UpdateReport provides a mock function with given fields: ctx, report
|
||||
func (_m *Controller) UpdateReport(ctx context.Context, report *pkgscan.CheckInReport) error {
|
||||
ret := _m.Called(ctx, report)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *pkgscan.CheckInReport) error); ok {
|
||||
r0 = rf(ctx, _a1)
|
||||
r0 = rf(ctx, report)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user