harbor/src/controller/event/handler/webhook/scan/scan.go

170 lines
5.0 KiB
Go

// 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 (
"context"
"time"
"github.com/goharbor/harbor/src/controller/artifact"
"github.com/goharbor/harbor/src/controller/event"
"github.com/goharbor/harbor/src/controller/event/handler/util"
"github.com/goharbor/harbor/src/controller/project"
"github.com/goharbor/harbor/src/controller/scan"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/pkg/notification"
"github.com/goharbor/harbor/src/pkg/notifier/model"
proModels "github.com/goharbor/harbor/src/pkg/project/models"
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
)
// Handler preprocess scan artifact event
type Handler struct {
}
// Name ...
func (si *Handler) Name() string {
return "ScanWebhook"
}
// Handle preprocess chart event data and then publish hook event
func (si *Handler) Handle(ctx context.Context, value interface{}) error {
if value == nil {
return errors.New("empty scan artifact event")
}
e, ok := value.(*event.ScanImageEvent)
if !ok {
return errors.New("invalid scan artifact event type")
}
policies, err := notification.PolicyMgr.GetRelatedPolices(ctx, e.Artifact.NamespaceID, e.EventType)
if err != nil {
return errors.Wrap(err, "scan preprocess handler")
}
// If we cannot find policy including event type in project, return directly
if len(policies) == 0 {
log.Debugf("Cannot find policy for %s event: %v", e.EventType, e)
return nil
}
// Get project
prj, err := project.Ctl.Get(ctx, e.Artifact.NamespaceID, project.Metadata(true))
if err != nil {
return errors.Wrap(err, "scan preprocess handler")
}
payload, err := constructScanImagePayload(ctx, e, prj)
if err != nil {
return errors.Wrap(err, "scan preprocess handler")
}
err = util.SendHookWithPolicies(ctx, policies, payload, e.EventType)
if err != nil {
return errors.Wrap(err, "scan preprocess handler")
}
return nil
}
// IsStateful ...
func (si *Handler) IsStateful() bool {
return false
}
func constructScanImagePayload(ctx context.Context, event *event.ScanImageEvent, project *proModels.Project) (*model.Payload, error) {
repoType := proModels.ProjectPrivate
if project.IsPublic() {
repoType = proModels.ProjectPublic
}
repoName := util.GetNameFromImgRepoFullName(event.Artifact.Repository)
payload := &model.Payload{
Type: event.EventType,
OccurAt: event.OccurAt.Unix(),
EventData: &model.EventData{
Repository: &model.Repository{
Name: repoName,
Namespace: project.Name,
RepoFullName: event.Artifact.Repository,
RepoType: repoType,
},
ScanType: event.ScanType,
},
Operator: event.Operator,
}
reference := event.Artifact.Tag
if reference == "" {
reference = event.Artifact.Digest
}
resURL, err := util.BuildImageResourceURL(event.Artifact.Repository, reference)
if err != nil {
return nil, errors.Wrap(err, "construct scan payload")
}
art, err := artifact.Ctl.GetByReference(ctx, event.Artifact.Repository, event.Artifact.Digest, nil)
if err != nil {
return nil, err
}
// Wait for reasonable time to make sure the report is ready
// Interval=500ms and total time = 5s
// If the report is still not ready in the total time, then failed at then
for i := 0; i < 10; i++ {
// First check in case it is ready
if re, err := scan.DefaultController.GetReport(ctx, art, []string{v1.MimeTypeNativeReport, v1.MimeTypeGenericVulnerabilityReport}); err == nil {
if len(re) > 0 && len(re[0].Report) > 0 {
break
}
} else {
log.Error(errors.Wrap(err, "construct scan payload: wait report ready loop"))
}
time.Sleep(500 * time.Millisecond)
}
scanSummaries := map[string]interface{}{}
if event.ScanType == v1.ScanTypeVulnerability {
scanSummaries, err = scan.DefaultController.GetSummary(ctx, art, []string{v1.MimeTypeNativeReport, v1.MimeTypeGenericVulnerabilityReport})
if err != nil {
return nil, errors.Wrap(err, "construct scan payload")
}
}
sbomOverview := map[string]interface{}{}
if event.ScanType == v1.ScanTypeSbom {
sbomOverview, err = scan.DefaultController.GetSummary(ctx, art, []string{v1.MimeTypeSBOMReport})
if err != nil {
return nil, errors.Wrap(err, "construct scan payload")
}
}
// Add scan overview and sbom overview
resource := &model.Resource{
Tag: event.Artifact.Tag,
Digest: event.Artifact.Digest,
ResourceURL: resURL,
ScanOverview: scanSummaries,
SBOMOverview: sbomOverview,
}
payload.EventData.Resources = append(payload.EventData.Resources, resource)
return payload, nil
}