137539 add missing network details revamp (#137541) (#137909)

* update event tab to show both alerts and events with toggle. (#136540)

* add test for SignasByCategory

* modify external_alerts_filter  to be more efficient

* Update usage across explore views to only use EventsQueryTabBody

* remove unused files and code related to external alerts  and move old alerts files to events_tab folder

* test fixes, and more removal of old usage

* update failing snapshots

* last bit of cleanup

* Fix type error

* fix type and translations issue

Co-authored-by: Kristof-Pierre Cummings <kristofpierre.cummings@elastic.co>

* update network details to match hosts, and users details pages

* add events table to network pages

* Fix minor bugs with network routeing and allow old route to reach new view

* Fix failing tests

* fix types and transltions

* minor fixes before code review

* [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix'

* update failing snapshot

* re-add /ip/ and have :/flowTarget appear before :/tabName

* [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix'

* remove unneeded import and update type

* fix navigation issue

* use constant for administration page, and add fallback route

* Update links and redirect behaviour

* fix dependency array

Co-authored-by: Kristof-Pierre Cummings <kristofpierre.cummings@elastic.co>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
(cherry picked from commit 241406a8df)

Co-authored-by: Kristof C <kpac.ja@gmail.com>
This commit is contained in:
Kibana Machine 2022-08-09 01:13:51 -04:00 committed by GitHub
parent ca4a5670bf
commit f2f359601f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 505 additions and 229 deletions

View file

@ -107,7 +107,7 @@ export enum SecurityPageName {
network = 'network',
networkAnomalies = 'network-anomalies',
networkDns = 'network-dns',
networkExternalAlerts = 'network-external_alerts',
networkEvents = 'network-events',
networkHttp = 'network-http',
networkTls = 'network-tls',
noPage = '',
@ -126,7 +126,6 @@ export enum SecurityPageName {
usersAnomalies = 'users-anomalies',
usersAuthentications = 'users-authentications',
usersEvents = 'users-events',
usersExternalAlerts = 'users-external_alerts',
usersRisk = 'users-risk',
}

View file

@ -97,7 +97,7 @@ describe('ml conditional links', () => {
visitWithoutDateRange(mlNetworkSingleIpNullKqlQuery);
cy.url().should(
'include',
'app/security/network/ip/127.0.0.1/source?sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))'
'app/security/network/ip/127.0.0.1/source/flows?sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))'
);
});
@ -105,7 +105,7 @@ describe('ml conditional links', () => {
visitWithoutDateRange(mlNetworkSingleIpKqlQuery);
cy.url().should(
'include',
'/app/security/network/ip/127.0.0.1/source?sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))&query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))'
'/app/security/network/ip/127.0.0.1/source/flows?sourcerer=(default:(id:security-solution-default,selectedPatterns:!(%27auditbeat-*%27)))&query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))'
);
});

View file

@ -7,6 +7,7 @@
import type { FlowTargetSourceDest } from '../../../../common/search_strategy/security_solution/network';
import { FlowTarget } from '../../../../common/search_strategy/security_solution/network';
import { NetworkDetailsRouteType } from '../../../network/pages/details/types';
import { appendSearch } from './helpers';
@ -15,5 +16,6 @@ export const getNetworkUrl = (search?: string) => `${appendSearch(search)}`;
export const getNetworkDetailsUrl = (
detailName: string,
flowTarget?: FlowTarget | FlowTargetSourceDest,
search?: string
) => `/ip/${detailName}/${flowTarget || FlowTarget.source}${appendSearch(search)}`;
search?: string,
tabName = NetworkDetailsRouteType.flows
) => `/ip/${detailName}/${flowTarget || FlowTarget.source}/${tabName}${appendSearch(search)}`;

View file

@ -75,17 +75,17 @@ describe('Custom Links', () => {
test('can handle array of ips', () => {
const wrapper = mount(<NetworkDetailsLink ip={[ipv4, ipv4a]} />);
expect(wrapper.find('EuiLink').first().prop('href')).toEqual(
`/ip/${encodeURIComponent(ipv4)}/source`
`/ip/${encodeURIComponent(ipv4)}/source/flows`
);
expect(wrapper.text()).toEqual(`${ipv4}${ipv4a}`);
expect(wrapper.find('EuiLink').last().prop('href')).toEqual(
`/ip/${encodeURIComponent(ipv4a)}/source`
`/ip/${encodeURIComponent(ipv4a)}/source/flows`
);
});
test('should render valid link to IP Details with ipv4 as the display text', () => {
const wrapper = mount(<NetworkDetailsLink ip={ipv4} />);
expect(wrapper.find('EuiLink').prop('href')).toEqual(
`/ip/${encodeURIComponent(ipv4)}/source`
`/ip/${encodeURIComponent(ipv4)}/source/flows`
);
expect(wrapper.text()).toEqual(ipv4);
});
@ -93,7 +93,7 @@ describe('Custom Links', () => {
test('should render valid link to IP Details with child text as the display text', () => {
const wrapper = mount(<NetworkDetailsLink ip={ipv4}>{hostName}</NetworkDetailsLink>);
expect(wrapper.find('EuiLink').prop('href')).toEqual(
`/ip/${encodeURIComponent(ipv4)}/source`
`/ip/${encodeURIComponent(ipv4)}/source/flows`
);
expect(wrapper.text()).toEqual(hostName);
});
@ -101,7 +101,7 @@ describe('Custom Links', () => {
test('should render valid link to IP Details with ipv6 as the display text', () => {
const wrapper = mount(<NetworkDetailsLink ip={ipv6} />);
expect(wrapper.find('EuiLink').prop('href')).toEqual(
`/ip/${encodeURIComponent(ipv6Encoded)}/source`
`/ip/${encodeURIComponent(ipv6Encoded)}/source/flows`
);
expect(wrapper.text()).toEqual(ipv6);
});

View file

@ -213,32 +213,24 @@ const NetworkDetailsLinkComponent: React.FC<{
onClick?: (e: SyntheticEvent) => void | undefined;
title?: string;
}> = ({ Component, children, ip, flowTarget = FlowTarget.source, isButton, onClick, title }) => {
const { formatUrl, search } = useFormatUrl(SecurityPageName.network);
const { navigateToApp } = useKibana().services.application;
const goToNetworkDetails = useCallback(
(ev, cIp: string) => {
ev.preventDefault();
navigateToApp(APP_UI_ID, {
deepLinkId: SecurityPageName.network,
path: getNetworkDetailsUrl(encodeURIComponent(encodeIpv6(cIp)), flowTarget, search),
});
},
[flowTarget, navigateToApp, search]
);
const getHref = useCallback(
(cIp: string) => formatUrl(getNetworkDetailsUrl(encodeURIComponent(encodeIpv6(cIp)))),
[formatUrl]
);
const getSecuritySolutionLinkProps = useGetSecuritySolutionLinkProps();
const getLink = useCallback(
(cIp: string, i: number) =>
isButton ? (
(cIp: string, i: number) => {
const { onClick: onClickNavigation, href } = getSecuritySolutionLinkProps({
deepLinkId: SecurityPageName.network,
path: getNetworkDetailsUrl(encodeURIComponent(encodeIpv6(cIp)), flowTarget),
});
const onLinkClick = onClick ?? ((e: SyntheticEvent) => onClickNavigation(e as MouseEvent));
return isButton ? (
<GenericLinkButton
Component={Component}
key={`${cIp}-${i}`}
dataTestSubj="data-grid-network-details"
onClick={onClick ?? ((e: SyntheticEvent) => goToNetworkDetails(e, cIp))}
href={getHref(cIp)}
onClick={onLinkClick}
href={href}
title={title ?? cIp}
>
{children}
@ -246,14 +238,15 @@ const NetworkDetailsLinkComponent: React.FC<{
) : (
<LinkAnchor
key={`${cIp}-${i}`}
onClick={onClick ?? ((e: SyntheticEvent) => goToNetworkDetails(e, cIp))}
href={getHref(cIp)}
onClick={onLinkClick}
href={href}
data-test-subj="network-details"
>
{children ? children : cIp}
</LinkAnchor>
),
[Component, children, getHref, goToNetworkDetails, isButton, onClick, title]
);
},
[children, Component, flowTarget, getSecuritySolutionLinkProps, onClick, isButton, title]
);
return isArray(ip) ? <>{ip.map(getLink)}</> : getLink(ip, 0);
};

View file

@ -262,7 +262,7 @@ describe('Matrix Histogram Component', () => {
{
detailName: undefined,
pageName: 'network',
tabName: 'external-alerts',
tabName: 'events',
},
]);

View file

@ -257,7 +257,7 @@ describe('Navigation Breadcrumbs', () => {
networkBreadcrumb,
{
text: ipv4,
href: `securitySolutionUI/network/ip/${ipv4}/source`,
href: `securitySolutionUI/network/ip/${ipv4}/source/flows`,
},
{ text: 'Flows', href: '' },
]);
@ -274,7 +274,7 @@ describe('Navigation Breadcrumbs', () => {
networkBreadcrumb,
{
text: ipv6,
href: `securitySolutionUI/network/ip/${ipv6Encoded}/source`,
href: `securitySolutionUI/network/ip/${ipv6Encoded}/source/flows`,
},
{ text: 'Flows', href: '' },
]);
@ -574,7 +574,7 @@ describe('Navigation Breadcrumbs', () => {
networkBreadcrumb,
{
text: ipv4,
href: `securitySolutionUI/network/ip/${ipv4}/source`,
href: `securitySolutionUI/network/ip/${ipv4}/source/flows`,
},
{ text: 'Flows', href: '' },
]);
@ -592,7 +592,7 @@ describe('Navigation Breadcrumbs', () => {
networkBreadcrumb,
{
text: ipv6,
href: `securitySolutionUI/network/ip/${ipv6Encoded}/source`,
href: `securitySolutionUI/network/ip/${ipv6Encoded}/source/flows`,
},
{ text: 'Flows', href: '' },
]);

View file

@ -9,6 +9,7 @@ import React, { useEffect, useState, useCallback } from 'react';
import { i18n } from '@kbn/i18n';
import type { KibanaPageTemplateProps } from '@kbn/shared-ux-components';
import { SecurityPageName } from '../../../../../common/constants';
import type { PrimaryNavigationProps } from './types';
import { usePrimaryNavigationItems } from './use_navigation_items';
import { useIsGroupedNavigationEnabled } from '../helpers';
@ -25,10 +26,14 @@ export const usePrimaryNavigation = ({
tabName,
}: PrimaryNavigationProps): KibanaPageTemplateProps['solutionNav'] => {
const isGroupedNavigationEnabled = useIsGroupedNavigationEnabled();
const mapLocationToTab = useCallback(
(): string => ((tabName && navTabs[tabName]) || navTabs[pageName])?.id ?? '',
[pageName, tabName, navTabs]
);
const mapLocationToTab = useCallback((): string => {
if (pageName === SecurityPageName.administration) {
// revist with ticket #137625. consider using tab Ids instead of tab Name for matching
return ((tabName && navTabs[tabName]) || navTabs[pageName])?.id ?? '';
} else {
return (navTabs[pageName] || (tabName && navTabs[tabName]))?.id ?? '';
}
}, [pageName, tabName, navTabs]);
const [selectedTabId, setSelectedTabId] = useState(mapLocationToTab());

View file

@ -45,7 +45,7 @@ export const useLensAttributes = ({
return hostNameExistsFilter;
}
if (pageName === SecurityPageName.network && tabName === NetworkRouteType.alerts) {
if (pageName === SecurityPageName.network && tabName === NetworkRouteType.events) {
return filterNetworkExternalAlertData;
}

View file

@ -62,6 +62,41 @@ export const hostNameExistsFilter: Filter[] = [
},
];
export const getNetworkDetailsPageFilter = (ipAddress?: string): Filter[] =>
ipAddress
? [
{
meta: {
alias: null,
negate: false,
disabled: false,
type: 'phrase',
key: 'source.ip',
params: {
query: ipAddress,
},
},
query: {
bool: {
should: [
{
match_phrase: {
'source.ip': ipAddress,
},
},
{
match_phrase: {
'destination.ip': ipAddress,
},
},
],
minimum_should_match: 1,
},
},
},
]
: [];
export const filterNetworkExternalAlertData: Filter[] = [
{
query: {

View file

@ -0,0 +1,43 @@
/*
* 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 { NETWORK_PATH } from '../../../common/constants';
import { FlowTargetSourceDest } from '../../../common/search_strategy';
import { NetworkDetailsRouteType } from './details/types';
import { NetworkRouteType } from './navigation/types';
const NETWORK_TABS = [
NetworkRouteType.flows,
NetworkRouteType.dns,
NetworkRouteType.http,
NetworkRouteType.tls,
NetworkRouteType.events,
];
const NETWORK_WITHOUT_ANOMALIES_TAB_PARAM = NETWORK_TABS.join('|');
const NETWORK_WITH_ANOMALIES_TAB_PARAM = [...NETWORK_TABS, NetworkRouteType.anomalies].join('|');
export const NETWORK_PATH_WITH_ANOMALIES = `${NETWORK_PATH}/:tabName(${NETWORK_WITH_ANOMALIES_TAB_PARAM})`;
export const NETWORK_PATH_WITHOUT_ANOMALIES = `${NETWORK_PATH}/:tabName(${NETWORK_WITHOUT_ANOMALIES_TAB_PARAM})`;
const DETAIL_TABS_PARAM = [
NetworkDetailsRouteType.flows,
NetworkDetailsRouteType.http,
NetworkDetailsRouteType.tls,
NetworkDetailsRouteType.anomalies,
NetworkDetailsRouteType.events,
NetworkDetailsRouteType.users,
].join('|');
export const FLOW_TARGET_PARAM = [
FlowTargetSourceDest.source,
FlowTargetSourceDest.destination,
].join('|');
export const NETWORK_DETAILS_PAGE_PATH = `${NETWORK_PATH}/ip/:detailName`;
export const NETWORK_DETAILS_TAB_PATH = `${NETWORK_DETAILS_PAGE_PATH}/:flowTarget(${FLOW_TARGET_PARAM})/:tabName(${DETAIL_TABS_PARAM})`;

View file

@ -0,0 +1,131 @@
/*
* 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, { useMemo } from 'react';
import { Switch } from 'react-router-dom';
import { EuiFlexItem, EuiSpacer } from '@elastic/eui';
import type { DataViewBase, Filter } from '@kbn/es-query';
import { Route } from '@kbn/kibana-react-plugin/public';
import { TimelineId } from '@kbn/timelines-plugin/common';
import { getNetworkDetailsPageFilter } from '../../../common/components/visualization_actions/utils';
import { AnomaliesNetworkTable } from '../../../common/components/ml/tables/anomalies_network_table';
import { FlowTargetSourceDest } from '../../../../common/search_strategy/security_solution/network';
import { EventsQueryTabBody } from '../../../common/components/events_tab/events_query_tab_body';
import type { GlobalTimeArgs } from '../../../common/containers/use_global_time';
import { AnomaliesQueryTabBody } from '../../../common/containers/anomalies/anomalies_query_tab_body';
import { NETWORK_DETAILS_PAGE_PATH } from '../constants';
import {
CountriesQueryTabBody,
HttpQueryTabBody,
IPsQueryTabBody,
TlsQueryTabBody,
UsersQueryTabBody,
} from '../navigation';
import { ConditionalFlexGroup } from '../navigation/conditional_flex_group';
import { networkModel } from '../../store';
import { NetworkDetailsRouteType } from './types';
interface NetworkDetailTabsProps {
ip: string;
endDate: string;
startDate: string;
filterQuery: string | undefined;
indexNames: string[];
skip: boolean;
setQuery: GlobalTimeArgs['setQuery'];
narrowDateRange: (score: unknown, interval: unknown) => void;
indexPattern: DataViewBase;
flowTarget: FlowTargetSourceDest;
}
export const NetworkDetailsTabs = React.memo<NetworkDetailTabsProps>(
({ narrowDateRange, indexPattern, flowTarget, ...rest }) => {
const type = networkModel.NetworkType.details;
const commonProps = { ...rest, type };
const flowTabProps = { ...commonProps, indexPattern };
const commonPropsWithFlowTarget = { ...commonProps, flowTarget };
const networkDetailsPageFilters: Filter[] = useMemo(
() => getNetworkDetailsPageFilter(rest.ip),
[rest.ip]
);
return (
<Switch>
<Route
path={`${NETWORK_DETAILS_PAGE_PATH}/:flowTarget/:tabName(${NetworkDetailsRouteType.flows})`}
>
<>
<ConditionalFlexGroup direction="column">
<EuiFlexItem>
<IPsQueryTabBody {...flowTabProps} flowTarget={FlowTargetSourceDest.source} />
</EuiFlexItem>
<EuiFlexItem>
<IPsQueryTabBody {...flowTabProps} flowTarget={FlowTargetSourceDest.destination} />
</EuiFlexItem>
</ConditionalFlexGroup>
<EuiSpacer />
<ConditionalFlexGroup direction="column">
<EuiFlexItem>
<CountriesQueryTabBody {...flowTabProps} flowTarget={FlowTargetSourceDest.source} />
</EuiFlexItem>
<EuiFlexItem>
<CountriesQueryTabBody
{...flowTabProps}
flowTarget={FlowTargetSourceDest.destination}
/>
</EuiFlexItem>
</ConditionalFlexGroup>
</>
</Route>
<Route
path={`${NETWORK_DETAILS_PAGE_PATH}/:flowTarget/:tabName(${NetworkDetailsRouteType.users})`}
>
<UsersQueryTabBody {...commonPropsWithFlowTarget} />
</Route>
<Route
path={`${NETWORK_DETAILS_PAGE_PATH}/:flowTarget/:tabName(${NetworkDetailsRouteType.http})`}
>
<HttpQueryTabBody {...commonProps} />
</Route>
<Route
path={`${NETWORK_DETAILS_PAGE_PATH}/:flowTarget/:tabName(${NetworkDetailsRouteType.tls})`}
>
<TlsQueryTabBody {...commonPropsWithFlowTarget} />
</Route>
<Route
path={`${NETWORK_DETAILS_PAGE_PATH}/:flowTarget/:tabName(${NetworkDetailsRouteType.anomalies})`}
>
<AnomaliesQueryTabBody
{...commonPropsWithFlowTarget}
hideHistogramIfEmpty={true}
AnomaliesTableComponent={AnomaliesNetworkTable}
narrowDateRange={narrowDateRange}
/>
</Route>
<Route
path={`${NETWORK_DETAILS_PAGE_PATH}/:flowTarget/:tabName(${NetworkDetailsRouteType.events})`}
>
<EventsQueryTabBody
pageFilters={networkDetailsPageFilters}
timelineId={TimelineId.networkPageEvents}
{...commonProps}
/>
</Route>
</Switch>
);
}
);
NetworkDetailsTabs.displayName = 'UsersDetailsTabs';

View file

@ -64,7 +64,6 @@ jest.mock('react-router-dom', () => {
jest.mock('../../containers/details', () => ({
useNetworkDetails: jest.fn().mockReturnValue([true, { networkDetails: {} }]),
}));
jest.mock('../../../common/lib/kibana');
jest.mock('../../../common/containers/sourcerer');
jest.mock('../../../common/containers/use_global_time', () => ({
useGlobalTime: jest.fn().mockReturnValue({
@ -75,6 +74,27 @@ jest.mock('../../../common/containers/use_global_time', () => ({
}),
}));
jest.mock('../../../common/lib/kibana', () => {
const original = jest.requireActual('../../../common/lib/kibana');
return {
...original,
useNavigation: () => ({
getAppUrl: jest.fn,
}),
useKibana: () => ({
services: {
...original.useKibana().services,
timelines: {
getUseDraggableKeyboardWrapper: () => () => ({
onBlur: jest.fn,
onKeyDown: jest.fn,
}),
},
},
}),
};
});
// Test will fail because we will to need to mock some core services to make the test work
// For now let's forget about SiemSearchBar and QueryBar
jest.mock('../../../common/components/search_bar', () => ({

View file

@ -5,21 +5,16 @@
* 2.0.
*/
import { EuiHorizontalRule, EuiSpacer, EuiFlexItem } from '@elastic/eui';
import { EuiHorizontalRule, EuiSpacer } from '@elastic/eui';
import React, { useCallback, useEffect, useMemo } from 'react';
import { useDispatch } from 'react-redux';
import { useParams } from 'react-router-dom';
import { getEsQueryConfig } from '@kbn/data-plugin/common';
import {
CountriesQueryTabBody,
HttpQueryTabBody,
IPsQueryTabBody,
TlsQueryTabBody,
UsersQueryTabBody,
} from '../navigation';
import { useDeepEqualSelector } from '../../../common/hooks/use_selector';
import { FlowTargetSourceDest, LastEventIndexKey } from '../../../../common/search_strategy';
import { LastEventIndexKey } from '../../../../common/search_strategy';
import type { FlowTargetSourceDest } from '../../../../common/search_strategy';
import { useGlobalTime } from '../../../common/containers/use_global_time';
import { FiltersGlobal } from '../../../common/components/filters_global';
import { HeaderPage } from '../../../common/components/header_page';
@ -27,7 +22,6 @@ import { LastEventTime } from '../../../common/components/last_event_time';
import { useAnomaliesTableData } from '../../../common/components/ml/anomaly/use_anomalies_table_data';
import { networkToCriteria } from '../../../common/components/ml/criteria/network_to_criteria';
import { scoreIntervalToDateTime } from '../../../common/components/ml/score/score_interval_to_datetime';
import { AnomaliesNetworkTable } from '../../../common/components/ml/tables/anomalies_network_table';
import { manageQuery } from '../../../common/components/page/manage_query';
import { FlowTargetSelectConnected } from '../../components/flow_target_select_connected';
import { IpOverview } from '../../components/details';
@ -37,23 +31,29 @@ import { useNetworkDetails, ID } from '../../containers/details';
import { useKibana } from '../../../common/lib/kibana';
import { decodeIpv6 } from '../../../common/lib/helpers';
import { convertToBuildEsQuery } from '../../../common/lib/keury';
import { ConditionalFlexGroup } from '../navigation/conditional_flex_group';
import { inputsSelectors } from '../../../common/store';
import { setAbsoluteRangeDatePicker } from '../../../common/store/inputs/actions';
import { setNetworkDetailsTablesActivePageToZero } from '../../store/actions';
import { SpyRoute } from '../../../common/utils/route/spy_routes';
import { AnomaliesQueryTabBody } from '../../../common/containers/anomalies/anomalies_query_tab_body';
import { networkModel } from '../../store';
import { SecurityPageName } from '../../../app/types';
import { useSourcererDataView } from '../../../common/containers/sourcerer';
import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query';
import { LandingPageComponent } from '../../../common/components/landing_page';
import { SecuritySolutionTabNavigation } from '../../../common/components/navigation';
import { getNetworkDetailsPageFilter } from '../../../common/components/visualization_actions/utils';
import { hasMlUserPermissions } from '../../../../common/machine_learning/has_ml_user_permissions';
import { useMlCapabilities } from '../../../common/components/ml/hooks/use_ml_capabilities';
import { navTabsNetworkDetails } from './nav_tabs';
import { NetworkDetailsTabs } from './details_tabs';
export { getTrailingBreadcrumbs } from './utils';
const NetworkDetailsManage = manageQuery(IpOverview);
const NetworkDetailsComponent: React.FC = () => {
const dispatch = useDispatch();
const capabilities = useMlCapabilities();
const { to, from, setQuery, isInitializing } = useGlobalTime();
const { detailName, flowTarget } = useParams<{
detailName: string;
@ -92,11 +92,17 @@ const NetworkDetailsComponent: React.FC = () => {
const { indicesExist, indexPattern, selectedPatterns } = useSourcererDataView();
const ip = decodeIpv6(detailName);
const queryFilters = useMemo(
() => [...getNetworkDetailsPageFilter(ip), ...filters],
[filters, ip]
);
const [filterQuery, kqlError] = convertToBuildEsQuery({
config: getEsQueryConfig(uiSettings),
indexPattern,
queries: [query],
filters,
filters: queryFilters,
});
useInvalidFilterQuery({ id: ID, filterQuery, kqlError, query, startDate: from, endDate: to });
@ -150,7 +156,6 @@ const NetworkDetailsComponent: React.FC = () => {
>
<FlowTargetSelectConnected flowTarget={flowTarget} />
</HeaderPage>
<NetworkDetailsManage
id={id}
inspect={inspect}
@ -169,131 +174,23 @@ const NetworkDetailsComponent: React.FC = () => {
narrowDateRange={narrowDateRange}
indexPatterns={selectedPatterns}
/>
<EuiHorizontalRule />
<ConditionalFlexGroup direction="column">
<EuiFlexItem>
<IPsQueryTabBody
endDate={to}
filterQuery={filterQuery}
flowTarget={FlowTargetSourceDest.source}
indexNames={selectedPatterns}
indexPattern={indexPattern}
ip={ip}
setQuery={setQuery}
skip={shouldSkip}
startDate={from}
type={type}
/>
</EuiFlexItem>
<EuiFlexItem>
<IPsQueryTabBody
endDate={to}
filterQuery={filterQuery}
flowTarget={FlowTargetSourceDest.destination}
indexNames={selectedPatterns}
indexPattern={indexPattern}
ip={ip}
setQuery={setQuery}
skip={shouldSkip}
startDate={from}
type={type}
/>
</EuiFlexItem>
</ConditionalFlexGroup>
<EuiSpacer />
<ConditionalFlexGroup direction="column">
<EuiFlexItem>
<CountriesQueryTabBody
endDate={to}
filterQuery={filterQuery}
flowTarget={FlowTargetSourceDest.source}
indexNames={selectedPatterns}
indexPattern={indexPattern}
ip={ip}
setQuery={setQuery}
skip={shouldSkip}
startDate={from}
type={type}
/>
</EuiFlexItem>
<EuiFlexItem>
<CountriesQueryTabBody
endDate={to}
filterQuery={filterQuery}
flowTarget={FlowTargetSourceDest.destination}
indexNames={selectedPatterns}
indexPattern={indexPattern}
ip={ip}
setQuery={setQuery}
skip={shouldSkip}
startDate={from}
type={type}
/>
</EuiFlexItem>
</ConditionalFlexGroup>
<EuiSpacer />
<UsersQueryTabBody
endDate={to}
filterQuery={filterQuery}
flowTarget={flowTarget}
indexNames={selectedPatterns}
ip={ip}
setQuery={setQuery}
skip={shouldSkip}
startDate={from}
type={type}
<SecuritySolutionTabNavigation
navTabs={navTabsNetworkDetails(ip, hasMlUserPermissions(capabilities), flowTarget)}
/>
<EuiSpacer />
<HttpQueryTabBody
endDate={to}
filterQuery={filterQuery}
indexNames={selectedPatterns}
<NetworkDetailsTabs
ip={ip}
setQuery={setQuery}
skip={shouldSkip}
endDate={to}
startDate={from}
type={type}
/>
<EuiSpacer />
<TlsQueryTabBody
endDate={to}
filterQuery={filterQuery}
flowTarget={flowTarget}
indexNames={selectedPatterns}
ip={ip}
setQuery={setQuery}
skip={shouldSkip}
startDate={from}
type={type}
/>
<EuiSpacer />
<AnomaliesQueryTabBody
AnomaliesTableComponent={AnomaliesNetworkTable}
endDate={to}
filterQuery={filterQuery}
flowTarget={flowTarget}
hideHistogramIfEmpty={true}
indexNames={selectedPatterns}
ip={ip}
setQuery={setQuery}
narrowDateRange={narrowDateRange}
setQuery={setQuery}
skip={shouldSkip}
startDate={from}
type={type}
indexPattern={indexPattern}
flowTarget={flowTarget}
/>
</SecuritySolutionPageWrapper>
</>

View file

@ -0,0 +1,62 @@
/*
* 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 { FlowTargetSourceDest } from '../../../../common/search_strategy';
import { navTabsNetworkDetails } from './nav_tabs';
import { NetworkDetailsRouteType } from './types';
describe('navTabsNetworkDetails', () => {
test('it should return all tabs if user has ML capabilities', () => {
const tabs = navTabsNetworkDetails('<ip-address>', true, FlowTargetSourceDest.source);
expect(tabs).toEqual(mockTabs);
});
test('it should not display anomalies tab if user has no ml permission', () => {
const tabs = navTabsNetworkDetails('<ip-address>', false, FlowTargetSourceDest.source);
expect(tabs).not.toHaveProperty(NetworkDetailsRouteType.anomalies);
});
});
const mockTabs = {
flows: {
id: 'flows',
name: 'Flows',
href: '/network/ip/<ip-address>/source/flows',
disabled: false,
},
users: {
id: 'users',
name: 'Users',
href: '/network/ip/<ip-address>/source/users',
disabled: false,
},
http: {
id: 'http',
name: 'HTTP',
href: '/network/ip/<ip-address>/source/http',
disabled: false,
},
tls: {
id: 'tls',
name: 'TLS',
href: '/network/ip/<ip-address>/source/tls',
disabled: false,
},
anomalies: {
id: 'anomalies',
name: 'Anomalies',
href: '/network/ip/<ip-address>/source/anomalies',
disabled: false,
},
events: {
id: 'events',
name: 'Events',
href: '/network/ip/<ip-address>/source/events',
disabled: false,
},
};

View file

@ -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 type { FlowTargetSourceDest } from '../../../../common/search_strategy';
import { NETWORK_PATH } from '../../../../common/constants';
import * as i18n from '../translations';
import type { NetworkDetailsNavTabs } from './types';
import { NetworkDetailsRouteType } from './types';
const getTabsOnNetworkDetailsUrl = (
ipAddress: string,
tabName: NetworkDetailsRouteType,
flowTarget: FlowTargetSourceDest
) => `${NETWORK_PATH}/ip/${ipAddress}/${flowTarget}/${tabName}`;
export const navTabsNetworkDetails = (
ipAddress: string,
hasMlUserPermissions: boolean,
flowTarget: FlowTargetSourceDest
): NetworkDetailsNavTabs => {
const networkDetailsNavTabs: NetworkDetailsNavTabs = {
[NetworkDetailsRouteType.flows]: {
id: NetworkDetailsRouteType.flows,
name: i18n.NAVIGATION_FLOWS_TITLE,
href: getTabsOnNetworkDetailsUrl(ipAddress, NetworkDetailsRouteType.flows, flowTarget),
disabled: false,
},
[NetworkDetailsRouteType.users]: {
id: NetworkDetailsRouteType.users,
name: i18n.NAVIGATION_USERS_TITLE,
href: getTabsOnNetworkDetailsUrl(ipAddress, NetworkDetailsRouteType.users, flowTarget),
disabled: false,
},
[NetworkDetailsRouteType.http]: {
id: NetworkDetailsRouteType.http,
name: i18n.NAVIGATION_HTTP_TITLE,
href: getTabsOnNetworkDetailsUrl(ipAddress, NetworkDetailsRouteType.http, flowTarget),
disabled: false,
},
[NetworkDetailsRouteType.tls]: {
id: NetworkDetailsRouteType.tls,
name: i18n.NAVIGATION_TLS_TITLE,
href: getTabsOnNetworkDetailsUrl(ipAddress, NetworkDetailsRouteType.tls, flowTarget),
disabled: false,
},
[NetworkDetailsRouteType.anomalies]: {
id: NetworkDetailsRouteType.anomalies,
name: i18n.NAVIGATION_ANOMALIES_TITLE,
href: getTabsOnNetworkDetailsUrl(ipAddress, NetworkDetailsRouteType.anomalies, flowTarget),
disabled: false,
},
[NetworkDetailsRouteType.events]: {
id: NetworkDetailsRouteType.events,
name: i18n.NAVIGATION_EVENTS_TITLE,
href: getTabsOnNetworkDetailsUrl(ipAddress, NetworkDetailsRouteType.events, flowTarget),
disabled: false,
},
};
if (!hasMlUserPermissions) {
delete networkDetailsNavTabs.anomalies;
}
return networkDetailsNavTabs;
};

View file

@ -5,9 +5,12 @@
* 2.0.
*/
import type { Optional } from '@kbn/utility-types';
import type { ESTermQuery } from '../../../../common/typed_json';
import { NetworkType } from '../../store/model';
import type { NavTab } from '../../../common/components/navigation/types';
import type { GlobalTimeArgs } from '../../../common/containers/use_global_time';
import { NetworkType } from '../../store/model';
export const type = NetworkType.details;
@ -21,3 +24,17 @@ export interface OwnProps {
skip: boolean;
setQuery: GlobalTimeArgs['setQuery'];
}
export enum NetworkDetailsRouteType {
anomalies = 'anomalies',
flows = 'flows',
tls = 'tls',
http = 'http',
events = 'events',
users = 'users',
}
export type NetworkDetailsNavTabs = Optional<
Record<`${NetworkDetailsRouteType}`, NavTab>,
'anomalies'
>;

View file

@ -12,19 +12,21 @@ import { decodeIpv6 } from '../../../common/lib/helpers';
import { getNetworkDetailsUrl } from '../../../common/components/link_to/redirect_to_network';
import { networkModel } from '../../store';
import * as i18n from '../translations';
import { NetworkRouteType } from '../navigation/types';
import { NetworkDetailsRouteType } from './types';
import type { NetworkRouteSpyState } from '../../../common/utils/route/types';
import { SecurityPageName } from '../../../app/types';
import type { GetSecuritySolutionUrl } from '../../../common/components/link_to';
import { NetworkRouteType } from '../navigation/types';
export const type = networkModel.NetworkType.details;
const TabNameMappedToI18nKey: Record<NetworkRouteType, string> = {
[NetworkRouteType.alerts]: i18n.NAVIGATION_EVENTS_TITLE,
[NetworkRouteType.anomalies]: i18n.NAVIGATION_ANOMALIES_TITLE,
[NetworkRouteType.flows]: i18n.NAVIGATION_FLOWS_TITLE,
const TabNameMappedToI18nKey: Record<NetworkDetailsRouteType | NetworkRouteType, string> = {
[NetworkDetailsRouteType.events]: i18n.NAVIGATION_EVENTS_TITLE,
[NetworkDetailsRouteType.anomalies]: i18n.NAVIGATION_ANOMALIES_TITLE,
[NetworkDetailsRouteType.flows]: i18n.NAVIGATION_FLOWS_TITLE,
[NetworkDetailsRouteType.users]: i18n.NAVIGATION_USERS_TITLE,
[NetworkDetailsRouteType.http]: i18n.NAVIGATION_HTTP_TITLE,
[NetworkDetailsRouteType.tls]: i18n.NAVIGATION_TLS_TITLE,
[NetworkRouteType.dns]: i18n.NAVIGATION_DNS_TITLE,
[NetworkRouteType.http]: i18n.NAVIGATION_HTTP_TITLE,
[NetworkRouteType.tls]: i18n.NAVIGATION_TLS_TITLE,
};
export const getTrailingBreadcrumbs = (

View file

@ -17,10 +17,18 @@ import { Network } from './network';
import { getNetworkRoutePath } from './navigation';
import { NetworkRouteType } from './navigation/types';
import { MlNetworkConditionalContainer } from '../../common/components/ml/conditional_links/ml_network_conditional_container';
import { FlowTarget } from '../../../common/search_strategy';
import { NETWORK_PATH } from '../../../common/constants';
import { FlowTargetSourceDest } from '../../../common/search_strategy';
import {
FLOW_TARGET_PARAM,
NETWORK_DETAILS_PAGE_PATH,
NETWORK_DETAILS_TAB_PATH,
} from './constants';
const ipDetailsPageBasePath = `${NETWORK_PATH}/ip/:detailName`;
const getPathWithFlowType = (detailName: string, flowTarget?: FlowTargetSourceDest) =>
`${NETWORK_PATH}/ip/${detailName}/${flowTarget || FlowTargetSourceDest.source}/${
NetworkRouteType.flows
}`;
const NetworkContainerComponent = () => {
const capabilities = useMlCapabilities();
@ -53,25 +61,32 @@ const NetworkContainerComponent = () => {
hasMlUserPermissions={userHasMlUserPermissions}
/>
</Route>
<Route path={`${ipDetailsPageBasePath}/:flowTarget`}>
<Route path={NETWORK_DETAILS_TAB_PATH}>
<NetworkDetails />
</Route>
<Route
path={ipDetailsPageBasePath}
path={`${NETWORK_DETAILS_PAGE_PATH}/:flowTarget(${FLOW_TARGET_PARAM})?`}
render={({
location: { search = '' },
match: {
params: { detailName },
params: { detailName, flowTarget },
},
location: { search = '' },
}) => (
<Redirect
to={{
pathname: `${NETWORK_PATH}/ip/${detailName}/${FlowTarget.source}`,
pathname: getPathWithFlowType(detailName, flowTarget),
search,
}}
/>
)}
/>
<Route>
<Redirect
to={{
pathname: NETWORK_PATH,
}}
/>
</Route>
</Switch>
);
};

View file

@ -45,10 +45,10 @@ export const navTabsNetwork = (hasMlUserPermissions: boolean): NetworkNavTab =>
href: getTabsOnNetworkUrl(NetworkRouteType.anomalies),
disabled: false,
},
[NetworkRouteType.alerts]: {
id: NetworkRouteType.alerts,
[NetworkRouteType.events]: {
id: NetworkRouteType.events,
name: i18n.NAVIGATION_EVENTS_TITLE,
href: getTabsOnNetworkUrl(NetworkRouteType.alerts),
href: getTabsOnNetworkUrl(NetworkRouteType.events),
disabled: false,
},
};

View file

@ -155,7 +155,7 @@ export const NetworkRoutes = React.memo<NetworkRoutesProps>(
AnomaliesTableComponent={AnomaliesNetworkTable}
/>
</Route>
<Route path={`${NETWORK_PATH}/:tabName(${NetworkRouteType.alerts})`}>
<Route path={`${NETWORK_PATH}/:tabName(${NetworkRouteType.events})`}>
<EventsQueryTabBody
pageFilters={filterNetworkExternalAlertData}
timelineId={TimelineId.networkPageEvents}

View file

@ -6,6 +6,8 @@
*/
import type { DataViewBase } from '@kbn/es-query';
import type { Optional } from 'utility-types';
import type { NarrowDateRange } from '../../../common/components/ml/types';
import type { ESTermQuery } from '../../../../common/typed_json';
@ -62,21 +64,10 @@ export enum NetworkRouteType {
anomalies = 'anomalies',
tls = 'tls',
http = 'http',
alerts = 'events', // changed officially to events in #136427
events = 'events',
}
export type KeyNetworkNavTabWithoutMlPermission = NetworkRouteType.dns &
NetworkRouteType.flows &
NetworkRouteType.http &
NetworkRouteType.tls &
NetworkRouteType.alerts;
type KeyNetworkNavTabWithMlPermission = KeyNetworkNavTabWithoutMlPermission &
NetworkRouteType.anomalies;
type KeyNetworkNavTab = KeyNetworkNavTabWithoutMlPermission | KeyNetworkNavTabWithMlPermission;
export type NetworkNavTab = Record<KeyNetworkNavTab, NavTab>;
export type NetworkNavTab = Optional<Record<`${NetworkRouteType}`, NavTab>, 'anomalies'>;
export type GetNetworkRoutePath = (
capabilitiesFetched: boolean,

View file

@ -5,25 +5,13 @@
* 2.0.
*/
import { NETWORK_PATH } from '../../../../common/constants';
import { NETWORK_PATH_WITH_ANOMALIES, NETWORK_PATH_WITHOUT_ANOMALIES } from '../constants';
import type { GetNetworkRoutePath } from './types';
import { NetworkRouteType } from './types';
export const getNetworkRoutePath: GetNetworkRoutePath = (
capabilitiesFetched,
hasMlUserPermission
) => {
if (capabilitiesFetched && !hasMlUserPermission) {
return `${NETWORK_PATH}/:tabName(${NetworkRouteType.flows}|${NetworkRouteType.dns}|${NetworkRouteType.http}|${NetworkRouteType.tls}|${NetworkRouteType.alerts})`;
}
return (
`${NETWORK_PATH}/:tabName(` +
`${NetworkRouteType.flows}|` +
`${NetworkRouteType.dns}|` +
`${NetworkRouteType.anomalies}|` +
`${NetworkRouteType.http}|` +
`${NetworkRouteType.tls}|` +
`${NetworkRouteType.alerts})`
);
};
) =>
capabilitiesFetched && !hasMlUserPermission
? NETWORK_PATH_WITHOUT_ANOMALIES
: NETWORK_PATH_WITH_ANOMALIES;

View file

@ -87,7 +87,7 @@ const NetworkComponent = React.memo<NetworkComponentProps>(
const canUseMaps = kibana.services.application.capabilities.maps.show;
const tabsFilters = useMemo(() => {
if (tabName === NetworkRouteType.alerts) {
if (tabName === NetworkRouteType.events) {
return filters.length > 0
? [...filters, ...filterNetworkExternalAlertData]
: filterNetworkExternalAlertData;

View file

@ -25,6 +25,13 @@ export const NAVIGATION_DNS_TITLE = i18n.translate(
}
);
export const NAVIGATION_USERS_TITLE = i18n.translate(
'xpack.securitySolution.network.navigation.usersTitle',
{
defaultMessage: 'Users',
}
);
export const NAVIGATION_TLS_TITLE = i18n.translate(
'xpack.securitySolution.network.navigation.tlsTitle',
{

View file

@ -1123,7 +1123,7 @@ tr:hover .c3:focus::before {
<a
class="euiLink emotion-euiLink-primary"
data-test-subj="network-details"
href="/ip/192.168.1.2/source"
href="/ip/192.168.1.2/source/flows"
rel="noreferrer"
>
192.168.1.2
@ -2002,7 +2002,7 @@ tr:hover .c3:focus::before {
<a
class="euiLink emotion-euiLink-primary"
data-test-subj="network-details"
href="/ip/10.1.2.3/source"
href="/ip/10.1.2.3/source/flows"
rel="noreferrer"
>
10.1.2.3

View file

@ -1261,7 +1261,7 @@ tr:hover .c5:focus::before {
<a
class="euiLink emotion-euiLink-primary"
data-test-subj="network-details"
href="/ip/192.168.1.2/source"
href="/ip/192.168.1.2/source/flows"
rel="noreferrer"
>
192.168.1.2
@ -2305,7 +2305,7 @@ tr:hover .c5:focus::before {
<a
class="euiLink emotion-euiLink-primary"
data-test-subj="network-details"
href="/ip/10.1.2.3/source"
href="/ip/10.1.2.3/source/flows"
rel="noreferrer"
>
10.1.2.3