2019-09-19 13:15:37 +02:00
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package scan
import (
2020-04-03 10:21:36 +02:00
"bytes"
2020-03-12 12:30:12 +01:00
"context"
2024-04-10 16:47:45 +02:00
"encoding/json"
2019-09-24 09:17:40 +02:00
"fmt"
2021-05-18 08:01:59 +02:00
"reflect"
2020-12-14 06:34:35 +01:00
"strings"
2020-03-12 12:30:12 +01:00
"sync"
2023-06-05 09:12:54 +02:00
"time"
2019-09-24 09:17:40 +02:00
2022-07-20 05:33:08 +02:00
"github.com/google/uuid"
2020-03-24 13:45:45 +01:00
ar "github.com/goharbor/harbor/src/controller/artifact"
2023-07-19 09:40:29 +02:00
"github.com/goharbor/harbor/src/controller/event/operator"
2020-11-20 06:13:12 +01:00
"github.com/goharbor/harbor/src/controller/robot"
2020-03-24 13:45:45 +01:00
sc "github.com/goharbor/harbor/src/controller/scanner"
2021-06-08 12:11:10 +02:00
"github.com/goharbor/harbor/src/controller/tag"
2019-09-19 13:15:37 +02:00
"github.com/goharbor/harbor/src/jobservice/job"
2023-06-05 09:12:54 +02:00
"github.com/goharbor/harbor/src/lib/cache"
2021-05-18 08:01:59 +02:00
"github.com/goharbor/harbor/src/lib/config"
2020-03-28 06:04:16 +01:00
"github.com/goharbor/harbor/src/lib/errors"
2020-04-02 08:08:52 +02:00
"github.com/goharbor/harbor/src/lib/log"
2020-12-14 06:34:35 +01:00
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/lib/q"
2021-08-27 11:28:33 +02:00
"github.com/goharbor/harbor/src/lib/retry"
2022-01-29 20:03:39 +01:00
"github.com/goharbor/harbor/src/pkg/accessory"
2021-05-18 08:01:59 +02:00
allowlist "github.com/goharbor/harbor/src/pkg/allowlist/models"
2020-03-12 16:42:53 +01:00
"github.com/goharbor/harbor/src/pkg/permission/types"
2021-01-04 03:24:31 +01:00
"github.com/goharbor/harbor/src/pkg/robot/model"
2019-09-24 09:17:40 +02:00
sca "github.com/goharbor/harbor/src/pkg/scan"
2019-09-19 13:15:37 +02:00
"github.com/goharbor/harbor/src/pkg/scan/dao/scan"
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
2020-12-25 01:47:46 +01:00
"github.com/goharbor/harbor/src/pkg/scan/postprocessors"
2019-09-24 09:17:40 +02:00
"github.com/goharbor/harbor/src/pkg/scan/report"
2019-09-19 13:15:37 +02:00
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
2024-04-16 15:34:19 +02:00
sbomModel "github.com/goharbor/harbor/src/pkg/scan/sbom/model"
2020-04-03 10:21:36 +02:00
"github.com/goharbor/harbor/src/pkg/scan/vuln"
2020-12-14 06:34:35 +01:00
"github.com/goharbor/harbor/src/pkg/task"
2024-04-16 15:34:19 +02:00
"github.com/goharbor/harbor/src/testing/controller/artifact"
2019-09-19 13:15:37 +02:00
)
2023-06-05 09:12:54 +02:00
var (
// DefaultController is a default singleton scan API controller.
DefaultController = NewController ( )
errScanAllStopped = errors . New ( "scanAll stopped" )
)
2019-09-24 09:17:40 +02:00
2021-01-15 07:49:16 +01:00
// const definitions
2019-10-12 10:29:38 +02:00
const (
configRegistryEndpoint = "registryEndpoint"
configCoreInternalAddr = "coreInternalAddr"
2020-12-14 06:34:35 +01:00
artfiactKey = "artifact"
registrationKey = "registration"
2024-04-19 09:36:56 +02:00
artifactIDKey = "artifact_id"
artifactTagKey = "artifact_tag"
reportUUIDsKey = "report_uuids"
robotIDKey = "robot_id"
enabledCapabilities = "enabled_capabilities"
2019-10-12 10:29:38 +02:00
)
// uuidGenerator is a func template which is for generating UUID.
type uuidGenerator func ( ) ( string , error )
// configGetter is a func template which is used to wrap the config management
// utility methods.
type configGetter func ( cfg string ) ( string , error )
2023-06-05 09:12:54 +02:00
// cacheGetter returns cache
type cacheGetter func ( ) cache . Cache
2021-06-08 12:11:10 +02:00
// launchScanJobParam is a param to launch scan job.
type launchScanJobParam struct {
ExecutionID int64
Registration * scanner . Registration
Artifact * ar . Artifact
Tag string
Reports [ ] * scan . Report
2024-04-09 10:05:30 +02:00
Type string
2021-06-08 12:11:10 +02:00
}
2019-09-19 13:15:37 +02:00
// basicController is default implementation of api.Controller interface
type basicController struct {
2019-09-24 09:17:40 +02:00
// Manage the scan report records
manager report . Manager
2020-03-12 12:30:12 +01:00
// Artifact controller
ar ar . Controller
2022-01-29 20:03:39 +01:00
// Accessory manager
acc accessory . Manager
2019-09-24 09:17:40 +02:00
// Scanner controller
sc sc . Controller
2019-10-12 10:29:38 +02:00
// Robot account controller
rc robot . Controller
2021-06-08 12:11:10 +02:00
// Tag controller
tagCtl tag . Controller
2024-04-16 15:34:19 +02:00
// Artifact controller
artCtl artifact . Controller
2019-10-12 10:29:38 +02:00
// UUID generator
uuid uuidGenerator
// Configuration getter func
config configGetter
2020-12-14 06:34:35 +01:00
cloneCtx func ( context . Context ) context . Context
makeCtx func ( ) context . Context
execMgr task . ExecutionManager
taskMgr task . Manager
2020-12-25 01:47:46 +01:00
// Converter for V1 report to V2 report
reportConverter postprocessors . NativeScanReportConverter
2023-06-05 09:12:54 +02:00
// cache stores the stop scan all marks
cache cacheGetter
2019-09-19 13:15:37 +02:00
}
// NewController news a scan API controller
func NewController ( ) Controller {
2019-09-24 09:17:40 +02:00
return & basicController {
// New report manager
manager : report . NewManager ( ) ,
2020-03-12 12:30:12 +01:00
// Refer to the default artifact controller
ar : ar . Ctl ,
2022-01-29 20:03:39 +01:00
// Refer to the default accessory manager
acc : accessory . Mgr ,
2019-10-12 10:29:38 +02:00
// Refer to the default scanner controller
2019-09-24 09:17:40 +02:00
sc : sc . DefaultController ,
2019-10-12 10:29:38 +02:00
// Refer to the default robot account controller
2020-11-20 06:13:12 +01:00
rc : robot . Ctl ,
2021-06-08 12:11:10 +02:00
// Refer to the default tag controller
tagCtl : tag . Ctl ,
2019-10-12 10:29:38 +02:00
// Generate UUID with uuid lib
uuid : func ( ) ( string , error ) {
aUUID , err := uuid . NewUUID ( )
if err != nil {
return "" , err
}
return aUUID . String ( ) , nil
} ,
// Get the required configuration options
config : func ( cfg string ) ( string , error ) {
switch cfg {
case configRegistryEndpoint :
return config . ExtEndpoint ( )
case configCoreInternalAddr :
return config . InternalCoreURL ( ) , nil
default :
return "" , errors . Errorf ( "configuration option %s not defined" , cfg )
}
} ,
2020-12-14 06:34:35 +01:00
cloneCtx : orm . Clone ,
makeCtx : orm . Context ,
execMgr : task . ExecMgr ,
taskMgr : task . Mgr ,
2020-12-25 01:47:46 +01:00
// Get the scan V1 to V2 report converters
2021-05-27 16:39:35 +02:00
reportConverter : postprocessors . Converter ,
2023-06-05 09:12:54 +02:00
cache : func ( ) cache . Cache {
return cache . Default ( )
} ,
2019-09-24 09:17:40 +02:00
}
2019-09-19 13:15:37 +02:00
}
2020-03-12 12:30:12 +01:00
// Collect artifacts itself or its children (exclude child which is image index and not supported by the scanner) when the artifact is scannable.
// Report placeholders will be created to track when scan the artifact.
// The reports of these artifacts will make together when get the reports of the artifact.
// There are two scenarios when artifact is scannable:
// 1. The scanner has capability for the artifact directly, eg the artifact is docker image.
// 2. The artifact is image index and the scanner has capability for any artifact which is referenced by the artifact.
func ( bc * basicController ) collectScanningArtifacts ( ctx context . Context , r * scanner . Registration , artifact * ar . Artifact ) ( [ ] * ar . Artifact , bool , error ) {
var (
scannable bool
artifacts [ ] * ar . Artifact
)
walkFn := func ( a * ar . Artifact ) error {
2022-01-29 20:03:39 +01:00
ok , err := bc . isAccessory ( ctx , a )
if err != nil {
return err
}
if ok {
return nil
}
2020-03-19 03:48:19 +01:00
supported := hasCapability ( r , a )
2020-03-12 12:30:12 +01:00
2020-03-19 03:48:19 +01:00
if ! supported && a . IsImageIndex ( ) {
2020-03-12 12:30:12 +01:00
// image index not supported by the scanner, so continue to walk its children
return nil
}
artifacts = append ( artifacts , a )
2020-03-19 03:48:19 +01:00
if supported {
2020-03-12 12:30:12 +01:00
scannable = true
return ar . ErrSkip // this artifact supported by the scanner, skip to walk its children
}
return nil
}
if err := bc . ar . Walk ( ctx , artifact , walkFn , nil ) ; err != nil {
return nil , false , err
}
return artifacts , scannable , nil
}
2019-09-19 13:15:37 +02:00
// Scan ...
2020-03-12 12:30:12 +01:00
func ( bc * basicController ) Scan ( ctx context . Context , artifact * ar . Artifact , options ... Option ) error {
2019-09-24 09:17:40 +02:00
if artifact == nil {
return errors . New ( "nil artifact to scan" )
}
2020-11-12 08:33:13 +01:00
r , err := bc . sc . GetRegistrationByProject ( ctx , artifact . ProjectID )
2019-09-24 09:17:40 +02:00
if err != nil {
return errors . Wrap ( err , "scan controller: scan" )
}
2019-10-31 04:33:43 +01:00
// In case it does not exist
if r == nil {
2020-03-28 15:22:43 +01:00
return errors . PreconditionFailedError ( nil ) . WithMessage ( "no available scanner for project: %d" , artifact . ProjectID )
2019-10-31 04:33:43 +01:00
}
2019-10-23 10:02:18 +02:00
// Check if it is disabled
if r . Disabled {
2022-03-23 03:56:00 +01:00
return errors . PreconditionFailedError ( nil ) . WithMessage ( "scanner %s is deactivated" , r . Name )
2019-10-23 10:02:18 +02:00
}
2020-03-12 12:30:12 +01:00
artifacts , scannable , err := bc . collectScanningArtifacts ( ctx , r , artifact )
2019-09-24 09:17:40 +02:00
if err != nil {
2020-03-12 12:30:12 +01:00
return err
2019-09-24 09:17:40 +02:00
}
2021-06-08 12:11:10 +02:00
// Parse options
opts , err := parseOptions ( options ... )
if err != nil {
return errors . Wrap ( err , "scan controller: scan" )
2019-09-24 09:17:40 +02:00
}
2024-04-17 16:51:11 +02:00
if ! scannable {
if opts . FromEvent {
// skip to return err for event related scan
return nil
}
return errors . BadRequestError ( nil ) . WithMessage ( "the configured scanner %s does not support scanning artifact with mime type %s" , r . Name , artifact . ManifestMediaType )
}
2021-06-08 12:11:10 +02:00
var (
errs [ ] error
launchScanJobParams [ ] * launchScanJobParam
)
2020-03-12 12:30:12 +01:00
for _ , art := range artifacts {
2024-04-16 15:34:19 +02:00
reports , err := bc . makeReportPlaceholder ( ctx , r , art , opts )
2020-03-12 12:30:12 +01:00
if err != nil {
2020-03-28 06:04:16 +01:00
if errors . IsConflictErr ( err ) {
2020-03-12 12:30:12 +01:00
errs = append ( errs , err )
} else {
return err
2019-09-24 09:17:40 +02:00
}
}
2021-06-08 12:11:10 +02:00
var tag string
if art . Digest == artifact . Digest {
tag = opts . Tag
}
if tag == "" {
latestTag , err := bc . getLatestTagOfArtifact ( ctx , art . ID )
if err != nil {
return err
}
tag = latestTag
}
2020-12-14 06:34:35 +01:00
if len ( reports ) > 0 {
2021-06-08 12:11:10 +02:00
launchScanJobParams = append ( launchScanJobParams , & launchScanJobParam {
Registration : r ,
Artifact : art ,
Tag : tag ,
Reports : reports ,
2024-04-09 10:05:30 +02:00
Type : opts . GetScanType ( ) ,
2021-06-08 12:11:10 +02:00
} )
2020-03-12 12:30:12 +01:00
}
}
2019-09-24 09:17:40 +02:00
2020-03-12 12:30:12 +01:00
// all report placeholder conflicted
if len ( errs ) == len ( artifacts ) {
return errs [ 0 ]
}
2019-09-24 09:17:40 +02:00
2020-12-14 06:34:35 +01:00
if opts . ExecutionID == 0 {
extraAttrs := map [ string ] interface { } {
artfiactKey : map [ string ] interface { } {
"id" : artifact . ID ,
"project_id" : artifact . ProjectID ,
"repository_name" : artifact . RepositoryName ,
"digest" : artifact . Digest ,
} ,
registrationKey : map [ string ] interface { } {
"id" : r . ID ,
"name" : r . Name ,
} ,
2024-04-19 09:36:56 +02:00
enabledCapabilities : map [ string ] interface { } {
"type" : opts . GetScanType ( ) ,
} ,
2020-12-14 06:34:35 +01:00
}
2023-07-19 09:40:29 +02:00
if op := operator . FromContext ( ctx ) ; op != "" {
extraAttrs [ "operator" ] = op
}
2023-05-10 07:23:54 +02:00
executionID , err := bc . execMgr . Create ( ctx , job . ImageScanJobVendorType , artifact . ID , task . ExecutionTriggerManual , extraAttrs )
2020-12-14 06:34:35 +01:00
if err != nil {
return err
}
opts . ExecutionID = executionID
}
2020-03-12 12:30:12 +01:00
errs = errs [ : 0 ]
2021-06-08 12:11:10 +02:00
for _ , launchScanJobParam := range launchScanJobParams {
launchScanJobParam . ExecutionID = opts . ExecutionID
2024-04-16 15:34:19 +02:00
if err := bc . launchScanJob ( ctx , launchScanJobParam , opts ) ; err != nil {
2020-04-08 07:57:48 +02:00
log . G ( ctx ) . Warningf ( "scan artifact %s@%s failed, error: %v" , artifact . RepositoryName , artifact . Digest , err )
2020-03-12 12:30:12 +01:00
errs = append ( errs , err )
2019-09-24 09:17:40 +02:00
}
}
2020-03-12 12:30:12 +01:00
// all scanning of the artifacts failed
2021-06-08 12:11:10 +02:00
if len ( errs ) == len ( launchScanJobParams ) {
2020-03-12 12:30:12 +01:00
return fmt . Errorf ( "scan artifact %s@%s failed" , artifact . RepositoryName , artifact . Digest )
2019-09-24 09:17:40 +02:00
}
2020-03-12 12:30:12 +01:00
return nil
}
2021-08-15 11:21:39 +02:00
// Stop scan job of a given artifact
2024-04-09 10:07:47 +02:00
func ( bc * basicController ) Stop ( ctx context . Context , artifact * ar . Artifact , capType string ) error {
2021-08-15 11:21:39 +02:00
if artifact == nil {
return errors . New ( "nil artifact to stop scan" )
}
2024-04-09 10:07:47 +02:00
query := q . New ( q . KeyWords { "vendor_type" : job . ImageScanJobVendorType , "extra_attrs.artifact.digest" : artifact . Digest , "extra_attrs.enabled_capabilities.type" : capType } )
2021-08-15 11:21:39 +02:00
executions , err := bc . execMgr . List ( ctx , query )
if err != nil {
return err
}
2024-04-09 10:07:47 +02:00
2021-08-15 11:21:39 +02:00
if len ( executions ) == 0 {
message := fmt . Sprintf ( "no scan job for artifact digest=%v" , artifact . Digest )
return errors . BadRequestError ( nil ) . WithMessage ( message )
}
execution := executions [ 0 ]
return bc . execMgr . Stop ( ctx , execution . ID )
}
2020-12-14 06:34:35 +01:00
func ( bc * basicController ) ScanAll ( ctx context . Context , trigger string , async bool ) ( int64 , error ) {
2023-07-19 09:40:29 +02:00
extra := make ( map [ string ] interface { } )
if op := operator . FromContext ( ctx ) ; op != "" {
extra [ "operator" ] = op
}
executionID , err := bc . execMgr . Create ( ctx , job . ScanAllVendorType , 0 , trigger , extra )
2020-03-12 12:30:12 +01:00
if err != nil {
2020-12-14 06:34:35 +01:00
return 0 , err
2020-03-12 12:30:12 +01:00
}
2020-12-14 06:34:35 +01:00
if async {
2021-01-27 09:44:39 +01:00
go func ( ctx context . Context ) {
2020-12-14 06:34:35 +01:00
// if async, this is running in another goroutine ensure the execution exists in db
2021-08-27 11:28:33 +02:00
err := retry . Retry ( func ( ) error {
2020-12-14 06:34:35 +01:00
_ , err := bc . execMgr . Get ( ctx , executionID )
return err
} )
if err != nil {
log . Errorf ( "failed to get the execution %d for the scan all" , executionID )
return
}
2022-06-07 11:00:36 +02:00
err = bc . startScanAll ( ctx , executionID )
2024-03-27 08:15:14 +01:00
if err != nil {
log . Errorf ( "failed to start scan all, executionID=%d, error: %v" , executionID , err )
}
2021-01-27 09:44:39 +01:00
} ( bc . makeCtx ( ) )
2020-12-14 06:34:35 +01:00
} else {
if err := bc . startScanAll ( ctx , executionID ) ; err != nil {
return 0 , err
}
2020-03-12 12:30:12 +01:00
}
2020-12-14 06:34:35 +01:00
return executionID , nil
}
2023-06-05 09:12:54 +02:00
func ( bc * basicController ) StopScanAll ( ctx context . Context , executionID int64 , async bool ) error {
stopScanAll := func ( ctx context . Context , executionID int64 ) error {
// mark scan all stopped
if err := bc . markScanAllStopped ( ctx , executionID ) ; err != nil {
return err
}
// stop the execution and sub tasks
return bc . execMgr . Stop ( ctx , executionID )
}
if async {
go func ( ) {
if err := stopScanAll ( ctx , executionID ) ; err != nil {
log . Errorf ( "failed to stop scan all, error: %v" , err )
}
} ( )
return nil
}
return stopScanAll ( ctx , executionID )
}
func scanAllStoppedKey ( execID int64 ) string {
return fmt . Sprintf ( "scan_all:execution_id:%d:stopped" , execID )
}
func ( bc * basicController ) markScanAllStopped ( ctx context . Context , execID int64 ) error {
// set the expire time to 2 hours, the duration should be large enough
// for controller to capture the stop flag, leverage the key recycled
// by redis TTL, no need to clean by scan controller as the new scan all
// will have a new unique execution id, the old key has no effects to anything.
return bc . cache ( ) . Save ( ctx , scanAllStoppedKey ( execID ) , "" , 2 * time . Hour )
}
func ( bc * basicController ) isScanAllStopped ( ctx context . Context , execID int64 ) bool {
return bc . cache ( ) . Contains ( ctx , scanAllStoppedKey ( execID ) )
}
2020-12-14 06:34:35 +01:00
func ( bc * basicController ) startScanAll ( ctx context . Context , executionID int64 ) error {
batchSize := 50
2021-01-08 03:10:31 +01:00
summary := struct {
TotalCount int ` json:"total_count" `
SubmitCount int ` json:"submit_count" `
ConflictCount int ` json:"conflict_count" `
PreconditionCount int ` json:"precondition_count" `
UnsupportCount int ` json:"unsupport_count" `
UnknowCount int ` json:"unknow_count" `
} { }
2023-06-05 09:12:54 +02:00
// with cancel function to signal downstream worker
ctx , cancel := context . WithCancel ( ctx )
defer cancel ( )
2021-01-08 03:10:31 +01:00
2020-12-14 06:34:35 +01:00
for artifact := range ar . Iterator ( ctx , batchSize , nil , nil ) {
2023-06-05 09:12:54 +02:00
if bc . isScanAllStopped ( ctx , executionID ) {
return errScanAllStopped
}
2021-01-08 03:10:31 +01:00
summary . TotalCount ++
2020-12-14 06:34:35 +01:00
scan := func ( ctx context . Context ) error {
return bc . Scan ( ctx , artifact , WithExecutionID ( executionID ) )
2020-03-12 12:30:12 +01:00
}
2020-12-14 06:34:35 +01:00
2021-09-17 18:07:24 +02:00
if err := orm . WithTransaction ( scan ) ( orm . SetTransactionOpNameToContext ( bc . makeCtx ( ) , "tx-start-scanall" ) ) ; err != nil {
2020-12-14 06:34:35 +01:00
// Just logged
log . Errorf ( "failed to scan artifact %s, error %v" , artifact , err )
2021-01-08 03:10:31 +01:00
switch errors . ErrCode ( err ) {
case errors . ConflictCode :
// a previous scan process is ongoing for the artifact
summary . ConflictCount ++
case errors . PreconditionCode :
// scanner not found or it's disabled
summary . PreconditionCount ++
case errors . BadRequestCode :
// artifact is unsupport
summary . UnsupportCount ++
default :
summary . UnknowCount ++
}
} else {
summary . SubmitCount ++
2019-10-31 04:33:43 +01:00
}
2021-01-08 03:10:31 +01:00
}
2019-10-31 04:33:43 +01:00
2023-07-19 09:40:29 +02:00
exec , err := bc . execMgr . Get ( ctx , executionID )
if err != nil {
return err
}
extraAttrs := exec . ExtraAttrs
if extraAttrs == nil {
extraAttrs = map [ string ] interface { } { "summary" : summary }
} else {
extraAttrs [ "summary" ] = summary
}
2021-01-08 03:10:31 +01:00
if err := bc . execMgr . UpdateExtraAttrs ( ctx , executionID , extraAttrs ) ; err != nil {
log . Errorf ( "failed to set the summary info for the scan all execution, error: %v" , err )
return err
}
if summary . SubmitCount > 0 { // at least one artifact submitted to the job service
return nil
2020-03-12 12:30:12 +01:00
}
2020-12-14 06:34:35 +01:00
// not artifact found
2021-01-08 03:10:31 +01:00
if summary . TotalCount == 0 {
if err := bc . execMgr . MarkDone ( ctx , executionID , "no artifact found" ) ; err != nil {
log . Errorf ( "failed to mark the execution %d to be done, error: %v" , executionID , err )
return err
2020-03-12 12:30:12 +01:00
}
2021-01-08 03:10:31 +01:00
} else if summary . PreconditionCount + summary . UnknowCount == 0 { // not scan job submitted and no failed
message := fmt . Sprintf ( "%d artifact(s) found" , summary . TotalCount )
if summary . UnsupportCount > 0 {
message = fmt . Sprintf ( "%s, %d artifact(s) not scannable" , message , summary . UnsupportCount )
}
if summary . ConflictCount > 0 {
message = fmt . Sprintf ( "%s, %d artifact(s) have a previous ongoing scan process" , message , summary . ConflictCount )
}
message = fmt . Sprintf ( "%s, but no scan job submitted to the job service" , message )
2020-03-12 12:30:12 +01:00
2020-12-14 06:34:35 +01:00
if err := bc . execMgr . MarkDone ( ctx , executionID , message ) ; err != nil {
log . Errorf ( "failed to mark the execution %d to be done, error: %v" , executionID , err )
return err
2020-03-12 12:30:12 +01:00
}
2021-01-08 03:10:31 +01:00
} else { // not scan job submitted and failed
message := fmt . Sprintf ( "%d artifact(s) found" , summary . TotalCount )
if summary . PreconditionCount > 0 {
2022-03-23 03:56:00 +01:00
message = fmt . Sprintf ( "%s, scanner not found or deactivated for %d of them" , message , summary . PreconditionCount )
2021-01-08 03:10:31 +01:00
}
if summary . UnknowCount > 0 {
message = fmt . Sprintf ( "%s, internal error happened for %d of them" , message , summary . UnknowCount )
}
message = fmt . Sprintf ( "%s, but no scan job submitted to the job service" , message )
if err := bc . execMgr . MarkError ( ctx , executionID , message ) ; err != nil {
log . Errorf ( "failed to mark the execution %d to be error, error: %v" , executionID , err )
return err
}
2019-09-24 09:17:40 +02:00
}
2020-12-14 06:34:35 +01:00
return nil
2020-03-12 12:30:12 +01:00
}
2024-04-16 15:34:19 +02:00
func ( bc * basicController ) makeReportPlaceholder ( ctx context . Context , r * scanner . Registration , art * ar . Artifact , opts * Options ) ( [ ] * scan . Report , error ) {
mimeTypes := r . GetProducesMimeTypes ( art . ManifestMediaType , opts . GetScanType ( ) )
2020-12-14 06:34:35 +01:00
oldReports , err := bc . manager . GetBy ( bc . cloneCtx ( ctx ) , art . Digest , r . UUID , mimeTypes )
2019-09-24 09:17:40 +02:00
if err != nil {
2020-12-14 06:34:35 +01:00
return nil , err
}
2024-04-16 15:34:19 +02:00
if err := bc . deleteArtifactAccessories ( ctx , oldReports ) ; err != nil {
return nil , err
}
2020-12-14 06:34:35 +01:00
if err := bc . assembleReports ( ctx , oldReports ... ) ; err != nil {
return nil , err
}
if len ( oldReports ) > 0 {
for _ , oldReport := range oldReports {
if ! job . Status ( oldReport . Status ) . Final ( ) {
return nil , errors . ConflictError ( nil ) . WithMessage ( "a previous scan process is %s" , oldReport . Status )
}
2019-09-24 09:17:40 +02:00
}
2020-12-14 06:34:35 +01:00
for _ , oldReport := range oldReports {
if err := bc . manager . Delete ( ctx , oldReport . UUID ) ; err != nil {
return nil , err
}
}
2019-09-24 09:17:40 +02:00
}
2020-12-14 06:34:35 +01:00
var reports [ ] * scan . Report
2024-04-16 15:34:19 +02:00
for _ , pm := range r . GetProducesMimeTypes ( art . ManifestMediaType , opts . GetScanType ( ) ) {
2020-12-14 06:34:35 +01:00
report := & scan . Report {
Digest : art . Digest ,
RegistrationUUID : r . UUID ,
MimeType : pm ,
}
create := func ( ctx context . Context ) error {
reportUUID , err := bc . manager . Create ( ctx , report )
if err != nil {
return err
}
report . UUID = reportUUID
return nil
}
2021-09-17 18:07:24 +02:00
if err := orm . WithTransaction ( create ) ( orm . SetTransactionOpNameToContext ( ctx , "tx-make-report-placeholder" ) ) ; err != nil {
2020-12-14 06:34:35 +01:00
return nil , err
}
reports = append ( reports , report )
2019-09-24 09:17:40 +02:00
}
2020-12-14 06:34:35 +01:00
return reports , nil
2019-09-19 13:15:37 +02:00
}
// GetReport ...
2020-03-12 12:30:12 +01:00
func ( bc * basicController ) GetReport ( ctx context . Context , artifact * ar . Artifact , mimeTypes [ ] string ) ( [ ] * scan . Report , error ) {
2019-09-24 09:17:40 +02:00
if artifact == nil {
return nil , errors . New ( "no way to get report for nil artifact" )
}
mimes := make ( [ ] string , 0 )
mimes = append ( mimes , mimeTypes ... )
if len ( mimes ) == 0 {
2020-12-25 01:47:46 +01:00
// Retrieve native and the new generic format as default
mimes = append ( mimes , v1 . MimeTypeNativeReport , v1 . MimeTypeGenericVulnerabilityReport )
2019-09-24 09:17:40 +02:00
}
// Get current scanner settings
2020-11-12 08:33:13 +01:00
r , err := bc . sc . GetRegistrationByProject ( ctx , artifact . ProjectID )
2019-09-24 09:17:40 +02:00
if err != nil {
return nil , errors . Wrap ( err , "scan controller: get report" )
}
if r == nil {
2020-03-28 06:04:16 +01:00
return nil , errors . NotFoundError ( nil ) . WithMessage ( "no scanner registration configured for project: %d" , artifact . ProjectID )
2020-03-12 12:30:12 +01:00
}
artifacts , scannable , err := bc . collectScanningArtifacts ( ctx , r , artifact )
if err != nil {
return nil , err
}
if ! scannable {
2020-03-28 06:04:16 +01:00
return nil , errors . NotFoundError ( nil ) . WithMessage ( "report not found for %s@%s" , artifact . RepositoryName , artifact . Digest )
2020-03-12 12:30:12 +01:00
}
groupReports := make ( [ ] [ ] * scan . Report , len ( artifacts ) )
var wg sync . WaitGroup
for i , a := range artifacts {
wg . Add ( 1 )
go func ( i int , a * ar . Artifact ) {
defer wg . Done ( )
2020-12-14 06:34:35 +01:00
reports , err := bc . manager . GetBy ( bc . cloneCtx ( ctx ) , a . Digest , r . UUID , mimes )
2020-03-12 12:30:12 +01:00
if err != nil {
log . Warningf ( "get reports of %s@%s failed, error: %v" , a . RepositoryName , a . Digest , err )
return
}
groupReports [ i ] = reports
} ( i , a )
}
wg . Wait ( )
var reports [ ] * scan . Report
for _ , group := range groupReports {
if len ( group ) != 0 {
reports = append ( reports , group ... )
} else {
2020-04-03 10:21:36 +02:00
// NOTE: If the artifact is OCI image, this happened when the artifact is not scanned,
2020-03-12 12:30:12 +01:00
// but its children artifacts may scanned so return empty report
return nil , nil
}
2019-09-24 09:17:40 +02:00
}
2020-12-14 06:34:35 +01:00
if len ( reports ) == 0 {
return nil , nil
}
if err := bc . assembleReports ( ctx , reports ... ) ; err != nil {
return nil , err
}
2020-03-12 12:30:12 +01:00
return reports , nil
2019-09-19 13:15:37 +02:00
}
2024-04-10 16:47:45 +02:00
func isSBOMMimeTypes ( mimeTypes [ ] string ) bool {
for _ , mimeType := range mimeTypes {
if mimeType == v1 . MimeTypeSBOMReport {
return true
}
}
return false
}
2019-09-24 09:17:40 +02:00
// GetSummary ...
2021-05-18 08:01:59 +02:00
func ( bc * basicController ) GetSummary ( ctx context . Context , artifact * ar . Artifact , mimeTypes [ ] string ) ( map [ string ] interface { } , error ) {
2019-09-24 09:17:40 +02:00
if artifact == nil {
return nil , errors . New ( "no way to get report summaries for nil artifact" )
}
2024-04-10 16:47:45 +02:00
if isSBOMMimeTypes ( mimeTypes ) {
return bc . GetSBOMSummary ( ctx , artifact , mimeTypes )
}
2019-09-24 09:17:40 +02:00
// Get reports first
2020-03-12 12:30:12 +01:00
rps , err := bc . GetReport ( ctx , artifact , mimeTypes )
2019-09-24 09:17:40 +02:00
if err != nil {
return nil , err
}
summaries := make ( map [ string ] interface { } , len ( rps ) )
for _ , rp := range rps {
2021-05-18 08:01:59 +02:00
sum , err := report . GenerateSummary ( rp )
2019-09-24 09:17:40 +02:00
if err != nil {
return nil , err
}
2020-03-12 12:30:12 +01:00
if s , ok := summaries [ rp . MimeType ] ; ok {
r , err := report . MergeSummary ( rp . MimeType , s , sum )
if err != nil {
return nil , err
}
summaries [ rp . MimeType ] = r
} else {
summaries [ rp . MimeType ] = sum
}
2019-09-24 09:17:40 +02:00
}
return summaries , nil
2019-09-19 13:15:37 +02:00
}
2024-04-10 16:47:45 +02:00
func ( bc * basicController ) GetSBOMSummary ( ctx context . Context , art * ar . Artifact , mimeTypes [ ] string ) ( map [ string ] interface { } , error ) {
if art == nil {
return nil , errors . New ( "no way to get report summaries for nil artifact" )
}
r , err := bc . sc . GetRegistrationByProject ( ctx , art . ProjectID )
if err != nil {
return nil , errors . Wrap ( err , "scan controller: get sbom summary" )
}
reports , err := bc . manager . GetBy ( ctx , art . Digest , r . UUID , mimeTypes )
if err != nil {
return nil , err
}
if len ( reports ) == 0 {
return map [ string ] interface { } { } , nil
}
reportContent := reports [ 0 ] . Report
2024-04-17 11:52:50 +02:00
result := map [ string ] interface { } { }
2024-04-10 16:47:45 +02:00
if len ( reportContent ) == 0 {
log . Warning ( "no content for current report" )
2024-04-17 11:52:50 +02:00
return result , nil
2024-04-10 16:47:45 +02:00
}
err = json . Unmarshal ( [ ] byte ( reportContent ) , & result )
return result , err
}
2020-12-14 06:34:35 +01:00
// GetScanLog ...
2022-06-28 12:12:38 +02:00
func ( bc * basicController ) GetScanLog ( ctx context . Context , artifact * ar . Artifact , uuid string ) ( [ ] byte , error ) {
2020-12-14 06:34:35 +01:00
if len ( uuid ) == 0 {
return nil , errors . New ( "empty uuid to get scan log" )
}
2022-06-28 12:12:38 +02:00
r , err := bc . sc . GetRegistrationByProject ( ctx , artifact . ProjectID )
if err != nil {
return nil , err
}
2020-12-14 06:34:35 +01:00
2022-06-28 12:12:38 +02:00
artifacts , _ , err := bc . collectScanningArtifacts ( ctx , r , artifact )
if err != nil {
return nil , err
}
artifactMap := map [ int64 ] interface { } { }
for _ , a := range artifacts {
artifactMap [ a . ID ] = struct { } { }
}
2020-12-14 06:34:35 +01:00
reportUUIDs := vuln . ParseReportIDs ( uuid )
tasks , err := bc . listScanTasks ( ctx , reportUUIDs )
2019-09-24 09:17:40 +02:00
if err != nil {
2020-12-14 06:34:35 +01:00
return nil , err
2019-09-24 09:17:40 +02:00
}
2020-12-14 06:34:35 +01:00
if len ( tasks ) == 0 {
2019-09-24 09:17:40 +02:00
return nil , nil
}
2020-12-14 06:34:35 +01:00
reportUUIDToTasks := map [ string ] * task . Task { }
2022-06-28 12:12:38 +02:00
for _ , t := range tasks {
if ! scanTaskForArtifacts ( t , artifactMap ) {
return nil , errors . NotFoundError ( nil ) . WithMessage ( "scan log with uuid: %s not found" , uuid )
}
for _ , reportUUID := range getReportUUIDs ( t . ExtraAttrs ) {
reportUUIDToTasks [ reportUUID ] = t
2019-09-24 09:17:40 +02:00
}
}
2020-04-03 10:21:36 +02:00
errs := map [ string ] error { }
2020-12-14 06:34:35 +01:00
logs := make ( map [ string ] [ ] byte , len ( tasks ) )
2020-04-03 10:21:36 +02:00
var (
mu sync . Mutex
wg sync . WaitGroup
)
2020-12-14 06:34:35 +01:00
for _ , reportUUID := range reportUUIDs {
2020-04-03 10:21:36 +02:00
wg . Add ( 1 )
2020-12-14 06:34:35 +01:00
go func ( reportUUID string ) {
2020-04-03 10:21:36 +02:00
defer wg . Done ( )
2020-12-14 06:34:35 +01:00
task , ok := reportUUIDToTasks [ reportUUID ]
if ! ok {
return
}
log , err := bc . taskMgr . GetLog ( ctx , task . ID )
2020-04-03 10:21:36 +02:00
mu . Lock ( )
defer mu . Unlock ( )
if err != nil {
2020-12-14 06:34:35 +01:00
errs [ reportUUID ] = err
2020-04-03 10:21:36 +02:00
} else {
2020-12-14 06:34:35 +01:00
logs [ reportUUID ] = log
2020-04-03 10:21:36 +02:00
}
2020-12-14 06:34:35 +01:00
} ( reportUUID )
2020-04-03 10:21:36 +02:00
}
wg . Wait ( )
2020-12-14 06:34:35 +01:00
if len ( reportUUIDs ) == 1 {
return logs [ reportUUIDs [ 0 ] ] , errs [ reportUUIDs [ 0 ] ]
2020-04-03 10:21:36 +02:00
}
2020-12-14 06:34:35 +01:00
if len ( errs ) == len ( reportUUIDs ) {
2020-04-03 10:21:36 +02:00
for _ , err := range errs {
return nil , err
}
}
var b bytes . Buffer
multiLogs := len ( logs ) > 1
2020-12-14 06:34:35 +01:00
for _ , reportUUID := range reportUUIDs {
log , ok := logs [ reportUUID ]
2020-04-03 10:21:36 +02:00
if ! ok || len ( log ) == 0 {
continue
}
if multiLogs {
if b . Len ( ) > 0 {
b . WriteString ( "\n\n\n\n" )
}
2020-12-14 06:34:35 +01:00
b . WriteString ( fmt . Sprintf ( "---------- Logs of report %s ----------\n" , reportUUID ) )
2020-04-03 10:21:36 +02:00
}
b . Write ( log )
}
return b . Bytes ( ) , nil
}
2022-06-28 12:12:38 +02:00
func scanTaskForArtifacts ( task * task . Task , artifactMap map [ int64 ] interface { } ) bool {
if task == nil {
return false
}
artifactID := int64 ( task . GetNumFromExtraAttrs ( artifactIDKey ) )
if artifactID == 0 {
return false
}
_ , exist := artifactMap [ artifactID ]
return exist
}
2019-10-21 14:07:00 +02:00
// DeleteReports ...
2020-12-14 06:34:35 +01:00
func ( bc * basicController ) DeleteReports ( ctx context . Context , digests ... string ) error {
if err := bc . manager . DeleteByDigests ( ctx , digests ... ) ; err != nil {
2019-11-04 10:34:42 +01:00
return errors . Wrap ( err , "scan controller: delete reports" )
}
return nil
}
2023-03-08 04:25:40 +01:00
func ( bc * basicController ) GetVulnerable ( ctx context . Context , artifact * ar . Artifact , allowlist allowlist . CVESet , allowlistIsExpired bool ) ( * Vulnerable , error ) {
2021-05-18 08:01:59 +02:00
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
}
2021-05-27 16:39:35 +02:00
if raw == nil {
return vulnerable , nil
}
2021-05-18 08:01:59 +02:00
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 {
2023-03-08 04:25:40 +01:00
if ! allowlistIsExpired && allowlist . Contains ( v . ID ) {
2021-05-18 08:01:59 +02:00
// 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
}
2020-02-21 10:58:24 +01:00
// makeRobotAccount creates a robot account based on the arguments for scanning.
2024-04-09 10:05:30 +02:00
func ( bc * basicController ) makeRobotAccount ( ctx context . Context , projectID int64 , repository string , registration * scanner . Registration , permission [ ] * types . Policy ) ( * robot . Robot , error ) {
2019-10-12 10:29:38 +02:00
// Use uuid as name to avoid duplicated entries.
UUID , err := bc . uuid ( )
if err != nil {
2020-02-21 10:58:24 +01:00
return nil , errors . Wrap ( err , "scan controller: make robot account" )
2019-10-12 10:29:38 +02:00
}
2020-12-14 06:34:35 +01:00
projectName := strings . Split ( repository , "/" ) [ 0 ]
2023-02-20 08:09:21 +01:00
scannerPrefix := config . ScannerRobotPrefix ( ctx )
2020-11-20 06:13:12 +01:00
robotReq := & robot . Robot {
Robot : model . Robot {
2023-02-20 08:09:21 +01:00
Name : fmt . Sprintf ( "%s-%s-%s" , scannerPrefix , registration . Name , UUID ) ,
2020-11-20 06:13:12 +01:00
Description : "for scan" ,
ProjectID : projectID ,
2024-01-15 06:25:56 +01:00
Duration : - 1 ,
2020-11-20 06:13:12 +01:00
} ,
Level : robot . LEVELPROJECT ,
Permissions : [ ] * robot . Permission {
{
Kind : "project" ,
2020-12-14 06:34:35 +01:00
Namespace : projectName ,
2024-04-09 10:05:30 +02:00
Access : permission ,
2020-11-20 06:13:12 +01:00
} ,
2020-03-17 12:30:21 +01:00
} ,
2019-10-12 10:29:38 +02:00
}
2020-12-03 11:13:06 +01:00
rb , pwd , err := bc . rc . Create ( ctx , robotReq )
2020-11-20 06:13:12 +01:00
if err != nil {
return nil , errors . Wrap ( err , "scan controller: make robot account" )
}
r , err := bc . rc . Get ( ctx , rb , & robot . Option { WithPermission : false } )
2019-10-12 10:29:38 +02:00
if err != nil {
2020-02-21 10:58:24 +01:00
return nil , errors . Wrap ( err , "scan controller: make robot account" )
2019-10-12 10:29:38 +02:00
}
2020-12-03 11:13:06 +01:00
r . Secret = pwd
2020-11-20 06:13:12 +01:00
return r , nil
2019-10-12 10:29:38 +02:00
}
2019-09-24 09:17:40 +02:00
// launchScanJob launches a job to run scan
2024-04-16 15:34:19 +02:00
func ( bc * basicController ) launchScanJob ( ctx context . Context , param * launchScanJobParam , opts * Options ) error {
2020-12-14 06:34:35 +01:00
// don't launch scan job for the artifact which is not supported by the scanner
2021-06-08 12:11:10 +02:00
if ! hasCapability ( param . Registration , param . Artifact ) {
2020-12-14 06:34:35 +01:00
return nil
}
2019-10-24 11:11:33 +02:00
var ck string
2021-06-08 12:11:10 +02:00
if param . Registration . UseInternalAddr {
2019-10-24 11:11:33 +02:00
ck = configCoreInternalAddr
} else {
ck = configRegistryEndpoint
}
registryAddr , err := bc . config ( ck )
2019-09-24 09:17:40 +02:00
if err != nil {
2020-12-14 06:34:35 +01:00
return errors . Wrap ( err , "scan controller: launch scan job" )
2019-09-24 09:17:40 +02:00
}
2024-04-09 10:05:30 +02:00
// Get Scanner handler by scan type to separate the scan logic for different scan types
handler := sca . GetScanHandler ( param . Type )
if handler == nil {
return fmt . Errorf ( "failed to get scan handler, type is %v" , param . Type )
}
robot , err := bc . makeRobotAccount ( ctx , param . Artifact . ProjectID , param . Artifact . RepositoryName , param . Registration , handler . RequiredPermissions ( ) )
2019-09-24 09:17:40 +02:00
if err != nil {
2020-12-14 06:34:35 +01:00
return errors . Wrap ( err , "scan controller: launch scan job" )
2019-09-24 09:17:40 +02:00
}
// Set job parameters
scanReq := & v1 . ScanRequest {
Registry : & v1 . Registry {
2020-04-08 07:57:48 +02:00
URL : registryAddr ,
2019-09-24 09:17:40 +02:00
} ,
2020-03-12 12:30:12 +01:00
Artifact : & v1 . Artifact {
2021-06-08 12:11:10 +02:00
NamespaceID : param . Artifact . ProjectID ,
Repository : param . Artifact . RepositoryName ,
Digest : param . Artifact . Digest ,
Tag : param . Tag ,
MimeType : param . Artifact . ManifestMediaType ,
2024-03-12 05:27:34 +01:00
Size : param . Artifact . Size ,
2020-03-12 12:30:12 +01:00
} ,
2024-04-16 15:34:19 +02:00
RequestType : [ ] * v1 . ScanType {
{
Type : opts . GetScanType ( ) ,
} ,
} ,
2019-09-24 09:17:40 +02:00
}
2021-06-08 12:11:10 +02:00
rJSON , err := param . Registration . ToJSON ( )
2019-09-24 09:17:40 +02:00
if err != nil {
2020-12-14 06:34:35 +01:00
return errors . Wrap ( err , "scan controller: launch scan job" )
2019-09-24 09:17:40 +02:00
}
sJSON , err := scanReq . ToJSON ( )
if err != nil {
2020-12-14 06:34:35 +01:00
return errors . Wrap ( err , "launch scan job" )
2019-09-24 09:17:40 +02:00
}
2020-04-08 07:57:48 +02:00
robotJSON , err := robot . ToJSON ( )
if err != nil {
2020-12-14 06:34:35 +01:00
return errors . Wrap ( err , "launch scan job" )
}
2021-06-08 12:11:10 +02:00
mimes := make ( [ ] string , len ( param . Reports ) )
reportUUIDs := make ( [ ] string , len ( param . Reports ) )
for i , report := range param . Reports {
2020-12-14 06:34:35 +01:00
mimes [ i ] = report . MimeType
reportUUIDs [ i ] = report . UUID
2020-04-08 07:57:48 +02:00
}
2019-09-24 09:17:40 +02:00
params := make ( map [ string ] interface { } )
params [ sca . JobParamRegistration ] = rJSON
2021-06-08 12:11:10 +02:00
params [ sca . JobParameterAuthType ] = param . Registration . GetRegistryAuthorizationType ( )
2019-09-24 09:17:40 +02:00
params [ sca . JobParameterRequest ] = sJSON
params [ sca . JobParameterMimes ] = mimes
2020-04-08 07:57:48 +02:00
params [ sca . JobParameterRobot ] = robotJSON
2019-09-24 09:17:40 +02:00
2020-12-14 06:34:35 +01:00
j := & task . Job {
2023-02-20 09:03:22 +01:00
Name : job . ImageScanJobVendorType ,
2020-12-14 06:34:35 +01:00
Metadata : & job . Metadata {
2019-09-24 09:17:40 +02:00
JobKind : job . KindGeneric ,
} ,
Parameters : params ,
}
2020-12-14 06:34:35 +01:00
// keep the report uuids in array so that when ?| operator support by the FilterRaw method of beego's orm
// we can list the tasks of the scan reports by one SQL
extraAttrs := map [ string ] interface { } {
2021-06-08 12:11:10 +02:00
artifactIDKey : param . Artifact . ID ,
artifactTagKey : param . Tag ,
2020-12-14 06:34:35 +01:00
robotIDKey : robot . ID ,
reportUUIDsKey : reportUUIDs ,
}
2021-06-08 12:11:10 +02:00
_ , err = bc . taskMgr . Create ( ctx , param . ExecutionID , j , extraAttrs )
2020-12-14 06:34:35 +01:00
return err
}
// listScanTasks returns the tasks of the reports
func ( bc * basicController ) listScanTasks ( ctx context . Context , reportUUIDs [ ] string ) ( [ ] * task . Task , error ) {
if len ( reportUUIDs ) == 0 {
return nil , nil
}
tasks := make ( [ ] * task . Task , len ( reportUUIDs ) )
errs := make ( [ ] error , len ( reportUUIDs ) )
var wg sync . WaitGroup
for i , reportUUID := range reportUUIDs {
wg . Add ( 1 )
go func ( ix int , reportUUID string ) {
defer wg . Done ( )
task , err := bc . getScanTask ( bc . cloneCtx ( ctx ) , reportUUID )
if err == nil {
tasks [ ix ] = task
} else if ! errors . IsNotFoundErr ( err ) {
errs [ ix ] = err
} else {
log . G ( ctx ) . Warningf ( "task for the scan report %s not found" , reportUUID )
}
} ( i , reportUUID )
}
wg . Wait ( )
for _ , err := range errs {
if err != nil {
return nil , err
}
}
var results [ ] * task . Task
for _ , task := range tasks {
if task != nil {
results = append ( results , task )
}
}
return results , nil
}
func ( bc * basicController ) getScanTask ( ctx context . Context , reportUUID string ) ( * task . Task , error ) {
2023-04-30 03:10:28 +02:00
// NOTE: the method uses the postgres' unique operations and should consider here if support other database in the future.
tasks , err := bc . taskMgr . ListScanTasksByReportUUID ( ctx , reportUUID )
2020-12-14 06:34:35 +01:00
if err != nil {
return nil , err
}
2023-04-30 03:10:28 +02:00
2020-12-14 06:34:35 +01:00
if len ( tasks ) == 0 {
return nil , errors . NotFoundError ( nil ) . WithMessage ( "task for report %s not found" , reportUUID )
}
return tasks [ 0 ] , nil
}
func ( bc * basicController ) assembleReports ( ctx context . Context , reports ... * scan . Report ) error {
reportUUIDs := make ( [ ] string , len ( reports ) )
for i , report := range reports {
reportUUIDs [ i ] = report . UUID
}
tasks , err := bc . listScanTasks ( ctx , reportUUIDs )
if err != nil {
return err
}
reportUUIDToTasks := map [ string ] * task . Task { }
for _ , task := range tasks {
for _ , reportUUID := range getReportUUIDs ( task . ExtraAttrs ) {
reportUUIDToTasks [ reportUUID ] = task
}
}
for _ , report := range reports {
if task , ok := reportUUIDToTasks [ report . UUID ] ; ok {
report . Status = task . Status
report . StartTime = task . StartTime
report . EndTime = task . EndTime
} else {
report . Status = job . ErrorStatus . String ( )
}
2021-05-27 16:39:35 +02:00
2020-12-25 01:47:46 +01:00
completeReport , err := bc . reportConverter . FromRelationalSchema ( ctx , report . UUID , report . Digest , report . Report )
if err != nil {
return err
}
report . Report = completeReport
2020-12-14 06:34:35 +01:00
}
return nil
}
2021-06-08 12:11:10 +02:00
func ( bc * basicController ) getLatestTagOfArtifact ( ctx context . Context , artifactID int64 ) ( string , error ) {
query := q . New ( q . KeyWords { "artifact_id" : artifactID } )
tags , err := bc . tagCtl . List ( ctx , query . First ( q . NewSort ( "push_time" , true ) ) , nil )
if err != nil {
return "" , err
}
if len ( tags ) == 0 {
return "" , nil
}
return tags [ 0 ] . Name , nil
}
2022-01-29 20:03:39 +01:00
func ( bc * basicController ) isAccessory ( ctx context . Context , art * ar . Artifact ) ( bool , error ) {
ac , err := bc . acc . List ( ctx , q . New ( q . KeyWords { "ArtifactID" : art . Artifact . ID , "digest" : art . Artifact . Digest } ) )
if err != nil {
return false , err
}
if len ( ac ) > 0 {
return true , nil
}
return false , nil
}
2020-12-14 06:34:35 +01:00
func getArtifactID ( extraAttrs map [ string ] interface { } ) int64 {
var artifactID float64
if extraAttrs != nil {
if v , ok := extraAttrs [ artifactIDKey ] ; ok {
artifactID , _ = v . ( float64 ) // int64 Unmarshal to float64
}
}
return int64 ( artifactID )
}
2021-06-08 12:11:10 +02:00
func getArtifactTag ( extraAttrs map [ string ] interface { } ) string {
var tag string
if extraAttrs != nil {
if v , ok := extraAttrs [ artifactTagKey ] ; ok {
tag , _ = v . ( string )
}
}
return tag
}
2020-12-14 06:34:35 +01:00
func getReportUUIDs ( extraAttrs map [ string ] interface { } ) [ ] string {
var reportUUIDs [ ] string
if extraAttrs != nil {
value , ok := extraAttrs [ reportUUIDsKey ]
if ok {
arr , _ := value . ( [ ] interface { } )
for _ , el := range arr {
if s , ok := el . ( string ) ; ok {
reportUUIDs = append ( reportUUIDs , s )
}
}
}
}
return reportUUIDs
}
func getRobotID ( extraAttrs map [ string ] interface { } ) int64 {
var trackID float64
if extraAttrs != nil {
if v , ok := extraAttrs [ robotIDKey ] ; ok {
trackID , _ = v . ( float64 ) // int64 Unmarshal to float64
}
}
return int64 ( trackID )
2019-09-19 13:15:37 +02:00
}
2019-10-24 11:11:33 +02:00
2019-11-04 10:34:42 +01:00
func parseOptions ( options ... Option ) ( * Options , error ) {
ops := & Options { }
for _ , op := range options {
if err := op ( ops ) ; err != nil {
return nil , err
}
}
return ops , nil
}
2024-04-16 15:34:19 +02:00
// deleteArtifactAccessories delete the accessory in reports, only delete sbom accessory
func ( bc * basicController ) deleteArtifactAccessories ( ctx context . Context , reports [ ] * scan . Report ) error {
for _ , rpt := range reports {
if rpt . MimeType != v1 . MimeTypeSBOMReport {
continue
}
if err := bc . deleteArtifactAccessory ( ctx , rpt . Report ) ; err != nil {
return err
}
}
return nil
}
// deleteArtifactAccessory check if current report has accessory info, if there is, delete it
func ( bc * basicController ) deleteArtifactAccessory ( ctx context . Context , report string ) error {
if len ( report ) == 0 {
return nil
}
sbomSummary := sbomModel . Summary { }
if err := json . Unmarshal ( [ ] byte ( report ) , & sbomSummary ) ; err != nil {
// it could be a non sbom report, just skip
log . Debugf ( "fail to unmarshal %v, skip to delete sbom report" , err )
return nil
}
repo , dgst := sbomSummary . SBOMAccArt ( )
if len ( repo ) == 0 || len ( dgst ) == 0 {
return nil
}
art , err := bc . ar . GetByReference ( ctx , repo , dgst , nil )
if err != nil {
if errors . IsNotFoundErr ( err ) {
return nil
}
return err
}
if art == nil {
return nil
}
err = bc . ar . Delete ( ctx , art . ID )
if errors . IsNotFoundErr ( err ) {
return nil
}
return err
}