From e6fa9b3fe19935b5dd64127803e4352331e95a4d Mon Sep 17 00:00:00 2001 From: Amardeepsingh Siglani Date: Thu, 13 Jun 2024 01:48:19 -0700 Subject: [PATCH 01/12] overview and connection pages UI Signed-off-by: Amardeepsingh Siglani --- cypress/integration/3_alerts.spec.js | 2 + public/app.scss | 1 + .../components/DetectorSchedule/Interval.tsx | 7 +- .../containers/Detectors/Detectors.tsx | 6 +- public/pages/Main/Main.tsx | 126 ++++++++------ public/pages/ThreatIntel/ThreatIntel.scss | 10 ++ .../containers/ConnectThreatIntelSource.tsx | 159 ++++++++++++++++++ .../containers/ThreatIntelOverview.tsx | 157 +++++++++++++++++ public/pages/ThreatIntel/utils/contants.ts | 21 +++ public/utils/constants.ts | 7 + types/ThreatIntel.ts | 86 ++++++++++ types/index.ts | 1 + 12 files changed, 529 insertions(+), 54 deletions(-) create mode 100644 public/pages/ThreatIntel/ThreatIntel.scss create mode 100644 public/pages/ThreatIntel/containers/ConnectThreatIntelSource.tsx create mode 100644 public/pages/ThreatIntel/containers/ThreatIntelOverview.tsx create mode 100644 public/pages/ThreatIntel/utils/contants.ts create mode 100644 types/ThreatIntel.ts diff --git a/cypress/integration/3_alerts.spec.js b/cypress/integration/3_alerts.spec.js index dc40577d6..88a42235a 100644 --- a/cypress/integration/3_alerts.spec.js +++ b/cypress/integration/3_alerts.spec.js @@ -60,8 +60,10 @@ describe('Alerts', () => { }); it('are generated', () => { + setupIntercept(cy, '/_security_analytics/alerts', 'getAlerts', 'GET'); // Refresh the table cy.get('[data-test-subj="superDatePickerApplyTimeButton"]').click({ force: true }); + cy.wait('@getAlerts').should('have.property', 'state', 'Complete'); cy.wait(10000); diff --git a/public/app.scss b/public/app.scss index d721e5522..ead573802 100644 --- a/public/app.scss +++ b/public/app.scss @@ -21,6 +21,7 @@ $euiTextColor: $euiColorDarkestShade !default; @import "./pages/Rules/components/RuleEditor/RuleEditorForm.scss"; @import "./pages/Rules/components/RuleEditor/DetectionVisualEditor.scss"; @import "./pages/Rules/components/RuleEditor/components/SelectionExpField.scss"; +@import "./pages/ThreatIntel/ThreatIntel.scss"; .selected-radio-panel { background-color: tintOrShade($euiColorPrimary, 90%, 70%); diff --git a/public/pages/CreateDetector/components/DefineDetector/components/DetectorSchedule/Interval.tsx b/public/pages/CreateDetector/components/DefineDetector/components/DetectorSchedule/Interval.tsx index 1d1f7e296..2bcd1b35f 100644 --- a/public/pages/CreateDetector/components/DefineDetector/components/DetectorSchedule/Interval.tsx +++ b/public/pages/CreateDetector/components/DefineDetector/components/DetectorSchedule/Interval.tsx @@ -14,10 +14,11 @@ import { import FormFieldHeader from '../../../../../../components/FormFieldHeader'; import React from 'react'; import { PeriodSchedule } from '../../../../../../../models/interfaces'; -import { Detector } from '../../../../../../../types'; +import { DetectorSchedule } from '../../../../../../../types'; export interface IntervalProps { - detector: Detector; + detector: { schedule: DetectorSchedule }; + label?: string; onDetectorScheduleChange(schedule: PeriodSchedule): void; } @@ -62,7 +63,7 @@ export class Interval extends React.Component { const { period } = this.props.detector.schedule; return ( } + label={} isInvalid={!isIntervalValid} error={'Enter schedule interval.'} > diff --git a/public/pages/Detectors/containers/Detectors/Detectors.tsx b/public/pages/Detectors/containers/Detectors/Detectors.tsx index 5d0e3fe32..d0d3ad32f 100644 --- a/public/pages/Detectors/containers/Detectors/Detectors.tsx +++ b/public/pages/Detectors/containers/Detectors/Detectors.tsx @@ -359,9 +359,9 @@ export default class Detectors extends Component - {actions[0]} - {actions[1]} - {actions[2]} + {actions.map((action) => { + return {action}; + })} diff --git a/public/pages/Main/Main.tsx b/public/pages/Main/Main.tsx index 80eeb49c5..654d5f9ad 100644 --- a/public/pages/Main/Main.tsx +++ b/public/pages/Main/Main.tsx @@ -58,7 +58,8 @@ import { DataSourceMenuWrapper } from '../../components/MDS/DataSourceMenuWrappe import { DataSourceOption } from 'src/plugins/data_source_management/public/components/data_source_menu/types'; import { DataSourceContext, DataSourceContextConsumer } from '../../services/DataSourceContext'; import { dataSourceInfo } from '../../services/utils/constants'; -import { getPlugins } from '../../utils/helpers'; +import { ThreatIntelOverview } from '../ThreatIntel/containers/ThreatIntelOverview'; +import { ConnectThreatIntelSource } from '../ThreatIntel/containers/ConnectThreatIntelSource'; enum Navigation { SecurityAnalytics = 'Security Analytics', @@ -70,6 +71,7 @@ enum Navigation { Correlations = 'Correlations', CorrelationRules = 'Correlation rules', LogTypes = 'Log types', + ThreatIntel = 'Threat Intelligence', } /** @@ -87,6 +89,7 @@ const HIDDEN_NAV_ROUTES: string[] = [ ROUTES.EDIT_DETECTOR_ALERT_TRIGGERS, `${ROUTES.LOG_TYPES}/.+`, ROUTES.LOG_TYPES_CREATE, + ROUTES.THREAT_INTEL_CONNECT_CUSTOM_SOURCE, ]; interface MainProps extends RouteComponentProps { @@ -98,7 +101,7 @@ interface MainProps extends RouteComponentProps { interface MainState { getStartedDismissedOnce: boolean; - selectedNavItemId: number; + selectedNavItemId: Navigation; dateTimeFilter: DateTimeFilter; callout?: ICalloutProps; toasts?: Toast[]; @@ -111,12 +114,13 @@ interface MainState { * */ -const navItemIndexByRoute: { [route: string]: number } = { - [ROUTES.OVERVIEW]: 1, - [ROUTES.FINDINGS]: 2, - [ROUTES.ALERTS]: 3, - [ROUTES.DETECTORS]: 4, - [ROUTES.RULES]: 5, +const navItemIdByRoute: { [route: string]: Navigation } = { + [ROUTES.OVERVIEW]: Navigation.Overview, + [ROUTES.FINDINGS]: Navigation.Findings, + [ROUTES.ALERTS]: Navigation.Alerts, + [ROUTES.DETECTORS]: Navigation.Detectors, + [ROUTES.RULES]: Navigation.Rules, + [ROUTES.THREAT_INTEL_OVERVIEW]: Navigation.ThreatIntel, }; export default class Main extends Component { @@ -131,7 +135,7 @@ export default class Main extends Component { }; this.state = { getStartedDismissedOnce: false, - selectedNavItemId: 1, + selectedNavItemId: Navigation.Overview, dateTimeFilter: defaultDateTimeFilter, findingFlyout: null, dataSourceLoading: props.multiDataSourceEnabled, @@ -185,30 +189,30 @@ export default class Main extends Component { }; /** - * Returns current component route index - * @return {number} + * Returns current component route's side nav id + * @return {string} */ - getCurrentRouteIndex = (): number | undefined => { - let index: number | undefined; + getCurrentRouteId = (): Navigation | undefined => { + let navItemId: Navigation | undefined; const pathname = this.props.location.pathname; - for (const [route, routeIndex] of Object.entries(navItemIndexByRoute)) { + for (const [route, routeId] of Object.entries(navItemIdByRoute)) { if (pathname.match(new RegExp(`^${route}`))) { - index = routeIndex; + navItemId = routeId; break; } } - return index; + return navItemId; }; updateSelectedNavItem() { - const routeIndex = this.getCurrentRouteIndex(); - if (routeIndex) { - this.setState({ selectedNavItemId: routeIndex }); + const navItemId = this.getCurrentRouteId(); + if (navItemId) { + this.setState({ selectedNavItemId: navItemId }); } if (this.props.location.pathname.includes('detector-details')) { - this.setState({ selectedNavItemId: navItemIndexByRoute[ROUTES.DETECTORS] }); + this.setState({ selectedNavItemId: navItemIdByRoute[ROUTES.DETECTORS] }); } } @@ -240,12 +244,12 @@ export default class Main extends Component { getSideNavItems = () => { const { history } = this.props; - const { selectedNavItemId: selectedNavItemIndex } = this.state; + const { selectedNavItemId } = this.state; return [ { name: Navigation.SecurityAnalytics, - id: 0, + id: Navigation.SecurityAnalytics, renderItem: () => { return ( <> @@ -259,66 +263,75 @@ export default class Main extends Component { items: [ { name: Navigation.Overview, - id: 1, + id: Navigation.Overview, onClick: () => { - this.setState({ selectedNavItemId: 1 }); + this.setState({ selectedNavItemId: Navigation.Overview }); history.push(ROUTES.OVERVIEW); }, - isSelected: selectedNavItemIndex === 1, + isSelected: selectedNavItemId === Navigation.Overview, }, { name: Navigation.Findings, - id: 2, + id: Navigation.Findings, onClick: () => { - this.setState({ selectedNavItemId: 2 }); + this.setState({ selectedNavItemId: Navigation.Findings }); history.push(ROUTES.FINDINGS); }, - isSelected: selectedNavItemIndex === 2, + isSelected: selectedNavItemId === Navigation.Findings, }, { name: Navigation.Alerts, - id: 3, + id: Navigation.Alerts, onClick: () => { - this.setState({ selectedNavItemId: 3 }); + this.setState({ selectedNavItemId: Navigation.Alerts }); history.push(ROUTES.ALERTS); }, - isSelected: selectedNavItemIndex === 3, + isSelected: selectedNavItemId === Navigation.Alerts, + }, + { + name: Navigation.ThreatIntel, + id: Navigation.ThreatIntel, + onClick: () => { + this.setState({ selectedNavItemId: Navigation.ThreatIntel }); + history.push(ROUTES.THREAT_INTEL_OVERVIEW); + }, + isSelected: selectedNavItemId === Navigation.ThreatIntel, }, { name: Navigation.Detectors, - id: 4, + id: Navigation.Detectors, onClick: () => { - this.setState({ selectedNavItemId: 4 }); + this.setState({ selectedNavItemId: Navigation.Detectors }); history.push(ROUTES.DETECTORS); }, forceOpen: true, - isSelected: selectedNavItemIndex === 4, + isSelected: selectedNavItemId === Navigation.Detectors, items: [ { name: Navigation.Rules, - id: 5, + id: Navigation.Rules, onClick: () => { - this.setState({ selectedNavItemId: 5 }); + this.setState({ selectedNavItemId: Navigation.Rules }); history.push(ROUTES.RULES); }, - isSelected: selectedNavItemIndex === 5, + isSelected: selectedNavItemId === Navigation.Rules, }, { name: Navigation.LogTypes, - id: 8, + id: Navigation.LogTypes, onClick: () => { - this.setState({ selectedNavItemId: 8 }); + this.setState({ selectedNavItemId: Navigation.LogTypes }); history.push(ROUTES.LOG_TYPES); }, - isSelected: selectedNavItemIndex === 8, + isSelected: selectedNavItemId === Navigation.LogTypes, }, ], }, { name: Navigation.Correlations, - id: 6, + id: Navigation.Correlations, onClick: () => { - this.setState({ selectedNavItemId: 6 }); + this.setState({ selectedNavItemId: Navigation.Correlations }); history.push(ROUTES.CORRELATIONS); }, renderItem: (props: any) => { @@ -328,7 +341,7 @@ export default class Main extends Component { { - this.setState({ selectedNavItemId: 6 }); + this.setState({ selectedNavItemId: Navigation.Correlations }); history.push(ROUTES.CORRELATIONS); }} > @@ -338,17 +351,17 @@ export default class Main extends Component { ); }, - isSelected: selectedNavItemIndex === 6, + isSelected: selectedNavItemId === Navigation.Correlations, forceOpen: true, items: [ { name: Navigation.CorrelationRules, - id: 7, + id: Navigation.CorrelationRules, onClick: () => { - this.setState({ selectedNavItemId: 7 }); + this.setState({ selectedNavItemId: Navigation.CorrelationRules }); history.push(ROUTES.CORRELATION_RULES); }, - isSelected: selectedNavItemIndex === 7, + isSelected: selectedNavItemId === Navigation.CorrelationRules, }, ], }, @@ -676,7 +689,11 @@ export default class Main extends Component { this.setState({ selectedNavItemId: 6 })} + onMount={() => + this.setState({ + selectedNavItemId: Navigation.Correlations, + }) + } dateTimeFilter={this.state.dateTimeFilter} setDateTimeFilter={this.setDateTimeFilter} dataSource={selectedDataSource} @@ -714,6 +731,19 @@ export default class Main extends Component { ); }} /> + { + return ; + }} + /> + { + return ; + }} + /> + diff --git a/public/pages/ThreatIntel/ThreatIntel.scss b/public/pages/ThreatIntel/ThreatIntel.scss new file mode 100644 index 000000000..ce0f23c94 --- /dev/null +++ b/public/pages/ThreatIntel/ThreatIntel.scss @@ -0,0 +1,10 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +*/ + +.connect-threat-intel-source-card { + .euiCard__children { + height: 100%; + } +} \ No newline at end of file diff --git a/public/pages/ThreatIntel/containers/ConnectThreatIntelSource.tsx b/public/pages/ThreatIntel/containers/ConnectThreatIntelSource.tsx new file mode 100644 index 000000000..44ae2df08 --- /dev/null +++ b/public/pages/ThreatIntel/containers/ConnectThreatIntelSource.tsx @@ -0,0 +1,159 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useContext, useEffect, useState } from 'react'; +import { + EuiButton, + EuiCheckboxGroup, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiPanel, + EuiSpacer, + EuiSwitch, + EuiText, + EuiTitle, + htmlIdGenerator, +} from '@elastic/eui'; +import { CoreServicesContext } from '../../../components/core_services'; +import { BREADCRUMBS, ROUTES } from '../../../utils/constants'; +import { Interval } from '../../CreateDetector/components/DefineDetector/components/DetectorSchedule/Interval'; +import { RouteComponentProps } from 'react-router-dom'; + +const idPrefix = htmlIdGenerator()(); + +export interface ConnectThreatIntelSourceProps extends RouteComponentProps {} + +export const ConnectThreatIntelSource: React.FC = ({ history }) => { + const context = useContext(CoreServicesContext); + const [onDemandChecked, setOnDemandChecked] = useState(false); + const checkboxes = [ + { + id: `${idPrefix}0`, + label: 'IP - addresses', + }, + { + id: `${idPrefix}1`, + label: 'Domains', + }, + { + id: `${idPrefix}2`, + label: 'File hash', + }, + ]; + const [checkboxIdToSelectedMap, setCheckboxIdToSelectedMap] = useState>( + {} + ); + + const onChange = (optionId: string) => { + const newCheckboxIdToSelectedMap = { + ...checkboxIdToSelectedMap, + ...{ + [optionId]: !checkboxIdToSelectedMap[optionId], + }, + }; + setCheckboxIdToSelectedMap(newCheckboxIdToSelectedMap); + }; + + useEffect(() => { + context?.chrome.setBreadcrumbs([ + BREADCRUMBS.SECURITY_ANALYTICS, + BREADCRUMBS.THREAT_INTEL_CONNECT_CUSTOM_SOURCE, + ]); + }, []); + + return ( + <> + + +

Connect custom threat intelligence source

+
+ + +

Details

+
+ + + + + + + {'Description - '} + optional + + } + > + + + + + + + + +

Connection details

+
+ + + + + + + + + + + {'S3 object key - '} + optional + + } + > + + + + Test connection + + +

Download schedule

