refactor: remove allowlist in GetSummary of scan controller (#14836)

Signed-off-by: He Weiwei <hweiwei@vmware.com>
This commit is contained in:
He Weiwei 2021-05-18 14:01:59 +08:00 committed by GitHub
parent 1a3335edc5
commit 0c315d8aee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 372 additions and 347 deletions

View File

@ -17,8 +17,6 @@ package preheat
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/goharbor/harbor/src/lib/config"
"github.com/goharbor/harbor/src/lib/orm"
"strings" "strings"
tk "github.com/docker/distribution/registry/auth/token" 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/controller/tag"
"github.com/goharbor/harbor/src/core/service/token" "github.com/goharbor/harbor/src/core/service/token"
"github.com/goharbor/harbor/src/jobservice/job" "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/errors"
"github.com/goharbor/harbor/src/lib/log" "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/q"
"github.com/goharbor/harbor/src/lib/selector" "github.com/goharbor/harbor/src/lib/selector"
"github.com/goharbor/harbor/src/pkg/label/model" "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/models/provider"
"github.com/goharbor/harbor/src/pkg/p2p/preheat/policy" "github.com/goharbor/harbor/src/pkg/p2p/preheat/policy"
pr "github.com/goharbor/harbor/src/pkg/p2p/preheat/provider" 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/scan/vuln"
"github.com/goharbor/harbor/src/pkg/task" "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 // 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) { func (de *defaultEnforcer) getVulnerabilitySev(ctx context.Context, p *models.Project, art *artifact.Artifact) (uint, error) {
al := p.CVEAllowlist.CVESet() vulnerable, err := de.scanCtl.GetVulnerable(ctx, art, p.CVEAllowlist.CVESet())
r, err := de.scanCtl.GetSummary(ctx, art, []string{v1.MimeTypeNativeReport, v1.MimeTypeGenericVulnerabilityReport}, report.WithCVEAllowlist(&al))
if err != nil { if err != nil {
if errors.IsNotFoundErr(err) { if errors.IsNotFoundErr(err) {
// no vulnerability report // 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") return defaultSeverityCode, errors.Wrap(err, "get vulnerability severity")
} }
// Severity is based on the native report format or the generic vulnerability report format. if !vulnerable.IsScanSuccess() {
// In case no supported report format, treat as same to the no report scenario // scan status may running or error
sum, ok := r[v1.MimeTypeNativeReport] return defaultSeverityCode, nil
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
}
} }
sm, ok := sum.(*vuln.NativeReportSummary) // no vulnerability found
if !ok { if vulnerable.Severity == nil {
return defaultSeverityCode, errors.New("malformed native summary report") 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 // toCandidates converts the artifacts to filtering candidates

View File

@ -24,6 +24,7 @@ import (
"github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/common/models"
car "github.com/goharbor/harbor/src/controller/artifact" 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/controller/tag"
"github.com/goharbor/harbor/src/lib/selector" "github.com/goharbor/harbor/src/lib/selector"
models2 "github.com/goharbor/harbor/src/pkg/allowlist/models" 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" 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"
"github.com/goharbor/harbor/src/pkg/p2p/preheat/provider/auth" "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" "github.com/goharbor/harbor/src/pkg/scan/vuln"
ta "github.com/goharbor/harbor/src/pkg/tag/model/tag" 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/artifact"
"github.com/goharbor/harbor/src/testing/controller/project" "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/mock"
"github.com/goharbor/harbor/src/testing/pkg/p2p/preheat/instance" "github.com/goharbor/harbor/src/testing/pkg/p2p/preheat/instance"
"github.com/goharbor/harbor/src/testing/pkg/p2p/preheat/policy" "github.com/goharbor/harbor/src/testing/pkg/p2p/preheat/policy"
@ -104,13 +104,13 @@ func (suite *EnforcerTestSuite) SetupSuite() {
mock.AnythingOfType("*artifact.Option"), mock.AnythingOfType("*artifact.Option"),
).Return(mockArtifacts(), nil) ).Return(mockArtifacts(), nil)
fakeScanCtl := &scan.Controller{} low := vuln.Low
fakeScanCtl.On("GetSummary", fakeScanCtl := &scantesting.Controller{}
fakeScanCtl.On("GetVulnerable",
context.TODO(), context.TODO(),
mock.AnythingOfType("*artifact.Artifact"), mock.AnythingOfType("*artifact.Artifact"),
[]string{v1.MimeTypeNativeReport, v1.MimeTypeGenericVulnerabilityReport}, mock.AnythingOfType("models.CVESet"),
mock.AnythingOfType("report.Option"), ).Return(&scan.Vulnerable{Severity: &low, ScanStatus: "Success"}, nil)
).Return(mockVulnerabilitySummary(), nil)
fakeProCtl := &project.Controller{} fakeProCtl := &project.Controller{}
fakeProCtl.On("Get", 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,
},
}
}

