Skip to content

Commit

Permalink
[CTI] adds Risky Host Overview Card (#109553) (#113155)
Browse files Browse the repository at this point in the history
Co-authored-by: Ece Özalp <[email protected]>
  • Loading branch information
kibanamachine and ecezalp authored Sep 27, 2021
1 parent 100f04e commit 59463dc
Show file tree
Hide file tree
Showing 59 changed files with 1,823 additions and 512 deletions.
2 changes: 2 additions & 0 deletions x-pack/plugins/security_solution/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,3 +312,5 @@ export const showAllOthersBucket: string[] = [
export const ELASTIC_NAME = 'estc';

export const TRANSFORM_STATS_URL = `/api/transform/transforms/${metadataTransformPattern}-*/_stats`;

export const RISKY_HOSTS_INDEX = 'ml_host_risk_score_latest';
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const allowedExperimentalValues = Object.freeze({
excludePoliciesInFilterEnabled: false,
uebaEnabled: false,
disableIsolationUIPendingStatuses: false,
riskyHostsEnabled: false,
});

type ExperimentalConfigKeys = Array<keyof ExperimentalFeatures>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export * from './common';
export * from './details';
export * from './first_last_seen';
export * from './kpi';
export * from './risky_hosts';
export * from './overview';
export * from './uncommon_processes';

Expand All @@ -22,5 +23,6 @@ export enum HostsQueries {
hosts = 'hosts',
hostsEntities = 'hostsEntities',
overview = 'overviewHost',
riskyHosts = 'riskyHosts',
uncommonProcesses = 'uncommonProcesses',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* 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 { Inspect, Maybe, RequestBasicOptions } from '../../..';
import { IEsSearchResponse } from '../../../../../../../../src/plugins/data/common';

export type HostsRiskyHostsRequestOptions = RequestBasicOptions;

export interface HostsRiskyHostsStrategyResponse extends IEsSearchResponse {
inspect?: Maybe<Inspect>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import {
HostsKpiUniqueIpsStrategyResponse,
HostsKpiUniqueIpsRequestOptions,
HostFirstLastSeenRequestOptions,
HostsRiskyHostsStrategyResponse,
HostsRiskyHostsRequestOptions,
} from './hosts';
import {
NetworkQueries,
Expand Down Expand Up @@ -124,6 +126,8 @@ export type StrategyResponseType<T extends FactoryQueryTypes> = T extends HostsQ
? HostDetailsStrategyResponse
: T extends UebaQueries.riskScore
? RiskScoreStrategyResponse
: T extends HostsQueries.riskyHosts
? HostsRiskyHostsStrategyResponse
: T extends UebaQueries.hostRules
? HostRulesStrategyResponse
: T extends UebaQueries.userRules
Expand Down Expand Up @@ -178,6 +182,8 @@ export type StrategyResponseType<T extends FactoryQueryTypes> = T extends HostsQ

export type StrategyRequestType<T extends FactoryQueryTypes> = T extends HostsQueries.hosts
? HostsRequestOptions
: T extends HostsQueries.riskyHosts
? HostsRiskyHostsRequestOptions
: T extends HostsQueries.details
? HostDetailsRequestOptions
: T extends HostsQueries.overview
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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 {
OVERVIEW_RISKY_HOSTS_ENABLE_MODULE_BUTTON,
OVERVIEW_RISKY_HOSTS_LINKS,
OVERVIEW_RISKY_HOSTS_LINKS_ERROR_INNER_PANEL,
OVERVIEW_RISKY_HOSTS_LINKS_WARNING_INNER_PANEL,
OVERVIEW_RISKY_HOSTS_TOTAL_EVENT_COUNT,
OVERVIEW_RISKY_HOSTS_VIEW_DASHBOARD_BUTTON,
} from '../../screens/overview';

import { loginAndWaitForPage } from '../../tasks/login';
import { OVERVIEW_URL } from '../../urls/navigation';
import { cleanKibana } from '../../tasks/common';
import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver';

describe('Risky Hosts Link Panel', () => {
before(() => {
cleanKibana();
});

it('renders disabled panel view as expected', () => {
loginAndWaitForPage(OVERVIEW_URL);
cy.get(`${OVERVIEW_RISKY_HOSTS_LINKS} ${OVERVIEW_RISKY_HOSTS_LINKS_ERROR_INNER_PANEL}`).should(
'exist'
);
cy.get(`${OVERVIEW_RISKY_HOSTS_VIEW_DASHBOARD_BUTTON}`).should('be.disabled');
cy.get(`${OVERVIEW_RISKY_HOSTS_TOTAL_EVENT_COUNT}`).should('have.text', 'Showing: 0 hosts');
cy.get(`${OVERVIEW_RISKY_HOSTS_ENABLE_MODULE_BUTTON}`).should('exist');
cy.get(`${OVERVIEW_RISKY_HOSTS_ENABLE_MODULE_BUTTON}`)
.should('have.attr', 'href')
.and('match', /host-risk-score.md/);
});

describe('enabled module', () => {
before(() => {
esArchiverLoad('risky_hosts');
});

after(() => {
esArchiverUnload('risky_hosts');
});

it('renders disabled dashboard module as expected when there are no hosts in the selected time period', () => {
loginAndWaitForPage(
`${OVERVIEW_URL}?sourcerer=(timerange:(from:%272021-07-08T04:00:00.000Z%27,kind:absolute,to:%272021-07-09T03:59:59.999Z%27))`
);
cy.get(
`${OVERVIEW_RISKY_HOSTS_LINKS} ${OVERVIEW_RISKY_HOSTS_LINKS_WARNING_INNER_PANEL}`
).should('exist');
cy.get(`${OVERVIEW_RISKY_HOSTS_VIEW_DASHBOARD_BUTTON}`).should('be.disabled');
cy.get(`${OVERVIEW_RISKY_HOSTS_TOTAL_EVENT_COUNT}`).should('have.text', 'Showing: 0 hosts');
});

it('renders dashboard module as expected when there are hosts in the selected time period', () => {
loginAndWaitForPage(OVERVIEW_URL);
cy.get(
`${OVERVIEW_RISKY_HOSTS_LINKS} ${OVERVIEW_RISKY_HOSTS_LINKS_WARNING_INNER_PANEL}`
).should('not.exist');
cy.get(`${OVERVIEW_RISKY_HOSTS_VIEW_DASHBOARD_BUTTON}`).should('be.disabled');
cy.get(`${OVERVIEW_RISKY_HOSTS_TOTAL_EVENT_COUNT}`).should('have.text', 'Showing: 1 host');
});
});
});
11 changes: 11 additions & 0 deletions x-pack/plugins/security_solution/cypress/screens/overview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,14 @@ export const OVERVIEW_CTI_LINKS_INFO_INNER_PANEL = '[data-test-subj="cti-inner-p
export const OVERVIEW_CTI_VIEW_DASHBOARD_BUTTON = '[data-test-subj="cti-view-dashboard-button"]';
export const OVERVIEW_CTI_TOTAL_EVENT_COUNT = `${OVERVIEW_CTI_LINKS} [data-test-subj="header-panel-subtitle"]`;
export const OVERVIEW_CTI_ENABLE_MODULE_BUTTON = '[data-test-subj="cti-enable-module-button"]';

export const OVERVIEW_RISKY_HOSTS_LINKS = '[data-test-subj="risky-hosts-dashboard-links"]';
export const OVERVIEW_RISKY_HOSTS_LINKS_ERROR_INNER_PANEL =
'[data-test-subj="risky-hosts-inner-panel-danger"]';
export const OVERVIEW_RISKY_HOSTS_LINKS_WARNING_INNER_PANEL =
'[data-test-subj="risky-hosts-inner-panel-warning"]';
export const OVERVIEW_RISKY_HOSTS_VIEW_DASHBOARD_BUTTON =
'[data-test-subj="risky-hosts-view-dashboard-button"]';
export const OVERVIEW_RISKY_HOSTS_TOTAL_EVENT_COUNT = `${OVERVIEW_RISKY_HOSTS_LINKS} [data-test-subj="header-panel-subtitle"]`;
export const OVERVIEW_RISKY_HOSTS_ENABLE_MODULE_BUTTON =
'[data-test-subj="risky-hosts-enable-module-button"]';
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { defaultControlColumn } from '../../../timelines/components/timeline/bod
import { EventsViewer } from './events_viewer';
import * as i18n from './translations';
import { GraphOverlay } from '../../../timelines/components/graph_overlay';

const EMPTY_CONTROL_COLUMNS: ControlColumnProps[] = [];
const leadingControlColumns: ControlColumnProps[] = [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* 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.
*/

export const isIndexNotFoundError = (error: unknown): boolean =>
(
error as {
attributes?: { caused_by?: { type?: string } };
}
).attributes?.caused_by?.type === 'index_not_found_exception';
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* 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 { render, screen } from '@testing-library/react';
import { TestProviders } from '../../../common/mock';
import { DisabledLinkPanel } from './disabled_link_panel';
import { ThreatIntelPanelView as TestView } from '../overview_cti_links/threat_intel_panel_view';

jest.mock('../../../common/lib/kibana');

describe('DisabledLinkPanel', () => {
const defaultProps = {
bodyCopy: 'body',
buttonCopy: 'button',
dataTestSubjPrefix: '_test_prefix_',
docLink: '/doclink',
LinkPanelViewComponent: TestView,
listItems: [],
titleCopy: 'title',
};

it('renders expected children', () => {
render(
<TestProviders>
<DisabledLinkPanel {...defaultProps} />
</TestProviders>
);

expect(screen.getByTestId('_test_prefix_-inner-panel-danger')).toBeInTheDocument();
expect(screen.getByTestId('_test_prefix_-enable-module-button')).toHaveTextContent(
defaultProps.buttonCopy
);
expect(screen.getByRole('link')).toHaveAttribute('href', defaultProps.docLink);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* 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, { memo } from 'react';
import { EuiButton } from '@elastic/eui';

import { InnerLinkPanel } from './inner_link_panel';
import { LinkPanelListItem, LinkPanelViewProps } from './types';

interface DisabledLinkPanelProps {
bodyCopy: string;
buttonCopy: string;
dataTestSubjPrefix: string;
docLink: string;
LinkPanelViewComponent: React.ComponentType<LinkPanelViewProps>;
listItems: LinkPanelListItem[];
titleCopy: string;
}

const DisabledLinkPanelComponent: React.FC<DisabledLinkPanelProps> = ({
bodyCopy,
buttonCopy,
dataTestSubjPrefix,
docLink,
LinkPanelViewComponent,
listItems,
titleCopy,
}) => (
<LinkPanelViewComponent
listItems={listItems}
splitPanel={
<InnerLinkPanel
body={bodyCopy}
button={
<EuiButton
href={docLink}
color="warning"
target="_blank"
data-test-subj={`${dataTestSubjPrefix}-enable-module-button`}
>
{buttonCopy}
</EuiButton>
}
color="warning"
dataTestSubj={`${dataTestSubjPrefix}-inner-panel-danger`}
title={titleCopy}
/>
}
/>
);

export const DisabledLinkPanel = memo(DisabledLinkPanelComponent);
DisabledLinkPanel.displayName = 'DisabledLinkPanel';
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* 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 { LinkPanelListItem } from '.';

export const isLinkPanelListItem = (
item: LinkPanelListItem | Partial<LinkPanelListItem>
): item is LinkPanelListItem =>
typeof item.title === 'string' && typeof item.path === 'string' && typeof item.count === 'number';

export interface EventCounts {
[key: string]: number;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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.
*/

export { InnerLinkPanel } from './inner_link_panel';
export { isLinkPanelListItem } from './helpers';
export { LinkPanel } from './link_panel';
export { LinkPanelListItem } from './types';
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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 { render, screen } from '@testing-library/react';
import { EuiButton } from '@elastic/eui';
import { TestProviders } from '../../../common/mock';
import { InnerLinkPanel } from './inner_link_panel';

describe('InnerLinkPanel', () => {
const defaultProps = {
body: 'test_body',
button: <EuiButton />,
dataTestSubj: 'custom_test_subj',
title: 'test_title',
};

it('renders expected children', () => {
render(
<TestProviders>
<InnerLinkPanel color="warning" {...defaultProps} />
</TestProviders>
);

expect(screen.getByTestId('custom_test_subj')).toBeInTheDocument();
expect(screen.getByRole('button')).toBeInTheDocument();
expect(screen.getByTestId('inner-link-panel-title')).toHaveTextContent(defaultProps.title);
});
});
Loading

0 comments on commit 59463dc

Please sign in to comment.