+
+ + setOnDemandChecked(event.target.checked)} + /> + + {!onDemandChecked && ( + <> + {}} + /> + + + )} +
+ + + + history.push(ROUTES.THREAT_INTEL_OVERVIEW)}>Cancel + + + Connect threat intel source + + + + ); +}; diff --git a/public/pages/ThreatIntel/containers/ThreatIntelOverview.tsx b/public/pages/ThreatIntel/containers/ThreatIntelOverview.tsx new file mode 100644 index 000000000..0c998f581 --- /dev/null +++ b/public/pages/ThreatIntel/containers/ThreatIntelOverview.tsx @@ -0,0 +1,157 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiAccordion, + EuiBadge, + EuiButton, + EuiCard, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiLink, + EuiPanel, + EuiSpacer, + EuiTabbedContent, + EuiTabbedContentTab, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import React, { MouseEventHandler, useContext, useEffect } from 'react'; +import { CoreServicesContext } from '../../../components/core_services'; +import { BREADCRUMBS, ROUTES } from '../../../utils/constants'; +import { useState } from 'react'; +import { threatIntelNextStepsProps } from '../utils/contants'; +import { ThreatIntelNextStepId, ThreatIntelSourceItem, dummySource } from '../../../../types'; +import { RouteComponentProps } from 'react-router-dom'; + +export interface ThreatIntelOverviewProps extends RouteComponentProps {} + +export const ThreatIntelOverview: React.FC = ({ history }) => { + const context = useContext(CoreServicesContext); + const [showNextSteps, setShowNextSteps] = useState(true); + const [threatIntelSources, setThreatIntelSources] = useState([ + dummySource, + ]); + + useEffect(() => { + context?.chrome.setBreadcrumbs([ + BREADCRUMBS.SECURITY_ANALYTICS, + BREADCRUMBS.THREAT_INTEL_OVERVIEW, + ]); + }, []); + + const nextStepClickHandlerById: Record = { + ['connect']: () => {}, + ['configure-scan']: () => {}, + }; + + const tabs: EuiTabbedContentTab[] = [ + { + id: 'threat-intel-sources', + name: Threat intel sources ({threatIntelSources.length}), + content: ( + <> + + +

Threat intelligence sources ({threatIntelSources.length})

+
+ + + + + + + Connect to your custom threat intel in Amazon S3 + + { + history.push({ + pathname: ROUTES.THREAT_INTEL_CONNECT_CUSTOM_SOURCE, + }); + }} + > + Connect threat intel source + + + + + + {threatIntelSources.map((source) => { + return ( + + + View details + + {' '} + Connected + + } + > + {source.iocTypes.map((iocType) => ( + {iocType} + ))} + + + ); + })} + + + ), + }, + ]; + + return ( + <> + +

Threat intelligence

+
+ + + Scan log data for indicators of compromise from threat intel data streams to identify + malicious actors and security threats.{' '} + + Learn more + + . + + + + + + + {threatIntelNextStepsProps.map(({ id, title, description, footerButtonText }) => ( + + {footerButtonText} + } + /> + + ))} + + + + + + + + ); +}; diff --git a/public/pages/ThreatIntel/utils/contants.ts b/public/pages/ThreatIntel/utils/contants.ts new file mode 100644 index 000000000..b0f9fbcca --- /dev/null +++ b/public/pages/ThreatIntel/utils/contants.ts @@ -0,0 +1,21 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ThreatIntelNextStepCardProps } from '../../../../types'; + +export const threatIntelNextStepsProps: ThreatIntelNextStepCardProps[] = [ + { + id: 'connect', + title: '1. Connect to threat intel sources', + description: 'Connect threat intel sources to get started', + footerButtonText: 'Manage threat intel sources', + }, + { + id: 'configure-scan', + title: '2. Set up the scan for your log sources', + description: 'Select log sources for scan and get alerted on security threats', + footerButtonText: 'Configure scan', + }, +]; diff --git a/public/utils/constants.ts b/public/utils/constants.ts index c27ac5726..cb248375a 100644 --- a/public/utils/constants.ts +++ b/public/utils/constants.ts @@ -49,6 +49,8 @@ export const ROUTES = Object.freeze({ CORRELATION_RULE_EDIT: '/correlations/rule', LOG_TYPES: '/log-types', LOG_TYPES_CREATE: '/create-log-type', + THREAT_INTEL_OVERVIEW: '/threat-intel', + THREAT_INTEL_CONNECT_CUSTOM_SOURCE: '/connect-threat-intel-source', get LANDING_PAGE(): string { return this.OVERVIEW; @@ -87,6 +89,11 @@ export const BREADCRUMBS = Object.freeze({ }, LOG_TYPES: { text: 'Log types', href: `#${ROUTES.LOG_TYPES}` }, LOG_TYPE_CREATE: { text: 'Create log type', href: `#${ROUTES.LOG_TYPES_CREATE}` }, + THREAT_INTEL_OVERVIEW: { text: 'Threat intelligence', href: `#${ROUTES.THREAT_INTEL_OVERVIEW}` }, + THREAT_INTEL_CONNECT_CUSTOM_SOURCE: { + text: 'Connect threat intel source', + href: `#${ROUTES.THREAT_INTEL_CONNECT_CUSTOM_SOURCE}`, + }, }); export enum SortDirection { diff --git a/types/ThreatIntel.ts b/types/ThreatIntel.ts new file mode 100644 index 000000000..5a877256a --- /dev/null +++ b/types/ThreatIntel.ts @@ -0,0 +1,86 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export type ThreatIntelNextStepId = 'connect' | 'configure-scan'; + +export interface ThreatIntelNextStepCardProps { + id: ThreatIntelNextStepId; + title: string; + description: string; + footerButtonText: string; +} + +export enum FeedType { + LICENSED, + OPEN_SOURCED, + S3_CUSTOM, + INTERNAL, + DEFAULT_OPEN_SOURCED, + EXTERNAL_LICENSED, + GUARDDUTY, +} + +export interface ThreatIntelSourceItem { + id: string; + feedName: string; + description: string; + isEnabled: boolean; + iocTypes: string[]; +} + +// export interface ThreatIntelSourceItem { +// id: string; +// version: number; +// feedName: string; +// description: string; +// feedFormat: string; +// feedType: FeedType; +// createdByUser: string; +// createdAt: number; +// enabledTime: number; +// lastUpdateTime: number; +// schedule: string; +// state: string; +// refreshType: string; +// lastRefreshedTime: number; +// lastRefreshedUser: string; +// isEnabled: boolean; +// iocMapStore: Record; +// iocTypes: string[]; +// } + +export const dummySource: ThreatIntelSourceItem = { + id: '', + feedName: 'AlienVault', + description: 'Short description for threat intel source', + isEnabled: true, + iocTypes: ['IP', 'Domain', 'File hash'], +}; + +/** + * + * + * + * + * private String id; + private Long version; + private String feedName; + private String feedFormat; + private FeedType feedType; + private String createdByUser; + private Instant createdAt; + + // private Source source; TODO: create Source Object + private Instant enabledTime; + private Instant lastUpdateTime; + private IntervalSchedule schedule; + private TIFJobState state; + public RefreshType refreshType; + public Instant lastRefreshedTime; + public String lastRefreshedUser; + private Boolean isEnabled; + private Map iocMapStore; + private List iocTypes; + */ diff --git a/types/index.ts b/types/index.ts index 8b0b8c7e6..e73b9b69f 100644 --- a/types/index.ts +++ b/types/index.ts @@ -20,3 +20,4 @@ export * from './SecurityAnalyticsContext'; export * from './DataSourceContext'; export * from './DataSource'; export * from './shared'; +export * from './ThreatIntel'; From 9541bfadb56d22274c9b7a78a4ffe56e45b69486 Mon Sep 17 00:00:00 2001 From: Amardeepsingh Siglani Date: Wed, 19 Jun 2024 00:09:35 -0700 Subject: [PATCH 02/12] all producer flows for threat intel UI with dummy data Signed-off-by: Amardeepsingh Siglani --- common/constants.ts | 12 + public/app.scss | 1 - .../Notifications/NotificationForm.tsx | 185 ++++++++++++++ .../components/Utility/DescriptionGroup.tsx | 35 +++ .../Utility/StatusWithIndicator.tsx | 23 ++ .../AlertCondition/AlertConditionPanel.tsx | 147 ++---------- .../containers/ConfigureAlerts.tsx | 13 +- .../ConfigureAlerts/models/interfaces.ts | 16 -- .../ConfigureAlerts/utils/helpers.ts | 3 +- .../DetectorSchedule/DetectorSchedule.tsx | 32 +-- .../components/DetectorSchedule/Interval.tsx | 8 +- .../containers/CreateDetector.tsx | 20 +- .../UpdateAlertConditions.tsx | 31 +-- .../Findings/components/CreateAlertFlyout.tsx | 10 +- .../FindingsTable/FindingsTable.tsx | 5 +- .../Findings/containers/Findings/Findings.tsx | 41 ++-- public/pages/Main/Main.tsx | 42 +++- public/pages/ThreatIntel/ThreatIntel.scss | 10 - .../EditThreatIntelAlertTriggers.tsx | 10 + .../components/IoCsTable/IoCsTable.tsx | 52 ++++ .../SelectThreatIntelLogSourcesForm.tsx | 225 ++++++++++++++++++ .../SetupThreatIntelAlertTriggers.tsx | 79 ++++++ .../ThreatIntelAlertTriggerForm.tsx | 115 +++++++++ .../ThreatIntelAlertTriggers.tsx | 152 ++++++++++++ .../ThreatIntelLogSources.tsx | 91 +++++++ .../ThreatIntelOverviewActions.tsx | 122 ++++++++++ .../ThreatIntelSourceDetails.tsx | 126 ++++++++++ .../ThreatIntelSourcesList.tsx | 100 ++++++++ .../components/Utility/ConfigActionButton.tsx | 38 +++ .../AddThreatIntelSource.tsx} | 44 ++-- .../Overview/ThreatIntelOverview.tsx | 174 ++++++++++++++ .../ThreatIntelScanConfigForm.tsx | 192 +++++++++++++++ .../containers/ThreatIntelOverview.tsx | 157 ------------ .../ThreatIntelSource/ThreatIntelSource.tsx | 132 ++++++++++ public/pages/ThreatIntel/utils/constants.ts | 9 + public/pages/ThreatIntel/utils/contants.ts | 21 -- public/pages/ThreatIntel/utils/helpers.ts | 79 ++++++ public/security_analytics_app.tsx | 6 + public/services/OpenSearchService.ts | 2 +- public/utils/constants.ts | 22 +- public/utils/helpers.tsx | 19 +- .../AlertConditionPanel.mock.ts | 2 +- .../NotificationChannelOption.mock.ts | 2 +- .../NotificationChannelTypeOptions.mock.ts | 2 +- types/Notification.ts | 12 + types/ThreatIntel.ts | 111 ++++++++- 46 files changed, 2228 insertions(+), 502 deletions(-) create mode 100644 public/components/Notifications/NotificationForm.tsx create mode 100644 public/components/Utility/DescriptionGroup.tsx create mode 100644 public/components/Utility/StatusWithIndicator.tsx delete mode 100644 public/pages/CreateDetector/components/ConfigureAlerts/models/interfaces.ts delete mode 100644 public/pages/ThreatIntel/ThreatIntel.scss create mode 100644 public/pages/ThreatIntel/components/EditThreatIntelAlertTriggers/EditThreatIntelAlertTriggers.tsx create mode 100644 public/pages/ThreatIntel/components/IoCsTable/IoCsTable.tsx create mode 100644 public/pages/ThreatIntel/components/SelectLogSourcesForm/SelectThreatIntelLogSourcesForm.tsx create mode 100644 public/pages/ThreatIntel/components/SetupThreatIntelAlertTriggers/SetupThreatIntelAlertTriggers.tsx create mode 100644 public/pages/ThreatIntel/components/ThreatIntelAlertTriggerForm/ThreatIntelAlertTriggerForm.tsx create mode 100644 public/pages/ThreatIntel/components/ThreatIntelAlertTriggers/ThreatIntelAlertTriggers.tsx create mode 100644 public/pages/ThreatIntel/components/ThreatIntelLogSources/ThreatIntelLogSources.tsx create mode 100644 public/pages/ThreatIntel/components/ThreatIntelOverviewActions/ThreatIntelOverviewActions.tsx create mode 100644 public/pages/ThreatIntel/components/ThreatIntelSourceDetails/ThreatIntelSourceDetails.tsx create mode 100644 public/pages/ThreatIntel/components/ThreatIntelSourcesList/ThreatIntelSourcesList.tsx create mode 100644 public/pages/ThreatIntel/components/Utility/ConfigActionButton.tsx rename public/pages/ThreatIntel/containers/{ConnectThreatIntelSource.tsx => AddThreatIntelSource/AddThreatIntelSource.tsx} (76%) create mode 100644 public/pages/ThreatIntel/containers/Overview/ThreatIntelOverview.tsx create mode 100644 public/pages/ThreatIntel/containers/ScanConfiguration/ThreatIntelScanConfigForm.tsx delete mode 100644 public/pages/ThreatIntel/containers/ThreatIntelOverview.tsx create mode 100644 public/pages/ThreatIntel/containers/ThreatIntelSource/ThreatIntelSource.tsx create mode 100644 public/pages/ThreatIntel/utils/constants.ts delete mode 100644 public/pages/ThreatIntel/utils/contants.ts create mode 100644 public/pages/ThreatIntel/utils/helpers.ts diff --git a/common/constants.ts b/common/constants.ts index 059fb806d..542236427 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -4,3 +4,15 @@ */ export const DEFAULT_RULE_UUID = '25b9c01c-350d-4b95-bed1-836d04a4f324'; + +export enum ThreatIntelIoc { + IPAddress = 'IP', + Domain = 'Domain', + FileHash = 'FileHash', +} + +export const IocLabel: { [k in ThreatIntelIoc]: string } = { + [ThreatIntelIoc.IPAddress]: 'IP-Address', + [ThreatIntelIoc.Domain]: 'Domains', + [ThreatIntelIoc.FileHash]: 'File hash', +}; diff --git a/public/app.scss b/public/app.scss index ead573802..d721e5522 100644 --- a/public/app.scss +++ b/public/app.scss @@ -21,7 +21,6 @@ $euiTextColor: $euiColorDarkestShade !default; @import "./pages/Rules/components/RuleEditor/RuleEditorForm.scss"; @import "./pages/Rules/components/RuleEditor/DetectionVisualEditor.scss"; @import "./pages/Rules/components/RuleEditor/components/SelectionExpField.scss"; -@import "./pages/ThreatIntel/ThreatIntel.scss"; .selected-radio-panel { background-color: tintOrShade($euiColorPrimary, 90%, 70%); diff --git a/public/components/Notifications/NotificationForm.tsx b/public/components/Notifications/NotificationForm.tsx new file mode 100644 index 000000000..5d01da2f5 --- /dev/null +++ b/public/components/Notifications/NotificationForm.tsx @@ -0,0 +1,185 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiAccordion, + EuiButton, + EuiComboBox, + EuiComboBoxOptionOption, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiSpacer, + EuiSwitch, + EuiText, + EuiTextArea, +} from '@elastic/eui'; +import React, { useState } from 'react'; +import { NOTIFICATIONS_HREF } from '../../utils/constants'; +import { NotificationsCallOut } from '../NotificationsCallOut'; +import { + NotificationChannelOption, + NotificationChannelTypeOptions, + TriggerAction, +} from '../../../types'; +import { getIsNotificationPluginInstalled } from '../../utils/helpers'; + +export interface NotificationFormProps { + allNotificationChannels: NotificationChannelTypeOptions[]; + loadingNotifications: boolean; + action: TriggerAction; + prepareMessage: (updateMessage?: boolean, onMount?: boolean) => void; + refreshNotificationChannels: () => void; + onChannelsChange: (selectedOptions: EuiComboBoxOptionOption[]) => void; + onMessageBodyChange: (message: string) => void; + onMessageSubjectChange: (subject: string) => void; +} + +export const NotificationForm: React.FC = ({ + action, + allNotificationChannels, + loadingNotifications, + prepareMessage, + refreshNotificationChannels, + onChannelsChange, + onMessageBodyChange, + onMessageSubjectChange, +}) => { + const hasNotificationPlugin = getIsNotificationPluginInstalled(); + const [showNotificationDetails, setShowNotificationDetails] = useState(true); + const selectedNotificationChannelOption: NotificationChannelOption[] = []; + if (action.destination_id) { + allNotificationChannels.forEach((typeOption) => { + const matchingChannel = typeOption.options.find( + (option) => option.value === action.destination_id + ); + if (matchingChannel) selectedNotificationChannelOption.push(matchingChannel); + }); + } + + return ( + <> + setShowNotificationDetails(e.target.checked)} + /> + + {showNotificationDetails && ( + <> + + + +

Notification channel

+ + } + > + []} + selectedOptions={ + selectedNotificationChannelOption as EuiComboBoxOptionOption[] + } + onChange={onChannelsChange} + singleSelection={{ asPlainText: true }} + onFocus={refreshNotificationChannels} + isDisabled={!hasNotificationPlugin} + /> +
+
+ + + Manage channels + + +
+ + {!hasNotificationPlugin && ( + <> + + + + )} + + + + +

Notification message

+ + } + paddingSize={'l'} + initialIsOpen={false} + > + + + +

