mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-22 10:15:35 +01:00
fix: export cve adds resource check and project validation (#17265)
1. Add resource permission check for API handler 2. Validate export cve params project 3. Optimize friendly human message when execution status is error Signed-off-by: chlins <chenyuzh@vmware.com>
This commit is contained in:
parent
bd1d441b01
commit
bff4e13087
@ -9228,10 +9228,6 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
x-omitempty: false
|
x-omitempty: false
|
||||||
description: The status text
|
description: The status text
|
||||||
job_name:
|
|
||||||
type: string
|
|
||||||
x-omitempty: false
|
|
||||||
description: The name of the job as specified by the user
|
|
||||||
user_name:
|
user_name:
|
||||||
type: string
|
type: string
|
||||||
x-omitempty: false
|
x-omitempty: false
|
||||||
|
@ -76,4 +76,5 @@ const (
|
|||||||
ResourceScanAll = Resource("scan-all")
|
ResourceScanAll = Resource("scan-all")
|
||||||
ResourceSystemVolumes = Resource("system-volumes")
|
ResourceSystemVolumes = Resource("system-volumes")
|
||||||
ResourcePurgeAuditLog = Resource("purge-audit")
|
ResourcePurgeAuditLog = Resource("purge-audit")
|
||||||
|
ResourceExportCVE = Resource("export-cve")
|
||||||
)
|
)
|
||||||
|
@ -124,6 +124,10 @@ var (
|
|||||||
{Resource: rbac.ResourcePreatPolicy, Action: rbac.ActionUpdate},
|
{Resource: rbac.ResourcePreatPolicy, Action: rbac.ActionUpdate},
|
||||||
{Resource: rbac.ResourcePreatPolicy, Action: rbac.ActionDelete},
|
{Resource: rbac.ResourcePreatPolicy, Action: rbac.ActionDelete},
|
||||||
{Resource: rbac.ResourcePreatPolicy, Action: rbac.ActionList},
|
{Resource: rbac.ResourcePreatPolicy, Action: rbac.ActionList},
|
||||||
|
|
||||||
|
{Resource: rbac.ResourceExportCVE, Action: rbac.ActionCreate},
|
||||||
|
{Resource: rbac.ResourceExportCVE, Action: rbac.ActionRead},
|
||||||
|
{Resource: rbac.ResourceExportCVE, Action: rbac.ActionList},
|
||||||
},
|
},
|
||||||
|
|
||||||
"maintainer": {
|
"maintainer": {
|
||||||
@ -208,6 +212,10 @@ var (
|
|||||||
|
|
||||||
{Resource: rbac.ResourceArtifactLabel, Action: rbac.ActionCreate},
|
{Resource: rbac.ResourceArtifactLabel, Action: rbac.ActionCreate},
|
||||||
{Resource: rbac.ResourceArtifactLabel, Action: rbac.ActionDelete},
|
{Resource: rbac.ResourceArtifactLabel, Action: rbac.ActionDelete},
|
||||||
|
|
||||||
|
{Resource: rbac.ResourceExportCVE, Action: rbac.ActionCreate},
|
||||||
|
{Resource: rbac.ResourceExportCVE, Action: rbac.ActionRead},
|
||||||
|
{Resource: rbac.ResourceExportCVE, Action: rbac.ActionList},
|
||||||
},
|
},
|
||||||
|
|
||||||
"developer": {
|
"developer": {
|
||||||
@ -270,6 +278,10 @@ var (
|
|||||||
|
|
||||||
{Resource: rbac.ResourceArtifactLabel, Action: rbac.ActionCreate},
|
{Resource: rbac.ResourceArtifactLabel, Action: rbac.ActionCreate},
|
||||||
{Resource: rbac.ResourceArtifactLabel, Action: rbac.ActionDelete},
|
{Resource: rbac.ResourceArtifactLabel, Action: rbac.ActionDelete},
|
||||||
|
|
||||||
|
{Resource: rbac.ResourceExportCVE, Action: rbac.ActionCreate},
|
||||||
|
{Resource: rbac.ResourceExportCVE, Action: rbac.ActionRead},
|
||||||
|
{Resource: rbac.ResourceExportCVE, Action: rbac.ActionList},
|
||||||
},
|
},
|
||||||
|
|
||||||
"guest": {
|
"guest": {
|
||||||
|
@ -114,6 +114,7 @@ func (c *controller) Start(ctx context.Context, request export.Request) (executi
|
|||||||
logger := log.GetLogger(ctx)
|
logger := log.GetLogger(ctx)
|
||||||
vendorID := int64(ctx.Value(export.CsvJobVendorIDKey).(int))
|
vendorID := int64(ctx.Value(export.CsvJobVendorIDKey).(int))
|
||||||
extraAttrs := make(map[string]interface{})
|
extraAttrs := make(map[string]interface{})
|
||||||
|
extraAttrs[export.ProjectIDsAttribute] = request.Projects
|
||||||
extraAttrs[export.JobNameAttribute] = request.JobName
|
extraAttrs[export.JobNameAttribute] = request.JobName
|
||||||
extraAttrs[export.UserNameAttribute] = request.UserName
|
extraAttrs[export.UserNameAttribute] = request.UserName
|
||||||
id, err := c.execMgr.Create(ctx, job.ScanDataExport, vendorID, task.ExecutionTriggerManual, extraAttrs)
|
id, err := c.execMgr.Create(ctx, job.ScanDataExport, vendorID, task.ExecutionTriggerManual, extraAttrs)
|
||||||
@ -170,6 +171,11 @@ func (c *controller) convertToExportExecStatus(ctx context.Context, exec *task.E
|
|||||||
StartTime: exec.StartTime,
|
StartTime: exec.StartTime,
|
||||||
EndTime: exec.EndTime,
|
EndTime: exec.EndTime,
|
||||||
}
|
}
|
||||||
|
if pids, ok := exec.ExtraAttrs[export.ProjectIDsAttribute]; ok {
|
||||||
|
for _, pid := range pids.([]interface{}) {
|
||||||
|
execStatus.ProjectIDs = append(execStatus.ProjectIDs, int64(pid.(float64)))
|
||||||
|
}
|
||||||
|
}
|
||||||
if digest, ok := exec.ExtraAttrs[export.DigestKey]; ok {
|
if digest, ok := exec.ExtraAttrs[export.DigestKey]; ok {
|
||||||
execStatus.ExportDataDigest = digest.(string)
|
execStatus.ExportDataDigest = digest.(string)
|
||||||
}
|
}
|
||||||
|
@ -241,6 +241,7 @@ func (suite *ScanDataExportExecutionTestSuite) TestStart() {
|
|||||||
{
|
{
|
||||||
// get execution succeeds
|
// get execution succeeds
|
||||||
attrs := make(map[string]interface{})
|
attrs := make(map[string]interface{})
|
||||||
|
attrs[export.ProjectIDsAttribute] = []int64{1}
|
||||||
attrs[export.JobNameAttribute] = "test-job"
|
attrs[export.JobNameAttribute] = "test-job"
|
||||||
attrs[export.UserNameAttribute] = "test-user"
|
attrs[export.UserNameAttribute] = "test-user"
|
||||||
suite.execMgr.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything, attrs).Return(int64(10), nil)
|
suite.execMgr.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything, attrs).Return(int64(10), nil)
|
||||||
@ -248,6 +249,7 @@ func (suite *ScanDataExportExecutionTestSuite) TestStart() {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
ctx = context.WithValue(ctx, export.CsvJobVendorIDKey, int(-1))
|
ctx = context.WithValue(ctx, export.CsvJobVendorIDKey, int(-1))
|
||||||
criteria := export.Request{}
|
criteria := export.Request{}
|
||||||
|
criteria.Projects = []int64{1}
|
||||||
criteria.UserName = "test-user"
|
criteria.UserName = "test-user"
|
||||||
criteria.JobName = "test-job"
|
criteria.JobName = "test-job"
|
||||||
executionId, err := suite.ctl.Start(ctx, criteria)
|
executionId, err := suite.ctl.Start(ctx, criteria)
|
||||||
@ -303,13 +305,14 @@ func (suite *ScanDataExportExecutionTestSuite) TestStartWithTaskManagerError() {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
ctx = context.WithValue(ctx, export.CsvJobVendorIDKey, int(-1))
|
ctx = context.WithValue(ctx, export.CsvJobVendorIDKey, int(-1))
|
||||||
attrs := make(map[string]interface{})
|
attrs := make(map[string]interface{})
|
||||||
|
attrs[export.ProjectIDsAttribute] = []int64{1}
|
||||||
attrs[export.JobNameAttribute] = "test-job"
|
attrs[export.JobNameAttribute] = "test-job"
|
||||||
attrs[export.UserNameAttribute] = "test-user"
|
attrs[export.UserNameAttribute] = "test-user"
|
||||||
suite.execMgr.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything, attrs).Return(int64(10), nil)
|
suite.execMgr.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything, attrs).Return(int64(10), nil)
|
||||||
suite.taskMgr.On("Create", mock.Anything, mock.Anything, mock.Anything).Return(int64(-1), errors.New("Test Error"))
|
suite.taskMgr.On("Create", mock.Anything, mock.Anything, mock.Anything).Return(int64(-1), errors.New("Test Error"))
|
||||||
mock.OnAnything(suite.execMgr, "StopAndWait").Return(nil)
|
mock.OnAnything(suite.execMgr, "StopAndWait").Return(nil)
|
||||||
mock.OnAnything(suite.execMgr, "MarkError").Return(nil)
|
mock.OnAnything(suite.execMgr, "MarkError").Return(nil)
|
||||||
_, err := suite.ctl.Start(ctx, export.Request{JobName: "test-job", UserName: "test-user"})
|
_, err := suite.ctl.Start(ctx, export.Request{JobName: "test-job", UserName: "test-user", Projects: []int64{1}})
|
||||||
suite.Error(err)
|
suite.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,14 @@ package export
|
|||||||
type CsvJobVendorID string
|
type CsvJobVendorID string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
JobNameAttribute = "job_name"
|
ProjectIDsAttribute = "project_ids"
|
||||||
UserNameAttribute = "user_name"
|
JobNameAttribute = "job_name"
|
||||||
ScanDataExportDir = "/var/scandata_exports"
|
UserNameAttribute = "user_name"
|
||||||
QueryPageSize = 100000
|
ScanDataExportDir = "/var/scandata_exports"
|
||||||
ArtifactGroupSize = 10000
|
QueryPageSize = 100000
|
||||||
DigestKey = "artifact_digest"
|
ArtifactGroupSize = 10000
|
||||||
CreateTimestampKey = "create_ts"
|
DigestKey = "artifact_digest"
|
||||||
Vendor = "SCAN_DATA_EXPORT"
|
CreateTimestampKey = "create_ts"
|
||||||
CsvJobVendorIDKey = CsvJobVendorID("vendorId")
|
Vendor = "SCAN_DATA_EXPORT"
|
||||||
|
CsvJobVendorIDKey = CsvJobVendorID("vendorId")
|
||||||
)
|
)
|
||||||
|
@ -78,6 +78,8 @@ type Execution struct {
|
|||||||
ID int64
|
ID int64
|
||||||
// UserID triggering the execution
|
// UserID triggering the execution
|
||||||
UserID int64
|
UserID int64
|
||||||
|
// ProjectIDs contains projects ids
|
||||||
|
ProjectIDs []int64
|
||||||
// Status provides the status of the execution
|
// Status provides the status of the execution
|
||||||
Status string
|
Status string
|
||||||
// StatusMessage contains the human-readable status message for the execution
|
// StatusMessage contains the human-readable status message for the execution
|
||||||
|
@ -15,7 +15,7 @@ import (
|
|||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/rbac"
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
"github.com/goharbor/harbor/src/controller/scandataexport"
|
"github.com/goharbor/harbor/src/controller/scandataexport"
|
||||||
"github.com/goharbor/harbor/src/jobservice/logger"
|
"github.com/goharbor/harbor/src/jobservice/job"
|
||||||
"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/pkg/scan/export"
|
"github.com/goharbor/harbor/src/pkg/scan/export"
|
||||||
@ -46,7 +46,19 @@ func (se *scanDataExportAPI) Prepare(ctx context.Context, operation string, para
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (se *scanDataExportAPI) ExportScanData(ctx context.Context, params operation.ExportScanDataParams) middleware.Responder {
|
func (se *scanDataExportAPI) ExportScanData(ctx context.Context, params operation.ExportScanDataParams) middleware.Responder {
|
||||||
if err := se.RequireAuthenticated(ctx); err != nil {
|
// validate project id, currently we only support single project
|
||||||
|
criteria := params.Criteria
|
||||||
|
if criteria == nil {
|
||||||
|
err := errors.New(errors.Errorf("criteria is invalid: %v", criteria)).WithCode(errors.BadRequestCode)
|
||||||
|
return se.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(criteria.Projects) != 1 {
|
||||||
|
err := errors.New(errors.Errorf("only support export single project, invalid value: %v", criteria.Projects)).WithCode(errors.BadRequestCode)
|
||||||
|
return se.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := se.RequireProjectAccess(ctx, criteria.Projects[0], rbac.ActionCreate, rbac.ResourceExportCVE); err != nil {
|
||||||
return se.SendError(ctx, err)
|
return se.SendError(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,17 +69,6 @@ func (se *scanDataExportAPI) ExportScanData(ctx context.Context, params operatio
|
|||||||
return operation.NewExportScanDataBadRequest().WithPayload(errors)
|
return operation.NewExportScanDataBadRequest().WithPayload(errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
// loop through the list of projects and validate that scan privilege and create privilege
|
|
||||||
// is available for all projects
|
|
||||||
// TODO : Should we just ignore projects that do not have the required level of access?
|
|
||||||
|
|
||||||
projects := params.Criteria.Projects
|
|
||||||
for _, project := range projects {
|
|
||||||
if err := se.RequireProjectAccess(ctx, project, rbac.ActionCreate, rbac.ResourceScan); err != nil {
|
|
||||||
return se.SendError(ctx, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
scanDataExportJob := new(models.ScanDataExportJob)
|
scanDataExportJob := new(models.ScanDataExportJob)
|
||||||
|
|
||||||
secContext, err := se.GetSecurityContext(ctx)
|
secContext, err := se.GetSecurityContext(ctx)
|
||||||
@ -104,12 +105,17 @@ func (se *scanDataExportAPI) ExportScanData(ctx context.Context, params operatio
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (se *scanDataExportAPI) GetScanDataExportExecution(ctx context.Context, params operation.GetScanDataExportExecutionParams) middleware.Responder {
|
func (se *scanDataExportAPI) GetScanDataExportExecution(ctx context.Context, params operation.GetScanDataExportExecutionParams) middleware.Responder {
|
||||||
err := se.RequireAuthenticated(ctx)
|
if err := se.RequireAuthenticated(ctx); err != nil {
|
||||||
|
return se.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
execution, err := se.scanDataExportCtl.GetExecution(ctx, params.ExecutionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return se.SendError(ctx, err)
|
return se.SendError(ctx, err)
|
||||||
}
|
}
|
||||||
execution, err := se.scanDataExportCtl.GetExecution(ctx, params.ExecutionID)
|
|
||||||
if err != nil {
|
// check the permission by project ids in execution
|
||||||
|
if err = se.requireProjectsAccess(ctx, execution.ProjectIDs, rbac.ActionRead, rbac.ResourceExportCVE); err != nil {
|
||||||
return se.SendError(ctx, err)
|
return se.SendError(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +140,6 @@ func (se *scanDataExportAPI) GetScanDataExportExecution(ctx context.Context, par
|
|||||||
StatusText: execution.StatusMessage,
|
StatusText: execution.StatusMessage,
|
||||||
Trigger: execution.Trigger,
|
Trigger: execution.Trigger,
|
||||||
UserID: execution.UserID,
|
UserID: execution.UserID,
|
||||||
JobName: execution.JobName,
|
|
||||||
UserName: execution.UserName,
|
UserName: execution.UserName,
|
||||||
FilePresent: execution.FilePresent,
|
FilePresent: execution.FilePresent,
|
||||||
}
|
}
|
||||||
@ -143,12 +148,11 @@ func (se *scanDataExportAPI) GetScanDataExportExecution(ctx context.Context, par
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (se *scanDataExportAPI) DownloadScanData(ctx context.Context, params operation.DownloadScanDataParams) middleware.Responder {
|
func (se *scanDataExportAPI) DownloadScanData(ctx context.Context, params operation.DownloadScanDataParams) middleware.Responder {
|
||||||
err := se.RequireAuthenticated(ctx)
|
if err := se.RequireAuthenticated(ctx); err != nil {
|
||||||
if err != nil {
|
|
||||||
return se.SendError(ctx, err)
|
return se.SendError(ctx, err)
|
||||||
}
|
}
|
||||||
execution, err := se.scanDataExportCtl.GetExecution(ctx, params.ExecutionID)
|
|
||||||
|
|
||||||
|
execution, err := se.scanDataExportCtl.GetExecution(ctx, params.ExecutionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if notFound := orm.AsNotFoundError(err, "execution with id: %d not found", params.ExecutionID); notFound != nil {
|
if notFound := orm.AsNotFoundError(err, "execution with id: %d not found", params.ExecutionID); notFound != nil {
|
||||||
return middleware.ResponderFunc(func(writer http.ResponseWriter, producer runtime.Producer) {
|
return middleware.ResponderFunc(func(writer http.ResponseWriter, producer runtime.Producer) {
|
||||||
@ -158,6 +162,11 @@ func (se *scanDataExportAPI) DownloadScanData(ctx context.Context, params operat
|
|||||||
return se.SendError(ctx, err)
|
return se.SendError(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check the permission by project ids in execution
|
||||||
|
if err = se.requireProjectsAccess(ctx, execution.ProjectIDs, rbac.ActionRead, rbac.ResourceExportCVE); err != nil {
|
||||||
|
return se.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
// check if the CSV artifact for the execution exists
|
// check if the CSV artifact for the execution exists
|
||||||
if !execution.FilePresent {
|
if !execution.FilePresent {
|
||||||
return middleware.ResponderFunc(func(writer http.ResponseWriter, producer runtime.Producer) {
|
return middleware.ResponderFunc(func(writer http.ResponseWriter, producer runtime.Producer) {
|
||||||
@ -182,7 +191,7 @@ func (se *scanDataExportAPI) DownloadScanData(ctx context.Context, params operat
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return se.SendError(ctx, err)
|
return se.SendError(ctx, err)
|
||||||
}
|
}
|
||||||
logger.Infof("reading data from file : %s", repositoryName)
|
log.Infof("reading data from file : %s", repositoryName)
|
||||||
|
|
||||||
return middleware.ResponderFunc(func(writer http.ResponseWriter, producer runtime.Producer) {
|
return middleware.ResponderFunc(func(writer http.ResponseWriter, producer runtime.Producer) {
|
||||||
defer se.cleanUpArtifact(ctx, repositoryName, execution.ExportDataDigest, params.ExecutionID, file)
|
defer se.cleanUpArtifact(ctx, repositoryName, execution.ExportDataDigest, params.ExecutionID, file)
|
||||||
@ -191,27 +200,29 @@ func (se *scanDataExportAPI) DownloadScanData(ctx context.Context, params operat
|
|||||||
writer.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", fmt.Sprintf("%s.csv", repositoryName)))
|
writer.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", fmt.Sprintf("%s.csv", repositoryName)))
|
||||||
nbytes, err := io.Copy(writer, file)
|
nbytes, err := io.Copy(writer, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("Encountered error while copying data: %v", err)
|
log.Errorf("Encountered error while copying data: %v", err)
|
||||||
} else {
|
} else {
|
||||||
logger.Debugf("Copied %v bytes from file to client", nbytes)
|
log.Debugf("Copied %v bytes from file to client", nbytes)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (se *scanDataExportAPI) GetScanDataExportExecutionList(ctx context.Context, params operation.GetScanDataExportExecutionListParams) middleware.Responder {
|
func (se *scanDataExportAPI) GetScanDataExportExecutionList(ctx context.Context, params operation.GetScanDataExportExecutionListParams) middleware.Responder {
|
||||||
err := se.RequireAuthenticated(ctx)
|
if err := se.RequireAuthenticated(ctx); err != nil {
|
||||||
if err != nil {
|
|
||||||
return se.SendError(ctx, err)
|
return se.SendError(ctx, err)
|
||||||
}
|
}
|
||||||
secContext, err := se.GetSecurityContext(ctx)
|
|
||||||
|
|
||||||
|
secContext, err := se.GetSecurityContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return se.SendError(ctx, err)
|
return se.SendError(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
executions, err := se.scanDataExportCtl.ListExecutions(ctx, secContext.GetUsername())
|
executions, err := se.scanDataExportCtl.ListExecutions(ctx, secContext.GetUsername())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return se.SendError(ctx, err)
|
return se.SendError(ctx, err)
|
||||||
}
|
}
|
||||||
|
// projectSet store the unrepeated project ids
|
||||||
|
projectSet := make(map[int64]struct{})
|
||||||
execs := make([]*models.ScanDataExportExecution, 0)
|
execs := make([]*models.ScanDataExportExecution, 0)
|
||||||
for _, execution := range executions {
|
for _, execution := range executions {
|
||||||
sdeExec := &models.ScanDataExportExecution{
|
sdeExec := &models.ScanDataExportExecution{
|
||||||
@ -223,11 +234,30 @@ func (se *scanDataExportAPI) GetScanDataExportExecutionList(ctx context.Context,
|
|||||||
Trigger: execution.Trigger,
|
Trigger: execution.Trigger,
|
||||||
UserID: execution.UserID,
|
UserID: execution.UserID,
|
||||||
UserName: execution.UserName,
|
UserName: execution.UserName,
|
||||||
JobName: execution.JobName,
|
|
||||||
FilePresent: execution.FilePresent,
|
FilePresent: execution.FilePresent,
|
||||||
}
|
}
|
||||||
|
// add human friendly message when status is error
|
||||||
|
if sdeExec.Status == job.ErrorStatus.String() {
|
||||||
|
sdeExec.StatusText = "Please contact the system administrator to check the logs of jobservice."
|
||||||
|
}
|
||||||
|
// store project ids
|
||||||
|
for _, pid := range execution.ProjectIDs {
|
||||||
|
projectSet[pid] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
execs = append(execs, sdeExec)
|
execs = append(execs, sdeExec)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// convert projectSet to pids
|
||||||
|
var pids []int64
|
||||||
|
for pid := range projectSet {
|
||||||
|
pids = append(pids, pid)
|
||||||
|
}
|
||||||
|
// check the permission by project ids in execution
|
||||||
|
if err = se.requireProjectsAccess(ctx, pids, rbac.ActionRead, rbac.ResourceExportCVE); err != nil {
|
||||||
|
return se.SendError(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
sdeExecList := models.ScanDataExportExecutionList{Items: execs}
|
sdeExecList := models.ScanDataExportExecutionList{Items: execs}
|
||||||
return operation.NewGetScanDataExportExecutionListOK().WithPayload(&sdeExecList)
|
return operation.NewGetScanDataExportExecutionListOK().WithPayload(&sdeExecList)
|
||||||
}
|
}
|
||||||
@ -247,7 +277,7 @@ func (se *scanDataExportAPI) convertToCriteria(requestCriteria *models.ScanDataE
|
|||||||
|
|
||||||
func (se *scanDataExportAPI) cleanUpArtifact(ctx context.Context, repositoryName, digest string, execID int64, file io.ReadCloser) {
|
func (se *scanDataExportAPI) cleanUpArtifact(ctx context.Context, repositoryName, digest string, execID int64, file io.ReadCloser) {
|
||||||
file.Close()
|
file.Close()
|
||||||
logger.Infof("Deleting report artifact : %v:%v", repositoryName, digest)
|
log.Infof("Deleting report artifact : %v:%v", repositoryName, digest)
|
||||||
|
|
||||||
// the entire delete operation is executed within a transaction to ensure that any failures
|
// the entire delete operation is executed within a transaction to ensure that any failures
|
||||||
// during the blob creation or tracking record creation result in a rollback of the transaction
|
// during the blob creation or tracking record creation result in a rollback of the transaction
|
||||||
@ -270,3 +300,15 @@ func (se *scanDataExportAPI) cleanUpArtifact(ctx context.Context, repositoryName
|
|||||||
log.Errorf("Error deleting system artifact record for %s/%s/%s: %v", vendor, repositoryName, digest, err)
|
log.Errorf("Error deleting system artifact record for %s/%s/%s: %v", vendor, repositoryName, digest, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (se *scanDataExportAPI) requireProjectsAccess(ctx context.Context, pids []int64, action rbac.Action, subresource ...rbac.Resource) error {
|
||||||
|
// check project permission one by one, return error if any project cannot
|
||||||
|
// access permission.
|
||||||
|
for _, pid := range pids {
|
||||||
|
if err := se.RequireProjectAccess(ctx, pid, action, subresource...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -75,6 +75,7 @@ func (suite *ScanExportTestSuite) TestAuthorization() {
|
|||||||
{http.MethodGet, "/export/cve/download/100", nil, nil},
|
{http.MethodGet, "/export/cve/download/100", nil, nil},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(false).Times(3)
|
||||||
suite.Security.On("IsAuthenticated").Return(false).Times(3)
|
suite.Security.On("IsAuthenticated").Return(false).Times(3)
|
||||||
for _, req := range reqs {
|
for _, req := range reqs {
|
||||||
|
|
||||||
@ -134,6 +135,7 @@ func (suite *ScanExportTestSuite) TestExportScanData() {
|
|||||||
|
|
||||||
// user authenticated but incorrect/unsupported header sent across
|
// user authenticated but incorrect/unsupported header sent across
|
||||||
{
|
{
|
||||||
|
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Once()
|
||||||
suite.Security.On("IsAuthenticated").Return(true).Once()
|
suite.Security.On("IsAuthenticated").Return(true).Once()
|
||||||
url := "/export/cve"
|
url := "/export/cve"
|
||||||
|
|
||||||
@ -157,6 +159,32 @@ func (suite *ScanExportTestSuite) TestExportScanData() {
|
|||||||
suite.Equal(nil, err)
|
suite.Equal(nil, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// should return 400 if project id number is not one
|
||||||
|
{
|
||||||
|
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Once()
|
||||||
|
suite.Security.On("IsAuthenticated").Return(true).Once()
|
||||||
|
url := "/export/cve"
|
||||||
|
|
||||||
|
criteria := models.ScanDataExportRequest{
|
||||||
|
CVEIds: "CVE-123",
|
||||||
|
Labels: []int64{100},
|
||||||
|
Projects: []int64{200, 300},
|
||||||
|
Repositories: "test-repo",
|
||||||
|
Tags: "{test-tag1, test-tag2}",
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(criteria)
|
||||||
|
buffer := bytes.NewBuffer(data)
|
||||||
|
|
||||||
|
headers := make(map[string]string)
|
||||||
|
headers["X-Scan-Data-Type"] = v1.MimeTypeGenericVulnerabilityReport
|
||||||
|
|
||||||
|
mock.OnAnything(suite.scanExportCtl, "Start").Return(int64(100), nil).Once()
|
||||||
|
res, err := suite.DoReq(http.MethodPost, url, buffer, headers)
|
||||||
|
suite.Equal(400, res.StatusCode)
|
||||||
|
suite.Equal(nil, err)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ScanExportTestSuite) TestExportScanDataGetUserIdError() {
|
func (suite *ScanExportTestSuite) TestExportScanDataGetUserIdError() {
|
||||||
@ -279,7 +307,6 @@ func (suite *ScanExportTestSuite) TestGetScanDataExportExecution() {
|
|||||||
respData := models.ScanDataExportExecution{}
|
respData := models.ScanDataExportExecution{}
|
||||||
json.NewDecoder(res.Body).Decode(&respData)
|
json.NewDecoder(res.Body).Decode(&respData)
|
||||||
suite.Equal("test-user", respData.UserName)
|
suite.Equal("test-user", respData.UserName)
|
||||||
suite.Equal("test-job", respData.JobName)
|
|
||||||
suite.Equal(false, respData.FilePresent)
|
suite.Equal(false, respData.FilePresent)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user