Skip to content

Commit

Permalink
Added support for editing correlation rule (#643)
Browse files Browse the repository at this point in the history
* added support for editing correlation rule

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* fixed query deletion; api method

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* removed logs

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* fixed rule expression builder

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

---------

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>
  • Loading branch information
amsiglan committed Jul 12, 2023
1 parent 205ee18 commit 0fee0e8
Show file tree
Hide file tree
Showing 13 changed files with 249 additions and 100 deletions.
4 changes: 2 additions & 2 deletions public/pages/Correlations/containers/CorrelationRules.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ export const CorrelationRules: React.FC<RouteComponentProps> = (props: RouteComp

const onRuleNameClick = useCallback((rule: CorrelationRule) => {
props.history.push({
pathname: ROUTES.CORRELATION_RULE_CREATE,
state: { rule, isReadOnly: true },
pathname: `${ROUTES.CORRELATION_RULE_EDIT}/${rule.id}`,
state: { rule, isReadOnly: false },
});
}, []);

Expand Down
156 changes: 73 additions & 83 deletions public/pages/Correlations/containers/CreateCorrelationRule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ import {
CorrelationRuleModel,
CorrelationRuleQuery,
} from '../../../../types';
import { BREADCRUMBS, ROUTES, isDarkMode } from '../../../utils/constants';
import { BREADCRUMBS, ROUTES } from '../../../utils/constants';
import { CoreServicesContext } from '../../../components/core_services';
import { RouteComponentProps } from 'react-router-dom';
import { RouteComponentProps, useParams } from 'react-router-dom';
import { CorrelationsExperimentalBanner } from '../components/ExperimentalBanner';
import { validateName } from '../../../utils/validation';
import { FieldMappingService, IndexService } from '../../../services';
Expand Down Expand Up @@ -99,41 +99,46 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (

return undefined;
}, []);
const params = useParams<{ ruleId: string }>();
const [initialValues, setInitialValues] = useState({
...correlationRuleStateDefaultValue,
});
const [action, setAction] = useState<CorrelationRuleAction>('Create');

useEffect(() => {
if (props.history.location.state?.rule) {
setAction('Edit');
setInitialValues(props.history.location.state?.rule);
} else if (params.ruleId) {
const setInitialRuleValues = async () => {
const ruleRes = await correlationStore.getCorrelationRule(params.ruleId);
if (ruleRes) {
setInitialValues(ruleRes);
}
};

setAction('Edit');
setInitialRuleValues();
}
}, []);

const submit = async (values: any) => {
let error;
if ((error = validateCorrelationRule(values))) {
errorNotificationToast(props.notifications, 'Create', 'rule', error);
errorNotificationToast(props.notifications, action, 'rule', error);
return;
}

await correlationStore.createCorrelationRule(values);
if (action === 'Edit') {
await correlationStore.updateCorrelationRule(values);
} else {
await correlationStore.createCorrelationRule(values);
}

props.history.push(ROUTES.CORRELATION_RULES);
};

const context = useContext(CoreServicesContext);
let action: CorrelationRuleAction = 'Create';
let initialValues = {
...correlationRuleStateDefaultValue,
};

if (props.history.location.state?.rule) {
action = 'Edit';
initialValues = props.history.location.state?.rule;

if (props.history.location.state.isReadOnly) {
action = 'Readonly';
}
}

const disableForm = action === 'Readonly';
const textClassName = disableForm
? isDarkMode
? 'readonly-text-color-dark-mode'
: 'readonly-text-color-light-mode'
: undefined;

const parseOptions = (indices: string[]) => {
return indices.map(
(index: string): CorrelationOptions => ({
Expand Down Expand Up @@ -206,9 +211,17 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
</EuiTitle>
}
extraAction={
queryIdx > 1 ? (
correlationQueries.length > 2 ? (
<EuiToolTip title={'Delete query'}>
<EuiButtonIcon iconType={'trash'} color="danger" />
<EuiButtonIcon
iconType={'trash'}
color="danger"
onClick={() => {
const newQueries = [...correlationQueries];
newQueries.splice(queryIdx, 1);
props.setFieldValue('queries', newQueries);
}}
/>
</EuiToolTip>
) : null
}
Expand Down Expand Up @@ -247,8 +260,6 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
query.index ? [{ value: query.index, label: query.index }] : []
}
isClearable={true}
isDisabled={disableForm}
className={textClassName}
/>
</EuiFormRow>
<EuiSpacer size="m" />
Expand Down Expand Up @@ -279,8 +290,6 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
onCreateOption={(e) => {
props.handleChange(`queries[${queryIdx}].logType`)(e);
}}
isDisabled={disableForm}
className={textClassName}
/>
</EuiFormRow>
<EuiSpacer size="xl" />
Expand Down Expand Up @@ -313,8 +322,6 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
)(e);
}}
isClearable={true}
isDisabled={disableForm}
className={textClassName}
/>
);

