mirror of
https://github.com/goharbor/harbor.git
synced 2025-01-09 01:17:43 +01:00
Merge pull request #2572 from reasonerjt/clair-integration
provide API to get vulnerability details
This commit is contained in:
commit
551e0fe9c1
@ -86,3 +86,13 @@ type ImageScanReq struct {
|
||||
Repo string `json:"repository"`
|
||||
Tag string `json:"tag"`
|
||||
}
|
||||
|
||||
// VulnerabilityItem is an item in the vulnerability result returned by vulnerability details API.
|
||||
type VulnerabilityItem struct {
|
||||
ID string `json:"id"`
|
||||
Severity Severity `json:"severity"`
|
||||
Pkg string `json:"package"`
|
||||
Version string `json:"version"`
|
||||
Description string `json:"description"`
|
||||
Fixed string `json:"fixedVersion,omitempty"`
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils"
|
||||
"github.com/vmware/harbor/src/common/utils/clair"
|
||||
registry_error "github.com/vmware/harbor/src/common/utils/error"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/common/utils/notary"
|
||||
@ -295,20 +296,17 @@ func (ra *RepositoryAPI) Delete() {
|
||||
// GetTag returns the tag of a repository
|
||||
func (ra *RepositoryAPI) GetTag() {
|
||||
repository := ra.GetString(":splat")
|
||||
|
||||
project, _ := utils.ParseRepository(repository)
|
||||
exist, err := ra.ProjectMgr.Exist(project)
|
||||
tag := ra.GetString(":tag")
|
||||
exist, _, err := ra.checkExistence(repository, tag)
|
||||
if err != nil {
|
||||
ra.HandleInternalServerError(fmt.Sprintf("failed to check the existence of project %s: %v",
|
||||
project, err))
|
||||
ra.HandleInternalServerError(fmt.Sprintf("failed to check the existence of resource, error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if !exist {
|
||||
ra.HandleNotFound(fmt.Sprintf("project %s not found", project))
|
||||
ra.HandleNotFound(fmt.Sprintf("resource: %s:%s not found", repository, tag))
|
||||
return
|
||||
}
|
||||
|
||||
project, _ := utils.ParseRepository(repository)
|
||||
if !ra.SecurityCtx.HasReadPerm(project) {
|
||||
if !ra.SecurityCtx.IsAuthenticated() {
|
||||
ra.HandleUnauthorized()
|
||||
@ -325,7 +323,6 @@ func (ra *RepositoryAPI) GetTag() {
|
||||
return
|
||||
}
|
||||
|
||||
tag := ra.GetString(":tag")
|
||||
_, exist, err = client.ManifestExist(tag)
|
||||
if err != nil {
|
||||
ra.HandleInternalServerError(fmt.Sprintf("failed to check the existence of %s:%s: %v", repository, tag, err))
|
||||
@ -749,6 +746,49 @@ func (ra *RepositoryAPI) ScanImage() {
|
||||
}
|
||||
}
|
||||
|
||||
// VulnerabilityDetails fetch vulnerability info from clair, transform to Harbor's format and return to client.
|
||||
func (ra *RepositoryAPI) VulnerabilityDetails() {
|
||||
if !config.WithClair() {
|
||||
log.Warningf("Harbor is not deployed with Clair, it's not impossible to get vulnerability details.")
|
||||
ra.RenderError(http.StatusServiceUnavailable, "")
|
||||
return
|
||||
}
|
||||
repository := ra.GetString(":splat")
|
||||
tag := ra.GetString(":tag")
|
||||
exist, digest, err := ra.checkExistence(repository, tag)
|
||||
if err != nil {
|
||||
ra.HandleInternalServerError(fmt.Sprintf("failed to check the existence of resource, error: %v", err))
|
||||
return
|
||||
}
|
||||
if !exist {
|
||||
ra.HandleNotFound(fmt.Sprintf("resource: %s:%s not found", repository, tag))
|
||||
return
|
||||
}
|
||||
project, _ := utils.ParseRepository(repository)
|
||||
if !ra.SecurityCtx.HasReadPerm(project) {
|
||||
if !ra.SecurityCtx.IsAuthenticated() {
|
||||
ra.HandleUnauthorized()
|
||||
return
|
||||
}
|
||||
ra.HandleForbidden(ra.SecurityCtx.GetUsername())
|
||||
return
|
||||
}
|
||||
overview, err := dao.GetImgScanOverview(digest)
|
||||
if err != nil {
|
||||
ra.HandleInternalServerError(fmt.Sprintf("failed to get the scan overview, error: %v", err))
|
||||
return
|
||||
}
|
||||
clairClient := clair.NewClient(config.ClairEndpoint(), nil)
|
||||
log.Debugf("The key for getting details: %s", overview.DetailsKey)
|
||||
details, err := clairClient.GetResult(overview.DetailsKey)
|
||||
if err != nil {
|
||||
ra.HandleInternalServerError(fmt.Sprintf("Failed to get scan details from Clair, error: %v", err))
|
||||
return
|
||||
}
|
||||
ra.Data["json"] = transformVulnerabilities(details)
|
||||
ra.ServeJSON()
|
||||
}
|
||||
|
||||
func getSignatures(repository, username string) (map[string]*notary.Target, error) {
|
||||
targets, err := notary.GetInternalTargets(config.InternalNotaryEndpoint(),
|
||||
username, repository)
|
||||
@ -768,6 +808,31 @@ func getSignatures(repository, username string) (map[string]*notary.Target, erro
|
||||
return signatures, nil
|
||||
}
|
||||
|
||||
func (ra *RepositoryAPI) checkExistence(repository, tag string) (bool, string, error) {
|
||||
project, _ := utils.ParseRepository(repository)
|
||||
exist, err := ra.ProjectMgr.Exist(project)
|
||||
if err != nil {
|
||||
return false, "", fmt.Errorf("failed to check the existence of project %s: %v", project, err)
|
||||
}
|
||||
if !exist {
|
||||
log.Errorf("project %s not found", project)
|
||||
return false, "", nil
|
||||
}
|
||||
client, err := ra.initRepositoryClient(repository)
|
||||
if err != nil {
|
||||
return false, "", fmt.Errorf("failed to initialize the client for %s: %v", repository, err)
|
||||
}
|
||||
digest, exist, err := client.ManifestExist(tag)
|
||||
if err != nil {
|
||||
return false, "", fmt.Errorf("failed to check the existence of %s:%s: %v", repository, tag, err)
|
||||
}
|
||||
if !exist {
|
||||
log.Errorf("%s not found", tag)
|
||||
return false, "", nil
|
||||
}
|
||||
return true, digest, nil
|
||||
}
|
||||
|
||||
//will return nil when it failed to get data. The parm "tag" is for logging only.
|
||||
func getScanOverview(digest string, tag string) *models.ImgScanOverview {
|
||||
data, err := dao.GetImgScanOverview(digest)
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils"
|
||||
"github.com/vmware/harbor/src/common/utils/clair"
|
||||
registry_error "github.com/vmware/harbor/src/common/utils/error"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/common/utils/registry"
|
||||
@ -557,3 +558,34 @@ func requestAsUI(method, url string, body io.Reader, expectSC int) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// transformVulnerabilities transforms the returned value of Clair API to a list of VulnerabilityItem
|
||||
func transformVulnerabilities(layerWithVuln *models.ClairLayerEnvelope) []*models.VulnerabilityItem {
|
||||
res := []*models.VulnerabilityItem{}
|
||||
l := layerWithVuln.Layer
|
||||
if l == nil {
|
||||
return res
|
||||
}
|
||||
features := l.Features
|
||||
if features == nil {
|
||||
return res
|
||||
}
|
||||
for _, f := range features {
|
||||
vulnerabilities := f.Vulnerabilities
|
||||
if vulnerabilities == nil {
|
||||
continue
|
||||
}
|
||||
for _, v := range vulnerabilities {
|
||||
vItem := &models.VulnerabilityItem{
|
||||
ID: v.Name,
|
||||
Pkg: f.Name,
|
||||
Version: f.Version,
|
||||
Severity: clair.ParseClairSev(v.Severity),
|
||||
Fixed: v.FixedBy,
|
||||
Description: v.Description,
|
||||
}
|
||||
res = append(res, vItem)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
@ -323,6 +323,11 @@ func WithClair() bool {
|
||||
return cfg[common.WithClair].(bool)
|
||||
}
|
||||
|
||||
// ClairEndpoint returns the end point of clair instance, by default it's the one deployed within Harbor.
|
||||
func ClairEndpoint() string {
|
||||
return "http://clair:6060"
|
||||
}
|
||||
|
||||
// AdmiralEndpoint returns the URL of admiral, if Harbor is not deployed with admiral it should return an empty string.
|
||||
func AdmiralEndpoint() string {
|
||||
cfg, err := mg.Get()
|
||||
|
@ -77,6 +77,7 @@ func initRouters() {
|
||||
beego.Router("/api/repositories/*/tags/:tag", &api.RepositoryAPI{}, "delete:Delete;get:GetTag")
|
||||
beego.Router("/api/repositories/*/tags", &api.RepositoryAPI{}, "get:GetTags")
|
||||
beego.Router("/api/repositories/*/tags/:tag/scan", &api.RepositoryAPI{}, "post:ScanImage")
|
||||
beego.Router("/api/repositories/*/tags/:tag/vulnerability/details", &api.RepositoryAPI{}, "Get:VulnerabilityDetails")
|
||||
beego.Router("/api/repositories/*/tags/:tag/manifest", &api.RepositoryAPI{}, "get:GetManifests")
|
||||
beego.Router("/api/repositories/*/signatures", &api.RepositoryAPI{}, "get:GetSignatures")
|
||||
beego.Router("/api/jobs/replication/", &api.RepJobAPI{}, "get:List")
|
||||
|
Loading…
Reference in New Issue
Block a user