-
Notifications
You must be signed in to change notification settings - Fork 8.6k
[SIEM] Create ML Rules #58053
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[SIEM] Create ML Rules #58053
Changes from all commits
a61dd5e
5843a9a
34bf432
56a0766
0b19ddc
28ede95
bec1a90
d9639af
190c7bb
919dfef
051f638
7ae29bf
6b25593
d5b03a2
82093ce
bb8abc2
526e8a3
a5d05e0
75eaa12
68cbf21
7a62dba
31ac415
26822eb
7434bcb
fb7c5f1
cec8075
b80d5d0
05d4dda
79d9cb4
5424746
a689d33
9cb88c7
85678cb
316a720
2cd97f1
23532ab
41e5af1
0a8caf0
9aca40c
330cc0c
92f9c57
addc6ac
0ca3858
190600c
a683ef5
94c1774
327a877
e85feb5
e80dd2c
5e85c7e
eed90df
abfcfc1
f0a1f0f
e703568
8ff89b0
edf354e
a949ce5
1f1b9e9
cf8d8c3
f692e01
1aa4321
8ff01e9
16b25d1
b4fd572
462d410
9f9f324
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| /* | ||
| * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| * or more contributor license agreements. Licensed under the Elastic License; | ||
| * you may not use this file except in compliance with the Elastic License. | ||
| */ | ||
|
|
||
| import React, { useCallback } from 'react'; | ||
| import { EuiFlexGrid, EuiFlexItem, EuiRange, EuiFormRow } from '@elastic/eui'; | ||
|
|
||
| import { FieldHook } from '../../../../../shared_imports'; | ||
|
|
||
| interface AnomalyThresholdSliderProps { | ||
| field: FieldHook; | ||
| } | ||
| type Event = React.ChangeEvent<HTMLInputElement>; | ||
| type EventArg = Event | React.MouseEvent<HTMLButtonElement>; | ||
|
|
||
| export const AnomalyThresholdSlider: React.FC<AnomalyThresholdSliderProps> = ({ field }) => { | ||
| const threshold = field.value as number; | ||
| const onThresholdChange = useCallback( | ||
| (event: EventArg) => { | ||
| const thresholdValue = Number((event as Event).target.value); | ||
| field.setValue(thresholdValue); | ||
| }, | ||
| [field] | ||
| ); | ||
|
|
||
| return ( | ||
| <EuiFormRow label={field.label} fullWidth> | ||
| <EuiFlexGrid columns={2}> | ||
| <EuiFlexItem> | ||
| <EuiRange | ||
| value={threshold} | ||
| onChange={onThresholdChange} | ||
| fullWidth | ||
| showInput | ||
| showRange | ||
| showTicks | ||
| tickInterval={25} | ||
| /> | ||
| </EuiFlexItem> | ||
| </EuiFlexGrid> | ||
| </EuiFormRow> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,7 @@ | |
| */ | ||
|
|
||
| import { EuiDescriptionList, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; | ||
| import { isEmpty, chunk, get, pick } from 'lodash/fp'; | ||
| import { isEmpty, chunk, get, pick, isNumber } from 'lodash/fp'; | ||
| import React, { memo, useState } from 'react'; | ||
| import styled from 'styled-components'; | ||
|
|
||
|
|
@@ -14,7 +14,6 @@ import { | |
| Filter, | ||
| esFilters, | ||
| FilterManager, | ||
| Query, | ||
| } from '../../../../../../../../../../src/plugins/data/public'; | ||
| import { DEFAULT_TIMELINE_TITLE } from '../../../../../components/timeline/translations'; | ||
| import { useKibana } from '../../../../../lib/kibana'; | ||
|
|
@@ -133,14 +132,14 @@ export const addFilterStateIfNotThere = (filters: Filter[]): Filter[] => { | |
| export const getDescriptionItem = ( | ||
| field: string, | ||
| label: string, | ||
| value: unknown, | ||
| data: unknown, | ||
| filterManager: FilterManager, | ||
| indexPatterns?: IIndexPattern | ||
| ): ListItems[] => { | ||
| if (field === 'queryBar') { | ||
| const filters = addFilterStateIfNotThere(get('queryBar.filters', value) ?? []); | ||
| const query = get('queryBar.query', value) as Query; | ||
| const savedId = get('queryBar.saved_id', value); | ||
| const filters = addFilterStateIfNotThere(get('queryBar.filters', data) ?? []); | ||
| const query = get('queryBar.query.query', data); | ||
| const savedId = get('queryBar.saved_id', data); | ||
|
rylnd marked this conversation as resolved.
|
||
| return buildQueryBarDescription({ | ||
| field, | ||
| filters, | ||
|
|
@@ -150,43 +149,37 @@ export const getDescriptionItem = ( | |
| indexPatterns, | ||
| }); | ||
| } else if (field === 'threat') { | ||
| const threat: IMitreEnterpriseAttack[] = get(field, value).filter( | ||
| const threat: IMitreEnterpriseAttack[] = get(field, data).filter( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would be better as just an unsafe cast rather than lodash get for a refactor like so: // TODO: use io-ts or a TypeGuard "is" to correctly narrow this later.
const threats = data as { threat: IMitreEnterpriseAttack[] };
const threat = threats.threat.filter(singleThreat => singleThreat.tactic.name !== 'none');It would be cleaner and if/when you have the io-ts type you can run a decode on it and handle a left and a right with the decode for more run time safety to do maybe a throw error if a maintainer messes it up.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deferred to #60567 |
||
| (singleThreat: IMitreEnterpriseAttack) => singleThreat.tactic.name !== 'none' | ||
| ); | ||
| return buildThreatDescription({ label, threat }); | ||
| } else if (field === 'references') { | ||
| const urls: string[] = get(field, value); | ||
| const urls: string[] = get(field, data); | ||
| return buildUrlsDescription(label, urls); | ||
| } else if (field === 'falsePositives') { | ||
| const values: string[] = get(field, value); | ||
| const values: string[] = get(field, data); | ||
| return buildUnorderedListArrayDescription(label, field, values); | ||
| } else if (Array.isArray(get(field, value))) { | ||
| const values: string[] = get(field, value); | ||
| } else if (Array.isArray(get(field, data))) { | ||
| const values: string[] = get(field, data); | ||
| return buildStringArrayDescription(label, field, values); | ||
| } else if (field === 'severity') { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Woa...This is very abnormal code flow you are refactoring here. } else if (field === 'falsePositives') {
const values: string[] = get(field, data);
return buildUnorderedListArrayDescription(label, field, values);
} else if (Array.isArray(get(field, data))) {
const values: string[] = get(field, data);
return buildStringArrayDescription(label, field, values);
} else if (field === 'severity') {It checks if it is an array rather than a field value part way down but not the the field switch? } else if (Array.isArray(get(field, data))) {This is going to make future changes brittle and error prone depending on where we add the field value. I would ask you do us all a favor and add what the field value is through a call of: } else if (field === 'whatever_string_value_this_is') {That might not even be getting touched? It is really hard to understand code the more I look at it.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deferred to #60567 |
||
| const val: string = get(field, value); | ||
| const val: string = get(field, data); | ||
| return buildSeverityDescription(label, val); | ||
| } else if (field === 'riskScore') { | ||
| return [ | ||
| { | ||
| title: label, | ||
| description: get(field, value), | ||
| }, | ||
| ]; | ||
| } else if (field === 'timeline') { | ||
| const timeline = get(field, value) as FieldValueTimeline; | ||
| const timeline = get(field, data) as FieldValueTimeline; | ||
| return [ | ||
| { | ||
| title: label, | ||
| description: timeline.title ?? DEFAULT_TIMELINE_TITLE, | ||
| }, | ||
| ]; | ||
| } else if (field === 'note') { | ||
| const val: string = get(field, value); | ||
| const val: string = get(field, data); | ||
| return buildNoteDescription(label, val); | ||
| } | ||
| const description: string = get(field, value); | ||
| if (!isEmpty(description)) { | ||
|
|
||
| const description: string = get(field, data); | ||
| if (isNumber(description) || !isEmpty(description)) { | ||
| return [ | ||
| { | ||
| title: label, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| /* | ||
| * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| * or more contributor license agreements. Licensed under the Elastic License; | ||
| * you may not use this file except in compliance with the Elastic License. | ||
| */ | ||
|
|
||
| import React, { useCallback } from 'react'; | ||
| import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSuperSelect, EuiText } from '@elastic/eui'; | ||
|
|
||
| import { FieldHook, getFieldValidityAndErrorMessage } from '../../../../../shared_imports'; | ||
| import { useSiemJobs } from '../../../../../components/ml_popover/hooks/use_siem_jobs'; | ||
|
|
||
| const JobDisplay = ({ title, description }: { title: string; description: string }) => ( | ||
| <> | ||
| <strong>{title}</strong> | ||
| <EuiText size="xs" color="subdued"> | ||
| <p>{description}</p> | ||
| </EuiText> | ||
| </> | ||
| ); | ||
|
|
||
| interface MlJobSelectProps { | ||
| field: FieldHook; | ||
| } | ||
|
|
||
| export const MlJobSelect: React.FC<MlJobSelectProps> = ({ field }) => { | ||
| const jobId = field.value as string; | ||
|
rylnd marked this conversation as resolved.
|
||
| const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); | ||
| const [isLoading, siemJobs] = useSiemJobs(false); | ||
| const handleJobChange = useCallback( | ||
| (machineLearningJobId: string) => { | ||
| field.setValue(machineLearningJobId); | ||
| }, | ||
| [field] | ||
| ); | ||
|
|
||
| const options = siemJobs.map(job => ({ | ||
| value: job.id, | ||
| inputDisplay: job.id, | ||
| dropdownDisplay: <JobDisplay title={job.id} description={job.description} />, | ||
| })); | ||
|
|
||
| return ( | ||
| <EuiFormRow fullWidth label={field.label} isInvalid={isInvalid} error={errorMessage}> | ||
| <EuiFlexGroup> | ||
| <EuiFlexItem> | ||
| <EuiSuperSelect | ||
| hasDividers | ||
| isLoading={isLoading} | ||
| onChange={handleJobChange} | ||
| options={options} | ||
| valueOfSelected={jobId} | ||
| /> | ||
| </EuiFlexItem> | ||
| </EuiFlexGroup> | ||
| </EuiFormRow> | ||
| ); | ||
| }; | ||
Uh oh!
There was an error while loading. Please reload this page.