add multiple manifest intercepetor handler

1, Add a interceptor to block request to upload manifest list
2, Discard notiification without tag.

Signed-off-by: wang yan <wangyan@vmware.com>
This commit is contained in:
wang yan 2019-05-06 15:02:23 +08:00
parent d74624d306
commit ab08a576e4
4 changed files with 97 additions and 15 deletions

View File

@ -79,6 +79,53 @@ func TestMatchPullManifest(t *testing.T) {
assert.Equal("sha256:ca4626b691f57d16ce1576231e4a2e2135554d32e13a85dcff380d51fdd13f6a", tag7)
}
func TestMatchPushManifest(t *testing.T) {
assert := assert.New(t)
req1, _ := http.NewRequest("POST", "http://127.0.0.1:5000/v2/library/ubuntu/manifests/14.04", nil)
res1, _, _ := MatchPushManifest(req1)
assert.False(res1, "%s %v is not a request to push manifest", req1.Method, req1.URL)
req2, _ := http.NewRequest("PUT", "http://192.168.0.3:80/v2/library/ubuntu/manifests/14.04", nil)
res2, repo2, tag2 := MatchPushManifest(req2)
assert.True(res2, "%s %v is a request to push manifest", req2.Method, req2.URL)
assert.Equal("library/ubuntu", repo2)
assert.Equal("14.04", tag2)
req3, _ := http.NewRequest("GET", "https://192.168.0.5:443/v1/library/ubuntu/manifests/14.04", nil)
res3, _, _ := MatchPushManifest(req3)
assert.False(res3, "%s %v is not a request to push manifest", req3.Method, req3.URL)
req4, _ := http.NewRequest("PUT", "https://192.168.0.5/v2/library/ubuntu/manifests/14.04", nil)
res4, repo4, tag4 := MatchPushManifest(req4)
assert.True(res4, "%s %v is a request to push manifest", req4.Method, req4.URL)
assert.Equal("library/ubuntu", repo4)
assert.Equal("14.04", tag4)
req5, _ := http.NewRequest("PUT", "https://myregistry.com/v2/path1/path2/golang/manifests/1.6.2", nil)
res5, repo5, tag5 := MatchPushManifest(req5)
assert.True(res5, "%s %v is a request to push manifest", req5.Method, req5.URL)
assert.Equal("path1/path2/golang", repo5)
assert.Equal("1.6.2", tag5)
req6, _ := http.NewRequest("PUT", "https://myregistry.com/v2/myproject/registry/manifests/sha256:ca4626b691f57d16ce1576231e4a2e2135554d32e13a85dcff380d51fdd13f6a", nil)
res6, repo6, tag6 := MatchPushManifest(req6)
assert.True(res6, "%s %v is a request to push manifest", req6.Method, req6.URL)
assert.Equal("myproject/registry", repo6)
assert.Equal("sha256:ca4626b691f57d16ce1576231e4a2e2135554d32e13a85dcff380d51fdd13f6a", tag6)
req7, _ := http.NewRequest("PUT", "https://myregistry.com/v2/myproject/manifests/sha256:ca4626b691f57d16ce1576231e4a2e2135554d32e13a85dcff380d51fdd13f6a", nil)
res7, repo7, tag7 := MatchPushManifest(req7)
assert.True(res7, "%s %v is a request to push manifest", req7.Method, req7.URL)
assert.Equal("myproject", repo7)
assert.Equal("sha256:ca4626b691f57d16ce1576231e4a2e2135554d32e13a85dcff380d51fdd13f6a", tag7)
req8, _ := http.NewRequest("PUT", "http://192.168.0.3:80/v2/library/ubuntu/manifests/14.04", nil)
res8, repo8, tag8 := MatchPushManifest(req8)
assert.True(res8, "%s %v is a request to push manifest", req8.Method, req8.URL)
assert.Equal("library/ubuntu", repo8)
assert.Equal("14.04", tag8)
}
func TestMatchListRepos(t *testing.T) {
assert := assert.New(t)
req1, _ := http.NewRequest("POST", "http://127.0.0.1:5000/v2/_catalog", nil)

View File

@ -43,6 +43,18 @@ func MatchPullManifest(req *http.Request) (bool, string, string) {
if req.Method != http.MethodGet {
return false, "", ""
}
return matchManifestURL(req)
}
// MatchPushManifest checks if the request looks like a request to push manifest. If it is returns the image and tag/sha256 digest as 2nd and 3rd return values
func MatchPushManifest(req *http.Request) (bool, string, string) {
if req.Method != http.MethodPut {
return false, "", ""
}
return matchManifestURL(req)
}
func matchManifestURL(req *http.Request) (bool, string, string) {
re := regexp.MustCompile(manifestURLPattern)
s := re.FindStringSubmatch(req.URL.Path)
if len(s) == 3 {
@ -168,6 +180,25 @@ func (rh readonlyHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
rh.next.ServeHTTP(rw, req)
}
type multipleManifestHandler struct {
next http.Handler
}
// The handler is responsible for blocking request to upload manifest list by docker client, which is not supported so far by Harbor.
func (mh multipleManifestHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
match, _, _ := MatchPushManifest(req)
if match {
contentType := req.Header.Get("Content-type")
// application/vnd.docker.distribution.manifest.list.v2+json
if strings.Contains(contentType, "manifest.list.v2") {
log.Debugf("Content-type: %s is not supported, failing the response.", contentType)
http.Error(rw, marshalError("UNSUPPORTED_MEDIA_TYPE", "Manifest.list is not supported."), http.StatusUnsupportedMediaType)
return
}
}
mh.next.ServeHTTP(rw, req)
}
type listReposHandler struct {
next http.Handler
}

View File

@ -38,7 +38,7 @@ func Init(urls ...string) error {
return err
}
Proxy = httputil.NewSingleHostReverseProxy(targetURL)
handlers = handlerChain{head: readonlyHandler{next: urlHandler{next: listReposHandler{next: contentTrustHandler{next: vulnerableHandler{next: Proxy}}}}}}
handlers = handlerChain{head: readonlyHandler{next: urlHandler{next: multipleManifestHandler{next: listReposHandler{next: contentTrustHandler{next: vulnerableHandler{next: Proxy}}}}}}}
return nil
}

View File

@ -93,6 +93,8 @@ func (n *NotificationHandler) Post() {
}()
if action == "push" {
// discard the notification without tag.
if tag != "" {
go func() {
exist := dao.RepositoryExists(repository)
if exist {
@ -107,6 +109,8 @@ func (n *NotificationHandler) Post() {
log.Errorf("Error happens when adding repository: %v", err)
}
}()
}
if !coreutils.WaitForManifestReady(repository, tag, 5) {
log.Errorf("Manifest for image %s:%s is not ready, skip the follow up actions.", repository, tag)
return