diff --git a/api/handlers/carves.go b/api/handlers/carves.go index cf792ba2..7ceffdc4 100644 --- a/api/handlers/carves.go +++ b/api/handlers/carves.go @@ -151,6 +151,73 @@ func (h *HandlersApi) CarvesRunHandler(w http.ResponseWriter, r *http.Request) { h.Inc(metricAPICarvesOK) } +// CarvesActionHandler - POST Handler to delete/expire a carve +func (h *HandlersApi) CarvesActionHandler(w http.ResponseWriter, r *http.Request) { + h.Inc(metricAPICarvesReq) + utils.DebugHTTPDump(r, h.Settings.DebugHTTP(settings.ServiceAPI, settings.NoEnvironmentID), false) + // Extract environment + envVar := r.PathValue("env") + if envVar == "" { + apiErrorResponse(w, "error with environment", http.StatusBadRequest, nil) + h.Inc(metricAPICarvesErr) + return + } + // Get environment + env, err := h.Envs.GetByUUID(envVar) + if err != nil { + apiErrorResponse(w, "error getting environment", http.StatusInternalServerError, nil) + h.Inc(metricAPICarvesErr) + return + } + // Get context data and check access + ctx := r.Context().Value(contextKey(contextAPI)).(contextValue) + if !h.Users.CheckPermissions(ctx[ctxUser], users.AdminLevel, env.UUID) { + apiErrorResponse(w, "no access", http.StatusForbidden, fmt.Errorf("attempt to use API by user %s", ctx[ctxUser])) + h.Inc(metricAPICarvesErr) + return + } + var msgReturn string + // Carve can not be empty + nameVar := r.PathValue("name") + if nameVar == "" { + apiErrorResponse(w, "name can not be empty", http.StatusBadRequest, nil) + h.Inc(metricAPICarvesErr) + return + } + // Check if carve exists + if !h.Queries.Exists(nameVar, env.ID) { + apiErrorResponse(w, "carve not found", http.StatusNotFound, nil) + h.Inc(metricAPICarvesErr) + return + } + // Extract action + actionVar := r.PathValue("action") + if actionVar == "" { + apiErrorResponse(w, "error getting action", http.StatusBadRequest, nil) + h.Inc(metricAPICarvesErr) + return + } + switch actionVar { + case settings.CarveDelete: + if err := h.Queries.Delete(nameVar, env.ID); err != nil { + apiErrorResponse(w, "error deleting carve", http.StatusInternalServerError, err) + h.Inc(metricAPICarvesErr) + return + } + msgReturn = fmt.Sprintf("carve %s deleted successfully", nameVar) + case settings.CarveExpire: + if err := h.Queries.Expire(nameVar, env.ID); err != nil { + apiErrorResponse(w, "error expiring carve", http.StatusInternalServerError, err) + h.Inc(metricAPICarvesErr) + return + } + msgReturn = fmt.Sprintf("carve %s expired successfully", nameVar) + } + // Return message as serialized response + utils.HTTPResponse(w, utils.JSONApplicationUTF8, http.StatusOK, types.ApiGenericResponse{Message: msgReturn}) + h.Inc(metricAPICarvesOK) +} + // GET Handler to return carves in JSON func (h *HandlersApi) apiCarvesShowHandler(w http.ResponseWriter, r *http.Request) { h.Inc(metricAPICarvesReq) diff --git a/api/handlers/queries.go b/api/handlers/queries.go index e3c41134..d9c1df88 100644 --- a/api/handlers/queries.go +++ b/api/handlers/queries.go @@ -216,6 +216,73 @@ func (h *HandlersApi) QueriesRunHandler(w http.ResponseWriter, r *http.Request) h.Inc(metricAPIQueriesOK) } +// QueriesActionHandler - POST Handler to delete/expire a query +func (h *HandlersApi) QueriesActionHandler(w http.ResponseWriter, r *http.Request) { + h.Inc(metricAPIQueriesReq) + utils.DebugHTTPDump(r, h.Settings.DebugHTTP(settings.ServiceAPI, settings.NoEnvironmentID), false) + // Extract environment + envVar := r.PathValue("env") + if envVar == "" { + apiErrorResponse(w, "error with environment", http.StatusBadRequest, nil) + h.Inc(metricAPIQueriesErr) + return + } + // Get environment + env, err := h.Envs.GetByUUID(envVar) + if err != nil { + apiErrorResponse(w, "error getting environment", http.StatusInternalServerError, nil) + h.Inc(metricAPIQueriesErr) + return + } + // Get context data and check access + ctx := r.Context().Value(contextKey(contextAPI)).(contextValue) + if !h.Users.CheckPermissions(ctx[ctxUser], users.AdminLevel, env.UUID) { + apiErrorResponse(w, "no access", http.StatusForbidden, fmt.Errorf("attempt to use API by user %s", ctx[ctxUser])) + h.Inc(metricAPIQueriesErr) + return + } + var msgReturn string + // Extract action + actionVar := r.PathValue("action") + if actionVar == "" { + apiErrorResponse(w, "error getting action", http.StatusBadRequest, nil) + h.Inc(metricAPIQueriesErr) + return + } + // Query can not be empty + nameVar := r.PathValue("name") + if nameVar == "" { + apiErrorResponse(w, "name can not be empty", http.StatusBadRequest, nil) + h.Inc(metricAPIQueriesErr) + return + } + // Check if query exists + if !h.Queries.Exists(nameVar, env.ID) { + apiErrorResponse(w, "query not found", http.StatusNotFound, nil) + h.Inc(metricAPIQueriesErr) + return + } + switch actionVar { + case settings.QueryDelete: + if err := h.Queries.Delete(nameVar, env.ID); err != nil { + apiErrorResponse(w, "error deleting query", http.StatusInternalServerError, err) + h.Inc(metricAPIQueriesErr) + return + } + msgReturn = fmt.Sprintf("query %s deleted successfully", nameVar) + case settings.QueryExpire: + if err := h.Queries.Expire(nameVar, env.ID); err != nil { + apiErrorResponse(w, "error expiring query", http.StatusInternalServerError, err) + h.Inc(metricAPIQueriesErr) + return + } + msgReturn = fmt.Sprintf("query %s expired successfully", nameVar) + } + // Return message as serialized response + utils.HTTPResponse(w, utils.JSONApplicationUTF8, http.StatusOK, types.ApiGenericResponse{Message: msgReturn}) + h.Inc(metricAPIQueriesOK) +} + // AllQueriesShowHandler - GET Handler to return all queries in JSON func (h *HandlersApi) AllQueriesShowHandler(w http.ResponseWriter, r *http.Request) { h.Inc(metricAPIQueriesReq) diff --git a/api/main.go b/api/main.go index 93c0d290..51ee027f 100644 --- a/api/main.go +++ b/api/main.go @@ -574,10 +574,12 @@ func osctrlAPIService() { muxAPI.Handle("GET "+_apiPath(apiQueriesPath)+"/{env}/{name}", handlerAuthCheck(http.HandlerFunc(handlersApi.QueryShowHandler))) muxAPI.Handle("GET "+_apiPath(apiQueriesPath)+"/{env}/results/{name}", handlerAuthCheck(http.HandlerFunc(handlersApi.QueryResultsHandler))) muxAPI.Handle("GET "+_apiPath(apiAllQueriesPath+"/{env}"), handlerAuthCheck(http.HandlerFunc(handlersApi.AllQueriesShowHandler))) + muxAPI.Handle("POST "+_apiPath(apiQueriesPath)+"/{env}/{action}/{name}", handlerAuthCheck(http.HandlerFunc(handlersApi.QueriesActionHandler))) // API: carves by environment muxAPI.Handle("GET "+_apiPath(apiCarvesPath)+"/{env}", handlerAuthCheck(http.HandlerFunc(handlersApi.CarveShowHandler))) muxAPI.Handle("POST "+_apiPath(apiCarvesPath)+"/{env}", handlerAuthCheck(http.HandlerFunc(handlersApi.CarvesRunHandler))) muxAPI.Handle("GET "+_apiPath(apiCarvesPath)+"/{env}/{name}", handlerAuthCheck(http.HandlerFunc(handlersApi.CarveShowHandler))) + muxAPI.Handle("POST "+_apiPath(apiCarvesPath)+"/{env}/{action}/{name}", handlerAuthCheck(http.HandlerFunc(handlersApi.CarvesActionHandler))) // API: users muxAPI.Handle("GET "+_apiPath(apiUsersPath)+"/{username}", handlerAuthCheck(http.HandlerFunc(handlersApi.UserHandler))) muxAPI.Handle("GET "+_apiPath(apiUsersPath), handlerAuthCheck(http.HandlerFunc(handlersApi.UsersHandler))) diff --git a/osctrl-api.yaml b/osctrl-api.yaml index 621a4bd7..c4cece0a 100644 --- a/osctrl-api.yaml +++ b/osctrl-api.yaml @@ -461,6 +461,12 @@ paths: description: Returns the requested on-demand query results by name and environment operationId: QueryResultsHandler parameters: + - name: env + in: path + description: Name or UUID of the requested osctrl environment + required: true + schema: + type: string - name: name in: path description: Name of the requested on-demand query @@ -495,6 +501,66 @@ paths: security: - Authorization: - query + /queries/{env}/{action}/{name}: + post: + tags: + - queries + summary: Execute action on on-demand query + description: Executes an action (delete/expire) in the on-demand query by name and environment + operationId: QueriesActionHandler + parameters: + - name: env + in: path + description: Name or UUID of the requested osctrl environment + required: true + schema: + type: string + - name: action + in: path + description: Action to execute (delete, expire) + required: true + schema: + type: string + - name: name + in: path + description: Name of the requested on-demand query + required: true + schema: + type: string + responses: + 200: + description: successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/ApiGenericResponse" + 400: + description: bad request + content: + application/json: + schema: + $ref: "#/components/schemas/ApiErrorResponse" + 403: + description: no access + content: + application/json: + schema: + $ref: "#/components/schemas/ApiErrorResponse" + 404: + description: no queries + content: + application/json: + schema: + $ref: "#/components/schemas/ApiErrorResponse" + 500: + description: error getting queries + content: + application/json: + schema: + $ref: "#/components/schemas/ApiErrorResponse" + security: + - Authorization: + - admin /all-queries/{env}: get: tags: @@ -542,7 +608,7 @@ paths: /carves/{env}: get: tags: - - queries + - carves summary: Get file carves description: Returns all file carves by environment operationId: CarvesShowHandler @@ -625,7 +691,7 @@ paths: /carves/{env}/{name}: get: tags: - - queries + - carves summary: Get a file carve description: Returns a file carve by environment and name operationId: CarveShowHandler @@ -672,6 +738,66 @@ paths: security: - Authorization: - carve + /carves/{env}/{action}/{name}: + post: + tags: + - carves + summary: Execute action on file carve + description: Executes an action (delete/expire) in the file carve by name and environment + operationId: CarvesActionHandler + parameters: + - name: env + in: path + description: Name or UUID of the requested osctrl environment + required: true + schema: + type: string + - name: action + in: path + description: Action to execute (delete, expire) + required: true + schema: + type: string + - name: name + in: path + description: Name of the requested file carve + required: true + schema: + type: string + responses: + 200: + description: successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/ApiGenericResponse" + 400: + description: bad request + content: + application/json: + schema: + $ref: "#/components/schemas/ApiErrorResponse" + 403: + description: no access + content: + application/json: + schema: + $ref: "#/components/schemas/ApiErrorResponse" + 404: + description: no queries + content: + application/json: + schema: + $ref: "#/components/schemas/ApiErrorResponse" + 500: + description: error getting queries + content: + application/json: + schema: + $ref: "#/components/schemas/ApiErrorResponse" + security: + - Authorization: + - admin /users: get: tags: diff --git a/queries/queries.go b/queries/queries.go index 9d262ef1..5b20e230 100644 --- a/queries/queries.go +++ b/queries/queries.go @@ -247,6 +247,13 @@ func (q *Queries) Gets(target, qtype string, envid uint) ([]DistributedQuery, er return queries, nil } +// Checks if a query exists in an environment, regardless of the status +func (q *Queries) Exists(name string, envid uint) bool { + var count int64 + q.DB.Model(&DistributedQuery{}).Where("name = ? AND environment_id = ?", name, envid).Count(&count) + return (count > 0) +} + // GetActive all active queries and carves by target func (q *Queries) GetActive(envid uint) ([]DistributedQuery, error) { var queries []DistributedQuery diff --git a/settings/settings.go b/settings/settings.go index c966b682..6bfba35a 100644 --- a/settings/settings.go +++ b/settings/settings.go @@ -73,6 +73,14 @@ const ( SetRpmPackage string = "set_rpm" ) +// Types of query/carve actions +const ( + QueryDelete string = "delete" + QueryExpire string = "expire" + CarveDelete string = QueryDelete + CarveExpire string = QueryExpire +) + // Types of package const ( PackageDeb string = "deb"