Skip to content

Commit

Permalink
[Security] Adds field mapping support to rule creation Part II (#71402)
Browse files Browse the repository at this point in the history
## Summary

Followup to #70288, which includes:

- [X] Rule Execution logic for:
  - [X] Severity Override
  - [X] Risk Score Override
  - [X] Rule Name Override
  - [X] Timestamp Override
- [X] Support for toggling display of Building Block Rules:
  - [X] Main Detections Page
  - [X] Rule Details Page
- [X] Integrates `AutocompleteField` for:
  - [X] Severity Override
  - [X] Risk Score Override
  - [X] Rule Name Override
  - [X] Timestamp Override
- [X] Fixes rehydration of `EditAboutStep` in `Edit Rule`
- [X] Fixes `Rule Details` Description rollup


Additional followup cleanup:
- [ ] Adds risk_score` to `risk_score_mapping`
- [ ] Improves field validation
- [ ] Disables override fields for ML Rules
- [ ] Orders `SeverityMapping` by `severity` on create/update
- [ ] Allow unbounded max-signals


### Checklist

Delete any items that are not applicable to this PR.

- [X] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md)
- [ ] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials
  - Syncing w/ @benskelker
- [X] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios
### For maintainers

- [X] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)
  • Loading branch information
spong authored Jul 14, 2020
1 parent 754ade5 commit 8da80fe
Show file tree
Hide file tree
Showing 47 changed files with 874 additions and 156 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ export const severity_mapping_item = t.exact(
severity,
})
);
export type SeverityMappingItem = t.TypeOf<typeof severity_mapping_item>;

export const severity_mapping = t.array(severity_mapping_item);
export type SeverityMapping = t.TypeOf<typeof severity_mapping>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface OperatorProps {
isLoading: boolean;
isDisabled: boolean;
isClearable: boolean;
fieldTypeFilter?: string[];
fieldInputWidth?: number;
onChange: (a: IFieldType[]) => void;
}
Expand All @@ -28,13 +29,22 @@ export const FieldComponent: React.FC<OperatorProps> = ({
isLoading = false,
isDisabled = false,
isClearable = false,
fieldTypeFilter = [],
fieldInputWidth = 190,
onChange,
}): JSX.Element => {
const getLabel = useCallback((field): string => field.name, []);
const optionsMemo = useMemo((): IFieldType[] => (indexPattern ? indexPattern.fields : []), [
indexPattern,
]);
const optionsMemo = useMemo((): IFieldType[] => {
if (indexPattern != null) {
if (fieldTypeFilter.length > 0) {
return indexPattern.fields.filter((f) => fieldTypeFilter.includes(f.type));
} else {
return indexPattern.fields;
}
} else {
return [];
}
}, [fieldTypeFilter, indexPattern]);
const selectedOptionsMemo = useMemo((): IFieldType[] => (selectedField ? [selectedField] : []), [
selectedField,
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ interface AutocompleteFieldMatchProps {
isLoading: boolean;
isDisabled: boolean;
isClearable: boolean;
fieldInputWidth?: number;
onChange: (arg: string) => void;
}

Expand All @@ -33,6 +34,7 @@ export const AutocompleteFieldMatchComponent: React.FC<AutocompleteFieldMatchPro
isLoading,
isDisabled = false,
isClearable = false,
fieldInputWidth,
onChange,
}): JSX.Element => {
const [isLoadingSuggestions, suggestions, updateSuggestions] = useFieldValueAutocomplete({
Expand Down Expand Up @@ -97,6 +99,7 @@ export const AutocompleteFieldMatchComponent: React.FC<AutocompleteFieldMatchPro
isInvalid={!isValid}
sortMatchesBy="startsWith"
data-test-subj="valuesAutocompleteComboBox matchComboxBox"
style={fieldInputWidth ? { width: `${fieldInputWidth}px` } : {}}
fullWidth
async
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export { UtilityBar } from './utility_bar';
export { UtilityBarAction } from './utility_bar_action';
export { UtilityBarGroup } from './utility_bar_group';
export { UtilityBarSection } from './utility_bar_section';
export { UtilityBarSpacer } from './utility_bar_spacer';
export { UtilityBarText } from './utility_bar_text';
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ export interface BarProps {
border?: boolean;
}

export interface BarSectionProps {
grow?: boolean;
}

export interface BarGroupProps {
grow?: boolean;
}

export const Bar = styled.aside.attrs({
className: 'siemUtilityBar',
})<BarProps>`
Expand All @@ -36,8 +44,8 @@ Bar.displayName = 'Bar';

export const BarSection = styled.div.attrs({
className: 'siemUtilityBar__section',
})`
${({ theme }) => css`
})<BarSectionProps>`
${({ grow, theme }) => css`
& + & {
margin-top: ${theme.eui.euiSizeS};
}
Expand All @@ -53,14 +61,18 @@ export const BarSection = styled.div.attrs({
margin-left: ${theme.eui.euiSize};
}
}
${grow &&
css`
flex: 1;
`}
`}
`;
BarSection.displayName = 'BarSection';

