Merge pull request #10490 from wy65701436/middleware-readonly

move readonly middleware to new v2 handler
This commit is contained in:
Wenkai Yin(尹文开) 2020-01-17 14:09:20 +08:00 committed by GitHub
commit 6e3733aa7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 115 additions and 4 deletions

View File

@ -89,6 +89,9 @@ const (
PreconditionCode = "PRECONDITION" PreconditionCode = "PRECONDITION"
// GeneralCode ... // GeneralCode ...
GeneralCode = "UNKNOWN" GeneralCode = "UNKNOWN"
// DENIED it's used by middleware(readonly, vul and content trust) and returned to docker client to index the request is denied.
DENIED = "DENIED"
) )
// New ... // New ...

View File

@ -0,0 +1,27 @@
package middleware
import (
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/core/config"
internal_errors "github.com/goharbor/harbor/src/internal/error"
"net/http"
)
type readonlyHandler struct {
next http.Handler
}
// ReadOnly middleware reject request when harbor set to readonly
func ReadOnly() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if config.ReadOnly() {
log.Warningf("The request is prohibited in readonly mode, url is: %s", req.URL.Path)
pkgE := internal_errors.New(nil).WithCode(internal_errors.DENIED).WithMessage("The system is in read only mode. Any modification is prohibited.")
http.Error(rw, internal_errors.NewErrs(pkgE).Error(), http.StatusForbidden)
return
}
next.ServeHTTP(rw, req)
})
}
}

View File

@ -0,0 +1,50 @@
package middleware
import (
"github.com/goharbor/harbor/src/common"
config2 "github.com/goharbor/harbor/src/common/config"
"github.com/goharbor/harbor/src/core/config"
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestMain(m *testing.M) {
conf := map[string]interface{}{
common.ReadOnly: "true",
}
kp := &config2.PresetKeyProvider{Key: "naa4JtarA1Zsc3uY"}
config.InitWithSettings(conf, kp)
result := m.Run()
if result != 0 {
os.Exit(result)
}
}
func TestReadOnly(t *testing.T) {
assert := assert.New(t)
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
})
// delete
req := httptest.NewRequest(http.MethodDelete, "/readonly1", nil)
rec := httptest.NewRecorder()
ReadOnly()(next).ServeHTTP(rec, req)
assert.Equal(rec.Code, http.StatusForbidden)
update := map[string]interface{}{
common.ReadOnly: "false",
}
config.GetCfgManager().UpdateConfig(update)
req2 := httptest.NewRequest(http.MethodDelete, "/readonly2", nil)
rec2 := httptest.NewRecorder()
ReadOnly()(next).ServeHTTP(rec2, req2)
assert.Equal(rec2.Code, http.StatusOK)
}

View File

@ -0,0 +1,22 @@
package blob
import (
"net/http"
"net/http/httputil"
)
// NewHandler returns the handler to handler catalog request
func NewHandler(proxy *httputil.ReverseProxy) http.Handler {
return &handler{
proxy: proxy,
}
}
type handler struct {
proxy *httputil.ReverseProxy
}
// ServeHTTP ...
func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
h.proxy.ServeHTTP(w, req)
}

View File

@ -0,0 +1 @@
package blob

View File

@ -16,6 +16,8 @@ package registry
import ( import (
"github.com/goharbor/harbor/src/pkg/project" "github.com/goharbor/harbor/src/pkg/project"
"github.com/goharbor/harbor/src/server/middleware"
"github.com/goharbor/harbor/src/server/registry/blob"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"net/url" "net/url"
@ -45,14 +47,20 @@ func New(url *url.URL) http.Handler {
// handle manifest // handle manifest
// TODO maybe we should split it into several sub routers based on the method // TODO maybe we should split it into several sub routers based on the method
manifestRouter := rootRouter.Path("/v2/{name:.*}/manifests/{reference}").Subrouter() manifestRouter := rootRouter.Path("/v2/{name:.*}/manifests/{reference}").Subrouter()
manifestRouter.NewRoute().Methods(http.MethodGet, http.MethodHead, http.MethodPut, http.MethodDelete). manifestRouter.NewRoute().Methods(http.MethodGet).Handler(manifest.NewHandler(project.Mgr, proxy))
Handler(manifest.NewHandler(project.Mgr, proxy)) manifestRouter.NewRoute().Methods(http.MethodHead).Handler(manifest.NewHandler(project.Mgr, proxy))
manifestRouter.NewRoute().Methods(http.MethodPut).Handler(middleware.WithMiddlewares(manifest.NewHandler(project.Mgr, proxy), middleware.ReadOnly()))
manifestRouter.NewRoute().Methods(http.MethodDelete).Handler(middleware.WithMiddlewares(manifest.NewHandler(project.Mgr, proxy), middleware.ReadOnly()))
// handle blob // handle blob
// as we need to apply middleware to the blob requests, so create a sub router to handle the blob APIs // as we need to apply middleware to the blob requests, so create a sub router to handle the blob APIs
blobRouter := rootRouter.PathPrefix("/v2/{name:.*}/blobs/").Subrouter() blobRouter := rootRouter.PathPrefix("/v2/{name:.*}/blobs/").Subrouter()
blobRouter.NewRoute().Methods(http.MethodGet, http.MethodHead, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete). blobRouter.NewRoute().Methods(http.MethodGet).Handler(blob.NewHandler(proxy))
Handler(proxy) blobRouter.NewRoute().Methods(http.MethodHead).Handler(blob.NewHandler(proxy))
blobRouter.NewRoute().Methods(http.MethodPost).Handler(middleware.WithMiddlewares(blob.NewHandler(proxy), middleware.ReadOnly()))
blobRouter.NewRoute().Methods(http.MethodPut).Handler(middleware.WithMiddlewares(blob.NewHandler(proxy), middleware.ReadOnly()))
blobRouter.NewRoute().Methods(http.MethodPatch).Handler(middleware.WithMiddlewares(blob.NewHandler(proxy), middleware.ReadOnly()))
blobRouter.NewRoute().Methods(http.MethodDelete).Handler(middleware.WithMiddlewares(blob.NewHandler(proxy), middleware.ReadOnly()))
// all other APIs are proxy to the backend docker registry // all other APIs are proxy to the backend docker registry
rootRouter.PathPrefix("/").Handler(proxy) rootRouter.PathPrefix("/").Handler(proxy)