From 6393035e5554d95f388104f33f2e1fb8f6d1fdb9 Mon Sep 17 00:00:00 2001 From: Christoph Kluge Date: Thu, 3 Aug 2023 17:47:09 +0200 Subject: [PATCH] first iteraton of implementing ip-secured enpoint --- internal/api/rest.go | 79 ++++++++++++++++++++++++++++++++++++++++++++ pkg/schema/config.go | 3 ++ 2 files changed, 82 insertions(+) diff --git a/internal/api/rest.go b/internal/api/rest.go index c199bc2..c1f6fd1 100644 --- a/internal/api/rest.go +++ b/internal/api/rest.go @@ -20,6 +20,7 @@ import ( "time" "github.com/ClusterCockpit/cc-backend/internal/auth" + "github.com/ClusterCockpit/cc-backend/internal/config" "github.com/ClusterCockpit/cc-backend/internal/graph" "github.com/ClusterCockpit/cc-backend/internal/graph/model" "github.com/ClusterCockpit/cc-backend/internal/importer" @@ -75,6 +76,8 @@ func (api *RestApi) MountRoutes(r *mux.Router) { r.HandleFunc("/jobs/delete_job/", api.deleteJobByRequest).Methods(http.MethodDelete) r.HandleFunc("/jobs/delete_job/{id}", api.deleteJobById).Methods(http.MethodDelete) r.HandleFunc("/jobs/delete_job_before/{ts}", api.deleteJobBefore).Methods(http.MethodDelete) + r.HandleFunc("/secured/addProject/{id}/{project}", api.secureUpdateUser).Methods(http.MethodPost) + r.HandleFunc("/secured/addRole/{id}/{role}", api.secureUpdateUser).Methods(http.MethodPost) if api.Authentication != nil { r.HandleFunc("/jwt/", api.getJWT).Methods(http.MethodGet) @@ -103,6 +106,11 @@ type DeleteJobApiResponse struct { Message string `json:"msg"` } +// UpdateUserApiResponse model +type UpdateUserApiResponse struct { + Message string `json:"msg"` +} + // StopJobApiRequest model type StopJobApiRequest struct { // Stop Time of job as epoch @@ -1043,6 +1051,77 @@ func (api *RestApi) updateUser(rw http.ResponseWriter, r *http.Request) { } } +func (api *RestApi) secureUpdateUser(rw http.ResponseWriter, r *http.Request) { + if user := auth.GetUser(r.Context()); user != nil && !user.HasRole(auth.RoleApi) { + handleError(fmt.Errorf("missing role: %v", auth.GetRoleString(auth.RoleApi)), http.StatusForbidden, rw) + return + } + + // If nothing declared in config: deny all request to this endpint + if config.Keys.ApiAllowedAddrs == nil || len(config.Keys.ApiAllowedAddrs) == 0 { + handleError(fmt.Errorf("denied by default policy!"), http.StatusForbidden, rw) + return + } + + // IP CHECK HERE (WIP) + // Probably better as private routine + IPAddress := r.Header.Get("X-Real-Ip") + if IPAddress == "" { + IPAddress = r.Header.Get("X-Forwarded-For") + } + if IPAddress == "" { + IPAddress = r.RemoteAddr + } + + // Also This + ipOk := false + for _, a := range config.Keys.ApiAllowedAddrs { + if a == IPAddress { + ipOk = true + } + } + + if IPAddress == "" || ipOk == false { + handleError(fmt.Errorf("unknown ip: %v", IPAddress), http.StatusForbidden, rw) + return + } + // IP CHECK END + + // Get Values + id := mux.Vars(r)["id"] + newproj := mux.Vars(r)["project"] + newrole := mux.Vars(r)["role"] + + // TODO: Handle anything but roles... + if newrole != "" { + if err := api.Authentication.AddRole(r.Context(), id, newrole); err != nil { + handleError(errors.New(err.Error()), http.StatusUnprocessableEntity, rw) + return + } + + rw.Header().Add("Content-Type", "application/json") + rw.WriteHeader(http.StatusOK) + json.NewEncoder(rw).Encode(UpdateUserApiResponse{ + Message: fmt.Sprintf("Successfully added role %s to %s", newrole, id), + }) + + } else if newproj != "" { + if err := api.Authentication.AddProject(r.Context(), id, newproj); err != nil { + handleError(errors.New(err.Error()), http.StatusUnprocessableEntity, rw) + return + } + + rw.Header().Add("Content-Type", "application/json") + rw.WriteHeader(http.StatusOK) + json.NewEncoder(rw).Encode(UpdateUserApiResponse{ + Message: fmt.Sprintf("Successfully added project %s to %s", newproj, id), + }) + + } else { + handleError(errors.New("Not Add [role|project]?"), http.StatusBadRequest, rw) + } +} + func (api *RestApi) updateConfiguration(rw http.ResponseWriter, r *http.Request) { rw.Header().Set("Content-Type", "text/plain") key, value := r.FormValue("key"), r.FormValue("value") diff --git a/pkg/schema/config.go b/pkg/schema/config.go index 9a88ea2..30515dd 100644 --- a/pkg/schema/config.go +++ b/pkg/schema/config.go @@ -69,6 +69,9 @@ type ProgramConfig struct { // Address where the http (or https) server will listen on (for example: 'localhost:80'). Addr string `json:"addr"` + // Addresses from which the /secured/* API endpoints can be reached + ApiAllowedAddrs []string `json:"apiAllowedAddrs"` + // Drop root permissions once .env was read and the port was taken. User string `json:"user"` Group string `json:"group"`