From 41346fe8c03a4cc3b7a92c89ad5c7691c25d6aa9 Mon Sep 17 00:00:00 2001 From: Tan Jiang Date: Thu, 15 Jun 2017 20:23:55 +0800 Subject: [PATCH] provide POST api/repostitores/xxx/tags/xxx/scan to trigger image scan --- src/common/models/scan_job.go | 6 +++ src/jobservice/api/scan.go | 11 ++---- src/ui/api/repository.go | 33 +++++++++++++++- src/ui/api/utils.go | 72 +++++++++++++++++++++++------------ src/ui/router.go | 1 + 5 files changed, 89 insertions(+), 34 deletions(-) diff --git a/src/common/models/scan_job.go b/src/common/models/scan_job.go index 207eae53a..a0ddaefa1 100644 --- a/src/common/models/scan_job.go +++ b/src/common/models/scan_job.go @@ -80,3 +80,9 @@ type ComponentsOverviewEntry struct { Sev int `json:"severity"` Count int `json:"count"` } + +// ImageScanReq represents the request body to send to job service for image scan +type ImageScanReq struct { + Repo string `json:"repository"` + Tag string `json:"tag"` +} diff --git a/src/jobservice/api/scan.go b/src/jobservice/api/scan.go index 5ead3f7de..d9c318114 100644 --- a/src/jobservice/api/scan.go +++ b/src/jobservice/api/scan.go @@ -31,20 +31,15 @@ type ImageScanJob struct { jobBaseAPI } -type imageScanReq struct { - Repo string `json:"repository"` - Tag string `json:"tag"` -} - // Prepare ... func (isj *ImageScanJob) Prepare() { - //TODO:add authenticate to check secret when integrate with UI API. - //isj.authenticate() + //TODO Uncomment to enable security check. + // isj.authenticate() } // Post creates a scanner job and hand it to statemachine. func (isj *ImageScanJob) Post() { - var data imageScanReq + var data models.ImageScanReq isj.DecodeJSONReq(&data) log.Debugf("data: %+v", data) regURL, err := config.LocalRegURL() diff --git a/src/ui/api/repository.go b/src/ui/api/repository.go index 7bb5755a1..4c4a8574d 100644 --- a/src/ui/api/repository.go +++ b/src/ui/api/repository.go @@ -610,7 +610,6 @@ func (ra *RepositoryAPI) GetTopRepos() { //GetSignatures returns signatures of a repository func (ra *RepositoryAPI) GetSignatures() { repoName := ra.GetString(":splat") - targets, err := notary.GetInternalTargets(config.InternalNotaryEndpoint(), ra.SecurityCtx.GetUsername(), repoName) if err != nil { @@ -621,6 +620,38 @@ func (ra *RepositoryAPI) GetSignatures() { ra.ServeJSON() } +//ScanImage handles +func (ra *RepositoryAPI) ScanImage() { + repoName := ra.GetString(":splat") + tag := ra.GetString(":tag") + projectName, _ := utils.ParseRepository(repoName) + exist, err := ra.ProjectMgr.Exist(projectName) + if err != nil { + ra.HandleInternalServerError(fmt.Sprintf("failed to check the existence of project %s: %v", + projectName, err)) + return + } + if !exist { + ra.HandleNotFound(fmt.Sprintf("project %s not found", projectName)) + return + } + if !ra.SecurityCtx.IsAuthenticated() { + ra.HandleUnauthorized() + return + } + if !ra.SecurityCtx.HasAllPerm(projectName) { + ra.HandleForbidden(ra.SecurityCtx.GetUsername()) + return + } + err = TriggerImageScan(repoName, tag) + //TODO better check existence + if err != nil { + log.Errorf("Error while calling job service to trigger image scan: %v", err) + ra.HandleInternalServerError("Failed to scan image, please check log for details") + return + } +} + func getSignatures(repository, username string) (map[string]*notary.Target, error) { targets, err := notary.GetInternalTargets(config.InternalNotaryEndpoint(), username, repository) diff --git a/src/ui/api/utils.go b/src/ui/api/utils.go index f75268d1d..2197a9c99 100644 --- a/src/ui/api/utils.go +++ b/src/ui/api/utils.go @@ -18,6 +18,7 @@ import ( "bytes" "encoding/json" "fmt" + "io" "io/ioutil" "net/http" "sort" @@ -26,10 +27,10 @@ import ( "github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/utils" + 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" "github.com/vmware/harbor/src/common/utils/registry/auth" - registry_error "github.com/vmware/harbor/src/common/utils/error" "github.com/vmware/harbor/src/ui/config" "github.com/vmware/harbor/src/ui/projectmanager" ) @@ -92,32 +93,9 @@ func TriggerReplication(policyID int64, repository string, if err != nil { return err } - url := buildReplicationURL() - req, err := http.NewRequest("POST", url, bytes.NewBuffer(b)) - if err != nil { - return err - } - addAuthentication(req) - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - if resp.StatusCode == http.StatusOK { - return nil - } - - b, err = ioutil.ReadAll(resp.Body) - if err != nil { - return err - } - - return fmt.Errorf("%d %s", resp.StatusCode, string(b)) + return requestAsUI("POST", url, bytes.NewBuffer(b), http.StatusOK) } // GetPoliciesByRepository returns policies according the repository @@ -451,6 +429,11 @@ func initRegistryClient() (r *registry.Registry, err error) { return registryClient, nil } +func buildScanJobURL() string { + url := config.InternalJobServiceURL() + return fmt.Sprintf("%s/api/jobs/scan", url) +} + func buildReplicationURL() string { url := config.InternalJobServiceURL() return fmt.Sprintf("%s/api/jobs/replication", url) @@ -535,3 +518,42 @@ func NewRepositoryClient(endpoint string, insecure bool, username, repository, s } return client, nil } + +// TriggerImageScan triggers an image scan job on jobservice. +func TriggerImageScan(repository string, tag string) error { + data := &models.ImageScanReq{ + Repo: repository, + Tag: tag, + } + b, err := json.Marshal(&data) + if err != nil { + return err + } + url := buildScanJobURL() + return requestAsUI("POST", url, bytes.NewBuffer(b), http.StatusOK) +} + +// Do not use this when you want to handle the response +// TODO: add a response handler to replace expectSC *when needed* +func requestAsUI(method, url string, body io.Reader, expectSC int) error { + req, err := http.NewRequest(method, url, body) + if err != nil { + return err + } + addAuthentication(req) + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != expectSC { + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + return fmt.Errorf("Unexpected status code: %d, text: %s", resp.StatusCode, string(b)) + } + return nil +} diff --git a/src/ui/router.go b/src/ui/router.go index 4c3e98e53..108b8abdc 100644 --- a/src/ui/router.go +++ b/src/ui/router.go @@ -76,6 +76,7 @@ func initRouters() { beego.Router("/api/repositories/*", &api.RepositoryAPI{}, "delete:Delete") beego.Router("/api/repositories/*/tags/:tag", &api.RepositoryAPI{}, "delete:Delete") 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/manifest", &api.RepositoryAPI{}, "get:GetManifests") beego.Router("/api/repositories/*/signatures", &api.RepositoryAPI{}, "get:GetSignatures") beego.Router("/api/jobs/replication/", &api.RepJobAPI{}, "get:List")