Skip to content
This repository was archived by the owner on Dec 15, 2020. It is now read-only.

Commit 6dbc3bc

Browse files
authored
Implement fleetctl goquery (#2186)
- Update fleet APIs to support necessary operations in goquery - Implement support for goquery in fleetctl
1 parent 1c2a0b8 commit 6dbc3bc

File tree

8 files changed

+300
-15
lines changed

8 files changed

+300
-15
lines changed

cmd/fleetctl/fleetctl.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ func main() {
5050
},
5151
},
5252
convertCommand(),
53+
goqueryCommand(),
5354
}
5455

5556
app.RunAndExitOnError()

cmd/fleetctl/goquery.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
7+
"github.com/AbGuthrie/goquery/v2"
8+
gqconfig "github.com/AbGuthrie/goquery/v2/config"
9+
gqhosts "github.com/AbGuthrie/goquery/v2/hosts"
10+
gqmodels "github.com/AbGuthrie/goquery/v2/models"
11+
"github.com/kolide/fleet/server/kolide"
12+
"github.com/kolide/fleet/server/service"
13+
"github.com/pkg/errors"
14+
"github.com/urfave/cli"
15+
)
16+
17+
type activeQuery struct {
18+
status string
19+
results []map[string]string
20+
}
21+
22+
type goqueryClient struct {
23+
client *service.Client
24+
queryCounter int
25+
queries map[string]activeQuery
26+
// goquery passes the UUID, while we need the hostname (or ID) to
27+
// query against Fleet. Keep a mapping so that we know how to target
28+
// the host.
29+
hostnameByUUID map[string]string
30+
}
31+
32+
func newGoqueryClient(fleetClient *service.Client) *goqueryClient {
33+
return &goqueryClient{
34+
client: fleetClient,
35+
queryCounter: 0,
36+
queries: make(map[string]activeQuery),
37+
hostnameByUUID: make(map[string]string),
38+
}
39+
}
40+
41+
func (c *goqueryClient) CheckHost(query string) (gqhosts.Host, error) {
42+
res, err := c.client.SearchTargets(query, nil, nil)
43+
if err != nil {
44+
return gqhosts.Host{}, err
45+
}
46+
47+
var host *kolide.Host
48+
for _, h := range res.Hosts {
49+
// We allow hosts to be looked up by hostname in addition to UUID
50+
if query == h.UUID || query == h.HostName || query == h.ComputerName {
51+
host = &h
52+
break
53+
}
54+
}
55+
56+
if host == nil {
57+
return gqhosts.Host{}, fmt.Errorf("host %s not found", query)
58+
}
59+
60+
c.hostnameByUUID[host.UUID] = host.HostName
61+
62+
return gqhosts.Host{
63+
UUID: host.UUID,
64+
ComputerName: host.ComputerName,
65+
Platform: host.Platform,
66+
Version: host.OsqueryVersion,
67+
}, nil
68+
}
69+
70+
func (c *goqueryClient) ScheduleQuery(uuid, query string) (string, error) {
71+
c.queryCounter++
72+
queryName := strconv.Itoa(c.queryCounter)
73+
74+
hostname, ok := c.hostnameByUUID[uuid]
75+
if !ok {
76+
return "", errors.New("could not lookup host")
77+
}
78+
79+
res, err := c.client.LiveQuery(query, []string{}, []string{hostname})
80+
if err != nil {
81+
return "", err
82+
}
83+
84+
c.queries[queryName] = activeQuery{status: "Pending"}
85+
86+
// We need to start a separate thread due to goquery expecting
87+
// scheduling a query and retrieving results to be separate
88+
// operations.
89+
go func() {
90+
select {
91+
case hostResult := <-res.Results():
92+
c.queries[queryName] = activeQuery{status: "Completed", results: hostResult.Rows}
93+
94+
// Print an error
95+
case err := <-res.Errors():
96+
c.queries[queryName] = activeQuery{status: "error: " + err.Error()}
97+
}
98+
}()
99+
100+
gqhosts.AddQueryToHost(uuid, gqhosts.Query{Name: queryName, SQL: query})
101+
return queryName, nil
102+
}
103+
104+
func (c *goqueryClient) FetchResults(queryName string) (gqmodels.Rows, string, error) {
105+
res, ok := c.queries[queryName]
106+
if !ok {
107+
return nil, "", fmt.Errorf("Unknown query %s", queryName)
108+
}
109+
110+
return res.results, res.status, nil
111+
}
112+
113+
func goqueryCommand() cli.Command {
114+
return cli.Command{
115+
Name: "goquery",
116+
Usage: "Start the goquery interface",
117+
Flags: []cli.Flag{
118+
configFlag(),
119+
contextFlag(),
120+
yamlFlag(),
121+
},
122+
Action: func(c *cli.Context) error {
123+
fleet, err := clientFromCLI(c)
124+
if err != nil {
125+
return err
126+
}
127+
128+
goquery.Run(newGoqueryClient(fleet), gqconfig.Config{})
129+
return nil
130+
},
131+
}
132+
}