Message subject

+ + } + fullWidth={true} + > + onMessageSubjectChange(e.target.value)} + required={true} + fullWidth={true} + /> +
+
+ + + +

Message body

+ + } + fullWidth={true} + > + onMessageBodyChange(e.target.value)} + required={true} + fullWidth={true} + /> +
+
+ + + + prepareMessage(true /* updateMessage */)} + > + Generate message + + + +
+
+ + + + )} + + ); +}; diff --git a/public/components/Utility/DescriptionGroup.tsx b/public/components/Utility/DescriptionGroup.tsx new file mode 100644 index 000000000..ef5a71875 --- /dev/null +++ b/public/components/Utility/DescriptionGroup.tsx @@ -0,0 +1,35 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiDescriptionList, + EuiFlexGroup, + EuiFlexGroupProps, + EuiFlexItem, + EuiFlexItemProps, +} from '@elastic/eui'; +import React from 'react'; + +export interface DescriptionGroupProps { + listItems: { title: NonNullable; description: NonNullable }[]; + itemProps?: Pick; + groupProps?: Pick; +} + +export const DescriptionGroup: React.FC = ({ + listItems, + itemProps, + groupProps, +}) => { + return ( + + {listItems.map((item, idx) => ( + + + + ))} + + ); +}; diff --git a/public/components/Utility/StatusWithIndicator.tsx b/public/components/Utility/StatusWithIndicator.tsx new file mode 100644 index 000000000..4883c815d --- /dev/null +++ b/public/components/Utility/StatusWithIndicator.tsx @@ -0,0 +1,23 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiIcon } from '@elastic/eui'; +import React from 'react'; + +export interface StatusWithIndicatorProps { + text: string; + indicatorColor: 'success' | 'text'; +} + +export const StatusWithIndicator: React.FC = ({ + text, + indicatorColor, +}) => { + return ( + + {text} + + ); +}; diff --git a/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/AlertConditionPanel.tsx b/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/AlertConditionPanel.tsx index 22608e617..e7192d92d 100644 --- a/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/AlertConditionPanel.tsx +++ b/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/AlertConditionPanel.tsx @@ -7,18 +7,13 @@ import React, { ChangeEvent, Component } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { EuiAccordion, - EuiButton, EuiCheckbox, EuiComboBox, EuiComboBoxOptionOption, EuiFieldText, - EuiFlexGroup, - EuiFlexItem, EuiFormRow, EuiSpacer, - EuiSwitch, EuiText, - EuiTextArea, EuiTitle, } from '@elastic/eui'; import { AlertCondition } from '../../../../../../../models/interfaces'; @@ -29,11 +24,13 @@ import { } from '../../utils/helpers'; import { ALERT_SEVERITY_OPTIONS } from '../../utils/constants'; import { CreateDetectorRulesOptions } from '../../../../../../models/types'; -import { NotificationChannelOption, NotificationChannelTypeOptions } from '../../models/interfaces'; -import { NOTIFICATIONS_HREF } from '../../../../../../utils/constants'; import { getNameErrorMessage, validateName } from '../../../../../../utils/validation'; -import { NotificationsCallOut } from '../../../../../../components/NotificationsCallOut'; -import { Detector } from '../../../../../../../types'; +import { + Detector, + NotificationChannelOption, + NotificationChannelTypeOptions, +} from '../../../../../../../types'; +import { NotificationForm } from '../../../../../../components/Notifications/NotificationForm'; interface AlertConditionPanelProps extends RouteComponentProps { alertCondition: AlertCondition; @@ -42,7 +39,6 @@ interface AlertConditionPanelProps extends RouteComponentProps { detector: Detector; indexNum: number; isEdit: boolean; - hasNotificationPlugin: boolean; loadingNotifications: boolean; onAlertTriggerChanged: (newDetector: Detector, emitMetrics?: boolean) => void; refreshNotificationChannels: () => void; @@ -53,7 +49,6 @@ interface AlertConditionPanelState { nameIsInvalid: boolean; previewToggle: boolean; selectedNames: EuiComboBoxOptionOption[]; - showNotificationDetails: boolean; detectionRulesTriggerEnabled: boolean; threatIntelTriggerEnabled: boolean; } @@ -69,7 +64,6 @@ export default class AlertConditionPanel extends Component< nameIsInvalid: false, previewToggle: false, selectedNames: [], - showNotificationDetails: true, detectionRulesTriggerEnabled: props.alertCondition.detection_types.includes('rules'), threatIntelTriggerEnabled: props.alertCondition.detection_types.includes('threat_intel'), }; @@ -200,7 +194,7 @@ export default class AlertConditionPanel extends Component< this.updateTrigger({ tags }); }; - onNotificationChannelsChange = (selectedOptions: EuiComboBoxOptionOption[]) => { + private onNotificationChannelsChange = (selectedOptions: EuiComboBoxOptionOption[]) => { const { alertCondition, onAlertTriggerChanged, @@ -282,13 +276,11 @@ export default class AlertConditionPanel extends Component< loadingNotifications, refreshNotificationChannels, rulesOptions, - hasNotificationPlugin, } = this.props; const { nameFieldTouched, nameIsInvalid, selectedNames, - showNotificationDetails, detectionRulesTriggerEnabled, threatIntelTriggerEnabled, } = this.state; @@ -525,123 +517,16 @@ export default class AlertConditionPanel extends Component< - this.setState({ showNotificationDetails: e.target.checked })} + - - - - {showNotificationDetails && ( - <> - - - -

Notification channel

- - } - > - []} - selectedOptions={ - selectedNotificationChannelOption as EuiComboBoxOptionOption[] - } - onChange={this.onNotificationChannelsChange} - singleSelection={{ asPlainText: true }} - onFocus={refreshNotificationChannels} - isDisabled={!hasNotificationPlugin} - /> -
-
- - - Manage channels - - -
- - {!hasNotificationPlugin && ( - <> - - - - )} - - - - -

Notification message

- - } - paddingSize={'l'} - initialIsOpen={false} - > - - - -

Message subject

- - } - fullWidth={true} - > - this.onMessageSubjectChange(e.target.value)} - required={true} - fullWidth={true} - /> -
-
- - - -

Message body

- - } - fullWidth={true} - > - this.onMessageBodyChange(e.target.value)} - required={true} - fullWidth={true} - /> -
-
- - - - this.prepareMessage(true)}> - Generate message - - - -
-
- - - - )} ); } diff --git a/public/pages/CreateDetector/components/ConfigureAlerts/containers/ConfigureAlerts.tsx b/public/pages/CreateDetector/components/ConfigureAlerts/containers/ConfigureAlerts.tsx index 5f36a817e..e14e3b1f0 100644 --- a/public/pages/CreateDetector/components/ConfigureAlerts/containers/ConfigureAlerts.tsx +++ b/public/pages/CreateDetector/components/ConfigureAlerts/containers/ConfigureAlerts.tsx @@ -17,7 +17,6 @@ import { import { MAX_ALERT_CONDITIONS } from '../utils/constants'; import AlertConditionPanel from '../components/AlertCondition'; import { CreateDetectorRulesOptions } from '../../../../../models/types'; -import { NotificationChannelTypeOptions } from '../models/interfaces'; import { getEmptyAlertCondition, getNotificationChannels, @@ -32,6 +31,7 @@ import { CreateDetectorSteps, Detector, DetectorCreationStep, + NotificationChannelTypeOptions, } from '../../../../../../types'; import { MetricsContext } from '../../../../../metrics/MetricsContext'; @@ -42,7 +42,6 @@ interface ConfigureAlertsProps extends RouteComponentProps { changeDetector: (detector: Detector) => void; updateDataValidState: (step: DetectorCreationStep, isValid: boolean) => void; notificationsService: NotificationsService; - hasNotificationPlugin: boolean; getTriggerName: () => string; metricsContext?: MetricsContext; } @@ -52,7 +51,7 @@ interface ConfigureAlertsState { notificationChannels: NotificationChannelTypeOptions[]; } -const isTriggerValid = (triggers: AlertCondition[], hasNotificationPlugin: boolean) => { +const isTriggerValid = (triggers: AlertCondition[]) => { return ( !triggers.length || triggers.every((trigger) => { @@ -105,7 +104,7 @@ export default class ConfigureAlerts extends Component { - const isTriggerDataValid = isTriggerValid( - newDetector.triggers, - this.props.hasNotificationPlugin - ); + const isTriggerDataValid = isTriggerValid(newDetector.triggers); this.props.changeDetector(newDetector); this.props.updateDataValidState(DetectorCreationStep.CONFIGURE_ALERTS, isTriggerDataValid); if (emitMetrics) { @@ -223,7 +219,6 @@ export default class ConfigureAlerts extends Component diff --git a/public/pages/CreateDetector/components/ConfigureAlerts/models/interfaces.ts b/public/pages/CreateDetector/components/ConfigureAlerts/models/interfaces.ts deleted file mode 100644 index fda16e043..000000000 --- a/public/pages/CreateDetector/components/ConfigureAlerts/models/interfaces.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -export interface NotificationChannelTypeOptions { - label: string; - options: NotificationChannelOption[]; -} - -export interface NotificationChannelOption { - label: string; - value: string; - type: string; - description: string; -} diff --git a/public/pages/CreateDetector/components/ConfigureAlerts/utils/helpers.ts b/public/pages/CreateDetector/components/ConfigureAlerts/utils/helpers.ts index c579bab59..638ba7008 100644 --- a/public/pages/CreateDetector/components/ConfigureAlerts/utils/helpers.ts +++ b/public/pages/CreateDetector/components/ConfigureAlerts/utils/helpers.ts @@ -5,10 +5,9 @@ import { EuiComboBoxOptionOption } from '@elastic/eui'; import { ALERT_SEVERITY_OPTIONS, CHANNEL_TYPES } from './constants'; -import { NotificationChannelTypeOptions } from '../models/interfaces'; import { NotificationsService } from '../../../../../services'; import { AlertCondition, TriggerAction } from '../../../../../../models/interfaces'; -import { FeatureChannelList } from '../../../../../../types'; +import { FeatureChannelList, NotificationChannelTypeOptions } from '../../../../../../types'; export const parseAlertSeverityToOption = (severity: string): EuiComboBoxOptionOption => { return Object.values(ALERT_SEVERITY_OPTIONS).find( diff --git a/public/pages/CreateDetector/components/DefineDetector/components/DetectorSchedule/DetectorSchedule.tsx b/public/pages/CreateDetector/components/DefineDetector/components/DetectorSchedule/DetectorSchedule.tsx index 802e60a84..0d63bd00f 100644 --- a/public/pages/CreateDetector/components/DefineDetector/components/DetectorSchedule/DetectorSchedule.tsx +++ b/public/pages/CreateDetector/components/DefineDetector/components/DetectorSchedule/DetectorSchedule.tsx @@ -4,33 +4,18 @@ */ import React from 'react'; -import { EuiSelectOption, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { EuiSpacer, EuiTitle } from '@elastic/eui'; import { PeriodSchedule } from '../../../../../../../models/interfaces'; import { Interval } from './Interval'; -import { CustomCron } from './CustomCron'; -import { Daily } from './Daily'; -import { Monthly } from './Monthly'; -import { Weekly } from './Weekly'; import { Detector } from '../../../../../../../types'; - -const frequencies: EuiSelectOption[] = [{ value: 'interval', text: 'By interval' }]; +import FormFieldHeader from '../../../../../../components/FormFieldHeader'; export interface DetectorScheduleProps { detector: Detector; onDetectorScheduleChange(schedule: PeriodSchedule): void; } -export interface DetectorScheduleState { - selectedFrequency: string; -} - -const components: { [freq: string]: typeof React.Component } = { - daily: Daily, - weekly: Weekly, - monthly: Monthly, - cronExpression: CustomCron, - interval: Interval, -}; +export interface DetectorScheduleState {} export class DetectorSchedule extends React.Component< DetectorScheduleProps, @@ -38,25 +23,16 @@ export class DetectorSchedule extends React.Component< > { constructor(props: DetectorScheduleProps) { super(props); - this.state = { - selectedFrequency: frequencies[0].value as string, - }; } - onFrequencySelected = (event: React.ChangeEvent) => { - this.setState({ selectedFrequency: event.target.value }); - }; - render() { - const FrequencyPicker = components[this.state.selectedFrequency]; - return ( <>

Detector schedule

- + } /> ); } diff --git a/public/pages/CreateDetector/components/DefineDetector/components/DetectorSchedule/Interval.tsx b/public/pages/CreateDetector/components/DefineDetector/components/DetectorSchedule/Interval.tsx index 2bcd1b35f..bb4b7b397 100644 --- a/public/pages/CreateDetector/components/DefineDetector/components/DetectorSchedule/Interval.tsx +++ b/public/pages/CreateDetector/components/DefineDetector/components/DetectorSchedule/Interval.tsx @@ -11,14 +11,14 @@ import { EuiSelect, EuiSelectOption, } from '@elastic/eui'; -import FormFieldHeader from '../../../../../../components/FormFieldHeader'; import React from 'react'; import { PeriodSchedule } from '../../../../../../../models/interfaces'; import { DetectorSchedule } from '../../../../../../../types'; export interface IntervalProps { detector: { schedule: DetectorSchedule }; - label?: string; + label?: string | React.ReactNode; + readonly?: boolean; onDetectorScheduleChange(schedule: PeriodSchedule): void; } @@ -63,7 +63,7 @@ export class Interval extends React.Component { const { period } = this.props.detector.schedule; return ( } + label={this.props.label} isInvalid={!isIntervalValid} error={'Enter schedule interval.'} > @@ -77,6 +77,7 @@ export class Interval extends React.Component { data-test-subj={'detector-schedule-number-select'} required={true} isInvalid={!isIntervalValid} + readOnly={this.props.readonly} /> @@ -85,6 +86,7 @@ export class Interval extends React.Component { onChange={this.onUnitChange} value={period.unit} data-test-subj={'detector-schedule-unit-select'} + disabled={this.props.readonly} /> diff --git a/public/pages/CreateDetector/containers/CreateDetector.tsx b/public/pages/CreateDetector/containers/CreateDetector.tsx index d009c0a9b..47e7abe41 100644 --- a/public/pages/CreateDetector/containers/CreateDetector.tsx +++ b/public/pages/CreateDetector/containers/CreateDetector.tsx @@ -16,13 +16,7 @@ import { } from '@elastic/eui'; import DefineDetector from '../components/DefineDetector/containers/DefineDetector'; import { createDetectorSteps, PENDING_DETECTOR_ID } from '../utils/constants'; -import { - BREADCRUMBS, - EMPTY_DEFAULT_DETECTOR, - OS_NOTIFICATION_PLUGIN, - PLUGIN_NAME, - ROUTES, -} from '../../../utils/constants'; +import { BREADCRUMBS, EMPTY_DEFAULT_DETECTOR, PLUGIN_NAME, ROUTES } from '../../../utils/constants'; import ConfigureAlerts from '../components/ConfigureAlerts'; import { FieldMapping } from '../../../../models/interfaces'; import { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps'; @@ -35,7 +29,6 @@ import { RuleItemInfo, } from '../components/DefineDetector/components/DetectionRules/types/interfaces'; import { NotificationsStart } from 'opensearch-dashboards/public'; -import { getPlugins } from '../../../utils/helpers'; import { CreateDetectorSteps, DataSourceManagerProps, @@ -62,7 +55,6 @@ export interface CreateDetectorState { stepDataValid: { [step in DetectorCreationStep | string]: boolean }; creatingDetector: boolean; rulesState: CreateDetectorRulesState; - plugins: string[]; loadingRules: boolean; } @@ -94,7 +86,6 @@ export default class CreateDetector extends Component { this.setState({ rulesState: { @@ -372,7 +355,6 @@ export default class CreateDetector extends Component diff --git a/public/pages/Detectors/components/UpdateAlertConditions/UpdateAlertConditions.tsx b/public/pages/Detectors/components/UpdateAlertConditions/UpdateAlertConditions.tsx index e5ad77c85..b26ee6cce 100644 --- a/public/pages/Detectors/components/UpdateAlertConditions/UpdateAlertConditions.tsx +++ b/public/pages/Detectors/components/UpdateAlertConditions/UpdateAlertConditions.tsx @@ -14,20 +14,12 @@ import { import ConfigureAlerts from '../../../CreateDetector/components/ConfigureAlerts'; import { DetectorsService, NotificationsService, OpenSearchService } from '../../../../services'; import { RuleOptions } from '../../../../models/interfaces'; -import { - ROUTES, - OS_NOTIFICATION_PLUGIN, - EMPTY_DEFAULT_DETECTOR, -} from '../../../../utils/constants'; +import { ROUTES, EMPTY_DEFAULT_DETECTOR } from '../../../../utils/constants'; import { NotificationsStart } from 'opensearch-dashboards/public'; -import { - errorNotificationToast, - getPlugins, - successNotificationToast, -} from '../../../../utils/helpers'; +import { errorNotificationToast, successNotificationToast } from '../../../../utils/helpers'; import { ServerResponse } from '../../../../../server/models/types'; import { DataStore } from '../../../../store/DataStore'; -import { Detector, DetectorCreationStep } from '../../../../../types'; +import { Detector, DetectorCreationStep, DetectorResponse } from '../../../../../types'; export interface UpdateAlertConditionsProps extends RouteComponentProps { @@ -42,7 +34,6 @@ export interface UpdateAlertConditionsState { rules: object; rulesOptions: RuleOptions[]; submitting: boolean; - plugins: string[]; isTriggerNameValid: boolean; } @@ -62,14 +53,12 @@ export default class UpdateAlertConditions extends Component< rules: {}, rulesOptions: [], submitting: false, - plugins: [], isTriggerNameValid: true, }; } componentDidMount() { this.getRules(); - this.getPlugins(); } changeDetector = (detector: Detector) => { @@ -132,13 +121,6 @@ export default class UpdateAlertConditions extends Component< } }; - async getPlugins() { - const { opensearchService } = this.props; - const plugins = await getPlugins(opensearchService); - - this.setState({ plugins }); - } - onCancel = () => { this.props.history.replace({ pathname: `${ROUTES.DETECTOR_DETAILS}/${this.props.location.state?.detectorHit._id}`, @@ -159,6 +141,7 @@ export default class UpdateAlertConditions extends Component< detectorHit = { _source: detector, _id: detector.id || '', + _index: '', }, } = state || {}; @@ -185,7 +168,10 @@ export default class UpdateAlertConditions extends Component< history.replace({ pathname: `${ROUTES.DETECTOR_DETAILS}/${detectorHit._id}`, state: { - detectorHit: { ...detectorHit, _source: { ...detectorHit._source, ...detector } }, + detectorHit: { + ...detectorHit, + _source: { ...(detectorHit._source as DetectorResponse), ...detector }, + }, }, }); }; @@ -208,7 +194,6 @@ export default class UpdateAlertConditions extends Component< rulesOptions={rulesOptions} changeDetector={this.changeDetector} updateDataValidState={this.updateDataValidState} - hasNotificationPlugin={this.state.plugins.includes(OS_NOTIFICATION_PLUGIN)} getTriggerName={() => ''} /> diff --git a/public/pages/Findings/components/CreateAlertFlyout.tsx b/public/pages/Findings/components/CreateAlertFlyout.tsx index c84f8f27a..8de40f34c 100644 --- a/public/pages/Findings/components/CreateAlertFlyout.tsx +++ b/public/pages/Findings/components/CreateAlertFlyout.tsx @@ -22,17 +22,16 @@ import { AlertCondition } from '../../../../models/interfaces'; import { DetectorsService } from '../../../services'; import { RulesSharedState } from '../../../models/interfaces'; import { DEFAULT_EMPTY_DATA } from '../../../utils/constants'; -import { NotificationChannelTypeOptions } from '../../CreateDetector/components/ConfigureAlerts/models/interfaces'; -import { Finding } from '../models/interfaces'; +import {} from '../models/interfaces'; import { getEmptyAlertCondition } from '../../CreateDetector/components/ConfigureAlerts/utils/helpers'; import { validateName } from '../../../utils/validation'; import { addDetectionType } from '../../../utils/helpers'; -import { Detector } from '../../../../types'; +import { Detector, FindingItemType, NotificationChannelTypeOptions } from '../../../../types'; interface CreateAlertFlyoutProps extends RouteComponentProps { closeFlyout: (refreshPage?: boolean) => void; detectorService: DetectorsService; - finding: Finding; + finding: FindingItemType; notificationChannels: NotificationChannelTypeOptions[]; refreshNotificationChannels: () => void; allRules: object; @@ -162,13 +161,12 @@ export default class CreateAlertFlyout extends Component< isEdit={false} loadingNotifications={loading} onAlertTriggerChanged={this.onAlertConditionChange} - hasNotificationPlugin={this.props.hasNotificationPlugin} /> - + closeFlyout()}> Cancel diff --git a/public/pages/Findings/components/FindingsTable/FindingsTable.tsx b/public/pages/Findings/components/FindingsTable/FindingsTable.tsx index 05d771ffa..53d4f1a54 100644 --- a/public/pages/Findings/components/FindingsTable/FindingsTable.tsx +++ b/public/pages/Findings/components/FindingsTable/FindingsTable.tsx @@ -31,12 +31,11 @@ import { CorrelationService, } from '../../../../services'; import CreateAlertFlyout from '../CreateAlertFlyout'; -import { NotificationChannelTypeOptions } from '../../../CreateDetector/components/ConfigureAlerts/models/interfaces'; import { parseAlertSeverityToOption } from '../../../CreateDetector/components/ConfigureAlerts/utils/helpers'; import { RuleSource } from '../../../../../server/models/interfaces'; import { DataStore } from '../../../../store/DataStore'; import { getSeverityColor } from '../../../Correlations/utils/constants'; -import { Finding, FindingItemType } from '../../../../../types'; +import { Finding, FindingItemType, NotificationChannelTypeOptions } from '../../../../../types'; interface FindingsTableProps extends RouteComponentProps { detectorService: DetectorsService; @@ -120,7 +119,7 @@ export default class FindingsTable extends Component { + renderCreateAlertFlyout = (finding: FindingItemType) => { if (this.state.flyoutOpen) this.closeFlyout(); else { const ruleOptions = finding.queries diff --git a/public/pages/Findings/containers/Findings/Findings.tsx b/public/pages/Findings/containers/Findings/Findings.tsx index d9f197107..a9a4acfef 100644 --- a/public/pages/Findings/containers/Findings/Findings.tsx +++ b/public/pages/Findings/containers/Findings/Findings.tsx @@ -28,7 +28,6 @@ import { BREADCRUMBS, DEFAULT_DATE_RANGE, MAX_RECENTLY_USED_TIME_RANGES, - OS_NOTIFICATION_PLUGIN, } from '../../../../utils/constants'; import { getChartTimeUnit, @@ -45,8 +44,8 @@ import { createSelectComponent, errorNotificationToast, renderVisualization, - getPlugins, getDuration, + getIsNotificationPluginInstalled, } from '../../../../utils/helpers'; import { RuleSource } from '../../../../../server/models/interfaces'; import { NotificationsStart } from 'opensearch-dashboards/public'; @@ -58,7 +57,7 @@ import { FeatureChannelList, DateTimeFilter, FindingItemType, - DetectorHit + DetectorHit, } from '../../../../../types'; interface FindingsProps extends RouteComponentProps, DataSourceProps { @@ -81,7 +80,6 @@ interface FindingsState { recentlyUsedRanges: DurationRange[]; groupBy: FindingsGroupByType; filteredFindings: FindingItemType[]; - plugins: string[]; timeUnit: TimeUnit; dateFormat: string; } @@ -123,7 +121,6 @@ class Findings extends Component { recentlyUsedRanges: [DEFAULT_DATE_RANGE], groupBy: 'logType', filteredFindings: [], - plugins: [], timeUnit: timeUnits.timeUnit, dateFormat: timeUnits.dateFormat, }; @@ -151,7 +148,6 @@ class Findings extends Component { onRefresh = async () => { await this.getNotificationChannels(); - await this.getPlugins(); await this.getFindings(); renderVisualization(this.generateVisualizationSpec(), 'findings-view'); }; @@ -164,13 +160,13 @@ class Findings extends Component { await this.getRules(Array.from(ruleIds)); this.setState({ findings: [...this.state.findings, ...findings] }); - } + }; abortGetFindings = () => { - this.abortGetFindingsControllers.forEach(controller => { + this.abortGetFindingsControllers.forEach((controller) => { controller.abort(); }); - } + }; getFindings = async () => { this.abortGetFindings(); @@ -184,7 +180,11 @@ class Findings extends Component { // Not looking for findings from specific detector if (!detectorId) { - await DataStore.findings.getAllFindings(abortController.signal, duration, this.onStreamingFindings); + await DataStore.findings.getAllFindings( + abortController.signal, + duration, + this.onStreamingFindings + ); } else { // get findings for a detector const getDetectorResponse = await detectorService.getDetectorWithId(detectorId); @@ -193,9 +193,15 @@ class Findings extends Component { const detectorHit: DetectorHit = { _id: getDetectorResponse.response._id, _index: '', - _source: getDetectorResponse.response.detector - } - await DataStore.findings.getFindingsPerDetector(detectorId, detectorHit, abortController.signal, duration, this.onStreamingFindings); + _source: getDetectorResponse.response.detector, + }; + await DataStore.findings.getFindingsPerDetector( + detectorId, + detectorHit, + abortController.signal, + duration, + this.onStreamingFindings + ); } else { errorNotificationToast(notifications, 'retrieve', 'findings', getDetectorResponse.error); } @@ -227,13 +233,6 @@ class Findings extends Component { this.setState({ notificationChannels: channels }); }; - async getPlugins() { - const { opensearchService } = this.props; - const plugins = await getPlugins(opensearchService); - - this.setState({ plugins }); - } - onTimeChange = ({ start, end }: { start: string; end: string }) => { let { recentlyUsedRanges } = this.state; recentlyUsedRanges = recentlyUsedRanges.filter( @@ -399,7 +398,7 @@ class Findings extends Component { notificationChannels={parseNotificationChannelsToOptions(notificationChannels)} refreshNotificationChannels={this.getNotificationChannels} onFindingsFiltered={this.onFindingsFiltered} - hasNotificationsPlugin={this.state.plugins.includes(OS_NOTIFICATION_PLUGIN)} + hasNotificationsPlugin={getIsNotificationPluginInstalled()} correlationService={this.props.correlationService} /> diff --git a/public/pages/Main/Main.tsx b/public/pages/Main/Main.tsx index 654d5f9ad..0eb8b6278 100644 --- a/public/pages/Main/Main.tsx +++ b/public/pages/Main/Main.tsx @@ -58,8 +58,10 @@ import { DataSourceMenuWrapper } from '../../components/MDS/DataSourceMenuWrappe import { DataSourceOption } from 'src/plugins/data_source_management/public/components/data_source_menu/types'; import { DataSourceContext, DataSourceContextConsumer } from '../../services/DataSourceContext'; import { dataSourceInfo } from '../../services/utils/constants'; -import { ThreatIntelOverview } from '../ThreatIntel/containers/ThreatIntelOverview'; -import { ConnectThreatIntelSource } from '../ThreatIntel/containers/ConnectThreatIntelSource'; +import { ThreatIntelOverview } from '../ThreatIntel/containers/Overview/ThreatIntelOverview'; +import { AddThreatIntelSource } from '../ThreatIntel/containers/AddThreatIntelSource/AddThreatIntelSource'; +import { ThreatIntelScanConfigForm } from '../ThreatIntel/containers/ScanConfiguration/ThreatIntelScanConfigForm'; +import { ThreatIntelSource } from '../ThreatIntel/containers/ThreatIntelSource/ThreatIntelSource'; enum Navigation { SecurityAnalytics = 'Security Analytics', @@ -89,7 +91,8 @@ const HIDDEN_NAV_ROUTES: string[] = [ ROUTES.EDIT_DETECTOR_ALERT_TRIGGERS, `${ROUTES.LOG_TYPES}/.+`, ROUTES.LOG_TYPES_CREATE, - ROUTES.THREAT_INTEL_CONNECT_CUSTOM_SOURCE, + ROUTES.THREAT_INTEL_ADD_CUSTOM_SOURCE, + ROUTES.THREAT_INTEL_SCAN_CONFIG, ]; interface MainProps extends RouteComponentProps { @@ -433,7 +436,7 @@ export default class Main extends Component { {...findingFlyout} history={history} indexPatternsService={services.indexPatternsService} - correlationService={services?.correlationsService} + correlationService={services.correlationsService} opensearchService={services.opensearchService} /> ) : null} @@ -445,7 +448,7 @@ export default class Main extends Component { {...props} setDateTimeFilter={this.setDateTimeFilter} dateTimeFilter={this.state.dateTimeFilter} - correlationService={services?.correlationsService} + correlationService={services.correlationsService} opensearchService={services.opensearchService} detectorService={services.detectorsService} notificationsService={services.notificationsService} @@ -659,8 +662,8 @@ export default class Main extends Component { render={(props: RouteComponentProps) => ( { render={(props: RouteComponentProps) => ( { }} /> { - return ; + return ; }} /> { return ; }} /> + ) => { + return ( + + ); + }} + /> + ) => { + return ; + }} + /> diff --git a/public/pages/ThreatIntel/ThreatIntel.scss b/public/pages/ThreatIntel/ThreatIntel.scss deleted file mode 100644 index ce0f23c94..000000000 --- a/public/pages/ThreatIntel/ThreatIntel.scss +++ /dev/null @@ -1,10 +0,0 @@ -/* -* Copyright OpenSearch Contributors -* SPDX-License-Identifier: Apache-2.0 -*/ - -.connect-threat-intel-source-card { - .euiCard__children { - height: 100%; - } -} \ No newline at end of file diff --git a/public/pages/ThreatIntel/components/EditThreatIntelAlertTriggers/EditThreatIntelAlertTriggers.tsx b/public/pages/ThreatIntel/components/EditThreatIntelAlertTriggers/EditThreatIntelAlertTriggers.tsx new file mode 100644 index 000000000..11fd4d334 --- /dev/null +++ b/public/pages/ThreatIntel/components/EditThreatIntelAlertTriggers/EditThreatIntelAlertTriggers.tsx @@ -0,0 +1,10 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +// export const EditThreatIntelAlertTriggers = () => { +// return ( +// < +// ) +// }; diff --git a/public/pages/ThreatIntel/components/IoCsTable/IoCsTable.tsx b/public/pages/ThreatIntel/components/IoCsTable/IoCsTable.tsx new file mode 100644 index 000000000..846307207 --- /dev/null +++ b/public/pages/ThreatIntel/components/IoCsTable/IoCsTable.tsx @@ -0,0 +1,52 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useState } from 'react'; +import { EuiBasicTableColumn, EuiInMemoryTable, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; +import { ThreatIntelIocData, dummyIoCDetails } from '../../../../../types'; + +export interface IoCstableProps {} + +export const IoCstable: React.FC = () => { + const [iocs, setIocs] = useState([dummyIoCDetails]); + const columns: EuiBasicTableColumn[] = [ + { + name: 'Value', + field: 'value', + }, + { + name: 'Type', + field: 'type', + }, + // { + // name: "Feed", + // field: "" + // }, + { + name: 'Created', + field: 'created', + render: (timestamp: number) => new Date(timestamp).toLocaleString(), + }, + { + name: 'Threat severity', + field: 'severity', + }, + { + name: 'Last updated', + field: 'modified', + render: (timestamp: number) => new Date(timestamp).toLocaleString(), + }, + ]; + + return ( + + + {iocs.length} malicious IoCs + + + + + ); +}; diff --git a/public/pages/ThreatIntel/components/SelectLogSourcesForm/SelectThreatIntelLogSourcesForm.tsx b/public/pages/ThreatIntel/components/SelectLogSourcesForm/SelectThreatIntelLogSourcesForm.tsx new file mode 100644 index 000000000..74db60617 --- /dev/null +++ b/public/pages/ThreatIntel/components/SelectLogSourcesForm/SelectThreatIntelLogSourcesForm.tsx @@ -0,0 +1,225 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiAccordion, + EuiBadge, + EuiButton, + EuiButtonEmpty, + EuiCheckbox, + EuiComboBox, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiPanel, + EuiPopover, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { IocLabel, ThreatIntelIoc } from '../../../../../common/constants'; +import React, { useState } from 'react'; +import { ThreatIntelLogSource } from '../../../../../types'; +import { Interval } from '../../../CreateDetector/components/DefineDetector/components/DetectorSchedule/Interval'; + +export interface SelectThreatIntelLogSourcesProps { + sources: ThreatIntelLogSource[]; + updateSources?: (sources: ThreatIntelLogSource[]) => void; +} + +export const SelectThreatIntelLogSources: React.FC = ({ + sources, + updateSources, +}) => { + const [selectedSources, setSelectedSources] = useState([...sources]); + const [iocWithAddFieldOpen, setIocWithAddFieldOpen] = useState< + { sourceName: string; ioc: string } | undefined + >(undefined); + + const onIocToggle = ( + source: ThreatIntelLogSource, + sourceIdx: number, + toggledIoc: ThreatIntelIoc, + enabled: boolean + ) => { + const newSources: ThreatIntelLogSource[] = [ + ...selectedSources.slice(0, sourceIdx), + { + ...source, + iocConfigMap: { + ...source.iocConfigMap, + [toggledIoc]: { + ...source.iocConfigMap[toggledIoc], + enabled: enabled, + }, + }, + }, + ...selectedSources.slice(sourceIdx + 1), + ]; + + setSelectedSources(newSources); + updateSources?.(newSources); + }; + + const onFieldAliasUpdate = ( + action: 'add' | 'remove', + source: ThreatIntelLogSource, + sourceIdx: number, + ioc: ThreatIntelIoc, + alias: string + ) => { + const aliasesSet = new Set(source.iocConfigMap[ioc].fieldAliases); + if (action === 'add') { + aliasesSet.add(alias); + } else { + aliasesSet.delete(alias); + } + + const newSources: ThreatIntelLogSource[] = [ + ...selectedSources.slice(0, sourceIdx), + { + ...source, + iocConfigMap: { + ...source.iocConfigMap, + [ioc]: { + ...source.iocConfigMap[ioc], + fieldAliases: Array.from(aliasesSet), + }, + }, + }, + ...selectedSources.slice(sourceIdx + 1), + ]; + + setSelectedSources(newSources); + updateSources?.(newSources); + }; + + return ( + + +

Select log sources

+
+ + + + + + {}} + /> + + +

Select fields to scan

+
+ +

+ To perform detection the IoC from threat intelligence feeds have to be matched against + selected fields in your data. +

+
+ + + {selectedSources.length === 0 ? ( + +

Select indexes/aliases above to view the fields.

+
+ ) : ( + selectedSources.map((source, idx) => { + const { name, iocConfigMap } = source; + return ( + <> + !config.enabled)} + paddingSize="l" + > +
+ {Object.entries(iocConfigMap).map(([ioc, config]) => ( + + + + onIocToggle(source, idx, ioc as ThreatIntelIoc, event.target.checked) + } + /> + + + + {config.fieldAliases.map((alias) => ( + + + onFieldAliasUpdate( + 'remove', + source, + idx, + ioc as ThreatIntelIoc, + alias + ) + } + > + {alias} + + + ))} + + setIocWithAddFieldOpen({ ioc, sourceName: name })} + > + Add fields + + } + panelPaddingSize="s" + closePopover={() => setIocWithAddFieldOpen(undefined)} + isOpen={ + iocWithAddFieldOpen && + iocWithAddFieldOpen.sourceName === name && + iocWithAddFieldOpen.ioc === ioc + } + > + + + + + Cancel + + + Add fields + + + + + + + + ))} +
+
+ + + ); + }) + )} +
+ ); +}; diff --git a/public/pages/ThreatIntel/components/SetupThreatIntelAlertTriggers/SetupThreatIntelAlertTriggers.tsx b/public/pages/ThreatIntel/components/SetupThreatIntelAlertTriggers/SetupThreatIntelAlertTriggers.tsx new file mode 100644 index 000000000..d5f4cbafb --- /dev/null +++ b/public/pages/ThreatIntel/components/SetupThreatIntelAlertTriggers/SetupThreatIntelAlertTriggers.tsx @@ -0,0 +1,79 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { NotificationChannelTypeOptions, ThreatIntelAlertTrigger } from '../../../../../types'; +import React, { useCallback, useEffect, useState } from 'react'; +import { ThreatIntelAlertTriggerForm } from '../ThreatIntelAlertTriggerForm/ThreatIntelAlertTriggerForm'; +import { + getNotificationChannels, + parseNotificationChannelsToOptions, +} from '../../../CreateDetector/components/ConfigureAlerts/utils/helpers'; +import { NotificationsService } from '../../../../services'; +import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; + +export interface SetupThreatIntelAlertTriggersProps { + alertTriggers: ThreatIntelAlertTrigger[]; + notificationsService: NotificationsService; + updateTriggers: (alertTriggers: ThreatIntelAlertTrigger[]) => void; +} + +export const SetupThreatIntelAlertTriggers: React.FC = ({ + alertTriggers, + notificationsService, + updateTriggers, +}) => { + const [loadingNotificationChannels, setLoadingNotificationChannels] = useState(false); + const [notificationChannels, setNotificationChannels] = useState< + NotificationChannelTypeOptions[] + >([]); + + const updateNotificationChannels = useCallback(async () => { + setLoadingNotificationChannels(true); + const channels = await getNotificationChannels(notificationsService); + const parsedChannels = parseNotificationChannelsToOptions(channels); + setNotificationChannels(parsedChannels); + setLoadingNotificationChannels(false); + }, [notificationsService]); + + useEffect(() => { + updateNotificationChannels(); + }, [notificationsService]); + + const onDeleteTrigger = (triggerIdx: number) => { + const newTriggers = [...alertTriggers].splice(triggerIdx, 1); + updateTriggers(newTriggers); + }; + + const onTriggerUpdate = (trigger: ThreatIntelAlertTrigger, triggerIdx: number) => { + const newTriggers = [ + ...alertTriggers.slice(0, triggerIdx), + trigger, + ...alertTriggers.slice(triggerIdx + 1), + ]; + + updateTriggers(newTriggers); + }; + + return ( + + +

Set up alert triggers

+
+ + {alertTriggers.map((trigger, idx) => { + return ( + onDeleteTrigger(idx)} + refreshNotificationChannels={updateNotificationChannels} + trigger={trigger} + updateTrigger={(trigger) => onTriggerUpdate(trigger, idx)} + /> + ); + })} +
+ ); +}; diff --git a/public/pages/ThreatIntel/components/ThreatIntelAlertTriggerForm/ThreatIntelAlertTriggerForm.tsx b/public/pages/ThreatIntel/components/ThreatIntelAlertTriggerForm/ThreatIntelAlertTriggerForm.tsx new file mode 100644 index 000000000..6a0ce920e --- /dev/null +++ b/public/pages/ThreatIntel/components/ThreatIntelAlertTriggerForm/ThreatIntelAlertTriggerForm.tsx @@ -0,0 +1,115 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiAccordion, + EuiButtonIcon, + EuiComboBox, + EuiComboBoxOptionOption, + EuiFieldText, + EuiFormRow, + EuiSpacer, +} from '@elastic/eui'; +import { NotificationChannelTypeOptions, ThreatIntelAlertTrigger } from '../../../../../types'; +import React from 'react'; +import { NotificationForm } from '../../../../components/Notifications/NotificationForm'; + +export interface ThreatIntelAlertTriggerProps { + allNotificationChannels: NotificationChannelTypeOptions[]; + loadingNotifications: boolean; + trigger: ThreatIntelAlertTrigger; + onDeleteTrgger: () => void; + refreshNotificationChannels: () => void; + updateTrigger: (trigger: ThreatIntelAlertTrigger) => void; +} + +export const ThreatIntelAlertTriggerForm: React.FC = ({ + allNotificationChannels, + loadingNotifications, + trigger, + onDeleteTrgger, + refreshNotificationChannels, + updateTrigger, +}) => { + const onChannelsChange = (selectedOptions: EuiComboBoxOptionOption[]) => { + updateTrigger({ + ...trigger, + action: { + ...trigger.action, + destination_id: selectedOptions[0]?.value || '', + destination_name: selectedOptions[0].label || '', + }, + }); + }; + + const onMessageSubjectChange = (subject: string) => { + updateTrigger({ + ...trigger, + action: { + ...trigger.action, + name: subject, + subject_template: { + ...trigger.action.subject_template, + source: subject, + }, + }, + }); + }; + + const onMessageBodyChange = (message: string) => { + updateTrigger({ + ...trigger, + action: { + ...trigger.action, + message_template: { + ...trigger.action.message_template, + source: message, + }, + }, + }); + }; + + const prepareMessage = () => {}; + + return ( + } + paddingSize="l" + > + + + + + + + + + + + + + + + + + ); +}; diff --git a/public/pages/ThreatIntel/components/ThreatIntelAlertTriggers/ThreatIntelAlertTriggers.tsx b/public/pages/ThreatIntel/components/ThreatIntelAlertTriggers/ThreatIntelAlertTriggers.tsx new file mode 100644 index 000000000..8b8a8742b --- /dev/null +++ b/public/pages/ThreatIntel/components/ThreatIntelAlertTriggers/ThreatIntelAlertTriggers.tsx @@ -0,0 +1,152 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiAccordion, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { ThreatIntelAlertTrigger } from '../../../../../types'; +import React, { useCallback } from 'react'; +import { IocLabel } from '../../../../../common/constants'; +import { + DescriptionGroup, + DescriptionGroupProps, +} from '../../../../components/Utility/DescriptionGroup'; +import { getThreatIntelALertSeverityLabel } from '../../utils/helpers'; +import { AlertSeverity } from '../../../Alerts/utils/constants'; +import { ConfigActionButton } from '../Utility/ConfigActionButton'; + +export interface ThreatIntelAlertTriggersProps { + triggers: ThreatIntelAlertTrigger[]; + threatIntelSourceCount: number; + scanConfigActionHandler: () => void; +} + +export const ThreatIntelAlertTriggers: React.FC = ({ + triggers, + threatIntelSourceCount, + scanConfigActionHandler, +}) => { + const getTriggerConditionDetails = ( + triggerCondtion: ThreatIntelAlertTrigger['triggerCondition'] + ): DescriptionGroupProps['listItems'] => { + return [ + { + title: 'Indicator type', + description: triggerCondtion.indicatorType.join(', ') || 'Any', + }, + { + title: 'Log source', + description: triggerCondtion.dataSource.join(', ') || 'Any', + }, + ]; + }; + + const getNotificationConfig = ( + alertSeverity: AlertSeverity, + triggerAction: ThreatIntelAlertTrigger['action'] + ): DescriptionGroupProps['listItems'] => { + return [ + { + title: 'Severity', + description: getThreatIntelALertSeverityLabel(alertSeverity), + }, + { + title: 'Notification channel', + description: triggerAction.destination_name, + }, + ]; + }; + + const createTitle = useCallback((text: string) => { + return ( + <> + +

{text}

+
+ + + ); + }, []); + + return ( + + + + + +

Log sources

+
+
+ + 0 && triggers.length ? 'edit' : 'configure'} + actionHandler={scanConfigActionHandler} + disabled={threatIntelSourceCount === 0} + /> + +
+ + {triggers.map(({ action, alertSeverity, name, triggerCondition }, idx) => { + const logSourcesCount = + triggerCondition.dataSource.length === 0 + ? 'Any' + : triggerCondition.dataSource.length.toString(); + const iocTriggerCondition = + triggerCondition.indicatorType.length === 0 + ? 'All types of indicators' + : triggerCondition.indicatorType.map((iocType) => IocLabel[iocType]).join(', '); + + return ( + + +

{name}

+
+ {`${logSourcesCount} log sources, ${iocTriggerCondition}`} + + } + paddingSize="m" + > + + ), + }, + { + title: createTitle('Notification'), + description: ( + + ), + }, + ]} + /> +
+ ); + })} +
+ ); +}; diff --git a/public/pages/ThreatIntel/components/ThreatIntelLogSources/ThreatIntelLogSources.tsx b/public/pages/ThreatIntel/components/ThreatIntelLogSources/ThreatIntelLogSources.tsx new file mode 100644 index 000000000..f056fa117 --- /dev/null +++ b/public/pages/ThreatIntel/components/ThreatIntelLogSources/ThreatIntelLogSources.tsx @@ -0,0 +1,91 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiBasicTableColumn, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiInMemoryTable, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { ThreatIntelLogSource } from '../../../../../types'; +import React, { useMemo } from 'react'; +import { ConfigActionButton } from '../Utility/ConfigActionButton'; + +export interface ThreatIntelLogSourcesProps { + logSources: ThreatIntelLogSource[]; + threatIntelSourceCount: number; + scanConfigActionHandler: () => void; +} + +export const ThreatIntelLogSources: React.FC = ({ + logSources, + threatIntelSourceCount, + scanConfigActionHandler, +}) => { + const columns: EuiBasicTableColumn[] = useMemo( + () => [ + { + field: 'name', + name: 'Index/Alias', + render: (name: string) => {name}, + }, + { + name: 'Indicator types', + render: ({ iocConfigMap }: ThreatIntelLogSource) => { + return Object.entries(iocConfigMap) + .filter(([_ioc, config]) => config.enabled) + .map(([ioc]) => ioc) + .join(', '); + }, + }, + { + name: 'Actions', + actions: [ + { + name: 'Inspect', + render: (item) => { + return ; + }, + }, + ], + }, + ], + [] + ); + + return ( + + + + + +

Log sources

+
+ + + + To perform detection the IoC from threat intelligence feeds have to be matched against + selected fields in your data. + + +
+ + 0 && logSources.length ? 'edit' : 'configure'} + actionHandler={scanConfigActionHandler} + disabled={threatIntelSourceCount === 0} + /> + +
+ + +
+ ); +}; diff --git a/public/pages/ThreatIntel/components/ThreatIntelOverviewActions/ThreatIntelOverviewActions.tsx b/public/pages/ThreatIntel/components/ThreatIntelOverviewActions/ThreatIntelOverviewActions.tsx new file mode 100644 index 000000000..cf33f2f45 --- /dev/null +++ b/public/pages/ThreatIntel/components/ThreatIntelOverviewActions/ThreatIntelOverviewActions.tsx @@ -0,0 +1,122 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { StatusWithIndicator } from '../../../../components/Utility/StatusWithIndicator'; +import { RouteComponentProps } from 'react-router-dom'; +import { ROUTES } from '../../../../utils/constants'; +import { ThreatIntelScanConfig } from '../../../../../types'; +import { ConfigureThreatIntelScanStep } from '../../utils/constants'; + +export interface ThreatIntelOverviewActionsProps { + sourceCount: number; + scanConfig: ThreatIntelScanConfig; + history: RouteComponentProps['history']; +} + +const statusByScanState = { + notRunning: , + running: , + stopped: , +}; + +export const ThreatIntelOverviewActions: React.FC = ({ + sourceCount, + scanConfig, + history, +}) => { + const scanIsSetup = scanConfig.logSources.length > 0; + const scanRunning = scanConfig.isRunning; + const alertTriggerSetup = scanConfig.triggers.length > 0; + + let status: React.ReactNode = null; + let actions = []; + + if (sourceCount === 0) { + status = statusByScanState['notRunning']; + actions.push({ + label: 'Configure scan', + disabled: true, + fill: true, + onClick: () => {}, + }); + } else if (!scanIsSetup) { + status = statusByScanState['notRunning']; + actions.push({ + label: 'Configure scan', + disabled: false, + fill: true, + onClick: () => { + history.push({ + pathname: ROUTES.THREAT_INTEL_SCAN_CONFIG, + }); + }, + }); + } else if (scanRunning) { + status = statusByScanState['running']; + actions.push({ + label: 'View findings', + disabled: false, + fill: false, + onClick: () => { + history.push({ + pathname: ROUTES.FINDINGS, + search: '?detectionType=Threat intelligence', + }); + }, + }); + + if (!alertTriggerSetup) { + actions.push({ + label: 'Set up alerts', + disabled: false, + fill: true, + onClick: () => { + history.push({ + pathname: ROUTES.THREAT_INTEL_SCAN_CONFIG, + state: { + scanConfig, + step: ConfigureThreatIntelScanStep.SetupAlertTriggers, + }, + }); + }, + }); + } else { + actions.push({ + label: 'View alerts', + disabled: false, + fill: false, + onClick: () => { + history.push({ + pathname: ROUTES.ALERTS, + search: '?detectionType=Threat intelligence', + }); + }, + }); + } + } else { + status = statusByScanState['stopped']; + actions.push({ + label: 'Start scan', + disabled: false, + fill: true, + onClick: () => {}, + }); + } + + return ( + + {status} + {actions.map((action, idx) => ( + + + {action.label} + + + ))} + + ); +}; diff --git a/public/pages/ThreatIntel/components/ThreatIntelSourceDetails/ThreatIntelSourceDetails.tsx b/public/pages/ThreatIntel/components/ThreatIntelSourceDetails/ThreatIntelSourceDetails.tsx new file mode 100644 index 000000000..a4b3bd947 --- /dev/null +++ b/public/pages/ThreatIntel/components/ThreatIntelSourceDetails/ThreatIntelSourceDetails.tsx @@ -0,0 +1,126 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useState } from 'react'; +import { + EuiBottomBar, + EuiButton, + EuiCheckboxGroup, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiPanel, + EuiSpacer, + EuiSwitch, + EuiTitle, +} from '@elastic/eui'; +import { Interval } from '../../../CreateDetector/components/DefineDetector/components/DetectorSchedule/Interval'; + +export interface ThreatIntelSourceDetailsProps {} + +export const ThreatIntelSourceDetails: React.FC = (props) => { + const [isReadOnly, setIsReadOnly] = useState(true); + const [onDemandChecked, setOnDemandChecked] = useState(false); + const checkboxes = [ + { + id: `ip`, + label: 'IP - addresses', + }, + { + id: `domain`, + label: 'Domains', + }, + { + id: `file_hash`, + label: 'File hash', + }, + ]; + const [checkboxIdToSelectedMap, setCheckboxIdToSelectedMap] = useState>( + {} + ); + + const onChange = (optionId: string) => { + const newCheckboxIdToSelectedMap = { + ...checkboxIdToSelectedMap, + ...{ + [optionId]: !checkboxIdToSelectedMap[optionId], + }, + }; + setCheckboxIdToSelectedMap(newCheckboxIdToSelectedMap); + }; + + return ( + <> + + + + +

Threat intel source details

+
+
+ + + + + + + <> + + setOnDemandChecked(event.target.checked)} + disabled={isReadOnly} + /> + + {!onDemandChecked && ( + <> + {}} + readonly={isReadOnly} + /> + + + )} + + + + + + + + + setIsReadOnly(false)} + > + Edit + + +
+
+ {!isReadOnly && ( + + + + setIsReadOnly(true)}>Discard + + + Save + + + + )} + + ); +}; diff --git a/public/pages/ThreatIntel/components/ThreatIntelSourcesList/ThreatIntelSourcesList.tsx b/public/pages/ThreatIntel/components/ThreatIntelSourcesList/ThreatIntelSourcesList.tsx new file mode 100644 index 000000000..dfe98a53e --- /dev/null +++ b/public/pages/ThreatIntel/components/ThreatIntelSourcesList/ThreatIntelSourcesList.tsx @@ -0,0 +1,100 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { + EuiBadge, + EuiButton, + EuiButtonEmpty, + EuiCard, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiPanel, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import { ROUTES } from '../../../../utils/constants'; +import { ThreatIntelSourceItem } from '../../../../../types'; +import { RouteComponentProps } from 'react-router-dom'; + +export interface ThreatIntelSourcesListProps { + threatIntelSources: ThreatIntelSourceItem[]; + history: RouteComponentProps['history']; +} + +export const ThreatIntelSourcesList: React.FC = ({ + threatIntelSources, + history, +}) => { + return ( + + + + + +

Threat intelligence sources ({threatIntelSources.length})

+
+
+ + { + history.push({ + pathname: ROUTES.THREAT_INTEL_ADD_CUSTOM_SOURCE, + }); + }} + > + Add threat intel source + + +
+ + + {threatIntelSources.map((source) => { + return ( + + + { + history.push({ + pathname: `${ROUTES.THREAT_INTEL_SOURCE_DETAILS}/hello`, + }); + }} + iconType={'iInCircle'} + > + More details + + + {source.isEnabled ? ( + <> + {' '} + Active + + ) : ( + Activate + )} + + } + > + {source.iocTypes.map((iocType) => ( + {iocType} + ))} + + + ); + })} + +
+ ); +}; diff --git a/public/pages/ThreatIntel/components/Utility/ConfigActionButton.tsx b/public/pages/ThreatIntel/components/Utility/ConfigActionButton.tsx new file mode 100644 index 000000000..83b9f9992 --- /dev/null +++ b/public/pages/ThreatIntel/components/Utility/ConfigActionButton.tsx @@ -0,0 +1,38 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiButton } from '@elastic/eui'; +import React from 'react'; + +export interface ConfigActionButtonProps { + action: 'configure' | 'edit'; + actionHandler: () => void; + disabled?: boolean; +} + +export const ConfigActionButton: React.FC = ({ + action, + disabled, + actionHandler, +}) => { + let buttonText; + + switch (action) { + case 'configure': + buttonText = 'Configure scan'; + break; + case 'edit': + buttonText = 'Edit scan configuration'; + break; + default: + buttonText = ''; + } + + return buttonText ? ( + + {buttonText} + + ) : null; +}; diff --git a/public/pages/ThreatIntel/containers/ConnectThreatIntelSource.tsx b/public/pages/ThreatIntel/containers/AddThreatIntelSource/AddThreatIntelSource.tsx similarity index 76% rename from public/pages/ThreatIntel/containers/ConnectThreatIntelSource.tsx rename to public/pages/ThreatIntel/containers/AddThreatIntelSource/AddThreatIntelSource.tsx index 44ae2df08..b2b629db1 100644 --- a/public/pages/ThreatIntel/containers/ConnectThreatIntelSource.tsx +++ b/public/pages/ThreatIntel/containers/AddThreatIntelSource/AddThreatIntelSource.tsx @@ -18,16 +18,16 @@ import { EuiTitle, htmlIdGenerator, } from '@elastic/eui'; -import { CoreServicesContext } from '../../../components/core_services'; -import { BREADCRUMBS, ROUTES } from '../../../utils/constants'; -import { Interval } from '../../CreateDetector/components/DefineDetector/components/DetectorSchedule/Interval'; +import { CoreServicesContext } from '../../../../components/core_services'; +import { BREADCRUMBS, ROUTES } from '../../../../utils/constants'; +import { Interval } from '../../../CreateDetector/components/DefineDetector/components/DetectorSchedule/Interval'; import { RouteComponentProps } from 'react-router-dom'; const idPrefix = htmlIdGenerator()(); -export interface ConnectThreatIntelSourceProps extends RouteComponentProps {} +export interface AddThreatIntelSourceProps extends RouteComponentProps {} -export const ConnectThreatIntelSource: React.FC = ({ history }) => { +export const AddThreatIntelSource: React.FC = ({ history }) => { const context = useContext(CoreServicesContext); const [onDemandChecked, setOnDemandChecked] = useState(false); const checkboxes = [ @@ -61,7 +61,8 @@ export const ConnectThreatIntelSource: React.FC = useEffect(() => { context?.chrome.setBreadcrumbs([ BREADCRUMBS.SECURITY_ANALYTICS, - BREADCRUMBS.THREAT_INTEL_CONNECT_CUSTOM_SOURCE, + BREADCRUMBS.THREAT_INTEL_OVERVIEW, + BREADCRUMBS.THREAT_INTEL_ADD_CUSTOM_SOURCE, ]); }, []); @@ -69,7 +70,7 @@ export const ConnectThreatIntelSource: React.FC = <> -

Connect custom threat intelligence source

+

Add custom threat intelligence source

@@ -91,14 +92,6 @@ export const ConnectThreatIntelSource: React.FC =
- - - -

Connection details

@@ -111,14 +104,7 @@ export const ConnectThreatIntelSource: React.FC =
- - {'S3 object key - '} - optional - - } - > + @@ -144,6 +130,16 @@ export const ConnectThreatIntelSource: React.FC = )} + +