Expand All @@ -332,8 +339,6 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
`queries[${queryIdx}].conditions[${conditionIdx}].value`
)}
value={condition.value}
disabled={disableForm}
className={textClassName}
/>
);

Expand All @@ -352,7 +357,6 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
)(e);
}}
className={'correlation_rule_field_condition'}
isDisabled={disableForm}
/>
);

Expand Down Expand Up @@ -387,7 +391,7 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
initialIsOpen={true}
buttonContent={`Field ${conditionIdx + 1}`}
extraAction={
query.conditions.length > 1 && !disableForm ? (
query.conditions.length > 1 ? (
<EuiToolTip title={'Delete field'}>
<EuiButtonIcon
iconType={'trash'}
Expand All @@ -400,7 +404,6 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
newCases
);
}}
disabled={disableForm}
/>
</EuiToolTip>
) : null
Expand All @@ -415,43 +418,37 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
</>
);
})}
{disableForm ? null : (
<EuiButton
style={{ width: 125 }}
onClick={() => {
props.setFieldValue(`queries[${queryIdx}].conditions`, [
...query.conditions,
...correlationRuleStateDefaultValue.queries[0].conditions,
]);
}}
iconType={'plusInCircle'}
disabled={disableForm}
>
Add field
</EuiButton>
)}
<EuiButton
style={{ width: 125 }}
onClick={() => {
props.setFieldValue(`queries[${queryIdx}].conditions`, [
...query.conditions,
...correlationRuleStateDefaultValue.queries[0].conditions,
]);
}}
iconType={'plusInCircle'}
>
Add field
</EuiButton>
</EuiAccordion>
</EuiPanel>
<EuiSpacer />
</>
);
})}
<EuiSpacer />
{disableForm ? null : (
<EuiButton
onClick={() => {
props.setFieldValue('queries', [
...correlationQueries,
{ ...correlationRuleStateDefaultValue.queries[0] },
]);
}}
iconType={'plusInCircle'}
fullWidth={true}
disabled={disableForm}
>
Add query
</EuiButton>
)}
<EuiButton
onClick={() => {
props.setFieldValue('queries', [
...correlationQueries,
{ ...correlationRuleStateDefaultValue.queries[0] },
]);
}}
iconType={'plusInCircle'}
fullWidth={true}
>
Add query
</EuiButton>
</>
);
};
Expand All @@ -469,14 +466,12 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
<>
<CorrelationsExperimentalBanner />
<EuiTitle>
<h1>{action === 'Readonly' ? 'C' : `${action} c`}orrelation rule</h1>
<h1>{`${action} correlation rule`}</h1>
</EuiTitle>
{action === 'Readonly' ? null : (
<EuiText size="s" color="subdued">
{action === 'Create' ? 'Create a' : 'Edit'} correlation rule to define threat scenarios of
interest between different log sources.
</EuiText>
)}
<EuiText size="s" color="subdued">
{action === 'Create' ? 'Create a' : 'Edit'} correlation rule to define threat scenarios of
interest between different log sources.
</EuiText>
<EuiSpacer size="l" />
<Formik
initialValues={initialValues}
Expand All @@ -497,6 +492,7 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
setSubmitting(false);
submit(values);
}}
enableReinitialize={true}
>
{({ values: { name, queries }, touched, errors, ...props }) => {
return (
Expand All @@ -514,9 +510,7 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
isInvalid={touched.name && !!errors?.name}
error={errors.name}
helpText={
disableForm
? undefined
: 'Rule name must contain 5-50 characters. Valid characters are a-z, A-Z, 0-9, hyphens, spaces, and underscores.'
'Rule name must contain 5-50 characters. Valid characters are a-z, A-Z, 0-9, hyphens, spaces, and underscores.'
}
>
<EuiFieldText
Expand All @@ -528,8 +522,6 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
}}
onBlur={props.handleBlur('name')}
value={name}
className={textClassName}
disabled={disableForm}
/>
</EuiFormRow>
<EuiSpacer />
Expand All @@ -538,9 +530,7 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
<ContentPanel
title="Correlation queries"
subTitleText={
disableForm
? 'Conditions used to match correlated findings.'
: 'Configure two or more queries to set the conditions for correlating findings.'
'Configure two or more queries to set the conditions for correlating findings.'
}
panelStyles={{ paddingLeft: 10, paddingRight: 10 }}
>
Expand Down
11 changes: 11 additions & 0 deletions public/pages/Main/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,17 @@ export default class Main extends Component<MainProps, MainState> {
/>
)}
/>
<Route
path={`${ROUTES.CORRELATION_RULE_EDIT}/:ruleId`}
render={(props: RouteComponentProps<any, any, any>) => (
<CreateCorrelationRule
{...props}
indexService={services?.indexService}
fieldMappingService={services?.fieldMappingService}
notifications={core?.notifications}
/>
)}
/>
<Route
path={`${ROUTES.CORRELATIONS}`}
render={(props: RouteComponentProps<any, any, any>) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
EuiButtonIcon,
EuiExpression,
} from '@elastic/eui';
import * as _ from 'lodash';
import _ from 'lodash';
import { Selection } from '../DetectionVisualEditor';

