diff --git a/src/core/middlewares/middlewares.go b/src/core/middlewares/middlewares.go index e5ccdae05..4c353a580 100644 --- a/src/core/middlewares/middlewares.go +++ b/src/core/middlewares/middlewares.go @@ -15,6 +15,7 @@ package middlewares import ( + "github.com/goharbor/harbor/src/server/middleware/readonly" "net/http" "path" "regexp" @@ -29,11 +30,28 @@ import ( ) var ( - blobURLRe = regexp.MustCompile("^/v2/(" + reference.NameRegexp.String() + ")/blobs/" + reference.DigestRegexp.String()) + match = regexp.MustCompile + numericRegexp = match(`[0-9]+`) + + blobURLRe = match("^/v2/(" + reference.NameRegexp.String() + ")/blobs/" + reference.DigestRegexp.String()) // fetchBlobAPISkipper skip transaction middleware for fetch blob API // because transaction use the ResponseBuffer for the response which will degrade the performance for fetch blob fetchBlobAPISkipper = middleware.MethodAndPathSkipper(http.MethodGet, blobURLRe) + + // readonlySkippers skip the post request when harbor sets to readonly. + readonlySkippers = []middleware.Skipper{ + middleware.MethodAndPathSkipper(http.MethodPost, match("^/c/login")), + middleware.MethodAndPathSkipper(http.MethodPost, match("^/c/userExists")), + middleware.MethodAndPathSkipper(http.MethodPost, match("^/c/oidc/onboard")), + middleware.MethodAndPathSkipper(http.MethodPost, match("^/service/notifications/jobs/adminjob/"+numericRegexp.String())), + middleware.MethodAndPathSkipper(http.MethodPost, match("^/service/notifications/jobs/replication/"+numericRegexp.String())), + middleware.MethodAndPathSkipper(http.MethodPost, match("^/service/notifications/jobs/replication/task/"+numericRegexp.String())), + middleware.MethodAndPathSkipper(http.MethodPost, match("^/service/notifications/jobs/webhook/"+numericRegexp.String())), + middleware.MethodAndPathSkipper(http.MethodPost, match("^/service/notifications/jobs/retention/task/"+numericRegexp.String())), + middleware.MethodAndPathSkipper(http.MethodPost, match("^/service/notifications/jobs/schedules/"+numericRegexp.String())), + middleware.MethodAndPathSkipper(http.MethodPost, match("^/service/notifications/jobs/webhook/"+numericRegexp.String())), + } ) // legacyAPISkipper skip middleware for legacy APIs @@ -52,6 +70,7 @@ func legacyAPISkipper(r *http.Request) bool { func MiddleWares() []beego.MiddleWare { return []beego.MiddleWare{ requestid.Middleware(), + readonly.Middleware(readonlySkippers...), orm.Middleware(legacyAPISkipper), transaction.Middleware(legacyAPISkipper, fetchBlobAPISkipper), } diff --git a/src/core/middlewares/middlewares_test.go b/src/core/middlewares/middlewares_test.go index 7448c6db8..d21a013d5 100644 --- a/src/core/middlewares/middlewares_test.go +++ b/src/core/middlewares/middlewares_test.go @@ -65,3 +65,34 @@ func Test_legacyAPISkipper(t *testing.T) { }) } } + +func Test_readonlySkipper(t *testing.T) { + type args struct { + r *http.Request + } + tests := []struct { + name string + args args + want bool + }{ + {"login", args{httptest.NewRequest(http.MethodPost, "/c/login", nil)}, true}, + {"login get", args{httptest.NewRequest(http.MethodGet, "/c/login", nil)}, false}, + {"onboard", args{httptest.NewRequest(http.MethodPost, "/c/oidc/onboard", nil)}, true}, + {"user exist", args{httptest.NewRequest(http.MethodPost, "/c/userExists", nil)}, true}, + {"user exist", args{httptest.NewRequest(http.MethodPost, "/service/notifications/jobs/adminjob/123456", nil)}, true}, + {"user exist", args{httptest.NewRequest(http.MethodPost, "/service/notifications/jobs/adminjob/abcdefg", nil)}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var pass bool + for _, skipper := range readonlySkippers { + if got := skipper(tt.args.r); got == tt.want { + pass = true + } + } + if !pass { + t.Errorf("readonlySkippers() = %v, want %v", tt.args, tt.want) + } + }) + } +} diff --git a/src/server/middleware/readonly/readonly.go b/src/server/middleware/readonly/readonly.go index 490c0fad5..30c3a4f5e 100644 --- a/src/server/middleware/readonly/readonly.go +++ b/src/server/middleware/readonly/readonly.go @@ -59,6 +59,8 @@ func Middleware(skippers ...middleware.Skipper) func(http.Handler) http.Handler func MiddlewareWithConfig(config Config, skippers ...middleware.Skipper) func(http.Handler) http.Handler { if len(skippers) == 0 { skippers = []middleware.Skipper{safeMethodSkipper} + } else { + skippers = append(skippers, []middleware.Skipper{safeMethodSkipper}...) } if config.ReadOnly == nil { diff --git a/src/server/registry/route.go b/src/server/registry/route.go index a0c0eeceb..dc07a1b23 100644 --- a/src/server/registry/route.go +++ b/src/server/registry/route.go @@ -21,7 +21,6 @@ import ( "github.com/goharbor/harbor/src/server/middleware/blob" "github.com/goharbor/harbor/src/server/middleware/contenttrust" "github.com/goharbor/harbor/src/server/middleware/immutable" - "github.com/goharbor/harbor/src/server/middleware/readonly" "github.com/goharbor/harbor/src/server/middleware/regtoken" "github.com/goharbor/harbor/src/server/middleware/v2auth" "github.com/goharbor/harbor/src/server/middleware/vulnerable" @@ -59,13 +58,11 @@ func RegisterRoutes() { root.NewRoute(). Method(http.MethodDelete). Path("/*/manifests/:reference"). - Middleware(readonly.Middleware()). Middleware(immutable.MiddlewareDelete()). HandlerFunc(deleteManifest) root.NewRoute(). Method(http.MethodPut). Path("/*/manifests/:reference"). - Middleware(readonly.Middleware()). Middleware(immutable.MiddlewarePush()). Middleware(blob.PutManifestMiddleware()). HandlerFunc(putManifest) @@ -86,15 +83,6 @@ func RegisterRoutes() { Path("/*/blobs/uploads/:session_id"). Middleware(blob.PutBlobUploadMiddleware()). Handler(proxy) - // blob - root.NewRoute(). - Method(http.MethodPost). - Method(http.MethodPut). - Method(http.MethodPatch). - Method(http.MethodDelete). - Path("/{name:.*}/blobs/"). - Middleware(readonly.Middleware()). - Handler(proxy) // others root.NewRoute().Path("/*").Handler(proxy) }