Skip to content

Commit

Permalink
feat: stop scan job proposal
Browse files Browse the repository at this point in the history
Signed-off-by: Shengwen Yu <yshengwen@vmware.com>
  • Loading branch information
Shengwen Yu committed Aug 15, 2021
1 parent 7412f6a commit eb6d951
Showing 1 changed file with 202 additions and 0 deletions.
202 changes: 202 additions & 0 deletions proposals/new/stop-scan-job-proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
# Proposal: Stop scan job

Author: `Shengwen Yu` `Weiwei He`

Discussion:

* https://github.com/goharbor/harbor/issues/14831
* https://github.com/goharbor/harbor/issues/15338

Version: **Draft** | Review | Approved

## Abstract

Scanning a container image to identify and detect potential vulnerabilities is an essentail feature for artifacts management to avoid malicious attack against production environment. Currently Harbor supports Trivy and Clair as two optional scanning tools, which can be configured during the Harbor installation process, to achieve this purpose. Generally speaking, there are two categories of scanning job in Harbor: one of the them is the scan job of a given artifact, the other one is a scan job for all artifacts. Nevertheless, ethier of the two can be resource-consuming, and makes Harbor unable to handle incoming requests. Therefore, it's worthwhile having a stop scan job feature in Harbor to terminate a "scan all job" or "scan job of a given artifact".

## Goal

1. Be able to cancel a scan job of a particilar artifact within a repository of a given project.
2. Be able to terminate a scan all job at the system level for all artifacts.

## Implementation

### Stop scan job of a particular artifact

Currently, creating a scan job of a particular artifact is defined in this API:

```bash
POST /projects/{project_name}/repositories/{repository_name}/artifacts/{reference}/scan
```

A new API will be added to cancel, as needed, a currently running scan job of an artifact, and this new API is defined as below:

```yaml
/projects/{project_name}/repositories/{repository_name}/artifacts/{reference}/scan:
delete:
summary: Cancelling a scan job for a particular artifact
description: Cancelling a scan job for a particular artifact
tags:
- scan
operationId: stopScanArtifact
parameters:
- $ref: '#/parameters/requestId'
- $ref: '#/parameters/projectName'
- $ref: '#/parameters/repositoryName'
- $ref: '#/parameters/reference'
responses:
'202':
$ref: '#/responses/202'
'400':
$ref: '#/responses/400'
'401':
$ref: '#/responses/401'
'403':
$ref: '#/responses/403'
'404':
$ref: '#/responses/404'
'500':
$ref: '#/responses/500'
```
The handler for "stop single scan job" is implemented as following in `src/server/v2.0/handler/scan.go` file:

```go
func (s *scanAPI) StopScanArtifact(ctx context.Context, params operation.StopScanArtifactParams) middleware.Responder {
if err := s.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionUpdate, rbac.ResourceScan); err != nil {
return s.SendError(ctx, err)
}
// get the artifact
curArtifact, err := s.artCtl.GetByReference(ctx, fmt.Sprintf("%s/%s", params.ProjectName, params.RepositoryName), params.Reference, nil)
if err != nil {
return s.SendError(ctx, err)
}
if err := s.scanCtl.Stop(ctx, curArtifact); err != nil {
return s.SendError(ctx, err)
}
return operation.NewStopScanArtifactAccepted()
}
```

This `StopScanArtifact` function introduces a new method, `Stop()`, in scan controller interface `src/controller/scan/controller.go`, defined as below:

```go
// Controller provides the related operations for triggering scan.
type Controller interface {
// ....
// Stop scan job of the given artifact
//
// Arguments:
// ctx context.Context : the context for this method
// artifact *artifact.Artifact : the artifact whose scan job to be stopped
//
// Returns:
// error : non nil error if any errors occurred
Stop(ctx context.Context, artifact *artifact.Artifact) error
// ...
}
```

And this `Stop()` method in scan controller is going to be implemented in this file `src/controller/scan/base_controller.go`:

```go
// Stop scan job of a given artifact
func (bc *basicController) Stop(ctx context.Context, artifact * ar.Artifact) error {
query := q.New(q.KeyWords{"extra_attrs.artifact.id": artifact.ID})
executions, err := bc.execMgr.List(ctx, query)
if err != nil {
return err
}
if len(executions) == 0 {
return errors.New(nil).WithCode(errors.NotFoundCode).
WithMessage("scan job of artifact ID=%v not found", artifact.ID)
}
execution := executions[0]
return bc.execMgr.Stop(ctx, execution.ID)
}
```

### Stop scan all job

When a Harbor user want to create a schedule or a manual trigger for the scan all job, he can refer to this API definition for more details:

```bash
POST /system/scanAll/schedule
```

The API introduced to stop "scan all job" is specified within the following API design:

```yaml
/system/scanAll/stop:
post:
summary: Stop scanAll job execution
description: Stop scanAll job execution
parameters:
- $ref: '#/parameters/requestId'
tags:
- scanAll
operationId: stopScanAll
responses:
'200':
$ref: '#/responses/200'
'400':
$ref: '#/responses/400'
'401':
$ref: '#/responses/401'
'403':
$ref: '#/responses/403'
'500':
$ref: '#/responses/500'
```

The handler for dealing with "stop scan all job" will be implemented like this, within `src/server/v2.0/handler/scan_all.go`file:

```go
// StopScanAllExecution stops the execution of scan all artifacts.
func (s *scanAllAPI) StopScanAllExecution(ctx context.Context, params operation.StopScanAllParams) middleware.Responder {
if err := s.RequireSystemAccess(ctx, rbac.ActionUpdate); err != nil {
return s.SendError(ctx, err)
}
execution, err := s.getLatestScanAllExecution(ctx)
if err != nil {
return s.SendError(ctx, err)
}
if execution == nil {
message := fmt.Sprintf("no scan all job is found currently")
return s.SendError(ctx, errors.BadRequestError(nil).WithMessage(message))
}
err = s.execMgr.Stop(ctx, execution.ID)
if err != nil {
return s.SendError(ctx, err)
}
return operation.NewStopScanAllOK()
}
```

### shouldStop() check

In `func (j *Job) Run(ctx job.Context, params job.Parameters) error {}` of file `src/pkg/scan/job.go`, we will do the `shouldStop` check on some main steps. `shouldStop` is an anolymous function to be added within `func (j *Job) Run()`:

```go
// shouldStop checks if the job should be stopped
shouldStop := func() bool {
if cmd, ok := ctx.OPCommand(); ok && cmd == job.StopCommand {
return true
}
return false
}
```

And `shouldStop()` will be invoked in these following scenarios:

1. before `client, err := r.Client(v1.DefaultClientPool)`
2. before `resp, err := client.SubmitScan(req)`
3. right after `case t := <-tm.C:`
4. before persisting scan report data to database, `for i, mimeType := range mimeTypes {...}`

0 comments on commit eb6d951

Please sign in to comment.