Skip to content
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

[Security team: AWP] Session view: Alert details tab #127500

Merged
merged 27 commits into from
Mar 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
868796a
alerts tab work. list view done
Mar 9, 2022
ab7b6d3
View mode toggle + group view implemented
Mar 9, 2022
d74d24a
tests written
Mar 10, 2022
e0bf515
clean up
Mar 10, 2022
09a6174
Merge branch 'main' into alert-details-tab
mitodrummer Mar 10, 2022
6e51c63
addressed @opauloh comments
Mar 10, 2022
38602b2
Merge branch 'main' into alert-details-tab
mitodrummer Mar 10, 2022
102c217
Merge branch 'main' into alert-details-tab
mitodrummer Mar 14, 2022
bb9c58f
Merge branch 'main' into alert-details-tab
mitodrummer Mar 14, 2022
23e3cb2
fixed weird bug due to importing assests from a test into its component
Mar 14, 2022
1411a54
empty state added for alerts tab
Mar 14, 2022
36ba884
Merge branch 'main' into alert-details-tab
mitodrummer Mar 15, 2022
130cfda
react-query caching keys updated to include sessionEntityId
Mar 16, 2022
a97856d
Merge branch 'alert-details-tab' of github.com:mitodrummer/kibana int…
Mar 16, 2022
1a28810
Merge branch 'main' into alert-details-tab
mitodrummer Mar 16, 2022
79a929e
rule_registry added as a dependency in order to use AlertsClient in a…
Mar 21, 2022
79e0d54
Merge branch 'alert-details-tab' of github.com:mitodrummer/kibana int…
Mar 21, 2022
e18e8ed
Merge branch 'main' into alert-details-tab
mitodrummer Mar 21, 2022
a28fa39
Merge branch 'main' into alert-details-tab
mitodrummer Mar 22, 2022
38288cf
fixed build/test errors due to merge. events route now orders by proc…
Mar 22, 2022
74098ed
Merge branch 'main' into alert-details-tab
mitodrummer Mar 22, 2022
af90b97
plumbing for the alert details tie in done.
Mar 22, 2022
6ad0f9e
removed rule_registry ecs mappings. kqualters PR will add this.
Mar 22, 2022
c056ac9
Merge branch 'main' into alert-details-tab
mitodrummer Mar 22, 2022
f076a62
Merge branch 'main' into alert-details-tab
mitodrummer Mar 23, 2022
90cc8b0
alerts index merge conflict fix
Mar 23, 2022
eb6865d
Merge branch 'alert-details-tab' of github.com:mitodrummer/kibana int…
Mar 23, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion x-pack/plugins/session_view/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
*/

