harbor/src/pkg/scan/rest/v1/models.go

279 lines
8.5 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 v1
import (
"encoding/json"
"fmt"
"github.com/goharbor/harbor/src/lib/errors"
)
var supportedMimeTypes = []string{
MimeTypeNativeReport,
MimeTypeGenericVulnerabilityReport,
MimeTypeSBOMReport,
}
// Scanner represents metadata of a Scanner Adapter which allow Harbor to lookup a scanner capable of
// scanning a given Artifact stored in its registry and making sure that it can interpret a
// returned result.
type Scanner struct {
// The name of the scanner.
Name string `json:"name"`
// The name of the scanner's provider.
Vendor string `json:"vendor"`
// The version of the scanner.
Version string `json:"version"`
}
// ScannerCapability consists of the set of recognized artifact MIME types and the set of scanner
// report MIME types. For example, a scanner capable of analyzing Docker images and producing
// a vulnerabilities report recognizable by Harbor web console might be represented with the
// following capability:
// - type: vulnerability
// - consumes MIME types:
// -- application/vnd.oci.image.manifest.v1+json
// -- application/vnd.docker.distribution.manifest.v2+json
// - produces MIME types
// -- application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0
// -- application/vnd.scanner.adapter.vuln.report.raw
type ScannerCapability struct {
// The type of the scanner capability, vulnerability or sbom
Type string `json:"type"`
// The set of MIME types of the artifacts supported by the scanner to produce the reports
// specified in the "produces_mime_types". A given mime type should only be present in one
// capability item.
ConsumesMimeTypes []string `json:"consumes_mime_types"`
// The set of MIME types of reports generated by the scanner for the consumes_mime_types of
// the same capability record.
ProducesMimeTypes []string `json:"produces_mime_types"`
}
// ScannerProperties is a set of custom properties that can further describe capabilities of a given scanner.
type ScannerProperties map[string]string
// ScannerAdapterMetadata represents metadata of a Scanner Adapter which allows Harbor to lookup
// a scanner capable of scanning a given Artifact stored in its registry and making sure that it
// can interpret a returned result.
type ScannerAdapterMetadata struct {
Scanner *Scanner `json:"scanner"`
Capabilities []*ScannerCapability `json:"capabilities"`
Properties ScannerProperties `json:"properties"`
}
// Validate validate the metadata
func (md *ScannerAdapterMetadata) Validate() error {
// Validate the required properties
if md.Scanner == nil ||
len(md.Scanner.Name) == 0 ||
len(md.Scanner.Version) == 0 ||
len(md.Scanner.Vendor) == 0 {
return errors.New("invalid scanner in metadata")
}
if len(md.Capabilities) == 0 {
return errors.New("invalid capabilities in metadata")
}
for _, ca := range md.Capabilities {
// v1.MimeTypeDockerArtifact is required now
found := false
for _, cm := range ca.ConsumesMimeTypes {
if cm == MimeTypeDockerArtifact {
found = true
break
}
}
if !found {
return errors.Errorf("missing %s in consumes_mime_types", MimeTypeDockerArtifact)
}
// either of v1.MimeTypeNativeReport OR v1.MimeTypeGenericVulnerabilityReport is required
found = false
for _, pm := range ca.ProducesMimeTypes {
if isSupportedMimeType(pm) {
found = true
break
}
}
if !found {
return errors.Errorf("missing %s or %s in produces_mime_types", MimeTypeNativeReport, MimeTypeGenericVulnerabilityReport)
}
}
return nil
}
func isSupportedMimeType(mimeType string) bool {
for _, mt := range supportedMimeTypes {
if mt == mimeType {
return true
}
}
return false
}
// HasCapability returns true when mine type of the artifact support by the scanner
func (md *ScannerAdapterMetadata) HasCapability(mimeType string) bool {
for _, capability := range md.Capabilities {
for _, mt := range capability.ConsumesMimeTypes {
if mt == mimeType {
return true
}
}
}
return false
}
// GetCapability returns capability for the mime type
func (md *ScannerAdapterMetadata) GetCapability(mimeType string) *ScannerCapability {
for _, capability := range md.Capabilities {
for _, mt := range capability.ConsumesMimeTypes {
if mt == mimeType {
return capability
}
}
}
return nil
}
// Artifact represents an artifact stored in Registry.
type Artifact struct {
// ID of the namespace (project). It will not be sent to scanner adapter.
NamespaceID int64 `json:"namespace_id,omitempty"`
// The full name of a Harbor repository containing the artifact, including the namespace.
// For example, `library/oracle/nosql`.
Repository string `json:"repository"`
// The info used to identify the version of the artifact,
Tag string `json:"tag"`
// The artifact's digest, consisting of an algorithm and hex portion.
// For example, `sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b`,
// represents sha256 based digest.
Digest string `json:"digest"`
// The mime type of the scanned artifact
MimeType string `json:"mime_type"`
// The size the scanned artifact
Size int64 `json:"size"`
}
// Registry represents Registry connection settings.
type Registry struct {
// A base URL of the Docker Registry v2 API exposed by Harbor.
URL string `json:"url"`
// An optional value of the HTTP Authorization header sent with each request to the Docker Registry for getting or exchanging token.
// For example, `Basic: Base64(username:password)`.
Authorization string `json:"authorization"`
}
// ScanRequest represents a structure that is sent to a Scanner Adapter to initiate artifact scanning.
// Conducts all the details required to pull the artifact from a Harbor registry.
type ScanRequest struct {
// Connection settings for the Docker Registry v2 API exposed by Harbor.
Registry *Registry `json:"registry"`
// Artifact to be scanned.
Artifact *Artifact `json:"artifact"`
// RequestType
RequestType []*ScanType `json:"enabled_capabilities"`
}
// ScanType represent the type of the scan request
type ScanType struct {
// Type sets the type of the scan, it could be sbom or vulnerability, default is vulnerability
Type string `json:"type"`
// ProducesMimeTypes defines scanreport should be
ProducesMimeTypes []string `json:"produces_mime_types"`
// Parameters extra parameters
Parameters map[string]interface{} `json:"parameters"`
}
// FromJSON parses ScanRequest from json data
func (s *ScanRequest) FromJSON(jsonData string) error {
if len(jsonData) == 0 {
return errors.New("empty json data to parse")
}
return json.Unmarshal([]byte(jsonData), s)
}
// ToJSON marshals ScanRequest to JSON data
func (s *ScanRequest) ToJSON() (string, error) {
data, err := json.Marshal(s)
if err != nil {
return "", err
}
return string(data), nil
}
// Validate ScanRequest
func (s *ScanRequest) Validate() error {
if s.Registry == nil ||
len(s.Registry.URL) == 0 {
return errors.New("scan request: invalid registry")
}
if s.Artifact == nil ||
len(s.Artifact.Digest) == 0 ||
len(s.Artifact.Repository) == 0 ||
len(s.Artifact.MimeType) == 0 {
return errors.New("scan request: invalid artifact")
}
return nil
}
// ScanResponse represents the response returned by the scanner adapter after scan request successfully
// submitted.
type ScanResponse struct {
// e.g: 3fa85f64-5717-4562-b3fc-2c963f66afa6
ID string `json:"id"`
}
// ErrorResponse contains error message when requests are not correctly handled.
type ErrorResponse struct {
// Error object
Err *Error `json:"error"`
}
// Error message
type Error struct {
// Message of the error
Message string `json:"message"`
}
// Error for ErrorResponse
func (er *ErrorResponse) Error() string {
if er.Err != nil {
return er.Err.Message
}
return "nil error"
}
// ReportNotReadyError is an error to indicate the scan report is not ready
type ReportNotReadyError struct {
// Seconds for next retry with seconds
RetryAfter int
}
// Error for ReportNotReadyError
func (rnr *ReportNotReadyError) Error() string {
return fmt.Sprintf("report is not ready yet, retry after %d", rnr.RetryAfter)
}