mirror of
https://github.com/goharbor/harbor.git
synced 2025-01-24 08:31:24 +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"`
|
Repo string `json:"repository"`
|
||||||
Tag string `json:"tag"`
|
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/dao"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils"
|
"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"
|
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/log"
|
||||||
"github.com/vmware/harbor/src/common/utils/notary"
|
"github.com/vmware/harbor/src/common/utils/notary"
|
||||||
@ -295,20 +296,17 @@ func (ra *RepositoryAPI) Delete() {
|
|||||||
// GetTag returns the tag of a repository
|
// GetTag returns the tag of a repository
|
||||||
func (ra *RepositoryAPI) GetTag() {
|
func (ra *RepositoryAPI) GetTag() {
|
||||||
repository := ra.GetString(":splat")
|
repository := ra.GetString(":splat")
|
||||||
|
tag := ra.GetString(":tag")
|
||||||
project, _ := utils.ParseRepository(repository)
|
exist, _, err := ra.checkExistence(repository, tag)
|
||||||
exist, err := ra.ProjectMgr.Exist(project)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ra.HandleInternalServerError(fmt.Sprintf("failed to check the existence of project %s: %v",
|
ra.HandleInternalServerError(fmt.Sprintf("failed to check the existence of resource, error: %v", err))
|
||||||
project, err))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !exist {
|
if !exist {
|
||||||
ra.HandleNotFound(fmt.Sprintf("project %s not found", project))
|
ra.HandleNotFound(fmt.Sprintf("resource: %s:%s not found", repository, tag))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
project, _ := utils.ParseRepository(repository)
|
||||||
if !ra.SecurityCtx.HasReadPerm(project) {
|
if !ra.SecurityCtx.HasReadPerm(project) {
|
||||||
if !ra.SecurityCtx.IsAuthenticated() {
|
if !ra.SecurityCtx.IsAuthenticated() {
|
||||||
ra.HandleUnauthorized()
|
ra.HandleUnauthorized()
|
||||||
@ -325,7 +323,6 @@ func (ra *RepositoryAPI) GetTag() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tag := ra.GetString(":tag")
|
|
||||||
_, exist, err = client.ManifestExist(tag)
|
_, exist, err = client.ManifestExist(tag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ra.HandleInternalServerError(fmt.Sprintf("failed to check the existence of %s:%s: %v", repository, tag, err))
|
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) {
|
func getSignatures(repository, username string) (map[string]*notary.Target, error) {
|
||||||
targets, err := notary.GetInternalTargets(config.InternalNotaryEndpoint(),
|
targets, err := notary.GetInternalTargets(config.InternalNotaryEndpoint(),
|
||||||
username, repository)
|
username, repository)
|
||||||
@ -768,6 +808,31 @@ func getSignatures(repository, username string) (map[string]*notary.Target, erro
|
|||||||
return signatures, nil
|
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.
|
//will return nil when it failed to get data. The parm "tag" is for logging only.
|
||||||
func getScanOverview(digest string, tag string) *models.ImgScanOverview {
|
func getScanOverview(digest string, tag string) *models.ImgScanOverview {
|
||||||
data, err := dao.GetImgScanOverview(digest)
|
data, err := dao.GetImgScanOverview(digest)
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils"
|
"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"
|
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/log"
|
||||||
"github.com/vmware/harbor/src/common/utils/registry"
|
"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
|
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)
|
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.
|
// AdmiralEndpoint returns the URL of admiral, if Harbor is not deployed with admiral it should return an empty string.
|
||||||
func AdmiralEndpoint() string {
|
func AdmiralEndpoint() string {
|
||||||
cfg, err := mg.Get()
|
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/:tag", &api.RepositoryAPI{}, "delete:Delete;get:GetTag")
|
||||||
beego.Router("/api/repositories/*/tags", &api.RepositoryAPI{}, "get:GetTags")
|
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/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/*/tags/:tag/manifest", &api.RepositoryAPI{}, "get:GetManifests")
|
||||||
beego.Router("/api/repositories/*/signatures", &api.RepositoryAPI{}, "get:GetSignatures")
|
beego.Router("/api/repositories/*/signatures", &api.RepositoryAPI{}, "get:GetSignatures")
|
||||||
beego.Router("/api/jobs/replication/", &api.RepJobAPI{}, "get:List")
|
beego.Router("/api/jobs/replication/", &api.RepJobAPI{}, "get:List")
|
||||||
|
Loading…
Reference in New Issue
Block a user