docs/cli/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,30 @@ Fleet exposes the aspects of an osquery deployment as a set of "objects". Object
2424
> Objects can be created, updated, and deleted by storing multiple object configuration files in a directory and using `kubectl apply` to recursively create and update those objects as needed.
2525
2626
Similarly, Fleet objects can be created, updated, and deleted by storing multiple object configuration files in a directory and using `fleetctl apply` to recursively create and update those objects as needed.
27+
28+
### Using goquery with `fleetctl`
29+
30+
Fleet and `fleetctl` have built in support for [goquery](https://github.com/AbGuthrie/goquery).
31+
32+
Use `fleetctl goquery` to open up the goquery console. When used with Fleet, goquery can connect using either a hostname or UUID.
33+
34+
```
35+
$ ./build/fleetctl get hosts
36+
+--------------------------------------+--------------+----------+---------+
37+
| UUID | HOSTNAME | PLATFORM | STATUS |
38+
+--------------------------------------+--------------+----------+---------+
39+
| 192343D5-0000-0000-B85B-58F656BED4C7 | 6523f89187f8 | centos | online |
40+
+--------------------------------------+--------------+----------+---------+
41+
$ ./build/fleetctl goquery
42+
goquery> .connect 6523f89187f8
43+
Verified Host(6523f89187f8) Exists.
44+
.
45+
goquery | 6523f89187f8:> .query select unix_time from time
46+
...
47+
------------------------------
48+
| host_hostname | unix_time |
49+
------------------------------
50+
| 6523f89187f8 | 1579842569 |
51+
------------------------------
52+
goquery | 6523f89187f8:>
53+
```

go.mod

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ go 1.12
44

55
require (
66
cloud.google.com/go v0.37.4
7+
github.com/AbGuthrie/goquery/v2 v2.0.1
78
github.com/VividCortex/gohistogram v1.0.0 // indirect
89
github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f
910
github.com/WatchBeam/clock v0.0.0-20170901150240-b08e6b4da7ea
10-
github.com/aws/aws-sdk-go v1.19.8
11+
github.com/aws/aws-sdk-go v1.26.8
1112
github.com/beevik/etree v1.1.0
1213
github.com/briandowns/spinner v0.0.0-20170614154858-48dbb65d7bd5
1314
github.com/dgrijalva/jwt-go v3.2.0+incompatible
@@ -26,17 +27,14 @@ require (
2627
github.com/igm/sockjs-go v0.0.0-20171030210102-c8a8c6429d10
2728
github.com/inconshreveable/mousetrap v1.0.0 // indirect
2829
github.com/jmoiron/sqlx v0.0.0-20180406164412-2aeb6a910c2b
29-
github.com/jonboulle/clockwork v0.1.0 // indirect
3030
github.com/kolide/goose v0.0.0-20181015214854-7aebd1deb5ab
3131
github.com/kolide/kit v0.0.0-20180421083548-36eb8dc43916
3232
github.com/kolide/launcher v0.0.0-20180427153757-cb412b945cf7
3333
github.com/kolide/osquery-go v0.0.0-20190904034940-a74aa860032d
34-
github.com/kr/pretty v0.1.0 // indirect
3534
github.com/lib/pq v1.2.0 // indirect
3635
github.com/magiconair/properties v1.7.6 // indirect
37-
github.com/mattn/go-colorable v0.0.9 // indirect
38-
github.com/mattn/go-isatty v0.0.3 // indirect
39-
github.com/mattn/go-runewidth v0.0.2 // indirect
36+
github.com/mattn/go-isatty v0.0.12 // indirect
37+
github.com/mattn/go-runewidth v0.0.8 // indirect
4038
github.com/mattn/go-sqlite3 v1.11.0 // indirect
4139
github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238 // indirect
4240
github.com/olekukonko/tablewriter v0.0.0-20180506121414-d4647c9c7a84
@@ -56,14 +54,13 @@ require (
5654
github.com/stretchr/testify v1.4.0
5755
github.com/urfave/cli v1.20.0
5856
go.opencensus.io v0.20.2 // indirect
59-
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
57+
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc
6058
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a // indirect
61-
golang.org/x/sync v0.0.0-20190412183630-56d357773e84 // indirect
62-
golang.org/x/sys v0.0.0-20190909082730-f460065e899a // indirect
59+
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 // indirect
6360
google.golang.org/api v0.3.2 // indirect
6461
google.golang.org/grpc v1.19.0
65-
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
6662
gopkg.in/guregu/null.v3 v3.4.0
6763
gopkg.in/natefinch/lumberjack.v2 v2.0.0-20170531160350-a96e63847dc3
6864
gopkg.in/yaml.v2 v2.2.2
65+
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a
6966
)

0 commit comments

Comments
 (0)