From a5e356a584d4e87585962da39ea52768f9d4b0eb Mon Sep 17 00:00:00 2001 From: Javier Marcos <1271349+javuto@users.noreply.github.com> Date: Thu, 18 Sep 2025 20:44:00 +0200 Subject: [PATCH] Query and carve based on tags --- cmd/admin/handlers/json-carves.go | 12 +- cmd/admin/handlers/json-nodes.go | 63 ----- cmd/admin/handlers/json-queries.go | 12 +- cmd/admin/handlers/post.go | 148 +++++++----- cmd/admin/handlers/templates.go | 43 ++-- cmd/admin/handlers/types-requests.go | 2 + cmd/admin/handlers/types-templates.go | 10 +- cmd/admin/handlers/utils.go | 118 +++++++--- cmd/admin/static/js/carves.js | 35 ++- cmd/admin/static/js/query.js | 39 +--- cmd/admin/templates/carves-details.html | 75 ++---- cmd/admin/templates/carves-run.html | 210 +++++------------ cmd/admin/templates/carves.html | 23 +- cmd/admin/templates/queries-logs.html | 72 ++---- cmd/admin/templates/queries-run.html | 291 ++++++------------------ cmd/admin/templates/queries.html | 23 +- cmd/api/handlers/carves.go | 2 +- cmd/api/handlers/queries.go | 7 +- cmd/cli/carve.go | 11 +- cmd/cli/query.go | 2 +- pkg/nodes/nodes.go | 64 ++++-- pkg/queries/queries.go | 3 +- pkg/queries/saved.go | 4 +- pkg/tags/tags.go | 9 + pkg/tags/utils.go | 22 ++ pkg/tags/utils_test.go | 6 + pkg/types/types.go | 1 + 27 files changed, 505 insertions(+), 802 deletions(-) diff --git a/cmd/admin/handlers/json-carves.go b/cmd/admin/handlers/json-carves.go index 7851d6c1..cf6c54a5 100644 --- a/cmd/admin/handlers/json-carves.go +++ b/cmd/admin/handlers/json-carves.go @@ -115,16 +115,6 @@ func (h *HandlersAdmin) JSONCarvesHandler(w http.ResponseWriter, r *http.Request data := make(CarveData) data["path"] = q.Path data["name"] = q.Name - // Preparing query targets - ts, _ := h.Queries.GetTargets(q.Name) - _ts := []CarveTarget{} - for _, t := range ts { - _t := CarveTarget{ - Type: t.Type, - Value: t.Value, - } - _ts = append(_ts, _t) - } // Preparing JSON _c := CarveJSON{ Name: q.Name, @@ -136,7 +126,7 @@ func (h *HandlersAdmin) JSONCarvesHandler(w http.ResponseWriter, r *http.Request }, Status: status, Progress: progress, - Targets: _ts, + Targets: parseCarveTarget(q.Target), } cJSON = append(cJSON, _c) } diff --git a/cmd/admin/handlers/json-nodes.go b/cmd/admin/handlers/json-nodes.go index 8f0016b6..139da3b4 100644 --- a/cmd/admin/handlers/json-nodes.go +++ b/cmd/admin/handlers/json-nodes.go @@ -111,66 +111,3 @@ func (h *HandlersAdmin) JSONEnvironmentHandler(w http.ResponseWriter, r *http.Re // Serve JSON utils.HTTPResponse(w, utils.JSONApplicationUTF8, http.StatusOK, returned) } - -// JSONPlatformHandler - Handler for JSON endpoints by platform -func (h *HandlersAdmin) JSONPlatformHandler(w http.ResponseWriter, r *http.Request) { - if h.DebugHTTPConfig.Enabled { - utils.DebugHTTPDump(h.DebugHTTP, r, h.DebugHTTPConfig.ShowBody) - } - // Get context data - ctx := r.Context().Value(sessions.ContextKey(sessions.CtxSession)).(sessions.ContextValue) - // Check permissions - if !h.Users.CheckPermissions(ctx[sessions.CtxUser], users.AdminLevel, users.NoEnvironment) { - log.Info().Msgf("%s has insufficient permissions", ctx[sessions.CtxUser]) - return - } - // Extract platform - platform := r.PathValue("platform") - if platform == "" { - log.Info().Msg("error getting platform") - return - } - // Extract target - target := r.PathValue("target") - if target == "" { - log.Info().Msg("error getting target") - return - } - // Verify target - if !NodeTargets[target] { - log.Info().Msgf("invalid target %s", target) - return - } - nodes, err := h.Nodes.GetByPlatform(platform, target, h.Settings.InactiveHours(settings.NoEnvironmentID)) - if err != nil { - log.Err(err).Msg("error getting nodes") - return - } - // Prepare data to be returned - var nJSON []NodeJSON - for _, n := range nodes { - nj := NodeJSON{ - UUID: n.UUID, - Username: n.Username, - Localname: n.Localname, - IP: n.IPAddress, - Platform: n.Platform, - Version: n.PlatformVersion, - Osquery: n.OsqueryVersion, - LastSeen: CreationTimes{ - Display: utils.PastFutureTimes(n.UpdatedAt), - Timestamp: utils.TimeTimestamp(n.UpdatedAt), - }, - FirstSeen: CreationTimes{ - Display: utils.PastFutureTimes(n.CreatedAt), - Timestamp: utils.TimeTimestamp(n.CreatedAt), - }, - } - nJSON = append(nJSON, nj) - } - returned := ReturnedNodes{ - Data: nJSON, - } - // Serve JSON - utils.HTTPResponse(w, utils.JSONApplicationUTF8, http.StatusOK, returned) -} diff --git a/cmd/admin/handlers/json-queries.go b/cmd/admin/handlers/json-queries.go index 867f881c..5b566d43 100644 --- a/cmd/admin/handlers/json-queries.go +++ b/cmd/admin/handlers/json-queries.go @@ -84,16 +84,6 @@ func (h *HandlersAdmin) JSONQueryJSON(q queries.DistributedQuery, env string) Qu if q.Expired { status = queries.StatusExpired } - // Preparing query targets - ts, _ := h.Queries.GetTargets(q.Name) - _ts := []QueryTarget{} - for _, t := range ts { - _t := QueryTarget{ - Type: t.Type, - Value: t.Value, - } - _ts = append(_ts, _t) - } return QueryJSON{ Creator: q.Creator, Name: q.Name, @@ -104,7 +94,7 @@ func (h *HandlersAdmin) JSONQueryJSON(q queries.DistributedQuery, env string) Qu }, Status: status, Progress: progress, - Targets: _ts, + Targets: parseQueryTarget(q.Target), } } diff --git a/cmd/admin/handlers/post.go b/cmd/admin/handlers/post.go index b95531ed..e29c0f5a 100644 --- a/cmd/admin/handlers/post.go +++ b/cmd/admin/handlers/post.go @@ -125,14 +125,8 @@ func (h *HandlersAdmin) QueryRunPOSTHandler(w http.ResponseWriter, r *http.Reque if q.ExpHours == 0 { expTime = time.Time{} } - newQuery := newQueryReady(ctx[sessions.CtxUser], q.Query, expTime, env.ID) - if err := h.Queries.Create(newQuery); err != nil { - adminErrorResponse(w, "error creating query", http.StatusInternalServerError, err) - return - } - // Get the query id - newQuery, err = h.Queries.Get(newQuery.Name, env.ID) - if err != nil { + newQuery := newQueryReady(ctx[sessions.CtxUser], q.Query, expTime, env.ID, q) + if err := h.Queries.Create(&newQuery); err != nil { adminErrorResponse(w, "error creating query", http.StatusInternalServerError, err) return } @@ -140,12 +134,13 @@ func (h *HandlersAdmin) QueryRunPOSTHandler(w http.ResponseWriter, r *http.Reque var expected []uint targetNodesID := []uint{} // TODO: Refactor this to use osctrl-api instead of direct DB queries - // Create environment target + // Extract targets by environment if len(q.Environments) > 0 { expected = []uint{} for _, e := range q.Environments { + // TODO: Check if user has permissions to query the environment if (e != "") && h.Envs.Exists(e) { - nodes, err := h.Nodes.GetByEnv(e, "active", h.Settings.InactiveHours(settings.NoEnvironmentID)) + nodes, err := h.Nodes.GetByEnv(e, nodes.ActiveNodes, h.Settings.InactiveHours(settings.NoEnvironmentID)) if err != nil { adminErrorResponse(w, "error getting nodes by environment", http.StatusInternalServerError, err) return @@ -160,10 +155,10 @@ func (h *HandlersAdmin) QueryRunPOSTHandler(w http.ResponseWriter, r *http.Reque // Create platform target if len(q.Platforms) > 0 { expected = []uint{} - platforms, _ := h.Nodes.GetAllPlatforms() + platforms, _ := h.Nodes.GetEnvIDPlatforms(env.ID) for _, p := range q.Platforms { if (p != "") && checkValidPlatform(platforms, p) { - nodes, err := h.Nodes.GetByPlatform(p, "active", h.Settings.InactiveHours(settings.NoEnvironmentID)) + nodes, err := h.Nodes.GetByPlatform(env.ID, p, nodes.ActiveNodes, h.Settings.InactiveHours(settings.NoEnvironmentID)) if err != nil { adminErrorResponse(w, "error getting nodes by platform", http.StatusInternalServerError, err) return @@ -205,7 +200,26 @@ func (h *HandlersAdmin) QueryRunPOSTHandler(w http.ResponseWriter, r *http.Reque } targetNodesID = utils.Intersect(targetNodesID, expected) } - + // Create tags target + if len(q.Tags) > 0 { + expected = []uint{} + for _, _t := range q.Tags { + if _t != "" { + exist, tag := h.Tags.ExistsGet(tags.GetStrTagName(_t), env.ID) + if exist { + tagged, err := h.Tags.GetTaggedNodes(tag) + if err != nil { + log.Err(err).Msgf("error getting tagged nodes for tag %s", _t) + continue + } + for _, tn := range tagged { + expected = append(expected, tn.NodeID) + } + } + } + } + targetNodesID = utils.Intersect(targetNodesID, expected) + } // If the list is empty, we don't need to create node queries if len(targetNodesID) != 0 { if err := h.Queries.CreateNodeQueries(targetNodesID, newQuery.ID); err != nil { @@ -273,101 +287,115 @@ func (h *HandlersAdmin) CarvesRunPOSTHandler(w http.ResponseWriter, r *http.Requ adminErrorResponse(w, "path can not be empty", http.StatusInternalServerError, nil) return } - query := generateCarveQuery(c.Path, false) - // Prepare and create new carve - carveName := generateCarveName() // Set query expiration expTime := queries.QueryExpiration(c.ExpHours) if c.ExpHours == 0 { expTime = time.Time{} } - newQuery := queries.DistributedQuery{ - Query: query, - Name: carveName, - Creator: ctx[sessions.CtxUser], - Expected: 0, - Executions: 0, - Active: true, - Completed: false, - Deleted: false, - Expired: false, - Expiration: expTime, - Type: queries.CarveQueryType, - Path: c.Path, - EnvironmentID: env.ID, - } - if err := h.Queries.Create(newQuery); err != nil { + newQuery := newCarveReady(ctx[sessions.CtxUser], c.Path, expTime, env.ID, c) + if err := h.Queries.Create(&newQuery); err != nil { adminErrorResponse(w, "error creating carve", http.StatusInternalServerError, err) return } - // Temporary list of UUIDs to calculate Expected - var expected []string - // Create environment target + // List all the nodes that match the query + var expected []uint + targetNodesID := []uint{} + // Extract targets by environment if len(c.Environments) > 0 { + expected = []uint{} for _, e := range c.Environments { + // TODO: Check if user has permissions to query the environment if (e != "") && h.Envs.Exists(e) { - if err := h.Queries.CreateTarget(carveName, queries.QueryTargetEnvironment, e); err != nil { - adminErrorResponse(w, "error creating carve environment target", http.StatusInternalServerError, err) - return - } - nodes, err := h.Nodes.GetByEnv(e, "active", h.Settings.InactiveHours(settings.NoEnvironmentID)) + nodes, err := h.Nodes.GetByEnv(e, nodes.ActiveNodes, h.Settings.InactiveHours(settings.NoEnvironmentID)) if err != nil { adminErrorResponse(w, "error getting nodes by environment", http.StatusInternalServerError, err) return } for _, n := range nodes { - expected = append(expected, n.UUID) + expected = append(expected, n.ID) } } } + targetNodesID = utils.Intersect(targetNodesID, expected) } // Create platform target if len(c.Platforms) > 0 { - platforms, _ := h.Nodes.GetAllPlatforms() + expected = []uint{} + platforms, _ := h.Nodes.GetEnvIDPlatforms(env.ID) for _, p := range c.Platforms { if (p != "") && checkValidPlatform(platforms, p) { - if err := h.Queries.CreateTarget(carveName, queries.QueryTargetPlatform, p); err != nil { - adminErrorResponse(w, "error creating carve platform target", http.StatusInternalServerError, err) - return - } - nodes, err := h.Nodes.GetByPlatform(p, "active", h.Settings.InactiveHours(settings.NoEnvironmentID)) + nodes, err := h.Nodes.GetByPlatform(env.ID, p, nodes.ActiveNodes, h.Settings.InactiveHours(settings.NoEnvironmentID)) if err != nil { adminErrorResponse(w, "error getting nodes by platform", http.StatusInternalServerError, err) return } for _, n := range nodes { - expected = append(expected, n.UUID) + expected = append(expected, n.ID) } } } + targetNodesID = utils.Intersect(targetNodesID, expected) } // Create UUIDs target if len(c.UUIDs) > 0 { + expected = []uint{} for _, u := range c.UUIDs { - if (u != "") && h.Nodes.CheckByUUID(u) { - if err := h.Queries.CreateTarget(carveName, queries.QueryTargetUUID, u); err != nil { - adminErrorResponse(w, "error creating carve UUID target", http.StatusInternalServerError, err) - return + if u != "" { + node, err := h.Nodes.GetByUUID(u) + if err != nil { + log.Err(err).Msgf("error getting node %s and failed to create carve query for it", u) + continue } - expected = append(expected, u) + expected = append(expected, node.ID) } } + targetNodesID = utils.Intersect(targetNodesID, expected) } // Create hostnames target if len(c.Hosts) > 0 { + expected = []uint{} for _, _h := range c.Hosts { - if (_h != "") && h.Nodes.CheckByHost(_h) { - if err := h.Queries.CreateTarget(carveName, queries.QueryTargetLocalname, _h); err != nil { - adminErrorResponse(w, "error creating carve hostname target", http.StatusInternalServerError, err) - return + if _h != "" { + node, err := h.Nodes.GetByIdentifier(_h) + if err != nil { + log.Err(err).Msgf("error getting node %s and failed to create carve query for it", _h) + continue } + expected = append(expected, node.ID) } } + targetNodesID = utils.Intersect(targetNodesID, expected) + } + // Create tags target + if len(c.Tags) > 0 { + expected = []uint{} + for _, _t := range c.Tags { + if _t != "" { + exist, tag := h.Tags.ExistsGet(tags.GetStrTagName(_t), env.ID) + if exist { + tagged, err := h.Tags.GetTaggedNodes(tag) + if err != nil { + log.Err(err).Msgf("error getting tagged nodes for tag %s", _t) + continue + } + for _, tn := range tagged { + expected = append(expected, tn.NodeID) + } + } + } + } + targetNodesID = utils.Intersect(targetNodesID, expected) + } + // If the list is empty, we don't need to create node queries + if len(targetNodesID) != 0 { + if err := h.Queries.CreateNodeQueries(targetNodesID, newQuery.ID); err != nil { + log.Err(err).Msgf("error creating node queries for carve %s", newQuery.Name) + adminErrorResponse(w, "error creating node queries", http.StatusInternalServerError, err) + return + } } - // Remove duplicates from expected - expectedClear := removeStringDuplicates(expected) // Update value for expected - if err := h.Queries.SetExpected(carveName, len(expectedClear), env.ID); err != nil { + if err := h.Queries.SetExpected(newQuery.Name, len(targetNodesID), env.ID); err != nil { adminErrorResponse(w, "error setting expected", http.StatusInternalServerError, err) return } diff --git a/cmd/admin/handlers/templates.go b/cmd/admin/handlers/templates.go index caba2dc9..8a778d3a 100644 --- a/cmd/admin/handlers/templates.go +++ b/cmd/admin/handlers/templates.go @@ -301,6 +301,12 @@ func (h *HandlersAdmin) QueryRunGETHandler(w http.ResponseWriter, r *http.Reques uuids = append(uuids, n.UUID) hosts = append(hosts, n.Localname) } + // Get all tags + allTags, err := h.Tags.GetByEnv(env.ID) + if err != nil { + log.Err(err).Msg("error getting tags") + return + } // Get if the user is admin user, err := h.Users.Get(ctx[sessions.CtxUser]) if err != nil { @@ -309,13 +315,15 @@ func (h *HandlersAdmin) QueryRunGETHandler(w http.ResponseWriter, r *http.Reques } // Prepare template data templateData := QueryRunTemplateData{ - Title: "Query osquery Nodes in " + env.Name + "", + Title: "Query osquery Nodes in " + env.Name, EnvUUID: env.UUID, + EnvName: env.Name, Metadata: h.TemplateMetadata(ctx, h.ServiceMetadata, user.Admin), Environments: h.allowedEnvironments(ctx[sessions.CtxUser], envAll), Platforms: platforms, UUIDs: uuids, Hosts: hosts, + Tags: tags.TagsToStrings(allTags), Tables: h.OsqueryTables, TablesVersion: h.OsqueryVersion, } @@ -377,7 +385,7 @@ func (h *HandlersAdmin) QueryListGETHandler(w http.ResponseWriter, r *http.Reque } // Prepare template data templateData := QueryTableTemplateData{ - Title: "All on-demand queries", + Title: "On-demand queries in " + env.Name, EnvUUID: env.UUID, Metadata: h.TemplateMetadata(ctx, h.ServiceMetadata, user.Admin), Environments: h.allowedEnvironments(ctx[sessions.CtxUser], envAll), @@ -436,7 +444,7 @@ func (h *HandlersAdmin) SavedQueriesGETHandler(w http.ResponseWriter, r *http.Re } // Prepare template data templateData := SavedQueriesTemplateData{ - Title: "Saved queries in " + env.Name + "", + Title: "Saved queries in " + env.Name, EnvUUID: env.UUID, Metadata: h.TemplateMetadata(ctx, h.ServiceMetadata, user.Admin), Environments: h.allowedEnvironments(ctx[sessions.CtxUser], envAll), @@ -511,15 +519,23 @@ func (h *HandlersAdmin) CarvesRunGETHandler(w http.ResponseWriter, r *http.Reque log.Err(err).Msg("error getting user") return } + // Get all tags + allTags, err := h.Tags.GetByEnv(env.ID) + if err != nil { + log.Err(err).Msg("error getting tags") + return + } // Prepare template data templateData := CarvesRunTemplateData{ - Title: "Query osquery Nodes in " + env.Name + "", + Title: "Query osquery Nodes in " + env.Name, EnvUUID: env.UUID, + EnvName: env.Name, Metadata: h.TemplateMetadata(ctx, h.ServiceMetadata, user.Admin), Environments: h.allowedEnvironments(ctx[sessions.CtxUser], envAll), Platforms: platforms, UUIDs: uuids, Hosts: hosts, + Tags: tags.TagsToStrings(allTags), Tables: h.OsqueryTables, TablesVersion: h.OsqueryVersion, } @@ -581,8 +597,9 @@ func (h *HandlersAdmin) CarvesListGETHandler(w http.ResponseWriter, r *http.Requ } // Prepare template data templateData := CarvesTableTemplateData{ - Title: "Carved files in " + env.Name + "", + Title: "Carved files in " + env.Name, EnvUUID: env.UUID, + EnvName: env.Name, Metadata: h.TemplateMetadata(ctx, h.ServiceMetadata, user.Admin), Environments: h.allowedEnvironments(ctx[sessions.CtxUser], envAll), Platforms: platforms, @@ -643,12 +660,6 @@ func (h *HandlersAdmin) QueryLogsHandler(w http.ResponseWriter, r *http.Request) log.Err(err).Msg("error getting query") return } - // Get query targets - targets, err := h.Queries.GetTargets(name) - if err != nil { - log.Err(err).Msg("error getting targets") - return - } leftMetadata := AsideLeftMetadata{ EnvUUID: env.UUID, Query: true, @@ -681,7 +692,7 @@ func (h *HandlersAdmin) QueryLogsHandler(w http.ResponseWriter, r *http.Request) Environments: h.allowedEnvironments(ctx[sessions.CtxUser], envAll), Platforms: platforms, Query: query, - QueryTargets: targets, + QueryTargets: parseQueryTarget(query.Target), ServiceConfig: *h.AdminConfig, } if err := t.Execute(w, templateData); err != nil { @@ -750,12 +761,6 @@ func (h *HandlersAdmin) CarvesDetailsHandler(w http.ResponseWriter, r *http.Requ log.Err(err).Msg("error getting query") return } - // Get query targets - targets, err := h.Queries.GetTargets(name) - if err != nil { - log.Err(err).Msg("error getting targets") - return - } // Get carves for this query queryCarves, err := h.Carves.GetByQuery(name, env.ID) if err != nil { @@ -792,7 +797,7 @@ func (h *HandlersAdmin) CarvesDetailsHandler(w http.ResponseWriter, r *http.Requ Environments: h.allowedEnvironments(ctx[sessions.CtxUser], envAll), Platforms: platforms, Query: query, - QueryTargets: targets, + QueryTargets: parseCarveTarget(query.Target), Carves: queryCarves, CarveBlocks: blocks, } diff --git a/cmd/admin/handlers/types-requests.go b/cmd/admin/handlers/types-requests.go index 2410d59e..b87eb89b 100644 --- a/cmd/admin/handlers/types-requests.go +++ b/cmd/admin/handlers/types-requests.go @@ -18,6 +18,7 @@ type DistributedQueryRequest struct { Platforms []string `json:"platform_list"` UUIDs []string `json:"uuid_list"` Hosts []string `json:"host_list"` + Tags []string `json:"tag_list"` Save bool `json:"save"` Name string `json:"name"` Query string `json:"query"` @@ -31,6 +32,7 @@ type DistributedCarveRequest struct { Platforms []string `json:"platform_list"` UUIDs []string `json:"uuid_list"` Hosts []string `json:"host_list"` + Tags []string `json:"tag_list"` Path string `json:"path"` ExpHours int `json:"exp_hours"` } diff --git a/cmd/admin/handlers/types-templates.go b/cmd/admin/handlers/types-templates.go index 2d384c91..9507ed66 100644 --- a/cmd/admin/handlers/types-templates.go +++ b/cmd/admin/handlers/types-templates.go @@ -45,6 +45,7 @@ type AsideLeftMetadata struct { type TableTemplateData struct { Title string EnvUUID string + EnvName string Selector string SelectorName string Target string @@ -100,10 +101,12 @@ type EnrollTemplateData struct { type QueryRunTemplateData struct { Title string EnvUUID string + EnvName string Environments []environments.TLSEnvironment Platforms []string UUIDs []string Hosts []string + Tags []string Tables []types.OsqueryTable TablesVersion string Metadata TemplateMetadata @@ -117,6 +120,7 @@ type CarvesRunTemplateData QueryRunTemplateData type GenericTableTemplateData struct { Title string EnvUUID string + EnvName string Environments []environments.TLSEnvironment Platforms []string Target string @@ -137,10 +141,11 @@ type CarvesTableTemplateData GenericTableTemplateData type CarvesDetailsTemplateData struct { Title string EnvUUID string + EnvName string Environments []environments.TLSEnvironment Platforms []string Query queries.DistributedQuery - QueryTargets []queries.DistributedQueryTarget + QueryTargets []CarveTarget Carves []carves.CarvedFile CarveBlocks map[string][]carves.CarvedBlock Metadata TemplateMetadata @@ -151,10 +156,11 @@ type CarvesDetailsTemplateData struct { type QueryLogsTemplateData struct { Title string EnvUUID string + EnvName string Environments []environments.TLSEnvironment Platforms []string Query queries.DistributedQuery - QueryTargets []queries.DistributedQueryTarget + QueryTargets []QueryTarget Metadata TemplateMetadata LeftMetadata AsideLeftMetadata ServiceConfig config.JSONConfigurationService diff --git a/cmd/admin/handlers/utils.go b/cmd/admin/handlers/utils.go index 6bc7aea9..07e95993 100644 --- a/cmd/admin/handlers/utils.go +++ b/cmd/admin/handlers/utils.go @@ -79,24 +79,37 @@ func generateCarveQuery(file string, glob bool) string { return "SELECT * FROM carves WHERE carve=1 AND path = '" + file + "';" } +// Helper to generate the file carve query +func newCarveReady(user, path string, exp time.Time, envid uint, req DistributedCarveRequest) queries.DistributedQuery { + return queries.DistributedQuery{ + Query: generateCarveQuery(path, false), + Name: generateCarveName(), + Creator: user, + Expected: 0, + Executions: 0, + Active: true, + Completed: false, + Deleted: false, + Expired: false, + Expiration: exp, + Type: queries.CarveQueryType, + Path: path, + EnvironmentID: envid, + Target: genTargetString(req.Environments, req.UUIDs, req.Hosts, req.Tags, req.Platforms), + } +} + // Helper to determine if a query may be a carve -func newQueryReady(user, query string, exp time.Time, envid uint) queries.DistributedQuery { - if strings.Contains(query, "carve(") || strings.Contains(query, "carve=1") { - return queries.DistributedQuery{ - Query: query, - Name: generateCarveName(), - Creator: user, - Expected: 0, - Executions: 0, - Active: true, - Completed: false, - Deleted: false, - Expired: false, - Expiration: exp, - Type: queries.CarveQueryType, - Path: query, - EnvironmentID: envid, +func newQueryReady(user, query string, exp time.Time, envid uint, req DistributedQueryRequest) queries.DistributedQuery { + if strings.Contains(query, "carve") { + cReq := DistributedCarveRequest{ + Environments: req.Environments, + Platforms: req.Platforms, + UUIDs: req.UUIDs, + Hosts: req.Hosts, + Tags: req.Tags, } + return newCarveReady(user, query, exp, envid, cReq) } return queries.DistributedQuery{ Query: query, @@ -111,24 +124,10 @@ func newQueryReady(user, query string, exp time.Time, envid uint) queries.Distri Expiration: exp, Type: queries.StandardQueryType, EnvironmentID: envid, + Target: genTargetString(req.Environments, req.UUIDs, req.Hosts, req.Tags, req.Platforms), } } -// Helper to remove duplicates from []string -func removeStringDuplicates(s []string) []string { - seen := make(map[string]struct{}, len(s)) - i := 0 - for _, v := range s { - if _, ok := seen[v]; ok { - continue - } - seen[v] = struct{}{} - s[i] = v - i++ - } - return s[:i] -} - // Helper to verify the service is valid func checkTargetService(service string) bool { if service == config.ServiceTLS { @@ -208,3 +207,60 @@ func (h *HandlersAdmin) generateFlags(flagsRaw, secretFile, certFile string) str replaced := strings.Replace(flagsRaw, "__SECRET_FILE__", secretFile, 1) return strings.Replace(replaced, "__CERT_FILE__", certFile, 1) } + +// Helper to generate the target string for on-demand queries and carves +func genTargetString(envs, uuids, hosts, tags, platforms []string) string { + var target string + if len(envs) > 0 { + target += "env[" + strings.Join(envs, ",") + "];" + } + if len(uuids) > 0 { + target += "uuid[" + strings.Join(uuids, ",") + "];" + } + if len(hosts) > 0 { + target += "host[" + strings.Join(hosts, ",") + "];" + } + if len(platforms) > 0 { + target += "platform[" + strings.Join(platforms, ",") + "];" + } + if len(tags) > 0 { + target += "tag[" + strings.Join(tags, ",") + "];" + } + return target +} + +// Helper to convert the target string to a slice of QueryTargets +func parseQueryTarget(target string) []QueryTarget { + var targets []QueryTarget + parts := strings.Split(target, ";") + for _, p := range parts { + if p != "" { + pType := strings.SplitN(p, "[", 2)[0] + pValue := strings.TrimSuffix(strings.SplitN(p, "[", 2)[1], "]") + t := QueryTarget{ + Type: pType, + Value: pValue, + } + targets = append(targets, t) + } + } + return targets +} + +// Helper to convert the target string to a slice of CarveTargets +func parseCarveTarget(target string) []CarveTarget { + var targets []CarveTarget + parts := strings.Split(target, ";") + for _, p := range parts { + if p != "" { + pType := strings.SplitN(p, "[", 2)[0] + pValue := strings.TrimSuffix(strings.SplitN(p, "[", 2)[1], "]") + t := CarveTarget{ + Type: pType, + Value: pValue, + } + targets = append(targets, t) + } + } + return targets +} diff --git a/cmd/admin/static/js/carves.js b/cmd/admin/static/js/carves.js index 9d58696d..3abea624 100644 --- a/cmd/admin/static/js/carves.js +++ b/cmd/admin/static/js/carves.js @@ -4,29 +4,21 @@ function sendCarve(_url, _redir) { var _platform_list = $("#target_platform").val(); var _uuid_list = $("#target_uuids").val(); var _host_list = $("#target_hosts").val(); + var _tag_list = $("#target_tags").val(); var _exp_hours = parseInt($("#expiration_hours").val()); - var _repeat = $('#target_repeat').prop('checked') ? 1 : 0; + var _repeat = $("#target_repeat").prop("checked") ? 1 : 0; var _path = $("#carve").val(); // Making sure targets are specified - if (_env_list.length === 0 && _platform_list.length === 0 && _uuid_list.length === 0 && _host_list.length === 0) { + if (_env_list.length === 0 && _platform_list.length === 0 && _uuid_list.length === 0 && _host_list.length === 0 && _tag_list.length === 0) { $("#warningModalMessage").text("No targets have been specified"); $("#warningModal").modal(); return; } - // Check if all environments have been selected - if (_env_list.includes("all_environments_99")) { - _env_list = []; - $('#target_env option').each(function () { - if ($(this).val() !== "" && $(this).val() !== "all_environments_99") { - _env_list.push($(this).val()); - } - }); - } // Check if all platforms have been selected if (_platform_list.includes("all_platforms_99")) { _platform_list = []; - $('#target_platform option').each(function () { + $("#target_platform option").each(function () { if ($(this).val() !== "" && $(this).val() !== "all_platforms_99") { _platform_list.push($(this).val()); } @@ -45,6 +37,7 @@ function sendCarve(_url, _redir) { platform_list: _platform_list, uuid_list: _uuid_list, host_list: _host_list, + tag_list: _tag_list, path: _path, exp_hours: _exp_hours, repeat: _repeat, @@ -63,28 +56,28 @@ $("#carve").keyup(function (event) { }); function deleteCarves(_names, _url) { - actionCarves('delete', _names, _url, window.location.pathname); + actionCarves("delete", _names, _url, window.location.pathname); } function confirmDeleteCarves(_names, _url) { - var modal_message = 'Are you sure you want to delete ' + _names.length + ' carve(s)?'; + var modal_message = "Are you sure you want to delete " + _names.length + " carve(s)?"; $("#confirmModalMessage").text(modal_message); - $('#confirm_action').click(function () { - $('#confirmModal').modal('hide'); + $("#confirm_action").click(function () { + $("#confirmModal").modal("hide"); deleteCarves(_names, _url); }); $("#confirmModal").modal(); } function deleteCarve(_ids, _url) { - actionCarves('delete', _ids, _url, window.location.pathname); + actionCarves("delete", _ids, _url, window.location.pathname); } function confirmDeleteCarve(_ids, _url) { - var modal_message = 'Are you sure you want to delete this carve?'; + var modal_message = "Are you sure you want to delete this carve?"; $("#confirmModalMessage").text(modal_message); - $('#confirm_action').click(function () { - $('#confirmModal').modal('hide'); + $("#confirm_action").click(function () { + $("#confirmModal").modal("hide"); deleteCarve(_ids, _url); }); $("#confirmModal").modal(); @@ -96,7 +89,7 @@ function actionCarves(_action, _ids, _url, _redir) { var data = { csrftoken: _csrftoken, ids: _ids, - action: _action + action: _action, }; sendPostRequest(data, _url, _redir, false); } diff --git a/cmd/admin/static/js/query.js b/cmd/admin/static/js/query.js index 209c9626..b09ebfca 100644 --- a/cmd/admin/static/js/query.js +++ b/cmd/admin/static/js/query.js @@ -4,6 +4,7 @@ function sendQuery(_queryUrl, _redir) { var _platform_list = $("#target_platform").val(); var _uuid_list = $("#target_uuids").val(); var _host_list = $("#target_hosts").val(); + var _tag_list = $("#target_tags").val(); var _exp_hours = parseInt($("#expiration_hours").val()); var _query_name = $("#save_query_name").val(); var _query_save = $("#save_query_check").is(":checked") ? true : false; @@ -11,25 +12,11 @@ function sendQuery(_queryUrl, _redir) { var _query = editor.getValue(); // Making sure targets are specified - if ( - _env_list.length === 0 && - _platform_list.length === 0 && - _uuid_list.length === 0 && - _host_list.length === 0 - ) { + if (_env_list.length === 0 && _platform_list.length === 0 && _uuid_list.length === 0 && _host_list.length === 0 && _tag_list.length === 0) { $("#warningModalMessage").text("No targets have been specified"); $("#warningModal").modal(); return; } - // Check if all environments have been selected - if (_env_list.includes("all_environments_99")) { - _env_list = []; - $("#target_env option").each(function () { - if ($(this).val() !== "" && $(this).val() !== "all_environments_99") { - _env_list.push($(this).val()); - } - }); - } // Check if all platforms have been selected if (_platform_list.includes("all_platforms_99")) { _platform_list = []; @@ -62,6 +49,7 @@ function sendQuery(_queryUrl, _redir) { platform_list: _platform_list, uuid_list: _uuid_list, host_list: _host_list, + tag_list: _tag_list, save: _query_save, name: _query_name, query: _query, @@ -110,8 +98,7 @@ function actionQueries(_action, _names, _url, _redir) { } function confirmDeleteQueries(_names, _url) { - var modal_message = - "Are you sure you want to delete " + _names.length + " query(s)?"; + var modal_message = "Are you sure you want to delete " + _names.length + " query(s)?"; $("#confirmModalMessage").text(modal_message); $("#confirm_action").click(function () { $("#confirmModal").modal("hide"); @@ -121,8 +108,7 @@ function confirmDeleteQueries(_names, _url) { } function confirmDeleteSavedQueries(_names, _url) { - var modal_message = - "Are you sure you want to delete " + _names.length + " query(s)?"; + var modal_message = "Are you sure you want to delete " + _names.length + " query(s)?"; $("#confirmModalMessage").text(modal_message); $("#confirm_action").click(function () { $("#confirmModal").modal("hide"); @@ -132,19 +118,8 @@ function confirmDeleteSavedQueries(_names, _url) { } function queryResultLink(link, query, url) { - var external_link = - ''; - return ( - '' + - query + - " - " + - external_link + - " " - ); + var external_link = ''; + return '' + query + " - " + external_link + " "; } function toggleSaveQuery() { diff --git a/cmd/admin/templates/carves-details.html b/cmd/admin/templates/carves-details.html index c1259e24..6f0e9657 100644 --- a/cmd/admin/templates/carves-details.html +++ b/cmd/admin/templates/carves-details.html @@ -11,34 +11,22 @@
- {{ $carveBlocks := .CarveBlocks }} {{ $template := . }} {{ with - .Query }} + {{ $carveBlocks := .CarveBlocks }} {{ $template := . }} {{ with .Query }}
{{ if .Expired }} - [ QUERY EXPIRED ] - {{ - .Name }} {{ else }} {{ if .Completed }} - [ QUERY COMPLETED ] - - {{ .Name }} {{ else }} [ - QUERY ACTIVE ] - {{ .Name }} - + [ QUERY EXPIRED ] - {{ .Name }} {{ else }} {{ if .Completed }} [ QUERY COMPLETED ] - {{ .Name }} {{ else }} + [ QUERY ACTIVE ] - {{ .Name }} - {{ inFutureTime .Expiration }} {{ end }} {{ end }}
-
- +
@@ -48,21 +36,11 @@ - - +
Path to Carve
+ {{ .Path }} - +
@@ -80,22 +58,13 @@
Type Value
- {{ .Expected }}/ - {{ .Executions }}{{ .Expected }}/ {{ .Executions }}/ {{ .Errors }}
- {{ .Query }} - {{ .Query }}
@@ -117,8 +86,7 @@ data-tooltip="true" data-placement="top" title="Download" - onclick="downloadCarve('/carves/{{ $template.EnvUUID }}/download/{{ $e.SessionID }}');" - > + onclick="downloadCarve('/carves/{{ $template.EnvUUID }}/download/{{ $e.SessionID }}');">
@@ -129,8 +97,7 @@ data-tooltip="true" data-placement="top" title="Delete" - onclick="confirmDeleteCarve(['{{ $e.CarveID }}'], '/carves/{{ $template.EnvUUID }}/actions');" - > + onclick="confirmDeleteCarve(['{{ $e.CarveID }}'], '/carves/{{ $template.EnvUUID }}/actions');">
@@ -154,9 +121,7 @@ Total / Block Size (bytes):
-

- {{ $e.CarveSize }} / {{ $e.BlockSize }} -

+

{{ $e.CarveSize }} / {{ $e.BlockSize }}

@@ -164,9 +129,7 @@ Total / Completed Blocks:
-

- {{ $e.TotalBlocks }} / {{ $e.CompletedBlocks }} -

+

{{ $e.TotalBlocks }} / {{ $e.CompletedBlocks }}

@@ -185,9 +148,7 @@ Request ID:
-

- {{ $e.RequestID }} -

+

{{ $e.RequestID }}

@@ -195,9 +156,7 @@ Session ID:
-

- {{ $e.SessionID }} -

+

{{ $e.SessionID }}

@@ -208,9 +167,7 @@ - +
diff --git a/cmd/admin/templates/carves-run.html b/cmd/admin/templates/carves-run.html index 1f34c18f..e9982edd 100644 --- a/cmd/admin/templates/carves-run.html +++ b/cmd/admin/templates/carves-run.html @@ -12,76 +12,41 @@
-
- Carve files in nodes - by target -
+
Carve files in nodes by target, within {{ .EnvName }}
-
- Select targets: -
+
Select targets:
-
+
- +
- {{ range $i, $e := $.Environments }} - + {{ end }} -
ex. dev
-
+
- -
- {{ range $i, $e := $.Platforms }} - + {{ end }} - +
ex. ubuntu @@ -89,100 +54,65 @@
-
+
-
- {{ range $i, $e := $.UUIDs }} - + {{ end }}
- ex. - 11111111-2222-3333-4444-555555555555 + ex. 11111111-2222-3333-4444-555555555555
-
+
-
- {{ range $i, $e := $.Hosts }} - + {{ end }}
- ex. MacBookPro + ex. MacBookPro
- -
-
-
-
- -
-
- Carve - expiration -
-
-
-
-
-
+
- -
- + + {{ range $i, $e := $.Tags }} + + {{ end }}
+ ex. tag:engineering +
+
+
+
+ +
+
+ +
+
@@ -194,8 +124,7 @@
- Full path for file or - directory to be carved: + Full path for file or directory to be carved:
@@ -207,20 +136,12 @@ data-tooltip="true" data-placement="top" title="Carve file/directory" - onclick="sendCarve('/carves/{{ .EnvUUID }}/run', '/carves/{{ .EnvUUID }}/list');" - > + onclick="sendCarve('/carves/{{ .EnvUUID }}/run', '/carves/{{ .EnvUUID }}/list');"> Carve
-
@@ -235,12 +156,7 @@
- +
@@ -269,15 +185,12 @@
Block ID