Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(extensions): consolidate extensions headers returned to UI by ext… #1473

Merged
merged 1 commit into from
May 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions pkg/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ import (
"os"
"path"
"path/filepath"
"strings"
"syscall"
"time"
"unicode/utf8"

"github.com/gorilla/mux"
"github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1"

Expand All @@ -31,8 +33,8 @@ const (
caCertFilename = "ca.crt"
)

func AllowedMethods(method string) []string {
return []string{http.MethodOptions, method}
func AllowedMethods(methods ...string) []string {
return append(methods, http.MethodOptions)
}

func Contains(slice []string, item string) bool {
Expand Down Expand Up @@ -283,3 +285,30 @@ func GetManifestArtifactType(manifestContent ispec.Manifest) string {

return manifestContent.Config.MediaType
}

func AddExtensionSecurityHeaders() mux.MiddlewareFunc { //nolint:varnamelen
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
resp.Header().Set("X-Content-Type-Options", "nosniff")

next.ServeHTTP(resp, req)
})
}
}

func ACHeadersHandler(allowedMethods ...string) mux.MiddlewareFunc {
headerValue := strings.Join(allowedMethods, ",")

return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
resp.Header().Set("Access-Control-Allow-Methods", headerValue)
resp.Header().Set("Access-Control-Allow-Headers", "Authorization,content-type")

if req.Method == http.MethodOptions {
return
}

next.ServeHTTP(resp, req)
})
}
}
19 changes: 8 additions & 11 deletions pkg/extensions/extension_mgmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

"zotregistry.io/zot/pkg/api/config"
"zotregistry.io/zot/pkg/api/constants"
"zotregistry.io/zot/pkg/common"
zcommon "zotregistry.io/zot/pkg/common"
"zotregistry.io/zot/pkg/log"
)

Expand Down Expand Up @@ -73,7 +73,7 @@ type mgmt struct {
func (mgmt *mgmt) handler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sanitizedConfig := mgmt.config.Sanitize()
buf, err := common.MarshalThroughStruct(sanitizedConfig, &StrippedConfig{})
buf, err := zcommon.MarshalThroughStruct(sanitizedConfig, &StrippedConfig{})
if err != nil {
mgmt.log.Error().Err(err).Msg("mgmt: couldn't marshal config response")
w.WriteHeader(http.StatusInternalServerError)
Expand All @@ -82,20 +82,17 @@ func (mgmt *mgmt) handler() http.Handler {
})
}

func addMgmtSecurityHeaders(h http.Handler) http.HandlerFunc { //nolint:varnamelen
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Content-Type-Options", "nosniff")

h.ServeHTTP(w, r)
}
}

func SetupMgmtRoutes(config *config.Config, router *mux.Router, log log.Logger) {
if config.Extensions.Mgmt != nil && *config.Extensions.Mgmt.Enable {
log.Info().Msg("setting up mgmt routes")

mgmt := mgmt{config: config, log: log}

router.PathPrefix(constants.ExtMgmt).Methods("GET").Handler(addMgmtSecurityHeaders(mgmt.handler()))
allowedMethods := zcommon.AllowedMethods(http.MethodGet)

mgmtRouter := router.PathPrefix(constants.ExtMgmt).Subrouter()
mgmtRouter.Use(zcommon.ACHeadersHandler(allowedMethods...))
eusebiu-constantin-petu-dbk marked this conversation as resolved.
Show resolved Hide resolved
mgmtRouter.Use(zcommon.AddExtensionSecurityHeaders())
mgmtRouter.Methods(allowedMethods...).Handler(mgmt.handler())
}
}
33 changes: 7 additions & 26 deletions pkg/extensions/extension_search.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

"zotregistry.io/zot/pkg/api/config"
"zotregistry.io/zot/pkg/api/constants"
zcommon "zotregistry.io/zot/pkg/common"
"zotregistry.io/zot/pkg/extensions/search"
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
"zotregistry.io/zot/pkg/extensions/search/gql_generated"
Expand Down Expand Up @@ -165,14 +166,6 @@ func (trivyT *trivyTask) DoWork() error {
return nil
}

func addSearchSecurityHeaders(h http.Handler) http.HandlerFunc { //nolint:varnamelen
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Content-Type-Options", "nosniff")

h.ServeHTTP(w, r)
}
}

func SetupSearchRoutes(config *config.Config, router *mux.Router, storeController storage.StoreController,
repoDB repodb.RepoDB, cveInfo CveInfo, log log.Logger,
) {
Expand All @@ -181,24 +174,12 @@ func SetupSearchRoutes(config *config.Config, router *mux.Router, storeControlle
if config.Extensions.Search != nil && *config.Extensions.Search.Enable {
resConfig := search.GetResolverConfig(log, storeController, repoDB, cveInfo)

extRouter := router.PathPrefix(constants.ExtSearch).Subrouter()
extRouter.Use(SearchACHeadersHandler())
extRouter.Methods("GET", "POST", "OPTIONS").
Handler(addSearchSecurityHeaders(gqlHandler.NewDefaultServer(gql_generated.NewExecutableSchema(resConfig))))
}
}

func SearchACHeadersHandler() mux.MiddlewareFunc {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
resp.Header().Set("Access-Control-Allow-Methods", "HEAD,GET,POST,OPTIONS")
resp.Header().Set("Access-Control-Allow-Headers", "Authorization,content-type")
allowedMethods := zcommon.AllowedMethods(http.MethodGet, http.MethodPost)

if req.Method == http.MethodOptions {
return
}

next.ServeHTTP(resp, req)
})
extRouter := router.PathPrefix(constants.ExtSearch).Subrouter()
extRouter.Use(zcommon.ACHeadersHandler(allowedMethods...))
extRouter.Use(zcommon.AddExtensionSecurityHeaders())
extRouter.Methods(allowedMethods...).
Handler(gqlHandler.NewDefaultServer(gql_generated.NewExecutableSchema(resConfig)))
}
}
23 changes: 5 additions & 18 deletions pkg/extensions/extension_userprefs.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,25 +34,12 @@ func SetupUserPreferencesRoutes(config *config.Config, router *mux.Router, store
if config.Extensions.Search != nil && *config.Extensions.Search.Enable {
log.Info().Msg("setting up user preferences routes")

userprefsRouter := router.PathPrefix(constants.ExtUserPreferences).Subrouter()
userprefsRouter.Use(UserPrefsACHeadersHandler())

userprefsRouter.HandleFunc("", HandleUserPrefs(repoDB, log)).Methods(zcommon.AllowedMethods(http.MethodPut)...)
}
}
allowedMethods := zcommon.AllowedMethods(http.MethodPut)

func UserPrefsACHeadersHandler() mux.MiddlewareFunc {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
resp.Header().Set("Access-Control-Allow-Methods", "HEAD,GET,POST,PUT,OPTIONS")
resp.Header().Set("Access-Control-Allow-Headers", "Authorization,content-type")

if req.Method == http.MethodOptions {
return
}

next.ServeHTTP(resp, req)
})
userprefsRouter := router.PathPrefix(constants.ExtUserPreferences).Subrouter()
userprefsRouter.Use(zcommon.ACHeadersHandler(allowedMethods...))
userprefsRouter.Use(zcommon.AddExtensionSecurityHeaders())
userprefsRouter.HandleFunc("", HandleUserPrefs(repoDB, log)).Methods(allowedMethods...)
}
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/extensions/extension_userprefs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (

var ErrTestError = errors.New("TestError")

func TestAllowedMethodsHeader(t *testing.T) {
func TestAllowedMethodsHeaderUserPrefs(t *testing.T) {
defaultVal := true

Convey("Test http options response", t, func() {
Expand All @@ -53,7 +53,7 @@ func TestAllowedMethodsHeader(t *testing.T) {

resp, _ := resty.R().Options(baseURL + constants.FullUserPreferencesPrefix)
So(resp, ShouldNotBeNil)
So(resp.Header().Get("Access-Control-Allow-Methods"), ShouldResemble, "HEAD,GET,POST,PUT,OPTIONS")
So(resp.Header().Get("Access-Control-Allow-Methods"), ShouldResemble, "PUT,OPTIONS")
So(resp.StatusCode(), ShouldEqual, http.StatusNoContent)
})
}
Expand Down
29 changes: 29 additions & 0 deletions pkg/extensions/extensions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -669,3 +669,32 @@ func TestMgmtWithBearer(t *testing.T) {
So(mgmtResp.HTTP.Auth.LDAP, ShouldBeNil)
})
}

func TestAllowedMethodsHeaderMgmt(t *testing.T) {
defaultVal := true

Convey("Test http options response", t, func() {
conf := config.New()
port := test.GetFreePort()
conf.HTTP.Port = port
conf.Extensions = &extconf.ExtensionConfig{
Mgmt: &extconf.MgmtConfig{
BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
},
}
baseURL := test.GetBaseURL(port)

ctlr := api.NewController(conf)
ctlr.Config.Storage.RootDirectory = t.TempDir()

ctrlManager := test.NewControllerManager(ctlr)

ctrlManager.StartAndWait(port)
defer ctrlManager.StopServer()

resp, _ := resty.R().Options(baseURL + constants.FullMgmtPrefix)
So(resp, ShouldNotBeNil)
So(resp.Header().Get("Access-Control-Allow-Methods"), ShouldResemble, "GET,OPTIONS")
So(resp.StatusCode(), ShouldEqual, http.StatusNoContent)
})
}