export const BarGroup = styled.div.attrs({
className: 'siemUtilityBar__group',
})`
${({ theme }) => css`
})<BarGroupProps>`
${({ grow, theme }) => css`
align-items: flex-start;
display: flex;
flex-wrap: wrap;
Expand Down Expand Up @@ -93,6 +105,10 @@ export const BarGroup = styled.div.attrs({
margin-right: 0;
}
}
${grow &&
css`
flex: 1;
`}
`}
`;
BarGroup.displayName = 'BarGroup';
Expand All @@ -118,3 +134,12 @@ export const BarAction = styled.div.attrs({
`}
`;
BarAction.displayName = 'BarAction';

export const BarSpacer = styled.div.attrs({
className: 'siemUtilityBar__spacer',
})`
${() => css`
flex: 1;
`}
`;
BarSpacer.displayName = 'BarSpacer';
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@

import React from 'react';

import { BarGroup } from './styles';
import { BarGroup, BarGroupProps } from './styles';

export interface UtilityBarGroupProps {
export interface UtilityBarGroupProps extends BarGroupProps {
children: React.ReactNode;
}

export const UtilityBarGroup = React.memo<UtilityBarGroupProps>(({ children }) => (
<BarGroup>{children}</BarGroup>
export const UtilityBarGroup = React.memo<UtilityBarGroupProps>(({ grow, children }) => (
<BarGroup grow={grow}>{children}</BarGroup>
));

UtilityBarGroup.displayName = 'UtilityBarGroup';
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@

import React from 'react';

import { BarSection } from './styles';
import { BarSection, BarSectionProps } from './styles';

export interface UtilityBarSectionProps {
export interface UtilityBarSectionProps extends BarSectionProps {
children: React.ReactNode;
}

export const UtilityBarSection = React.memo<UtilityBarSectionProps>(({ children }) => (
<BarSection>{children}</BarSection>
export const UtilityBarSection = React.memo<UtilityBarSectionProps>(({ grow, children }) => (
<BarSection grow={grow}>{children}</BarSection>
));

UtilityBarSection.displayName = 'UtilityBarSection';
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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 from 'react';

import { BarSpacer } from './styles';

export interface UtilityBarSpacerProps {
dataTestSubj?: string;
}

export const UtilityBarSpacer = React.memo<UtilityBarSpacerProps>(({ dataTestSubj }) => (
<BarSpacer data-test-subj={dataTestSubj} />
));

UtilityBarSpacer.displayName = 'UtilityBarSpacer';
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ describe('AlertsUtilityBar', () => {
currentFilter="closed"
selectAll={jest.fn()}
showClearSelection={true}
showBuildingBlockAlerts={false}
onShowBuildingBlockAlertsChanged={jest.fn()}
updateAlertsStatus={jest.fn()}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import { isEmpty } from 'lodash/fp';
import React, { useCallback } from 'react';
import numeral from '@elastic/numeral';

import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiCheckbox } from '@elastic/eui';
import styled from 'styled-components';

import { Status } from '../../../../../common/detection_engine/schemas/common/schemas';
import { Link } from '../../../../common/components/link_icon';
import { DEFAULT_NUMBER_FORMAT } from '../../../../../common/constants';
Expand All @@ -18,6 +19,7 @@ import {
UtilityBarAction,
UtilityBarGroup,
UtilityBarSection,
UtilityBarSpacer,
UtilityBarText,
} from '../../../../common/components/utility_bar';
import * as i18n from './translations';
Expand All @@ -34,6 +36,8 @@ interface AlertsUtilityBarProps {
currentFilter: Status;
selectAll: () => void;
selectedEventIds: Readonly<Record<string, TimelineNonEcsData[]>>;
showBuildingBlockAlerts: boolean;
onShowBuildingBlockAlertsChanged: (showBuildingBlockAlerts: boolean) => void;
showClearSelection: boolean;
totalCount: number;
updateAlertsStatus: UpdateAlertsStatus;
Expand All @@ -52,6 +56,8 @@ const AlertsUtilityBarComponent: React.FC<AlertsUtilityBarProps> = ({
selectedEventIds,
currentFilter,
selectAll,
showBuildingBlockAlerts,
onShowBuildingBlockAlertsChanged,
showClearSelection,
updateAlertsStatus,
}) => {
Expand Down Expand Up @@ -125,17 +131,36 @@ const AlertsUtilityBarComponent: React.FC<AlertsUtilityBarProps> = ({
</UtilityBarFlexGroup>
);

const UtilityBarAdditionalFiltersContent = (closePopover: () => void) => (
<UtilityBarFlexGroup direction="column">
<EuiFlexItem>
<EuiCheckbox
id="showBuildingBlockAlertsCheckbox"
aria-label="showBuildingBlockAlerts"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
closePopover();
onShowBuildingBlockAlertsChanged(e.target.checked);
}}
checked={showBuildingBlockAlerts}
color="text"
data-test-subj="showBuildingBlockAlertsCheckbox"
label={i18n.ADDITIONAL_FILTERS_ACTIONS_SHOW_BUILDING_BLOCK}
/>
</EuiFlexItem>
</UtilityBarFlexGroup>
);

return (
<>
<UtilityBar>
<UtilityBarSection>
<UtilityBarSection grow={true}>
<UtilityBarGroup>
<UtilityBarText dataTestSubj="showingAlerts">
{i18n.SHOWING_ALERTS(formattedTotalCount, totalCount)}
</UtilityBarText>
</UtilityBarGroup>

<UtilityBarGroup>
<UtilityBarGroup grow={true}>
{canUserCRUD && hasIndexWrite && (
<>
<UtilityBarText dataTestSubj="selectedAlerts">
Expand Down Expand Up @@ -174,6 +199,17 @@ const AlertsUtilityBarComponent: React.FC<AlertsUtilityBarProps> = ({
</UtilityBarAction>
</>
)}
<UtilityBarSpacer />
<UtilityBarAction
dataTestSubj="additionalFilters"
disabled={areEventsLoading}
iconType="arrowDown"
iconSide="right"
ownFocus={false}
popoverContent={UtilityBarAdditionalFiltersContent}
>
{i18n.ADDITIONAL_FILTERS_ACTIONS}
</UtilityBarAction>
</UtilityBarGroup>
</UtilityBarSection>
</UtilityBar>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,20 @@ export const SELECT_ALL_ALERTS = (totalAlertsFormatted: string, totalAlerts: num
'Select all {totalAlertsFormatted} {totalAlerts, plural, =1 {alert} other {alerts}}',
});

export const ADDITIONAL_FILTERS_ACTIONS = i18n.translate(
'xpack.securitySolution.detectionEngine.alerts.utilityBar.additionalFiltersTitle',
{
defaultMessage: 'Additional filters',
}
);

export const ADDITIONAL_FILTERS_ACTIONS_SHOW_BUILDING_BLOCK = i18n.translate(
'xpack.securitySolution.detectionEngine.alerts.utilityBar.additionalFiltersActions.showBuildingBlockTitle',
{
defaultMessage: 'Include building block alerts',
}
);

export const CLEAR_SELECTION = i18n.translate(
'xpack.securitySolution.detectionEngine.alerts.utilityBar.clearSelectionTitle',
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,25 @@ export const buildAlertsRuleIdFilter = (ruleId: string): Filter[] => [
},
];

export const buildShowBuildingBlockFilter = (showBuildingBlockAlerts: boolean): Filter[] => [
...(showBuildingBlockAlerts
? []
: [
{
meta: {
alias: null,
negate: true,
disabled: false,
type: 'exists',
key: 'signal.rule.building_block_type',
value: 'exists',
},
// @ts-ignore TODO: Rework parent typings to support ExistsFilter[]
exists: { field: 'signal.rule.building_block_type' },
},
]),
];

export const alertsHeaders: ColumnHeaderOptions[] = [
{
columnHeaderType: defaultColumnHeaderType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ describe('AlertsTableComponent', () => {
clearEventsLoading={jest.fn()}
setEventsDeleted={jest.fn()}
clearEventsDeleted={jest.fn()}
showBuildingBlockAlerts={false}
onShowBuildingBlockAlertsChanged={jest.fn()}
updateTimelineIsLoading={jest.fn()}
updateTimeline={jest.fn()}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ interface OwnProps {
hasIndexWrite: boolean;
from: string;
loading: boolean;
showBuildingBlockAlerts: boolean;
onShowBuildingBlockAlertsChanged: (showBuildingBlockAlerts: boolean) => void;
signalsIndex: string;
to: string;
}
Expand Down Expand Up @@ -94,6 +96,8 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
selectedEventIds,
setEventsDeleted,
setEventsLoading,
showBuildingBlockAlerts,
onShowBuildingBlockAlertsChanged,
signalsIndex,
to,
updateTimeline,
Expand Down Expand Up @@ -302,6 +306,8 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
currentFilter={filterGroup}
selectAll={selectAllCallback}
selectedEventIds={selectedEventIds}
showBuildingBlockAlerts={showBuildingBlockAlerts}
onShowBuildingBlockAlertsChanged={onShowBuildingBlockAlertsChanged}
showClearSelection={showClearSelectionAction}
totalCount={totalCount}
updateAlertsStatus={updateAlertsStatusCallback.bind(null, refetchQuery)}
Expand All @@ -313,6 +319,8 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
hasIndexWrite,
clearSelectionCallback,
filterGroup,
showBuildingBlockAlerts,
onShowBuildingBlockAlertsChanged,
loadingEventIds.length,
selectAllCallback,
selectedEventIds,
Expand Down
Loading

0 comments on commit 8da80fe

Please sign in to comment.