mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-16 20:01:35 +01:00
feat: return scan report and summary by header (#13898)
Add X-Accept-Vulnerabilities header to the list/get artifact and get artifact vulnerability addition APIs, and these APIs will traverse the mime types in this header and return the first report and summary found from the mime type. Signed-off-by: He Weiwei <hweiwei@vmware.com>
This commit is contained in:
parent
511bd86930
commit
ed31cf9417
@ -367,6 +367,7 @@ paths:
|
||||
- $ref: '#/parameters/query'
|
||||
- $ref: '#/parameters/page'
|
||||
- $ref: '#/parameters/pageSize'
|
||||
- $ref: '#/parameters/acceptVulnerabilities'
|
||||
- name: with_tag
|
||||
in: query
|
||||
description: Specify whether the tags are included inside the returning artifacts
|
||||
@ -465,6 +466,7 @@ paths:
|
||||
- $ref: '#/parameters/reference'
|
||||
- $ref: '#/parameters/page'
|
||||
- $ref: '#/parameters/pageSize'
|
||||
- $ref: '#/parameters/acceptVulnerabilities'
|
||||
- name: with_tag
|
||||
in: query
|
||||
description: Specify whether the tags are inclued inside the returning artifacts
|
||||
@ -699,6 +701,38 @@ paths:
|
||||
$ref: '#/responses/404'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
/projects/{project_name}/repositories/{repository_name}/artifacts/{reference}/additions/vulnerabilities:
|
||||
get:
|
||||
summary: Get the vulnerabilities addition of the specific artifact
|
||||
description: Get the vulnerabilities addition of the artifact specified by the reference under the project and repository.
|
||||
tags:
|
||||
- artifact
|
||||
operationId: getVulnerabilitiesAddition
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/projectName'
|
||||
- $ref: '#/parameters/repositoryName'
|
||||
- $ref: '#/parameters/reference'
|
||||
- $ref: '#/parameters/acceptVulnerabilities'
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
headers:
|
||||
Content-Type:
|
||||
description: The content type of the vulnerabilities addition
|
||||
type: string
|
||||
schema:
|
||||
type: string
|
||||
'400':
|
||||
$ref: '#/responses/400'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'404':
|
||||
$ref: '#/responses/404'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
/projects/{project_name}/repositories/{repository_name}/artifacts/{reference}/additions/{addition}:
|
||||
get:
|
||||
summary: Get the addition of the specific artifact
|
||||
@ -715,7 +749,7 @@ paths:
|
||||
in: path
|
||||
description: The type of addition.
|
||||
type: string
|
||||
enum: [build_history, values.yaml, readme.md, dependencies, vulnerabilities]
|
||||
enum: [build_history, values.yaml, readme.md, dependencies]
|
||||
required: true
|
||||
responses:
|
||||
'200':
|
||||
@ -2337,6 +2371,14 @@ parameters:
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
acceptVulnerabilities:
|
||||
name: X-Accept-Vulnerabilities
|
||||
in: header
|
||||
type: string
|
||||
default: 'application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0'
|
||||
description: |-
|
||||
A comma-separated lists of MIME types for the scan report or scan summary. The first mime type will be used when the report found for it.
|
||||
Currently the mime type supports 'application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0' and 'application/vnd.security.vulnerability.report; version=1.1'
|
||||
projectName:
|
||||
name: project_name
|
||||
in: path
|
||||
|
@ -16,6 +16,7 @@ package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
@ -27,14 +28,15 @@ import (
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
"github.com/goharbor/harbor/src/controller/artifact"
|
||||
"github.com/goharbor/harbor/src/controller/artifact/processor"
|
||||
"github.com/goharbor/harbor/src/controller/event/metadata"
|
||||
"github.com/goharbor/harbor/src/controller/project"
|
||||
"github.com/goharbor/harbor/src/controller/repository"
|
||||
"github.com/goharbor/harbor/src/controller/scan"
|
||||
"github.com/goharbor/harbor/src/controller/tag"
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/pkg/notification"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/report"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/handler/assembler"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/handler/model"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||
@ -42,10 +44,6 @@ import (
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
const (
|
||||
vulnerabilitiesAddition = "vulnerabilities"
|
||||
)
|
||||
|
||||
func newArtifactAPI() *artifactAPI {
|
||||
return &artifactAPI{
|
||||
artCtl: artifact.Ctl,
|
||||
@ -100,7 +98,7 @@ func (a *artifactAPI) ListArtifacts(ctx context.Context, params operation.ListAr
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
assembler := assembler.NewVulAssembler(boolValue(params.WithScanOverview))
|
||||
assembler := assembler.NewVulAssembler(lib.BoolValue(params.WithScanOverview), parseScanReportMimeTypes(params.XAcceptVulnerabilities))
|
||||
var artifacts []*models.Artifact
|
||||
for _, art := range arts {
|
||||
artifact := &model.Artifact{}
|
||||
@ -131,7 +129,7 @@ func (a *artifactAPI) GetArtifact(ctx context.Context, params operation.GetArtif
|
||||
art := &model.Artifact{}
|
||||
art.Artifact = *artifact
|
||||
|
||||
assembler.NewVulAssembler(boolValue(params.WithScanOverview)).WithArtifacts(art).Assemble(ctx)
|
||||
assembler.NewVulAssembler(lib.BoolValue(params.WithScanOverview), parseScanReportMimeTypes(params.XAcceptVulnerabilities)).WithArtifacts(art).Assemble(ctx)
|
||||
|
||||
return operation.NewGetArtifactOK().WithPayload(art.ToSwagger())
|
||||
}
|
||||
@ -335,6 +333,59 @@ func (a *artifactAPI) ListTags(ctx context.Context, params operation.ListTagsPar
|
||||
WithPayload(ts)
|
||||
}
|
||||
|
||||
func (a *artifactAPI) GetVulnerabilitiesAddition(ctx context.Context, params operation.GetVulnerabilitiesAdditionParams) middleware.Responder {
|
||||
if err := a.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionRead, rbac.ResourceArtifactAddition); err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
artifact, err := a.artCtl.GetByReference(ctx, fmt.Sprintf("%s/%s", params.ProjectName, params.RepositoryName), params.Reference, nil)
|
||||
if err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
vulnerabilities := make(map[string]interface{})
|
||||
|
||||
for _, mimeType := range parseScanReportMimeTypes(params.XAcceptVulnerabilities) {
|
||||
reports, err := a.scanCtl.GetReport(ctx, artifact, []string{mimeType})
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
if len(vulnerabilities) != 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
content, _ := json.Marshal(vulnerabilities)
|
||||
|
||||
return middleware.ResponderFunc(func(w http.ResponseWriter, p runtime.Producer) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(content)
|
||||
})
|
||||
}
|
||||
|
||||
func (a *artifactAPI) GetAddition(ctx context.Context, params operation.GetAdditionParams) middleware.Responder {
|
||||
if err := a.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionRead, rbac.ResourceArtifactAddition); err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
@ -345,13 +396,7 @@ func (a *artifactAPI) GetAddition(ctx context.Context, params operation.GetAddit
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
var addition *processor.Addition
|
||||
|
||||
if params.Addition == vulnerabilitiesAddition {
|
||||
addition, err = resolveVulnerabilitiesAddition(ctx, artifact)
|
||||
} else {
|
||||
addition, err = a.artCtl.GetAddition(ctx, artifact.ID, strings.ToUpper(params.Addition))
|
||||
}
|
||||
addition, err := a.artCtl.GetAddition(ctx, artifact.ID, strings.ToUpper(params.Addition))
|
||||
if err != nil {
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
@ -393,7 +438,7 @@ func (a *artifactAPI) RemoveLabel(ctx context.Context, params operation.RemoveLa
|
||||
func option(withTag, withImmutableStatus, withLabel, withSignature *bool) *artifact.Option {
|
||||
option := &artifact.Option{
|
||||
WithTag: true, // return the tag by default
|
||||
WithLabel: boolValue(withLabel),
|
||||
WithLabel: lib.BoolValue(withLabel),
|
||||
}
|
||||
|
||||
if withTag != nil {
|
||||
@ -402,8 +447,8 @@ func option(withTag, withImmutableStatus, withLabel, withSignature *bool) *artif
|
||||
|
||||
if option.WithTag {
|
||||
option.TagOption = &tag.Option{
|
||||
WithImmutableStatus: boolValue(withImmutableStatus),
|
||||
WithSignature: boolValue(withSignature),
|
||||
WithImmutableStatus: lib.BoolValue(withImmutableStatus),
|
||||
WithSignature: lib.BoolValue(withSignature),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,9 +15,20 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/goharbor/harbor/src/controller/artifact"
|
||||
"github.com/goharbor/harbor/src/controller/project"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scan"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/restapi"
|
||||
artifacttesting "github.com/goharbor/harbor/src/testing/controller/artifact"
|
||||
scantesting "github.com/goharbor/harbor/src/testing/controller/scan"
|
||||
"github.com/goharbor/harbor/src/testing/mock"
|
||||
htesting "github.com/goharbor/harbor/src/testing/server/v2.0/handler"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
@ -50,3 +61,134 @@ func TestParse(t *testing.T) {
|
||||
repository, reference, err = parse(input)
|
||||
require.NotNil(t, err)
|
||||
}
|
||||
|
||||
type ArtifactTestSuite struct {
|
||||
htesting.Suite
|
||||
|
||||
artCtl *artifacttesting.Controller
|
||||
scanCtl *scantesting.Controller
|
||||
|
||||
report1 *scan.Report
|
||||
report2 *scan.Report
|
||||
}
|
||||
|
||||
func (suite *ArtifactTestSuite) SetupSuite() {
|
||||
suite.artCtl = &artifacttesting.Controller{}
|
||||
suite.scanCtl = &scantesting.Controller{}
|
||||
|
||||
suite.Config = &restapi.Config{
|
||||
ArtifactAPI: &artifactAPI{
|
||||
artCtl: suite.artCtl,
|
||||
scanCtl: suite.scanCtl,
|
||||
},
|
||||
}
|
||||
|
||||
suite.Suite.SetupSuite()
|
||||
|
||||
mock.OnAnything(projectCtlMock, "GetByName").Return(&project.Project{ProjectID: 1}, nil)
|
||||
|
||||
suite.report1 = &scan.Report{
|
||||
MimeType: v1.MimeTypeNativeReport,
|
||||
Report: "{}",
|
||||
}
|
||||
|
||||
suite.report2 = &scan.Report{
|
||||
MimeType: v1.MimeTypeGenericVulnerabilityReport,
|
||||
Report: "{}",
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ArtifactTestSuite) onGetReport(mimeType string, reports ...*scan.Report) {
|
||||
suite.scanCtl.On("GetReport", mock.Anything, mock.Anything, []string{mimeType}).Return(reports, nil).Once()
|
||||
}
|
||||
|
||||
func (suite *ArtifactTestSuite) TestGetVulnerabilitiesAddition() {
|
||||
times := 6
|
||||
suite.Security.On("IsAuthenticated").Return(true).Times(times)
|
||||
suite.Security.On("IsSysAdmin").Return(true).Times(times)
|
||||
mock.OnAnything(suite.Security, "Can").Return(true).Times(times)
|
||||
mock.OnAnything(suite.artCtl, "GetByReference").Return(&artifact.Artifact{}, nil).Times(times)
|
||||
|
||||
url := "/projects/library/repositories/photon/artifacts/2.0/additions/vulnerabilities"
|
||||
|
||||
{
|
||||
// report not found for the default X-Accept-Vulnerabilities
|
||||
suite.onGetReport(v1.MimeTypeNativeReport)
|
||||
|
||||
var body map[string]interface{}
|
||||
res, err := suite.GetJSON(url, &body)
|
||||
suite.NoError(err)
|
||||
suite.Equal(200, res.StatusCode)
|
||||
suite.Empty(body)
|
||||
}
|
||||
|
||||
{
|
||||
// report found for the default X-Accept-Vulnerabilities
|
||||
suite.onGetReport(v1.MimeTypeNativeReport, suite.report1)
|
||||
|
||||
var body map[string]interface{}
|
||||
res, err := suite.GetJSON(url, &body)
|
||||
suite.NoError(err)
|
||||
suite.Equal(200, res.StatusCode)
|
||||
suite.NotEmpty(body)
|
||||
suite.Contains(body, v1.MimeTypeNativeReport)
|
||||
}
|
||||
|
||||
{
|
||||
// report found for the X-Accept-Vulnerabilities of "application/vnd.security.vulnerability.report; version=1.1"
|
||||
suite.onGetReport(v1.MimeTypeGenericVulnerabilityReport, suite.report2)
|
||||
|
||||
var body map[string]interface{}
|
||||
res, err := suite.GetJSON(url, &body, map[string]string{"X-Accept-Vulnerabilities": v1.MimeTypeGenericVulnerabilityReport})
|
||||
suite.NoError(err)
|
||||
suite.Equal(200, res.StatusCode)
|
||||
suite.NotEmpty(body)
|
||||
suite.Contains(body, v1.MimeTypeGenericVulnerabilityReport)
|
||||
}
|
||||
|
||||
{
|
||||
// report found for "application/vnd.security.vulnerability.report; version=1.1"
|
||||
// and the X-Accept-Vulnerabilities is "application/vnd.security.vulnerability.report; version=1.1, application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0"
|
||||
suite.onGetReport(v1.MimeTypeGenericVulnerabilityReport, suite.report2)
|
||||
|
||||
var body map[string]interface{}
|
||||
res, err := suite.GetJSON(url, &body, map[string]string{"X-Accept-Vulnerabilities": v1.MimeTypeGenericVulnerabilityReport + "," + v1.MimeTypeNativeReport})
|
||||
suite.NoError(err)
|
||||
suite.Equal(200, res.StatusCode)
|
||||
suite.NotEmpty(body)
|
||||
suite.Contains(body, v1.MimeTypeGenericVulnerabilityReport)
|
||||
}
|
||||
|
||||
{
|
||||
// report not found for "application/vnd.security.vulnerability.report; version=1.1"
|
||||
// report found for "application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0"
|
||||
// and the X-Accept-Vulnerabilities is "application/vnd.security.vulnerability.report; version=1.1, application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0"
|
||||
suite.onGetReport(v1.MimeTypeGenericVulnerabilityReport)
|
||||
suite.onGetReport(v1.MimeTypeNativeReport, suite.report1)
|
||||
|
||||
var body map[string]interface{}
|
||||
res, err := suite.GetJSON(url, &body, map[string]string{"X-Accept-Vulnerabilities": v1.MimeTypeGenericVulnerabilityReport + "," + v1.MimeTypeNativeReport})
|
||||
suite.NoError(err)
|
||||
suite.Equal(200, res.StatusCode)
|
||||
suite.NotEmpty(body)
|
||||
suite.Contains(body, v1.MimeTypeNativeReport)
|
||||
}
|
||||
|
||||
{
|
||||
// report not found for "application/vnd.security.vulnerability.report; version=1.1"
|
||||
// report not found for "application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0"
|
||||
// and the X-Accept-Vulnerabilities is "application/vnd.security.vulnerability.report; version=1.1, application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0"
|
||||
suite.onGetReport(v1.MimeTypeGenericVulnerabilityReport)
|
||||
suite.onGetReport(v1.MimeTypeNativeReport)
|
||||
|
||||
var body map[string]interface{}
|
||||
res, err := suite.GetJSON(url, &body, map[string]string{"X-Accept-Vulnerabilities": v1.MimeTypeGenericVulnerabilityReport + "," + v1.MimeTypeNativeReport})
|
||||
suite.NoError(err)
|
||||
suite.Equal(200, res.StatusCode)
|
||||
suite.Empty(body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactTestSuite(t *testing.T) {
|
||||
suite.Run(t, &ArtifactTestSuite{})
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ import (
|
||||
"github.com/goharbor/harbor/src/controller/scan"
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/handler/model"
|
||||
)
|
||||
|
||||
@ -29,12 +28,13 @@ const (
|
||||
)
|
||||
|
||||
// NewVulAssembler returns vul assembler
|
||||
func NewVulAssembler(withScanOverview bool) *VulAssembler {
|
||||
func NewVulAssembler(withScanOverview bool, mimeTypes []string) *VulAssembler {
|
||||
return &VulAssembler{
|
||||
scanChecker: scan.NewChecker(),
|
||||
scanCtl: scan.DefaultController,
|
||||
|
||||
withScanOverview: withScanOverview,
|
||||
mimeTypes: mimeTypes,
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,6 +45,7 @@ type VulAssembler struct {
|
||||
|
||||
artifacts []*model.Artifact
|
||||
withScanOverview bool
|
||||
mimeTypes []string
|
||||
}
|
||||
|
||||
// WithArtifacts set artifacts for the assembler
|
||||
@ -72,11 +73,14 @@ func (assembler *VulAssembler) Assemble(ctx context.Context) error {
|
||||
artifact.SetAdditionLink(vulnerabilitiesAddition, version)
|
||||
|
||||
if assembler.withScanOverview {
|
||||
overview, err := assembler.scanCtl.GetSummary(ctx, &artifact.Artifact, []string{v1.MimeTypeNativeReport, v1.MimeTypeGenericVulnerabilityReport})
|
||||
if err != nil {
|
||||
log.Warningf("get scan summary of artifact %s@%s failed, error:%v", artifact.RepositoryName, artifact.Digest, err)
|
||||
} else if len(overview) > 0 {
|
||||
artifact.ScanOverview = overview
|
||||
for _, mimeType := range assembler.mimeTypes {
|
||||
overview, err := assembler.scanCtl.GetSummary(ctx, &artifact.Artifact, []string{mimeType})
|
||||
if err != nil {
|
||||
log.Warningf("get scan summary of artifact %s@%s for %s failed, error:%v", artifact.RepositoryName, artifact.Digest, mimeType, err)
|
||||
} else if len(overview) > 0 {
|
||||
artifact.ScanOverview = overview
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ func (suite *VulAssemblerTestSuite) TestScannable() {
|
||||
scanChecker: checker,
|
||||
scanCtl: scanCtl,
|
||||
withScanOverview: true,
|
||||
mimeTypes: []string{"mimeType"},
|
||||
}
|
||||
|
||||
mock.OnAnything(checker, "IsScannable").Return(true, nil)
|
||||
|
@ -36,6 +36,10 @@ import (
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
)
|
||||
|
||||
var (
|
||||
baseProjectCtl = project.Ctl
|
||||
)
|
||||
|
||||
// BaseAPI base API handler
|
||||
type BaseAPI struct{}
|
||||
|
||||
@ -68,7 +72,7 @@ func (b *BaseAPI) HasProjectPermission(ctx context.Context, projectIDOrName inte
|
||||
}
|
||||
|
||||
if projectName != "" {
|
||||
p, err := project.Ctl.GetByName(ctx, projectName)
|
||||
p, err := baseProjectCtl.GetByName(ctx, projectName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get project %s: %v", projectName, err)
|
||||
return false
|
||||
|
@ -15,9 +15,17 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/suite"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/goharbor/harbor/src/controller/project"
|
||||
projecttesting "github.com/goharbor/harbor/src/testing/controller/project"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
var (
|
||||
projectCtlMock *projecttesting.Controller
|
||||
)
|
||||
|
||||
type baseHandlerTestSuite struct {
|
||||
@ -108,3 +116,15 @@ func (b *baseHandlerTestSuite) TestLinks() {
|
||||
func TestBaseHandler(t *testing.T) {
|
||||
suite.Run(t, &baseHandlerTestSuite{})
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
projectCtlMock = &projecttesting.Controller{}
|
||||
|
||||
baseProjectCtl = projectCtlMock
|
||||
|
||||
exitVal := m.Run()
|
||||
|
||||
baseProjectCtl = project.Ctl
|
||||
|
||||
os.Exit(exitVal)
|
||||
}
|
||||
|
@ -15,19 +15,14 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/goharbor/harbor/src/controller/artifact"
|
||||
"github.com/goharbor/harbor/src/controller/artifact/processor"
|
||||
"github.com/goharbor/harbor/src/controller/scan"
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
"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"
|
||||
)
|
||||
|
||||
@ -46,49 +41,24 @@ const (
|
||||
ScheduleNone = "None"
|
||||
)
|
||||
|
||||
func boolValue(v *bool) bool {
|
||||
if v != nil {
|
||||
return *v
|
||||
}
|
||||
func parseScanReportMimeTypes(header *string) []string {
|
||||
var mimeTypes []string
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func resolveVulnerabilitiesAddition(ctx context.Context, artifact *artifact.Artifact) (*processor.Addition, error) {
|
||||
reports, err := scan.DefaultController.GetReport(ctx, artifact, []string{v1.MimeTypeNativeReport, v1.MimeTypeGenericVulnerabilityReport})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vulnerabilities := make(map[string]interface{})
|
||||
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 nil, err
|
||||
}
|
||||
|
||||
if v, ok := vulnerabilities[rp.MimeType]; ok {
|
||||
r, err := report.Merge(rp.MimeType, v, vrp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if header != nil {
|
||||
for _, mimeType := range strings.Split(*header, ",") {
|
||||
mimeType = strings.TrimSpace(mimeType)
|
||||
switch mimeType {
|
||||
case v1.MimeTypeNativeReport, v1.MimeTypeGenericVulnerabilityReport:
|
||||
mimeTypes = append(mimeTypes, mimeType)
|
||||
}
|
||||
vulnerabilities[rp.MimeType] = r
|
||||
} else {
|
||||
vulnerabilities[rp.MimeType] = vrp
|
||||
}
|
||||
}
|
||||
|
||||
content, _ := json.Marshal(vulnerabilities)
|
||||
if len(mimeTypes) == 0 {
|
||||
mimeTypes = append(mimeTypes, v1.MimeTypeNativeReport)
|
||||
}
|
||||
|
||||
return &processor.Addition{
|
||||
Content: content,
|
||||
ContentType: "application/json",
|
||||
}, nil
|
||||
return mimeTypes
|
||||
}
|
||||
|
||||
func unescapePathParams(params interface{}, fieldNames ...string) error {
|
||||
|
@ -63,34 +63,39 @@ func (suite *Suite) TearDownSuite() {
|
||||
}
|
||||
|
||||
// DoReq ...
|
||||
func (suite *Suite) DoReq(method string, url string, body io.Reader, contentTypes ...string) (*http.Response, error) {
|
||||
func (suite *Suite) DoReq(method string, url string, body io.Reader, headers ...map[string]string) (*http.Response, error) {
|
||||
req, err := http.NewRequest(method, suite.ts.URL+"/api/v2.0"+url, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
contentType := "application/json"
|
||||
if len(contentTypes) > 0 {
|
||||
contentType = contentTypes[0]
|
||||
if len(headers) > 0 {
|
||||
for key, value := range headers[0] {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
contentType := req.Header.Get("Content-Type")
|
||||
if contentType == "" {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
|
||||
return suite.tc.Do(req)
|
||||
}
|
||||
|
||||
// Delete ...
|
||||
func (suite *Suite) Delete(url string, contentTypes ...string) (*http.Response, error) {
|
||||
return suite.DoReq(http.MethodDelete, url, nil, contentTypes...)
|
||||
func (suite *Suite) Delete(url string, headers ...map[string]string) (*http.Response, error) {
|
||||
return suite.DoReq(http.MethodDelete, url, nil, headers...)
|
||||
}
|
||||
|
||||
// Get ...
|
||||
func (suite *Suite) Get(url string, contentTypes ...string) (*http.Response, error) {
|
||||
return suite.DoReq(http.MethodGet, url, nil, contentTypes...)
|
||||
func (suite *Suite) Get(url string, headers ...map[string]string) (*http.Response, error) {
|
||||
return suite.DoReq(http.MethodGet, url, nil, headers...)
|
||||
}
|
||||
|
||||
// GetJSON ...
|
||||
func (suite *Suite) GetJSON(url string, js interface{}) (*http.Response, error) {
|
||||
res, err := suite.Get(url)
|
||||
func (suite *Suite) GetJSON(url string, js interface{}, headers ...map[string]string) (*http.Response, error) {
|
||||
res, err := suite.Get(url, headers...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -113,8 +118,8 @@ func (suite *Suite) GetJSON(url string, js interface{}) (*http.Response, error)
|
||||
}
|
||||
|
||||
// Patch ...
|
||||
func (suite *Suite) Patch(url string, body io.Reader, contentTypes ...string) (*http.Response, error) {
|
||||
return suite.DoReq(http.MethodPatch, url, body, contentTypes...)
|
||||
func (suite *Suite) Patch(url string, body io.Reader, headers ...map[string]string) (*http.Response, error) {
|
||||
return suite.DoReq(http.MethodPatch, url, body, headers...)
|
||||
}
|
||||
|
||||
// PatchJSON ...
|
||||
@ -128,8 +133,8 @@ func (suite *Suite) PatchJSON(url string, js interface{}) (*http.Response, error
|
||||
}
|
||||
|
||||
// Post ...
|
||||
func (suite *Suite) Post(url string, body io.Reader, contentTypes ...string) (*http.Response, error) {
|
||||
return suite.DoReq(http.MethodPost, url, body, contentTypes...)
|
||||
func (suite *Suite) Post(url string, body io.Reader, headers ...map[string]string) (*http.Response, error) {
|
||||
return suite.DoReq(http.MethodPost, url, body, headers...)
|
||||
}
|
||||
|
||||
// PostJSON ...
|
||||
@ -143,8 +148,8 @@ func (suite *Suite) PostJSON(url string, js interface{}) (*http.Response, error)
|
||||
}
|
||||
|
||||
// Put ...
|
||||
func (suite *Suite) Put(url string, body io.Reader, contentTypes ...string) (*http.Response, error) {
|
||||
return suite.DoReq(http.MethodPut, url, body, contentTypes...)
|
||||
func (suite *Suite) Put(url string, body io.Reader, headers ...map[string]string) (*http.Response, error) {
|
||||
return suite.DoReq(http.MethodPut, url, body, headers...)
|
||||
}
|
||||
|
||||
// PutJSON ...
|
||||
|
Loading…
Reference in New Issue
Block a user