Skip to content

Commit 6154486

Browse files
[9.4](backport #51781) Osquerybeat: emit pack_name and query_name in scheduled query results (#51902)
* Osquerybeat: emit pack_name and query_name in scheduled query results (#51781) Scheduled pack queries (both osquery native interval schedules and osquerybeat RRULE schedules) now include pack_name and query_name in their result and response documents. pack_name comes from a new optional `pack_name` config field on a pack (alongside the existing pack_id); query_name comes from the pack's queries config map key. Both fields are only emitted when set. These fields are required by the Osquery Manager dashboards, which group and label scheduled query results by pack and query name. (cherry picked from commit 5d82c71) # Conflicts: # x-pack/osquerybeat/beater/config_plugin.go # x-pack/osquerybeat/beater/recurrence_query_handler.go # x-pack/osquerybeat/internal/config/osquery.go * Resolve 9.4 backport conflicts Keep only the additive pack_name/query_name changes; drop the RRULE scheduler handler and other main-only pieces (profiling resolution, schedule mode validation, pack schedule defaults) that do not exist on the 9.4 base. --------- Co-authored-by: Marc Guasch <marc-gr@users.noreply.github.com> Co-authored-by: Marc Guasch <marc.guasch@elastic.co>
1 parent 18f3756 commit 6154486

10 files changed

Lines changed: 165 additions & 22 deletions

File tree

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Kind can be one of:
2+
# - breaking-change: a change to previously-documented behavior
3+
# - deprecation: functionality that is being removed in a later release
4+
# - bug-fix: fixes a problem in a previous version
5+
# - enhancement: extends functionality but does not break or fix existing behavior
6+
# - feature: new functionality
7+
# - known-issue: problems that we are aware of in a given version
8+
# - security: impacts on the security of a product or a user’s deployment.
9+
# - upgrade: important information for someone upgrading from a prior version
10+
# - other: does not fit into any of the other categories
11+
kind: bug-fix
12+
13+
# Change summary; a 80ish characters long description of the change.
14+
summary: Emit pack_name and query_name in osquerybeat scheduled query results
15+
16+
# Long description; in case the summary is not enough to describe the change
17+
# this field accommodate a description without length limits.
18+
# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment.
19+
description: |
20+
Scheduled pack queries now include two additional fields in their result and
21+
response documents: pack_name, taken from the new optional `pack_name` config
22+
field on a pack (alongside the existing pack_id), and query_name, taken from
23+
the pack's queries config map key. Both fields are only emitted when set.
24+
These fields are required by the Osquery Manager dashboards, which group and
25+
label scheduled query results by pack and query name; without them the
26+
dashboards cannot render those results correctly.
27+
28+
# Affected component; a word indicating the component this changeset affects.
29+
component: osquerybeat
30+
31+
# PR URL; optional; the PR number that added the changeset.
32+
# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added.
33+
# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number.
34+
# Please provide it if you are adding a fragment for a different PR.
35+
#pr: https://github.com/owner/repo/1234
36+
37+
# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of).
38+
# If not present is automatically filled by the tooling with the issue linked to the PR number.
39+
#issue: https://github.com/owner/repo/1234

x-pack/osquerybeat/beater/action_handler.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ type actionResultPublisher interface {
2626
}
2727

2828
type queryResultPublisher interface {
29-
Publish(index, idValue, idFieldKey, responseID, spaceID, packID string, meta map[string]interface{}, hits []map[string]interface{}, ecsm ecs.Mapping, reqData interface{})
29+
Publish(index, idValue, idFieldKey, responseID, spaceID, packID, packName, queryName string, meta map[string]interface{}, hits []map[string]interface{}, ecsm ecs.Mapping, reqData interface{})
3030
}
3131

3232
type scheduledResponsePublisher interface {
33-
PublishScheduledResponse(scheduleID, packID, spaceID, responseID string, startedAt, completedAt, plannedScheduleTime time.Time, resultCount int, scheduleExecutionCount int64)
33+
PublishScheduledResponse(scheduleID, packID, packName, queryName, spaceID, responseID string, startedAt, completedAt, plannedScheduleTime time.Time, resultCount int, scheduleExecutionCount int64)
3434
}
3535

3636
type queryProfilePublisher interface {
@@ -172,7 +172,7 @@ func (a *actionHandler) executeQuery(ctx context.Context, index string, ac actio
172172

173173
a.log.Debugf("Completed query in: %v", duration)
174174

175-
a.publisher.Publish(index, ac.ID, "action_id", responseID, "", "", nil, hits, ac.ECSMapping, req["data"])
175+
a.publisher.Publish(index, ac.ID, "action_id", responseID, "", "", "", "", nil, hits, ac.ECSMapping, req["data"])
176176

177177
return len(hits), nil
178178
}

x-pack/osquerybeat/beater/action_handler_test.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,26 @@ type mockPublisher struct {
3636
idValue string
3737
idFieldKey string
3838
responseID string
39+
spaceID string
40+
packID string
41+
packName string
42+
queryName string
3943
meta map[string]interface{}
4044
hits []map[string]interface{}
4145
ecsm ecs.Mapping
4246
reqData interface{}
4347
profile map[string]interface{}
4448
}
4549

46-
func (p *mockPublisher) Publish(index, idValue, idFieldKey, responseID, spaceID, packID string, meta map[string]interface{}, hits []map[string]interface{}, ecsm ecs.Mapping, reqData interface{}) {
50+
func (p *mockPublisher) Publish(index, idValue, idFieldKey, responseID, spaceID, packID, packName, queryName string, meta map[string]interface{}, hits []map[string]interface{}, ecsm ecs.Mapping, reqData interface{}) {
4751
p.index = index
4852
p.idValue = idValue
4953
p.idFieldKey = idFieldKey
5054
p.responseID = responseID
55+
p.spaceID = spaceID
56+
p.packID = packID
57+
p.packName = packName
58+
p.queryName = queryName
5159
p.meta = meta
5260
p.hits = hits
5361
p.ecsm = ecsm

x-pack/osquerybeat/beater/config_plugin.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,12 @@ type QueryInfo struct {
4343
// Interval is the schedule interval in seconds for native schedules; used to compute schedule_execution_count
4444
Interval int
4545
// PackID is the policy-defined pack identifier for pack queries; empty for top-level schedule queries.
46-
PackID string
47-
Profile bool // whether to collect and publish profile for this query
46+
PackID string
47+
// PackName is the policy-defined human-readable pack name for pack queries; empty for top-level schedule queries.
48+
PackName string
49+
// QueryName is the query's config map key (pack query name or top-level schedule name).
50+
QueryName string
51+
Profile bool // whether to collect and publish profile for this query
4852
}
4953

5054
type queryInfoMap map[string]QueryInfo
@@ -234,8 +238,10 @@ func (p *ConfigPlugin) set(inputs []config.InputConfig) (err error) {
234238
osqueryConfig = inputs[0].Osquery
235239
}
236240

237-
// Common code to register query with lookup maps, enforce snapshot and increment queries count
238-
registerQuery := func(name, ns string, qi config.Query, packID string) (config.Query, error) {
241+
// Common code to register query with lookup maps, enforce snapshot and increment queries count.
242+
// queryName is the config map key (pack query name or top-level schedule name); packID/packName
243+
// are the pack identifiers for pack queries and empty for top-level schedule queries.
244+
registerQuery := func(name, ns string, qi config.Query, packID, packName, queryName string) (config.Query, error) {
239245
var ecsm ecs.Mapping
240246
ecsm, err = flattenECSMapping(qi.ECSMapping)
241247
if err != nil {
@@ -250,6 +256,8 @@ func (p *ConfigPlugin) set(inputs []config.InputConfig) (err error) {
250256
SpaceID: qi.SpaceID,
251257
Interval: qi.Interval,
252258
PackID: packID,
259+
PackName: packName,
260+
QueryName: queryName,
253261
Profile: qi.Profile,
254262
}
255263
namespaces[name] = ns
@@ -265,7 +273,7 @@ func (p *ConfigPlugin) set(inputs []config.InputConfig) (err error) {
265273

266274
// Iterate osquery configuration's scheduled queries, add flattened ECS mappings to lookup map
267275
for name, qi := range osqueryConfig.Schedule {
268-
qi, err = registerQuery(name, p.namespace, qi, "")
276+
qi, err = registerQuery(name, p.namespace, qi, "", "", name)
269277
if err != nil {
270278
return err
271279
}
@@ -279,7 +287,7 @@ func (p *ConfigPlugin) set(inputs []config.InputConfig) (err error) {
279287
packID = packName
280288
}
281289
for name, qi := range pack.Queries {
282-
qi, err = registerQuery(getPackQueryName(packName, name), p.namespace, qi, packID)
290+
qi, err = registerQuery(getPackQueryName(packName, name), p.namespace, qi, packID, pack.PackName, name)
283291
if err != nil {
284292
return err
285293
}
@@ -305,7 +313,7 @@ func (p *ConfigPlugin) set(inputs []config.InputConfig) (err error) {
305313
Profile: stream.Profile,
306314
}
307315

308-
qi, err = registerQuery(getPackQueryName(input.Name, stream.ID), p.namespace, qi, input.Name)
316+
qi, err = registerQuery(getPackQueryName(input.Name, stream.ID), p.namespace, qi, input.Name, "", stream.ID)
309317
if err != nil {
310318
return err
311319
}

x-pack/osquerybeat/beater/config_plugin_test.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,14 @@ func TestSet_ScheduleMetadataIncludesSpaceID(t *testing.T) {
531531
if diff := cmp.Diff(queryPeriod, qi.Interval); diff != "" {
532532
t.Error(diff)
533533
}
534+
// query_name for a top-level scheduled query is the schedule config map key.
535+
if diff := cmp.Diff(queryName, qi.QueryName); diff != "" {
536+
t.Error(diff)
537+
}
538+
// Top-level schedule queries do not belong to a pack.
539+
if diff := cmp.Diff("", qi.PackName); diff != "" {
540+
t.Error(diff)
541+
}
534542
}
535543

536544
func TestSet_ScheduleMetadataIncludesPackID(t *testing.T) {
@@ -540,6 +548,7 @@ func TestSet_ScheduleMetadataIncludesPackID(t *testing.T) {
540548
const (
541549
packName = "my-pack"
542550
packID = "pack-uuid-123"
551+
packLabel = "My Pack"
543552
queryName = "uptime_query"
544553
querySQL = "select * from uptime"
545554
queryPeriod = 60
@@ -555,7 +564,8 @@ func TestSet_ScheduleMetadataIncludesPackID(t *testing.T) {
555564
Osquery: &config.OsqueryConfig{
556565
Packs: map[string]config.Pack{
557566
packName: {
558-
PackID: packID,
567+
PackID: packID,
568+
PackName: packLabel,
559569
Queries: map[string]config.Query{
560570
queryName: {
561571
Query: querySQL,
@@ -586,4 +596,11 @@ func TestSet_ScheduleMetadataIncludesPackID(t *testing.T) {
586596
if diff := cmp.Diff(packID, qi.PackID); diff != "" {
587597
t.Error(diff)
588598
}
599+
if diff := cmp.Diff(packLabel, qi.PackName); diff != "" {
600+
t.Error(diff)
601+
}
602+
// query_name is taken from the pack's queries config map key.
603+
if diff := cmp.Diff(queryName, qi.QueryName); diff != "" {
604+
t.Error(diff)
605+
}
589606
}

x-pack/osquerybeat/beater/osquerybeat.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,7 @@ func (bt *osquerybeat) handleQueryResult(ctx context.Context, cli *osqdcli.Clien
592592
publishResolved := func(resultType, action string, hits []map[string]interface{}) {
593593
totalHits += len(hits)
594594
meta := queryResultMeta(resultType, action, res, scheduleExecutionCount, plannedScheduleTime)
595-
bt.pub.Publish(config.Datastream(ns), scheduleID, "schedule_id", responseID, qi.SpaceID, qi.PackID, meta, hits, qi.ECSMapping, nil)
595+
bt.pub.Publish(config.Datastream(ns), scheduleID, "schedule_id", responseID, qi.SpaceID, qi.PackID, qi.PackName, qi.QueryName, meta, hits, qi.ECSMapping, nil)
596596
}
597597

598598
if res.Action == "snapshot" {
@@ -630,7 +630,7 @@ func (bt *osquerybeat) handleQueryResult(ctx context.Context, cli *osqdcli.Clien
630630
}
631631
}
632632

633-
bt.pub.PublishScheduledResponse(scheduleID, qi.PackID, qi.SpaceID, responseID, runTime, runTime, plannedScheduleTime, totalHits, scheduleExecutionCount)
633+
bt.pub.PublishScheduledResponse(scheduleID, qi.PackID, qi.PackName, qi.QueryName, qi.SpaceID, responseID, runTime, runTime, plannedScheduleTime, totalHits, scheduleExecutionCount)
634634
}
635635

636636
func queryResultMeta(typ, action string, res QueryResult, scheduleExecutionCount int64, plannedScheduleTime time.Time) map[string]interface{} {

x-pack/osquerybeat/beater/osquerybeat_publisher_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ type mockBeatPublisher struct {
2121
var _ osquerybeatPublisher = (*mockBeatPublisher)(nil)
2222
var _ scheduledQueryPublisher = (*mockBeatPublisher)(nil)
2323

24-
func (m *mockBeatPublisher) Publish(index, idValue, idFieldKey, responseID, spaceID, packID string, meta map[string]interface{}, hits []map[string]interface{}, ecsm ecs.Mapping, reqData interface{}) {
24+
func (m *mockBeatPublisher) Publish(index, idValue, idFieldKey, responseID, spaceID, packID, packName, queryName string, meta map[string]interface{}, hits []map[string]interface{}, ecsm ecs.Mapping, reqData interface{}) {
2525
}
2626

2727
func (m *mockBeatPublisher) PublishActionResult(req map[string]interface{}, res map[string]interface{}) {
2828
}
2929

30-
func (m *mockBeatPublisher) PublishScheduledResponse(scheduleID, packID, spaceID, responseID string, startedAt, completedAt, plannedScheduleTime time.Time, resultCount int, scheduleExecutionCount int64) {
30+
func (m *mockBeatPublisher) PublishScheduledResponse(scheduleID, packID, packName, queryName, spaceID, responseID string, startedAt, completedAt, plannedScheduleTime time.Time, resultCount int, scheduleExecutionCount int64) {
3131
}
3232

3333
func (m *mockBeatPublisher) PublishQueryProfile(index, queryName, actionID, responseID string, profile map[string]interface{}, reqData interface{}) {

x-pack/osquerybeat/internal/config/osquery.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,10 @@ type Query struct {
7373
type Pack struct {
7474
// PackID is the policy-defined pack identifier; used in result/response documents for correlation.
7575
// If empty, the pack map key (pack name) is used when publishing.
76-
PackID string `config:"pack_id,omitempty" json:"pack_id,omitempty"`
76+
PackID string `config:"pack_id,omitempty" json:"pack_id,omitempty"`
77+
// PackName is the policy-defined human-readable pack name; emitted as pack_name in
78+
// result/response documents alongside pack_id. Optional and not sent to osqueryd.
79+
PackName string `config:"pack_name,omitempty" json:"-"`
7780
Discovery []string `config:"discovery" json:"discovery,omitempty"`
7881
Platform string `config:"platform" json:"platform,omitempty"`
7982
Version string `config:"version" json:"version,omitempty"`

x-pack/osquerybeat/internal/pub/publisher.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,12 +153,12 @@ func (p *Publisher) Configure(inputs []config.InputConfig) error {
153153
return nil
154154
}
155155

156-
func (p *Publisher) Publish(index, idValue, idFieldKey, responseID, spaceID, packID string, meta map[string]interface{}, hits []map[string]interface{}, ecsm ecs.Mapping, reqData interface{}) {
156+
func (p *Publisher) Publish(index, idValue, idFieldKey, responseID, spaceID, packID, packName, queryName string, meta map[string]interface{}, hits []map[string]interface{}, ecsm ecs.Mapping, reqData interface{}) {
157157
p.mx.Lock()
158158
defer p.mx.Unlock()
159159

160160
for _, hit := range hits {
161-
event := hitToEvent(index, p.b.Info.Name, idValue, idFieldKey, responseID, spaceID, packID, meta, hit, ecsm, reqData)
161+
event := hitToEvent(index, p.b.Info.Name, idValue, idFieldKey, responseID, spaceID, packID, packName, queryName, meta, hit, ecsm, reqData)
162162
p.client.Publish(event)
163163
}
164164
p.log.Infof("%d events sent to index %s", len(hits), index)
@@ -201,7 +201,7 @@ func (p *Publisher) PublishActionResult(req map[string]interface{}, res map[stri
201201
// PublishScheduledResponse publishes a synthetic response document for a scheduled query run (no action).
202202
// Includes schedule_execution_count;
203203
// native uses 1 + (run_time - start_date) / interval).
204-
func (p *Publisher) PublishScheduledResponse(scheduleID, packID, spaceID, responseID string, startedAt, completedAt, plannedScheduleTime time.Time, resultCount int, scheduleExecutionCount int64) {
204+
func (p *Publisher) PublishScheduledResponse(scheduleID, packID, packName, queryName, spaceID, responseID string, startedAt, completedAt, plannedScheduleTime time.Time, resultCount int, scheduleExecutionCount int64) {
205205
p.mx.Lock()
206206
defer p.mx.Unlock()
207207

@@ -227,6 +227,12 @@ func (p *Publisher) PublishScheduledResponse(scheduleID, packID, spaceID, respon
227227
if packID != "" {
228228
fields["pack_id"] = packID
229229
}
230+
if packName != "" {
231+
fields["pack_name"] = packName
232+
}
233+
if queryName != "" {
234+
fields["query_name"] = queryName
235+
}
230236
if spaceID != "" {
231237
fields["space_id"] = spaceID
232238
}
@@ -363,7 +369,7 @@ func (p *Publisher) processorsForInputConfig(inCfg config.InputConfig, defaultDa
363369
return procs, nil
364370
}
365371

366-
func hitToEvent(index, eventType, idValue, idFieldKey, responseID, spaceID, packID string, meta, hit map[string]interface{}, ecsm ecs.Mapping, reqData interface{}) beat.Event {
372+
func hitToEvent(index, eventType, idValue, idFieldKey, responseID, spaceID, packID, packName, queryName string, meta, hit map[string]interface{}, ecsm ecs.Mapping, reqData interface{}) beat.Event {
367373
var fields mapstr.M
368374

369375
if len(ecsm) > 0 {
@@ -413,6 +419,12 @@ func hitToEvent(index, eventType, idValue, idFieldKey, responseID, spaceID, pack
413419
if packID != "" {
414420
event.Fields["pack_id"] = packID
415421
}
422+
if packName != "" {
423+
event.Fields["pack_name"] = packName
424+
}
425+
if queryName != "" {
426+
event.Fields["query_name"] = queryName
427+
}
416428
if index != "" {
417429
event.Meta = mapstr.M{events.FieldMetaRawIndex: index}
418430
}

x-pack/osquerybeat/internal/pub/publisher_test.go

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func TestHitToEvent(t *testing.T) {
6464

6565
for i := 0; i < maxMask; i++ {
6666
p := genParams(i)
67-
ev := hitToEvent(p.index, p.eventType, p.idValue, p.idFieldKey, p.responseID, "", "", p.meta, p.hit, p.ecsm, p.reqData)
67+
ev := hitToEvent(p.index, p.eventType, p.idValue, p.idFieldKey, p.responseID, "", "", "", "", p.meta, p.hit, p.ecsm, p.reqData)
6868

6969
if p.index != "" {
7070
diff := cmp.Diff(p.index, ev.Meta[events.FieldMetaRawIndex])
@@ -209,6 +209,8 @@ func TestHitToEvent_SpaceID(t *testing.T) {
209209
uuid.Must(uuid.NewV4()).String(),
210210
spaceID,
211211
"",
212+
"",
213+
"",
212214
nil,
213215
map[string]interface{}{"foo": "bar"},
214216
nil,
@@ -230,6 +232,8 @@ func TestHitToEvent_PackID(t *testing.T) {
230232
uuid.Must(uuid.NewV4()).String(),
231233
"",
232234
packID,
235+
"",
236+
"",
233237
nil,
234238
map[string]interface{}{"foo": "bar"},
235239
nil,
@@ -241,6 +245,58 @@ func TestHitToEvent_PackID(t *testing.T) {
241245
}
242246
}
243247

248+
func TestHitToEvent_PackNameAndQueryName(t *testing.T) {
249+
packName := "My Pack"
250+
queryName := "processes"
251+
ev := hitToEvent(
252+
"logs-osquery_manager.result-default",
253+
"osquery_manager",
254+
"sched-123",
255+
"schedule_id",
256+
uuid.Must(uuid.NewV4()).String(),
257+
"",
258+
"pack-xyz",
259+
packName,
260+
queryName,
261+
nil,
262+
map[string]interface{}{"foo": "bar"},
263+
nil,
264+
nil,
265+
)
266+
267+
if diff := cmp.Diff(packName, ev.Fields["pack_name"]); diff != "" {
268+
t.Error(diff)
269+
}
270+
if diff := cmp.Diff(queryName, ev.Fields["query_name"]); diff != "" {
271+
t.Error(diff)
272+
}
273+
}
274+
275+
func TestHitToEvent_NoPackNameOrQueryName(t *testing.T) {
276+
ev := hitToEvent(
277+
"logs-osquery_manager.result-default",
278+
"osquery_manager",
279+
"action-123",
280+
"action_id",
281+
uuid.Must(uuid.NewV4()).String(),
282+
"",
283+
"",
284+
"",
285+
"",
286+
nil,
287+
map[string]interface{}{"foo": "bar"},
288+
nil,
289+
nil,
290+
)
291+
292+
if _, ok := ev.Fields["pack_name"]; ok {
293+
t.Error(`expected no "pack_name" field when packName is empty`)
294+
}
295+
if _, ok := ev.Fields["query_name"]; ok {
296+
t.Error(`expected no "query_name" field when queryName is empty`)
297+
}
298+
}
299+
244300
func toMap(t *testing.T, s string) map[string]interface{} {
245301
var m map[string]interface{}
246302
err := json.Unmarshal([]byte(s), &m)

0 commit comments

Comments
 (0)