Skip to content

Commit 0a57403

Browse files
authored
[SIEM] [DETECTION ENGINE] Details and Edit view for a rule (#53252) (#53698)
* re-structure detection engine + change routing name * add editing/details feature for a rule add feature to not edit immutable rule * review I * review II * change constant * review III
1 parent 6ac1726 commit 0a57403

File tree

85 files changed

+2188
-1528
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

85 files changed

+2188
-1528
lines changed

x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
FetchRulesResponse,
1515
NewRule,
1616
Rule,
17+
FetchRuleProps,
1718
} from './types';
1819
import { throwIfNotOk } from '../../../hooks/api/api';
1920
import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants';
@@ -27,7 +28,7 @@ import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants';
2728
*/
2829
export const addRule = async ({ rule, kbnVersion, signal }: AddRulesProps): Promise<NewRule> => {
2930
const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}`, {
30-
method: 'POST',
31+
method: rule.id != null ? 'PUT' : 'POST',
3132
credentials: 'same-origin',
3233
headers: {
3334
'content-type': 'application/json',
@@ -96,6 +97,28 @@ export const fetchRules = async ({
9697
: response.json();
9798
};
9899

100+
/**
101+
* Fetch a Rule by providing a Rule ID
102+
*
103+
* @param id Rule ID's (not rule_id)
104+
* @param kbnVersion current Kibana Version to use for headers
105+
*/
106+
export const fetchRuleById = async ({ id, kbnVersion, signal }: FetchRuleProps): Promise<Rule> => {
107+
const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}?id=${id}`, {
108+
method: 'GET',
109+
credentials: 'same-origin',
110+
headers: {
111+
'content-type': 'application/json',
112+
'kbn-version': kbnVersion,
113+
'kbn-xsrf': kbnVersion,
114+
},
115+
signal,
116+
});
117+
await throwIfNotOk(response);
118+
const rule: Rule = await response.json();
119+
return rule;
120+
};
121+
99122
/**
100123
* Enables/Disables provided Rule ID's
101124
*
@@ -177,11 +200,14 @@ export const duplicateRules = async ({
177200
body: JSON.stringify({
178201
...rule,
179202
name: `${rule.name} [Duplicate]`,
203+
created_at: undefined,
180204
created_by: undefined,
181205
id: undefined,
182206
rule_id: undefined,
207+
updated_at: undefined,
183208
updated_by: undefined,
184209
enabled: rule.enabled,
210+
immutable: false,
185211
}),
186212
})
187213
);

x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/fetch_index_patterns.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7-
import { isEmpty, get } from 'lodash/fp';
7+
import { isEmpty, isEqual, get } from 'lodash/fp';
88
import { useEffect, useState, Dispatch, SetStateAction } from 'react';
9-
import { IIndexPattern } from 'src/plugins/data/public';
109

10+
import { IIndexPattern } from '../../../../../../../../src/plugins/data/public';
1111
import {
1212
BrowserFields,
1313
getBrowserFields,
@@ -40,6 +40,12 @@ export const useFetchIndexPatterns = (defaultIndices: string[] = []): Return =>
4040
const [isLoading, setIsLoading] = useState(false);
4141
const [, dispatchToaster] = useStateToaster();
4242

43+
useEffect(() => {
44+
if (!isEqual(defaultIndices, indices)) {
45+
setIndices(defaultIndices);
46+
}
47+
}, [defaultIndices, indices]);
48+
4349
useEffect(() => {
4450
let isSubscribed = true;
4551
const abortCtrl = new AbortController();
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
export * from './api';
8+
export * from './fetch_index_patterns';
9+
export * from './persist_rule';
10+
export * from './types';
11+
export * from './use_rule';
12+
export * from './use_rules';

x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ export const NewRuleSchema = t.intersection([
1010
t.type({
1111
description: t.string,
1212
enabled: t.boolean,
13+
filters: t.array(t.unknown),
1314
index: t.array(t.string),
1415
interval: t.string,
1516
language: t.string,
1617
name: t.string,
1718
query: t.string,
19+
risk_score: t.number,
1820
severity: t.string,
1921
type: t.union([t.literal('query'), t.literal('saved_query')]),
2022
}),
@@ -26,7 +28,9 @@ export const NewRuleSchema = t.intersection([
2628
max_signals: t.number,
2729
references: t.array(t.string),
2830
rule_id: t.string,
31+
saved_id: t.string,
2932
tags: t.array(t.string),
33+
threats: t.array(t.unknown),
3034
to: t.string,
3135
updated_by: t.string,
3236
}),
@@ -41,29 +45,41 @@ export interface AddRulesProps {
4145
signal: AbortSignal;
4246
}
4347

48+
const MetaRule = t.type({
49+
from: t.string,
50+
});
51+
4452
export const RuleSchema = t.intersection([
4553
t.type({
54+
created_at: t.string,
4655
created_by: t.string,
4756
description: t.string,
4857
enabled: t.boolean,
58+
false_positives: t.array(t.string),
59+
filters: t.array(t.unknown),
60+
from: t.string,
4961
id: t.string,
5062
index: t.array(t.string),
5163
interval: t.string,
64+
immutable: t.boolean,
5265
language: t.string,
5366
name: t.string,
67+
max_signals: t.number,
68+
meta: MetaRule,
5469
query: t.string,
70+
references: t.array(t.string),
71+
risk_score: t.number,
5572
rule_id: t.string,
5673
severity: t.string,
5774
type: t.string,
75+
tags: t.array(t.string),
76+
to: t.string,
77+
threats: t.array(t.unknown),
78+
updated_at: t.string,
5879
updated_by: t.string,
5980
}),
6081
t.partial({
61-
false_positives: t.array(t.string),
62-
from: t.string,
63-
max_signals: t.number,
64-
references: t.array(t.string),
65-
tags: t.array(t.string),
66-
to: t.string,
82+
saved_id: t.string,
6783
}),
6884
]);
6985

@@ -99,6 +115,12 @@ export interface FetchRulesResponse {
99115
data: Rule[];
100116
}
101117

118+
export interface FetchRuleProps {
119+
id: string;
120+
kbnVersion: string;
121+
signal: AbortSignal;
122+
}
123+
102124
export interface EnableRulesProps {
103125
ids: string[];
104126
enabled: boolean;
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { useEffect, useState } from 'react';
8+
9+
import { useKibanaUiSetting } from '../../../lib/settings/use_kibana_ui_setting';
10+
import { DEFAULT_KBN_VERSION } from '../../../../common/constants';
11+
import { useStateToaster } from '../../../components/toasters';
12+
import { errorToToaster } from '../../../components/ml/api/error_to_toaster';
13+
import { fetchRuleById } from './api';
14+
import * as i18n from './translations';
15+
import { Rule } from './types';
16+
17+
type Return = [boolean, Rule | null];
18+
19+
/**
20+
* Hook for using to get a Rule from the Detection Engine API
21+
*
22+
* @param id desired Rule ID's (not rule_id)
23+
*
24+
*/
25+
export const useRule = (id: string | undefined): Return => {
26+
const [rule, setRule] = useState<Rule | null>(null);
27+
const [loading, setLoading] = useState(true);
28+
const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION);
29+
const [, dispatchToaster] = useStateToaster();
30+
31+
useEffect(() => {
32+
let isSubscribed = true;
33+
const abortCtrl = new AbortController();
34+
35+
async function fetchData(idToFetch: string) {
36+
try {
37+
setLoading(true);
38+
const ruleResponse = await fetchRuleById({
39+
id: idToFetch,
40+
kbnVersion,
41+
signal: abortCtrl.signal,
42+
});
43+
44+
if (isSubscribed) {
45+
setRule(ruleResponse);
46+
}
47+
} catch (error) {
48+
if (isSubscribed) {
49+
setRule(null);
50+
errorToToaster({ title: i18n.RULE_FETCH_FAILURE, error, dispatchToaster });
51+
}
52+
}
53+
if (isSubscribed) {
54+
setLoading(false);
55+
}
56+
}
57+
if (id != null) {
58+
fetchData(id);
59+
}
60+
return () => {
61+
isSubscribed = false;
62+
abortCtrl.abort();
63+
};
64+
}, [id]);
65+
66+
return [loading, rule];
67+
};

