Skip to content

Commit

Permalink
[2.x] Backport 1033 and 1063 to 2.x (#1070)
Browse files Browse the repository at this point in the history
* fixed logic to extend all alerts (#1033)

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* [Threat intel][Part 2] Findings and Alerts integration; Enhancements and fixes for producer flows (#1063)

* findings flyout; minor refactors, fixes

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* update alert state

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* alert flyout added

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* fixed flyout action button

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* show toast on error; open threat intel tabs from overview actions

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* fixed alert update API

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* toggle scan; delete scan config support

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* fixed complete button in table

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* fixed Ip enum

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* updated snapshot

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

---------

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

---------

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>
  • Loading branch information
amsiglan committed Jul 2, 2024
1 parent bca2c2c commit 9172b05
Show file tree
Hide file tree
Showing 48 changed files with 3,820 additions and 2,011 deletions.
10 changes: 6 additions & 4 deletions common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
export const DEFAULT_RULE_UUID = '25b9c01c-350d-4b95-bed1-836d04a4f324';

export enum ThreatIntelIocType {
IPAddress = 'ip',
Domain = 'domain',
FileHash = 'hash',
Domain = 'domain_name',
FileHash = 'hashes',
IPV4 = 'ipv4_addr',
IPV6 = 'ipv6_addr',
}

export const IocLabel: { [k in ThreatIntelIocType]: string } = {
[ThreatIntelIocType.IPAddress]: 'IP-Address',
[ThreatIntelIocType.IPV4]: 'IPV4-Address',
[ThreatIntelIocType.IPV6]: 'IPV6-Address',
[ThreatIntelIocType.Domain]: 'Domains',
[ThreatIntelIocType.FileHash]: 'File hash',
};
19 changes: 12 additions & 7 deletions public/components/Notifications/NotificationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@ import { getIsNotificationPluginInstalled } from '../../utils/helpers';
export interface NotificationFormProps {
allNotificationChannels: NotificationChannelTypeOptions[];
loadingNotifications: boolean;
action: TriggerAction;
action?: TriggerAction;
prepareMessage: (updateMessage?: boolean, onMount?: boolean) => void;
refreshNotificationChannels: () => void;
onChannelsChange: (selectedOptions: EuiComboBoxOptionOption<string>[]) => void;
onMessageBodyChange: (message: string) => void;
onMessageSubjectChange: (subject: string) => void;
onNotificationToggle?: (enabled: boolean) => void;
}

export const NotificationForm: React.FC<NotificationFormProps> = ({
Expand All @@ -47,11 +48,12 @@ export const NotificationForm: React.FC<NotificationFormProps> = ({
onChannelsChange,
onMessageBodyChange,
onMessageSubjectChange,
onNotificationToggle,
}) => {
const hasNotificationPlugin = getIsNotificationPluginInstalled();
const [showNotificationDetails, setShowNotificationDetails] = useState(true);
const [isActionRemoved, setIsActionRemoved] = useState(true);
const selectedNotificationChannelOption: NotificationChannelOption[] = [];
if (action.destination_id) {
if (!isActionRemoved && action?.destination_id) {
allNotificationChannels.forEach((typeOption) => {
const matchingChannel = typeOption.options.find(
(option) => option.value === action.destination_id
Expand All @@ -64,11 +66,14 @@ export const NotificationForm: React.FC<NotificationFormProps> = ({
<>
<EuiSwitch
label="Send notification"
checked={showNotificationDetails}
onChange={(e) => setShowNotificationDetails(e.target.checked)}
checked={isActionRemoved}
onChange={(e) => {
setIsActionRemoved(e.target.checked);
onNotificationToggle?.(e.target.checked);
}}
/>
<EuiSpacer />
{showNotificationDetails && (
{isActionRemoved && (
<>
<EuiFlexGroup alignItems={'flexEnd'}>
<EuiFlexItem style={{ maxWidth: 400 }}>
Expand Down Expand Up @@ -116,7 +121,7 @@ export const NotificationForm: React.FC<NotificationFormProps> = ({
<EuiSpacer size={'l'} />

<EuiAccordion
id={`alert-condition-notify-msg-${action.id ?? 'draft'}`}
id={`alert-condition-notify-msg-${action?.id ?? 'draft'}`}
buttonContent={
<EuiText size="m">
<p>Notification message</p>
Expand Down
2 changes: 1 addition & 1 deletion public/components/Utility/DescriptionGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import React from 'react';
export interface DescriptionGroupProps {
listItems: { title: NonNullable<React.ReactNode>; description: NonNullable<React.ReactNode> }[];
itemProps?: Pick<EuiFlexItemProps, 'grow'>;
groupProps?: Pick<EuiFlexGroupProps, 'justifyContent'>;
groupProps?: Pick<EuiFlexGroupProps, 'justifyContent' | 'direction'>;
}

export const DescriptionGroup: React.FC<DescriptionGroupProps> = ({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import {
EuiBadge,
EuiBasicTable,
EuiBasicTableColumn,
EuiButton,
EuiButtonIcon,
EuiFlexGroup,
EuiFlexItem,
EuiFlyout,
EuiFlyoutBody,
EuiFlyoutHeader,
EuiLink,
EuiSpacer,
EuiText,
EuiTitle,
} from '@elastic/eui';
import React, { useEffect, useState } from 'react';
import { ThreatIntelAlert, ThreatIntelFinding } from '../../../../../types';
import { DescriptionGroup } from '../../../../components/Utility/DescriptionGroup';
import { capitalize } from 'lodash';
import { renderTime } from '../../../../utils/helpers';
import { IocLabel } from '../../../../../common/constants';
import { DataStore } from '../../../../store/DataStore';
import { DEFAULT_EMPTY_DATA } from '../../../../utils/constants';
import { ContentPanel } from '../../../../components/ContentPanel';

export interface ThreatIntelAlertFlyoutProps {
alertItem: ThreatIntelAlert;
onClose: () => void;
onAlertStateChange: (
selectedItems: ThreatIntelAlert[],
nextState: 'ACKNOWLEDGED' | 'COMPLETED'
) => Promise<boolean>;
}

export const ThreatIntelAlertFlyout: React.FC<ThreatIntelAlertFlyoutProps> = ({
alertItem,
onAlertStateChange,
onClose,
}) => {
const [alertState, setAlertState] = useState(alertItem.state);
const [loading, setLoading] = useState(false);
const showActionButton = alertState === 'ACTIVE' || alertState === 'ACKNOWLEDGED';
const [findings, setFindings] = useState<ThreatIntelFinding[]>([]);

useEffect(() => {
setLoading(true);
DataStore.threatIntel
.getThreatIntelFindingsByIds(alertItem.finding_ids)
.then((findings) => {
setFindings(findings);
setLoading(false);
})
.catch((_e) => setLoading(false));
}, []);

const createFindingTableColumns = (): EuiBasicTableColumn<ThreatIntelFinding>[] => {
const backButton = (
<EuiButtonIcon
iconType="arrowLeft"
aria-label="back"
onClick={() => DataStore.findings.closeFlyout()}
display="base"
size="s"
data-test-subj={'finding-details-flyout-back-button'}
/>
);

return [
{
field: 'timestamp',
name: 'Time',
sortable: true,
dataType: 'date',
render: renderTime,
},
{
field: 'id',
name: 'Finding ID',
sortable: true,
dataType: 'string',
render: (id: string, finding: ThreatIntelFinding) =>
(
<EuiLink
onClick={() => {
DataStore.findings.openThreatIntelFindingFlyout(finding, backButton);
}}
data-test-subj={'finding-details-flyout-button'}
>
{id.length > 7 ? `${id.slice(0, 7)}...` : id}
</EuiLink>
) || DEFAULT_EMPTY_DATA,
},
{
field: 'ioc_feed_ids',
name: 'Threat intel sources',
render: (ioc_feed_ids: ThreatIntelFinding['ioc_feed_ids']) => {
return (
<EuiText>
<span>
{ioc_feed_ids
.slice(0, 2)
.map(({ feed_name }) => feed_name)
.join(', ')}
</span>
{ioc_feed_ids.length > 2 ? (
<EuiBadge>+{ioc_feed_ids.slice(2).length} more</EuiBadge>
) : null}
</EuiText>
);
},
},
];
};

return (
<EuiFlyout onClose={onClose} ownFocus hideCloseButton>
<EuiFlyoutHeader hasBorder={true}>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={2}>
<EuiTitle size={'m'}>
<h3>Alert details</h3>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={8}>
<EuiFlexGroup justifyContent="flexEnd" alignItems="center">
{showActionButton && (
<EuiFlexItem grow={false}>
<EuiButton
onClick={() => {
const nextState = alertState === 'ACTIVE' ? 'ACKNOWLEDGED' : 'COMPLETED';
onAlertStateChange([alertItem], nextState).then((success) => {
if (success) {
setAlertState(nextState);
}
});
}}
data-test-subj={'alert-details-flyout-acknowledge-button'}
>
{alertState === 'ACTIVE' ? 'Acknowledge' : 'Complete'}
</EuiButton>
</EuiFlexItem>
)}
<EuiFlexItem grow={false}>
<EuiButtonIcon
aria-label="close"
iconType="cross"
iconSize="m"
display="empty"
onClick={onClose}
data-test-subj={'alert-details-flyout-close-button'}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<DescriptionGroup
listItems={[
{ title: 'Alert trigger name', description: alertItem.trigger_name },
{ title: 'Alert status', description: alertState },
{ title: 'Alert severity', description: capitalize(alertItem.severity) },
]}
/>
<EuiSpacer size="m" />
<DescriptionGroup
listItems={[
{ title: 'Start time', description: renderTime(alertItem.start_time) },
{ title: 'Last updated time', description: renderTime(alertItem.last_updated_time) },
{ title: 'Indicator type', description: IocLabel[alertItem.ioc_type] },
]}
/>
<EuiSpacer />
<ContentPanel title={`Findings (${findings.length})`}>
<EuiBasicTable<ThreatIntelFinding>
columns={createFindingTableColumns()}
items={findings}
loading={loading}
/>
</ContentPanel>
</EuiFlyoutBody>
</EuiFlyout>
);
};
Loading

0 comments on commit 9172b05

Please sign in to comment.