mirror of
https://github.com/fleetdm/fleet
synced 2026-04-30 18:07:56 +00:00
📺 Looom: https://www.loom.com/share/1aec4616fa4449e7abac579084aef0ba?sid=0884f742-feb3-48bb-82dc-b7834bc9a6e1 Fixed fleetctl issue where it was creating a new query when running a query by name, as opposed to using the existing saved query. #15630 API change will be in a separate PR: https://github.com/fleetdm/fleet/pull/15673 # Checklist for submitter If some of the following don't apply, delete the relevant line. <!-- Note that API documentation changes are now addressed by the product design team. --> - [x] Changes file added for user-visible changes in `changes/` or `orbit/changes/`. See [Changes files](https://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information. - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [x] Added/updated tests - [x] Manual QA for all new/changed functionality
133 lines
3.2 KiB
Go
133 lines
3.2 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
|
|
"github.com/AbGuthrie/goquery/v2"
|
|
gqconfig "github.com/AbGuthrie/goquery/v2/config"
|
|
gqhosts "github.com/AbGuthrie/goquery/v2/hosts"
|
|
gqmodels "github.com/AbGuthrie/goquery/v2/models"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"github.com/fleetdm/fleet/v4/server/service"
|
|
"github.com/urfave/cli/v2"
|
|
)
|
|
|
|
type activeQuery struct {
|
|
status string
|
|
results []map[string]string
|
|
}
|
|
|
|
type goqueryClient struct {
|
|
client *service.Client
|
|
queryCounter int
|
|
queries map[string]activeQuery
|
|
// goquery passes the UUID, while we need the hostname (or ID) to
|
|
// query against Fleet. Keep a mapping so that we know how to target
|
|
// the host.
|
|
hostnameByUUID map[string]string
|
|
}
|
|
|
|
func newGoqueryClient(fleetClient *service.Client) *goqueryClient {
|
|
return &goqueryClient{
|
|
client: fleetClient,
|
|
queryCounter: 0,
|
|
queries: make(map[string]activeQuery),
|
|
hostnameByUUID: make(map[string]string),
|
|
}
|
|
}
|
|
|
|
func (c *goqueryClient) CheckHost(query string) (gqhosts.Host, error) {
|
|
res, err := c.client.SearchTargets(query, nil, nil)
|
|
if err != nil {
|
|
return gqhosts.Host{}, err
|
|
}
|
|
|
|
var host *fleet.Host
|
|
for _, h := range res.Hosts {
|
|
// We allow hosts to be looked up by hostname in addition to UUID
|
|
if query == h.UUID || query == h.Hostname || query == h.ComputerName {
|
|
host = h
|
|
break
|
|
}
|
|
}
|
|
|
|
if host == nil {
|
|
return gqhosts.Host{}, fmt.Errorf("host %s not found", query)
|
|
}
|
|
|
|
c.hostnameByUUID[host.UUID] = host.Hostname
|
|
|
|
return gqhosts.Host{
|
|
UUID: host.UUID,
|
|
ComputerName: host.ComputerName,
|
|
Platform: host.Platform,
|
|
Version: host.OsqueryVersion,
|
|
}, nil
|
|
}
|
|
|
|
func (c *goqueryClient) ScheduleQuery(uuid, query string) (string, error) {
|
|
c.queryCounter++
|
|
queryName := strconv.Itoa(c.queryCounter)
|
|
|
|
hostname, ok := c.hostnameByUUID[uuid]
|
|
if !ok {
|
|
return "", errors.New("could not lookup host")
|
|
}
|
|
|
|
res, err := c.client.LiveQuery(query, nil, []string{}, []string{hostname})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
c.queries[queryName] = activeQuery{status: "Pending"}
|
|
|
|
// We need to start a separate thread due to goquery expecting
|
|
// scheduling a query and retrieving results to be separate
|
|
// operations.
|
|
go func() {
|
|
select {
|
|
case hostResult := <-res.Results():
|
|
c.queries[queryName] = activeQuery{status: "Completed", results: hostResult.Rows}
|
|
|
|
// Print an error
|
|
case err := <-res.Errors():
|
|
c.queries[queryName] = activeQuery{status: "error: " + err.Error()}
|
|
}
|
|
}()
|
|
|
|
gqhosts.AddQueryToHost(uuid, gqhosts.Query{Name: queryName, SQL: query})
|
|
return queryName, nil
|
|
}
|
|
|
|
func (c *goqueryClient) FetchResults(queryName string) (gqmodels.Rows, string, error) {
|
|
res, ok := c.queries[queryName]
|
|
if !ok {
|
|
return nil, "", fmt.Errorf("Unknown query %s", queryName)
|
|
}
|
|
|
|
return res.results, res.status, nil
|
|
}
|
|
|
|
func goqueryCommand() *cli.Command {
|
|
return &cli.Command{
|
|
Name: "goquery",
|
|
Usage: "Start the goquery interface",
|
|
Flags: []cli.Flag{
|
|
configFlag(),
|
|
contextFlag(),
|
|
yamlFlag(),
|
|
debugFlag(),
|
|
},
|
|
Action: func(c *cli.Context) error {
|
|
fleet, err := clientFromCLI(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
goquery.Run(newGoqueryClient(fleet), gqconfig.Config{})
|
|
return nil
|
|
},
|
|
}
|
|
}
|