From a7a0d900ad9ca90f00f8ba11ada3b17a4dd2f421 Mon Sep 17 00:00:00 2001 From: mehdi Date: Mon, 27 Apr 2026 23:55:31 +0330 Subject: [PATCH 1/2] add new env to api --- cmd/api/handlers/environments.go | 123 +++++++++++++++++++++++++++++++ cmd/api/handlers/handlers.go | 6 ++ cmd/api/main.go | 6 ++ pkg/types/types.go | 10 +++ 4 files changed, 145 insertions(+) diff --git a/cmd/api/handlers/environments.go b/cmd/api/handlers/environments.go index 8fbe0136..cde1c6e8 100644 --- a/cmd/api/handlers/environments.go +++ b/cmd/api/handlers/environments.go @@ -9,6 +9,7 @@ import ( "github.com/jmpsec/osctrl/pkg/auditlog" "github.com/jmpsec/osctrl/pkg/environments" "github.com/jmpsec/osctrl/pkg/settings" + "github.com/jmpsec/osctrl/pkg/tags" "github.com/jmpsec/osctrl/pkg/types" "github.com/jmpsec/osctrl/pkg/users" "github.com/jmpsec/osctrl/pkg/utils" @@ -422,3 +423,125 @@ func (h *HandlersApi) EnvRemoveActionsHandler(w http.ResponseWriter, r *http.Req h.AuditLog.EnvAction(ctx[ctxUser], actionVar+" removal for environment "+env.Name, strings.Split(r.RemoteAddr, ":")[0], env.ID) utils.HTTPResponse(w, utils.JSONApplicationUTF8, http.StatusOK, types.ApiGenericResponse{Message: msgReturn}) } + +// EnvActionsHandler - POST Handler to perform actions (create, delete, edit) on environments +func (h *HandlersApi) EnvActionsHandler(w http.ResponseWriter, r *http.Request) { + // Debug HTTP if enabled + if h.DebugHTTPConfig.EnableHTTP { + utils.DebugHTTPDump(h.DebugHTTP, r, h.DebugHTTPConfig.ShowBody) + } + // Get context data and check access + ctx := r.Context().Value(ContextKey(contextAPI)).(ContextValue) + if !h.Users.CheckPermissions(ctx[ctxUser], users.AdminLevel, users.NoEnvironment) { + apiErrorResponse(w, "no access", http.StatusForbidden, fmt.Errorf("attempt to use API by user %s", ctx[ctxUser])) + return + } + var e types.ApiEnvRequest + // Parse request JSON body + if err := json.NewDecoder(r.Body).Decode(&e); err != nil { + apiErrorResponse(w, "error parsing POST body", http.StatusInternalServerError, err) + return + } + var msgReturn string + switch e.Action { + case "create": + // Verify request fields + if !environments.VerifyEnvFilters(e.Name, e.Icon, e.Type, e.Hostname) { + apiErrorResponse(w, "invalid data", http.StatusBadRequest, nil) + return + } + // Check if environment already exists + if !h.Envs.Exists(e.Name) { + env := h.Envs.Empty(e.Name, e.Hostname) + env.Icon = e.Icon + env.Type = e.Type + if e.UUID != "" { + env.UUID = e.UUID + } + // Empty configuration + env.Configuration = h.Envs.GenEmptyConfiguration(true) + // Generate flags + flags, err := h.Envs.GenerateFlags(env, "", "", h.OsqueryValues) + if err != nil { + apiErrorResponse(w, "error generating flags", http.StatusInternalServerError, err) + return + } + env.Flags = flags + // Create environment + if err := h.Envs.Create(&env); err != nil { + apiErrorResponse(w, "error creating environment", http.StatusInternalServerError, err) + return + } + // Generate full permissions for the user creating the environment + access := h.Users.GenEnvUserAccess([]string{env.UUID}, true, true, true, true) + perms := h.Users.GenPermissions(ctx[ctxUser], "osctrl-api", access) + if err := h.Users.CreatePermissions(perms); err != nil { + apiErrorResponse(w, "error generating permissions", http.StatusInternalServerError, err) + return + } + // Create a tag for this new environment + if !h.Tags.Exists(env.Name) { + if err := h.Tags.NewTag( + env.Name, + "Tag for environment "+env.Name, + "", + env.Icon, + ctx[ctxUser], + env.ID, + false, + tags.TagTypeEnv, + ""); err != nil { + msgReturn = fmt.Sprintf("error generating tag %s ", err.Error()) + return + } + } + msgReturn = "environment created successfully" + } else { + apiErrorResponse(w, "environment already exists", http.StatusBadRequest, fmt.Errorf("environment %s already exists", e.Name)) + return + } + case "delete": + // Verify request fields + if !environments.EnvNameFilter(e.Name) { + apiErrorResponse(w, "invalid environment name", http.StatusBadRequest, nil) + return + } + if h.Envs.Exists(e.UUID) { + if err := h.Envs.Delete(e.Name); err != nil { + apiErrorResponse(w, "error deleting environment", http.StatusInternalServerError, err) + return + } + msgReturn = "environment deleted successfully" + } else { + apiErrorResponse(w, "environment not found", http.StatusNotFound, fmt.Errorf("environment %s not found", e.Name)) + return + } + case "edit": + // Verify request fields + if !environments.EnvUUIDFilter(e.UUID) { + apiErrorResponse(w, "invalid environment UUID", http.StatusBadRequest, nil) + return + } + if !environments.HostnameFilter(e.Hostname) { + apiErrorResponse(w, "invalid hostname", http.StatusBadRequest, nil) + return + } + if h.Envs.Exists(e.UUID) { + if err := h.Envs.UpdateHostname(e.UUID, e.Hostname); err != nil { + apiErrorResponse(w, "error updating hostname", http.StatusInternalServerError, err) + return + } + msgReturn = "environment updated successfully" + } else { + apiErrorResponse(w, "environment not found", http.StatusNotFound, fmt.Errorf("environment %s not found", e.UUID)) + return + } + default: + apiErrorResponse(w, "invalid action", http.StatusBadRequest, fmt.Errorf("invalid action %s", e.Action)) + return + } + // Return serialized response + log.Debug().Msgf("Environment action %s completed: %s", e.Action, msgReturn) + h.AuditLog.EnvAction(ctx[ctxUser], e.Action+" - "+e.Name, strings.Split(r.RemoteAddr, ":")[0], auditlog.NoEnvironment) + utils.HTTPResponse(w, utils.JSONApplicationUTF8, http.StatusOK, types.ApiGenericResponse{Message: msgReturn}) +} diff --git a/cmd/api/handlers/handlers.go b/cmd/api/handlers/handlers.go index bcccc31b..4b6b5b85 100644 --- a/cmd/api/handlers/handlers.go +++ b/cmd/api/handlers/handlers.go @@ -36,6 +36,7 @@ type HandlersApi struct { ApiConfig *config.APIConfiguration DebugHTTP *zerolog.Logger DebugHTTPConfig *config.YAMLConfigurationDebug + OsqueryValues config.YAMLConfigurationOsquery } type HandlersOption func(*HandlersApi) @@ -111,6 +112,11 @@ func WithAuditLog(auditLog *auditlog.AuditLogManager) HandlersOption { h.AuditLog = auditLog } } +func WithOsqueryValues(values config.YAMLConfigurationOsquery) HandlersOption { + return func(h *HandlersApi) { + h.OsqueryValues = values + } +} func WithDebugHTTP(cfg *config.YAMLConfigurationDebug) HandlersOption { return func(h *HandlersApi) { diff --git a/cmd/api/main.go b/cmd/api/main.go index 7e8531ae..5ea20e48 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -248,6 +248,8 @@ func osctrlAPIService() { handlers.WithName(serviceName), handlers.WithAuditLog(auditLog), handlers.WithDebugHTTP(flagParams.Debug), + handlers.WithOsqueryValues(*flagParams.Osquery), + ) // ///////////////////////// API @@ -356,6 +358,10 @@ func osctrlAPIService() { muxAPI.Handle( "GET "+_apiPath(apiEnvironmentsPath), handlerAuthCheck(http.HandlerFunc(handlersApi.EnvironmentsHandler), flagParams.Service.Auth, flagParams.JWT.JWTSecret)) + muxAPI.Handle( + "POST "+_apiPath(apiEnvironmentsPath), + handlerAuthCheck(http.HandlerFunc(handlersApi.EnvActionsHandler), flagParams.Service.Auth, flagParams.JWT.JWTSecret)) + muxAPI.Handle( "GET "+_apiPath(apiEnvironmentsPath)+"/{env}", handlerAuthCheck(http.HandlerFunc(handlersApi.EnvironmentHandler), flagParams.Service.Auth, flagParams.JWT.JWTSecret)) diff --git a/pkg/types/types.go b/pkg/types/types.go index 7bfabb2d..b4dfc27b 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -110,6 +110,16 @@ type ApiActionsRequest struct { DebPkgURL string `json:"url_deb_pkg"` } +// ApiEnvRequest to receive environment action requests +type ApiEnvRequest struct { + Action string `json:"action"` + Name string `json:"name"` + UUID string `json:"uuid"` + Hostname string `json:"hostname"` + Icon string `json:"icon"` + Type string `json:"type"` +} + // ApiTagsRequest to receive tag requests type ApiTagsRequest struct { Name string `json:"name"` From a428fd2310da2b5a58b22f7e553c9feb9de78e5d Mon Sep 17 00:00:00 2001 From: mehdi Date: Tue, 28 Apr 2026 00:30:34 +0330 Subject: [PATCH 2/2] fix remove api actions --- cmd/api/handlers/environments.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/api/handlers/environments.go b/cmd/api/handlers/environments.go index cde1c6e8..50d84e89 100644 --- a/cmd/api/handlers/environments.go +++ b/cmd/api/handlers/environments.go @@ -392,24 +392,24 @@ func (h *HandlersApi) EnvRemoveActionsHandler(w http.ResponseWriter, r *http.Req var msgReturn string switch actionVar { case settings.ActionExtend: - if err := h.Envs.ExtendEnroll(env.UUID); err != nil { + if err := h.Envs.ExtendRemove(env.UUID); err != nil { apiErrorResponse(w, "error extending remove", http.StatusInternalServerError, err) return } msgReturn = "remove extended successfully" case settings.ActionExpire: - if err := h.Envs.ExpireEnroll(env.UUID); err != nil { + if err := h.Envs.ExpireRemove(env.UUID); err != nil { apiErrorResponse(w, "error expiring remove", http.StatusInternalServerError, err) return } case settings.ActionRotate: - if err := h.Envs.RotateEnroll(env.UUID); err != nil { + if err := h.Envs.RotateRemove(env.UUID); err != nil { apiErrorResponse(w, "error rotating remove", http.StatusInternalServerError, err) return } msgReturn = "remove rotated successfully" case settings.ActionNotexpire: - if err := h.Envs.NotExpireEnroll(env.UUID); err != nil { + if err := h.Envs.NotExpireRemove(env.UUID); err != nil { apiErrorResponse(w, "error setting no remove", http.StatusInternalServerError, err) return }