Types of malicious indicators

+
+ + + @@ -151,7 +147,7 @@ export const ConnectThreatIntelSource: React.FC = history.push(ROUTES.THREAT_INTEL_OVERVIEW)}>Cancel - Connect threat intel source + Add threat intel source diff --git a/public/pages/ThreatIntel/containers/Overview/ThreatIntelOverview.tsx b/public/pages/ThreatIntel/containers/Overview/ThreatIntelOverview.tsx new file mode 100644 index 000000000..9e7e0a7be --- /dev/null +++ b/public/pages/ThreatIntel/containers/Overview/ThreatIntelOverview.tsx @@ -0,0 +1,174 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiAccordion, + EuiButton, + EuiCard, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiSpacer, + EuiTabbedContent, + EuiTabbedContentTab, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import React, { MouseEventHandler, useContext, useEffect, useMemo } from 'react'; +import { CoreServicesContext } from '../../../../components/core_services'; +import { BREADCRUMBS, ROUTES } from '../../../../utils/constants'; +import { useState } from 'react'; +import { + ThreatIntelNextStepId, + ThreatIntelScanConfig, + ThreatIntelSourceItem, + dummyLogSource, + dummySource, +} from '../../../../../types'; +import { RouteComponentProps } from 'react-router-dom'; +import { ThreatIntelSourcesList } from '../../components/ThreatIntelSourcesList/ThreatIntelSourcesList'; +import { ThreatIntelLogSources } from '../../components/ThreatIntelLogSources/ThreatIntelLogSources'; +import { getEmptyThreatIntelAlertTrigger, getThreatIntelNextStepsProps } from '../../utils/helpers'; +import { ThreatIntelAlertTriggers } from '../../components/ThreatIntelAlertTriggers/ThreatIntelAlertTriggers'; +import { ThreatIntelOverviewActions } from '../../components/ThreatIntelOverviewActions/ThreatIntelOverviewActions'; + +export interface ThreatIntelOverviewProps extends RouteComponentProps {} + +export const ThreatIntelOverview: React.FC = ({ history }) => { + const context = useContext(CoreServicesContext); + const [threatIntelSources, setThreatIntelSources] = useState([ + dummySource, + ]); + const [scanConfig, setScanConfig] = useState({ + isRunning: true, + logSources: threatIntelSources.length ? [dummyLogSource] : [], + triggers: [getEmptyThreatIntelAlertTrigger()], + }); + + const onEditScanConfig = () => { + history.push({ + pathname: `${ROUTES.THREAT_INTEL_SCAN_CONFIG}`, + state: { scanConfig }, + }); + }; + + const tabs: EuiTabbedContentTab[] = useMemo( + () => [ + { + id: 'threat-intel-sources', + name: Threat intel sources ({threatIntelSources.length}), + content: ( + + ), + }, + { + id: 'log-sources', + name: Log sources, + content: ( + + ), + }, + { + id: 'alert-triggers', + name: Alert triggers, + content: ( + + ), + }, + ], + [scanConfig, threatIntelSources] + ); + + useEffect(() => { + context?.chrome.setBreadcrumbs([ + BREADCRUMBS.SECURITY_ANALYTICS, + BREADCRUMBS.THREAT_INTEL_OVERVIEW, + ]); + }, []); + + const nextStepClickHandlerById: Record = { + ['add-source']: () => { + history.push({ + pathname: ROUTES.THREAT_INTEL_ADD_CUSTOM_SOURCE, + }); + }, + ['configure-scan']: () => { + history.push({ + pathname: ROUTES.THREAT_INTEL_SCAN_CONFIG, + state: { scanConfig }, + }); + }, + }; + + const threatIntelNextStepsProps = getThreatIntelNextStepsProps( + threatIntelSources.length > 0, + scanConfig.logSources.length > 0 + ); + + return ( + <> + + + +

Threat intelligence

+
+
+ + + +
+ + + + Scan log data for indicators of compromise from threat intel data streams to identify + malicious actors and security threats.{' '} + + Learn more + + . + + + + + + + {threatIntelNextStepsProps.map( + ({ id, title, description, footerButtonProps: { text, disabled } }) => ( + + + {text} + + } + /> + + ) + )} + + + + + + ); +}; diff --git a/public/pages/ThreatIntel/containers/ScanConfiguration/ThreatIntelScanConfigForm.tsx b/public/pages/ThreatIntel/containers/ScanConfiguration/ThreatIntelScanConfigForm.tsx new file mode 100644 index 000000000..9e2fca3ed --- /dev/null +++ b/public/pages/ThreatIntel/containers/ScanConfiguration/ThreatIntelScanConfigForm.tsx @@ -0,0 +1,192 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useContext, useEffect, useState } from 'react'; +import { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiSteps, + EuiTitle, +} from '@elastic/eui'; +import { BREADCRUMBS, PLUGIN_NAME, ROUTES } from '../../../../utils/constants'; +import { CoreServicesContext } from '../../../../components/core_services'; +import { SelectThreatIntelLogSources } from '../../components/SelectLogSourcesForm/SelectThreatIntelLogSourcesForm'; +import { + ThreatIntelScanConfig, + ThreatIntelAlertTrigger, + ThreatIntelLogSource, +} from '../../../../../types'; +import { SetupThreatIntelAlertTriggers } from '../../components/SetupThreatIntelAlertTriggers/SetupThreatIntelAlertTriggers'; +import { NotificationsService } from '../../../../services'; +import { getEmptyThreatIntelAlertTrigger } from '../../utils/helpers'; +import { RouteComponentProps } from 'react-router-dom'; +import { ConfigureThreatIntelScanStep } from '../../utils/constants'; + +export interface ThreatIntelScanConfigFormProps + extends RouteComponentProps< + any, + any, + { scanConfig?: ThreatIntelScanConfig; step?: ConfigureThreatIntelScanStep } + > { + notificationsService: NotificationsService; +} + +export const ThreatIntelScanConfigForm: React.FC = ({ + notificationsService, + location, +}) => { + const isEdit = !!location.state?.scanConfig?.logSources?.length; + const context = useContext(CoreServicesContext); + const [currentStep, setCurrentStep] = useState( + location.state?.step ?? ConfigureThreatIntelScanStep.SelectLogSources + ); + const [configureInProgress, setConfigureInProgress] = useState(false); + const [stepDataValid, setStepDataValid] = useState({ + [ConfigureThreatIntelScanStep.SelectLogSources]: true, + [ConfigureThreatIntelScanStep.SetupAlertTriggers]: false, + }); + const [configureScanPayload, setConfigureScanPayload] = useState(() => { + return { + isRunning: location.state?.scanConfig?.isRunning ?? false, + logSources: location.state?.scanConfig?.logSources ?? [], + triggers: !!location.state?.scanConfig?.triggers.length + ? location.state?.scanConfig?.triggers + : [getEmptyThreatIntelAlertTrigger()], + }; + }); + + useEffect(() => { + context?.chrome.setBreadcrumbs([ + BREADCRUMBS.SECURITY_ANALYTICS, + BREADCRUMBS.THREAT_INTEL_OVERVIEW, + isEdit + ? BREADCRUMBS.THREAT_INTEL_EDIT_SCAN_CONFIG + : BREADCRUMBS.THREAT_INTEL_SETUP_SCAN_CONFIG, + ]); + }, []); + + const onSourcesChange = (logSources: ThreatIntelLogSource[]): void => { + setConfigureScanPayload({ + ...configureScanPayload, + logSources, + }); + }; + + const onTriggersChange = (triggers: ThreatIntelAlertTrigger[]) => { + setConfigureScanPayload({ + ...configureScanPayload, + triggers, + }); + }; + + const getStepConent = (step: ConfigureThreatIntelScanStep) => { + switch (step) { + case ConfigureThreatIntelScanStep.SelectLogSources: + return ( + + ); + case ConfigureThreatIntelScanStep.SetupAlertTriggers: + return ( + + ); + default: + return null; + } + }; + + const onPreviousClick = () => { + setCurrentStep(ConfigureThreatIntelScanStep.SelectLogSources); + }; + + const onNextClick = () => { + setCurrentStep(ConfigureThreatIntelScanStep.SetupAlertTriggers); + }; + + const onSubmit = () => { + setConfigureInProgress(true); + }; + + return ( + <> + + + , + }, + { + title: 'Set up alert triggers', + status: + currentStep === ConfigureThreatIntelScanStep.SetupAlertTriggers + ? undefined + : 'disabled', + children: <>, + }, + ]} + /> + + + +