export interface SelectionExpFieldProps {
Expand All @@ -24,20 +24,46 @@ interface UsedSelection {
description: string;
}

const operationOptionsFirstExpression = [
{ value: '', text: '' },
{ value: 'not', text: 'NOT' },
];

const operatorOptions = [
{ value: '', text: '' },
{ value: 'and', text: 'AND' },
{ value: 'or', text: 'OR' },
{ value: 'and not', text: 'AND NOT' },
{ value: 'or not', text: 'OR NOT' },
];

export const SelectionExpField: React.FC<SelectionExpFieldProps> = ({
selections,
dataTestSubj,
onChange,
value,
}) => {
const DEFAULT_DESCRIPTION = 'Select';
const OPERATORS = ['and', 'or', 'not'];
const OPERATORS = ['and', 'or', 'and not', 'or not', 'not'];
const [usedExpressions, setUsedExpressions] = useState<UsedSelection[]>([]);

useEffect(() => {
let expressions: UsedSelection[] = [];
if (value?.length) {
let values = value.split(' ');
const temp = value.split('and not');
let values = temp
.map((_) => {
return _.trim()
.split('or not')
.map((leaf) => leaf.split(' '))
.reduce((prev, curr) => {
return [...prev, 'or not', ...curr];
});
})
.reduce((prev, curr) => {
return [...prev, 'and not', ...curr];
});

if (OPERATORS.indexOf(values[0]) === -1) values = ['', ...values];

let counter = 0;
Expand Down Expand Up @@ -110,12 +136,7 @@ export const SelectionExpField: React.FC<SelectionExpFieldProps> = ({
compressed
value={exp.description}
onChange={(e) => changeExtDescription(e, exp, idx)}
options={[
{ value: '', text: '' },
{ value: 'and', text: 'AND' },
{ value: 'or', text: 'OR' },
{ value: 'not', text: 'NOT' },
]}
options={idx === 0 ? operationOptionsFirstExpression : operatorOptions}
/>
</EuiFlexItem>
{selections.length > usedExpressions.length && (
Expand Down Expand Up @@ -212,7 +233,7 @@ export const SelectionExpField: React.FC<SelectionExpFieldProps> = ({
description={exp.description}
value={exp.name}
isActive={exp.isOpen}
onClick={(e) => onSelectionPopup(e, idx)}
onClick={(e: any) => onSelectionPopup(e, idx)}
/>
}
isOpen={exp.isOpen}
Expand Down
Loading

0 comments on commit 0fee0e8

Please sign in to comment.