From 5650a24c9bc3178ce543be34f4a362e4c9aa037d Mon Sep 17 00:00:00 2001 From: rachithrr Date: Tue, 21 Dec 2021 16:23:12 -0500 Subject: [PATCH 01/27] query logger is set up. --- ctl/server.go | 1 + http/handler.go | 9 +++++++++ server.go | 8 ++++++++ server/config.go | 3 +++ server/server.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 63 insertions(+) diff --git a/ctl/server.go b/ctl/server.go index 83edb5456..5379ce0d3 100644 --- a/ctl/server.go +++ b/ctl/server.go @@ -21,6 +21,7 @@ func BuildServerFlags(cmd *cobra.Command, srv *server.Command) { flags.StringVar(&srv.Config.AdvertiseGRPC, "advertise-grpc", srv.Config.AdvertiseGRPC, "Address to advertise externally for gRPC.") flags.IntVar(&srv.Config.MaxWritesPerRequest, "max-writes-per-request", srv.Config.MaxWritesPerRequest, "Number of write commands per request.") flags.StringVar(&srv.Config.LogPath, "log-path", srv.Config.LogPath, "Log path") + flags.StringVar(&srv.Config.QueryLogPath , "query-log-path", srv.Config.QueryLogPath, "Path to save user queries") flags.BoolVar(&srv.Config.Verbose, "verbose", srv.Config.Verbose, "Enable verbose logging") flags.Uint64Var(&srv.Config.MaxMapCount, "max-map-count", srv.Config.MaxMapCount, "Limits the maximum number of active mmaps. FeatureBase will fall back to reading files once this is exhausted. Set below your system's vm.max_map_count.") flags.Uint64Var(&srv.Config.MaxFileCount, "max-file-count", srv.Config.MaxFileCount, "Soft limit on the maximum number of fragment files FeatureBase keeps open simultaneously.") diff --git a/http/handler.go b/http/handler.go index 9e626babf..39c99e5d2 100644 --- a/http/handler.go +++ b/http/handler.go @@ -51,6 +51,8 @@ type Handler struct { logger logger.Logger + querylogger logger.Logger + // Keeps the query argument validators for each handler validators map[string]*queryValidationSpec @@ -127,6 +129,13 @@ func OptHandlerLogger(logger logger.Logger) handlerOption { } } +func OptHandlerQueryLogger(logger logger.Logger) handlerOption { + return func(h *Handler) error { + h.querylogger = logger + return nil + } +} + // OptHandlerListener set the listener that will be used by the HTTP server. // Url must be the advertised URL. It will be used to show a log to the user // about where the Web UI is. This option is mandatory. diff --git a/server.go b/server.go index 8858ab122..ba5891863 100644 --- a/server.go +++ b/server.go @@ -67,6 +67,7 @@ type Server struct { // nolint: maligned systemInfo SystemInfo gcNotifier GCNotifier logger logger.Logger + querylogger logger.Logger snapshotQueue SnapshotQueue nodeID string @@ -112,6 +113,13 @@ func OptServerLogger(l logger.Logger) ServerOption { } } +func OptServerQueryLogger(l logger.Logger) ServerOption { + return func(s *Server) error { + s.querylogger = l + return nil + } +} + // OptServerReplicaN is a functional option on Server // used to set the number of replicas. func OptServerReplicaN(n int) ServerOption { diff --git a/server/config.go b/server/config.go index c215d1596..7801b8ce0 100644 --- a/server/config.go +++ b/server/config.go @@ -81,6 +81,9 @@ type Config struct { // LogPath configures where Pilosa will write logs. LogPath string `toml:"log-path"` + // QueryLogPath, security logs + QueryLogPath string `toml:"query-log-path"` + // Verbose toggles verbose logging which can be useful for debugging. Verbose bool `toml:"verbose"` diff --git a/server/server.go b/server/server.go index a6d0049ae..d06b52ba8 100644 --- a/server/server.go +++ b/server/server.go @@ -68,7 +68,9 @@ type Command struct { done chan struct{} logOutput io.Writer + querylogOutput io.Writer logger loggerLogger + querylogger loggerLogger Handler pilosa.Handler grpcServer *grpcServer @@ -334,6 +336,10 @@ func (m *Command) SetupServer() error { if err != nil { return errors.Wrap(err, "setting up logger") } + err = m.setupQueryLogger() + if err != nil { + return errors.Wrap(err, "setting up querylogger") + } m.logger.Infof("%s", pilosa.VersionInfo(m.Config.Future.Rename)) @@ -473,6 +479,7 @@ func (m *Command) SetupServer() error { pilosa.OptServerOpenTranslateReader(http.GetOpenTranslateReaderWithLockerFunc(c, &sync.Mutex{})), pilosa.OptServerOpenIDAllocator(pilosa.OpenIDAllocator), pilosa.OptServerLogger(m.logger), + pilosa.OptServerQueryLogger(m.querylogger), pilosa.OptServerSystemInfo(gopsutil.NewSystemInfo()), pilosa.OptServerGCNotifier(gcnotify.NewActiveGCNotifier()), pilosa.OptServerStatsClient(statsClient), @@ -527,6 +534,7 @@ func (m *Command) SetupServer() error { http.OptHandlerAllowedOrigins(m.Config.Handler.AllowedOrigins), http.OptHandlerAPI(m.API), http.OptHandlerLogger(m.logger), + http.OptHandlerQueryLogger(m.querylogger), http.OptHandlerFileSystem(&statik.FileSystem{}), http.OptHandlerListener(m.ln, m.Config.Advertise), http.OptHandlerCloseTimeout(m.closeTimeout), @@ -576,6 +584,40 @@ func (m *Command) setupLogger() error { return nil } +func (m *Command) setupQueryLogger() error { + var f *logger.FileWriter + var err error + + if m.Config.QueryLogPath == "" { + f, err = logger.NewFileWriterMode( "queries/query.log", 600) + if err != nil { + return errors.Wrap(err, "opening file") + } + } else { + f, err = logger.NewFileWriterMode(m.Config.QueryLogPath , 600) + if err != nil { + return errors.Wrap(err, "opening file") + } + } + m.querylogOutput = f + + m.querylogger = logger.NewStandardLogger(m.querylogOutput) + + sighup := make(chan os.Signal, 1) + signal.Notify(sighup, syscall.SIGHUP) + go func() { + for { + // reopen log file on SIGHUP + <-sighup + err = f.Reopen() + if err != nil { + m.querylogger.Infof("reopen: %s\n", err.Error()) + } + } + }() + return nil +} + // Close shuts down the server. func (m *Command) Close() error { select { From 49e9faa03b3bf2e0509238bfe6a8556a0fc46618 Mon Sep 17 00:00:00 2001 From: Samir Patel <48686912+54mir@users.noreply.github.com> Date: Wed, 22 Dec 2021 16:49:43 -0600 Subject: [PATCH 02/27] stub out checker --- http/handler.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/http/handler.go b/http/handler.go index 6d7796087..2d9e57d22 100644 --- a/http/handler.go +++ b/http/handler.go @@ -518,6 +518,20 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.Handler.ServeHTTP(w, r) } +// func (h *Handler)checkAuthorization(w http.ResponseWriter, r *http.Request, index string, neededPermission string) (bool, error){ +// groups, err := h.auth.Authenticate(w, r) +// if err != nil { +// return false, errors.Wrap(err, "authenticating") +// } + +// for group := range groups{ +// // is this group admin? +// // what kind of permissions do they have for this index? + +// } + +// } + // statikHandler implements the http.Handler interface, and responds to // requests for static assets with the appropriate file contents embedded // in a statik filesystem. From 0ef67fd69989b20b894fab268c456125b3658e7d Mon Sep 17 00:00:00 2001 From: Samir Patel <48686912+54mir@users.noreply.github.com> Date: Mon, 27 Dec 2021 16:42:13 -0500 Subject: [PATCH 03/27] move query logger option to auth --- ctl/server.go | 2 +- server/config.go | 4 +--- server/server.go | 48 +++++++++++++++++++++++++----------------------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/ctl/server.go b/ctl/server.go index 3a4ccf463..2f1f0ee65 100644 --- a/ctl/server.go +++ b/ctl/server.go @@ -21,7 +21,6 @@ func BuildServerFlags(cmd *cobra.Command, srv *server.Command) { flags.StringVar(&srv.Config.AdvertiseGRPC, "advertise-grpc", srv.Config.AdvertiseGRPC, "Address to advertise externally for gRPC.") flags.IntVar(&srv.Config.MaxWritesPerRequest, "max-writes-per-request", srv.Config.MaxWritesPerRequest, "Number of write commands per request.") flags.StringVar(&srv.Config.LogPath, "log-path", srv.Config.LogPath, "Log path") - flags.StringVar(&srv.Config.QueryLogPath , "query-log-path", srv.Config.QueryLogPath, "Path to save user queries") flags.BoolVar(&srv.Config.Verbose, "verbose", srv.Config.Verbose, "Enable verbose logging") flags.Uint64Var(&srv.Config.MaxMapCount, "max-map-count", srv.Config.MaxMapCount, "Limits the maximum number of active mmaps. FeatureBase will fall back to reading files once this is exhausted. Set below your system's vm.max_map_count.") flags.Uint64Var(&srv.Config.MaxFileCount, "max-file-count", srv.Config.MaxFileCount, "Soft limit on the maximum number of fragment files FeatureBase keeps open simultaneously.") @@ -122,5 +121,6 @@ func BuildServerFlags(cmd *cobra.Command, srv *server.Command) { flags.StringVar(&srv.Config.Auth.HashKey, "auth.hash-key", srv.Config.Auth.HashKey, "First Secret for Auth.") flags.StringVar(&srv.Config.Auth.BlockKey, "auth.block-key", srv.Config.Auth.BlockKey, "Second Secret for Auth.") flags.StringVar(&srv.Config.Auth.PermissionsFile, "auth.permissions", srv.Config.Auth.PermissionsFile, "Permissions' file with group authorization.") + flags.StringVar(&srv.Config.Auth.QueryLogPath, "auth.query-log-path", srv.Config.Auth.QueryLogPath, "Path to log user queries") } diff --git a/server/config.go b/server/config.go index df9dafdee..0e86c25d7 100644 --- a/server/config.go +++ b/server/config.go @@ -84,9 +84,6 @@ type Config struct { // LogPath configures where Pilosa will write logs. LogPath string `toml:"log-path"` - // QueryLogPath, security logs - QueryLogPath string `toml:"query-log-path"` - // Verbose toggles verbose logging which can be useful for debugging. Verbose bool `toml:"verbose"` @@ -253,6 +250,7 @@ type Auth struct { HashKey string `toml:"hash-key"` BlockKey string `toml:"block-key"` PermissionsFile string `toml:"permissions"` + QueryLogPath string `toml:"query-log-path"` } // Namespace returns the namespace to use based on the Future flag. diff --git a/server/server.go b/server/server.go index 387a34deb..577cef578 100644 --- a/server/server.go +++ b/server/server.go @@ -69,10 +69,10 @@ type Command struct { // done will be closed when Command.Close() is called done chan struct{} - logOutput io.Writer + logOutput io.Writer querylogOutput io.Writer - logger loggerLogger - querylogger loggerLogger + logger loggerLogger + querylogger loggerLogger Handler pilosa.Handler grpcServer *grpcServer @@ -336,10 +336,6 @@ func (m *Command) SetupServer() error { if err != nil { return errors.Wrap(err, "setting up logger") } - err = m.setupQueryLogger() - if err != nil { - return errors.Wrap(err, "setting up querylogger") - } m.logger.Infof("%s", pilosa.VersionInfo(m.Config.Future.Rename)) @@ -530,6 +526,7 @@ func (m *Command) SetupServer() error { return errors.Wrap(err, "new grpc server") } + var p authz.GroupPermissions if m.Config.Auth.Enable { m.Config.MustValidateAuth() permsFile, err := os.Open(m.Config.Auth.PermissionsFile) @@ -538,7 +535,6 @@ func (m *Command) SetupServer() error { } defer permsFile.Close() - var p authz.GroupPermissions if err = p.ReadPermissionsFile(permsFile); err != nil { return err } @@ -548,6 +544,11 @@ func (m *Command) SetupServer() error { if err != nil { return errors.Wrap(err, "instantiating authN object") } + + err = m.setupQueryLogger() + if err != nil { + return errors.Wrap(err, "setting up querylogger") + } } m.Handler, err = http.NewHandler( @@ -559,7 +560,8 @@ func (m *Command) SetupServer() error { http.OptHandlerListener(m.ln, m.Config.Advertise), http.OptHandlerCloseTimeout(m.closeTimeout), http.OptHandlerMiddleware(m.grpcServer.middleware(m.Config.Handler.AllowedOrigins)), - http.OptHandlerAuth(m.auth), + http.OptHandlerAuthN(m.auth), + http.OptHandlerAuthZ(&p), ) return errors.Wrap(err, "new handler") } @@ -609,13 +611,13 @@ func (m *Command) setupQueryLogger() error { var f *logger.FileWriter var err error - if m.Config.QueryLogPath == "" { - f, err = logger.NewFileWriterMode( "queries/query.log", 600) + if m.Config.Auth.QueryLogPath == "" { + f, err = logger.NewFileWriterMode("queries/query.log", 600) if err != nil { return errors.Wrap(err, "opening file") } } else { - f, err = logger.NewFileWriterMode(m.Config.QueryLogPath , 600) + f, err = logger.NewFileWriterMode(m.Config.Auth.QueryLogPath, 600) if err != nil { return errors.Wrap(err, "opening file") } @@ -624,18 +626,18 @@ func (m *Command) setupQueryLogger() error { m.querylogger = logger.NewStandardLogger(m.querylogOutput) - sighup := make(chan os.Signal, 1) - signal.Notify(sighup, syscall.SIGHUP) - go func() { - for { - // reopen log file on SIGHUP - <-sighup - err = f.Reopen() - if err != nil { - m.querylogger.Infof("reopen: %s\n", err.Error()) - } + sighup := make(chan os.Signal, 1) + signal.Notify(sighup, syscall.SIGHUP) + go func() { + for { + // reopen log file on SIGHUP + <-sighup + err = f.Reopen() + if err != nil { + m.querylogger.Infof("reopen: %s\n", err.Error()) } - }() + } + }() return nil } From 7a6595d6289357253bcff222fb216638f422c473 Mon Sep 17 00:00:00 2001 From: Samir Patel <48686912+54mir@users.noreply.github.com> Date: Mon, 27 Dec 2021 16:43:47 -0500 Subject: [PATCH 04/27] authorize few endpoints e.g. query --- authz/authorization.go | 39 ++++++++++++++++++++++++ http/handler.go | 68 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 94 insertions(+), 13 deletions(-) diff --git a/authz/authorization.go b/authz/authorization.go index 11bc9faac..5dc9ce765 100644 --- a/authz/authorization.go +++ b/authz/authorization.go @@ -29,6 +29,15 @@ type GroupPermissions struct { Admin string `yaml:"admin"` } +type Permission int64 + +const ( + None Permission = iota + Read + Write + Admin +) + func (p *GroupPermissions) ReadPermissionsFile(permsFile io.Reader) (err error) { permsData, err := ioutil.ReadAll(permsFile) @@ -118,3 +127,33 @@ func (p *GroupPermissions) GetAuthorizedIndexList(groups []authn.Group, desiredP } return indexList } + +func IsComparable(from, to string) bool { + switch from { + case "admin": + return true + case "write": + if to == "write" || to == "read" { + return true + } + case "read": + if to == "read" { + return true + } + } + return false +} + +func (p Permission) String() string { + switch p { + case Read: + return "read" + case Write: + return "write" + case Admin: + return "admin" + case None: + return "none" + } + return "unknown" +} diff --git a/http/handler.go b/http/handler.go index 2d9e57d22..b0dbd696b 100644 --- a/http/handler.go +++ b/http/handler.go @@ -30,6 +30,7 @@ import ( "github.com/gorilla/mux" pilosa "github.com/molecula/featurebase/v2" "github.com/molecula/featurebase/v2/authn" + "github.com/molecula/featurebase/v2/authz" "github.com/molecula/featurebase/v2/encoding/proto" "github.com/molecula/featurebase/v2/ingest" "github.com/molecula/featurebase/v2/logger" @@ -72,6 +73,8 @@ type Handler struct { pprofCPUProfileBuffer *bytes.Buffer auth *authn.Auth + + permissions *authz.GroupPermissions } // externalPrefixFlag denotes endpoints that are intended to be exposed to clients. @@ -118,9 +121,16 @@ func OptHandlerAPI(api *pilosa.API) handlerOption { } } -func OptHandlerAuth(auth *authn.Auth) handlerOption { +func OptHandlerAuthN(authn *authn.Auth) handlerOption { return func(h *Handler) error { - h.auth = auth + h.auth = authn + return nil + } +} + +func OptHandlerAuthZ(gp *authz.GroupPermissions) handlerOption { + return func(h *Handler) error { + h.permissions = gp return nil } } @@ -518,20 +528,36 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.Handler.ServeHTTP(w, r) } -// func (h *Handler)checkAuthorization(w http.ResponseWriter, r *http.Request, index string, neededPermission string) (bool, error){ -// groups, err := h.auth.Authenticate(w, r) -// if err != nil { -// return false, errors.Wrap(err, "authenticating") -// } - -// for group := range groups{ -// // is this group admin? -// // what kind of permissions do they have for this index? - -// } +// func (h *Handler) isAuthenticated(w http.ResponseWriter, r *http.Request) bool { // } +func (h *Handler) isAuthorized(w http.ResponseWriter, r *http.Request, req *pilosa.QueryRequest, index, desiredPermission, endpoint string) bool { + if h.auth == nil { + return true + } + groups, err := h.auth.Authenticate(w, r) + if err != nil { + http.Error(w, errors.Wrap(err, "authenticating").Error(), http.StatusBadRequest) + return false + } + + p, err := h.permissions.GetPermissions(groups, index) + uinfo := h.auth.GetUserInfo(w, r) + var query string + if req != nil { + query = fmt.Sprintf("%s%s", req.Query, req.SQLQuery) + } + h.querylogger.Infof("User ID: %s, User Name: %s, Endpoint: %s, Index: %s, Query: %s, Err: %v", uinfo.UserID, uinfo.UserName, endpoint, index, query, err) + if err != nil || !authz.IsComparable(p, desiredPermission) { + w.Header().Add("Content-Type", "text/plain") + w.WriteHeader(http.StatusForbidden) + return false + } + + return true +} + // statikHandler implements the http.Handler interface, and responds to // requests for static assets with the appropriate file contents embedded // in a statik filesystem. @@ -820,6 +846,10 @@ func (h *Handler) handleGetStatus(w http.ResponseWriter, r *http.Request) { } func (h *Handler) handleGetInfo(w http.ResponseWriter, r *http.Request) { + if !h.isAuthorized(w, r, nil, "--", authz.Admin.String(), r.URL.Path) { + return + } + if !validHeaderAcceptJSON(r.Header) { http.Error(w, "JSON only acceptable response", http.StatusNotAcceptable) return @@ -861,6 +891,10 @@ func (h *Handler) handlePostQuery(w http.ResponseWriter, r *http.Request) { qerr := r.Context().Value(contextKeyQueryError) req, ok := qreq.(*pilosa.QueryRequest) + if !h.isAuthorized(w, r, req, req.Index, authz.Admin.String(), r.URL.Path) { + return + } + if DoPerQueryProfiling { backend := pilosa.CurrentBackend() reqHash := hash(req.Query) @@ -2430,6 +2464,10 @@ func (h *Handler) handlePostClusterResizeRemoveNode(w http.ResponseWriter, r *ht http.Error(w, "JSON only acceptable response", http.StatusNotAcceptable) return } + + if !h.isAuthorized(w, r, nil, "--", authz.Admin.String(), r.URL.Path) { + return + } // Decode request. var req removeNodeRequest err := json.NewDecoder(r.Body).Decode(&req) @@ -2467,6 +2505,10 @@ type removeNodeResponse struct { // handlePostClusterResizeAbort handles POST /cluster/resize/abort request. func (h *Handler) handlePostClusterResizeAbort(w http.ResponseWriter, r *http.Request) { + if !h.isAuthorized(w, r, nil, "--", authz.Admin.String(), r.URL.Path) { + return + } + if !validHeaderAcceptJSON(r.Header) { http.Error(w, "JSON only acceptable response", http.StatusNotAcceptable) return From b9961870c14088b1cf8cf2ae2fbb7eef6828b230 Mon Sep 17 00:00:00 2001 From: Samir Patel <48686912+54mir@users.noreply.github.com> Date: Mon, 27 Dec 2021 18:22:53 -0500 Subject: [PATCH 05/27] implement as mw --- http/handler.go | 110 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 73 insertions(+), 37 deletions(-) diff --git a/http/handler.go b/http/handler.go index b0dbd696b..c54845241 100644 --- a/http/handler.go +++ b/http/handler.go @@ -411,7 +411,7 @@ func newRouter(handler *Handler) http.Handler { router.HandleFunc("/index/{index}/field/{field}/mutex-check", handler.handleGetMutexCheck).Methods("GET").Name("GetMutexCheck") router.HandleFunc("/index/{index}/field/{field}/import-roaring/{shard}", handler.handlePostImportRoaring).Methods("POST").Name("PostImportRoaring") router.HandleFunc("/index/{index}/query", handler.handlePostQuery).Methods("POST").Name("PostQuery") - router.HandleFunc("/info", handler.handleGetInfo).Methods("GET").Name("GetInfo") + router.HandleFunc("/info", handler.mwAuth(handler.handleGetInfo, authz.Admin)).Methods("GET").Name("GetInfo") router.HandleFunc("/recalculate-caches", handler.handleRecalculateCaches).Methods("POST").Name("RecalculateCaches") router.HandleFunc("/schema", handler.handleGetSchema).Methods("GET").Name("GetSchema") router.HandleFunc("/schema/details", handler.handleGetSchemaDetails).Methods("GET").Name("GetSchemaDetails") @@ -528,36 +528,72 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.Handler.ServeHTTP(w, r) } -// func (h *Handler) isAuthenticated(w http.ResponseWriter, r *http.Request) bool { +func (h *Handler) mwAuth(handler http.HandlerFunc, perm authz.Permission) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if h.auth != nil { -// } + groups, err := h.auth.Authenticate(w, r) + if err != nil { + http.Error(w, errors.Wrap(err, "authenticating").Error(), http.StatusBadRequest) + return + } -func (h *Handler) isAuthorized(w http.ResponseWriter, r *http.Request, req *pilosa.QueryRequest, index, desiredPermission, endpoint string) bool { - if h.auth == nil { - return true - } - groups, err := h.auth.Authenticate(w, r) - if err != nil { - http.Error(w, errors.Wrap(err, "authenticating").Error(), http.StatusBadRequest) - return false - } + indexName, ok := mux.Vars(r)["index"] + if !ok { + indexName = "" + } - p, err := h.permissions.GetPermissions(groups, index) - uinfo := h.auth.GetUserInfo(w, r) - var query string - if req != nil { - query = fmt.Sprintf("%s%s", req.Query, req.SQLQuery) - } - h.querylogger.Infof("User ID: %s, User Name: %s, Endpoint: %s, Index: %s, Query: %s, Err: %v", uinfo.UserID, uinfo.UserName, endpoint, index, query, err) - if err != nil || !authz.IsComparable(p, desiredPermission) { - w.Header().Add("Content-Type", "text/plain") - w.WriteHeader(http.StatusForbidden) - return false - } + p, err := h.permissions.GetPermissions(groups, indexName) + uinfo := h.auth.GetUserInfo(w, r) - return true + //get query string if applicable + var query string + // qreq := r.Context().Value(contextKeyQueryRequest) + // req, ok := qreq.(*pilosa.QueryRequest) + // if !ok { + // query = fmt.Sprintf("%s%s", req.Query, req.SQLQuery) + // } + + h.querylogger.Infof("User ID: %s, User Name: %s, Endpoint: %s, Index: %s, Query: %s, Err: %v", uinfo.UserID, uinfo.UserName, r.URL.Path, indexName, query, err) + if err != nil || !authz.IsComparable(p, perm.String()) { + w.Header().Add("Content-Type", "text/plain") + w.WriteHeader(http.StatusForbidden) + return + } + } + handler.ServeHTTP(w, r) + + } } +// TODO: DELETE + +// func (h *Handler) isAuthorized(w http.ResponseWriter, r *http.Request, req *pilosa.QueryRequest, index, desiredPermission, endpoint string) bool { +// if h.auth == nil { +// return true +// } +// groups, err := h.auth.Authenticate(w, r) +// if err != nil { +// http.Error(w, errors.Wrap(err, "authenticating").Error(), http.StatusBadRequest) +// return false +// } + +// p, err := h.permissions.GetPermissions(groups, index) +// uinfo := h.auth.GetUserInfo(w, r) +// var query string +// if req != nil { +// query = fmt.Sprintf("%s%s", req.Query, req.SQLQuery) +// } +// h.querylogger.Infof("User ID: %s, User Name: %s, Endpoint: %s, Index: %s, Query: %s, Err: %v", uinfo.UserID, uinfo.UserName, endpoint, index, query, err) +// if err != nil || !authz.IsComparable(p, desiredPermission) { +// w.Header().Add("Content-Type", "text/plain") +// w.WriteHeader(http.StatusForbidden) +// return false +// } + +// return true +// } + // statikHandler implements the http.Handler interface, and responds to // requests for static assets with the appropriate file contents embedded // in a statik filesystem. @@ -846,9 +882,9 @@ func (h *Handler) handleGetStatus(w http.ResponseWriter, r *http.Request) { } func (h *Handler) handleGetInfo(w http.ResponseWriter, r *http.Request) { - if !h.isAuthorized(w, r, nil, "--", authz.Admin.String(), r.URL.Path) { - return - } + // if !h.isAuthorized(w, r, nil, "--", authz.Admin.String(), r.URL.Path) { + // return + // } if !validHeaderAcceptJSON(r.Header) { http.Error(w, "JSON only acceptable response", http.StatusNotAcceptable) @@ -891,9 +927,9 @@ func (h *Handler) handlePostQuery(w http.ResponseWriter, r *http.Request) { qerr := r.Context().Value(contextKeyQueryError) req, ok := qreq.(*pilosa.QueryRequest) - if !h.isAuthorized(w, r, req, req.Index, authz.Admin.String(), r.URL.Path) { - return - } + // if !h.isAuthorized(w, r, req, req.Index, authz.Admin.String(), r.URL.Path) { + // return + // } if DoPerQueryProfiling { backend := pilosa.CurrentBackend() @@ -2465,9 +2501,9 @@ func (h *Handler) handlePostClusterResizeRemoveNode(w http.ResponseWriter, r *ht return } - if !h.isAuthorized(w, r, nil, "--", authz.Admin.String(), r.URL.Path) { - return - } + // if !h.isAuthorized(w, r, nil, "--", authz.Admin.String(), r.URL.Path) { + // return + // } // Decode request. var req removeNodeRequest err := json.NewDecoder(r.Body).Decode(&req) @@ -2505,9 +2541,9 @@ type removeNodeResponse struct { // handlePostClusterResizeAbort handles POST /cluster/resize/abort request. func (h *Handler) handlePostClusterResizeAbort(w http.ResponseWriter, r *http.Request) { - if !h.isAuthorized(w, r, nil, "--", authz.Admin.String(), r.URL.Path) { - return - } + // if !h.isAuthorized(w, r, nil, "--", authz.Admin.String(), r.URL.Path) { + // return + // } if !validHeaderAcceptJSON(r.Header) { http.Error(w, "JSON only acceptable response", http.StatusNotAcceptable) From 7544e7d1cb3c03f050a2cddf0cd4da7e1898c707 Mon Sep 17 00:00:00 2001 From: Samir Patel <48686912+54mir@users.noreply.github.com> Date: Tue, 28 Dec 2021 09:24:46 -0500 Subject: [PATCH 06/27] extend mw --- http/handler.go | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/http/handler.go b/http/handler.go index c54845241..0f230cbca 100644 --- a/http/handler.go +++ b/http/handler.go @@ -410,7 +410,7 @@ func newRouter(handler *Handler) http.Handler { router.HandleFunc("/index/{index}/field/{field}/import", handler.handlePostImport).Methods("POST").Name("PostImport") router.HandleFunc("/index/{index}/field/{field}/mutex-check", handler.handleGetMutexCheck).Methods("GET").Name("GetMutexCheck") router.HandleFunc("/index/{index}/field/{field}/import-roaring/{shard}", handler.handlePostImportRoaring).Methods("POST").Name("PostImportRoaring") - router.HandleFunc("/index/{index}/query", handler.handlePostQuery).Methods("POST").Name("PostQuery") + router.HandleFunc("/index/{index}/query", handler.mwAuth(handler.handlePostQuery, authz.Read)).Methods("POST").Name("PostQuery") router.HandleFunc("/info", handler.mwAuth(handler.handleGetInfo, authz.Admin)).Methods("GET").Name("GetInfo") router.HandleFunc("/recalculate-caches", handler.handleRecalculateCaches).Methods("POST").Name("RecalculateCaches") router.HandleFunc("/schema", handler.handleGetSchema).Methods("GET").Name("GetSchema") @@ -547,14 +547,21 @@ func (h *Handler) mwAuth(handler http.HandlerFunc, perm authz.Permission) http.H uinfo := h.auth.GetUserInfo(w, r) //get query string if applicable - var query string - // qreq := r.Context().Value(contextKeyQueryRequest) - // req, ok := qreq.(*pilosa.QueryRequest) - // if !ok { - // query = fmt.Sprintf("%s%s", req.Query, req.SQLQuery) - // } + queryRequest := r.Context().Value(contextKeyQueryRequest) - h.querylogger.Infof("User ID: %s, User Name: %s, Endpoint: %s, Index: %s, Query: %s, Err: %v", uinfo.UserID, uinfo.UserName, r.URL.Path, indexName, query, err) + var queryString string + if req, ok := queryRequest.(*pilosa.QueryRequest); ok { + queryString = req.Query + } + writeWords := []string{"store", "set", "clear", "clearrow"} + q := strings.ToLower(queryString) + for _, w := range writeWords { + if strings.Contains(q, w) { + perm = authz.Write + } + } + + h.querylogger.Infof("User ID: %s, User Name: %s, Endpoint: %s, Index: %s, Query: %s, Err: %v", uinfo.UserID, uinfo.UserName, r.URL.Path, indexName, queryString, err) if err != nil || !authz.IsComparable(p, perm.String()) { w.Header().Add("Content-Type", "text/plain") w.WriteHeader(http.StatusForbidden) From 6d590581d4548f05a7c9d80f4468fc748f5cf726 Mon Sep 17 00:00:00 2001 From: Samir Patel <48686912+54mir@users.noreply.github.com> Date: Tue, 28 Dec 2021 10:06:40 -0500 Subject: [PATCH 07/27] apply mw to handlers --- http/handler.go | 145 ++++++++++++++++++++++++------------------------ 1 file changed, 74 insertions(+), 71 deletions(-) diff --git a/http/handler.go b/http/handler.go index 0f230cbca..a83222091 100644 --- a/http/handler.go +++ b/http/handler.go @@ -387,94 +387,97 @@ var latticeRoutes = []string{"/tables", "/query", "/querybuilder", "/signin"} // // newRouter creates a new mux http router. func newRouter(handler *Handler) http.Handler { router := mux.NewRouter() - router.HandleFunc("/cluster/resize/abort", handler.handlePostClusterResizeAbort).Methods("POST").Name("PostClusterResizeAbort") - router.HandleFunc("/cluster/resize/remove-node", handler.handlePostClusterResizeRemoveNode).Methods("POST").Name("PostClusterResizeRemoveNode") + router.HandleFunc("/cluster/resize/abort", handler.mwAuth(handler.handlePostClusterResizeAbort, authz.Admin)).Methods("POST").Name("PostClusterResizeAbort") + router.HandleFunc("/cluster/resize/remove-node", handler.mwAuth(handler.handlePostClusterResizeRemoveNode, authz.Admin)).Methods("POST").Name("PostClusterResizeRemoveNode") + + // TODO: figure out how to protect these if needed router.PathPrefix("/debug/pprof/").Handler(http.DefaultServeMux).Methods("GET") router.PathPrefix("/debug/fgprof").Handler(fgprof.Handler()).Methods("GET") router.Handle("/debug/vars", expvar.Handler()).Methods("GET") router.Handle("/metrics", promhttp.Handler()) - router.HandleFunc("/metrics.json", handler.handleGetMetricsJSON).Methods("GET").Name("GetMetricsJSON") - router.HandleFunc("/export", handler.handleGetExport).Methods("GET").Name("GetExport") - router.HandleFunc("/import-atomic-record", handler.handlePostImportAtomicRecord).Methods("POST").Name("PostImportAtomicRecord") - router.HandleFunc("/index", handler.handleGetIndexes).Methods("GET").Name("GetIndexes") - router.HandleFunc("/index", handler.handlePostIndex).Methods("POST").Name("PostIndex") - router.HandleFunc("/index/", handler.handlePostIndex).Methods("POST").Name("PostIndex") - router.HandleFunc("/index/{index}", handler.handleGetIndex).Methods("GET").Name("GetIndex") - router.HandleFunc("/index/{index}", handler.handlePostIndex).Methods("POST").Name("PostIndex") - router.HandleFunc("/index/{index}", handler.handleDeleteIndex).Methods("DELETE").Name("DeleteIndex") - //router.HandleFunc("/index/{index}/field", handler.handleGetFields).Methods("GET") // Not implemented. - router.HandleFunc("/index/{index}/field", handler.handlePostField).Methods("POST").Name("PostField") - router.HandleFunc("/index/{index}/field/", handler.handlePostField).Methods("POST").Name("PostField") - router.HandleFunc("/index/{index}/field/{field}", handler.handlePostField).Methods("POST").Name("PostField") - router.HandleFunc("/index/{index}/field/{field}", handler.handleDeleteField).Methods("DELETE").Name("DeleteField") - router.HandleFunc("/index/{index}/field/{field}/import", handler.handlePostImport).Methods("POST").Name("PostImport") - router.HandleFunc("/index/{index}/field/{field}/mutex-check", handler.handleGetMutexCheck).Methods("GET").Name("GetMutexCheck") - router.HandleFunc("/index/{index}/field/{field}/import-roaring/{shard}", handler.handlePostImportRoaring).Methods("POST").Name("PostImportRoaring") + + router.HandleFunc("/metrics.json", handler.mwAuth(handler.handleGetMetricsJSON, authz.Admin)).Methods("GET").Name("GetMetricsJSON") + router.HandleFunc("/export", handler.mwAuth(handler.handleGetExport, authz.Read)).Methods("GET").Name("GetExport") + router.HandleFunc("/import-atomic-record", handler.mwAuth(handler.handlePostImportAtomicRecord, authz.Admin)).Methods("POST").Name("PostImportAtomicRecord") + router.HandleFunc("/index", handler.mwAuth(handler.handleGetIndexes, authz.Read)).Methods("GET").Name("GetIndexes") + router.HandleFunc("/index", handler.mwAuth(handler.handlePostIndex, authz.Admin)).Methods("POST").Name("PostIndex") + router.HandleFunc("/index/", handler.mwAuth(handler.handlePostIndex, authz.Admin)).Methods("POST").Name("PostIndex") + router.HandleFunc("/index/{index}", handler.mwAuth(handler.handleGetIndex, authz.Read)).Methods("GET").Name("GetIndex") + router.HandleFunc("/index/{index}", handler.mwAuth(handler.handlePostIndex, authz.Admin)).Methods("POST").Name("PostIndex") + router.HandleFunc("/index/{index}", handler.mwAuth(handler.handleDeleteIndex, authz.Admin)).Methods("DELETE").Name("DeleteIndex") + //router.HandleFunc("/index/{index}/field", handler.mwAuth(handler.handleGetFields, authz.Read)).Methods("GET") // Not implemented. + router.HandleFunc("/index/{index}/field", handler.mwAuth(handler.handlePostField, authz.Write)).Methods("POST").Name("PostField") + router.HandleFunc("/index/{index}/field/", handler.mwAuth(handler.handlePostField, authz.Write)).Methods("POST").Name("PostField") + router.HandleFunc("/index/{index}/field/{field}", handler.mwAuth(handler.handlePostField, authz.Write)).Methods("POST").Name("PostField") + router.HandleFunc("/index/{index}/field/{field}", handler.mwAuth(handler.handleDeleteField, authz.Write)).Methods("DELETE").Name("DeleteField") + router.HandleFunc("/index/{index}/field/{field}/import", handler.mwAuth(handler.handlePostImport, authz.Read)).Methods("POST").Name("PostImport") + router.HandleFunc("/index/{index}/field/{field}/mutex-check", handler.mwAuth(handler.handleGetMutexCheck, authz.Read)).Methods("GET").Name("GetMutexCheck") + router.HandleFunc("/index/{index}/field/{field}/import-roaring/{shard}", handler.mwAuth(handler.handlePostImportRoaring, authz.Read)).Methods("POST").Name("PostImportRoaring") router.HandleFunc("/index/{index}/query", handler.mwAuth(handler.handlePostQuery, authz.Read)).Methods("POST").Name("PostQuery") router.HandleFunc("/info", handler.mwAuth(handler.handleGetInfo, authz.Admin)).Methods("GET").Name("GetInfo") - router.HandleFunc("/recalculate-caches", handler.handleRecalculateCaches).Methods("POST").Name("RecalculateCaches") - router.HandleFunc("/schema", handler.handleGetSchema).Methods("GET").Name("GetSchema") - router.HandleFunc("/schema/details", handler.handleGetSchemaDetails).Methods("GET").Name("GetSchemaDetails") - router.HandleFunc("/schema", handler.handlePostSchema).Methods("POST").Name("PostSchema") - router.HandleFunc("/status", handler.handleGetStatus).Methods("GET").Name("GetStatus") - router.HandleFunc("/transaction", handler.handlePostTransaction).Methods("POST").Name("PostTransaction") - router.HandleFunc("/transaction/", handler.handlePostTransaction).Methods("POST").Name("PostTransaction") - router.HandleFunc("/transaction/{id}", handler.handleGetTransaction).Methods("GET").Name("GetTransaction") - router.HandleFunc("/transaction/{id}", handler.handlePostTransaction).Methods("POST").Name("PostTransaction") - router.HandleFunc("/transaction/{id}/finish", handler.handlePostFinishTransaction).Methods("POST").Name("PostFinishTransaction") - router.HandleFunc("/transactions", handler.handleGetTransactions).Methods("GET").Name("GetTransactions") - router.HandleFunc("/queries", handler.handleGetActiveQueries).Methods("GET").Name("GetActiveQueries") - router.HandleFunc("/query-history", handler.handleGetPastQueries).Methods("GET").Name("GetPastQueries") - router.HandleFunc("/version", handler.handleGetVersion).Methods("GET").Name("GetVersion") + router.HandleFunc("/recalculate-caches", handler.mwAuth(handler.handleRecalculateCaches, authz.Admin)).Methods("POST").Name("RecalculateCaches") + router.HandleFunc("/schema", handler.mwAuth(handler.handleGetSchema, authz.Read)).Methods("GET").Name("GetSchema") + router.HandleFunc("/schema/details", handler.mwAuth(handler.handleGetSchemaDetails, authz.Read)).Methods("GET").Name("GetSchemaDetails") + router.HandleFunc("/schema", handler.mwAuth(handler.handlePostSchema, authz.Admin)).Methods("POST").Name("PostSchema") + router.HandleFunc("/status", handler.mwAuth(handler.handleGetStatus, authz.Read)).Methods("GET").Name("GetStatus") + router.HandleFunc("/transaction", handler.mwAuth(handler.handlePostTransaction, authz.Read)).Methods("POST").Name("PostTransaction") + router.HandleFunc("/transaction/", handler.mwAuth(handler.handlePostTransaction, authz.Read)).Methods("POST").Name("PostTransaction") + router.HandleFunc("/transaction/{id}", handler.mwAuth(handler.handleGetTransaction, authz.Read)).Methods("GET").Name("GetTransaction") + router.HandleFunc("/transaction/{id}", handler.mwAuth(handler.handlePostTransaction, authz.Read)).Methods("POST").Name("PostTransaction") + router.HandleFunc("/transaction/{id}/finish", handler.mwAuth(handler.handlePostFinishTransaction, authz.Read)).Methods("POST").Name("PostFinishTransaction") + router.HandleFunc("/transactions", handler.mwAuth(handler.handleGetTransactions, authz.Read)).Methods("GET").Name("GetTransactions") + router.HandleFunc("/queries", handler.mwAuth(handler.handleGetActiveQueries, authz.Read)).Methods("GET").Name("GetActiveQueries") + router.HandleFunc("/query-history", handler.mwAuth(handler.handleGetPastQueries, authz.Read)).Methods("GET").Name("GetPastQueries") + router.HandleFunc("/version", handler.mwAuth(handler.handleGetVersion, authz.Read)).Methods("GET").Name("GetVersion") // /ui endpoints are for UI use; they may change at any time. - router.HandleFunc("/ui/usage", handler.handleGetUsage).Methods("GET").Name("GetUsage") - router.HandleFunc("/ui/transaction", handler.handleGetTransactionList).Methods("GET").Name("GetTransactionList") - router.HandleFunc("/ui/transaction/", handler.handleGetTransactionList).Methods("GET").Name("GetTransactionList") - router.HandleFunc("/ui/shard-distribution", handler.handleGetShardDistribution).Methods("GET").Name("GetShardDistribution") + router.HandleFunc("/ui/usage", handler.mwAuth(handler.handleGetUsage, authz.Read)).Methods("GET").Name("GetUsage") + router.HandleFunc("/ui/transaction", handler.mwAuth(handler.handleGetTransactionList, authz.Read)).Methods("GET").Name("GetTransactionList") + router.HandleFunc("/ui/transaction/", handler.mwAuth(handler.handleGetTransactionList, authz.Read)).Methods("GET").Name("GetTransactionList") + router.HandleFunc("/ui/shard-distribution", handler.mwAuth(handler.handleGetShardDistribution, authz.Read)).Methods("GET").Name("GetShardDistribution") // /internal endpoints are for internal use only; they may change at any time. // DO NOT rely on these for external applications! - router.HandleFunc("/internal/cluster/message", handler.handlePostClusterMessage).Methods("POST").Name("PostClusterMessage") - router.HandleFunc("/internal/fragment/block/data", handler.handleGetFragmentBlockData).Methods("GET").Name("GetFragmentBlockData") - router.HandleFunc("/internal/fragment/blocks", handler.handleGetFragmentBlocks).Methods("GET").Name("GetFragmentBlocks") - router.HandleFunc("/internal/fragment/data", handler.handleGetFragmentData).Methods("GET").Name("GetFragmentData") - router.HandleFunc("/internal/fragment/nodes", handler.handleGetFragmentNodes).Methods("GET").Name("GetFragmentNodes") - router.HandleFunc("/internal/partition/nodes", handler.handleGetPartitionNodes).Methods("GET").Name("GetPartitionNodes") - router.HandleFunc("/internal/translate/data", handler.handleGetTranslateData).Methods("GET").Name("GetTranslateData") - router.HandleFunc("/internal/translate/data", handler.handlePostTranslateData).Methods("POST").Name("PostTranslateData") - router.HandleFunc("/internal/translate/keys", handler.handlePostTranslateKeys).Methods("POST").Name("PostTranslateKeys") - router.HandleFunc("/internal/translate/ids", handler.handlePostTranslateIDs).Methods("POST").Name("PostTranslateIDs") - router.HandleFunc("/internal/index/{index}/field/{field}/mutex-check", handler.handleInternalGetMutexCheck).Methods("GET").Name("InternalGetMutexCheck") - router.HandleFunc("/internal/index/{index}/field/{field}/remote-available-shards/{shardID}", handler.handleDeleteRemoteAvailableShard).Methods("DELETE") - router.HandleFunc("/internal/index/{index}/shard/{shard}/snapshot", handler.handleGetIndexShardSnapshot).Methods("GET").Name("GetIndexShardSnapshot") - router.HandleFunc("/internal/index/{index}/shards", handler.handleGetIndexAvailableShards).Methods("GET").Name("GetIndexAvailableShards") - router.HandleFunc("/internal/nodes", handler.handleGetNodes).Methods("GET").Name("GetNodes") - router.HandleFunc("/internal/shards/max", handler.handleGetShardsMax).Methods("GET").Name("GetShardsMax") // TODO: deprecate, but it's being used by the client - router.HandleFunc("/internal/ingest/{index}", handler.handlePostIngestData).Methods("POST").Name("PostIngestData") - router.HandleFunc("/internal/ingest/{index}/node", handler.handlePostIngestNode).Methods("POST").Name("PostIngestNode") + router.HandleFunc("/internal/cluster/message", handler.mwAuth(handler.handlePostClusterMessage, authz.Admin)).Methods("POST").Name("PostClusterMessage") + router.HandleFunc("/internal/fragment/block/data", handler.mwAuth(handler.handleGetFragmentBlockData, authz.Admin)).Methods("GET").Name("GetFragmentBlockData") + router.HandleFunc("/internal/fragment/blocks", handler.mwAuth(handler.handleGetFragmentBlocks, authz.Admin)).Methods("GET").Name("GetFragmentBlocks") + router.HandleFunc("/internal/fragment/data", handler.mwAuth(handler.handleGetFragmentData, authz.Admin)).Methods("GET").Name("GetFragmentData") + router.HandleFunc("/internal/fragment/nodes", handler.mwAuth(handler.handleGetFragmentNodes, authz.Admin)).Methods("GET").Name("GetFragmentNodes") + router.HandleFunc("/internal/partition/nodes", handler.mwAuth(handler.handleGetPartitionNodes, authz.Admin)).Methods("GET").Name("GetPartitionNodes") + router.HandleFunc("/internal/translate/data", handler.mwAuth(handler.handleGetTranslateData, authz.Admin)).Methods("GET").Name("GetTranslateData") + router.HandleFunc("/internal/translate/data", handler.mwAuth(handler.handlePostTranslateData, authz.Admin)).Methods("POST").Name("PostTranslateData") + router.HandleFunc("/internal/translate/keys", handler.mwAuth(handler.handlePostTranslateKeys, authz.Admin)).Methods("POST").Name("PostTranslateKeys") + router.HandleFunc("/internal/translate/ids", handler.mwAuth(handler.handlePostTranslateIDs, authz.Admin)).Methods("POST").Name("PostTranslateIDs") + router.HandleFunc("/internal/index/{index}/field/{field}/mutex-check", handler.mwAuth(handler.handleInternalGetMutexCheck, authz.Admin)).Methods("GET").Name("InternalGetMutexCheck") + router.HandleFunc("/internal/index/{index}/field/{field}/remote-available-shards/{shardID}", handler.mwAuth(handler.handleDeleteRemoteAvailableShard, authz.Admin)).Methods("DELETE") + router.HandleFunc("/internal/index/{index}/shard/{shard}/snapshot", handler.mwAuth(handler.handleGetIndexShardSnapshot, authz.Admin)).Methods("GET").Name("GetIndexShardSnapshot") + router.HandleFunc("/internal/index/{index}/shards", handler.mwAuth(handler.handleGetIndexAvailableShards, authz.Admin)).Methods("GET").Name("GetIndexAvailableShards") + router.HandleFunc("/internal/nodes", handler.mwAuth(handler.handleGetNodes, authz.Admin)).Methods("GET").Name("GetNodes") + router.HandleFunc("/internal/shards/max", handler.mwAuth(handler.handleGetShardsMax, authz.Admin)).Methods("GET").Name("GetShardsMax") // TODO: deprecate, but it's being used by the client + router.HandleFunc("/internal/ingest/{index}", handler.mwAuth(handler.handlePostIngestData, authz.Admin)).Methods("POST").Name("PostIngestData") + router.HandleFunc("/internal/ingest/{index}/node", handler.mwAuth(handler.handlePostIngestNode, authz.Admin)).Methods("POST").Name("PostIngestNode") - router.HandleFunc("/internal/schema", handler.handleIngestSchema).Methods("POST").Name("PostIngestSchema") - router.HandleFunc("/internal/translate/index/{index}/keys/find", handler.handleFindIndexKeys).Methods("POST").Name("FindIndexKeys") - router.HandleFunc("/internal/translate/index/{index}/keys/create", handler.handleCreateIndexKeys).Methods("POST").Name("CreateIndexKeys") - router.HandleFunc("/internal/translate/index/{index}/{partition}", handler.handlePostTranslateIndexDB).Methods("POST").Name("PostTranslateIndexDB") - router.HandleFunc("/internal/translate/field/{index}/{field}", handler.handlePostTranslateFieldDB).Methods("POST").Name("PostTranslateFieldDB") - router.HandleFunc("/internal/translate/field/{index}/{field}/keys/find", handler.handleFindFieldKeys).Methods("POST").Name("FindFieldKeys") - router.HandleFunc("/internal/translate/field/{index}/{field}/keys/create", handler.handleCreateFieldKeys).Methods("POST").Name("CreateFieldKeys") - router.HandleFunc("/internal/translate/field/{index}/{field}/keys/like", handler.handleMatchField).Methods("POST").Name("MatchFieldKeys") + router.HandleFunc("/internal/schema", handler.mwAuth(handler.handleIngestSchema, authz.Admin)).Methods("POST").Name("PostIngestSchema") + router.HandleFunc("/internal/translate/index/{index}/keys/find", handler.mwAuth(handler.handleFindIndexKeys, authz.Admin)).Methods("POST").Name("FindIndexKeys") + router.HandleFunc("/internal/translate/index/{index}/keys/create", handler.mwAuth(handler.handleCreateIndexKeys, authz.Admin)).Methods("POST").Name("CreateIndexKeys") + router.HandleFunc("/internal/translate/index/{index}/{partition}", handler.mwAuth(handler.handlePostTranslateIndexDB, authz.Admin)).Methods("POST").Name("PostTranslateIndexDB") + router.HandleFunc("/internal/translate/field/{index}/{field}", handler.mwAuth(handler.handlePostTranslateFieldDB, authz.Admin)).Methods("POST").Name("PostTranslateFieldDB") + router.HandleFunc("/internal/translate/field/{index}/{field}/keys/find", handler.mwAuth(handler.handleFindFieldKeys, authz.Admin)).Methods("POST").Name("FindFieldKeys") + router.HandleFunc("/internal/translate/field/{index}/{field}/keys/create", handler.mwAuth(handler.handleCreateFieldKeys, authz.Admin)).Methods("POST").Name("CreateFieldKeys") + router.HandleFunc("/internal/translate/field/{index}/{field}/keys/like", handler.mwAuth(handler.handleMatchField, authz.Admin)).Methods("POST").Name("MatchFieldKeys") - router.HandleFunc("/internal/idalloc/reserve", handler.handleReserveIDs).Methods("POST").Name("ReserveIDs") - router.HandleFunc("/internal/idalloc/commit", handler.handleCommitIDs).Methods("POST").Name("CommitIDs") - router.HandleFunc("/internal/idalloc/restore", handler.handleRestoreIDAlloc).Methods("POST").Name("RestoreIDAllocData") - router.HandleFunc("/internal/idalloc/reset/{index}", handler.handleResetIDAlloc).Methods("POST").Name("ResetIDAlloc") - router.HandleFunc("/internal/idalloc/data", handler.handleIDAllocData).Methods("GET").Name("IDAllocData") + router.HandleFunc("/internal/idalloc/reserve", handler.mwAuth(handler.handleReserveIDs, authz.Admin)).Methods("POST").Name("ReserveIDs") + router.HandleFunc("/internal/idalloc/commit", handler.mwAuth(handler.handleCommitIDs, authz.Admin)).Methods("POST").Name("CommitIDs") + router.HandleFunc("/internal/idalloc/restore", handler.mwAuth(handler.handleRestoreIDAlloc, authz.Admin)).Methods("POST").Name("RestoreIDAllocData") + router.HandleFunc("/internal/idalloc/reset/{index}", handler.mwAuth(handler.handleResetIDAlloc, authz.Admin)).Methods("POST").Name("ResetIDAlloc") + router.HandleFunc("/internal/idalloc/data", handler.mwAuth(handler.handleIDAllocData, authz.Admin)).Methods("GET").Name("IDAllocData") - router.HandleFunc("/internal/restore/{index}/{shardID}", handler.handlePostRestore).Methods("POST").Name("Restore") + router.HandleFunc("/internal/restore/{index}/{shardID}", handler.mwAuth(handler.handlePostRestore, authz.Admin)).Methods("POST").Name("Restore") // endpoints for collecting cpu profiles from a chosen begin point to // when the client wants to stop. Used for profiling imports that // could be long or short. - router.HandleFunc("/cpu-profile/start", handler.handleCPUProfileStart).Methods("GET").Name("CPUProfileStart") - router.HandleFunc("/cpu-profile/stop", handler.handleCPUProfileStop).Methods("GET").Name("CPUProfileStop") + router.HandleFunc("/cpu-profile/start", handler.mwAuth(handler.handleCPUProfileStart, authz.Admin)).Methods("GET").Name("CPUProfileStart") + router.HandleFunc("/cpu-profile/stop", handler.mwAuth(handler.handleCPUProfileStop, authz.Admin)).Methods("GET").Name("CPUProfileStop") router.HandleFunc("/login", handler.handleLogin).Methods("GET").Name("Login") router.HandleFunc("/logout", handler.handleLogout).Methods("GET").Name("Logout") From e5fa99a5314b065e3f7d8cb4237c2fe6f69920da Mon Sep 17 00:00:00 2001 From: Samir Patel <48686912+54mir@users.noreply.github.com> Date: Tue, 28 Dec 2021 10:08:44 -0500 Subject: [PATCH 08/27] apply mw to handlers --- http/handler.go | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/http/handler.go b/http/handler.go index a83222091..b11bd6f0e 100644 --- a/http/handler.go +++ b/http/handler.go @@ -576,34 +576,6 @@ func (h *Handler) mwAuth(handler http.HandlerFunc, perm authz.Permission) http.H } } -// TODO: DELETE - -// func (h *Handler) isAuthorized(w http.ResponseWriter, r *http.Request, req *pilosa.QueryRequest, index, desiredPermission, endpoint string) bool { -// if h.auth == nil { -// return true -// } -// groups, err := h.auth.Authenticate(w, r) -// if err != nil { -// http.Error(w, errors.Wrap(err, "authenticating").Error(), http.StatusBadRequest) -// return false -// } - -// p, err := h.permissions.GetPermissions(groups, index) -// uinfo := h.auth.GetUserInfo(w, r) -// var query string -// if req != nil { -// query = fmt.Sprintf("%s%s", req.Query, req.SQLQuery) -// } -// h.querylogger.Infof("User ID: %s, User Name: %s, Endpoint: %s, Index: %s, Query: %s, Err: %v", uinfo.UserID, uinfo.UserName, endpoint, index, query, err) -// if err != nil || !authz.IsComparable(p, desiredPermission) { -// w.Header().Add("Content-Type", "text/plain") -// w.WriteHeader(http.StatusForbidden) -// return false -// } - -// return true -// } - // statikHandler implements the http.Handler interface, and responds to // requests for static assets with the appropriate file contents embedded // in a statik filesystem. @@ -892,9 +864,6 @@ func (h *Handler) handleGetStatus(w http.ResponseWriter, r *http.Request) { } func (h *Handler) handleGetInfo(w http.ResponseWriter, r *http.Request) { - // if !h.isAuthorized(w, r, nil, "--", authz.Admin.String(), r.URL.Path) { - // return - // } if !validHeaderAcceptJSON(r.Header) { http.Error(w, "JSON only acceptable response", http.StatusNotAcceptable) @@ -2511,9 +2480,6 @@ func (h *Handler) handlePostClusterResizeRemoveNode(w http.ResponseWriter, r *ht return } - // if !h.isAuthorized(w, r, nil, "--", authz.Admin.String(), r.URL.Path) { - // return - // } // Decode request. var req removeNodeRequest err := json.NewDecoder(r.Body).Decode(&req) @@ -2551,9 +2517,6 @@ type removeNodeResponse struct { // handlePostClusterResizeAbort handles POST /cluster/resize/abort request. func (h *Handler) handlePostClusterResizeAbort(w http.ResponseWriter, r *http.Request) { - // if !h.isAuthorized(w, r, nil, "--", authz.Admin.String(), r.URL.Path) { - // return - // } if !validHeaderAcceptJSON(r.Header) { http.Error(w, "JSON only acceptable response", http.StatusNotAcceptable) From d95d4dac9d0fe874d83d72e569b4eb4aa5301629 Mon Sep 17 00:00:00 2001 From: Samir Patel <48686912+54mir@users.noreply.github.com> Date: Tue, 28 Dec 2021 17:36:52 -0500 Subject: [PATCH 09/27] pass group membership thru context --- http/handler.go | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/http/handler.go b/http/handler.go index b11bd6f0e..86312dc18 100644 --- a/http/handler.go +++ b/http/handler.go @@ -283,6 +283,7 @@ type contextKeyQuery int const ( contextKeyQueryRequest contextKeyQuery = iota contextKeyQueryError + contextKeyGroupMembership ) // addQueryContext puts the results of handler.readQueryRequest into the Context for use by @@ -547,6 +548,7 @@ func (h *Handler) mwAuth(handler http.HandlerFunc, perm authz.Permission) http.H } p, err := h.permissions.GetPermissions(groups, indexName) + //check error uinfo := h.auth.GetUserInfo(w, r) //get query string if applicable @@ -563,13 +565,17 @@ func (h *Handler) mwAuth(handler http.HandlerFunc, perm authz.Permission) http.H perm = authz.Write } } - - h.querylogger.Infof("User ID: %s, User Name: %s, Endpoint: %s, Index: %s, Query: %s, Err: %v", uinfo.UserID, uinfo.UserName, r.URL.Path, indexName, queryString, err) + if perm != authz.Admin { + h.querylogger.Infof("User ID: %s, User Name: %s, Endpoint: %s, Index: %s, Query: %s, Err: %v", uinfo.UserID, uinfo.UserName, r.URL.Path, indexName, queryString, err) + } if err != nil || !authz.IsComparable(p, perm.String()) { w.Header().Add("Content-Type", "text/plain") w.WriteHeader(http.StatusForbidden) return } + + ctx := context.WithValue(r.Context(), contextKeyGroupMembership, groups) + handler.ServeHTTP(w, r.WithContext(ctx)) } handler.ServeHTTP(w, r) @@ -744,6 +750,15 @@ func headerAcceptRoaringRow(header http.Header) bool { return false } +//WIP +func (h *Handler) filterResponse(schema []*pilosa.IndexInfo, g []authn.Group) { + // if h.auth != nil{ + // indexes := h.permissions.GetAuthorizedIndexList(g, authz.Read.String()) + + // } + +} + // handleGetSchema handles GET /schema requests. func (h *Handler) handleGetSchema(w http.ResponseWriter, r *http.Request) { if !validHeaderAcceptJSON(r.Header) { @@ -760,6 +775,9 @@ func (h *Handler) handleGetSchema(w http.ResponseWriter, r *http.Request) { h.logger.Printf("getting schema error: %s", err) } + groups := r.Context().Value(contextKeyGroupMembership) + h.filterResponse(schema, groups.([]authn.Group)) + if err := json.NewEncoder(w).Encode(pilosa.Schema{Indexes: schema}); err != nil { h.logger.Errorf("write schema response error: %s", err) } From 77509003105295fbb9fad325303724ca372c9b00 Mon Sep 17 00:00:00 2001 From: Samir Patel <48686912+54mir@users.noreply.github.com> Date: Wed, 29 Dec 2021 13:18:42 -0500 Subject: [PATCH 10/27] more logging --- http/handler.go | 27 +++++++++++++++++++-------- server/server.go | 3 +++ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/http/handler.go b/http/handler.go index 86312dc18..352604a00 100644 --- a/http/handler.go +++ b/http/handler.go @@ -548,7 +548,8 @@ func (h *Handler) mwAuth(handler http.HandlerFunc, perm authz.Permission) http.H } p, err := h.permissions.GetPermissions(groups, indexName) - //check error + // err is being checked later, after logging + uinfo := h.auth.GetUserInfo(w, r) //get query string if applicable @@ -565,7 +566,10 @@ func (h *Handler) mwAuth(handler http.HandlerFunc, perm authz.Permission) http.H perm = authz.Write } } - if perm != authz.Admin { + + queryString = strings.Replace(queryString, "\n", "", -1) + + if r.Method == "POST" { h.querylogger.Infof("User ID: %s, User Name: %s, Endpoint: %s, Index: %s, Query: %s, Err: %v", uinfo.UserID, uinfo.UserName, r.URL.Path, indexName, queryString, err) } if err != nil || !authz.IsComparable(p, perm.String()) { @@ -751,13 +755,19 @@ func headerAcceptRoaringRow(header http.Header) bool { } //WIP -func (h *Handler) filterResponse(schema []*pilosa.IndexInfo, g []authn.Group) { - // if h.auth != nil{ - // indexes := h.permissions.GetAuthorizedIndexList(g, authz.Read.String()) +// func (h *Handler) filterResponse(r *http.Request, schema []*pilosa.IndexInfo) { +// if h.auth != nil { +// groups := r.Context().Value(contextKeyGroupMembership) - // } +// // indexes := h.permissions.GetAuthorizedIndexList(g, authz.Read.String()) +// for _, s := range schema { +// h.querylogger.Infof(s.Name) -} +// } + +// } + +// } // handleGetSchema handles GET /schema requests. func (h *Handler) handleGetSchema(w http.ResponseWriter, r *http.Request) { @@ -776,7 +786,8 @@ func (h *Handler) handleGetSchema(w http.ResponseWriter, r *http.Request) { } groups := r.Context().Value(contextKeyGroupMembership) - h.filterResponse(schema, groups.([]authn.Group)) + h.querylogger.Infof("groups: %+v", groups) + // h.filterResponse(r, schema) if err := json.NewEncoder(w).Encode(pilosa.Schema{Indexes: schema}); err != nil { h.logger.Errorf("write schema response error: %s", err) diff --git a/server/server.go b/server/server.go index 577cef578..a322e2938 100644 --- a/server/server.go +++ b/server/server.go @@ -549,6 +549,9 @@ func (m *Command) SetupServer() error { if err != nil { return errors.Wrap(err, "setting up querylogger") } + + m.querylogger.Infof("Group with admin level access: %v", p.Admin) + m.querylogger.Infof("Permissions: %+v", p.Permissions) } m.Handler, err = http.NewHandler( From 17679eb924dcf66e9b07157401ec6198f03d6ca4 Mon Sep 17 00:00:00 2001 From: reesporte Date: Wed, 29 Dec 2021 13:38:11 -0600 Subject: [PATCH 11/27] create a Permissions type makes it nice to say p.Satisfies(otherPerm) --- authz/authorization.go | 92 +++++++++++++++---------------------- authz/authorization_test.go | 50 ++++++++++---------- http/handler.go | 2 +- 3 files changed, 63 insertions(+), 81 deletions(-) diff --git a/authz/authorization.go b/authz/authorization.go index 5dc9ce765..727d4db3f 100644 --- a/authz/authorization.go +++ b/authz/authorization.go @@ -25,19 +25,34 @@ import ( ) type GroupPermissions struct { - Permissions map[string]map[string]string `yaml:"user-groups"` - Admin string `yaml:"admin"` + Permissions map[string]map[string]Permission `yaml:"user-groups"` + Admin string `yaml:"admin"` } -type Permission int64 +type Permission string const ( - None Permission = iota - Read - Write - Admin + None Permission = "" + Read Permission = "read" + Write Permission = "write" + Admin Permission = "admin" ) +// Satisfies returns whether `p` satisfies the permissions required by `b` +func (p Permission) Satisfies(b Permission) bool { + switch p { + case "": + return b == "" + case "read": + return b == "" || b == "read" + case "write": + return b == "" || b == "read" || b == "write" + case "admin": + return b == "" || b == "read" || b == "write" || b == "admin" + } + return false +} + func (p *GroupPermissions) ReadPermissionsFile(permsFile io.Reader) (err error) { permsData, err := ioutil.ReadAll(permsFile) @@ -53,19 +68,18 @@ func (p *GroupPermissions) ReadPermissionsFile(permsFile io.Reader) (err error) return } -func (p *GroupPermissions) GetPermissions(groups []authn.Group, index string) (permission string, errors error) { - +func (p *GroupPermissions) GetPermissions(groups []authn.Group, index string) (permission Permission, errors error) { if admin := p.IsAdmin(groups); admin { - return "admin", nil + return Admin, nil } - allPermissions := map[string]bool{ - "write": false, - "read": false, + allPermissions := map[Permission]bool{ + Write: false, + Read: false, } if len(groups) == 0 { - return "", fmt.Errorf("user is not part of any groups in identity provider") + return None, fmt.Errorf("user is not part of any groups in identity provider") } var groupsDenied []string @@ -74,7 +88,7 @@ func (p *GroupPermissions) GetPermissions(groups []authn.Group, index string) (p if perm, ok := p.Permissions[group.GroupID][index]; ok { allPermissions[perm] = true } else { - return "", fmt.Errorf("user %s does not have permission to index %s", group.UserID, index) + return None, fmt.Errorf("user %s does not have permission to index %s", group.UserID, index) } } else { groupsDenied = append(groupsDenied, group.GroupID) @@ -82,15 +96,15 @@ func (p *GroupPermissions) GetPermissions(groups []authn.Group, index string) (p } if len(groupsDenied) == len(groups) { - return "", fmt.Errorf("group(s) %s does not have permission to FeatureBase", groupsDenied) + return None, fmt.Errorf("group(s) %s does not have permission to FeatureBase", groupsDenied) } - if allPermissions["write"] { - return "write", nil - } else if allPermissions["read"] { - return "read", nil + if allPermissions[Write] { + return Write, nil + } else if allPermissions[Read] { + return Read, nil } else { - return "", fmt.Errorf("no permissions found") + return None, fmt.Errorf("no permissions found") } } @@ -103,7 +117,7 @@ func (p *GroupPermissions) IsAdmin(groups []authn.Group) bool { return false } -func (p *GroupPermissions) GetAuthorizedIndexList(groups []authn.Group, desiredPermission string) (indexList []string) { +func (p *GroupPermissions) GetAuthorizedIndexList(groups []authn.Group, desiredPermission Permission) (indexList []string) { // if user is admin, find all indexes in permissions file and return them if admin := p.IsAdmin(groups); admin { for groupId := range p.Permissions { @@ -117,9 +131,7 @@ func (p *GroupPermissions) GetAuthorizedIndexList(groups []authn.Group, desiredP for _, group := range groups { if _, ok := p.Permissions[group.GroupID]; ok { for index, permission := range p.Permissions[group.GroupID] { - if permission == desiredPermission { - indexList = append(indexList, index) - } else if permission == "write" && desiredPermission == "read" { + if permission >= desiredPermission { indexList = append(indexList, index) } } @@ -127,33 +139,3 @@ func (p *GroupPermissions) GetAuthorizedIndexList(groups []authn.Group, desiredP } return indexList } - -func IsComparable(from, to string) bool { - switch from { - case "admin": - return true - case "write": - if to == "write" || to == "read" { - return true - } - case "read": - if to == "read" { - return true - } - } - return false -} - -func (p Permission) String() string { - switch p { - case Read: - return "read" - case Write: - return "write" - case Admin: - return "admin" - case None: - return "none" - } - return "unknown" -} diff --git a/authz/authorization_test.go b/authz/authorization_test.go index bfda894a9..b8b9f5491 100644 --- a/authz/authorization_test.go +++ b/authz/authorization_test.go @@ -40,16 +40,16 @@ admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"` admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"` singlePermission := authz.GroupPermissions{ - Permissions: map[string]map[string]string{ - "dca35310-ecda-4f23-86cd-876aee55906b": {"test": "read"}, + Permissions: map[string]map[string]authz.Permission{ + "dca35310-ecda-4f23-86cd-876aee55906b": {"test": authz.Read}, }, Admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe", } multiPermission := authz.GroupPermissions{ - Permissions: map[string]map[string]string{ - "dca35310-ecda-4f23-86cd-876aee55906b": {"test": "read", "test2": "write"}, - "dca35310-ecda-4f23-86cd-876aee559900": {"test": "write"}}, + Permissions: map[string]map[string]authz.Permission{ + "dca35310-ecda-4f23-86cd-876aee55906b": {"test": authz.Read, "test2": authz.Write}, + "dca35310-ecda-4f23-86cd-876aee559900": {"test": authz.Write}}, Admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe", } @@ -123,56 +123,56 @@ admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"` yamlData string groups []authn.Group index string - userAccess string + userAccess authz.Permission err string }{ { permissions1, groupsList1, "test", - "", + authz.None, "user is not part of any groups in identity provider", }, { permissions1, groupsList3, "test1", - "", + authz.None, "does not have permission to index", }, { permissions2, groupsList2, "test", - "", + authz.None, "does not have permission to FeatureBase", }, { permissions1, groupsList3, "test", - "read", + authz.Read, "", }, { permissions2, groupsList3, "test", - "write", + authz.Write, "", }, { permissions3, groupsList4, "test", - "admin", + authz.Admin, "", }, { permissions4, groupsList3, "test", - "", + authz.None, "no permissions found", }, } @@ -214,8 +214,8 @@ func TestAuth_IsAdmin(t *testing.T) { } groupPermissions := authz.GroupPermissions{ - Permissions: map[string]map[string]string{ - "dca35310-ecda-4f23-86cd-876aee55906b": {"test": "write"}, + Permissions: map[string]map[string]authz.Permission{ + "dca35310-ecda-4f23-86cd-876aee55906b": {"test": authz.Write}, }, Admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe", } @@ -259,13 +259,13 @@ func TestAuth_GetAuthorizedIndexList(t *testing.T) { } p := authz.GroupPermissions{ - Permissions: map[string]map[string]string{ + Permissions: map[string]map[string]authz.Permission{ "dca35310-ecda-4f23-86cd-876aee55906b": { - "test1": "read", - "test2": "write", + "test1": authz.Read, + "test2": authz.Write, }, "dca35310-ecda-4f23-86cd-876aee559900": { - "test3": "read", + "test3": authz.Read, }, }, Admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe", @@ -273,32 +273,32 @@ func TestAuth_GetAuthorizedIndexList(t *testing.T) { tests := []struct { groups []authn.Group - permission string + permission authz.Permission output []string }{ { group1, - "read", + authz.Read, []string{"test1", "test2"}, }, { group1, - "write", + authz.Write, []string{"test2"}, }, { group3, - "write", + authz.Write, nil, }, { group2, - "read", + authz.Read, []string{"test1", "test2", "test3"}, }, { group2, - "write", + authz.Write, []string{"test1", "test2", "test3"}, }, } diff --git a/http/handler.go b/http/handler.go index 86312dc18..661f04b4e 100644 --- a/http/handler.go +++ b/http/handler.go @@ -568,7 +568,7 @@ func (h *Handler) mwAuth(handler http.HandlerFunc, perm authz.Permission) http.H if perm != authz.Admin { h.querylogger.Infof("User ID: %s, User Name: %s, Endpoint: %s, Index: %s, Query: %s, Err: %v", uinfo.UserID, uinfo.UserName, r.URL.Path, indexName, queryString, err) } - if err != nil || !authz.IsComparable(p, perm.String()) { + if err != nil || !p.Satisfies(perm.String()) { w.Header().Add("Content-Type", "text/plain") w.WriteHeader(http.StatusForbidden) return From cf86be16c15ce249b13f07e5f2727f90f225fcd8 Mon Sep 17 00:00:00 2001 From: Samir Patel <48686912+54mir@users.noreply.github.com> Date: Wed, 29 Dec 2021 14:41:31 -0500 Subject: [PATCH 12/27] add authN only middleware for /internal --- http/handler.go | 203 +++++++++++++++++++++++++++--------------------- 1 file changed, 114 insertions(+), 89 deletions(-) diff --git a/http/handler.go b/http/handler.go index 352604a00..6fb3afa45 100644 --- a/http/handler.go +++ b/http/handler.go @@ -388,8 +388,8 @@ var latticeRoutes = []string{"/tables", "/query", "/querybuilder", "/signin"} // // newRouter creates a new mux http router. func newRouter(handler *Handler) http.Handler { router := mux.NewRouter() - router.HandleFunc("/cluster/resize/abort", handler.mwAuth(handler.handlePostClusterResizeAbort, authz.Admin)).Methods("POST").Name("PostClusterResizeAbort") - router.HandleFunc("/cluster/resize/remove-node", handler.mwAuth(handler.handlePostClusterResizeRemoveNode, authz.Admin)).Methods("POST").Name("PostClusterResizeRemoveNode") + router.HandleFunc("/cluster/resize/abort", handler.chkAuthZ(handler.handlePostClusterResizeAbort, authz.Admin)).Methods("POST").Name("PostClusterResizeAbort") + router.HandleFunc("/cluster/resize/remove-node", handler.chkAuthZ(handler.handlePostClusterResizeRemoveNode, authz.Admin)).Methods("POST").Name("PostClusterResizeRemoveNode") // TODO: figure out how to protect these if needed router.PathPrefix("/debug/pprof/").Handler(http.DefaultServeMux).Methods("GET") @@ -397,88 +397,88 @@ func newRouter(handler *Handler) http.Handler { router.Handle("/debug/vars", expvar.Handler()).Methods("GET") router.Handle("/metrics", promhttp.Handler()) - router.HandleFunc("/metrics.json", handler.mwAuth(handler.handleGetMetricsJSON, authz.Admin)).Methods("GET").Name("GetMetricsJSON") - router.HandleFunc("/export", handler.mwAuth(handler.handleGetExport, authz.Read)).Methods("GET").Name("GetExport") - router.HandleFunc("/import-atomic-record", handler.mwAuth(handler.handlePostImportAtomicRecord, authz.Admin)).Methods("POST").Name("PostImportAtomicRecord") - router.HandleFunc("/index", handler.mwAuth(handler.handleGetIndexes, authz.Read)).Methods("GET").Name("GetIndexes") - router.HandleFunc("/index", handler.mwAuth(handler.handlePostIndex, authz.Admin)).Methods("POST").Name("PostIndex") - router.HandleFunc("/index/", handler.mwAuth(handler.handlePostIndex, authz.Admin)).Methods("POST").Name("PostIndex") - router.HandleFunc("/index/{index}", handler.mwAuth(handler.handleGetIndex, authz.Read)).Methods("GET").Name("GetIndex") - router.HandleFunc("/index/{index}", handler.mwAuth(handler.handlePostIndex, authz.Admin)).Methods("POST").Name("PostIndex") - router.HandleFunc("/index/{index}", handler.mwAuth(handler.handleDeleteIndex, authz.Admin)).Methods("DELETE").Name("DeleteIndex") - //router.HandleFunc("/index/{index}/field", handler.mwAuth(handler.handleGetFields, authz.Read)).Methods("GET") // Not implemented. - router.HandleFunc("/index/{index}/field", handler.mwAuth(handler.handlePostField, authz.Write)).Methods("POST").Name("PostField") - router.HandleFunc("/index/{index}/field/", handler.mwAuth(handler.handlePostField, authz.Write)).Methods("POST").Name("PostField") - router.HandleFunc("/index/{index}/field/{field}", handler.mwAuth(handler.handlePostField, authz.Write)).Methods("POST").Name("PostField") - router.HandleFunc("/index/{index}/field/{field}", handler.mwAuth(handler.handleDeleteField, authz.Write)).Methods("DELETE").Name("DeleteField") - router.HandleFunc("/index/{index}/field/{field}/import", handler.mwAuth(handler.handlePostImport, authz.Read)).Methods("POST").Name("PostImport") - router.HandleFunc("/index/{index}/field/{field}/mutex-check", handler.mwAuth(handler.handleGetMutexCheck, authz.Read)).Methods("GET").Name("GetMutexCheck") - router.HandleFunc("/index/{index}/field/{field}/import-roaring/{shard}", handler.mwAuth(handler.handlePostImportRoaring, authz.Read)).Methods("POST").Name("PostImportRoaring") - router.HandleFunc("/index/{index}/query", handler.mwAuth(handler.handlePostQuery, authz.Read)).Methods("POST").Name("PostQuery") - router.HandleFunc("/info", handler.mwAuth(handler.handleGetInfo, authz.Admin)).Methods("GET").Name("GetInfo") - router.HandleFunc("/recalculate-caches", handler.mwAuth(handler.handleRecalculateCaches, authz.Admin)).Methods("POST").Name("RecalculateCaches") - router.HandleFunc("/schema", handler.mwAuth(handler.handleGetSchema, authz.Read)).Methods("GET").Name("GetSchema") - router.HandleFunc("/schema/details", handler.mwAuth(handler.handleGetSchemaDetails, authz.Read)).Methods("GET").Name("GetSchemaDetails") - router.HandleFunc("/schema", handler.mwAuth(handler.handlePostSchema, authz.Admin)).Methods("POST").Name("PostSchema") - router.HandleFunc("/status", handler.mwAuth(handler.handleGetStatus, authz.Read)).Methods("GET").Name("GetStatus") - router.HandleFunc("/transaction", handler.mwAuth(handler.handlePostTransaction, authz.Read)).Methods("POST").Name("PostTransaction") - router.HandleFunc("/transaction/", handler.mwAuth(handler.handlePostTransaction, authz.Read)).Methods("POST").Name("PostTransaction") - router.HandleFunc("/transaction/{id}", handler.mwAuth(handler.handleGetTransaction, authz.Read)).Methods("GET").Name("GetTransaction") - router.HandleFunc("/transaction/{id}", handler.mwAuth(handler.handlePostTransaction, authz.Read)).Methods("POST").Name("PostTransaction") - router.HandleFunc("/transaction/{id}/finish", handler.mwAuth(handler.handlePostFinishTransaction, authz.Read)).Methods("POST").Name("PostFinishTransaction") - router.HandleFunc("/transactions", handler.mwAuth(handler.handleGetTransactions, authz.Read)).Methods("GET").Name("GetTransactions") - router.HandleFunc("/queries", handler.mwAuth(handler.handleGetActiveQueries, authz.Read)).Methods("GET").Name("GetActiveQueries") - router.HandleFunc("/query-history", handler.mwAuth(handler.handleGetPastQueries, authz.Read)).Methods("GET").Name("GetPastQueries") - router.HandleFunc("/version", handler.mwAuth(handler.handleGetVersion, authz.Read)).Methods("GET").Name("GetVersion") + router.HandleFunc("/metrics.json", handler.chkAuthZ(handler.handleGetMetricsJSON, authz.Admin)).Methods("GET").Name("GetMetricsJSON") + router.HandleFunc("/export", handler.chkAuthZ(handler.handleGetExport, authz.Read)).Methods("GET").Name("GetExport") + router.HandleFunc("/import-atomic-record", handler.chkAuthZ(handler.handlePostImportAtomicRecord, authz.Admin)).Methods("POST").Name("PostImportAtomicRecord") + router.HandleFunc("/index", handler.chkAuthZ(handler.handleGetIndexes, authz.Read)).Methods("GET").Name("GetIndexes") + router.HandleFunc("/index", handler.chkAuthZ(handler.handlePostIndex, authz.Admin)).Methods("POST").Name("PostIndex") + router.HandleFunc("/index/", handler.chkAuthZ(handler.handlePostIndex, authz.Admin)).Methods("POST").Name("PostIndex") + router.HandleFunc("/index/{index}", handler.chkAuthZ(handler.handleGetIndex, authz.Read)).Methods("GET").Name("GetIndex") + router.HandleFunc("/index/{index}", handler.chkAuthZ(handler.handlePostIndex, authz.Admin)).Methods("POST").Name("PostIndex") + router.HandleFunc("/index/{index}", handler.chkAuthZ(handler.handleDeleteIndex, authz.Admin)).Methods("DELETE").Name("DeleteIndex") + //router.HandleFunc("/index/{index}/field", handler.chkAuthZ(handler.handleGetFields, authz.Read)).Methods("GET") // Not implemented. + router.HandleFunc("/index/{index}/field", handler.chkAuthZ(handler.handlePostField, authz.Write)).Methods("POST").Name("PostField") + router.HandleFunc("/index/{index}/field/", handler.chkAuthZ(handler.handlePostField, authz.Write)).Methods("POST").Name("PostField") + router.HandleFunc("/index/{index}/field/{field}", handler.chkAuthZ(handler.handlePostField, authz.Write)).Methods("POST").Name("PostField") + router.HandleFunc("/index/{index}/field/{field}", handler.chkAuthZ(handler.handleDeleteField, authz.Write)).Methods("DELETE").Name("DeleteField") + router.HandleFunc("/index/{index}/field/{field}/import", handler.chkAuthZ(handler.handlePostImport, authz.Read)).Methods("POST").Name("PostImport") + router.HandleFunc("/index/{index}/field/{field}/mutex-check", handler.chkAuthZ(handler.handleGetMutexCheck, authz.Read)).Methods("GET").Name("GetMutexCheck") + router.HandleFunc("/index/{index}/field/{field}/import-roaring/{shard}", handler.chkAuthZ(handler.handlePostImportRoaring, authz.Read)).Methods("POST").Name("PostImportRoaring") + router.HandleFunc("/index/{index}/query", handler.chkAuthZ(handler.handlePostQuery, authz.Read)).Methods("POST").Name("PostQuery") + router.HandleFunc("/info", handler.chkAuthZ(handler.handleGetInfo, authz.Admin)).Methods("GET").Name("GetInfo") + router.HandleFunc("/recalculate-caches", handler.chkAuthZ(handler.handleRecalculateCaches, authz.Admin)).Methods("POST").Name("RecalculateCaches") + router.HandleFunc("/schema", handler.chkAuthZ(handler.handleGetSchema, authz.Read)).Methods("GET").Name("GetSchema") + router.HandleFunc("/schema/details", handler.chkAuthZ(handler.handleGetSchemaDetails, authz.Read)).Methods("GET").Name("GetSchemaDetails") + router.HandleFunc("/schema", handler.chkAuthZ(handler.handlePostSchema, authz.Admin)).Methods("POST").Name("PostSchema") + router.HandleFunc("/status", handler.chkAuthZ(handler.handleGetStatus, authz.Read)).Methods("GET").Name("GetStatus") + router.HandleFunc("/transaction", handler.chkAuthZ(handler.handlePostTransaction, authz.Read)).Methods("POST").Name("PostTransaction") + router.HandleFunc("/transaction/", handler.chkAuthZ(handler.handlePostTransaction, authz.Read)).Methods("POST").Name("PostTransaction") + router.HandleFunc("/transaction/{id}", handler.chkAuthZ(handler.handleGetTransaction, authz.Read)).Methods("GET").Name("GetTransaction") + router.HandleFunc("/transaction/{id}", handler.chkAuthZ(handler.handlePostTransaction, authz.Read)).Methods("POST").Name("PostTransaction") + router.HandleFunc("/transaction/{id}/finish", handler.chkAuthZ(handler.handlePostFinishTransaction, authz.Read)).Methods("POST").Name("PostFinishTransaction") + router.HandleFunc("/transactions", handler.chkAuthZ(handler.handleGetTransactions, authz.Read)).Methods("GET").Name("GetTransactions") + router.HandleFunc("/queries", handler.chkAuthZ(handler.handleGetActiveQueries, authz.Read)).Methods("GET").Name("GetActiveQueries") + router.HandleFunc("/query-history", handler.chkAuthZ(handler.handleGetPastQueries, authz.Read)).Methods("GET").Name("GetPastQueries") + router.HandleFunc("/version", handler.chkAuthZ(handler.handleGetVersion, authz.Read)).Methods("GET").Name("GetVersion") // /ui endpoints are for UI use; they may change at any time. - router.HandleFunc("/ui/usage", handler.mwAuth(handler.handleGetUsage, authz.Read)).Methods("GET").Name("GetUsage") - router.HandleFunc("/ui/transaction", handler.mwAuth(handler.handleGetTransactionList, authz.Read)).Methods("GET").Name("GetTransactionList") - router.HandleFunc("/ui/transaction/", handler.mwAuth(handler.handleGetTransactionList, authz.Read)).Methods("GET").Name("GetTransactionList") - router.HandleFunc("/ui/shard-distribution", handler.mwAuth(handler.handleGetShardDistribution, authz.Read)).Methods("GET").Name("GetShardDistribution") + router.HandleFunc("/ui/usage", handler.chkAuthZ(handler.handleGetUsage, authz.Read)).Methods("GET").Name("GetUsage") + router.HandleFunc("/ui/transaction", handler.chkAuthZ(handler.handleGetTransactionList, authz.Read)).Methods("GET").Name("GetTransactionList") + router.HandleFunc("/ui/transaction/", handler.chkAuthZ(handler.handleGetTransactionList, authz.Read)).Methods("GET").Name("GetTransactionList") + router.HandleFunc("/ui/shard-distribution", handler.chkAuthZ(handler.handleGetShardDistribution, authz.Read)).Methods("GET").Name("GetShardDistribution") // /internal endpoints are for internal use only; they may change at any time. // DO NOT rely on these for external applications! - router.HandleFunc("/internal/cluster/message", handler.mwAuth(handler.handlePostClusterMessage, authz.Admin)).Methods("POST").Name("PostClusterMessage") - router.HandleFunc("/internal/fragment/block/data", handler.mwAuth(handler.handleGetFragmentBlockData, authz.Admin)).Methods("GET").Name("GetFragmentBlockData") - router.HandleFunc("/internal/fragment/blocks", handler.mwAuth(handler.handleGetFragmentBlocks, authz.Admin)).Methods("GET").Name("GetFragmentBlocks") - router.HandleFunc("/internal/fragment/data", handler.mwAuth(handler.handleGetFragmentData, authz.Admin)).Methods("GET").Name("GetFragmentData") - router.HandleFunc("/internal/fragment/nodes", handler.mwAuth(handler.handleGetFragmentNodes, authz.Admin)).Methods("GET").Name("GetFragmentNodes") - router.HandleFunc("/internal/partition/nodes", handler.mwAuth(handler.handleGetPartitionNodes, authz.Admin)).Methods("GET").Name("GetPartitionNodes") - router.HandleFunc("/internal/translate/data", handler.mwAuth(handler.handleGetTranslateData, authz.Admin)).Methods("GET").Name("GetTranslateData") - router.HandleFunc("/internal/translate/data", handler.mwAuth(handler.handlePostTranslateData, authz.Admin)).Methods("POST").Name("PostTranslateData") - router.HandleFunc("/internal/translate/keys", handler.mwAuth(handler.handlePostTranslateKeys, authz.Admin)).Methods("POST").Name("PostTranslateKeys") - router.HandleFunc("/internal/translate/ids", handler.mwAuth(handler.handlePostTranslateIDs, authz.Admin)).Methods("POST").Name("PostTranslateIDs") - router.HandleFunc("/internal/index/{index}/field/{field}/mutex-check", handler.mwAuth(handler.handleInternalGetMutexCheck, authz.Admin)).Methods("GET").Name("InternalGetMutexCheck") - router.HandleFunc("/internal/index/{index}/field/{field}/remote-available-shards/{shardID}", handler.mwAuth(handler.handleDeleteRemoteAvailableShard, authz.Admin)).Methods("DELETE") - router.HandleFunc("/internal/index/{index}/shard/{shard}/snapshot", handler.mwAuth(handler.handleGetIndexShardSnapshot, authz.Admin)).Methods("GET").Name("GetIndexShardSnapshot") - router.HandleFunc("/internal/index/{index}/shards", handler.mwAuth(handler.handleGetIndexAvailableShards, authz.Admin)).Methods("GET").Name("GetIndexAvailableShards") - router.HandleFunc("/internal/nodes", handler.mwAuth(handler.handleGetNodes, authz.Admin)).Methods("GET").Name("GetNodes") - router.HandleFunc("/internal/shards/max", handler.mwAuth(handler.handleGetShardsMax, authz.Admin)).Methods("GET").Name("GetShardsMax") // TODO: deprecate, but it's being used by the client - router.HandleFunc("/internal/ingest/{index}", handler.mwAuth(handler.handlePostIngestData, authz.Admin)).Methods("POST").Name("PostIngestData") - router.HandleFunc("/internal/ingest/{index}/node", handler.mwAuth(handler.handlePostIngestNode, authz.Admin)).Methods("POST").Name("PostIngestNode") + router.HandleFunc("/internal/cluster/message", handler.chkAuthN(handler.handlePostClusterMessage)).Methods("POST").Name("PostClusterMessage") + router.HandleFunc("/internal/fragment/block/data", handler.chkAuthN(handler.handleGetFragmentBlockData)).Methods("GET").Name("GetFragmentBlockData") + router.HandleFunc("/internal/fragment/blocks", handler.chkAuthN(handler.handleGetFragmentBlocks)).Methods("GET").Name("GetFragmentBlocks") + router.HandleFunc("/internal/fragment/data", handler.chkAuthN(handler.handleGetFragmentData)).Methods("GET").Name("GetFragmentData") + router.HandleFunc("/internal/fragment/nodes", handler.chkAuthN(handler.handleGetFragmentNodes)).Methods("GET").Name("GetFragmentNodes") + router.HandleFunc("/internal/partition/nodes", handler.chkAuthN(handler.handleGetPartitionNodes)).Methods("GET").Name("GetPartitionNodes") + router.HandleFunc("/internal/translate/data", handler.chkAuthN(handler.handleGetTranslateData)).Methods("GET").Name("GetTranslateData") + router.HandleFunc("/internal/translate/data", handler.chkAuthN(handler.handlePostTranslateData)).Methods("POST").Name("PostTranslateData") + router.HandleFunc("/internal/translate/keys", handler.chkAuthN(handler.handlePostTranslateKeys)).Methods("POST").Name("PostTranslateKeys") + router.HandleFunc("/internal/translate/ids", handler.chkAuthN(handler.handlePostTranslateIDs)).Methods("POST").Name("PostTranslateIDs") + router.HandleFunc("/internal/index/{index}/field/{field}/mutex-check", handler.chkAuthN(handler.handleInternalGetMutexCheck)).Methods("GET").Name("InternalGetMutexCheck") + router.HandleFunc("/internal/index/{index}/field/{field}/remote-available-shards/{shardID}", handler.chkAuthN(handler.handleDeleteRemoteAvailableShard)).Methods("DELETE") + router.HandleFunc("/internal/index/{index}/shard/{shard}/snapshot", handler.chkAuthN(handler.handleGetIndexShardSnapshot)).Methods("GET").Name("GetIndexShardSnapshot") + router.HandleFunc("/internal/index/{index}/shards", handler.chkAuthN(handler.handleGetIndexAvailableShards)).Methods("GET").Name("GetIndexAvailableShards") + router.HandleFunc("/internal/nodes", handler.chkAuthN(handler.handleGetNodes)).Methods("GET").Name("GetNodes") + router.HandleFunc("/internal/shards/max", handler.chkAuthN(handler.handleGetShardsMax)).Methods("GET").Name("GetShardsMax") // TODO: deprecate, but it's being used by the client + router.HandleFunc("/internal/ingest/{index}", handler.chkAuthN(handler.handlePostIngestData)).Methods("POST").Name("PostIngestData") + router.HandleFunc("/internal/ingest/{index}/node", handler.chkAuthN(handler.handlePostIngestNode)).Methods("POST").Name("PostIngestNode") - router.HandleFunc("/internal/schema", handler.mwAuth(handler.handleIngestSchema, authz.Admin)).Methods("POST").Name("PostIngestSchema") - router.HandleFunc("/internal/translate/index/{index}/keys/find", handler.mwAuth(handler.handleFindIndexKeys, authz.Admin)).Methods("POST").Name("FindIndexKeys") - router.HandleFunc("/internal/translate/index/{index}/keys/create", handler.mwAuth(handler.handleCreateIndexKeys, authz.Admin)).Methods("POST").Name("CreateIndexKeys") - router.HandleFunc("/internal/translate/index/{index}/{partition}", handler.mwAuth(handler.handlePostTranslateIndexDB, authz.Admin)).Methods("POST").Name("PostTranslateIndexDB") - router.HandleFunc("/internal/translate/field/{index}/{field}", handler.mwAuth(handler.handlePostTranslateFieldDB, authz.Admin)).Methods("POST").Name("PostTranslateFieldDB") - router.HandleFunc("/internal/translate/field/{index}/{field}/keys/find", handler.mwAuth(handler.handleFindFieldKeys, authz.Admin)).Methods("POST").Name("FindFieldKeys") - router.HandleFunc("/internal/translate/field/{index}/{field}/keys/create", handler.mwAuth(handler.handleCreateFieldKeys, authz.Admin)).Methods("POST").Name("CreateFieldKeys") - router.HandleFunc("/internal/translate/field/{index}/{field}/keys/like", handler.mwAuth(handler.handleMatchField, authz.Admin)).Methods("POST").Name("MatchFieldKeys") + router.HandleFunc("/internal/schema", handler.chkAuthN(handler.handleIngestSchema)).Methods("POST").Name("PostIngestSchema") + router.HandleFunc("/internal/translate/index/{index}/keys/find", handler.chkAuthN(handler.handleFindIndexKeys)).Methods("POST").Name("FindIndexKeys") + router.HandleFunc("/internal/translate/index/{index}/keys/create", handler.chkAuthN(handler.handleCreateIndexKeys)).Methods("POST").Name("CreateIndexKeys") + router.HandleFunc("/internal/translate/index/{index}/{partition}", handler.chkAuthN(handler.handlePostTranslateIndexDB)).Methods("POST").Name("PostTranslateIndexDB") + router.HandleFunc("/internal/translate/field/{index}/{field}", handler.chkAuthN(handler.handlePostTranslateFieldDB)).Methods("POST").Name("PostTranslateFieldDB") + router.HandleFunc("/internal/translate/field/{index}/{field}/keys/find", handler.chkAuthN(handler.handleFindFieldKeys)).Methods("POST").Name("FindFieldKeys") + router.HandleFunc("/internal/translate/field/{index}/{field}/keys/create", handler.chkAuthN(handler.handleCreateFieldKeys)).Methods("POST").Name("CreateFieldKeys") + router.HandleFunc("/internal/translate/field/{index}/{field}/keys/like", handler.chkAuthN(handler.handleMatchField)).Methods("POST").Name("MatchFieldKeys") - router.HandleFunc("/internal/idalloc/reserve", handler.mwAuth(handler.handleReserveIDs, authz.Admin)).Methods("POST").Name("ReserveIDs") - router.HandleFunc("/internal/idalloc/commit", handler.mwAuth(handler.handleCommitIDs, authz.Admin)).Methods("POST").Name("CommitIDs") - router.HandleFunc("/internal/idalloc/restore", handler.mwAuth(handler.handleRestoreIDAlloc, authz.Admin)).Methods("POST").Name("RestoreIDAllocData") - router.HandleFunc("/internal/idalloc/reset/{index}", handler.mwAuth(handler.handleResetIDAlloc, authz.Admin)).Methods("POST").Name("ResetIDAlloc") - router.HandleFunc("/internal/idalloc/data", handler.mwAuth(handler.handleIDAllocData, authz.Admin)).Methods("GET").Name("IDAllocData") + router.HandleFunc("/internal/idalloc/reserve", handler.chkAuthN(handler.handleReserveIDs)).Methods("POST").Name("ReserveIDs") + router.HandleFunc("/internal/idalloc/commit", handler.chkAuthN(handler.handleCommitIDs)).Methods("POST").Name("CommitIDs") + router.HandleFunc("/internal/idalloc/restore", handler.chkAuthN(handler.handleRestoreIDAlloc)).Methods("POST").Name("RestoreIDAllocData") + router.HandleFunc("/internal/idalloc/reset/{index}", handler.chkAuthN(handler.handleResetIDAlloc)).Methods("POST").Name("ResetIDAlloc") + router.HandleFunc("/internal/idalloc/data", handler.chkAuthN(handler.handleIDAllocData)).Methods("GET").Name("IDAllocData") - router.HandleFunc("/internal/restore/{index}/{shardID}", handler.mwAuth(handler.handlePostRestore, authz.Admin)).Methods("POST").Name("Restore") + router.HandleFunc("/internal/restore/{index}/{shardID}", handler.chkAuthN(handler.handlePostRestore)).Methods("POST").Name("Restore") // endpoints for collecting cpu profiles from a chosen begin point to // when the client wants to stop. Used for profiling imports that // could be long or short. - router.HandleFunc("/cpu-profile/start", handler.mwAuth(handler.handleCPUProfileStart, authz.Admin)).Methods("GET").Name("CPUProfileStart") - router.HandleFunc("/cpu-profile/stop", handler.mwAuth(handler.handleCPUProfileStop, authz.Admin)).Methods("GET").Name("CPUProfileStop") + router.HandleFunc("/cpu-profile/start", handler.chkAuthZ(handler.handleCPUProfileStart, authz.Admin)).Methods("GET").Name("CPUProfileStart") + router.HandleFunc("/cpu-profile/stop", handler.chkAuthZ(handler.handleCPUProfileStop, authz.Admin)).Methods("GET").Name("CPUProfileStop") router.HandleFunc("/login", handler.handleLogin).Methods("GET").Name("Login") router.HandleFunc("/logout", handler.handleLogout).Methods("GET").Name("Logout") @@ -532,7 +532,22 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.Handler.ServeHTTP(w, r) } -func (h *Handler) mwAuth(handler http.HandlerFunc, perm authz.Permission) http.HandlerFunc { +func (h *Handler) chkAuthN(handler http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if h.auth != nil { + _, err := h.auth.Authenticate(w, r) + if err != nil { + http.Error(w, errors.Wrap(err, "authenticating").Error(), http.StatusBadRequest) + return + } + } else { + handler.ServeHTTP(w, r) + } + + } +} + +func (h *Handler) chkAuthZ(handler http.HandlerFunc, perm authz.Permission) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if h.auth != nil { @@ -580,8 +595,9 @@ func (h *Handler) mwAuth(handler http.HandlerFunc, perm authz.Permission) http.H ctx := context.WithValue(r.Context(), contextKeyGroupMembership, groups) handler.ServeHTTP(w, r.WithContext(ctx)) + } else { + handler.ServeHTTP(w, r) } - handler.ServeHTTP(w, r) } } @@ -754,20 +770,30 @@ func headerAcceptRoaringRow(header http.Header) bool { return false } -//WIP -// func (h *Handler) filterResponse(r *http.Request, schema []*pilosa.IndexInfo) { -// if h.auth != nil { -// groups := r.Context().Value(contextKeyGroupMembership) +func (h *Handler) filterResponse(w http.ResponseWriter, r *http.Request, schema []*pilosa.IndexInfo) []*pilosa.IndexInfo { + if h.auth != nil { + g := r.Context().Value(contextKeyGroupMembership) + if g == nil { + w.Header().Add("Content-Type", "text/plain") + w.WriteHeader(http.StatusForbidden) + return nil + } + indexes := h.permissions.GetAuthorizedIndexList(g.([]authn.Group), authz.Read.String()) + var new []*pilosa.IndexInfo + for _, s := range schema { + for _, index := range indexes { + if s.Name == index { + new = append(new, s) + } + } -// // indexes := h.permissions.GetAuthorizedIndexList(g, authz.Read.String()) -// for _, s := range schema { -// h.querylogger.Infof(s.Name) + } + return new -// } + } + return schema -// } - -// } +} // handleGetSchema handles GET /schema requests. func (h *Handler) handleGetSchema(w http.ResponseWriter, r *http.Request) { @@ -785,9 +811,7 @@ func (h *Handler) handleGetSchema(w http.ResponseWriter, r *http.Request) { h.logger.Printf("getting schema error: %s", err) } - groups := r.Context().Value(contextKeyGroupMembership) - h.querylogger.Infof("groups: %+v", groups) - // h.filterResponse(r, schema) + schema = h.filterResponse(w, r, schema) if err := json.NewEncoder(w).Encode(pilosa.Schema{Indexes: schema}); err != nil { h.logger.Errorf("write schema response error: %s", err) @@ -807,6 +831,7 @@ func (h *Handler) handleGetSchemaDetails(w http.ResponseWriter, r *http.Request) h.logger.Printf("error getting detailed schema: %s", err) return } + schema = h.filterResponse(w, r, schema) if err := json.NewEncoder(w).Encode(pilosa.Schema{Indexes: schema}); err != nil { h.logger.Printf("write schema response error: %s", err) } From d18b73940227640ceb1a5e7345b331e5aaa009e2 Mon Sep 17 00:00:00 2001 From: Samir Patel <48686912+54mir@users.noreply.github.com> Date: Mon, 3 Jan 2022 11:49:38 -0600 Subject: [PATCH 13/27] add test cases --- http/handler.go | 11 ++- http/handler_internal_test.go | 129 +++++++++++++++++++++++++++++++--- 2 files changed, 128 insertions(+), 12 deletions(-) diff --git a/http/handler.go b/http/handler.go index 8aea55f22..33375ad5a 100644 --- a/http/handler.go +++ b/http/handler.go @@ -550,7 +550,7 @@ func (h *Handler) chkAuthN(handler http.HandlerFunc) http.HandlerFunc { func (h *Handler) chkAuthZ(handler http.HandlerFunc, perm authz.Permission) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if h.auth != nil { - + fmt.Println("a") groups, err := h.auth.Authenticate(w, r) if err != nil { http.Error(w, errors.Wrap(err, "authenticating").Error(), http.StatusBadRequest) @@ -562,6 +562,9 @@ func (h *Handler) chkAuthZ(handler http.HandlerFunc, perm authz.Permission) http indexName = "" } + if h.permissions == nil { + panic("authentication is turned on without authorization permissions set") + } p, err := h.permissions.GetPermissions(groups, indexName) // err is being checked later, after logging @@ -587,7 +590,7 @@ func (h *Handler) chkAuthZ(handler http.HandlerFunc, perm authz.Permission) http if r.Method == "POST" { h.querylogger.Infof("User ID: %s, User Name: %s, Endpoint: %s, Index: %s, Query: %s, Err: %v", uinfo.UserID, uinfo.UserName, r.URL.Path, indexName, queryString, err) } - if err != nil || !p.Satisfies(perm.String()) { + if err != nil || !p.Satisfies(perm) { w.Header().Add("Content-Type", "text/plain") w.WriteHeader(http.StatusForbidden) return @@ -596,6 +599,7 @@ func (h *Handler) chkAuthZ(handler http.HandlerFunc, perm authz.Permission) http ctx := context.WithValue(r.Context(), contextKeyGroupMembership, groups) handler.ServeHTTP(w, r.WithContext(ctx)) } else { + fmt.Println("z") handler.ServeHTTP(w, r) } @@ -778,7 +782,7 @@ func (h *Handler) filterResponse(w http.ResponseWriter, r *http.Request, schema w.WriteHeader(http.StatusForbidden) return nil } - indexes := h.permissions.GetAuthorizedIndexList(g.([]authn.Group), authz.Read.String()) + indexes := h.permissions.GetAuthorizedIndexList(g.([]authn.Group), authz.Read) var new []*pilosa.IndexInfo for _, s := range schema { for _, index := range indexes { @@ -956,6 +960,7 @@ var DoPerQueryProfiling = false // handlePostQuery handles /query requests. func (h *Handler) handlePostQuery(w http.ResponseWriter, r *http.Request) { // Read previouly parsed request from context + fmt.Println("hi") qreq := r.Context().Value(contextKeyQueryRequest) qerr := r.Context().Value(contextKeyQueryError) req, ok := qreq.(*pilosa.QueryRequest) diff --git a/http/handler_internal_test.go b/http/handler_internal_test.go index e9cfee87a..2bcc02f97 100644 --- a/http/handler_internal_test.go +++ b/http/handler_internal_test.go @@ -5,6 +5,7 @@ import ( "bytes" "encoding/hex" "encoding/json" + "fmt" "io/ioutil" gohttp "net/http" "net/http/httptest" @@ -18,6 +19,8 @@ import ( "github.com/gorilla/securecookie" pilosa "github.com/molecula/featurebase/v2" "github.com/molecula/featurebase/v2/authn" + + "github.com/molecula/featurebase/v2/authz" "github.com/molecula/featurebase/v2/logger" "github.com/molecula/featurebase/v2/pql" "golang.org/x/oauth2" @@ -184,7 +187,7 @@ func readResponse(w *httptest.ResponseRecorder) ([]byte, error) { return ioutil.ReadAll(res.Body) } -func TestHandlerAuth(t *testing.T) { +func TestAuthentication(t *testing.T) { type evaluate func(w *httptest.ResponseRecorder, data []byte) type endpoint func(w gohttp.ResponseWriter, r *gohttp.Request) var ( @@ -259,7 +262,7 @@ func TestHandlerAuth(t *testing.T) { expiredCV := authn.CookieValue{ UserID: "narcissus", UserName: "Caravaggio", - GroupMembership: []authn.Group{}, + GroupMembership: []authn.Group{grp}, Token: &expiredToken, } @@ -309,13 +312,37 @@ func TestHandlerAuth(t *testing.T) { Expires: token.Expiry, } + // permissions1 := `"user-groups": + // "dca35310-ecda-4f23-86cd-876aee55906b": + // "test": "read" + // admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"` + + permissions2 := `"user-groups": + "dca35310-ecda-4f23-86cd-876aee559900": + "test": "write" +admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"` + + // permissions3 := `"user-groups": + // "dca35310-ecda-4f23-86cd-876aee55906b": + // "test": "write" + // "test2": "read" + // "dca35310-ecda-4f23-86cd-876aee559900": + // "test": "read" + // admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"` + + // permissions4 := `"user-groups": + // "dca35310-ecda-4f23-86cd-876aee559900": + // "test": "" + // admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"` + tests := []struct { - name string - path string - kind string - cookie *gohttp.Cookie - handler endpoint - fn evaluate + name string + path string + kind string + yamlData string + cookie *gohttp.Cookie + handler endpoint + fn evaluate }{ { name: "Login", @@ -538,6 +565,71 @@ func TestHandlerAuth(t *testing.T) { } }, }, + //Tests: + // auth off + //. bad authentication cookie + //. no index name + //. no permissions + // not authorized + // authorized w/o query string + // authorized w/ query + //. test handlePostQuery + // test handleGetSchema + { + name: "MW-AuthOff", + path: "/index/{index}/query", + kind: "middleware", + cookie: validCookie, + handler: func(w gohttp.ResponseWriter, r *gohttp.Request) { + f := hOff.chkAuthZ(hOff.handlePostQuery, authz.Admin) + f(w, r) + }, + fn: func(w *httptest.ResponseRecorder, data []byte) { + if w.Result().StatusCode != 400 { + t.Errorf("expected http code 400, got: %+v", w.Result().StatusCode) + } + }, + }, + { + name: "MW-ExpiredAuth", + path: "/index/{index}/query", + kind: "middleware", + cookie: expiredCookie, + handler: func(w gohttp.ResponseWriter, r *gohttp.Request) { + f := h.chkAuthZ(h.handlePostQuery, authz.Admin) + f(w, r) + }, + fn: func(w *httptest.ResponseRecorder, data []byte) { + if w.Result().StatusCode != 307 { + t.Errorf("expected http code 307, got: %+v", w.Result().StatusCode) + } + + }, + }, + { + name: "MW-NoIndexNoAdmin", + path: "/index/{index}/query", + kind: "middleware", + cookie: validCookie, + handler: func(w gohttp.ResponseWriter, r *gohttp.Request) { + h := h + permFile := strings.NewReader(permissions2) + var p authz.GroupPermissions + if err := p.ReadPermissionsFile(permFile); err != nil { + t.Errorf("Error: %s", err) + } + h.permissions = &p + fmt.Printf("%+v\n", h) + f := h.chkAuthZ(h.handlePostQuery, authz.Write) + f(w, r) + }, + fn: func(w *httptest.ResponseRecorder, data []byte) { + if w.Result().StatusCode != 403 { + t.Errorf("expected http code 403, got: %+v", w.Result().StatusCode) + } + + }, + }, } for _, test := range tests { @@ -546,7 +638,9 @@ func TestHandlerAuth(t *testing.T) { t.Run(test.name, func(t *testing.T) { r := httptest.NewRequest(gohttp.MethodGet, test.path, nil) w := httptest.NewRecorder() - r.AddCookie(test.cookie) + if test.cookie != nil { + r.AddCookie(test.cookie) + } test.handler(w, r) data, err := readResponse(w) if err != nil { @@ -571,6 +665,23 @@ func TestHandlerAuth(t *testing.T) { test.fn(w, data) }) + case "middleware": + t.Run(test.name, func(t *testing.T) { + r := httptest.NewRequest(gohttp.MethodGet, test.path, nil) + w := httptest.NewRecorder() + if test.cookie != nil { + r.AddCookie(test.cookie) + } + + test.handler(w, r) + fmt.Println("hey") + data, err := readResponse(w) + if err != nil { + t.Errorf("expected no errors reading response, got: %+v", err) + } + + test.fn(w, data) + }) } } From 7834db23478815da4894543b05d2e9527cfee8d7 Mon Sep 17 00:00:00 2001 From: Samir Patel <48686912+54mir@users.noreply.github.com> Date: Mon, 3 Jan 2022 16:24:14 -0600 Subject: [PATCH 14/27] change write call detection --- http/handler.go | 43 +++++++++++++++++------------------ http/handler_internal_test.go | 32 +++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 25 deletions(-) diff --git a/http/handler.go b/http/handler.go index 33375ad5a..43cd9dda5 100644 --- a/http/handler.go +++ b/http/handler.go @@ -284,8 +284,13 @@ const ( contextKeyQueryRequest contextKeyQuery = iota contextKeyQueryError contextKeyGroupMembership + contextKeyPermission ) +func GetContextKeyPermission() contextKeyQuery { + return contextKeyPermission +} + // addQueryContext puts the results of handler.readQueryRequest into the Context for use by // both other middleware and any handlers. func (h *Handler) addQueryContext(next http.Handler) http.Handler { @@ -550,23 +555,15 @@ func (h *Handler) chkAuthN(handler http.HandlerFunc) http.HandlerFunc { func (h *Handler) chkAuthZ(handler http.HandlerFunc, perm authz.Permission) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if h.auth != nil { - fmt.Println("a") groups, err := h.auth.Authenticate(w, r) if err != nil { http.Error(w, errors.Wrap(err, "authenticating").Error(), http.StatusBadRequest) return } - indexName, ok := mux.Vars(r)["index"] - if !ok { - indexName = "" - } - if h.permissions == nil { panic("authentication is turned on without authorization permissions set") } - p, err := h.permissions.GetPermissions(groups, indexName) - // err is being checked later, after logging uinfo := h.auth.GetUserInfo(w, r) @@ -577,29 +574,32 @@ func (h *Handler) chkAuthZ(handler http.HandlerFunc, perm authz.Permission) http if req, ok := queryRequest.(*pilosa.QueryRequest); ok { queryString = req.Query } - writeWords := []string{"store", "set", "clear", "clearrow"} - q := strings.ToLower(queryString) - for _, w := range writeWords { - if strings.Contains(q, w) { - perm = authz.Write - } + + q, _ := pql.ParseString(fmt.Sprintf(queryString)) + if q.WriteCallN() > 0 { + perm = authz.Write } queryString = strings.Replace(queryString, "\n", "", -1) if r.Method == "POST" { - h.querylogger.Infof("User ID: %s, User Name: %s, Endpoint: %s, Index: %s, Query: %s, Err: %v", uinfo.UserID, uinfo.UserName, r.URL.Path, indexName, queryString, err) - } - if err != nil || !p.Satisfies(perm) { - w.Header().Add("Content-Type", "text/plain") - w.WriteHeader(http.StatusForbidden) - return + h.querylogger.Infof("User ID: %s, User Name: %s, Endpoint: %s, Index: %s, Query: %s, Err: %v", uinfo.UserID, uinfo.UserName, r.URL.Path, "indexName", queryString, err) } ctx := context.WithValue(r.Context(), contextKeyGroupMembership, groups) + indexName, ok := mux.Vars(r)["index"] + if ok { + p, err := h.permissions.GetPermissions(groups, indexName) + ctx = context.WithValue(r.Context(), contextKeyPermission, p) + if err != nil || !p.Satisfies(perm) { + w.Header().Add("Content-Type", "text/plain") + w.WriteHeader(http.StatusForbidden) + return + } + } + handler.ServeHTTP(w, r.WithContext(ctx)) } else { - fmt.Println("z") handler.ServeHTTP(w, r) } @@ -960,7 +960,6 @@ var DoPerQueryProfiling = false // handlePostQuery handles /query requests. func (h *Handler) handlePostQuery(w http.ResponseWriter, r *http.Request) { // Read previouly parsed request from context - fmt.Println("hi") qreq := r.Context().Value(contextKeyQueryRequest) qerr := r.Context().Value(contextKeyQueryError) req, ok := qreq.(*pilosa.QueryRequest) diff --git a/http/handler_internal_test.go b/http/handler_internal_test.go index 2bcc02f97..6dd92a955 100644 --- a/http/handler_internal_test.go +++ b/http/handler_internal_test.go @@ -5,7 +5,6 @@ import ( "bytes" "encoding/hex" "encoding/json" - "fmt" "io/ioutil" gohttp "net/http" "net/http/httptest" @@ -19,6 +18,7 @@ import ( "github.com/gorilla/securecookie" pilosa "github.com/molecula/featurebase/v2" "github.com/molecula/featurebase/v2/authn" + "github.com/stretchr/testify/assert" "github.com/molecula/featurebase/v2/authz" "github.com/molecula/featurebase/v2/logger" @@ -595,6 +595,22 @@ admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"` path: "/index/{index}/query", kind: "middleware", cookie: expiredCookie, + handler: func(w gohttp.ResponseWriter, r *gohttp.Request) { + f := h.chkAuthN(h.handlePostQuery) + f(w, r) + }, + fn: func(w *httptest.ResponseRecorder, data []byte) { + if w.Result().StatusCode != 307 { + t.Errorf("expected http code 307, got: %+v", w.Result().StatusCode) + } + + }, + }, + { + name: "MW-ExpiredAuth2", + path: "/index/{index}/query", + kind: "middleware", + cookie: expiredCookie, handler: func(w gohttp.ResponseWriter, r *gohttp.Request) { f := h.chkAuthZ(h.handlePostQuery, authz.Admin) f(w, r) @@ -606,6 +622,18 @@ admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"` }, }, + { + name: "MW-NoPermissions", + path: "/index/{index}/query", + kind: "middleware", + cookie: validCookie, + handler: func(w gohttp.ResponseWriter, r *gohttp.Request) { + h := h + f := h.chkAuthZ(h.handlePostQuery, authz.Write) + assert.Panics(t, func() { f(w, r) }, "expected panic") + }, + fn: func(w *httptest.ResponseRecorder, data []byte) {}, + }, { name: "MW-NoIndexNoAdmin", path: "/index/{index}/query", @@ -619,7 +647,6 @@ admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"` t.Errorf("Error: %s", err) } h.permissions = &p - fmt.Printf("%+v\n", h) f := h.chkAuthZ(h.handlePostQuery, authz.Write) f(w, r) }, @@ -674,7 +701,6 @@ admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"` } test.handler(w, r) - fmt.Println("hey") data, err := readResponse(w) if err != nil { t.Errorf("expected no errors reading response, got: %+v", err) From 1b10f26258e979a5e33aa98ab9dd44b9e1b0ef16 Mon Sep 17 00:00:00 2001 From: Samir Patel <48686912+54mir@users.noreply.github.com> Date: Mon, 3 Jan 2022 17:41:09 -0600 Subject: [PATCH 15/27] fix permission stuff for write queries --- http/handler.go | 19 +++++++++++++------ http/handler_internal_test.go | 10 ---------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/http/handler.go b/http/handler.go index 43cd9dda5..3eb824cfb 100644 --- a/http/handler.go +++ b/http/handler.go @@ -38,6 +38,7 @@ import ( "github.com/molecula/featurebase/v2/rbf" "github.com/molecula/featurebase/v2/topology" "github.com/molecula/featurebase/v2/tracing" + "github.com/molecula/featurebase/v2/vprint" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus/promhttp" dto "github.com/prometheus/client_model/go" @@ -554,6 +555,7 @@ func (h *Handler) chkAuthN(handler http.HandlerFunc) http.HandlerFunc { func (h *Handler) chkAuthZ(handler http.HandlerFunc, perm authz.Permission) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + lperm := perm if h.auth != nil { groups, err := h.auth.Authenticate(w, r) if err != nil { @@ -568,16 +570,20 @@ func (h *Handler) chkAuthZ(handler http.HandlerFunc, perm authz.Permission) http uinfo := h.auth.GetUserInfo(w, r) //get query string if applicable - queryRequest := r.Context().Value(contextKeyQueryRequest) var queryString string + queryRequest := r.Context().Value(contextKeyQueryRequest) if req, ok := queryRequest.(*pilosa.QueryRequest); ok { queryString = req.Query - } - q, _ := pql.ParseString(fmt.Sprintf(queryString)) - if q.WriteCallN() > 0 { - perm = authz.Write + q, err := pql.ParseString(queryString) + if err != nil { + http.Error(w, errors.Wrap(err, "parsing query string").Error(), http.StatusBadRequest) + return + } + if q.WriteCallN() > 0 { + lperm = authz.Write + } } queryString = strings.Replace(queryString, "\n", "", -1) @@ -590,8 +596,9 @@ func (h *Handler) chkAuthZ(handler http.HandlerFunc, perm authz.Permission) http indexName, ok := mux.Vars(r)["index"] if ok { p, err := h.permissions.GetPermissions(groups, indexName) + vprint.VV("p: %+v,perm: %+v,indexName: %+v", p, lperm, indexName) ctx = context.WithValue(r.Context(), contextKeyPermission, p) - if err != nil || !p.Satisfies(perm) { + if err != nil || !p.Satisfies(lperm) { w.Header().Add("Content-Type", "text/plain") w.WriteHeader(http.StatusForbidden) return diff --git a/http/handler_internal_test.go b/http/handler_internal_test.go index 6dd92a955..6240f813e 100644 --- a/http/handler_internal_test.go +++ b/http/handler_internal_test.go @@ -565,16 +565,6 @@ admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"` } }, }, - //Tests: - // auth off - //. bad authentication cookie - //. no index name - //. no permissions - // not authorized - // authorized w/o query string - // authorized w/ query - //. test handlePostQuery - // test handleGetSchema { name: "MW-AuthOff", path: "/index/{index}/query", From a7fada30dd78b85818b51f618c50653dfa91d7b2 Mon Sep 17 00:00:00 2001 From: Samir Patel <48686912+54mir@users.noreply.github.com> Date: Mon, 3 Jan 2022 20:43:51 -0600 Subject: [PATCH 16/27] revisions 1 --- authn/authenticate.go | 2 +- server/server.go | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/authn/authenticate.go b/authn/authenticate.go index f4d1a8cb8..123446893 100644 --- a/authn/authenticate.go +++ b/authn/authenticate.go @@ -34,7 +34,7 @@ func NewAuth(logger logger.Logger, url string, scopes []string, authUrl, tokenUr auth := &Auth{ logger: logger, cookieName: "molecula-chip", - refreshWithin: time.Minute * time.Duration(15), + refreshWithin: 15 * time.Minute, groupEndpoint: groupEndpoint, logoutEndpoint: logout, fbURL: url, diff --git a/server/server.go b/server/server.go index a322e2938..fd7475237 100644 --- a/server/server.go +++ b/server/server.go @@ -632,11 +632,8 @@ func (m *Command) setupQueryLogger() error { sighup := make(chan os.Signal, 1) signal.Notify(sighup, syscall.SIGHUP) go func() { - for { - // reopen log file on SIGHUP - <-sighup - err = f.Reopen() - if err != nil { + for range sighup { + if err := f.Reopen(); err != nil { m.querylogger.Infof("reopen: %s\n", err.Error()) } } From 414dff1d22dac07e5df542d79afbdd90bb49adaf Mon Sep 17 00:00:00 2001 From: Samir Patel <48686912+54mir@users.noreply.github.com> Date: Mon, 3 Jan 2022 22:56:21 -0600 Subject: [PATCH 17/27] revisions --- authn/authenticate.go | 41 ++++++++++++++--------------- authn/authenticate_internal_test.go | 14 +++++----- http/handler.go | 37 +++++++------------------- http/handler_internal_test.go | 26 +++--------------- 4 files changed, 40 insertions(+), 78 deletions(-) diff --git a/authn/authenticate.go b/authn/authenticate.go index 123446893..76c9f8d5d 100644 --- a/authn/authenticate.go +++ b/authn/authenticate.go @@ -6,7 +6,7 @@ import ( "encoding/hex" "encoding/json" "fmt" - "io/ioutil" + "io" "net/http" "time" @@ -114,23 +114,24 @@ func (a *Auth) Login(w http.ResponseWriter, r *http.Request) { } func (a *Auth) Logout(w http.ResponseWriter, r *http.Request) { - newCookie := a.getEmptyCookie() - http.SetCookie(w, newCookie) + http.SetCookie(w, a.getEmptyCookie()) redirect := fmt.Sprintf("%s?post_logout_redirect_uri=%s/", a.logoutEndpoint, a.fbURL) http.Redirect(w, r, redirect, http.StatusTemporaryRedirect) } -// Gets user information from dP and sets a secure cookie +// Gets user information from IdP and sets a secure cookie func (a *Auth) Redirect(w http.ResponseWriter, r *http.Request) { code := r.FormValue("code") - token, err := a.getToken(code) + token, err := a.getToken(r, code) if err != nil { + a.logger.Warnf("getting token from IdP: %+v", err) http.Error(w, "Bad Request: 400", http.StatusBadRequest) return } cv, err := a.newCookieValue(token) if err != nil || cv == nil { + a.logger.Warnf("creating cookie: %+v", err) http.Error(w, "Bad Request: 400", http.StatusBadRequest) return } @@ -143,17 +144,18 @@ func (a *Auth) GetUserInfo(w http.ResponseWriter, r *http.Request) *UserInfo { var resp UserInfo cookie, err := a.readCookie(w, r) if err != nil { - //add logging + a.logger.Warnf("was not able to read cookie for req: %+v", r) return &resp } - resp.UserID = cookie.UserID - resp.UserName = cookie.UserName - return &resp + return &UserInfo{ + UserID: cookie.UserID, + UserName: cookie.UserName, + } } -func (a *Auth) getToken(code string) (*oauth2.Token, error) { - token, err := a.oAuthConfig.Exchange(context.Background(), code) +func (a *Auth) getToken(r *http.Request, code string) (*oauth2.Token, error) { + token, err := a.oAuthConfig.Exchange(r.Context(), code) if err != nil { return nil, errors.Wrap(err, "exchanging auth code for token") } @@ -189,21 +191,20 @@ func (a *Auth) newCookieValue(token *oauth2.Token) (*CookieValue, error) { func (a *Auth) getGroupMembership(token *oauth2.Token) (Groups, error) { var groups Groups - var bearer = fmt.Sprintf("Bearer %s", token.AccessToken) req, err := http.NewRequest("GET", a.groupEndpoint, nil) if err != nil { return groups, errors.Wrap(err, "creating new request to group endpoint") } - req.Header.Add("Authorization", bearer) - client := &http.Client{} + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token.AccessToken)) + client := http.DefaultClient response, err := client.Do(req) if err != nil { return groups, errors.Wrap(err, "getting group membership info") } defer response.Body.Close() - rawGroups, err := ioutil.ReadAll(response.Body) + rawGroups, err := io.ReadAll(response.Body) if err != nil { return groups, errors.Wrap(err, "failed reading group membership response") } @@ -224,8 +225,7 @@ func (a *Auth) readCookie(w http.ResponseWriter, r *http.Request) (*CookieValue, var value CookieValue err = a.secure.Decode(a.cookieName, cookie.Value, &value) if err != nil { - newCookie := a.getEmptyCookie() - http.SetCookie(w, newCookie) + http.SetCookie(w, a.getEmptyCookie()) return nil, errors.Wrap(err, "decoding cookie") } @@ -238,7 +238,7 @@ func (a *Auth) setCookie(w http.ResponseWriter, cookie *CookieValue) error { return errors.Wrap(err, "encoding CookieValue") } - newCookie := &http.Cookie{ + http.SetCookie(w, &http.Cookie{ Name: a.cookieName, Value: encoded, Path: "/", @@ -246,8 +246,7 @@ func (a *Auth) setCookie(w http.ResponseWriter, cookie *CookieValue) error { HttpOnly: true, SameSite: http.SameSiteStrictMode, Expires: cookie.Token.Expiry, - } - http.SetCookie(w, newCookie) + }) return nil } @@ -264,7 +263,7 @@ func (a *Auth) refreshToken(w http.ResponseWriter, cookie *CookieValue) error { if newToken.Expiry != cookie.Token.Expiry { cv, err := a.newCookieValue(newToken) if err != nil { - errors.Wrap(err, "setting cookie") + return errors.Wrap(err, "setting cookie") } a.setCookie(w, cv) diff --git a/authn/authenticate_internal_test.go b/authn/authenticate_internal_test.go index 8df8c3b03..07cce1396 100644 --- a/authn/authenticate_internal_test.go +++ b/authn/authenticate_internal_test.go @@ -67,20 +67,20 @@ func TestAuth(t *testing.T) { w := httptest.NewRecorder() err := a.setCookie(w, &validCV) if err != nil { - t.Errorf("expected no errors, got: %v", err) + t.Fatalf("expected no errors, got: %v", err) } if w.Result().Cookies()[0].Value == "" { - t.Errorf("expected some value, got: %+v", w.Result().Cookies()[0].Value) + t.Fatalf("expected some value, got: %+v", w.Result().Cookies()[0].Value) } if w.Result().Cookies()[0].Path != "/" { - t.Errorf("expected path to be /, got: %+v", w.Result().Cookies()[0].Path) + t.Fatalf("expected path to be /, got: %+v", w.Result().Cookies()[0].Path) } }) t.Run("GetEmptyCookie", func(t *testing.T) { c := a.getEmptyCookie() if c.Value != "" { - t.Errorf("expected empty cookie, got: %+v", c.Value) + t.Fatalf("expected empty cookie, got: %+v", c.Value) } }) t.Run("KeyLength", func(t *testing.T) { @@ -98,20 +98,20 @@ func TestAuth(t *testing.T) { ShortKey, ) if err == nil || !strings.Contains(err.Error(), "decoding block key") { - t.Errorf("expected error decoding block key got: %v", err) + t.Fatalf("expected error decoding block key got: %v", err) } }) t.Run("NewCookieValue-BadAccessToken", func(t *testing.T) { _, err := a.newCookieValue(&tokenAT) if err == nil || !strings.Contains(err.Error(), "jwt claims") { - t.Errorf("expected failure regarding jwt claims, got: %v", err) + t.Fatalf("expected failure regarding jwt claims, got: %v", err) } }) t.Run("CookieValue-NoAccessToken", func(t *testing.T) { _, err := a.newCookieValue(&tokenNoAT) if err == nil || !strings.Contains(err.Error(), "access token") { - t.Errorf("expected failure regarding access token, got: %v", err) + t.Fatalf("expected failure regarding access token, got: %v", err) } }) diff --git a/http/handler.go b/http/handler.go index 3eb824cfb..7f9aaf014 100644 --- a/http/handler.go +++ b/http/handler.go @@ -38,7 +38,6 @@ import ( "github.com/molecula/featurebase/v2/rbf" "github.com/molecula/featurebase/v2/topology" "github.com/molecula/featurebase/v2/tracing" - "github.com/molecula/featurebase/v2/vprint" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus/promhttp" dto "github.com/prometheus/client_model/go" @@ -541,15 +540,12 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *Handler) chkAuthN(handler http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if h.auth != nil { - _, err := h.auth.Authenticate(w, r) - if err != nil { + if _, err := h.auth.Authenticate(w, r); err != nil { http.Error(w, errors.Wrap(err, "authenticating").Error(), http.StatusBadRequest) return } - } else { - handler.ServeHTTP(w, r) } - + handler.ServeHTTP(w, r) } } @@ -564,13 +560,12 @@ func (h *Handler) chkAuthZ(handler http.HandlerFunc, perm authz.Permission) http } if h.permissions == nil { - panic("authentication is turned on without authorization permissions set") + h.logger.Errorf("authentication is turned on without authorization permissions set") + http.Error(w, errors.New("authorizing").Error(), http.StatusInternalServerError) } uinfo := h.auth.GetUserInfo(w, r) - //get query string if applicable - var queryString string queryRequest := r.Context().Value(contextKeyQueryRequest) if req, ok := queryRequest.(*pilosa.QueryRequest); ok { @@ -596,7 +591,6 @@ func (h *Handler) chkAuthZ(handler http.HandlerFunc, perm authz.Permission) http indexName, ok := mux.Vars(r)["index"] if ok { p, err := h.permissions.GetPermissions(groups, indexName) - vprint.VV("p: %+v,perm: %+v,indexName: %+v", p, lperm, indexName) ctx = context.WithValue(r.Context(), contextKeyPermission, p) if err != nil || !p.Satisfies(lperm) { w.Header().Add("Content-Type", "text/plain") @@ -785,8 +779,7 @@ func (h *Handler) filterResponse(w http.ResponseWriter, r *http.Request, schema if h.auth != nil { g := r.Context().Value(contextKeyGroupMembership) if g == nil { - w.Header().Add("Content-Type", "text/plain") - w.WriteHeader(http.StatusForbidden) + http.Error(w, "not authorized", http.StatusForbidden) return nil } indexes := h.permissions.GetAuthorizedIndexList(g.([]authn.Group), authz.Read) @@ -971,10 +964,6 @@ func (h *Handler) handlePostQuery(w http.ResponseWriter, r *http.Request) { qerr := r.Context().Value(contextKeyQueryError) req, ok := qreq.(*pilosa.QueryRequest) - // if !h.isAuthorized(w, r, req, req.Index, authz.Admin.String(), r.URL.Path) { - // return - // } - if DoPerQueryProfiling { backend := pilosa.CurrentBackend() reqHash := hash(req.Query) @@ -3514,9 +3503,7 @@ func (h *Handler) handlePostRestore(w http.ResponseWriter, r *http.Request) { func (h *Handler) handleLogin(w http.ResponseWriter, r *http.Request) { if h.auth == nil { - w.Header().Add("Content-Type", "text/plain") - w.WriteHeader(http.StatusNoContent) - w.Write([]byte("Auth Off")) //nolint:errcheck + http.Error(w, "Auth Off", http.StatusNoContent) return } @@ -3539,9 +3526,7 @@ func (h *Handler) handleCheckAuthentication(w http.ResponseWriter, r *http.Reque return } if h.auth == nil { - w.Header().Add("Content-Type", "text/plain") - w.WriteHeader(http.StatusNoContent) - w.Write([]byte("Auth Off")) //nolint:errcheck + http.Error(w, "Auth Off", http.StatusNoContent) return } groups, err := h.auth.Authenticate(w, r) @@ -3562,9 +3547,7 @@ func (h *Handler) handleUserInfo(w http.ResponseWriter, r *http.Request) { return } if h.auth == nil { - w.Header().Add("Content-Type", "text/plain") - w.WriteHeader(http.StatusNoContent) - w.Write([]byte("Auth Off")) //nolint:errcheck + http.Error(w, "Auth Off", http.StatusNoContent) return } if err := json.NewEncoder(w).Encode(h.auth.GetUserInfo(w, r)); err != nil { @@ -3574,9 +3557,7 @@ func (h *Handler) handleUserInfo(w http.ResponseWriter, r *http.Request) { func (h *Handler) handleLogout(w http.ResponseWriter, r *http.Request) { if h.auth == nil { - w.Header().Add("Content-Type", "text/plain") - w.WriteHeader(http.StatusNoContent) - w.Write([]byte("Auth Off")) //nolint:errcheck + http.Error(w, "Auth Off", http.StatusNoContent) return } h.auth.Logout(w, r) diff --git a/http/handler_internal_test.go b/http/handler_internal_test.go index 6240f813e..f4856e235 100644 --- a/http/handler_internal_test.go +++ b/http/handler_internal_test.go @@ -312,29 +312,11 @@ func TestAuthentication(t *testing.T) { Expires: token.Expiry, } - // permissions1 := `"user-groups": - // "dca35310-ecda-4f23-86cd-876aee55906b": - // "test": "read" - // admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"` - - permissions2 := `"user-groups": + permissions1 := `"user-groups": "dca35310-ecda-4f23-86cd-876aee559900": "test": "write" admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"` - // permissions3 := `"user-groups": - // "dca35310-ecda-4f23-86cd-876aee55906b": - // "test": "write" - // "test2": "read" - // "dca35310-ecda-4f23-86cd-876aee559900": - // "test": "read" - // admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"` - - // permissions4 := `"user-groups": - // "dca35310-ecda-4f23-86cd-876aee559900": - // "test": "" - // admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"` - tests := []struct { name string path string @@ -631,7 +613,7 @@ admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"` cookie: validCookie, handler: func(w gohttp.ResponseWriter, r *gohttp.Request) { h := h - permFile := strings.NewReader(permissions2) + permFile := strings.NewReader(permissions1) var p authz.GroupPermissions if err := p.ReadPermissionsFile(permFile); err != nil { t.Errorf("Error: %s", err) @@ -641,8 +623,8 @@ admin: "ac97c9e2-346b-42a2-b6da-18bcb61a32fe"` f(w, r) }, fn: func(w *httptest.ResponseRecorder, data []byte) { - if w.Result().StatusCode != 403 { - t.Errorf("expected http code 403, got: %+v", w.Result().StatusCode) + if w.Result().StatusCode != 400 { + t.Errorf("expected http code 400, got: %+v", w.Result().StatusCode) } }, From 23a1b4c536f0cba171febab300d82e72bba4912c Mon Sep 17 00:00:00 2001 From: Samir Patel <48686912+54mir@users.noreply.github.com> Date: Tue, 4 Jan 2022 16:02:25 -0600 Subject: [PATCH 18/27] revisions and docs --- authn/authenticate.go | 31 +++++++++++++++++++++-------- authn/authenticate_internal_test.go | 8 +++++--- install/featurebase.conf | 1 + 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/authn/authenticate.go b/authn/authenticate.go index 76c9f8d5d..6a5221600 100644 --- a/authn/authenticate.go +++ b/authn/authenticate.go @@ -17,6 +17,7 @@ import ( "golang.org/x/oauth2" ) +// Auth holds state, configuration, and utilities needed for authentication. type Auth struct { logger logger.Logger cookieName string @@ -26,10 +27,11 @@ type Auth struct { secure *securecookie.SecureCookie groupEndpoint string logoutEndpoint string - fbURL string + fbURL string // fbURL is the domain FB is hosted on, used for post logout redirection oAuthConfig *oauth2.Config } +// NewAuth is a constructor that returns a new auth object func NewAuth(logger logger.Logger, url string, scopes []string, authUrl, tokenUrl, groupEndpoint, logout, clientID, clientSecret, hashKey, blockKey string) (*Auth, error) { auth := &Auth{ logger: logger, @@ -85,6 +87,8 @@ type UserInfo struct { UserName string `json:"username"` } +// Authenticate reads and validates a cookie, redirects if invalid or missing, otherwise returns +// the group membership information stored in the cookie. func (a *Auth) Authenticate(w http.ResponseWriter, r *http.Request) ([]Group, error) { cookie, err := a.readCookie(w, r) if err != nil { @@ -108,18 +112,20 @@ func (a *Auth) Authenticate(w http.ResponseWriter, r *http.Request) ([]Group, er } +// Login redirects user to the IdP authorize endpoint for auth code func (a *Auth) Login(w http.ResponseWriter, r *http.Request) { authUrl := a.oAuthConfig.AuthCodeURL(a.oAuthConfig.Endpoint.AuthURL) http.Redirect(w, r, authUrl, http.StatusTemporaryRedirect) } +// Logout clears out user cookie and redirects user to IdP's logout endpoint func (a *Auth) Logout(w http.ResponseWriter, r *http.Request) { http.SetCookie(w, a.getEmptyCookie()) redirect := fmt.Sprintf("%s?post_logout_redirect_uri=%s/", a.logoutEndpoint, a.fbURL) http.Redirect(w, r, redirect, http.StatusTemporaryRedirect) } -// Gets user information from IdP and sets a secure cookie +// Redirect gets user information from IdP and sets a secure cookie func (a *Auth) Redirect(w http.ResponseWriter, r *http.Request) { code := r.FormValue("code") token, err := a.getToken(r, code) @@ -140,6 +146,7 @@ func (a *Auth) Redirect(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/", http.StatusTemporaryRedirect) } +// GetUserInfo reads user's cookie and returns their username and userId func (a *Auth) GetUserInfo(w http.ResponseWriter, r *http.Request) *UserInfo { var resp UserInfo cookie, err := a.readCookie(w, r) @@ -154,6 +161,7 @@ func (a *Auth) GetUserInfo(w http.ResponseWriter, r *http.Request) *UserInfo { } +// getToken exhanges authorization code for an oAuth2 token func (a *Auth) getToken(r *http.Request, code string) (*oauth2.Token, error) { token, err := a.oAuthConfig.Exchange(r.Context(), code) if err != nil { @@ -162,6 +170,7 @@ func (a *Auth) getToken(r *http.Request, code string) (*oauth2.Token, error) { return token, nil } +// newCookieValue parses a jwt `token` and returns relevant information in a cookie value struct func (a *Auth) newCookieValue(token *oauth2.Token) (*CookieValue, error) { if token == nil { return nil, errors.New("baking cookie due to nil token") @@ -169,9 +178,14 @@ func (a *Auth) newCookieValue(token *oauth2.Token) (*CookieValue, error) { if token.AccessToken == "" { return nil, errors.New("no access token provided") } - accessParsed, err := jwt.Parse(token.AccessToken, nil) - if accessParsed == nil || accessParsed.Claims == nil { - return nil, errors.Wrap(err, "parsing jwt claims from access tokens") + + // We are using ParseUnverified here because we're using the OAuth2.0 authZ code flow + // which assumes that the IdP gives good responses. This means that if the IdP is + // insecure, then we are too. But that's the way OAuth works, unfortunately. + // Also, we assume the jwt is not tampered with bc we communicate with the IdP over HTTPS only. + accessParsed, _, err := new(jwt.Parser).ParseUnverified(token.AccessToken, jwt.MapClaims{}) + if accessParsed == nil || accessParsed.Claims == nil || err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("%v parsing jwt claims from access tokens", accessParsed)) } claims := accessParsed.Claims.(jwt.MapClaims) @@ -189,6 +203,7 @@ func (a *Auth) newCookieValue(token *oauth2.Token) (*CookieValue, error) { }, nil } +// getGroupMembership uses a oauth2 token to retrieve group membership information from IdP func (a *Auth) getGroupMembership(token *oauth2.Token) (Groups, error) { var groups Groups req, err := http.NewRequest("GET", a.groupEndpoint, nil) @@ -197,8 +212,7 @@ func (a *Auth) getGroupMembership(token *oauth2.Token) (Groups, error) { } req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token.AccessToken)) - client := http.DefaultClient - response, err := client.Do(req) + response, err := http.DefaultClient.Do(req) if err != nil { return groups, errors.Wrap(err, "getting group membership info") } @@ -216,6 +230,7 @@ func (a *Auth) getGroupMembership(token *oauth2.Token) (Groups, error) { return groups, nil } +// readCookie decodes an encrypted and signed cookie and returns the contained info func (a *Auth) readCookie(w http.ResponseWriter, r *http.Request) (*CookieValue, error) { cookie, err := r.Cookie(a.cookieName) if err != nil { @@ -263,7 +278,7 @@ func (a *Auth) refreshToken(w http.ResponseWriter, cookie *CookieValue) error { if newToken.Expiry != cookie.Token.Expiry { cv, err := a.newCookieValue(newToken) if err != nil { - return errors.Wrap(err, "setting cookie") + return errors.Wrap(err, "creating cookie value from token") } a.setCookie(w, cv) diff --git a/authn/authenticate_internal_test.go b/authn/authenticate_internal_test.go index 07cce1396..860283433 100644 --- a/authn/authenticate_internal_test.go +++ b/authn/authenticate_internal_test.go @@ -71,11 +71,13 @@ func TestAuth(t *testing.T) { } if w.Result().Cookies()[0].Value == "" { - t.Fatalf("expected some value, got: %+v", w.Result().Cookies()[0].Value) + t.Errorf("expected something, got empty string") } - if w.Result().Cookies()[0].Path != "/" { - t.Fatalf("expected path to be /, got: %+v", w.Result().Cookies()[0].Path) + + if got, want := w.Result().Cookies()[0].Path, "/"; got != want { + t.Fatalf("path=%s, want %s", got, want) } + }) t.Run("GetEmptyCookie", func(t *testing.T) { c := a.getEmptyCookie() diff --git a/install/featurebase.conf b/install/featurebase.conf index 033db191d..94903194c 100644 --- a/install/featurebase.conf +++ b/install/featurebase.conf @@ -386,3 +386,4 @@ log-path = "/var/log/molecula/featurebase.log" # hash-key = "" # block-key = "" # permissions = "" +# query-log-path = "" From e3358867414923a0eba4715a74932cbe46e273a9 Mon Sep 17 00:00:00 2001 From: reesporte Date: Tue, 4 Jan 2022 16:23:25 -0600 Subject: [PATCH 19/27] adding Groups Struct back in "It was pure hubris that brought us to this point." --- authn/authenticate.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/authn/authenticate.go b/authn/authenticate.go index 27a1a8318..af95e953d 100644 --- a/authn/authenticate.go +++ b/authn/authenticate.go @@ -82,6 +82,11 @@ type Group struct { GroupName string `json:"displayName"` } +// Groups holds a slice of Group informations for marshalling from Json +type Groups struct { + Groups []Group `json:"value"` +} + // UserInfo holds user information for an authenticated user type UserInfo struct { UserID string `json:"userid"` @@ -203,7 +208,7 @@ func (a *Auth) newCookieValue(token *oauth2.Token) (*CookieValue, error) { return &CookieValue{ UserID: claims["oid"].(string), UserName: claims["name"].(string), - GroupMembership: groups, + GroupMembership: groups.Groups, Token: token, }, nil } From fd896de270466092fabe0ca39462505a4d91667b Mon Sep 17 00:00:00 2001 From: reesporte Date: Wed, 5 Jan 2022 12:10:33 -0600 Subject: [PATCH 20/27] rename CookieValue to AuthContext because we're not using cookies anymore --- authn/authenticate.go | 24 ++++++++++++------------ authn/authenticate_internal_test.go | 10 +++++----- http/handler_internal_test.go | 6 +++--- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/authn/authenticate.go b/authn/authenticate.go index af95e953d..a5567842b 100644 --- a/authn/authenticate.go +++ b/authn/authenticate.go @@ -67,8 +67,8 @@ func NewAuth(logger logger.Logger, url string, scopes []string, authURL, tokenUR return auth, nil } -// CookieValue holds the value of an authenticated user's cookie -type CookieValue struct { +// AuthContext holds the value of an authenticated user's cookie +type AuthContext struct { UserID string UserName string GroupMembership []Group @@ -145,7 +145,7 @@ func (a *Auth) Redirect(w http.ResponseWriter, r *http.Request) { return } - cv, err := a.newCookieValue(token) + cv, err := a.newAuthContext(token) if err != nil || cv == nil { a.logger.Warnf("creating cookie: %+v", err) http.Error(w, "Bad Request: 400", http.StatusBadRequest) @@ -180,8 +180,8 @@ func (a *Auth) getToken(r *http.Request, code string) (*oauth2.Token, error) { return token, nil } -// newCookieValue parses a jwt `token` and returns relevant information in a cookie value struct -func (a *Auth) newCookieValue(token *oauth2.Token) (*CookieValue, error) { +// newAuthContext parses a jwt `token` and returns relevant information in a cookie value struct +func (a *Auth) newAuthContext(token *oauth2.Token) (*AuthContext, error) { if token == nil { return nil, errors.New("baking cookie due to nil token") } @@ -205,7 +205,7 @@ func (a *Auth) newCookieValue(token *oauth2.Token) (*CookieValue, error) { } // not needed at this point in the logic and makes the encoded cookie too large token.AccessToken = "" - return &CookieValue{ + return &AuthContext{ UserID: claims["oid"].(string), UserName: claims["name"].(string), GroupMembership: groups.Groups, @@ -242,13 +242,13 @@ func (a *Auth) getGroupMembership(token *oauth2.Token) (Groups, error) { } // readCookie decodes an encrypted and signed cookie and returns the contained info -func (a *Auth) readCookie(w http.ResponseWriter, r *http.Request) (*CookieValue, error) { +func (a *Auth) readCookie(w http.ResponseWriter, r *http.Request) (*AuthContext, error) { cookie, err := r.Cookie(a.cookieName) if err != nil { return nil, errors.Wrap(err, "cookie not found") } - var value CookieValue + var value AuthContext err = a.secure.Decode(a.cookieName, cookie.Value, &value) if err != nil { http.SetCookie(w, a.getEmptyCookie()) @@ -258,10 +258,10 @@ func (a *Auth) readCookie(w http.ResponseWriter, r *http.Request) (*CookieValue, return &value, nil } -func (a *Auth) setCookie(w http.ResponseWriter, cookie *CookieValue) error { +func (a *Auth) setCookie(w http.ResponseWriter, cookie *AuthContext) error { encoded, err := a.secure.Encode(a.cookieName, cookie) if err != nil { - return errors.Wrap(err, "encoding CookieValue") + return errors.Wrap(err, "encoding AuthContext") } http.SetCookie(w, &http.Cookie{ @@ -276,7 +276,7 @@ func (a *Auth) setCookie(w http.ResponseWriter, cookie *CookieValue) error { return nil } -func (a *Auth) refreshToken(w http.ResponseWriter, cookie *CookieValue) error { +func (a *Auth) refreshToken(w http.ResponseWriter, cookie *AuthContext) error { if cookie.Token.RefreshToken == "" { return errors.New("no refresh token found, check auth scopes to see if refresh tokens are being provided by your IdP") } @@ -287,7 +287,7 @@ func (a *Auth) refreshToken(w http.ResponseWriter, cookie *CookieValue) error { } if newToken.Expiry != cookie.Token.Expiry { - cv, err := a.newCookieValue(newToken) + cv, err := a.newAuthContext(newToken) if err != nil { return errors.Wrap(err, "creating cookie value from token") } diff --git a/authn/authenticate_internal_test.go b/authn/authenticate_internal_test.go index b4f17b17e..7af62092e 100644 --- a/authn/authenticate_internal_test.go +++ b/authn/authenticate_internal_test.go @@ -56,7 +56,7 @@ func TestAuth(t *testing.T) { GroupID: "abcd123-A", GroupName: "Romantic Painters", } - validCV := CookieValue{ + validCV := AuthContext{ UserID: "snowstorm", UserName: "J.M.W. Turner", GroupMembership: []Group{grp}, @@ -103,15 +103,15 @@ func TestAuth(t *testing.T) { t.Fatalf("expected error decoding block key got: %v", err) } }) - t.Run("NewCookieValue-BadAccessToken", func(t *testing.T) { - _, err := a.newCookieValue(&tokenAT) + t.Run("NewAuthContext-BadAccessToken", func(t *testing.T) { + _, err := a.newAuthContext(&tokenAT) if err == nil || !strings.Contains(err.Error(), "jwt claims") { t.Fatalf("expected failure regarding jwt claims, got: %v", err) } }) - t.Run("CookieValue-NoAccessToken", func(t *testing.T) { - _, err := a.newCookieValue(&tokenNoAT) + t.Run("AuthContext-NoAccessToken", func(t *testing.T) { + _, err := a.newAuthContext(&tokenNoAT) if err == nil || !strings.Contains(err.Error(), "access token") { t.Fatalf("expected failure regarding access token, got: %v", err) } diff --git a/http/handler_internal_test.go b/http/handler_internal_test.go index f4856e235..a073e7a38 100644 --- a/http/handler_internal_test.go +++ b/http/handler_internal_test.go @@ -246,20 +246,20 @@ func TestAuthentication(t *testing.T) { GroupName: "Romantic Painters", } - validCV := authn.CookieValue{ + validCV := authn.AuthContext{ UserID: "snowstorm", UserName: "J.M.W. Turner", GroupMembership: []authn.Group{grp}, Token: &token, } - emptyCV := authn.CookieValue{ + emptyCV := authn.AuthContext{ UserID: "narcissus", UserName: "Caravaggio", GroupMembership: []authn.Group{}, Token: &token, } - expiredCV := authn.CookieValue{ + expiredCV := authn.AuthContext{ UserID: "narcissus", UserName: "Caravaggio", GroupMembership: []authn.Group{grp}, From 3fe381ff2213f713f220e9093eaf5456da1d6de6 Mon Sep 17 00:00:00 2001 From: Samir Patel <48686912+54mir@users.noreply.github.com> Date: Wed, 5 Jan 2022 17:29:59 -0600 Subject: [PATCH 21/27] address feeback --- http/handler.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/http/handler.go b/http/handler.go index 7f9aaf014..e18a611c1 100644 --- a/http/handler.go +++ b/http/handler.go @@ -561,7 +561,8 @@ func (h *Handler) chkAuthZ(handler http.HandlerFunc, perm authz.Permission) http if h.permissions == nil { h.logger.Errorf("authentication is turned on without authorization permissions set") - http.Error(w, errors.New("authorizing").Error(), http.StatusInternalServerError) + http.Error(w, "authorizing", http.StatusInternalServerError) + return } uinfo := h.auth.GetUserInfo(w, r) @@ -779,7 +780,7 @@ func (h *Handler) filterResponse(w http.ResponseWriter, r *http.Request, schema if h.auth != nil { g := r.Context().Value(contextKeyGroupMembership) if g == nil { - http.Error(w, "not authorized", http.StatusForbidden) + http.Error(w, "Forbidden", http.StatusForbidden) return nil } indexes := h.permissions.GetAuthorizedIndexList(g.([]authn.Group), authz.Read) @@ -3503,7 +3504,7 @@ func (h *Handler) handlePostRestore(w http.ResponseWriter, r *http.Request) { func (h *Handler) handleLogin(w http.ResponseWriter, r *http.Request) { if h.auth == nil { - http.Error(w, "Auth Off", http.StatusNoContent) + http.Error(w, "", http.StatusNoContent) return } @@ -3514,7 +3515,7 @@ func (h *Handler) handleRedirect(w http.ResponseWriter, r *http.Request) { if h.auth == nil { w.Header().Add("Content-Type", "text/plain") w.WriteHeader(http.StatusNoContent) - w.Write([]byte("Auth Off")) //nolint:errcheck + w.Write([]byte("")) //nolint:errcheck return } h.auth.Redirect(w, r) @@ -3526,7 +3527,7 @@ func (h *Handler) handleCheckAuthentication(w http.ResponseWriter, r *http.Reque return } if h.auth == nil { - http.Error(w, "Auth Off", http.StatusNoContent) + http.Error(w, "", http.StatusNoContent) return } groups, err := h.auth.Authenticate(w, r) @@ -3547,7 +3548,7 @@ func (h *Handler) handleUserInfo(w http.ResponseWriter, r *http.Request) { return } if h.auth == nil { - http.Error(w, "Auth Off", http.StatusNoContent) + http.Error(w, "", http.StatusNoContent) return } if err := json.NewEncoder(w).Encode(h.auth.GetUserInfo(w, r)); err != nil { @@ -3557,7 +3558,7 @@ func (h *Handler) handleUserInfo(w http.ResponseWriter, r *http.Request) { func (h *Handler) handleLogout(w http.ResponseWriter, r *http.Request) { if h.auth == nil { - http.Error(w, "Auth Off", http.StatusNoContent) + http.Error(w, "", http.StatusNoContent) return } h.auth.Logout(w, r) From 41bde6ccba68e641e039b754aa147d7b6f899d1f Mon Sep 17 00:00:00 2001 From: Samir Patel <48686912+54mir@users.noreply.github.com> Date: Thu, 6 Jan 2022 10:35:11 -0600 Subject: [PATCH 22/27] don't write content to no content --- http/handler.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/http/handler.go b/http/handler.go index a1aa086c1..60a9e4d2c 100644 --- a/http/handler.go +++ b/http/handler.go @@ -3536,9 +3536,7 @@ func (h *Handler) handleLogin(w http.ResponseWriter, r *http.Request) { func (h *Handler) handleRedirect(w http.ResponseWriter, r *http.Request) { if h.auth == nil { - w.Header().Add("Content-Type", "text/plain") - w.WriteHeader(http.StatusNoContent) - w.Write([]byte("")) //nolint:errcheck + http.Error(w, "", http.StatusNoContent) return } h.auth.Redirect(w, r) From 5dfca76fbb29b6b340c954a46395b43e0f45eb6b Mon Sep 17 00:00:00 2001 From: kcrodgers24 Date: Thu, 6 Jan 2022 09:29:03 -0800 Subject: [PATCH 23/27] correct TLS enabled check --- server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.go b/server/server.go index 43b60f8f9..66b85ff7b 100644 --- a/server/server.go +++ b/server/server.go @@ -557,7 +557,7 @@ func (m *Command) SetupServer() error { m.Config.Postgres.Bind = "" // TLS must be enabled if auth is - if m.Config.TLS.CertificatePath == "" || m.Config.TLS.CertificateKeyPath == "" || m.Config.TLS.CACertPath == "" { + if m.Config.TLS.CertificatePath == "" || m.Config.TLS.CertificateKeyPath == "" { return fmt.Errorf("transport layer security (TLS) is not configured properly. TLS is required when AuthN/Z is enabled, current configuration: %v", m.Config.TLS) } From 1e2aa7c807ce58926b246df3061390f00060d1db Mon Sep 17 00:00:00 2001 From: Fletcher Haynes Date: Thu, 6 Jan 2022 10:36:20 -0800 Subject: [PATCH 24/27] Changed smoke test to allow failure --- .gitlab/.gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab/.gitlab-ci.yml b/.gitlab/.gitlab-ci.yml index c32893e8c..672c82a61 100644 --- a/.gitlab/.gitlab-ci.yml +++ b/.gitlab/.gitlab-ci.yml @@ -262,6 +262,7 @@ smoke test: - ./qa/scripts/teardownSmokeTest.sh needs: - job: build for linux arm64 + allow_failure: true artifacts: when: always paths: @@ -314,4 +315,4 @@ gauntlet: - ./qa/scripts/setupSamsungGauntlet.sh - ./qa/scripts/testSamsungGauntlet.sh after_script: - - ./qa/scripts/teardownSamsungGauntlet.sh \ No newline at end of file + - ./qa/scripts/teardownSamsungGauntlet.sh From bebc54b4e2701e2c012fd8dae2b05ea0013fbb7f Mon Sep 17 00:00:00 2001 From: reesporte Date: Fri, 7 Jan 2022 09:30:11 -0600 Subject: [PATCH 25/27] fix file perms to be _actually_ 600 based on staticcheck results: server/server.go:627:58: file mode '600' evaluates to 01130; did you mean '0600'? (SA9002) server/server.go:632:65: file mode '600' evaluates to 01130; did you mean '0600'? (SA9002) --- server/server.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/server.go b/server/server.go index 66b85ff7b..9bfd1baad 100644 --- a/server/server.go +++ b/server/server.go @@ -624,12 +624,12 @@ func (m *Command) setupQueryLogger() error { var err error if m.Config.Auth.QueryLogPath == "" { - f, err = logger.NewFileWriterMode("queries/query.log", 600) + f, err = logger.NewFileWriterMode("queries/query.log", 0600) if err != nil { return errors.Wrap(err, "opening file") } } else { - f, err = logger.NewFileWriterMode(m.Config.Auth.QueryLogPath, 600) + f, err = logger.NewFileWriterMode(m.Config.Auth.QueryLogPath, 0600) if err != nil { return errors.Wrap(err, "opening file") } From 5779b1c0366a4b4ff2ebe244ea2b54d866f9d1b2 Mon Sep 17 00:00:00 2001 From: pokeeffe-molecula Date: Fri, 7 Jan 2022 10:12:20 -0600 Subject: [PATCH 26/27] disable gauntlet --- .gitlab/.gitlab-ci.yml | 92 +++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/.gitlab/.gitlab-ci.yml b/.gitlab/.gitlab-ci.yml index 672c82a61..99fe1807f 100644 --- a/.gitlab/.gitlab-ci.yml +++ b/.gitlab/.gitlab-ci.yml @@ -270,49 +270,49 @@ smoke test: reports: junit: report.xml -gauntlet: - stage: gauntlet - timeout: 4h - image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest - variables: - PROFILE: "default" - AWS_SSH_PRIVATE_KEY: $AWS_FBCI_SSH_KEY - AWS_ACCESS_KEY_ID: $AWS_FBCI_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY: $AWS_FBCI_SECRET_ACCESS_KEY - TF_VAR_cluster_prefix: "" - TF_VAR_branch: "" - rules: - - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' - - if: '$CI_PIPELINE_SOURCE == "schedule" || $CI_PIPELINE_SOURCE == "web"' - before_script: - - apt-get update && apt-get install -y gnupg software-properties-common curl git - - curl -fsSL https://apt.releases.hashicorp.com/gpg | apt-key add - - - apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main" - - apt-get update && apt-get install terraform - - aws configure set aws_access_key_id $AWS_FBCI_ACCESS_KEY_ID - - aws configure set aws_secret_access_key $AWS_FBCI_SECRET_ACCESS_KEY - - aws configure set region "us-east-2" - - aws configure set aws_profile $PROFILE - - echo $AWS_FBCI_SSH_KEY > gitlab-featurebase-ci.pem - - chmod 400 gitlab-featurebase-ci.pem - - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )' - - eval $(ssh-agent -s) - - mkdir -p ~/.ssh - - echo $AWS_FBCI_SSH_KEY > /root/.ssh/gitlab-featurebase-ci.pem - - chmod 400 /root/.ssh/gitlab-featurebase-ci.pem - - echo "$AWS_FBCI_SSH_KEY" | ssh-add - - - chmod 700 /root/.ssh - - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config' - - apt update && apt -y install jq wget - - wget https://go.dev/dl/go1.17.5.linux-amd64.tar.gz - - tar -C /usr/local -xzf go1.17.5.linux-amd64.tar.gz - - export PATH=$PATH:/usr/local/go/bin - - TF_VAR_cluster_prefix="smoke-$(openssl rand -base64 12 | tr -d /=+ | cut -c -16)" - - echo "Cluster Prefix --> $TF_VAR_cluster_prefix" - - TF_VAR_branch=$CI_COMMIT_BRANCH - - echo "Branch --> $TF_VAR_branch" - script: - - ./qa/scripts/setupSamsungGauntlet.sh - - ./qa/scripts/testSamsungGauntlet.sh - after_script: - - ./qa/scripts/teardownSamsungGauntlet.sh +# gauntlet: +# stage: gauntlet +# timeout: 4h +# image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest +# variables: +# PROFILE: "default" +# AWS_SSH_PRIVATE_KEY: $AWS_FBCI_SSH_KEY +# AWS_ACCESS_KEY_ID: $AWS_FBCI_ACCESS_KEY_ID +# AWS_SECRET_ACCESS_KEY: $AWS_FBCI_SECRET_ACCESS_KEY +# TF_VAR_cluster_prefix: "" +# TF_VAR_branch: "" +# rules: +# - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' +# - if: '$CI_PIPELINE_SOURCE == "schedule" || $CI_PIPELINE_SOURCE == "web"' +# before_script: +# - apt-get update && apt-get install -y gnupg software-properties-common curl git +# - curl -fsSL https://apt.releases.hashicorp.com/gpg | apt-key add - +# - apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main" +# - apt-get update && apt-get install terraform +# - aws configure set aws_access_key_id $AWS_FBCI_ACCESS_KEY_ID +# - aws configure set aws_secret_access_key $AWS_FBCI_SECRET_ACCESS_KEY +# - aws configure set region "us-east-2" +# - aws configure set aws_profile $PROFILE +# - echo $AWS_FBCI_SSH_KEY > gitlab-featurebase-ci.pem +# - chmod 400 gitlab-featurebase-ci.pem +# - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )' +# - eval $(ssh-agent -s) +# - mkdir -p ~/.ssh +# - echo $AWS_FBCI_SSH_KEY > /root/.ssh/gitlab-featurebase-ci.pem +# - chmod 400 /root/.ssh/gitlab-featurebase-ci.pem +# - echo "$AWS_FBCI_SSH_KEY" | ssh-add - +# - chmod 700 /root/.ssh +# - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config' +# - apt update && apt -y install jq wget +# - wget https://go.dev/dl/go1.17.5.linux-amd64.tar.gz +# - tar -C /usr/local -xzf go1.17.5.linux-amd64.tar.gz +# - export PATH=$PATH:/usr/local/go/bin +# - TF_VAR_cluster_prefix="smoke-$(openssl rand -base64 12 | tr -d /=+ | cut -c -16)" +# - echo "Cluster Prefix --> $TF_VAR_cluster_prefix" +# - TF_VAR_branch=$CI_COMMIT_BRANCH +# - echo "Branch --> $TF_VAR_branch" +# script: +# - ./qa/scripts/setupSamsungGauntlet.sh +# - ./qa/scripts/testSamsungGauntlet.sh +# after_script: +# - ./qa/scripts/teardownSamsungGauntlet.sh From 219322b67521acf56eadedcc7f4ef5a2e691fe53 Mon Sep 17 00:00:00 2001 From: pokeeffe-molecula Date: Fri, 7 Jan 2022 10:43:52 -0600 Subject: [PATCH 27/27] fix ordering of rules --- .gitlab/.gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/.gitlab-ci.yml b/.gitlab/.gitlab-ci.yml index 99fe1807f..d7fa96b63 100644 --- a/.gitlab/.gitlab-ci.yml +++ b/.gitlab/.gitlab-ci.yml @@ -282,8 +282,8 @@ smoke test: # TF_VAR_cluster_prefix: "" # TF_VAR_branch: "" # rules: -# - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' # - if: '$CI_PIPELINE_SOURCE == "schedule" || $CI_PIPELINE_SOURCE == "web"' +# - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' # before_script: # - apt-get update && apt-get install -y gnupg software-properties-common curl git # - curl -fsSL https://apt.releases.hashicorp.com/gpg | apt-key add -