From 7dd62276af41e7237f5f6b882665b4600601a5aa Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 28 Jan 2021 16:49:25 +0100 Subject: [PATCH 1/3] Move devMode config option to config pkg --- config/devmode.go | 39 +++++++++++++++++++++++++++++++++++++++ config/expertise.go | 2 +- config/main.go | 6 ++++++ config/option.go | 6 +++--- 4 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 config/devmode.go diff --git a/config/devmode.go b/config/devmode.go new file mode 100644 index 0000000..7dbd190 --- /dev/null +++ b/config/devmode.go @@ -0,0 +1,39 @@ +package config + +import ( + "flag" + + "github.com/safing/portbase/log" +) + +// Configuration Keys. +var ( + CfgDevModeKey = "core/devMode" + defaultDevMode bool +) + +func init() { + flag.BoolVar(&defaultDevMode, "devmode", false, "enable development mode") +} + +func logDevModeOverride() { + if defaultDevMode { + log.Warning("config: development mode is enabled by default by the -devmode flag") + } +} + +func registerDevModeOption() error { + return Register(&Option{ + Name: "Development Mode", + Key: CfgDevModeKey, + Description: "In Development Mode, security restrictions are lifted/softened to enable unrestricted access for debugging and testing purposes.", + OptType: OptTypeBool, + ExpertiseLevel: ExpertiseLevelDeveloper, + ReleaseLevel: ReleaseLevelStable, + DefaultValue: defaultDevMode, + Annotations: Annotations{ + DisplayOrderAnnotation: 512, + CategoryAnnotation: "Development", + }, + }) +} diff --git a/config/expertise.go b/config/expertise.go index 6d61608..eafb40f 100644 --- a/config/expertise.go +++ b/config/expertise.go @@ -14,7 +14,7 @@ import ( // to change deep configuration settings. type ExpertiseLevel uint8 -// Expertise Level constants +// Expertise Level constants. const ( ExpertiseLevelUser ExpertiseLevel = 0 ExpertiseLevelExpert ExpertiseLevel = 1 diff --git a/config/main.go b/config/main.go index d02fdc3..2857ce8 100644 --- a/config/main.go +++ b/config/main.go @@ -47,6 +47,12 @@ func prep() error { modules.SetCmdLineOperation(exportConfigCmd) } + logDevModeOverride() + err := registerDevModeOption() + if err != nil { + return err + } + return nil } diff --git a/config/option.go b/config/option.go index 759eac3..bbcb27d 100644 --- a/config/option.go +++ b/config/option.go @@ -60,7 +60,7 @@ type PossibleValue struct { // future well-known annotation additions do not conflict // with vendor/product/package specific annoations. // -// Format: :: +// Format: :: //. type Annotations map[string]interface{} // Well known annotations defined by this package. @@ -144,7 +144,7 @@ type ValueRequirement struct { Value interface{} } -// Values for the DisplayHintAnnotation +// Values for the DisplayHintAnnotation. const ( // DisplayHintOneOf is used to mark an option // as a "select"-style option. That is, only one of @@ -263,7 +263,7 @@ func (option *Option) SetAnnotation(key string, value interface{}) { option.Annotations[key] = value } -// GetAnnotation returns the value of the annotation key +// GetAnnotation returns the value of the annotation key. func (option *Option) GetAnnotation(key string) (interface{}, bool) { option.Lock() defer option.Unlock() From afdb367adafb58d28046a6987806c1e9e62bee27 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 28 Jan 2021 16:50:23 +0100 Subject: [PATCH 2/3] Remove unused middlewares and fix linter errors --- api/authentication.go | 16 ------------- api/client/const.go | 2 +- api/client/message.go | 2 +- api/config.go | 2 +- api/endpoints.go | 2 +- api/main.go | 2 +- api/middleware.go | 52 ------------------------------------------- 7 files changed, 5 insertions(+), 73 deletions(-) delete mode 100644 api/middleware.go diff --git a/api/authentication.go b/api/authentication.go index 2ef6352..792e6e2 100644 --- a/api/authentication.go +++ b/api/authentication.go @@ -133,22 +133,6 @@ func SetAuthenticator(fn AuthenticatorFunc) error { return nil } -func authMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - token := authenticateRequest(w, r, next) - if token == nil { - // Authenticator already replied. - return - } - - // Add token to request and serve next handler. - if _, apiRequest := getAPIContext(r); apiRequest != nil { - apiRequest.AuthToken = token - } - next.ServeHTTP(w, r) - }) -} - func authenticateRequest(w http.ResponseWriter, r *http.Request, targetHandler http.Handler) *AuthToken { tracer := log.Tracer(r.Context()) diff --git a/api/client/const.go b/api/client/const.go index c189c0e..080e938 100644 --- a/api/client/const.go +++ b/api/client/const.go @@ -1,6 +1,6 @@ package client -// message types +// Message Types. const ( msgRequestGet = "get" msgRequestQuery = "query" diff --git a/api/client/message.go b/api/client/message.go index 7280659..3e9b9dc 100644 --- a/api/client/message.go +++ b/api/client/message.go @@ -9,7 +9,7 @@ import ( "github.com/tevino/abool" ) -// Client errors +// Client errors. var ( ErrMalformedMessage = errors.New("malformed message") ) diff --git a/api/config.go b/api/config.go index ba40a44..706f5b4 100644 --- a/api/config.go +++ b/api/config.go @@ -7,7 +7,7 @@ import ( "github.com/safing/portbase/log" ) -// Config Keys +// Config Keys. const ( CfgDefaultListenAddressKey = "core/listenAddress" CfgAPIKeys = "core/apiKeys" diff --git a/api/endpoints.go b/api/endpoints.go index 2c92b97..c977fee 100644 --- a/api/endpoints.go +++ b/api/endpoints.go @@ -70,7 +70,7 @@ type ( RecordFunc func(ar *Request) (r record.Record, err error) ) -// MIME Types +// MIME Types. const ( MimeTypeJSON string = "application/json" MimeTypeText string = "text/plain" diff --git a/api/main.go b/api/main.go index 78846ef..e47b618 100644 --- a/api/main.go +++ b/api/main.go @@ -17,7 +17,7 @@ var ( exportEndpoints bool ) -// API Errors +// API Errors. var ( ErrAuthenticationAlreadySet = errors.New("the authentication function has already been set") ErrAuthenticationImmutable = errors.New("the authentication function can only be set before the api has started") diff --git a/api/middleware.go b/api/middleware.go deleted file mode 100644 index 7877037..0000000 --- a/api/middleware.go +++ /dev/null @@ -1,52 +0,0 @@ -package api - -import ( - "context" - "net/http" - - "github.com/safing/portbase/log" -) - -// Middleware is a function that can be added as a middleware to the API endpoint. -type Middleware func(next http.Handler) http.Handler - -type mwHandler struct { - handlers []Middleware - final http.Handler -} - -func (mwh *mwHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - handlerLock.RLock() - defer handlerLock.RUnlock() - - // final handler - handler := mwh.final - - // build middleware chain - // loop in reverse to build the handler chain in the correct order - for i := len(mwh.handlers) - 1; i >= 0; i-- { - handler = mwh.handlers[i](handler) - } - - // start - handler.ServeHTTP(w, r) -} - -// ModuleWorker is an http middleware that wraps the request in a module worker. -func ModuleWorker(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _ = module.RunWorker("http request", func(_ context.Context) error { - next.ServeHTTP(w, r) - return nil - }) - }) -} - -// LogTracer is an http middleware that attaches a log tracer to the request context. -func LogTracer(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx, tracer := log.AddTracer(r.Context()) - next.ServeHTTP(w, r.WithContext(ctx)) - tracer.Submit() - }) -} From 89fad3d9caeb3d286f88b56c90aeb885e1896816 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 28 Jan 2021 16:50:57 +0100 Subject: [PATCH 3/3] Use HTTP security headers on all requests --- api/authentication.go | 15 ++++++++++++++- api/config.go | 4 ++++ api/endpoints.go | 4 +++- api/router.go | 35 +++++++++++++++++------------------ 4 files changed, 38 insertions(+), 20 deletions(-) diff --git a/api/authentication.go b/api/authentication.go index 792e6e2..bbac2c9 100644 --- a/api/authentication.go +++ b/api/authentication.go @@ -242,6 +242,14 @@ func authenticateRequest(w http.ResponseWriter, r *http.Request, targetHandler h } func checkAuth(w http.ResponseWriter, r *http.Request, authRequired bool) (token *AuthToken, handled bool) { + // Return highest possible permissions in dev mode. + if devMode() { + return &AuthToken{ + Read: PermitSelf, + Write: PermitSelf, + }, false + } + // Check for valid API key. token = checkAPIKey(r) if token != nil { @@ -462,7 +470,12 @@ func deleteSession(sessionKey string) { } func isReadMethod(method string) bool { - return method == http.MethodGet || method == http.MethodHead + switch method { + case http.MethodGet, http.MethodHead, http.MethodOptions: + return true + default: + return false + } } func parseAPIPermission(s string) (Permission, error) { diff --git a/api/config.go b/api/config.go index 706f5b4..ebcc515 100644 --- a/api/config.go +++ b/api/config.go @@ -19,6 +19,8 @@ var ( defaultListenAddress string configuredAPIKeys config.StringArrayOption + + devMode config.BoolOption ) func init() { @@ -79,6 +81,8 @@ func registerConfig() error { } configuredAPIKeys = config.GetAsStringArray(CfgAPIKeys, []string{}) + devMode = config.Concurrent.GetAsBool(config.CfgDevModeKey, false) + return nil } diff --git a/api/endpoints.go b/api/endpoints.go index c977fee..bdb068d 100644 --- a/api/endpoints.go +++ b/api/endpoints.go @@ -256,6 +256,9 @@ func (eh *endpointHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { apiRequest.InputData = inputData case http.MethodGet: // Nothing special to do here. + case http.MethodOptions: + w.WriteHeader(http.StatusNoContent) + return default: http.Error(w, "Unsupported method for the actions API.", http.StatusMethodNotAllowed) return @@ -308,7 +311,6 @@ func (eh *endpointHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Write response. w.Header().Set("Content-Type", apiEndpoint.MimeType+"; charset=utf-8") w.Header().Set("Content-Length", strconv.Itoa(len(responseData))) - w.Header().Set("X-Content-Type-Options", "nosniff") w.WriteHeader(http.StatusOK) _, err = w.Write(responseData) if err != nil { diff --git a/api/router.go b/api/router.go index 81154a5..6b2280c 100644 --- a/api/router.go +++ b/api/router.go @@ -16,17 +16,6 @@ var ( // gorilla mux mainMux = mux.NewRouter() - // middlewares - middlewareHandler = &mwHandler{ - final: mainMux, - handlers: []Middleware{ - ModuleWorker, - LogTracer, - RequestLogger, - authMiddleware, - }, - } - // main server and lock server = &http.Server{} handlerLock sync.RWMutex @@ -46,18 +35,12 @@ func RegisterHandleFunc(path string, handleFunc func(http.ResponseWriter, *http. return mainMux.HandleFunc(path, handleFunc) } -// RegisterMiddleware registers a middle function with the API endoint. -func RegisterMiddleware(middleware Middleware) { - handlerLock.Lock() - defer handlerLock.Unlock() - middlewareHandler.handlers = append(middlewareHandler.handlers, middleware) -} - // Serve starts serving the API endpoint. func Serve() { // configure server server.Addr = listenAddressConfig() server.Handler = &mainHandler{ + // TODO: mainMux should not be modified anymore. mux: mainMux, } @@ -134,6 +117,22 @@ func (mh *mainHandler) handle(w http.ResponseWriter, r *http.Request) error { return nil } + // Add security headers. + if !devMode() { + w.Header().Set( + "Content-Security-Policy", + "default-src 'self'; "+ + "style-src 'self' 'unsafe-inline'; "+ + "img-src 'self' data:", + ) + w.Header().Set("Referrer-Policy", "no-referrer") + w.Header().Set("X-Content-Type-Options", "nosniff") + w.Header().Set("X-Frame-Options", "deny") + w.Header().Set("X-XSS-Protection", "1; mode=block") + } else { + w.Header().Set("Access-Control-Allow-Origin", "*") + } + // Handle request. switch { case handler != nil: