Skip to content

Commit

Permalink
[CTI] Adds Threat Intel dashboard links to Overview page (#100423)
Browse files Browse the repository at this point in the history
  • Loading branch information
ecezalp authored Jun 28, 2021
1 parent af38aca commit 5f2392f
Show file tree
Hide file tree
Showing 25 changed files with 1,501 additions and 0 deletions.
12 changes: 12 additions & 0 deletions x-pack/plugins/security_solution/common/cti/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,15 @@ export const EVENT_ENRICHMENT_INDICATOR_FIELD_MAP = {
'url.full': 'threatintel.indicator.url.full',
'registry.path': 'threatintel.indicator.registry.path',
};

export const CTI_DEFAULT_SOURCES = [
'Abuse URL',
'Abuse Malware',
'AlienVault OTX',
'Anomali',
'Anomali ThreatStream',
'Malware Bazaar',
'MISP',
];

export const DEFAULT_CTI_SOURCE_INDEX = ['filebeat-*'];
1 change: 1 addition & 0 deletions x-pack/plugins/security_solution/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"encryptedSavedObjects",
"fleet",
"ml",
"dashboard",
"newsfeed",
"security",
"spaces",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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 { Provider } from 'react-redux';
import { cloneDeep } from 'lodash/fp';
import { mount } from 'enzyme';
import { I18nProvider } from '@kbn/i18n/react';
import { CtiDisabledModule } from './cti_disabled_module';
import { ThemeProvider } from 'styled-components';
import { createStore, State } from '../../../common/store';
import {
createSecuritySolutionStorageMock,
kibanaObservable,
mockGlobalState,
SUB_PLUGINS_REDUCER,
} from '../../../common/mock';
import { mockTheme } from './mock';

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

describe('CtiDisabledModule', () => {
const state: State = mockGlobalState;

const { storage } = createSecuritySolutionStorageMock();
let store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);

beforeEach(() => {
const myState = cloneDeep(state);
store = createStore(myState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
});

it('renders splitPanel with "danger" variant', () => {
const wrapper = mount(
<Provider store={store}>
<I18nProvider>
<ThemeProvider theme={mockTheme}>
<CtiDisabledModule />
</ThemeProvider>
</I18nProvider>
</Provider>
);

expect(
wrapper.find(
'[data-test-subj="cti-dashboard-links"] [data-test-subj="cti-inner-panel-danger"]'
).length
).toEqual(1);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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 { EuiButton } from '@elastic/eui';
import { ThreatIntelPanelView } from './threat_intel_panel_view';
import { EMPTY_LIST_ITEMS } from '../../containers/overview_cti_links/helpers';
import { useKibana } from '../../../common/lib/kibana';
import { CtiInnerPanel } from './cti_inner_panel';
import * as i18n from './translations';

export const CtiDisabledModuleComponent = () => {
const threatIntelDocLink = `${
useKibana().services.docLinks.links.filebeat.base
}/filebeat-module-threatintel.html`;

const danger = useMemo(
() => (
<CtiInnerPanel
color={'danger'}
title={i18n.DANGER_TITLE}
body={i18n.DANGER_BODY}
button={
<EuiButton href={threatIntelDocLink} color={'danger'} fill>
{i18n.DANGER_BUTTON}
</EuiButton>
}
data-test-subj="cti-inner-panel-danger"
/>
),
[threatIntelDocLink]
);

return (
<ThreatIntelPanelView totalEventCount={0} splitPanel={danger} listItems={EMPTY_LIST_ITEMS} />
);
};

CtiDisabledModuleComponent.displayName = 'CtiDisabledModule';

export const CtiDisabledModule = React.memo(CtiDisabledModuleComponent);
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* 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 { Provider } from 'react-redux';
import { cloneDeep } from 'lodash/fp';
import { mount } from 'enzyme';
import { I18nProvider } from '@kbn/i18n/react';
import { CtiEnabledModule } from './cti_enabled_module';
import { ThemeProvider } from 'styled-components';
import { createStore, State } from '../../../common/store';
import {
createSecuritySolutionStorageMock,
kibanaObservable,
mockGlobalState,
SUB_PLUGINS_REDUCER,
} from '../../../common/mock';
import { mockTheme, mockProps, mockCtiEventCountsResponse, mockCtiLinksResponse } from './mock';
import { useCtiEventCounts } from '../../containers/overview_cti_links/use_cti_event_counts';
import { useCtiDashboardLinks } from '../../containers/overview_cti_links';

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

jest.mock('../../containers/overview_cti_links/use_cti_event_counts');
const useCTIEventCountsMock = useCtiEventCounts as jest.Mock;
useCTIEventCountsMock.mockReturnValue(mockCtiEventCountsResponse);

jest.mock('../../containers/overview_cti_links');
const useCtiDashboardLinksMock = useCtiDashboardLinks as jest.Mock;
useCtiDashboardLinksMock.mockReturnValue(mockCtiLinksResponse);

describe('CtiEnabledModule', () => {
const state: State = mockGlobalState;

const { storage } = createSecuritySolutionStorageMock();
let store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);

beforeEach(() => {
const myState = cloneDeep(state);
store = createStore(myState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
});

it('renders CtiWithEvents when there are events', () => {
const wrapper = mount(
<Provider store={store}>
<I18nProvider>
<ThemeProvider theme={mockTheme}>
<CtiEnabledModule {...mockProps} />
</ThemeProvider>
</I18nProvider>
</Provider>
);

expect(wrapper.exists('[data-test-subj="cti-with-events"]')).toBe(true);
});

it('renders CtiWithNoEvents when there are no events', () => {
useCTIEventCountsMock.mockReturnValueOnce({ totalCount: 0 });
const wrapper = mount(
<Provider store={store}>
<I18nProvider>
<ThemeProvider theme={mockTheme}>
<CtiEnabledModule {...mockProps} />
</ThemeProvider>
</I18nProvider>
</Provider>
);

expect(wrapper.exists('[data-test-subj="cti-with-no-events"]')).toBe(true);
});

it('renders null while event counts are loading', () => {
useCTIEventCountsMock.mockReturnValueOnce({ totalCount: -1 });
const wrapper = mount(
<Provider store={store}>
<I18nProvider>
<ThemeProvider theme={mockTheme}>
<CtiEnabledModule {...mockProps} />
</ThemeProvider>
</I18nProvider>
</Provider>
);

expect(wrapper.html()).toEqual('');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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 { ThreatIntelLinkPanelProps } from './index';
import { useCtiEventCounts } from '../../containers/overview_cti_links/use_cti_event_counts';
import { CtiNoEvents } from './cti_no_events';
import { CtiWithEvents } from './cti_with_events';

export const CtiEnabledModuleComponent: React.FC<ThreatIntelLinkPanelProps> = (props) => {
const { eventCountsByDataset, totalCount } = useCtiEventCounts(props);
const { to, from } = props;

switch (totalCount) {
case -1:
return null;
case 0:
return <CtiNoEvents to={to} from={from} data-test-subj="cti-with-no-events" />;
default:
return (
<CtiWithEvents
data-test-subj="cti-with-events"
eventCountsByDataset={eventCountsByDataset}
totalCount={totalCount}
to={to}
from={from}
/>
);
}
};

CtiEnabledModuleComponent.displayName = 'CtiEnabledModule';

export const CtiEnabledModule = React.memo(CtiEnabledModuleComponent);
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 React from 'react';
import styled from 'styled-components';
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSplitPanel, EuiText } from '@elastic/eui';

const PanelContainer = styled(EuiSplitPanel.Inner)`
margin-bottom: ${({ theme }) => theme.eui.paddingSizes.m};
`;

const ButtonContainer = styled(EuiFlexGroup)`
padding: ${({ theme }) => theme.eui.paddingSizes.s};
`;

const Title = styled(EuiText)<{ textcolor: 'primary' | 'warning' | 'danger' }>`
color: ${({ theme, textcolor }) =>
textcolor === 'primary'
? theme.eui.euiColorPrimary
: textcolor === 'warning'
? theme.eui.euiColorWarningText
: theme.eui.euiColorDangerText};
margin-bottom: ${({ theme }) => theme.eui.paddingSizes.m};
`;

const Icon = styled(EuiIcon)`
padding: 0;
margin-top: ${({ theme }) => theme.eui.paddingSizes.m};
margin-left: 12px;
transform: scale(${({ color }) => (color === 'primary' ? 1.4 : 1)});
`;

export const CtiInnerPanel = ({
color,
title,
body,
button,
}: {
color: 'primary' | 'warning' | 'danger';
title: string;
body: string;
button?: JSX.Element;
}) => {
const iconType = color === 'primary' ? 'iInCircle' : color === 'warning' ? 'help' : 'alert';
return (
<PanelContainer grow={false} color={color}>
<EuiFlexGroup direction={'column'}>
<EuiFlexItem>
<EuiFlexGroup direction={'row'}>
<Icon type={iconType} size="m" color={color} />
<EuiFlexItem>
<Title textcolor={color}>{title}</Title>
</EuiFlexItem>
</EuiFlexGroup>
{body}
</EuiFlexItem>
{button && (
<ButtonContainer>
<EuiFlexItem grow={false}>{button}</EuiFlexItem>
</ButtonContainer>
)}
</EuiFlexGroup>
</PanelContainer>
);
};
Loading

0 comments on commit 5f2392f

Please sign in to comment.