Skip to content

Commit

Permalink
Add threat alerts card for Analytics (All) workspace use case (#1124)
Browse files Browse the repository at this point in the history
* add data source threat alerts card

Signed-off-by: Joanne Wang <jowg@amazon.com>

* refactor RULE_SEVERITY_OPTIONS

Signed-off-by: Joanne Wang <jowg@amazon.com>

* fix and run tests

Signed-off-by: Joanne Wang <jowg@amazon.com>

* updated based on comments and run yarn test

Signed-off-by: Joanne Wang <jowg@amazon.com>

---------

Signed-off-by: Joanne Wang <jowg@amazon.com>
(cherry picked from commit 7a23b49)
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
github-actions[bot] committed Sep 3, 2024
1 parent 8fbefc4 commit 854e255
Show file tree
Hide file tree
Showing 18 changed files with 462 additions and 87 deletions.
2 changes: 1 addition & 1 deletion opensearch_dashboards.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "2.17.0.0",
"opensearchDashboardsVersion": "2.17.0",
"configPath": ["opensearch_security_analytics"],
"requiredPlugins": ["data", "navigation", "opensearchDashboardsUtils"],
"requiredPlugins": ["data", "navigation", "opensearchDashboardsUtils", "contentManagement"],
"optionalPlugins": ["dataSource", "dataSourceManagement"],
"server": true,
"ui": true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useCallback, useEffect, useState } from 'react';
import {
EuiBadge,
EuiBasicTable,
EuiBasicTableColumn,
EuiFlexGroup,
EuiFlexItem,
EuiLink,
EuiLoadingContent,
EuiPanel,
EuiText,
EuiTitle,
} from '@elastic/eui';
import {
DataSourceManagementPluginSetup,
DataSourceOption,
} from '../../../../../src/plugins/data_source_management/public';
import {
dataSourceFilterFn,
errorNotificationToast,
getBadgeText,
getSeverityColor,
getTruncatedText,
renderTime,
} from '../../utils/helpers';
import { THREAT_ALERTS_NAV_ID } from '../../utils/constants';
import {
getApplication,
getNotifications,
getSavedObjectsClient,
} from '../../services/utils/constants';
import { DataStore } from '../../store/DataStore';
import { DetectorsService } from '../../services';
import { OverviewAlertItem } from '../../../types';
import { Time } from '@opensearch-project/opensearch/api/types';

export interface DataSourceAlertsCardProps {
getDataSourceMenu: DataSourceManagementPluginSetup['ui']['getDataSourceMenu'];
detectorService: DetectorsService;
}