View File

@ -18,7 +18,7 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"github.com/goharbor/harbor/src/lib/config" "reflect"
"strings" "strings"
"sync" "sync"
@ -28,10 +28,12 @@ import (
sc "github.com/goharbor/harbor/src/controller/scanner" sc "github.com/goharbor/harbor/src/controller/scanner"
"github.com/goharbor/harbor/src/jobservice/job" "github.com/goharbor/harbor/src/jobservice/job"
"github.com/goharbor/harbor/src/lib" "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/errors"
"github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/lib/orm" "github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/lib/q" "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/permission/types"
"github.com/goharbor/harbor/src/pkg/robot/model" "github.com/goharbor/harbor/src/pkg/robot/model"
sca "github.com/goharbor/harbor/src/pkg/scan" sca "github.com/goharbor/harbor/src/pkg/scan"
@ -533,7 +535,7 @@ func (bc *basicController) GetReport(ctx context.Context, artifact *ar.Artifact,
} }
// GetSummary ... // 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 { if artifact == nil {
return nil, errors.New("no way to get report summaries for nil artifact") 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)) summaries := make(map[string]interface{}, len(rps))
for _, rp := range rps { for _, rp := range rps {
sum, err := report.GenerateSummary(rp, options...) sum, err := report.GenerateSummary(rp)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -699,6 +701,85 @@ func (bc *basicController) DeleteReports(ctx context.Context, digests ...string)
return nil 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. // 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) { 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. // Use uuid as name to avoid duplicated entries.

View File

@ -18,14 +18,27 @@ import (
"context" "context"
"github.com/goharbor/harbor/src/controller/artifact" "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" sca "github.com/goharbor/harbor/src/pkg/scan"
"github.com/goharbor/harbor/src/pkg/scan/dao/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. // 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 { type Controller interface {
// Scan the given artifact // Scan the given artifact
// //
@ -56,12 +69,11 @@ type Controller interface {
// ctx context.Context : the context for this method // ctx context.Context : the context for this method
// artifact *artifact.Artifact : the scanned artifact // artifact *artifact.Artifact : the scanned artifact
// mimeTypes []string : the mime types of the reports // mimeTypes []string : the mime types of the reports
// options ...report.Option : optional report options, specify if needed
// //
// Returns: // Returns:
// map[string]interface{} : report summaries indexed by mime types // map[string]interface{} : report summaries indexed by mime types
// error : non nil error if any errors occurred // 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 // Get the scan log for the specified artifact with the given digest
// //
@ -103,4 +115,15 @@ type Controller interface {
// Returns: // Returns:
// error : non nil error if any errors occurred // error : non nil error if any errors occurred
ScanAll(ctx context.Context, trigger string, async bool) (int64, error) 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)
} }

View File

@ -16,6 +16,7 @@ package report
import ( import (
"github.com/goharbor/harbor/src/lib/errors" "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" v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
"github.com/goharbor/harbor/src/pkg/scan/vuln" "github.com/goharbor/harbor/src/pkg/scan/vuln"
) )
@ -53,3 +54,36 @@ func MergeNativeReport(r1, r2 interface{}) (interface{}, error) {
return nr1.Merge(nr2), nil 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
}

View File

@ -19,7 +19,6 @@ import (
"github.com/goharbor/harbor/src/jobservice/job" "github.com/goharbor/harbor/src/jobservice/job"
"github.com/goharbor/harbor/src/lib/errors" "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" "github.com/goharbor/harbor/src/pkg/scan/dao/scan"
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
"github.com/goharbor/harbor/src/pkg/scan/vuln" "github.com/goharbor/harbor/src/pkg/scan/vuln"
@ -29,20 +28,11 @@ import (
type Options struct { type Options struct {
// If it is set, the returned report will contains artifact digest for the vulnerabilities // If it is set, the returned report will contains artifact digest for the vulnerabilities
ArtifactDigest string 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. // Option for getting the report w/ summary with func template way.
type Option func(options *Options) 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 // WithArtifactDigest is an option of setting artifact digest
func WithArtifactDigest(artifactDigest string) Option { func WithArtifactDigest(artifactDigest string) Option {
return func(options *Options) { 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. // GenerateNativeSummary generates the report summary for the native report.
func GenerateNativeSummary(r *scan.Report, options ...Option) (interface{}, error) { func GenerateNativeSummary(r *scan.Report, options ...Option) (interface{}, error) {
ops := &Options{}
for _, op := range options {
op(ops)
}
sum := &vuln.NativeReportSummary{} sum := &vuln.NativeReportSummary{}
sum.ReportID = r.UUID sum.ReportID = r.UUID
sum.StartTime = r.StartTime sum.StartTime = r.StartTime
@ -120,9 +105,6 @@ func GenerateNativeSummary(r *scan.Report, options ...Option) (interface{}, erro
if sum.Duration < 0 { if sum.Duration < 0 {
sum.Duration = 0 sum.Duration = 0
} }
if len(ops.CVEAllowlist) > 0 {
sum.CVEBypassed = make([]string, 0)
}
sum.ScanStatus = job.ErrorStatus.String() sum.ScanStatus = job.ErrorStatus.String()
if job.Status(r.Status).Code() != -1 { 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.Severity = rp.Severity
sum.Scanner = rp.Scanner sum.Scanner = rp.Scanner
sum.UpdateSeveritySummaryAndByPassed(rp.GetVulnerabilityItemList(), ops.CVEAllowlist) sum.UpdateSeveritySummary(rp.GetVulnerabilityItemList())
return sum, nil return sum, nil
} }

View File

@ -19,7 +19,6 @@ import (
"testing" "testing"
"time" "time"
models2 "github.com/goharbor/harbor/src/pkg/allowlist/models"
"github.com/goharbor/harbor/src/pkg/scan/dao/scan" "github.com/goharbor/harbor/src/pkg/scan/dao/scan"
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
"github.com/goharbor/harbor/src/pkg/scan/vuln" "github.com/goharbor/harbor/src/pkg/scan/vuln"
@ -103,23 +102,6 @@ func (suite *SummaryTestSuite) TestSummaryGenerateSummaryNoOptions() {
suite.Equal("0.1.0", nativeSummary.Scanner.Version) 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 ... // TestSummaryGenerateSummaryWrongMime ...
func (suite *SummaryTestSuite) TestSummaryGenerateSummaryWrongMime() { func (suite *SummaryTestSuite) TestSummaryGenerateSummaryWrongMime() {
suite.r.MimeType = "wrong-mime" suite.r.MimeType = "wrong-mime"

View File

@ -18,7 +18,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
models2 "github.com/goharbor/harbor/src/pkg/allowlist/models"
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" 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 // GetSeveritySummary returns the severity and summary of l
func (l *VulnerabilityItemList) GetSeveritySummaryAndByPassed(allowlist models2.CVESet) (Severity, *VulnerabilitySummary, []string) { func (l *VulnerabilityItemList) GetSeveritySummary() (Severity, *VulnerabilitySummary) {
if l == nil {
return Severity(""), nil
}
sum := &VulnerabilitySummary{ sum := &VulnerabilitySummary{
Total: len(l.Items()), Total: len(l.Items()),
Summary: make(SeveritySummary), Summary: make(SeveritySummary),
} }
var bypassed []string
severity := None severity := None
for _, v := range l.Items() { 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 { if num, ok := sum.Summary[v.Severity]; ok {
sum.Summary[v.Severity] = num + 1 sum.Summary[v.Severity] = num + 1
} else { } else {
@ -180,14 +171,13 @@ func (l *VulnerabilityItemList) GetSeveritySummaryAndByPassed(allowlist models2.
if v.Severity.Code() > severity.Code() { if v.Severity.Code() > severity.Code() {
severity = v.Severity severity = v.Severity
} }
// If the CVE item has a fixable version // If the CVE item has a fixable version
if len(v.FixVersion) > 0 { if len(v.FixVersion) > 0 {
sum.Fixable++ sum.Fixable++
} }
} }
return severity, sum, bypassed return severity, sum
} }
// VulnerabilityItem represents one found vulnerability // VulnerabilityItem represents one found vulnerability

View File

@ -19,7 +19,6 @@ import (
"reflect" "reflect"
"testing" "testing"
models2 "github.com/goharbor/harbor/src/pkg/allowlist/models"
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -80,7 +79,7 @@ func TestReportMarshalJSON(t *testing.T) {
assert.Contains(string(b), "vulnerabilities") assert.Contains(string(b), "vulnerabilities")
} }
func TestGetSummarySeverityAndByPassed(t *testing.T) { func TestGetSummarySeverity(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
vul1 := &VulnerabilityItem{ vul1 := &VulnerabilityItem{
@ -102,34 +101,14 @@ func TestGetSummarySeverityAndByPassed(t *testing.T) {
l := VulnerabilityItemList{} l := VulnerabilityItemList{}
l.Add(vul1, vul2, vul3) l.Add(vul1, vul2, vul3)
{ s := SeveritySummary{
s := SeveritySummary{ Low: 2,
Low: 2, Medium: 1,
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)
} }
{ severity, sum := l.GetSeveritySummary()
s := SeveritySummary{ assert.Equal(Medium, severity)
Low: 2, assert.Equal(3, sum.Total)
} assert.Equal(1, sum.Fixable)
assert.Equal(s, sum.Summary)
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)
}
} }

View File

@ -18,7 +18,6 @@ import (
"time" "time"
"github.com/goharbor/harbor/src/jobservice/job" "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" v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
) )
@ -39,30 +38,12 @@ type NativeReportSummary struct {
TotalCount int `json:"-"` TotalCount int `json:"-"`
CompleteCount int `json:"-"` CompleteCount int `json:"-"`
VulnerabilityItemList *VulnerabilityItemList `json:"-"` VulnerabilityItemList *VulnerabilityItemList `json:"-"`
CVESet models2.CVESet `json:"-"`
} }
// UpdateSeveritySummaryAndByPassed update the Severity, Summary and CVEBypassed of the sum from l and s // UpdateSeveritySummary update the Severity, Summary of the sum from l
func (sum *NativeReportSummary) UpdateSeveritySummaryAndByPassed(l *VulnerabilityItemList, s models2.CVESet) { func (sum *NativeReportSummary) UpdateSeveritySummary(l *VulnerabilityItemList) {
sum.VulnerabilityItemList = l sum.VulnerabilityItemList = l
sum.CVESet = s sum.Severity, sum.Summary = l.GetSeveritySummary()
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()
} }
// Merge ... // Merge ...
@ -79,17 +60,17 @@ func (sum *NativeReportSummary) Merge(another *NativeReportSummary) *NativeRepor
} else { } else {
r.Scanner = another.Scanner r.Scanner = another.Scanner
} }
r.TotalCount = sum.TotalCount + another.TotalCount r.TotalCount = sum.TotalCount + another.TotalCount
r.CompleteCount = sum.CompleteCount + another.CompleteCount r.CompleteCount = sum.CompleteCount + another.CompleteCount
r.CompletePercent = r.CompleteCount * 100 / r.TotalCount r.CompletePercent = r.CompleteCount * 100 / r.TotalCount
r.ReportID = mergeReportID(sum.ReportID, another.ReportID) 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( if r.ScanStatus != job.RunningStatus.String() {
NewVulnerabilityItemList(sum.VulnerabilityItemList, another.VulnerabilityItemList), l := NewVulnerabilityItemList(sum.VulnerabilityItemList, another.VulnerabilityItemList)
models2.NewCVESet(sum.CVESet, another.CVESet), r.UpdateSeveritySummary(l)
) }
return r return r
} }

View File

@ -25,6 +25,7 @@ func TestMergeNativeReportSummary(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
errorStatus := job.ErrorStatus.String() errorStatus := job.ErrorStatus.String()
runningStatus := job.RunningStatus.String() runningStatus := job.RunningStatus.String()
successStatus := job.SuccessStatus.String()
v1 := VulnerabilitySummary{ v1 := VulnerabilitySummary{
Total: 1, Total: 1,
@ -42,82 +43,135 @@ func TestMergeNativeReportSummary(t *testing.T) {
}) })
{ {
n1 := NativeReportSummary{ // running && running
ScanStatus: runningStatus, n1 := &NativeReportSummary{
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{
ScanStatus: runningStatus, ScanStatus: runningStatus,
Severity: Severity(""),
TotalCount: 1, TotalCount: 1,
} }
r := n1.Merge(&NativeReportSummary{ r := n1.Merge(&NativeReportSummary{
ScanStatus: errorStatus, ScanStatus: runningStatus,
Severity: Severity(""),
TotalCount: 1, TotalCount: 1,
}) })
assert.Equal(runningStatus, r.ScanStatus) assert.Equal(runningStatus, r.ScanStatus)
assert.Equal(Severity(""), r.Severity)
assert.Nil(r.Summary) assert.Nil(r.Summary)
} }
{ {
n1 := &NativeReportSummary{ // running && success
ScanStatus: errorStatus, n1 := NativeReportSummary{
Severity: Severity(""), ScanStatus: runningStatus,
TotalCount: 1, TotalCount: 1,
} }
r := n1.Merge(&NativeReportSummary{ r := n1.Merge(&NativeReportSummary{
ScanStatus: runningStatus, ScanStatus: successStatus,
Severity: Low, Severity: Low,
TotalCount: 1, TotalCount: 1,
CompleteCount: 1,
Summary: &v1, Summary: &v1,
VulnerabilityItemList: l, VulnerabilityItemList: l,
}) })
assert.Equal(runningStatus, r.ScanStatus) assert.Equal(runningStatus, r.ScanStatus)
assert.Equal(Low, r.Severity) assert.Nil(r.Summary)
assert.Equal(v1, *r.Summary)
} }
{ {
n1 := &NativeReportSummary{ // running && error
ScanStatus: runningStatus, 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, Severity: Low,
TotalCount: 1, 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, Summary: &v1,
VulnerabilityItemList: l, VulnerabilityItemList: l,
} }
r := n1.Merge(&NativeReportSummary{ r := n1.Merge(&NativeReportSummary{
ScanStatus: runningStatus, ScanStatus: errorStatus,
Severity: Low, TotalCount: 1,
TotalCount: 1,
Summary: &v1,
VulnerabilityItemList: l,
}) })
assert.Equal(runningStatus, r.ScanStatus) assert.Equal(successStatus, r.ScanStatus)
assert.Equal(Low, r.Severity) assert.Equal(Low, r.Severity)
assert.Equal(v1, *r.Summary) 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)
} }
} }

View File

@ -79,12 +79,14 @@ func mergeSeverity(s1, s2 Severity) Severity {
return s2 return s2
} }
func mergeScanStatus(s1, s2 string) string { // MergeScanStatus ...
func MergeScanStatus(s1, s2 string) string {
j1, j2 := job.Status(s1), job.Status(s2) j1, j2 := job.Status(s1), job.Status(s2)
if j1 == job.RunningStatus || j2 == job.RunningStatus { if j1 == job.RunningStatus || j2 == job.RunningStatus {
return job.RunningStatus.String() return job.RunningStatus.String()
} else if j1 == job.SuccessStatus || j2 == job.SuccessStatus { } 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() return job.SuccessStatus.String()
} }

View File

@ -91,7 +91,7 @@ func Test_mergeScanStatus(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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) t.Errorf("mergeScanStatus() = %v, want %v", got, tt.want)
} }
}) })

View File

@ -25,8 +25,6 @@ import (
"github.com/goharbor/harbor/src/lib" "github.com/goharbor/harbor/src/lib"
"github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/log" "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/pkg/scan/vuln"
"github.com/goharbor/harbor/src/server/middleware" "github.com/goharbor/harbor/src/server/middleware"
"github.com/goharbor/harbor/src/server/middleware/util" "github.com/goharbor/harbor/src/server/middleware/util"
@ -92,28 +90,20 @@ func Middleware() func(http.Handler) http.Handler {
} }
allowlist := proj.CVEAllowlist.CVESet() 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()) projectSeverity := vuln.ParseSeverityVersion3(proj.Severity())
rawSummary, ok := summaries[v1.MimeTypeNativeReport] vulnerable, err := scanController.GetVulnerable(ctx, art, allowlist)
if !ok { if err != nil {
rawSummary, ok = summaries[v1.MimeTypeGenericVulnerabilityReport] if errors.IsNotFoundErr(err) {
if !ok {
// No report yet? // 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.' `+ 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) `To continue with pull, please contact your project administrator for help.`, projectSeverity)
return errors.New(nil).WithCode(errors.PROJECTPOLICYVIOLATION).WithMessage(msg) return errors.New(nil).WithCode(errors.PROJECTPOLICYVIOLATION).WithMessage(msg)
} }
}
summary, ok := rawSummary.(*vuln.NativeReportSummary) logger.Errorf("get vulnerability summary of the artifact %s@%s failed, error: %v", art.RepositoryName, art.Digest, err)
if !ok { return err
return fmt.Errorf("report summary is invalid")
} }
if art.IsImageIndex() { 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.' `+ 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) 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 // Do judgement
if summary.Severity.Code() >= projectSeverity.Code() { if vulnerable.Severity != nil && vulnerable.Severity.Code() >= projectSeverity.Code() {
thing := "vulnerability" thing := "vulnerability"
if summary.Summary.Total > 1 { if vulnerable.VulnerabilitiesCount > 1 {
thing = "vulnerabilities" 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.' `+ 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.`, `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) return errors.New(nil).WithCode(errors.PROJECTPOLICYVIOLATION).WithMessage(msg)
} }
// Print scannerPull CVE list // Print scannerPull CVE list
if len(summary.CVEBypassed) > 0 { for _, cve := range vulnerable.CVEBypassed {
for _, cve := range summary.CVEBypassed { logger.Infof("Vulnerable policy check: bypassed CVE %s", cve)
logger.Infof("Vulnerable policy check: bypassed CVE %s", cve)
}
} }
return nil return nil

View File

@ -28,7 +28,7 @@ import (
"github.com/goharbor/harbor/src/controller/project" "github.com/goharbor/harbor/src/controller/project"
"github.com/goharbor/harbor/src/controller/scan" "github.com/goharbor/harbor/src/controller/scan"
"github.com/goharbor/harbor/src/lib" "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" "github.com/goharbor/harbor/src/pkg/scan/vuln"
securitytesting "github.com/goharbor/harbor/src/testing/common/security" securitytesting "github.com/goharbor/harbor/src/testing/common/security"
artifacttesting "github.com/goharbor/harbor/src/testing/controller/artifact" 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.artifactController, "GetByReference").Return(suite.artifact, nil)
mock.OnAnything(suite.projectController, "Get").Return(suite.project, nil) mock.OnAnything(suite.projectController, "Get").Return(suite.project, nil)
mock.OnAnything(suite.checker, "IsScannable").Return(true, 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() req := suite.makeRequest()
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
@ -235,12 +235,7 @@ func (suite *MiddlewareTestSuite) TestArtifactScanFailed() {
mock.OnAnything(suite.artifactController, "GetByReference").Return(suite.artifact, nil) mock.OnAnything(suite.artifactController, "GetByReference").Return(suite.artifact, nil)
mock.OnAnything(suite.projectController, "Get").Return(suite.project, nil) mock.OnAnything(suite.projectController, "Get").Return(suite.project, nil)
mock.OnAnything(suite.checker, "IsScannable").Return(true, nil) mock.OnAnything(suite.checker, "IsScannable").Return(true, nil)
mock.OnAnything(suite.scanController, "GetSummary").Return(map[string]interface{}{ mock.OnAnything(suite.scanController, "GetVulnerable").Return(&scan.Vulnerable{ScanStatus: "Error"}, nil)
v1.MimeTypeNativeReport: &vuln.NativeReportSummary{
ScanStatus: "Error",
CVEBypassed: []string{"cve-2020"},
},
}, nil)
req := suite.makeRequest() req := suite.makeRequest()
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
@ -249,26 +244,11 @@ func (suite *MiddlewareTestSuite) TestArtifactScanFailed() {
suite.Equal(rr.Code, http.StatusPreconditionFailed) 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.artifactController, "GetByReference").Return(suite.artifact, nil)
mock.OnAnything(suite.projectController, "Get").Return(suite.project, nil) mock.OnAnything(suite.projectController, "Get").Return(suite.project, nil)
mock.OnAnything(suite.checker, "IsScannable").Return(true, nil) mock.OnAnything(suite.checker, "IsScannable").Return(true, nil)
mock.OnAnything(suite.scanController, "GetSummary").Return(nil, fmt.Errorf("error")) mock.OnAnything(suite.scanController, "GetVulnerable").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)
req := suite.makeRequest() req := suite.makeRequest()
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
@ -281,32 +261,9 @@ func (suite *MiddlewareTestSuite) TestNoVulnerabilities() {
mock.OnAnything(suite.artifactController, "GetByReference").Return(suite.artifact, nil) mock.OnAnything(suite.artifactController, "GetByReference").Return(suite.artifact, nil)
mock.OnAnything(suite.projectController, "Get").Return(suite.project, nil) mock.OnAnything(suite.projectController, "Get").Return(suite.project, nil)
mock.OnAnything(suite.checker, "IsScannable").Return(true, nil) mock.OnAnything(suite.checker, "IsScannable").Return(true, nil)
mock.OnAnything(suite.scanController, "GetSummary").Return(map[string]interface{}{ mock.OnAnything(suite.scanController, "GetVulnerable").Return(&scan.Vulnerable{
v1.MimeTypeNativeReport: &vuln.NativeReportSummary{ ScanStatus: "Success",
ScanStatus: "Success", CVEBypassed: []string{"cve-2020"},
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"},
},
}, nil) }, nil)
req := suite.makeRequest() req := suite.makeRequest()
@ -317,16 +274,15 @@ func (suite *MiddlewareTestSuite) TestTotalVulnerabilitiesIsZero() {
} }
func (suite *MiddlewareTestSuite) TestAllowed() { func (suite *MiddlewareTestSuite) TestAllowed() {
low := vuln.Low
mock.OnAnything(suite.artifactController, "GetByReference").Return(suite.artifact, nil) mock.OnAnything(suite.artifactController, "GetByReference").Return(suite.artifact, nil)
mock.OnAnything(suite.projectController, "Get").Return(suite.project, nil) mock.OnAnything(suite.projectController, "Get").Return(suite.project, nil)
mock.OnAnything(suite.checker, "IsScannable").Return(true, nil) mock.OnAnything(suite.checker, "IsScannable").Return(true, nil)
mock.OnAnything(suite.scanController, "GetSummary").Return(map[string]interface{}{ mock.OnAnything(suite.scanController, "GetVulnerable").Return(&scan.Vulnerable{
v1.MimeTypeNativeReport: &vuln.NativeReportSummary{ ScanStatus: "Success",
ScanStatus: "Success", Severity: &low,
Severity: vuln.Low, VulnerabilitiesCount: 1,
Summary: &vuln.VulnerabilitySummary{Total: 1}, CVEBypassed: []string{"cve-2020"},
CVEBypassed: []string{"cve-2020"},
},
}, nil) }, nil)
req := suite.makeRequest() req := suite.makeRequest()
@ -341,14 +297,14 @@ func (suite *MiddlewareTestSuite) TestPrevented() {
mock.OnAnything(suite.projectController, "Get").Return(suite.project, nil) mock.OnAnything(suite.projectController, "Get").Return(suite.project, nil)
mock.OnAnything(suite.checker, "IsScannable").Return(true, nil) mock.OnAnything(suite.checker, "IsScannable").Return(true, nil)
critical := vuln.Critical
{ {
// only one vulnerability // only one vulnerability
mock.OnAnything(suite.scanController, "GetSummary").Return(map[string]interface{}{ mock.OnAnything(suite.scanController, "GetVulnerable").Return(&scan.Vulnerable{
v1.MimeTypeNativeReport: &vuln.NativeReportSummary{ ScanStatus: "Success",
ScanStatus: "Success", Severity: &critical,
Severity: vuln.Critical, VulnerabilitiesCount: 1,
Summary: &vuln.VulnerabilitySummary{Total: 1},
},
}, nil).Once() }, nil).Once()
req := suite.makeRequest() req := suite.makeRequest()
@ -362,12 +318,10 @@ func (suite *MiddlewareTestSuite) TestPrevented() {
{ {
// multiple vulnerabilities // multiple vulnerabilities
mock.OnAnything(suite.scanController, "GetSummary").Return(map[string]interface{}{ mock.OnAnything(suite.scanController, "GetVulnerable").Return(&scan.Vulnerable{
v1.MimeTypeNativeReport: &vuln.NativeReportSummary{ ScanStatus: "Success",
ScanStatus: "Success", Severity: &critical,
Severity: vuln.Critical, VulnerabilitiesCount: 2,
Summary: &vuln.VulnerabilitySummary{Total: 2},
},
}, nil).Once() }, nil).Once()
req := suite.makeRequest() req := suite.makeRequest()
@ -381,14 +335,15 @@ func (suite *MiddlewareTestSuite) TestPrevented() {
} }
func (suite *MiddlewareTestSuite) TestArtifactIsImageIndex() { func (suite *MiddlewareTestSuite) TestArtifactIsImageIndex() {
critical := vuln.Critical
suite.artifact.ManifestMediaType = manifestlist.MediaTypeManifestList suite.artifact.ManifestMediaType = manifestlist.MediaTypeManifestList
mock.OnAnything(suite.artifactController, "GetByReference").Return(suite.artifact, nil) mock.OnAnything(suite.artifactController, "GetByReference").Return(suite.artifact, nil)
mock.OnAnything(suite.projectController, "Get").Return(suite.project, nil) mock.OnAnything(suite.projectController, "Get").Return(suite.project, nil)
mock.OnAnything(suite.checker, "IsScannable").Return(true, nil) mock.OnAnything(suite.checker, "IsScannable").Return(true, nil)
mock.OnAnything(suite.scanController, "GetSummary").Return(map[string]interface{}{ mock.OnAnything(suite.scanController, "GetVulnerable").Return(&scan.Vulnerable{
v1.MimeTypeNativeReport: &vuln.NativeReportSummary{ ScanStatus: "Success",
Severity: vuln.Critical, Severity: &critical,
},
}, nil) }, nil)
req := suite.makeRequest() req := suite.makeRequest()

View File

@ -351,28 +351,17 @@ func (a *artifactAPI) GetVulnerabilitiesAddition(ctx context.Context, params ope
return a.SendError(ctx, err) return a.SendError(ctx, err)
} }
for _, rp := range reports { vrp, err := report.Reports(reports).ResolveData(mimeType)
// Resolve scan report data only when it is ready if err != nil {
if len(rp.Report) == 0 { return a.SendError(ctx, err)
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
}
} }
if vrp == nil {
continue
}
vulnerabilities[mimeType] = vrp
if len(vulnerabilities) != 0 { if len(vulnerabilities) != 0 {
break break
} }

View File

@ -11,9 +11,9 @@ import (
mock "github.com/stretchr/testify/mock" 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" 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 return r0, r1
} }
// GetSummary provides a mock function with given fields: ctx, _a1, mimeTypes, options // GetSummary provides a mock function with given fields: ctx, _a1, mimeTypes
func (_m *Controller) GetSummary(ctx context.Context, _a1 *artifact.Artifact, mimeTypes []string, options ...report.Option) (map[string]interface{}, error) { func (_m *Controller) GetSummary(ctx context.Context, _a1 *artifact.Artifact, mimeTypes []string) (map[string]interface{}, error) {
_va := make([]interface{}, len(options)) ret := _m.Called(ctx, _a1, mimeTypes)
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...)
var r0 map[string]interface{} var r0 map[string]interface{}
if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, []string, ...report.Option) map[string]interface{}); ok { if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, []string) map[string]interface{}); ok {
r0 = rf(ctx, _a1, mimeTypes, options...) r0 = rf(ctx, _a1, mimeTypes)
} else { } else {
if ret.Get(0) != nil { if ret.Get(0) != nil {
r0 = ret.Get(0).(map[string]interface{}) 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 var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *artifact.Artifact, []string, ...report.Option) error); ok { if rf, ok := ret.Get(1).(func(context.Context, *artifact.Artifact, []string) error); ok {
r1 = rf(ctx, _a1, mimeTypes, options...) 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 { } else {
r1 = ret.Error(1) r1 = ret.Error(1)
} }
@ -162,13 +178,13 @@ func (_m *Controller) ScanAll(ctx context.Context, trigger string, async bool) (
return r0, r1 return r0, r1
} }
// UpdateReport provides a mock function with given fields: ctx, _a1 // UpdateReport provides a mock function with given fields: ctx, report
func (_m *Controller) UpdateReport(ctx context.Context, _a1 *pkgscan.CheckInReport) error { func (_m *Controller) UpdateReport(ctx context.Context, report *pkgscan.CheckInReport) error {
ret := _m.Called(ctx, _a1) ret := _m.Called(ctx, report)
var r0 error var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *pkgscan.CheckInReport) error); ok { if rf, ok := ret.Get(0).(func(context.Context, *pkgscan.CheckInReport) error); ok {
r0 = rf(ctx, _a1) r0 = rf(ctx, report)
} else { } else {
r0 = ret.Error(0) r0 = ret.Error(0)
} }