Skip to content

Commit

Permalink
Hosts Risk Step 2 - Hosts Page - Risk Column #119734 (#120487)
Browse files Browse the repository at this point in the history
* Add Host risk classification column to All hosts table

* Add cypress test to risk column on all hosts table

* Fix unit test

* Add unit test

* Add tooltip to host risk column

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
machadoum and kibanamachine authored Dec 13, 2021
1 parent 32d0e87 commit da96f61
Show file tree
Hide file tree
Showing 26 changed files with 606 additions and 171 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export interface HostItem {
endpoint?: Maybe<EndpointFields>;
host?: Maybe<HostEcs>;
lastSeen?: Maybe<string[]>;
risk?: string;
}

export interface HostValue {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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 { getHostRiskIndex } from '.';

describe('hosts risk search_strategy getHostRiskIndex', () => {
it('should properly return index if space is specified', () => {
expect(getHostRiskIndex('testName')).toEqual('ml_host_risk_score_latest_testName');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import type {
IEsSearchRequest,
IEsSearchResponse,
} from '../../../../../../../../src/plugins/data/common';
import { RISKY_HOSTS_INDEX_PREFIX } from '../../../../constants';
import { Inspect, Maybe, TimerangeInput } from '../../../common';

export interface HostsRiskScoreRequestOptions extends IEsSearchRequest {
defaultIndex: string[];
factoryQueryType?: FactoryQueryTypes;
hostName?: string;
hostNames?: string[];
timerange?: TimerangeInput;
}

Expand All @@ -38,3 +39,7 @@ export interface RuleRisk {
rule_name: string;
rule_risk: string;
}

export const getHostRiskIndex = (spaceId: string): string => {
return `${RISKY_HOSTS_INDEX_PREFIX}${spaceId}`;
};
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 { loginAndWaitForPage } from '../../tasks/login';

import { HOSTS_URL } from '../../urls/navigation';
import { cleanKibana } from '../../tasks/common';
import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver';
import { TABLE_CELL } from '../../screens/alerts_details';
import { kqlSearch } from '../../tasks/security_header';

describe('All hosts table', () => {
before(() => {
cleanKibana();
esArchiverLoad('risky_hosts');
});

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

it('it renders risk column', () => {
loginAndWaitForPage(HOSTS_URL);
kqlSearch('host.name: "siem-kibana" {enter}');

cy.get('[data-test-subj="tableHeaderCell_node.risk_4"]').should('exist');
cy.get(`${TABLE_CELL} .euiTableCellContent`).eq(4).should('have.text', 'Low');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,8 @@
import { loginAndWaitForPage } from '../../tasks/login';

import { HOSTS_URL } from '../../urls/navigation';
import { cleanKibana } from '../../tasks/common';

describe('RiskyHosts KPI', () => {
before(() => {
cleanKibana();
});

it('it renders', () => {
loginAndWaitForPage(HOSTS_URL);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import { useAppToasts } from '../../hooks/use_app_toasts';
import { useKibana } from '../../lib/kibana';
import { inputsActions } from '../../store/actions';
import { isIndexNotFoundError } from '../../utils/exceptions';
import { HostsRiskScore } from '../../../../common/search_strategy';
import { getHostRiskIndex, HostsRiskScore } from '../../../../common/search_strategy';

import { useHostsRiskScoreComplete } from './use_hosts_risk_score_complete';
import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features';
import { getHostRiskIndex } from '../../../helpers';

export const QUERY_ID = 'host_risk_score';
const noop = () => {};
Expand Down Expand Up @@ -104,7 +104,7 @@ export const useHostsRiskScore = ({
timerange: timerange
? { to: timerange.to, from: timerange.from, interval: '' }
: undefined,
hostName,
hostNames: hostName ? [hostName] : undefined,
defaultIndex: [getHostRiskIndex(space.id)],
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ export const getHostsRiskScore = ({
data,
defaultIndex,
timerange,
hostName,
hostNames,
signal,
}: GetHostsRiskScoreProps): Observable<HostsRiskScoreStrategyResponse> =>
data.search.search<HostsRiskScoreRequestOptions, HostsRiskScoreStrategyResponse>(
{
defaultIndex,
factoryQueryType: HostsQueries.hostsRiskScore,
timerange,
hostName,
hostNames,
},
{
strategy: 'securitySolutionSearchStrategy',
Expand Down
7 changes: 0 additions & 7 deletions x-pack/plugins/security_solution/public/helpers.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { Capabilities } from '../../../../src/core/public';
import { CASES_FEATURE_ID, SERVER_APP_ID } from '../common/constants';
import {
parseRoute,
getHostRiskIndex,
isSubPluginAvailable,
getSubPluginRoutesByCapabilities,
RedirectRoute,
Expand Down Expand Up @@ -65,12 +64,6 @@ describe('public helpers parseRoute', () => {
});
});

describe('public helpers export getHostRiskIndex', () => {
it('should properly return index if space is specified', () => {
expect(getHostRiskIndex('testName')).toEqual('ml_host_risk_score_latest_testName');
});
});

describe('#getSubPluginRoutesByCapabilities', () => {
const mockRender = () => null;
const mockSubPlugins = {
Expand Down
5 changes: 0 additions & 5 deletions x-pack/plugins/security_solution/public/helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
EXCEPTIONS_PATH,
RULES_PATH,
UEBA_PATH,
RISKY_HOSTS_INDEX_PREFIX,
SERVER_APP_ID,
CASES_FEATURE_ID,
OVERVIEW_PATH,
Expand Down Expand Up @@ -164,10 +163,6 @@ export const isDetectionsPath = (pathname: string): boolean => {
});
};

export const getHostRiskIndex = (spaceId: string): string => {
return `${RISKY_HOSTS_INDEX_PREFIX}${spaceId}`;
};

export const getSubPluginRoutesByCapabilities = (
subPlugins: StartedSubPlugins,
capabilities: Capabilities
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* 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 { render } from '@testing-library/react';
import React from 'react';
import { HostRiskSeverity } from '../../../../common/search_strategy';
import { TestProviders } from '../../../common/mock';
import { HostRiskScore } from './host_risk_score';

import { EuiHealth, EuiHealthProps } from '@elastic/eui';

import { euiThemeVars } from '@kbn/ui-shared-deps-src/theme';

jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
...jest.requireActual('@elastic/eui'),
EuiHealth: jest.fn((props: EuiHealthProps) => <original.EuiHealth {...props} />),
};
});

describe('HostRiskScore', () => {
const context = {};
it('renders critical severity risk score', () => {
const { container } = render(
<TestProviders>
<HostRiskScore severity={HostRiskSeverity.critical} />
</TestProviders>
);

expect(container).toHaveTextContent(HostRiskSeverity.critical);

expect(EuiHealth as jest.Mock).toHaveBeenLastCalledWith(
expect.objectContaining({ color: euiThemeVars.euiColorDanger }),
context
);
});

it('renders hight severity risk score', () => {
const { container } = render(
<TestProviders>
<HostRiskScore severity={HostRiskSeverity.high} />
</TestProviders>
);

expect(container).toHaveTextContent(HostRiskSeverity.high);

expect(EuiHealth as jest.Mock).toHaveBeenLastCalledWith(
expect.objectContaining({ color: euiThemeVars.euiColorVis9_behindText }),
context
);
});

it('renders moderate severity risk score', () => {
const { container } = render(
<TestProviders>
<HostRiskScore severity={HostRiskSeverity.moderate} />
</TestProviders>
);

expect(container).toHaveTextContent(HostRiskSeverity.moderate);

expect(EuiHealth as jest.Mock).toHaveBeenLastCalledWith(
expect.objectContaining({ color: euiThemeVars.euiColorWarning }),
context
);
});

it('renders low severity risk score', () => {
const { container } = render(
<TestProviders>
<HostRiskScore severity={HostRiskSeverity.low} />
</TestProviders>
);

expect(container).toHaveTextContent(HostRiskSeverity.low);

expect(EuiHealth as jest.Mock).toHaveBeenLastCalledWith(
expect.objectContaining({ color: euiThemeVars.euiColorVis0 }),
context
);
});

it('renders unknown severity risk score', () => {
const { container } = render(
<TestProviders>
<HostRiskScore severity={HostRiskSeverity.unknown} />
</TestProviders>
);

expect(container).toHaveTextContent(HostRiskSeverity.unknown);

expect(EuiHealth as jest.Mock).toHaveBeenLastCalledWith(
expect.objectContaining({ color: euiThemeVars.euiColorMediumShade }),
context
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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 { EuiHealth, transparentize } from '@elastic/eui';

import styled, { css } from 'styled-components';
import { euiLightVars } from '@kbn/ui-shared-deps-src/theme';
import { HostRiskSeverity } from '../../../../common/search_strategy';

const HOST_RISK_SEVERITY_COLOUR = {
Unknown: euiLightVars.euiColorMediumShade,
Low: euiLightVars.euiColorVis0,
Moderate: euiLightVars.euiColorWarning,
High: euiLightVars.euiColorVis9_behindText,
Critical: euiLightVars.euiColorDanger,
};

const HostRiskBadge = styled.div<{ $severity: HostRiskSeverity }>`
${({ theme, $severity }) => css`
width: fit-content;
padding-right: ${theme.eui.paddingSizes.s};
padding-left: ${theme.eui.paddingSizes.xs};
${($severity === 'Critical' || $severity === 'High') &&
css`
background-color: ${transparentize(theme.eui.euiColorDanger, 0.2)};
border-radius: 999px; // pill shaped
`};
`}
`;

export const HostRiskScore: React.FC<{ severity: HostRiskSeverity }> = ({ severity }) => (
<HostRiskBadge color={euiLightVars.euiColorDanger} $severity={severity}>
<EuiHealth className="eui-alignMiddle" color={HOST_RISK_SEVERITY_COLOUR[severity]}>
{severity}
</EuiHealth>
</HostRiskBadge>
);
Loading

0 comments on commit da96f61

Please sign in to comment.