export const PROCESS_EVENTS_ROUTE = '/internal/session_view/process_events_route';
export const ALERTS_ROUTE = '/internal/session_view/alerts_route';
export const ALERT_STATUS_ROUTE = '/internal/session_view/alert_status_route';
export const SESSION_ENTRY_LEADERS_ROUTE = '/internal/session_view/session_entry_leaders_route';
export const PROCESS_EVENTS_INDEX = 'logs-endpoint.events.process-default';
export const ALERTS_INDEX = '.siem-signals-default';
export const ALERTS_INDEX = '.alerts-security.alerts-default'; // TODO: changes to remove this and use AlertsClient instead to get indices.
export const ENTRY_SESSION_ENTITY_ID_PROPERTY = 'process.entry_leader.entity_id';
export const ALERT_UUID_PROPERTY = 'kibana.alert.uuid';
export const KIBANA_DATE_FORMAT = 'MMM DD, YYYY @ hh:mm:ss.SSS';
Expand Down
7 changes: 4 additions & 3 deletions x-pack/plugins/session_view/kibana.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
{
"id": "sessionView",
"version": "8.0.0",
"version": "1.0.0",
"kibanaVersion": "kibana",
"owner": {
"name": "Security Team",
"githubTeam": "security-team"
},
"requiredPlugins": [
"data",
"timelines"
"timelines",
"ruleRegistry"
],
"requiredBundles": [
"kibanaReact",
"kibanaReact",
"esUiShared"
],
"server": true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';
import { AppContextTestRender, createAppRootMockRenderer } from '../../test';
import {
DetailPanelAlertActions,
BUTTON_TEST_ID,
SHOW_DETAILS_TEST_ID,
JUMP_TO_PROCESS_TEST_ID,
} from './index';
import { mockAlerts } from '../../../common/mocks/constants/session_view_process.mock';
import userEvent from '@testing-library/user-event';
import { ProcessImpl } from '../process_tree/hooks';

describe('DetailPanelAlertActions component', () => {
let render: () => ReturnType<AppContextTestRender['render']>;
let renderResult: ReturnType<typeof render>;
let mockedContext: AppContextTestRender;
let mockShowAlertDetails = jest.fn((uuid) => uuid);
let mockOnProcessSelected = jest.fn((process) => process);

beforeEach(() => {
mockedContext = createAppRootMockRenderer();
mockShowAlertDetails = jest.fn((uuid) => uuid);
mockOnProcessSelected = jest.fn((process) => process);
});

describe('When DetailPanelAlertActions is mounted', () => {
it('renders a popover when button is clicked', async () => {
const mockEvent = mockAlerts[0];

renderResult = mockedContext.render(
<DetailPanelAlertActions
event={mockEvent}
onProcessSelected={mockOnProcessSelected}
onShowAlertDetails={mockShowAlertDetails}
/>
);

userEvent.click(renderResult.getByTestId(BUTTON_TEST_ID));
expect(renderResult.queryByTestId(SHOW_DETAILS_TEST_ID)).toBeTruthy();
expect(renderResult.queryByTestId(JUMP_TO_PROCESS_TEST_ID)).toBeTruthy();
expect(mockShowAlertDetails.mock.calls.length).toBe(0);
expect(mockOnProcessSelected.mock.calls.length).toBe(0);
});

it('calls alert flyout callback when View details clicked', async () => {
const mockEvent = mockAlerts[0];

renderResult = mockedContext.render(
<DetailPanelAlertActions
event={mockEvent}
onProcessSelected={mockOnProcessSelected}
onShowAlertDetails={mockShowAlertDetails}
/>
);

userEvent.click(renderResult.getByTestId(BUTTON_TEST_ID));
userEvent.click(renderResult.getByTestId(SHOW_DETAILS_TEST_ID));
expect(mockShowAlertDetails.mock.calls.length).toBe(1);
expect(mockShowAlertDetails.mock.results[0].value).toBe(mockEvent.kibana?.alert.uuid);
expect(mockOnProcessSelected.mock.calls.length).toBe(0);
});

it('calls onProcessSelected when Jump to process clicked', async () => {
const mockEvent = mockAlerts[0];

renderResult = mockedContext.render(
<DetailPanelAlertActions
event={mockEvent}
onProcessSelected={mockOnProcessSelected}
onShowAlertDetails={mockShowAlertDetails}
/>
);

userEvent.click(renderResult.getByTestId(BUTTON_TEST_ID));
userEvent.click(renderResult.getByTestId(JUMP_TO_PROCESS_TEST_ID));
expect(mockOnProcessSelected.mock.calls.length).toBe(1);
expect(mockOnProcessSelected.mock.results[0].value).toBeInstanceOf(ProcessImpl);
expect(mockShowAlertDetails.mock.calls.length).toBe(0);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useState, useCallback } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiPopover, EuiContextMenuPanel, EuiButtonIcon, EuiContextMenuItem } from '@elastic/eui';
import { Process, ProcessEvent } from '../../../common/types/process_tree';
import { ProcessImpl } from '../process_tree/hooks';

export const BUTTON_TEST_ID = 'sessionView:detailPanelAlertActionsBtn';
export const SHOW_DETAILS_TEST_ID = 'sessionView:detailPanelAlertActionShowDetails';
export const JUMP_TO_PROCESS_TEST_ID = 'sessionView:detailPanelAlertActionJumpToProcess';

interface DetailPanelAlertActionsDeps {
event: ProcessEvent;
onShowAlertDetails: (alertId: string) => void;
onProcessSelected: (process: Process) => void;
}

/**
* Detail panel alert context menu actions
*/
export const DetailPanelAlertActions = ({
event,
onShowAlertDetails,
onProcessSelected,
}: DetailPanelAlertActionsDeps) => {
const [isPopoverOpen, setPopover] = useState(false);

const onClosePopover = useCallback(() => {
setPopover(false);
}, []);

const onToggleMenu = useCallback(() => {
setPopover(!isPopoverOpen);
}, [isPopoverOpen]);

const onJumpToAlert = useCallback(() => {
const process = new ProcessImpl(event.process.entity_id);
process.addEvent(event);

onProcessSelected(process);
setPopover(false);
}, [event, onProcessSelected]);

const onShowDetails = useCallback(() => {
if (event.kibana) {
onShowAlertDetails(event.kibana.alert.uuid);
setPopover(false);
}
}, [event, onShowAlertDetails]);

if (!event.kibana) {
return null;
}

const { uuid } = event.kibana.alert;

const menuItems = [
<EuiContextMenuItem key="details" data-test-subj={SHOW_DETAILS_TEST_ID} onClick={onShowDetails}>
<FormattedMessage
id="xpack.sessionView.detailPanelAlertListItem.showDetailsAction"
defaultMessage="View alert details"
/>
</EuiContextMenuItem>,
<EuiContextMenuItem
key="jumpTo"
data-test-subj={JUMP_TO_PROCESS_TEST_ID}
onClick={onJumpToAlert}
>
<FormattedMessage
id="xpack.sessionView.detailPanelAlertListItem.jumpToAlert"
defaultMessage="Jump to alerted process"
/>
</EuiContextMenuItem>,
];

return (
<EuiPopover
id={uuid}
button={
<EuiButtonIcon
display="empty"
size="s"
iconType="boxesHorizontal"
aria-label={i18n.translate('xpack.sessionView.detailPanelAlertListItem.moreButton', {
defaultMessage: 'More',
})}
data-test-subj={BUTTON_TEST_ID}
onClick={onToggleMenu}
/>
}
isOpen={isPopoverOpen}
closePopover={onClosePopover}
panelPaddingSize="none"
anchorPosition="leftCenter"
>
<EuiContextMenuPanel size="s" items={menuItems} />
</EuiPopover>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { useMemo } from 'react';
import { useEuiTheme, transparentize } from '@elastic/eui';
import { CSSObject, css } from '@emotion/react';

interface StylesDeps {
minimal?: boolean;
isInvestigated?: boolean;
}

export const useStyles = ({ minimal = false, isInvestigated = false }: StylesDeps) => {
const { euiTheme } = useEuiTheme();

const cached = useMemo(() => {
const { colors, font, size, border } = euiTheme;

const dangerBorder = transparentize(colors.danger, 0.2);
const dangerBackground = transparentize(colors.danger, 0.08);
const borderThickness = border.width.thin;
const mediumPadding = size.m;

let alertTitleColor = colors.text;
let borderColor = colors.lightShade;

if (isInvestigated) {
alertTitleColor = colors.primaryText;
borderColor = dangerBorder;
}

const alertItem = css`
border: ${borderThickness} solid ${borderColor};
padding: ${mediumPadding};
border-radius: ${border.radius.medium};

margin: 0 ${mediumPadding} ${mediumPadding} ${mediumPadding};
background-color: ${colors.emptyShade};

& .euiAccordion__buttonContent {
width: 100%;
}

& .euiAccordion__button {
min-width: 0;
width: calc(100% - ${size.l});
}

& .euiAccordion__childWrapper {
overflow: visible;
}
`;

const alertTitle: CSSObject = {
display: minimal ? 'none' : 'initial',
color: alertTitleColor,
fontWeight: font.weight.semiBold,
textOverflow: 'ellipsis',
overflow: 'hidden',
whiteSpace: 'nowrap',
};

const alertIcon: CSSObject = {
marginRight: size.s,
};

const alertAccordionButton: CSSObject = {
width: `calc(100% - ${size.l})`,
minWidth: 0,
};

const processPanel: CSSObject = {
border: `${borderThickness} solid ${colors.lightShade}`,
fontFamily: font.familyCode,
marginTop: mediumPadding,
padding: `${size.xs} ${size.s}`,
};

const investigatedLabel: CSSObject = {
position: 'relative',
zIndex: 1,
bottom: `-${mediumPadding}`,
left: `-${mediumPadding}`,
width: `calc(100% + ${mediumPadding} * 2)`,
borderTop: `${borderThickness} solid ${dangerBorder}`,
borderBottomLeftRadius: border.radius.medium,
borderBottomRightRadius: border.radius.medium,
backgroundColor: dangerBackground,
textAlign: 'center',
};

return {
alertItem,
alertTitle,
alertIcon,
alertAccordionButton,
processPanel,
investigatedLabel,
};
}, [euiTheme, isInvestigated, minimal]);

return cached;
};
Loading