x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/api.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,46 @@
55
*/
66

77
import chrome from 'ui/chrome';
8-
import { UpdateSignalStatusProps } from './types';
8+
99
import { throwIfNotOk } from '../../../hooks/api/api';
10-
import { DETECTION_ENGINE_SIGNALS_STATUS_URL } from '../../../../common/constants';
10+
import {
11+
DETECTION_ENGINE_QUERY_SIGNALS_URL,
12+
DETECTION_ENGINE_SIGNALS_STATUS_URL,
13+
} from '../../../../common/constants';
14+
import { QuerySignals, SignalSearchResponse, UpdateSignalStatusProps } from './types';
15+
16+
/**
17+
* Fetch Signals by providing a query
18+
*
19+
* @param query String to match a dsl
20+
* @param kbnVersion current Kibana Version to use for headers
21+
*/
22+
export const fetchQuerySignals = async <Hit, Aggregations>({
23+
query,
24+
kbnVersion,
25+
signal,
26+
}: QuerySignals): Promise<SignalSearchResponse<Hit, Aggregations>> => {
27+
const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_QUERY_SIGNALS_URL}`, {
28+
method: 'POST',
29+
credentials: 'same-origin',
30+
headers: {
31+
'content-type': 'application/json',
32+
'kbn-version': kbnVersion,
33+
'kbn-xsrf': kbnVersion,
34+
},
35+
body: query,
36+
signal,
37+
});
38+
await throwIfNotOk(response);
39+
const signals = await response.json();
40+
return signals;
41+
};
1142

1243
/**
1344
* Update signal status by query
1445
*
1546
* @param query of signals to update
16-
* @param status to update to ('open' / 'closed')
47+
* @param status to update to('open' / 'closed')
1748
* @param kbnVersion current Kibana Version to use for headers
1849
* @param signal to cancel request
1950
*/

x-pack/legacy/plugins/siem/public/pages/detection_engine/edit_rule/translations.ts renamed to x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/translations.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
import { i18n } from '@kbn/i18n';
88

9-
export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.editRule.pageTitle', {
10-
defaultMessage: 'Edit rule settings',
11-
});
9+
export const SIGNAL_FETCH_FAILURE = i18n.translate(
10+
'xpack.siem.containers.detectionEngine.signals.errorFetchingSignalsDescription',
11+
{
12+
defaultMessage: 'Failed to query signals',
13+
}
14+
);

x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,34 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7+
export interface QuerySignals {
8+
query: string;
9+
kbnVersion: string;
10+
signal: AbortSignal;
11+
}
12+
13+
export interface SignalsResponse {
14+
took: number;
15+
timeout: boolean;
16+
}
17+
18+
export interface SignalSearchResponse<Hit = {}, Aggregations = undefined> extends SignalsResponse {
19+
_shards: {
20+
total: number;
21+
successful: number;
22+
skipped: number;
23+
failed: number;
24+
};
25+
aggregations?: Aggregations;
26+
hits: {
27+
total: {
28+
value: number;
29+
relation: string;
30+
};
31+
hits: Hit[];
32+
};
33+
}
34+
735
export interface UpdateSignalStatusProps {
836
query: object;
937
status: 'open' | 'closed';

0 commit comments

Comments
 (0)