Merge pull request #2572 from reasonerjt/clair-integration

provide API to get vulnerability details
This commit is contained in:
Daniel Jiang 2017-06-20 13:56:52 +08:00 committed by GitHub
commit 551e0fe9c1
5 changed files with 122 additions and 9 deletions

View File

@ -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"`
}

View File

@ -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)

View File

@ -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
}

View File

@ -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()

View File

@ -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")