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"
2019-09-24 09:17:40 +02:00
"fmt"
2020-12-14 06:34:35 +01:00
"strings"
2020-03-12 12:30:12 +01:00
"sync"
2019-09-24 09:17:40 +02:00
2019-10-12 10:29:38 +02:00
"github.com/goharbor/harbor/src/common/rbac"
2020-03-24 13:45:45 +01:00
ar "github.com/goharbor/harbor/src/controller/artifact"
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"
2019-10-12 10:29:38 +02:00
"github.com/goharbor/harbor/src/core/config"
2019-09-19 13:15:37 +02:00
"github.com/goharbor/harbor/src/jobservice/job"
2020-12-14 06:34:35 +01:00
"github.com/goharbor/harbor/src/lib"
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"
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"
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"
2019-10-12 10:29:38 +02:00
"github.com/google/uuid"
2019-09-19 13:15:37 +02:00
)
2019-09-24 09:17:40 +02:00
// DefaultController is a default singleton scan API controller.
var DefaultController = NewController ( )
2021-01-15 07:49:16 +01:00
// const definitions
2019-10-12 10:29:38 +02:00
const (
2021-01-15 07:49:16 +01:00
VendorTypeScanAll = "SCAN_ALL"
2019-10-12 10:29:38 +02:00
configRegistryEndpoint = "registryEndpoint"
configCoreInternalAddr = "coreInternalAddr"
2020-12-14 06:34:35 +01:00
artfiactKey = "artifact"
registrationKey = "registration"
artifactIDKey = "artifact_id"
reportUUIDsKey = "report_uuids"
robotIDKey = "robot_id"
2019-10-12 10:29:38 +02:00
)
2021-01-06 09:41:46 +01:00
func init ( ) {
// keep only the latest created 5 scan all execution records
2021-01-15 07:49:16 +01:00
task . SetExecutionSweeperCount ( VendorTypeScanAll , 5 )
2021-01-06 09:41:46 +01:00
}
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 )
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
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
// 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
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 ,
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 ,
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
reportConverter : postprocessors . NewNativeToRelationalSchemaConverter ( ) ,
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 {
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 {
2020-03-28 15:22:43 +01:00
return errors . PreconditionFailedError ( nil ) . WithMessage ( "scanner %s is disabled" , 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
}
2020-03-12 12:30:12 +01:00
if ! scannable {
2020-12-17 09:58:49 +01:00
return errors . BadRequestError ( nil ) . WithMessage ( "the configured scanner %s does not support scanning artifact with mime type %s" , r . Name , artifact . ManifestMediaType )
2020-03-12 12:30:12 +01:00
}
type Param struct {
2020-12-14 06:34:35 +01:00
Artifact * ar . Artifact
Reports [ ] * scan . Report
2019-09-24 09:17:40 +02:00
}
2020-03-12 12:30:12 +01:00
params := [ ] * Param { }
var errs [ ] error
for _ , art := range artifacts {
2020-12-14 06:34:35 +01:00
reports , err := bc . makeReportPlaceholder ( ctx , r , art )
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
}
}
2020-12-14 06:34:35 +01:00
if len ( reports ) > 0 {
params = append ( params , & Param { Artifact : art , Reports : reports } )
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
// Parse options
opts , err := parseOptions ( options ... )
if err != nil {
return errors . Wrap ( err , "scan controller: scan" )
}
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 ,
} ,
}
executionID , err := bc . execMgr . Create ( ctx , job . ImageScanJob , r . ID , task . ExecutionTriggerManual , extraAttrs )
if err != nil {
return err
}
opts . ExecutionID = executionID
}
2020-03-12 12:30:12 +01:00
errs = errs [ : 0 ]
for _ , param := range params {
2020-12-14 06:34:35 +01:00
if err := bc . launchScanJob ( ctx , opts . ExecutionID , param . Artifact , r , param . Reports ) ; 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
if len ( errs ) == len ( params ) {
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
}
2020-12-14 06:34:35 +01:00
func ( bc * basicController ) ScanAll ( ctx context . Context , trigger string , async bool ) ( int64 , error ) {
2021-01-15 07:49:16 +01:00
executionID , err := bc . execMgr . Create ( ctx , VendorTypeScanAll , 0 , trigger )
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 {
go func ( ) {
// if async, this is running in another goroutine ensure the execution exists in db
err := lib . RetryUntil ( func ( ) error {
_ , 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
}
bc . startScanAll ( bc . makeCtx ( ) , executionID )
} ( )
} 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
}
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" `
} { }
2020-12-14 06:34:35 +01:00
for artifact := range ar . Iterator ( ctx , batchSize , nil , nil ) {
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
if err := orm . WithTransaction ( scan ) ( ctx ) ; err != nil {
// 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
2021-01-08 03:10:31 +01:00
extraAttrs := map [ string ] interface { } { "summary" : summary }
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 {
message = fmt . Sprintf ( "%s, scanner not found or disabled for %d of them" , message , summary . PreconditionCount )
}
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
}
2020-12-14 06:34:35 +01:00
func ( bc * basicController ) makeReportPlaceholder ( ctx context . Context , r * scanner . Registration , art * ar . Artifact ) ( [ ] * scan . Report , error ) {
mimeTypes := r . GetProducesMimeTypes ( art . ManifestMediaType )
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
}
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
for _ , pm := range r . GetProducesMimeTypes ( art . ManifestMediaType ) {
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
}
if err := orm . WithTransaction ( create ) ( ctx ) ; err != nil {
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
}
2019-09-24 09:17:40 +02:00
// GetSummary ...
2020-03-12 12:30:12 +01:00
func ( bc * basicController ) GetSummary ( ctx context . Context , artifact * ar . Artifact , mimeTypes [ ] string , options ... report . Option ) ( 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" )
}
// 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 {
2019-10-15 07:45:53 +02:00
sum , err := report . GenerateSummary ( rp , options ... )
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
}
2020-12-14 06:34:35 +01:00
// GetScanLog ...
func ( bc * basicController ) GetScanLog ( ctx context . Context , uuid string ) ( [ ] byte , error ) {
if len ( uuid ) == 0 {
return nil , errors . New ( "empty uuid to get scan log" )
}
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 { }
for _ , task := range tasks {
for _ , reportUUID := range getReportUUIDs ( task . ExtraAttrs ) {
reportUUIDToTasks [ reportUUID ] = task
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
}
2020-12-14 06:34:35 +01:00
func ( bc * basicController ) UpdateReport ( ctx context . Context , report * sca . CheckInReport ) error {
rpl , err := bc . manager . GetBy ( ctx , report . Digest , report . RegistrationUUID , [ ] string { report . MimeType } )
if err != nil {
return errors . Wrap ( err , "scan controller: handle job hook" )
2019-09-24 09:17:40 +02:00
}
2020-12-14 06:34:35 +01:00
if len ( rpl ) == 0 {
return errors . New ( "no report found to update data" )
2019-09-24 09:17:40 +02:00
}
2020-12-25 01:47:46 +01:00
log . Infof ( "Converting report ID %s to the new V2 schema" , rpl [ 0 ] . UUID )
_ , reportData , err := bc . reportConverter . ToRelationalSchema ( ctx , rpl [ 0 ] . UUID , rpl [ 0 ] . RegistrationUUID , rpl [ 0 ] . Digest , report . RawReport )
if err != nil {
return errors . Wrapf ( err , "Failed to convert vulnerability data to new schema for report UUID : %s" , rpl [ 0 ] . UUID )
}
// update the original report with the new summarized report with all vulnerability data removed.
// this is required since the top level layers relay on the vuln.Report struct that
// contains additional metadata within the report which if stored in the new columns within the scan_report table
// would be redundant
if err := bc . manager . UpdateReportData ( ctx , rpl [ 0 ] . UUID , reportData ) ; err != nil {
2020-12-14 06:34:35 +01:00
return errors . Wrap ( err , "scan controller: handle job hook" )
2019-10-21 14:07:00 +02:00
}
2020-12-25 01:47:46 +01:00
log . Infof ( "Converted report ID %s to the new V2 schema" , rpl [ 0 ] . UUID )
2020-12-14 06:34:35 +01:00
return nil
2019-09-24 09:17:40 +02:00
}
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
}
2020-02-21 10:58:24 +01:00
// makeRobotAccount creates a robot account based on the arguments for scanning.
2020-11-20 06:13:12 +01:00
func ( bc * basicController ) makeRobotAccount ( ctx context . Context , projectID int64 , repository string , registration * scanner . Registration ) ( * 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 ]
2020-11-20 06:13:12 +01:00
robotReq := & robot . Robot {
Robot : model . Robot {
Name : fmt . Sprintf ( "%s-%s" , registration . Name , UUID ) ,
Description : "for scan" ,
ProjectID : projectID ,
} ,
Level : robot . LEVELPROJECT ,
Permissions : [ ] * robot . Permission {
{
Kind : "project" ,
2020-12-14 06:34:35 +01:00
Namespace : projectName ,
2020-11-20 06:13:12 +01:00
Access : [ ] * types . Policy {
{
Resource : rbac . ResourceRepository ,
Action : rbac . ActionPull ,
} ,
{
Resource : rbac . ResourceRepository ,
Action : rbac . ActionScannerPull ,
} ,
} ,
} ,
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
2020-12-14 06:34:35 +01:00
func ( bc * basicController ) launchScanJob ( ctx context . Context , executionID int64 , artifact * ar . Artifact , registration * scanner . Registration , reports [ ] * scan . Report ) error {
// don't launch scan job for the artifact which is not supported by the scanner
if ! hasCapability ( registration , artifact ) {
return nil
}
2019-10-24 11:11:33 +02:00
var ck string
if registration . UseInternalAddr {
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
}
2020-11-20 06:13:12 +01:00
robot , err := bc . makeRobotAccount ( ctx , artifact . ProjectID , artifact . RepositoryName , registration )
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 {
NamespaceID : artifact . ProjectID ,
Repository : artifact . RepositoryName ,
Digest : artifact . Digest ,
MimeType : artifact . ManifestMediaType ,
} ,
2019-09-24 09:17:40 +02:00
}
rJSON , err := registration . ToJSON ( )
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" )
}
mimes := make ( [ ] string , len ( reports ) )
reportUUIDs := make ( [ ] string , len ( reports ) )
for i , report := range reports {
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
2020-04-08 07:57:48 +02:00
params [ sca . JobParameterAuthType ] = 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 {
2019-09-24 09:17:40 +02:00
Name : job . ImageScanJob ,
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 { } {
artifactIDKey : artifact . ID ,
robotIDKey : robot . ID ,
reportUUIDsKey : reportUUIDs ,
}
// NOTE: due to the limitation of the beego's orm, the List method of the task manager not support ?! operator for the jsonb field,
// we cann't list the tasks for scan reports of uuid1, uuid2 by SQL `SELECT * FROM task WHERE (extra_attrs->'report_uuids')::jsonb ?| array['uuid1', 'uuid2']`
// or by `SELECT * FROM task WHERE id IN (SELECT id FROM task WHERE (extra_attrs->'report_uuids')::jsonb ?| array['uuid1', 'uuid2'])`
// so save {"report:uuid1": "1", "report:uuid2": "2"} in the extra_attrs of the task, and then list it with
// SQL `SELECT * FROM task WHERE extra_attrs->>'report:uuid1' = '1'` in loop
for _ , reportUUID := range reportUUIDs {
extraAttrs [ "report:" + reportUUID ] = "1"
}
_ , err = bc . taskMgr . Create ( ctx , executionID , j , extraAttrs )
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 ) {
query := q . New ( q . KeyWords { "extra_attrs." + "report:" + reportUUID : "1" } )
tasks , err := bc . taskMgr . List ( bc . cloneCtx ( ctx ) , query )
if err != nil {
return nil , err
}
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 ( )
}
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
}
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 )
}
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
}