diff --git a/src/core/proxy/interceptor_test.go b/src/core/proxy/interceptor_test.go index 40a30c0cb..be445316d 100644 --- a/src/core/proxy/interceptor_test.go +++ b/src/core/proxy/interceptor_test.go @@ -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) diff --git a/src/core/proxy/interceptors.go b/src/core/proxy/interceptors.go index fb7c29e0e..b8a3fe3b8 100644 --- a/src/core/proxy/interceptors.go +++ b/src/core/proxy/interceptors.go @@ -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 } diff --git a/src/core/proxy/proxy.go b/src/core/proxy/proxy.go index 930f9290d..eadbfed38 100644 --- a/src/core/proxy/proxy.go +++ b/src/core/proxy/proxy.go @@ -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 } diff --git a/src/core/service/notifications/registry/handler.go b/src/core/service/notifications/registry/handler.go index 16f8f2585..d3530f979 100644 --- a/src/core/service/notifications/registry/handler.go +++ b/src/core/service/notifications/registry/handler.go @@ -93,20 +93,24 @@ func (n *NotificationHandler) Post() { }() if action == "push" { - go func() { - exist := dao.RepositoryExists(repository) - if exist { - return - } - log.Debugf("Add repository %s into DB.", repository) - repoRecord := models.RepoRecord{ - Name: repository, - ProjectID: pro.ProjectID, - } - if err := dao.AddRepository(repoRecord); err != nil { - log.Errorf("Error happens when adding repository: %v", err) - } - }() + // discard the notification without tag. + if tag != "" { + go func() { + exist := dao.RepositoryExists(repository) + if exist { + return + } + log.Debugf("Add repository %s into DB.", repository) + repoRecord := models.RepoRecord{ + Name: repository, + ProjectID: pro.ProjectID, + } + if err := dao.AddRepository(repoRecord); err != nil { + 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