Skip to content

Commit 8c52006

Browse files
szwarckonradkibanamachinetomsonpl
authored andcommitted
[EDR Workflows][Osquery] Action results namespace awareness (elastic#225617)
# Add Namespace Awareness to Osquery Live Query and Action Results ## Dependencies Depends on elastic/elasticsearch#130824 ## Summary Adds namespace awareness to osquery live query and action results to ensure proper space isolation. Refactors frontend hooks to use API endpoints as a necessary step to implement server-side namespace filtering. **Removed constraint requiring user to have access to logs.osquery_manager.results-default index. Now user can use results index from any namespace** ## Changes Made ### Namespace Awareness Implementation - Added space-scoped integration namespace retrieval for osquery results - Enhanced DSL queries to support space-aware index patterns - Implemented proper space isolation to prevent cross-space data leakage - Extended `OsqueryAppContextService` with namespace-aware capabilities ### Frontend Hook Refactoring (Required for Namespace Support) - `use_all_results.ts`: Refactored to call API endpoint instead of direct search strategy - `use_action_results.ts`: Refactored to call new API endpoint instead of direct search strategy - Moved complex namespace logic to server-side for proper space isolation - Maintained backward compatibility for consuming components ### New API Infrastructure - Created `action_results/get_action_results_route.ts` endpoint with namespace support - Enhanced existing live query results endpoint with namespace filtering - Added proper request/response validation and route registration ### Server-Side Utilities - New utility: `get_integration_namespaces.ts` for space-scoped namespace retrieval - New utility: `build_index_name_with_namespace.ts` for consistent index pattern building - Enhanced action results DSL with `integrationNamespaces` parameter support - Improved live query results DSL namespace handling ### Elasticsearch: - Added `logs-osquery_manager.result-*` to `kibana_system` role so that we can use internal es client (which in the end uses user with `kibana_system` role) to query all indices but filter by user accessible namespaces in our own services. --------- Co-authored-by: kibanamachine <[email protected]> Co-authored-by: Tomasz Ciecierski <[email protected]>
1 parent 018dbee commit 8c52006

File tree

27 files changed

+1276
-229
lines changed

27 files changed

+1276
-229
lines changed

x-pack/platform/plugins/private/translations/translations/de-DE.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31670,8 +31670,6 @@
3167031670
"xpack.osquery.live_queries_all.fetchError": "Fehler beim Abrufen von Live-Abfragen",
3167131671
"xpack.osquery.liveQueriesHistory.newLiveQueryButtonLabel": "Neue Live-Abfrage",
3167231672
"xpack.osquery.liveQueriesHistory.pageTitle": "Verlauf der Live-Abfragen",
31673-
"xpack.osquery.liveQuery.permissionDeniedPromptBody": "Um die Abfrageergebnisse anzuzeigen, bitten Sie Ihren Administrator, Ihre Nutzerrolle so zu aktualisieren, dass Sie die Index {read}-Berechtigungen für den Index {logs} haben.",
31674-
"xpack.osquery.liveQuery.permissionDeniedPromptTitle": "Zugriff verweigert",
3167531673
"xpack.osquery.liveQuery.queryForm.packQueryTypeDescription": "Führen Sie einen Satz von Abfragen in einem Paket aus.",
3167631674
"xpack.osquery.liveQuery.queryForm.packQueryTypeLabel": "Pack",
3167731675
"xpack.osquery.liveQuery.queryForm.singleQueryTypeDescription": "Führen Sie eine gespeicherte Abfrage oder eine neue aus.",
@@ -31824,7 +31822,6 @@
3182431822
"xpack.osquery.queryPlaygroundFlyout.title": "Testabfrage",
3182531823
"xpack.osquery.results.errorSearchDescription": "Bei der Suche nach allen Ergebnissen ist ein Fehler aufgetreten",
3182631824
"xpack.osquery.results.failSearchDescription": "Ergebnisse konnten nicht abgerufen werden",
31827-
"xpack.osquery.results.fetchError": "Fehler beim Abrufen von Ergebnissen",
3182831825
"xpack.osquery.results.multipleAgentsResponded": "{agentsResponded, plural, one {# agent has} other {# agents have}} geantwortet, es wurden keine Osquery-Daten gemeldet.",
3182931826
"xpack.osquery.results.permissionDenied": "Um auf diese Ergebnisse zuzugreifen, bitten Sie Ihren Administrator um {osquery} Kibana-Berechtigungen.",
3183031827
"xpack.osquery.savedQueries.dropdown.searchFieldLabel": "Abfrage",

x-pack/platform/plugins/private/translations/translations/fr-FR.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31735,8 +31735,6 @@
3173531735
"xpack.osquery.live_queries_all.fetchError": "Erreur lors de la récupération des recherches en direct",
3173631736
"xpack.osquery.liveQueriesHistory.newLiveQueryButtonLabel": "Nouvelle recherche en direct",
3173731737
"xpack.osquery.liveQueriesHistory.pageTitle": "Historique des recherches en direct",
31738-
"xpack.osquery.liveQuery.permissionDeniedPromptBody": "Pour pouvoir consulter les résultats de requête, demandez à votre administrateur de mettre à jour votre rôle utilisateur de sorte à disposer des privilèges de {read} pour les index {logs}.",
31739-
"xpack.osquery.liveQuery.permissionDeniedPromptTitle": "Autorisation refusée",
3174031738
"xpack.osquery.liveQuery.queryForm.packQueryTypeDescription": "Exécutez un ensemble de requêtes en un pack.",
3174131739
"xpack.osquery.liveQuery.queryForm.packQueryTypeLabel": "Pack",
3174231740
"xpack.osquery.liveQuery.queryForm.singleQueryTypeDescription": "Exécutez une requête enregistrée ou une nouvelle requête.",
@@ -31889,7 +31887,6 @@
3188931887
"xpack.osquery.queryPlaygroundFlyout.title": "Tester la recherche",
3189031888
"xpack.osquery.results.errorSearchDescription": "Une erreur s'est produite lors de la recherche de tous les résultats",
3189131889
"xpack.osquery.results.failSearchDescription": "Impossible de récupérer les résultats",
31892-
"xpack.osquery.results.fetchError": "Erreur lors de la récupération des résultats",
3189331890
"xpack.osquery.results.multipleAgentsResponded": "{agentsResponded, plural, one {# agent has} other {# agents ont}} répondu, aucune donnée osquery n'a été signalée.",
3189431891
"xpack.osquery.results.permissionDenied": "Pour accéder à ces résultats, demandez à votre administrateur vos privilèges Kibana pour {osquery}.",
3189531892
"xpack.osquery.savedQueries.dropdown.searchFieldLabel": "Requête",

x-pack/platform/plugins/private/translations/translations/ja-JP.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31774,8 +31774,6 @@
3177431774
"xpack.osquery.live_queries_all.fetchError": "ライブクエリーの取得エラー",
3177531775
"xpack.osquery.liveQueriesHistory.newLiveQueryButtonLabel": "新しいライブクエリー",
3177631776
"xpack.osquery.liveQueriesHistory.pageTitle": "ライブクエリー履歴",
31777-
"xpack.osquery.liveQuery.permissionDeniedPromptBody": "クエリー結果を表示するには、ユーザーロールを更新して、{logs}インデックスに対する{read}権限を付与するように、管理者に依頼してください。",
31778-
"xpack.osquery.liveQuery.permissionDeniedPromptTitle": "パーミッションが拒否されました",
3177931777
"xpack.osquery.liveQuery.queryForm.packQueryTypeDescription": "パックでクエリーのセットを実行します。",
3178031778
"xpack.osquery.liveQuery.queryForm.packQueryTypeLabel": "パック",
3178131779
"xpack.osquery.liveQuery.queryForm.singleQueryTypeDescription": "保存されたクエリーまたは新しいクエリーを実行します。",
@@ -31929,7 +31927,6 @@
3192931927
"xpack.osquery.queryPlaygroundFlyout.title": "クエリーのテスト",
3193031928
"xpack.osquery.results.errorSearchDescription": "すべての結果検索でエラーが発生しました",
3193131929
"xpack.osquery.results.failSearchDescription": "結果を取得できませんでした",
31932-
"xpack.osquery.results.fetchError": "結果の取得中にエラーが発生しました",
3193331930
"xpack.osquery.results.multipleAgentsResponded": "{agentsResponded, plural, one {# agent has} other {# エージェント}}が応答しましたが、osqueryデータは報告されていません。",
3193431931
"xpack.osquery.results.permissionDenied": "これらの結果にアクセスするには、{osquery} Kibana権限について管理者に確認してください。",
3193531932
"xpack.osquery.savedQueries.dropdown.searchFieldLabel": "クエリー",

x-pack/platform/plugins/private/translations/translations/zh-CN.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31756,8 +31756,6 @@
3175631756
"xpack.osquery.live_queries_all.fetchError": "提取实时查询时出错",
3175731757
"xpack.osquery.liveQueriesHistory.newLiveQueryButtonLabel": "新建实时查询",
3175831758
"xpack.osquery.liveQueriesHistory.pageTitle": "实时查询历史记录",
31759-
"xpack.osquery.liveQuery.permissionDeniedPromptBody": "要查看查询结果,请要求管理员将您的角色更新为具有 {logs} 索引的索引 {read} 权限。",
31760-
"xpack.osquery.liveQuery.permissionDeniedPromptTitle": "权限被拒绝",
3176131759
"xpack.osquery.liveQuery.queryForm.packQueryTypeDescription": "打包运行一组查询。",
3176231760
"xpack.osquery.liveQuery.queryForm.packQueryTypeLabel": "包",
3176331761
"xpack.osquery.liveQuery.queryForm.singleQueryTypeDescription": "运行已保存查询或新查询。",
@@ -31911,7 +31909,6 @@
3191131909
"xpack.osquery.queryPlaygroundFlyout.title": "测试查询",
3191231910
"xpack.osquery.results.errorSearchDescription": "搜索所有结果时发生错误",
3191331911
"xpack.osquery.results.failSearchDescription": "无法获取结果",
31914-
"xpack.osquery.results.fetchError": "提取结果时出错",
3191531912
"xpack.osquery.results.multipleAgentsResponded": "{agentsResponded, plural, one {# 个代理已} other {# 个代理已}}响应,未报告任何 osquery 数据。",
3191631913
"xpack.osquery.results.permissionDenied": "要访问这些结果,请联系管理员获取 {osquery} Kibana 权限。",
3191731914
"xpack.osquery.savedQueries.dropdown.searchFieldLabel": "查询",
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import * as t from 'io-ts';
9+
import { toNumberRt } from '@kbn/io-ts-utils';
10+
import { Direction } from '../../search_strategy';
11+
12+
export const getActionResultsRequestQuerySchema = t.type({
13+
startDate: t.union([t.string, t.undefined]),
14+
page: t.union([toNumberRt, t.undefined]),
15+
pageSize: t.union([toNumberRt, t.undefined]),
16+
sort: t.union([t.string, t.undefined]),
17+
sortOrder: t.union([t.literal(Direction.asc), t.literal(Direction.desc), t.undefined]),
18+
kuery: t.union([t.string, t.undefined]),
19+
agentIds: t.union([t.string, t.undefined]),
20+
});
21+
22+
export type GetActionResultsRequestQuerySchema = t.OutputOf<
23+
typeof getActionResultsRequestQuerySchema
24+
>;
25+
26+
export const getActionResultsRequestParamsSchema = t.type({
27+
actionId: t.string,
28+
});
29+
30+
export type GetActionResultsRequestParamsSchema = t.OutputOf<
31+
typeof getActionResultsRequestParamsSchema
32+
>;

x-pack/platform/plugins/shared/osquery/common/api/live_query/get_live_query_results_route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export const getLiveQueryResultsRequestQuerySchema = t.type({
1515
pageSize: t.union([toNumberRt, t.undefined]),
1616
sort: t.union([t.string, t.undefined]),
1717
sortOrder: t.union([t.literal(Direction.asc), t.literal(Direction.desc), t.undefined]),
18+
startDate: t.union([t.string, t.undefined]),
1819
});
1920

2021
export type GetLiveQueryResultsRequestQuerySchema = t.OutputOf<

x-pack/platform/plugins/shared/osquery/common/search_strategy/osquery/actions/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,5 @@ export interface ActionResultsRequestOptions extends RequestOptionsPaginated {
8888
actionId: string;
8989
startDate?: string;
9090
useNewDataStream?: boolean;
91+
integrationNamespaces?: string[];
9192
}

x-pack/platform/plugins/shared/osquery/common/search_strategy/osquery/results/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ export interface ResultsRequestOptions extends Omit<RequestOptionsPaginated, 'so
2323
agentId?: string;
2424
startDate?: string;
2525
sort: SortField[];
26+
integrationNamespaces?: string[];
2627
}

