From b9c861f3f122fafcc7ef7c288e97a2b26f18c8a5 Mon Sep 17 00:00:00 2001 From: stonezdj Date: Tue, 16 Jun 2020 14:31:12 +0800 Subject: [PATCH] Add disable push for proxy project Add middleware for blob and manifest push operation Signed-off-by: stonezdj --- src/server/middleware/repoproxy/proxy.go | 44 +++++++++ src/server/middleware/repoproxy/proxy_test.go | 90 +++++++++++++++++++ src/server/registry/route.go | 3 + 3 files changed, 137 insertions(+) create mode 100644 src/server/middleware/repoproxy/proxy_test.go diff --git a/src/server/middleware/repoproxy/proxy.go b/src/server/middleware/repoproxy/proxy.go index 8d23e9563..abf7cc073 100644 --- a/src/server/middleware/repoproxy/proxy.go +++ b/src/server/middleware/repoproxy/proxy.go @@ -17,6 +17,8 @@ package repoproxy import ( "context" "fmt" + "github.com/goharbor/harbor/src/common/secret" + "github.com/goharbor/harbor/src/common/security" "github.com/goharbor/harbor/src/lib/errors" httpLib "github.com/goharbor/harbor/src/lib/http" "github.com/goharbor/harbor/src/replication/model" @@ -136,3 +138,45 @@ func setHeaders(w http.ResponseWriter, size int64, mediaType string, dig string) h.Set("Docker-Content-Digest", dig) h.Set("Etag", dig) } + +// isProxyProject check the project is a proxy project +func isProxyProject(p *models.Project) bool { + if p == nil { + return false + } + return p.RegistryID > 0 +} + +// isProxySession check if current security context is proxy session +func isProxySession(ctx context.Context) bool { + sc, ok := security.FromContext(ctx) + if !ok { + log.Error("Failed to get security context") + return false + } + if sc.IsSolutionUser() && sc.GetUsername() == secret.ProxyserviceUser { + return true + } + return false +} + +// DisableBlobAndManifestUploadMiddleware disable push artifact to a proxy project with a non-proxy session +func DisableBlobAndManifestUploadMiddleware() func(http.Handler) http.Handler { + return middleware.New(func(w http.ResponseWriter, r *http.Request, next http.Handler) { + ctx := r.Context() + art := lib.GetArtifactInfo(ctx) + p, err := project.Ctl.GetByName(ctx, art.ProjectName) + if err != nil { + httpLib.SendError(w, err) + return + } + if isProxyProject(p) && !isProxySession(ctx) { + httpLib.SendError(w, + errors.MethodNotAllowedError( + errors.Errorf("can not push artifact to a proxy project: %v", p.Name))) + return + } + next.ServeHTTP(w, r) + return + }) +} diff --git a/src/server/middleware/repoproxy/proxy_test.go b/src/server/middleware/repoproxy/proxy_test.go new file mode 100644 index 000000000..ab0b44db6 --- /dev/null +++ b/src/server/middleware/repoproxy/proxy_test.go @@ -0,0 +1,90 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package repoproxy + +import ( + "context" + "github.com/goharbor/harbor/src/common/models" + "github.com/goharbor/harbor/src/common/security" + securitySecret "github.com/goharbor/harbor/src/common/security/secret" + "github.com/goharbor/harbor/src/core/config" + "testing" +) + +func TestIsProxyProject(t *testing.T) { + cases := []struct { + name string + in *models.Project + want bool + }{ + { + name: `no proxy`, + in: &models.Project{RegistryID: 0}, + want: false, + }, + { + name: `normal proxy`, + in: &models.Project{RegistryID: 1}, + want: true, + }, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + + got := isProxyProject(tt.in) + + if got != tt.want { + t.Errorf(`(%v) = %v; want "%v"`, tt.in, got, tt.want) + } + + }) + } +} + +func TestIsProxySession(t *testing.T) { + config.Init() + sc1 := securitySecret.NewSecurityContext("123456789", config.SecretStore) + otherCtx := security.NewContext(context.Background(), sc1) + + sc2 := securitySecret.NewSecurityContext(config.ProxyServiceSecret, config.SecretStore) + proxyCtx := security.NewContext(context.Background(), sc2) + cases := []struct { + name string + in context.Context + want bool + }{ + { + name: `normal`, + in: otherCtx, + want: false, + }, + { + name: `proxy user`, + in: proxyCtx, + want: true, + }, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + got := isProxySession(tt.in) + if got != tt.want { + t.Errorf(`(%v) = %v; want "%v"`, tt.in, got, tt.want) + } + + }) + } +} diff --git a/src/server/registry/route.go b/src/server/registry/route.go index bc3b48eae..636f3a644 100644 --- a/src/server/registry/route.go +++ b/src/server/registry/route.go @@ -64,6 +64,7 @@ func RegisterRoutes() { root.NewRoute(). Method(http.MethodPut). Path("/*/manifests/:reference"). + Middleware(repoproxy.DisableBlobAndManifestUploadMiddleware()). Middleware(immutable.Middleware()). Middleware(quota.PutManifestMiddleware()). Middleware(blob.PutManifestMiddleware()). @@ -85,11 +86,13 @@ func RegisterRoutes() { root.NewRoute(). Method(http.MethodPatch). Path("/*/blobs/uploads/:session_id"). + Middleware(repoproxy.DisableBlobAndManifestUploadMiddleware()). Middleware(blob.PatchBlobUploadMiddleware()). Handler(proxy) root.NewRoute(). Method(http.MethodPut). Path("/*/blobs/uploads/:session_id"). + Middleware(repoproxy.DisableBlobAndManifestUploadMiddleware()). Middleware(quota.PutBlobUploadMiddleware()). Middleware(blob.PutBlobUploadMiddleware()). Handler(proxy)