export const DataSourceThreatAlertsCard: React.FC<DataSourceAlertsCardProps> = ({
getDataSourceMenu,
detectorService,
}) => {
const DataSourceSelector = getDataSourceMenu();
const notifications = getNotifications();
const [loading, setLoading] = useState(false);
const [dataSource, setDataSource] = useState<DataSourceOption>({
label: 'Local cluster',
id: '',
});
const [alerts, setAlerts] = useState<any[]>([]);

const getAlerts = async () => {
try {
const detectorsRes = await detectorService.getDetectors();
if (detectorsRes.ok) {
const detectors: any = {};
const detectorIds = detectorsRes.response.hits.hits.map((hit: any) => {
detectors[hit._id] = { ...hit._source, id: hit._id };
return hit._id;
});

let alerts: any[] = [];
const abortController = new AbortController();

for (let id of detectorIds) {
const alertsRes = await DataStore.alerts.getAlertsByDetector(
id,
detectors[id].name,
abortController.signal,
undefined,
undefined,
25
);
alerts = alerts.concat(alertsRes);
}

alerts.sort((a, b) => new Date(b.start_time).getTime() - new Date(a.start_time).getTime());
setAlerts(alerts.slice(0, 25));
setLoading(false);
} else {
errorNotificationToast(notifications, 'retrieve', 'detectors', detectorsRes.error);
setLoading(false);
}
} catch (e: any) {
errorNotificationToast(notifications, 'retrieve', 'alerts', e);
setLoading(false);
}
};

useEffect(() => {
setLoading(true);
getAlerts();
}, [dataSource]);

const onDataSourceSelected = useCallback(
(options: any[]) => {
if (dataSource?.id !== undefined && dataSource?.id !== options[0]?.id) {
setDataSource(options[0]);
}
},
[dataSource]
);

const columns: EuiBasicTableColumn<OverviewAlertItem>[] = [
{
field: 'start_time',
name: 'Time',
sortable: true,
align: 'left',
render: (start_time: Time) => {
return <EuiText size="s">{renderTime(start_time)}</EuiText>;
},
},
{
field: 'trigger_name',
name: 'Alert trigger',
sortable: false,
align: 'left',
render: (triggerName: string) => {
return (
<span style={{ color: '#006BB4' }} className="eui-textTruncate">
{getTruncatedText(triggerName)}
</span>
);
},
},
{
field: 'severity',
name: 'Alert severity',
sortable: true,
align: 'left',
render: (severity: string) => {
const severityColor = getSeverityColor(severity);
return (
<EuiBadge
color={severityColor?.background}
style={{ padding: '1px 8px', color: severityColor?.text }}
>
{getBadgeText(severity)}
</EuiBadge>
);
},
},
];

return (
<EuiPanel hasBorder={false} hasShadow={false}>
<EuiFlexGroup
style={{ height: '100%' }}
direction="column"
justifyContent="spaceBetween"
alignItems="flexStart"
gutterSize="xs"
>
<EuiFlexItem grow={false} style={{ width: '100%', height: '90%' }}>
<EuiFlexGroup direction="column" style={{ height: '100%' }}>
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="s" justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiTitle size="s">
<h3>Recent threat alerts</h3>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<DataSourceSelector
componentType={'DataSourceSelectable'}
componentConfig={{
savedObjects: getSavedObjectsClient(),
notifications: getNotifications(),
onSelectedDataSources: onDataSourceSelected,
fullWidth: false,
dataSourceFilter: dataSourceFilterFn,
activeOption: dataSource ? [{ id: dataSource.id }] : undefined,
}}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem style={{ overflow: 'scroll' }}>
{loading ? (
<EuiLoadingContent />
) : (
<EuiBasicTable tableCaption="threat_alerts" items={alerts} columns={columns} />
)}
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiLink
href={getApplication().getUrlForApp(THREAT_ALERTS_NAV_ID, { path: '#/dashboard' })}
target="_blank"
>
<EuiText size="s" className="eui-displayInline">
View all
</EuiText>
</EuiLink>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
);
};
10 changes: 7 additions & 3 deletions public/pages/Correlations/containers/CreateCorrelationRule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
NotificationChannelTypeOptions,
} from '../../../../types';
import {
ALERT_SEVERITY_OPTIONS,
BREADCRUMBS,
NOTIFICATIONS_HREF,
OS_NOTIFICATION_PLUGIN,
Expand Down Expand Up @@ -75,7 +76,6 @@ import {
} from '../../CreateDetector/components/ConfigureAlerts/utils/helpers';
import { NotificationsCallOut } from '../../../../public/components/NotificationsCallOut';
import { ExperimentalBanner } from '../components/ExperimentalBanner';
import { ALERT_SEVERITY_OPTIONS } from '../../CreateDetector/components/ConfigureAlerts/utils/constants';
import uuid from 'uuid';
import { PageHeader } from '../../../components/PageHeader/PageHeader';

Expand Down Expand Up @@ -714,12 +714,16 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
</EuiCompressedFormRow>
</EuiFlexItem>
<EuiFlexItem grow={false} style={{ minWidth: 200 }}>
<EuiCompressedFormRow label={<EuiText size={'s'}>Field value</EuiText>}>
<EuiCompressedFormRow
label={<EuiText size={'s'}>Field value</EuiText>}
>
{fieldValueInput}
</EuiCompressedFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiCompressedFormRow label={<p style={{ visibility: 'hidden' }}>_</p>}>
<EuiCompressedFormRow
label={<p style={{ visibility: 'hidden' }}>_</p>}
>
<EuiSmallButtonIcon
iconType={'trash'}
color="danger"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import {
getEmptyAlertCondition,
parseAlertSeverityToOption,
} from '../../utils/helpers';
import { ALERT_SEVERITY_OPTIONS } from '../../utils/constants';
import { CreateDetectorRulesOptions } from '../../../../../../models/types';
import { getNameErrorMessage, validateName } from '../../../../../../utils/validation';
import {
Expand All @@ -31,6 +30,7 @@ import {
NotificationChannelTypeOptions,
} from '../../../../../../../types';
import { NotificationForm } from '../../../../../../components/Notifications/NotificationForm';
import { ALERT_SEVERITY_OPTIONS } from '../../../../../../utils/constants';

interface AlertConditionPanelProps extends RouteComponentProps {
alertCondition: AlertCondition;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ Object {
tabindex="-1"
>
<span
badgelabel="Highest"
class="euiComboBoxPill euiComboBoxPill--plainText"
id="1"
text="1 (Highest)"
Expand Down Expand Up @@ -1345,6 +1346,7 @@ Object {
tabindex="-1"
>
<span
badgelabel="Highest"
class="euiComboBoxPill euiComboBoxPill--plainText"
id="1"
text="1 (Highest)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,13 @@
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export const MAX_ALERT_CONDITIONS = 10;
export const MIN_ALERT_CONDITIONS = 0;

// SEVERITY_OPTIONS have the id, value, label, and text fields because some EUI components
// (e.g, EuiComboBox) require value/label pairings, while others
// (e.g., EuiCheckboxGroup) require id/text pairings.
export const ALERT_SEVERITY_OPTIONS = {
HIGHEST: { id: '1', value: '1', label: '1 (Highest)', text: '1 (Highest)' },
HIGH: { id: '2', value: '2', label: '2 (High)', text: '2 (High)' },
MEDIUM: { id: '3', value: '3', label: '3 (Medium)', text: '3 (Medium)' },
LOW: { id: '4', value: '4', label: '4 (Low)', text: '4 (Low)' },
LOWEST: { id: '5', value: '5', label: '5 (Lowest)', text: '5 (Lowest)' },
};

export const RULE_SEVERITY_OPTIONS = {
CRITICAL: { id: '1', value: 'critical', label: 'Critical', text: 'critical' },
HIGH: { id: '2', value: 'high', label: 'High', text: 'High' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
*/

import { EuiComboBoxOptionOption } from '@elastic/eui';
import { ALERT_SEVERITY_OPTIONS, CHANNEL_TYPES } from './constants';
import { CHANNEL_TYPES } from './constants';
import { NotificationsService } from '../../../../../services';
import { AlertCondition, TriggerAction } from '../../../../../../models/interfaces';
import { FeatureChannelList, NotificationChannelTypeOptions } from '../../../../../../types';
import { ALERT_SEVERITY_OPTIONS } from '../../../../../utils/constants';

export const parseAlertSeverityToOption = (severity: string): EuiComboBoxOptionOption<string> => {
return Object.values(ALERT_SEVERITY_OPTIONS).find(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import { NotificationChannelTypeOptions, ThreatIntelAlertTrigger } from '../../.
import React from 'react';
import { NotificationForm } from '../../../../components/Notifications/NotificationForm';
import { ThreatIntelIocType } from '../../../../../common/constants';
import { ALERT_SEVERITY_OPTIONS } from '../../../CreateDetector/components/ConfigureAlerts/utils/constants';
import { parseAlertSeverityToOption } from '../../../CreateDetector/components/ConfigureAlerts/utils/helpers';
import { AlertSeverity } from '../../../Alerts/utils/constants';
import { getEmptyThreatIntelAlertTriggerAction } from '../../utils/helpers';
import { ALERT_SEVERITY_OPTIONS } from '../../../../utils/constants';

export interface ThreatIntelAlertTriggerProps {
allNotificationChannels: NotificationChannelTypeOptions[];
Expand Down
3 changes: 1 addition & 2 deletions public/pages/ThreatIntel/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { DEFAULT_EMPTY_DATA } from '../../../utils/constants';
import { ALERT_SEVERITY_OPTIONS, DEFAULT_EMPTY_DATA } from '../../../utils/constants';
import {
ThreatIntelAlertTrigger,
ThreatIntelScanConfig,
Expand All @@ -19,7 +19,6 @@ import {
ThreatIntelAlertTriggerAction,
} from '../../../../types';
import { AlertSeverity } from '../../Alerts/utils/constants';
import { ALERT_SEVERITY_OPTIONS } from '../../CreateDetector/components/ConfigureAlerts/utils/constants';
import _ from 'lodash';
import { ThreatIntelIocType } from '../../../../common/constants';

Expand Down
Loading

0 comments on commit 854e255

Please sign in to comment.