{isEdit ? 'Edit' : 'Set up'} real-time scan

+
+ + {getStepConent(currentStep)} +
+
+ + + + + Cancel + + + + {currentStep === ConfigureThreatIntelScanStep.SelectLogSources && ( + + + Next + + + )} + + {currentStep === ConfigureThreatIntelScanStep.SetupAlertTriggers && ( + <> + + + Back + + + + + Save and start monitoring + + + + )} + + + ); +}; diff --git a/public/pages/ThreatIntel/containers/ThreatIntelOverview.tsx b/public/pages/ThreatIntel/containers/ThreatIntelOverview.tsx deleted file mode 100644 index 0c998f581..000000000 --- a/public/pages/ThreatIntel/containers/ThreatIntelOverview.tsx +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - EuiAccordion, - EuiBadge, - EuiButton, - EuiCard, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiLink, - EuiPanel, - EuiSpacer, - EuiTabbedContent, - EuiTabbedContentTab, - EuiText, - EuiTitle, -} from '@elastic/eui'; -import React, { MouseEventHandler, useContext, useEffect } from 'react'; -import { CoreServicesContext } from '../../../components/core_services'; -import { BREADCRUMBS, ROUTES } from '../../../utils/constants'; -import { useState } from 'react'; -import { threatIntelNextStepsProps } from '../utils/contants'; -import { ThreatIntelNextStepId, ThreatIntelSourceItem, dummySource } from '../../../../types'; -import { RouteComponentProps } from 'react-router-dom'; - -export interface ThreatIntelOverviewProps extends RouteComponentProps {} - -export const ThreatIntelOverview: React.FC = ({ history }) => { - const context = useContext(CoreServicesContext); - const [showNextSteps, setShowNextSteps] = useState(true); - const [threatIntelSources, setThreatIntelSources] = useState([ - dummySource, - ]); - - useEffect(() => { - context?.chrome.setBreadcrumbs([ - BREADCRUMBS.SECURITY_ANALYTICS, - BREADCRUMBS.THREAT_INTEL_OVERVIEW, - ]); - }, []); - - const nextStepClickHandlerById: Record = { - ['connect']: () => {}, - ['configure-scan']: () => {}, - }; - - const tabs: EuiTabbedContentTab[] = [ - { - id: 'threat-intel-sources', - name: Threat intel sources ({threatIntelSources.length}), - content: ( - <> - - -

Threat intelligence sources ({threatIntelSources.length})

-
- - - - - - - Connect to your custom threat intel in Amazon S3 - - { - history.push({ - pathname: ROUTES.THREAT_INTEL_CONNECT_CUSTOM_SOURCE, - }); - }} - > - Connect threat intel source - - - - - - {threatIntelSources.map((source) => { - return ( - - - View details - - {' '} - Connected - - } - > - {source.iocTypes.map((iocType) => ( - {iocType} - ))} - - - ); - })} - - - ), - }, - ]; - - return ( - <> - -

Threat intelligence

-
- - - Scan log data for indicators of compromise from threat intel data streams to identify - malicious actors and security threats.{' '} - - Learn more - - . - - - - - - - {threatIntelNextStepsProps.map(({ id, title, description, footerButtonText }) => ( - - {footerButtonText} - } - /> - - ))} - - - - - - - - ); -}; diff --git a/public/pages/ThreatIntel/containers/ThreatIntelSource/ThreatIntelSource.tsx b/public/pages/ThreatIntel/containers/ThreatIntelSource/ThreatIntelSource.tsx new file mode 100644 index 000000000..a69c41e78 --- /dev/null +++ b/public/pages/ThreatIntel/containers/ThreatIntelSource/ThreatIntelSource.tsx @@ -0,0 +1,132 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useContext, useMemo, useState } from 'react'; +import { ThreatIntelSourceItem, dummySource } from '../../../../../types'; +import { RouteComponentProps } from 'react-router-dom'; +import { BREADCRUMBS, ROUTES } from '../../../../utils/constants'; +import { useEffect } from 'react'; +import { CoreServicesContext } from '../../../../components/core_services'; +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiLoadingContent, + EuiPanel, + EuiSpacer, + EuiTabbedContent, + EuiTabbedContentTab, + EuiTitle, +} from '@elastic/eui'; +import { DescriptionGroup } from '../../../../components/Utility/DescriptionGroup'; +import { IoCstable } from '../../components/IoCsTable/IoCsTable'; +import { ThreatIntelSourceDetails } from '../../components/ThreatIntelSourceDetails/ThreatIntelSourceDetails'; + +export interface ThreatIntelSource + extends RouteComponentProps {} + +export const ThreatIntelSource: React.FC = ({ location: { state } }) => { + const context = useContext(CoreServicesContext); + const [source, setSource] = useState(state?.source); + + useEffect(() => { + const sourceId = location.pathname.replace(`${ROUTES.THREAT_INTEL_SOURCE_DETAILS}/`, ''); + + if (sourceId) { + const getSource = async () => { + setSource(dummySource); + }; + + getSource(); + } + }, []); + + useEffect(() => { + const baseCrumbs = [BREADCRUMBS.SECURITY_ANALYTICS, BREADCRUMBS.THREAT_INTEL_OVERVIEW]; + + context?.chrome.setBreadcrumbs( + !source + ? baseCrumbs + : [...baseCrumbs, BREADCRUMBS.THREAT_INTEL_SOURCE_DETAILS(source.feedName, source.id)] + ); + }, [source]); + + const tabs: EuiTabbedContentTab[] = useMemo( + () => [ + { + id: 'iocs', + name: Indicators of Compromise, + content: , + }, + { + id: 'source-details', + name: Source details, + content: , + }, + ], + [] + ); + + if (!source) { + return ; + } + + const { feedName, isEnabled, description } = source; + + return ( + <> + + + +

{feedName}

+
+
+ + + + + {' '} + {isEnabled ? 'Active' : 'Inactive'} + + + + {isEnabled ? 'Deactivate' : 'Activate'} + + + Refresh + + + +
+ + + + + + + + + + ); +}; diff --git a/public/pages/ThreatIntel/utils/constants.ts b/public/pages/ThreatIntel/utils/constants.ts new file mode 100644 index 000000000..768120e11 --- /dev/null +++ b/public/pages/ThreatIntel/utils/constants.ts @@ -0,0 +1,9 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export enum ConfigureThreatIntelScanStep { + SelectLogSources = 'SelectLogSources', + SetupAlertTriggers = 'SetupAlertTriggers', +} diff --git a/public/pages/ThreatIntel/utils/contants.ts b/public/pages/ThreatIntel/utils/contants.ts deleted file mode 100644 index b0f9fbcca..000000000 --- a/public/pages/ThreatIntel/utils/contants.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { ThreatIntelNextStepCardProps } from '../../../../types'; - -export const threatIntelNextStepsProps: ThreatIntelNextStepCardProps[] = [ - { - id: 'connect', - title: '1. Connect to threat intel sources', - description: 'Connect threat intel sources to get started', - footerButtonText: 'Manage threat intel sources', - }, - { - id: 'configure-scan', - title: '2. Set up the scan for your log sources', - description: 'Select log sources for scan and get alerted on security threats', - footerButtonText: 'Configure scan', - }, -]; diff --git a/public/pages/ThreatIntel/utils/helpers.ts b/public/pages/ThreatIntel/utils/helpers.ts new file mode 100644 index 000000000..783725e2b --- /dev/null +++ b/public/pages/ThreatIntel/utils/helpers.ts @@ -0,0 +1,79 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DEFAULT_EMPTY_DATA } from '../../../utils/constants'; +import { + ThreatIntelAlertTrigger, + ThreatIntelNextStepCardProps, + TriggerAction, +} from '../../../../types'; +import { AlertSeverity } from '../../Alerts/utils/constants'; +import { ALERT_SEVERITY_OPTIONS } from '../../CreateDetector/components/ConfigureAlerts/utils/constants'; + +export function getEmptyThreatIntelAlertTrigger(): ThreatIntelAlertTrigger { + const emptyTriggerAction: TriggerAction = { + id: '', + name: '', + destination_id: '', + subject_template: { + source: '', + lang: 'mustache', + }, + message_template: { + source: '', + lang: 'mustache', + }, + throttle_enabled: false, + throttle: { + value: 10, + unit: 'MINUTES', + }, + }; + + return { + name: 'Trigger 1', + triggerCondition: { + indicatorType: [], + dataSource: [], + }, + alertSeverity: AlertSeverity.ONE, + action: { + ...emptyTriggerAction, + destination_name: '', + }, + }; +} + +export function getThreatIntelALertSeverityLabel(severity: AlertSeverity) { + return ( + Object.values(ALERT_SEVERITY_OPTIONS).find((option) => option.value === severity)?.label || + DEFAULT_EMPTY_DATA + ); +} + +export function getThreatIntelNextStepsProps( + logSourceAdded: boolean, + isScanSetup: boolean +): ThreatIntelNextStepCardProps[] { + return [ + { + id: 'add-source', + title: '1. Setup threat intel sources', + description: 'Add/activate threat intel source(s) to get started', + footerButtonProps: { + text: 'Add threat intel source', + }, + }, + { + id: 'configure-scan', + title: '2. Set up the scan for your log sources', + description: 'Select log sources for scan and get alerted on security threats', + footerButtonProps: { + text: isScanSetup ? 'Edit scan configuration' : 'Configure scan', + disabled: !logSourceAdded, + }, + }, + ]; +} diff --git a/public/security_analytics_app.tsx b/public/security_analytics_app.tsx index 6bc6cd83b..598d9dbaa 100644 --- a/public/security_analytics_app.tsx +++ b/public/security_analytics_app.tsx @@ -34,6 +34,8 @@ import MetricsService from './services/MetricsService'; import { MetricsContext } from './metrics/MetricsContext'; import { CHANNEL_TYPES } from './pages/CreateDetector/components/ConfigureAlerts/utils/constants'; import { DataSourceManagementPluginSetup } from '../../../src/plugins/data_source_management/public'; +import { getPlugins, setIsNotificationPluginInstalled } from './utils/helpers'; +import { OS_NOTIFICATION_PLUGIN } from './utils/constants'; export function renderApp( coreStart: CoreStart, @@ -107,6 +109,10 @@ export function renderApp( CHANNEL_TYPES.splice(0, CHANNEL_TYPES.length, ...response.response); } }); + + getPlugins(opensearchService).then((plugins): void => { + setIsNotificationPluginInstalled(plugins.includes(OS_NOTIFICATION_PLUGIN)); + }); }); return () => ReactDOM.unmountComponentAtNode(params.element); diff --git a/public/services/OpenSearchService.ts b/public/services/OpenSearchService.ts index a3916d61f..cea1d039f 100644 --- a/public/services/OpenSearchService.ts +++ b/public/services/OpenSearchService.ts @@ -18,7 +18,7 @@ export default class OpenSearchService { private savedObjectsClient: SavedObjectsClientContract ) {} - getPlugins = async (): Promise> => { + getPlugins = async (): Promise> => { let url = `..${API.PLUGINS}`; return await this.httpClient.get(url, { query: { diff --git a/public/utils/constants.ts b/public/utils/constants.ts index cb248375a..d10632a3a 100644 --- a/public/utils/constants.ts +++ b/public/utils/constants.ts @@ -50,7 +50,9 @@ export const ROUTES = Object.freeze({ LOG_TYPES: '/log-types', LOG_TYPES_CREATE: '/create-log-type', THREAT_INTEL_OVERVIEW: '/threat-intel', - THREAT_INTEL_CONNECT_CUSTOM_SOURCE: '/connect-threat-intel-source', + THREAT_INTEL_ADD_CUSTOM_SOURCE: '/add-threat-intel-source', + THREAT_INTEL_SCAN_CONFIG: '/scan-config', + THREAT_INTEL_SOURCE_DETAILS: '/threat-intel-source', get LANDING_PAGE(): string { return this.OVERVIEW; @@ -90,10 +92,22 @@ export const BREADCRUMBS = Object.freeze({ LOG_TYPES: { text: 'Log types', href: `#${ROUTES.LOG_TYPES}` }, LOG_TYPE_CREATE: { text: 'Create log type', href: `#${ROUTES.LOG_TYPES_CREATE}` }, THREAT_INTEL_OVERVIEW: { text: 'Threat intelligence', href: `#${ROUTES.THREAT_INTEL_OVERVIEW}` }, - THREAT_INTEL_CONNECT_CUSTOM_SOURCE: { - text: 'Connect threat intel source', - href: `#${ROUTES.THREAT_INTEL_CONNECT_CUSTOM_SOURCE}`, + THREAT_INTEL_ADD_CUSTOM_SOURCE: { + text: 'Add threat intel source', + href: `#${ROUTES.THREAT_INTEL_ADD_CUSTOM_SOURCE}`, }, + THREAT_INTEL_SETUP_SCAN_CONFIG: { + text: 'Setup real-time scan', + href: `#${ROUTES.THREAT_INTEL_SCAN_CONFIG}`, + }, + THREAT_INTEL_EDIT_SCAN_CONFIG: { + text: 'Edit real-time scan', + href: `#${ROUTES.THREAT_INTEL_SCAN_CONFIG}`, + }, + THREAT_INTEL_SOURCE_DETAILS: (name: string, id: string) => ({ + text: `${name}`, + href: `#${ROUTES.THREAT_INTEL_SOURCE_DETAILS}/${id}`, + }), }); export enum SortDirection { diff --git a/public/utils/helpers.tsx b/public/utils/helpers.tsx index f8566dfd0..535ae132b 100644 --- a/public/utils/helpers.tsx +++ b/public/utils/helpers.tsx @@ -31,8 +31,8 @@ import { RuleItemInfo, } from '../pages/CreateDetector/components/DefineDetector/components/DetectionRules/types/interfaces'; import { compile, TopLevelSpec } from 'vega-lite'; -import { parse, View } from 'vega/build-es5/vega.js'; -import { expressionInterpreter as vegaExpressionInterpreter } from 'vega-interpreter/build/vega-interpreter'; +import { parse, View } from 'vega'; +import { expressionInterpreter as vegaExpressionInterpreter } from 'vega-interpreter'; import { RuleInfo } from '../../server/models/interfaces'; import { NotificationsStart } from 'opensearch-dashboards/public'; import { IndexService, OpenSearchService } from '../services'; @@ -237,7 +237,7 @@ export function renderVisualization(spec: TopLevelSpec, containerId: string) { `; }, }); - view = new View(parse(spec, null, { expr: vegaExpressionInterpreter }), { + view = new View(parse(spec, undefined, { expr: vegaExpressionInterpreter } as any), { renderer: 'canvas', // renderer (canvas or svg) container: `#${containerId}`, // parent DOM container hover: true, // enable hover processing @@ -560,6 +560,15 @@ export function getDuration({ startTime, endTime }: DateTimeFilter): Duration { return { startTime: startMoment.valueOf(), - endTime: endMoment.valueOf() - } + endTime: endMoment.valueOf(), + }; +} + +let isNotificationPluginInstalled = false; +export function setIsNotificationPluginInstalled(isInstalled: boolean) { + isNotificationPluginInstalled = isInstalled; +} + +export function getIsNotificationPluginInstalled(): boolean { + return isNotificationPluginInstalled; } diff --git a/test/mocks/CreateDetector/components/ConfigureAlerts/components/AlertCondition/AlertConditionPanel.mock.ts b/test/mocks/CreateDetector/components/ConfigureAlerts/components/AlertCondition/AlertConditionPanel.mock.ts index 18c936fa7..79b9702aa 100644 --- a/test/mocks/CreateDetector/components/ConfigureAlerts/components/AlertCondition/AlertConditionPanel.mock.ts +++ b/test/mocks/CreateDetector/components/ConfigureAlerts/components/AlertCondition/AlertConditionPanel.mock.ts @@ -4,13 +4,13 @@ */ import { AlertCondition } from '../../../../../../../models/interfaces'; -import { NotificationChannelTypeOptions } from '../../../../../../../public/pages/CreateDetector/components/ConfigureAlerts/models/interfaces'; import { RuleOptions } from '../../../../../../../public/models/interfaces'; import ruleOptionsMock from '../../../../../Rules/RuleOptions.mock'; import alertConditionMock from './AlertCondition.mock'; import notificationChannelTypeOptionsMock from '../../../../../services/notifications/NotificationChannelTypeOptions.mock'; import detectorMock from '../../../../../Detectors/containers/Detectors/Detector.mock'; import AlertConditionPanel from '../../../../../../../public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition'; +import { NotificationChannelTypeOptions } from '../../../../../../../types'; const alertCondition: AlertCondition = alertConditionMock; const notificationChannelTypeOptions: NotificationChannelTypeOptions = notificationChannelTypeOptionsMock; diff --git a/test/mocks/services/notifications/NotificationChannelOption.mock.ts b/test/mocks/services/notifications/NotificationChannelOption.mock.ts index a8d7cf5f8..ee173b7bc 100644 --- a/test/mocks/services/notifications/NotificationChannelOption.mock.ts +++ b/test/mocks/services/notifications/NotificationChannelOption.mock.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { NotificationChannelOption } from '../../../../public/pages/CreateDetector/components/ConfigureAlerts/models/interfaces'; +import { NotificationChannelOption } from '../../../../types'; export default { label: 'some_label', diff --git a/test/mocks/services/notifications/NotificationChannelTypeOptions.mock.ts b/test/mocks/services/notifications/NotificationChannelTypeOptions.mock.ts index c18979cf7..d43688c33 100644 --- a/test/mocks/services/notifications/NotificationChannelTypeOptions.mock.ts +++ b/test/mocks/services/notifications/NotificationChannelTypeOptions.mock.ts @@ -3,8 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { NotificationChannelTypeOptions } from '../../../../types'; import notificationChannelOptionMock from './NotificationChannelOption.mock'; -import { NotificationChannelTypeOptions } from '../../../../public/pages/CreateDetector/components/ConfigureAlerts/models/interfaces'; export default { label: 'some_label', diff --git a/types/Notification.ts b/types/Notification.ts index d9f3ec9ab..9ed34406e 100644 --- a/types/Notification.ts +++ b/types/Notification.ts @@ -44,3 +44,15 @@ export interface GetFeaturesResponse { allowed_config_type_list: string[]; plugin_features: { tooltip_support: boolean }; } + +export interface NotificationChannelTypeOptions { + label: string; + options: NotificationChannelOption[]; +} + +export interface NotificationChannelOption { + label: string; + value: string; + type: string; + description: string; +} diff --git a/types/ThreatIntel.ts b/types/ThreatIntel.ts index 5a877256a..f393c7624 100644 --- a/types/ThreatIntel.ts +++ b/types/ThreatIntel.ts @@ -3,13 +3,20 @@ * SPDX-License-Identifier: Apache-2.0 */ -export type ThreatIntelNextStepId = 'connect' | 'configure-scan'; +import { ThreatIntelIoc } from '../common/constants'; +import { AlertSeverity } from '../public/pages/Alerts/utils/constants'; +import { TriggerAction } from './Alert'; + +export type ThreatIntelNextStepId = 'add-source' | 'configure-scan'; export interface ThreatIntelNextStepCardProps { id: ThreatIntelNextStepId; title: string; description: string; - footerButtonText: string; + footerButtonProps: { + text: string; + disabled?: boolean; + }; } export enum FeedType { @@ -30,6 +37,66 @@ export interface ThreatIntelSourceItem { iocTypes: string[]; } +export interface LogSourceIocConfig { + enabled: boolean; + fieldAliases: string[]; +} + +export type ThreatIntelIocConfigMap = { + [k in ThreatIntelIoc]: LogSourceIocConfig; +}; + +export interface ThreatIntelLogSource { + name: string; + iocConfigMap: ThreatIntelIocConfigMap; +} + +export interface ThreatIntelAlertTrigger { + name: string; + triggerCondition: { + indicatorType: ThreatIntelIoc[]; + dataSource: string[]; + }; + alertSeverity: AlertSeverity; + action: TriggerAction & { destination_name: string }; +} + +export interface ThreatIntelScanConfig { + isRunning: boolean; + logSources: ThreatIntelLogSource[]; + triggers: ThreatIntelAlertTrigger[]; +} + +export interface ThreatIntelIocData { + id: string; + name: string; + type: ThreatIntelIoc; + value: string; + severity: string; + created: number; + modified: number; + description: string; + labels: string[]; + feedId: string; + specVersion: string; + version: number; +} + +export const dummyIoCDetails: ThreatIntelIocData = { + id: 'indicator--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f', + name: 'my-bad-ip', + type: ThreatIntelIoc.IPAddress, + value: '192.0.2.1', + severity: 'High', + created: 1718761171, + modified: 1718761171, + description: 'Random IP address', + labels: [], + feedId: 'random-feed-id', + specVersion: '', + version: 1, +}; + // export interface ThreatIntelSourceItem { // id: string; // version: number; @@ -52,13 +119,49 @@ export interface ThreatIntelSourceItem { // } export const dummySource: ThreatIntelSourceItem = { - id: '', + id: 'hello-world', feedName: 'AlienVault', description: 'Short description for threat intel source', - isEnabled: true, + isEnabled: false, iocTypes: ['IP', 'Domain', 'File hash'], }; +export const dummyLogSource: ThreatIntelLogSource = { + name: 'windows*', + iocConfigMap: { + [ThreatIntelIoc.IPAddress]: { + enabled: true, + fieldAliases: ['src_ip', 'dst.ip'], + }, + [ThreatIntelIoc.Domain]: { + enabled: true, + fieldAliases: ['domain'], + }, + [ThreatIntelIoc.FileHash]: { + enabled: false, + fieldAliases: ['hash'], + }, + }, +}; + +export const dummyLogSource2: ThreatIntelLogSource = { + name: 'cloudtrail*', + iocConfigMap: { + [ThreatIntelIoc.IPAddress]: { + enabled: true, + fieldAliases: ['src_ip', 'dst.ip'], + }, + [ThreatIntelIoc.Domain]: { + enabled: true, + fieldAliases: ['domain'], + }, + [ThreatIntelIoc.FileHash]: { + enabled: false, + fieldAliases: ['hash'], + }, + }, +}; + /** * * From 365d88cb5f26f60c7ada5c66d58cd2e6f00a3574 Mon Sep 17 00:00:00 2001 From: Amardeepsingh Siglani Date: Wed, 19 Jun 2024 00:11:30 -0700 Subject: [PATCH 03/12] updated enum name Signed-off-by: Amardeepsingh Siglani --- common/constants.ts | 10 ++++----- .../SelectThreatIntelLogSourcesForm.tsx | 17 +++++++++----- types/ThreatIntel.ts | 22 +++++++++---------- 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/common/constants.ts b/common/constants.ts index 542236427..95cdad20d 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -5,14 +5,14 @@ export const DEFAULT_RULE_UUID = '25b9c01c-350d-4b95-bed1-836d04a4f324'; -export enum ThreatIntelIoc { +export enum ThreatIntelIocType { IPAddress = 'IP', Domain = 'Domain', FileHash = 'FileHash', } -export const IocLabel: { [k in ThreatIntelIoc]: string } = { - [ThreatIntelIoc.IPAddress]: 'IP-Address', - [ThreatIntelIoc.Domain]: 'Domains', - [ThreatIntelIoc.FileHash]: 'File hash', +export const IocLabel: { [k in ThreatIntelIocType]: string } = { + [ThreatIntelIocType.IPAddress]: 'IP-Address', + [ThreatIntelIocType.Domain]: 'Domains', + [ThreatIntelIocType.FileHash]: 'File hash', }; diff --git a/public/pages/ThreatIntel/components/SelectLogSourcesForm/SelectThreatIntelLogSourcesForm.tsx b/public/pages/ThreatIntel/components/SelectLogSourcesForm/SelectThreatIntelLogSourcesForm.tsx index 74db60617..883519a9c 100644 --- a/public/pages/ThreatIntel/components/SelectLogSourcesForm/SelectThreatIntelLogSourcesForm.tsx +++ b/public/pages/ThreatIntel/components/SelectLogSourcesForm/SelectThreatIntelLogSourcesForm.tsx @@ -19,7 +19,7 @@ import { EuiText, EuiTitle, } from '@elastic/eui'; -import { IocLabel, ThreatIntelIoc } from '../../../../../common/constants'; +import { IocLabel, ThreatIntelIocType } from '../../../../../common/constants'; import React, { useState } from 'react'; import { ThreatIntelLogSource } from '../../../../../types'; import { Interval } from '../../../CreateDetector/components/DefineDetector/components/DetectorSchedule/Interval'; @@ -41,7 +41,7 @@ export const SelectThreatIntelLogSources: React.FC { const newSources: ThreatIntelLogSource[] = [ @@ -67,7 +67,7 @@ export const SelectThreatIntelLogSources: React.FC { const aliasesSet = new Set(source.iocConfigMap[ioc].fieldAliases); @@ -148,10 +148,15 @@ export const SelectThreatIntelLogSources: React.FC - onIocToggle(source, idx, ioc as ThreatIntelIoc, event.target.checked) + onIocToggle( + source, + idx, + ioc as ThreatIntelIocType, + event.target.checked + ) } /> @@ -170,7 +175,7 @@ export const SelectThreatIntelLogSources: React.FC Date: Wed, 19 Jun 2024 00:19:18 -0700 Subject: [PATCH 04/12] separated dummy data Signed-off-by: Amardeepsingh Siglani --- .../components/IoCsTable/IoCsTable.tsx | 3 +- .../Overview/ThreatIntelOverview.tsx | 3 +- .../ThreatIntelSource/ThreatIntelSource.tsx | 3 +- public/pages/ThreatIntel/utils/constants.ts | 63 +++++++++++ types/ThreatIntel.ts | 106 ------------------ 5 files changed, 68 insertions(+), 110 deletions(-) diff --git a/public/pages/ThreatIntel/components/IoCsTable/IoCsTable.tsx b/public/pages/ThreatIntel/components/IoCsTable/IoCsTable.tsx index 846307207..bdf064679 100644 --- a/public/pages/ThreatIntel/components/IoCsTable/IoCsTable.tsx +++ b/public/pages/ThreatIntel/components/IoCsTable/IoCsTable.tsx @@ -5,7 +5,8 @@ import React, { useState } from 'react'; import { EuiBasicTableColumn, EuiInMemoryTable, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; -import { ThreatIntelIocData, dummyIoCDetails } from '../../../../../types'; +import { ThreatIntelIocData } from '../../../../../types'; +import { dummyIoCDetails } from '../../utils/constants'; export interface IoCstableProps {} diff --git a/public/pages/ThreatIntel/containers/Overview/ThreatIntelOverview.tsx b/public/pages/ThreatIntel/containers/Overview/ThreatIntelOverview.tsx index 9e7e0a7be..3d6af2349 100644 --- a/public/pages/ThreatIntel/containers/Overview/ThreatIntelOverview.tsx +++ b/public/pages/ThreatIntel/containers/Overview/ThreatIntelOverview.tsx @@ -24,8 +24,6 @@ import { ThreatIntelNextStepId, ThreatIntelScanConfig, ThreatIntelSourceItem, - dummyLogSource, - dummySource, } from '../../../../../types'; import { RouteComponentProps } from 'react-router-dom'; import { ThreatIntelSourcesList } from '../../components/ThreatIntelSourcesList/ThreatIntelSourcesList'; @@ -33,6 +31,7 @@ import { ThreatIntelLogSources } from '../../components/ThreatIntelLogSources/Th import { getEmptyThreatIntelAlertTrigger, getThreatIntelNextStepsProps } from '../../utils/helpers'; import { ThreatIntelAlertTriggers } from '../../components/ThreatIntelAlertTriggers/ThreatIntelAlertTriggers'; import { ThreatIntelOverviewActions } from '../../components/ThreatIntelOverviewActions/ThreatIntelOverviewActions'; +import { dummyLogSource, dummySource } from '../../utils/constants'; export interface ThreatIntelOverviewProps extends RouteComponentProps {} diff --git a/public/pages/ThreatIntel/containers/ThreatIntelSource/ThreatIntelSource.tsx b/public/pages/ThreatIntel/containers/ThreatIntelSource/ThreatIntelSource.tsx index a69c41e78..bc4f45a30 100644 --- a/public/pages/ThreatIntel/containers/ThreatIntelSource/ThreatIntelSource.tsx +++ b/public/pages/ThreatIntel/containers/ThreatIntelSource/ThreatIntelSource.tsx @@ -4,7 +4,7 @@ */ import React, { useContext, useMemo, useState } from 'react'; -import { ThreatIntelSourceItem, dummySource } from '../../../../../types'; +import { ThreatIntelSourceItem } from '../../../../../types'; import { RouteComponentProps } from 'react-router-dom'; import { BREADCRUMBS, ROUTES } from '../../../../utils/constants'; import { useEffect } from 'react'; @@ -24,6 +24,7 @@ import { import { DescriptionGroup } from '../../../../components/Utility/DescriptionGroup'; import { IoCstable } from '../../components/IoCsTable/IoCsTable'; import { ThreatIntelSourceDetails } from '../../components/ThreatIntelSourceDetails/ThreatIntelSourceDetails'; +import { dummySource } from '../../utils/constants'; export interface ThreatIntelSource extends RouteComponentProps {} diff --git a/public/pages/ThreatIntel/utils/constants.ts b/public/pages/ThreatIntel/utils/constants.ts index 768120e11..835130491 100644 --- a/public/pages/ThreatIntel/utils/constants.ts +++ b/public/pages/ThreatIntel/utils/constants.ts @@ -3,7 +3,70 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { ThreatIntelIocType } from '../../../../common/constants'; +import { ThreatIntelIocData, ThreatIntelLogSource, ThreatIntelSourceItem } from '../../../../types'; + export enum ConfigureThreatIntelScanStep { SelectLogSources = 'SelectLogSources', SetupAlertTriggers = 'SetupAlertTriggers', } + +// TODO: Remove below Dummy data once APIs are integrated +export const dummyIoCDetails: ThreatIntelIocData = { + id: 'indicator--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f', + name: 'my-bad-ip', + type: ThreatIntelIocType.IPAddress, + value: '192.0.2.1', + severity: 'High', + created: 1718761171, + modified: 1718761171, + description: 'Random IP address', + labels: [], + feedId: 'random-feed-id', + specVersion: '', + version: 1, +}; + +export const dummySource: ThreatIntelSourceItem = { + id: 'hello-world', + feedName: 'AlienVault', + description: 'Short description for threat intel source', + isEnabled: false, + iocTypes: ['IP', 'Domain', 'File hash'], +}; + +export const dummyLogSource: ThreatIntelLogSource = { + name: 'windows*', + iocConfigMap: { + [ThreatIntelIocType.IPAddress]: { + enabled: true, + fieldAliases: ['src_ip', 'dst.ip'], + }, + [ThreatIntelIocType.Domain]: { + enabled: true, + fieldAliases: ['domain'], + }, + [ThreatIntelIocType.FileHash]: { + enabled: false, + fieldAliases: ['hash'], + }, + }, +}; + +export const dummyLogSource2: ThreatIntelLogSource = { + name: 'cloudtrail*', + iocConfigMap: { + [ThreatIntelIocType.IPAddress]: { + enabled: true, + fieldAliases: ['src_ip', 'dst.ip'], + }, + [ThreatIntelIocType.Domain]: { + enabled: true, + fieldAliases: ['domain'], + }, + [ThreatIntelIocType.FileHash]: { + enabled: false, + fieldAliases: ['hash'], + }, + }, +}; diff --git a/types/ThreatIntel.ts b/types/ThreatIntel.ts index ad717a601..2febcacbf 100644 --- a/types/ThreatIntel.ts +++ b/types/ThreatIntel.ts @@ -81,109 +81,3 @@ export interface ThreatIntelIocData { specVersion: string; version: number; } - -export const dummyIoCDetails: ThreatIntelIocData = { - id: 'indicator--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f', - name: 'my-bad-ip', - type: ThreatIntelIocType.IPAddress, - value: '192.0.2.1', - severity: 'High', - created: 1718761171, - modified: 1718761171, - description: 'Random IP address', - labels: [], - feedId: 'random-feed-id', - specVersion: '', - version: 1, -}; - -// export interface ThreatIntelSourceItem { -// id: string; -// version: number; -// feedName: string; -// description: string; -// feedFormat: string; -// feedType: FeedType; -// createdByUser: string; -// createdAt: number; -// enabledTime: number; -// lastUpdateTime: number; -// schedule: string; -// state: string; -// refreshType: string; -// lastRefreshedTime: number; -// lastRefreshedUser: string; -// isEnabled: boolean; -// iocMapStore: Record; -// iocTypes: string[]; -// } - -export const dummySource: ThreatIntelSourceItem = { - id: 'hello-world', - feedName: 'AlienVault', - description: 'Short description for threat intel source', - isEnabled: false, - iocTypes: ['IP', 'Domain', 'File hash'], -}; - -export const dummyLogSource: ThreatIntelLogSource = { - name: 'windows*', - iocConfigMap: { - [ThreatIntelIocType.IPAddress]: { - enabled: true, - fieldAliases: ['src_ip', 'dst.ip'], - }, - [ThreatIntelIocType.Domain]: { - enabled: true, - fieldAliases: ['domain'], - }, - [ThreatIntelIocType.FileHash]: { - enabled: false, - fieldAliases: ['hash'], - }, - }, -}; - -export const dummyLogSource2: ThreatIntelLogSource = { - name: 'cloudtrail*', - iocConfigMap: { - [ThreatIntelIocType.IPAddress]: { - enabled: true, - fieldAliases: ['src_ip', 'dst.ip'], - }, - [ThreatIntelIocType.Domain]: { - enabled: true, - fieldAliases: ['domain'], - }, - [ThreatIntelIocType.FileHash]: { - enabled: false, - fieldAliases: ['hash'], - }, - }, -}; - -/** - * - * - * - * - * private String id; - private Long version; - private String feedName; - private String feedFormat; - private FeedType feedType; - private String createdByUser; - private Instant createdAt; - - // private Source source; TODO: create Source Object - private Instant enabledTime; - private Instant lastUpdateTime; - private IntervalSchedule schedule; - private TIFJobState state; - public RefreshType refreshType; - public Instant lastRefreshedTime; - public String lastRefreshedUser; - private Boolean isEnabled; - private Map iocMapStore; - private List iocTypes; - */ From 962621c054f1e0fd525db45789939bd00360f40a Mon Sep 17 00:00:00 2001 From: Amardeepsingh Siglani Date: Wed, 19 Jun 2024 00:28:36 -0700 Subject: [PATCH 05/12] updated snapshots Signed-off-by: Amardeepsingh Siglani --- .../AlertConditionPanel.test.tsx.snap | 122 +++++++++++++++--- .../UpdateAlertConditions.test.tsx.snap | 1 - .../UpdateDetectorBasicDetails.test.tsx.snap | 11 +- 3 files changed, 110 insertions(+), 24 deletions(-) diff --git a/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/__snapshots__/AlertConditionPanel.test.tsx.snap b/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/__snapshots__/AlertConditionPanel.test.tsx.snap index ba7edfb3a..773a5f95c 100644 --- a/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/__snapshots__/AlertConditionPanel.test.tsx.snap +++ b/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/__snapshots__/AlertConditionPanel.test.tsx.snap @@ -588,7 +588,7 @@ Object {