[BAT] fix issues (#3037)

* fix issues

* update
This commit is contained in:
Yan 2017-08-14 15:19:48 +08:00 committed by GitHub
parent 4139944723
commit d0103856f1
3 changed files with 136 additions and 32 deletions

View File

@ -94,6 +94,22 @@ func TestMatchPullManifest(t *testing.T) {
assert.Equal("sha256:ca4626b691f57d16ce1576231e4a2e2135554d32e13a85dcff380d51fdd13f6a", tag7)
}
func TestMatchListRepos(t *testing.T) {
assert := assert.New(t)
req1, _ := http.NewRequest("POST", "http://127.0.0.1:5000/v2/_catalog", nil)
res1 := MatchListRepos(req1)
assert.False(res1, "%s %v is not a request to list repos", req1.Method, req1.URL)
req2, _ := http.NewRequest("GET", "http://127.0.0.1:5000/v2/_catalog", nil)
res2 := MatchListRepos(req2)
assert.True(res2, "%s %v is a request to list repos", req2.Method, req2.URL)
req3, _ := http.NewRequest("GET", "https://192.168.0.5:443/v1/_catalog", nil)
res3 := MatchListRepos(req3)
assert.False(res3, "%s %v is not a request to pull manifest", req3.Method, req3.URL)
}
func TestEnvPolicyChecker(t *testing.T) {
assert := assert.New(t)
if err := os.Setenv("PROJECT_CONTENT_TRUST", "1"); err != nil {
@ -191,3 +207,9 @@ func TestMarshalError(t *testing.T) {
js := marshalError("Not Found", 404)
assert.Equal("{\"code\":404,\"message\":\"Not Found\",\"details\":\"Not Found\"}", js)
}
func TestIsDigest(t *testing.T) {
assert := assert.New(t)
assert.False(isDigest("latest"))
assert.True(isDigest("sha256:1359608115b94599e5641638bac5aef1ddfaa79bb96057ebf41ebc8d33acf8a7"))
}

View File

@ -8,9 +8,9 @@ import (
"github.com/vmware/harbor/src/common/utils/clair"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/common/utils/notary"
// "github.com/vmware/harbor/src/ui/api"
"github.com/vmware/harbor/src/ui/config"
"github.com/vmware/harbor/src/ui/projectmanager"
uiutils "github.com/vmware/harbor/src/ui/utils"
"context"
"fmt"
@ -18,6 +18,7 @@ import (
"net/http/httptest"
"os"
"regexp"
"strconv"
"strings"
)
@ -25,6 +26,7 @@ type contextKey string
const (
manifestURLPattern = `^/v2/((?:[a-z0-9]+(?:[._-][a-z0-9]+)*/)+)manifests/([\w][\w.:-]{0,127})`
catalogURLPattern = `/v2/_catalog`
imageInfoCtxKey = contextKey("ImageInfo")
//TODO: temp solution, remove after vmware/harbor#2242 is resolved.
tokenUsername = "harbor-ui"
@ -54,6 +56,19 @@ func MatchPullManifest(req *http.Request) (bool, string, string) {
return false, "", ""
}
// MatchListRepos checks if the request looks like a request to list repositories.
func MatchListRepos(req *http.Request) bool {
if req.Method != http.MethodGet {
return false
}
re := regexp.MustCompile(catalogURLPattern)
s := re.FindStringSubmatch(req.URL.Path)
if len(s) == 1 {
return true
}
return false
}
// policyChecker checks the policy of a project by project name, to determine if it's needed to check the image's status under this project.
type policyChecker interface {
// contentTrustEnabled returns whether a project has enabled content trust.
@ -100,7 +115,6 @@ func newPMSPolicyChecker(pm projectmanager.ProjectManager) policyChecker {
}
}
// TODO: Get project manager with PM factory.
func getPolicyChecker() policyChecker {
if config.WithAdmiral() {
return newPMSPolicyChecker(config.GlobalProjectMgr)
@ -110,7 +124,7 @@ func getPolicyChecker() policyChecker {
type imageInfo struct {
repository string
tag string
reference string
projectName string
digest string
}
@ -119,31 +133,37 @@ type urlHandler struct {
next http.Handler
}
//TODO: wrap a ResponseWriter to get the status code?
func (uh urlHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
log.Debugf("in url handler, path: %s", req.URL.Path)
req.URL.Path = strings.TrimPrefix(req.URL.Path, RegistryProxyPrefix)
flag, repository, tag := MatchPullManifest(req)
flag, repository, reference := MatchPullManifest(req)
if flag {
components := strings.SplitN(repository, "/", 2)
if len(components) < 2 {
http.Error(rw, marshalError(fmt.Sprintf("Bad repository name: %s", repository), http.StatusInternalServerError), http.StatusBadRequest)
return
}
rec = httptest.NewRecorder()
uh.next.ServeHTTP(rec, req)
if rec.Result().StatusCode != http.StatusOK {
copyResp(rec, rw)
client, err := uiutils.NewRepositoryClientForUI(tokenUsername, repository)
if err != nil {
log.Errorf("Error creating repository Client: %v", err)
http.Error(rw, marshalError(fmt.Sprintf("Failed due to internal Error: %v", err), http.StatusInternalServerError), http.StatusInternalServerError)
return
}
digest := rec.Header().Get(http.CanonicalHeaderKey("Docker-Content-Digest"))
digest, _, err := client.ManifestExist(reference)
if err != nil {
log.Errorf("Failed to get digest for reference: %s, error: %v", reference, err)
http.Error(rw, marshalError(fmt.Sprintf("Failed due to internal Error: %v", err), http.StatusInternalServerError), http.StatusInternalServerError)
return
}
img := imageInfo{
repository: repository,
tag: tag,
reference: reference,
projectName: components[0],
digest: digest,
}
log.Debugf("image info of the request: %#v", img)
ctx := context.WithValue(req.Context(), imageInfoCtxKey, img)
req = req.WithContext(ctx)
@ -151,6 +171,58 @@ func (uh urlHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
uh.next.ServeHTTP(rw, req)
}
type listReposHandler struct {
next http.Handler
}
func (lrh listReposHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
listReposFlag := MatchListRepos(req)
if listReposFlag {
rec = httptest.NewRecorder()
lrh.next.ServeHTTP(rec, req)
if rec.Result().StatusCode != http.StatusOK {
copyResp(rec, rw)
return
}
var ctlg struct {
Repositories []string `json:"repositories"`
}
decoder := json.NewDecoder(rec.Body)
if err := decoder.Decode(&ctlg); err != nil {
log.Errorf("Decode repositories error: %v", err)
copyResp(rec, rw)
return
}
var entries []string
for repo := range ctlg.Repositories {
log.Debugf("the repo in the reponse %s", ctlg.Repositories[repo])
exist := dao.RepositoryExists(ctlg.Repositories[repo])
if exist {
entries = append(entries, ctlg.Repositories[repo])
}
}
type Repos struct {
Repositories []string `json:"repositories"`
}
resp := &Repos{Repositories: entries}
respJSON, err := json.Marshal(resp)
if err != nil {
log.Errorf("Encode repositories error: %v", err)
copyResp(rec, rw)
return
}
for k, v := range rec.Header() {
rw.Header()[k] = v
}
clen := len(respJSON)
rw.Header().Set(http.CanonicalHeaderKey("Content-Length"), strconv.Itoa(clen))
rw.Write(respJSON)
return
}
lrh.next.ServeHTTP(rw, req)
}
type contentTrustHandler struct {
next http.Handler
}
@ -162,6 +234,10 @@ func (cth contentTrustHandler) ServeHTTP(rw http.ResponseWriter, req *http.Reque
return
}
img, _ := req.Context().Value(imageInfoCtxKey).(imageInfo)
if img.digest == "" {
cth.next.ServeHTTP(rw, req)
return
}
if !getPolicyChecker().contentTrustEnabled(img.projectName) {
cth.next.ServeHTTP(rw, req)
return
@ -190,6 +266,10 @@ func (vh vulnerableHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request)
return
}
img, _ := req.Context().Value(imageInfoCtxKey).(imageInfo)
if img.digest == "" {
vh.next.ServeHTTP(rw, req)
return
}
projectVulnerableEnabled, projectVulnerableSeverity := getPolicyChecker().vulnerablePolicy(img.projectName)
if !projectVulnerableEnabled {
vh.next.ServeHTTP(rw, req)
@ -197,7 +277,7 @@ func (vh vulnerableHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request)
}
overview, err := dao.GetImgScanOverview(img.digest)
if err != nil {
log.Errorf("failed to get ImgScanOverview with repo: %s, tag: %s, digest: %s. Error: %v", img.repository, img.tag, img.digest, err)
log.Errorf("failed to get ImgScanOverview with repo: %s, reference: %s, digest: %s. Error: %v", img.repository, img.reference, img.digest, err)
http.Error(rw, marshalError("Failed to get ImgScanOverview.", http.StatusPreconditionFailed), http.StatusPreconditionFailed)
return
}
@ -216,39 +296,42 @@ func (vh vulnerableHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request)
vh.next.ServeHTTP(rw, req)
}
type funnelHandler struct {
next http.Handler
}
func (fu funnelHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
imgRaw := req.Context().Value(imageInfoCtxKey)
if imgRaw != nil {
log.Debugf("Return the original response as no the interceptor takes action.")
copyResp(rec, rw)
return
}
fu.next.ServeHTTP(rw, req)
}
func matchNotaryDigest(img imageInfo) (bool, error) {
targets, err := notary.GetInternalTargets(NotaryEndpoint, tokenUsername, img.repository)
if err != nil {
return false, err
}
for _, t := range targets {
if t.Tag == img.tag {
log.Debugf("found tag: %s in notary, try to match digest.", img.tag)
if isDigest(img.reference) {
d, err := notary.DigestFromTarget(t)
if err != nil {
return false, err
}
return img.digest == d, nil
if img.digest == d {
return true, nil
}
} else {
if t.Tag == img.reference {
log.Debugf("found reference: %s in notary, try to match digest.", img.reference)
d, err := notary.DigestFromTarget(t)
if err != nil {
return false, err
}
if img.digest == d {
return true, nil
}
}
}
}
log.Debugf("image: %#v, not found in notary", img)
return false, nil
}
//A sha256 is a string with 64 characters.
func isDigest(ref string) bool {
return strings.HasPrefix(ref, "sha256:") && len(ref) == 71
}
func copyResp(rec *httptest.ResponseRecorder, rw http.ResponseWriter) {
for k, v := range rec.Header() {
rw.Header()[k] = v

View File

@ -41,8 +41,7 @@ func Init(urls ...string) error {
return err
}
Proxy = httputil.NewSingleHostReverseProxy(targetURL)
//TODO: add vulnerable interceptor.
handlers = handlerChain{head: urlHandler{next: contentTrustHandler{next: vulnerableHandler{next: funnelHandler{next: Proxy}}}}}
handlers = handlerChain{head: urlHandler{next: listReposHandler{next: contentTrustHandler{next: vulnerableHandler{next: Proxy}}}}}
return nil
}