x-pack/platform/plugins/shared/osquery/public/action_results/action_results_summary.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
1212
import { AgentIdToName } from '../agents/agent_id_to_name';
1313
import { useActionResults } from './use_action_results';
1414
import { Direction } from '../../common/search_strategy';
15-
import { useActionResultsPrivileges } from './use_action_privileges';
1615

1716
interface ActionResultsSummaryProps {
1817
actionId: string;
@@ -42,9 +41,7 @@ const ActionResultsSummaryComponent: React.FC<ActionResultsSummaryProps> = ({
4241
[expirationDate]
4342
);
4443
const [isLive, setIsLive] = useState(true);
45-
const { data: hasActionResultsPrivileges } = useActionResultsPrivileges();
4644
const {
47-
// @ts-expect-error update types
4845
data: { aggregations, edges },
4946
} = useActionResults({
5047
actionId,
@@ -55,7 +52,6 @@ const ActionResultsSummaryComponent: React.FC<ActionResultsSummaryProps> = ({
5552
direction: Direction.asc,
5653
sortField: '@timestamp',
5754
isLive,
58-
skip: !hasActionResultsPrivileges,
5955
});
6056

6157
useEffect(() => {

x-pack/platform/plugins/shared/osquery/public/action_results/use_action_results.ts

Lines changed: 48 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,25 @@
55
* 2.0.
66
*/
77

8-
import type { estypes } from '@elastic/elasticsearch';
9-
import { flatten, reverse, uniqBy } from 'lodash/fp';
108
import { useQuery } from '@tanstack/react-query';
119
import { i18n } from '@kbn/i18n';
12-
import { lastValueFrom } from 'rxjs';
1310
import type { InspectResponse } from '../common/helpers';
14-
import { getInspectResponse, generateTablePaginationOptions } from '../common/helpers';
1511
import { useKibana } from '../common/lib/kibana';
16-
import type {
17-
ResultEdges,
18-
ActionResultsRequestOptions,
19-
ActionResultsStrategyResponse,
20-
Direction,
21-
} from '../../common/search_strategy';
22-
import { OsqueryQueries } from '../../common/search_strategy';
23-
import { queryClient } from '../query_client';
12+
import type { ResultEdges, Direction } from '../../common/search_strategy';
13+
import { API_VERSIONS } from '../../common/constants';
2414

2515
import { useErrorToast } from '../common/hooks/use_error_toast';
2616

27-
export interface ResultsArgs {
28-
results: ResultEdges;
29-
id: string;
17+
export interface ActionResultsArgs {
18+
edges: ResultEdges;
19+
aggregations: {
20+
totalRowCount: number;
21+
totalResponded: number;
22+
successful: number;
23+
failed: number;
24+
pending: number;
25+
};
3026
inspect: InspectResponse;
31-
isInspected: boolean;
3227
}
3328

3429
export interface UseActionResults {
@@ -40,10 +35,22 @@ export interface UseActionResults {
4035
limit: number;
4136
sortField: string;
4237
kuery?: string;
43-
skip?: boolean;
4438
isLive?: boolean;
4539
}
4640

41+
interface ActionResultsResponse {
42+
edges: ResultEdges;
43+
total: number;
44+
aggregations: {
45+
totalRowCount: number;
46+
totalResponded: number;
47+
successful: number;
48+
failed: number;
49+
pending: number;
50+
};
51+
inspect?: InspectResponse;
52+
}
53+
4754
export const useActionResults = ({
4855
actionId,
4956
activePage,
@@ -53,78 +60,47 @@ export const useActionResults = ({
5360
sortField,
5461
kuery,
5562
startDate,
56-
skip = false,
5763
isLive = false,
5864
}: UseActionResults) => {
59-
const { data } = useKibana().services;
65+
const { http } = useKibana().services;
6066
const setErrorToast = useErrorToast();
6167

62-
return useQuery<{}, Error, ActionResultsStrategyResponse>(
63-
['actionResults', { actionId }],
64-
async () => {
65-
const responseData = await lastValueFrom(
66-
data.search.search<ActionResultsRequestOptions, ActionResultsStrategyResponse>(
67-
{
68-
actionId,
69-
startDate,
70-
factoryQueryType: OsqueryQueries.actionResults,
71-
kuery,
72-
pagination: generateTablePaginationOptions(activePage, limit),
73-
sort: {
74-
direction,
75-
field: sortField,
76-
},
77-
},
78-
{
79-
strategy: 'osquerySearchStrategy',
80-
}
81-
)
82-
);
83-
84-
const totalResponded =
85-
responseData.rawResponse?.aggregations?.aggs.responses_by_action_id?.doc_count ?? 0;
86-
const totalRowCount =
87-
responseData.rawResponse?.aggregations?.aggs.responses_by_action_id?.rows_count?.value ?? 0;
88-
const aggsBuckets =
89-
responseData.rawResponse?.aggregations?.aggs.responses_by_action_id?.responses.buckets;
90-
91-
const cachedData = queryClient.getQueryData<ActionResultsStrategyResponse>([
92-
'actionResults',
93-
{ actionId },
94-
]);
95-
96-
const previousEdges = cachedData?.edges.length
97-
? cachedData?.edges
98-
: agentIds?.map(
99-
(agentId) =>
100-
({ fields: { agent_id: [agentId] } } as unknown as estypes.SearchHit<object>)
101-
) ?? [];
102-
103-
return {
104-
...responseData,
105-
edges: reverse(uniqBy('fields.agent_id[0]', flatten([responseData.edges, previousEdges]))),
106-
aggregations: {
107-
totalRowCount,
108-
totalResponded,
109-
successful: aggsBuckets?.find((bucket) => bucket.key === 'success')?.doc_count ?? 0,
110-
failed: aggsBuckets?.find((bucket) => bucket.key === 'error')?.doc_count ?? 0,
68+
return useQuery<ActionResultsResponse, Error, ActionResultsArgs>(
69+
['actionResults', { actionId, activePage, limit, direction, sortField }],
70+
() =>
71+
http.get<ActionResultsResponse>(`/api/osquery/action_results/${actionId}`, {
72+
version: API_VERSIONS.public.v1,
73+
query: {
74+
page: activePage,
75+
pageSize: limit,
76+
sort: sortField,
77+
sortOrder: direction,
78+
...(kuery && { kuery }),
79+
...(startDate && { startDate }),
80+
...(agentIds?.length && { agentIds: agentIds.join(',') }),
11181
},
112-
inspect: getInspectResponse(responseData, {} as InspectResponse),
113-
};
114-
},
82+
}),
11583
{
84+
select: (response) => ({
85+
edges: response.edges,
86+
aggregations: response.aggregations,
87+
inspect: response.inspect || { dsl: [], response: [] },
88+
}),
11689
initialData: {
11790
edges: [],
91+
total: 0,
11892
aggregations: {
93+
totalRowCount: 0,
11994
totalResponded: 0,
12095
successful: 0,
12196
pending: agentIds?.length ?? 0,
12297
failed: 0,
12398
},
99+
inspect: { dsl: [], response: [] },
124100
},
125101
refetchInterval: isLive ? 5000 : false,
126102
keepPreviousData: true,
127-
enabled: !skip && !!agentIds?.length,
103+
enabled: !!agentIds?.length,
128104
onSuccess: () => setErrorToast(),
129105
onError: (error) =>
130106
setErrorToast(error, {

0 commit comments

Comments
 (0)