From bca2c2c57e54ea5a8962e11cb3f25d620cd4af5f Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Wed, 3 Jul 2024 04:09:24 +0530 Subject: [PATCH] Show fields for aliases when selected in correlation rule and threat intel monitor scan (#1064) (#1065) * get fields for aliases in correlation rules and threat intel monitor * updated snapshot --------- (cherry picked from commit 0cfb24e1908b645d537c852e6122a5639dff9742) Signed-off-by: Amardeepsingh Siglani Signed-off-by: github-actions[bot] Co-authored-by: github-actions[bot] --- .../containers/CreateCorrelationRule.tsx | 198 +++++++++++------- .../EditFieldMappings.test.tsx.snap | 2 + .../SelectThreatIntelLogSourcesForm.tsx | 2 +- public/services/FieldMappingService.ts | 9 + public/utils/helpers.tsx | 9 +- server/clusters/addFieldMappingMethods.ts | 14 ++ server/routes/FieldMappingRoutes.ts | 12 ++ server/services/FieldMappingService.ts | 39 ++++ server/utils/constants.ts | 4 +- 9 files changed, 207 insertions(+), 82 deletions(-) diff --git a/public/pages/Correlations/containers/CreateCorrelationRule.tsx b/public/pages/Correlations/containers/CreateCorrelationRule.tsx index 4b3cfe273..7cc6fd177 100644 --- a/public/pages/Correlations/containers/CreateCorrelationRule.tsx +++ b/public/pages/Correlations/containers/CreateCorrelationRule.tsx @@ -41,17 +41,38 @@ import { CorrelationRuleModel, CorrelationRuleQuery, DataSourceProps, + NotificationChannelOption, + NotificationChannelTypeOptions, } from '../../../../types'; -import { BREADCRUMBS, NOTIFICATIONS_HREF, OS_NOTIFICATION_PLUGIN, ROUTES } from '../../../utils/constants'; +import { + BREADCRUMBS, + NOTIFICATIONS_HREF, + OS_NOTIFICATION_PLUGIN, + ROUTES, +} from '../../../utils/constants'; import { CoreServicesContext } from '../../../components/core_services'; import { RouteComponentProps, useParams } from 'react-router-dom'; import { validateName } from '../../../utils/validation'; -import { FieldMappingService, IndexService, OpenSearchService, NotificationsService } from '../../../services'; -import { errorNotificationToast, getDataSources, getFieldsForIndex, getLogTypeOptions, getPlugins } from '../../../utils/helpers'; +import { + FieldMappingService, + IndexService, + OpenSearchService, + NotificationsService, +} from '../../../services'; +import { + errorNotificationToast, + getDataSources, + getFieldsForIndex, + getLogTypeOptions, + getPlugins, +} from '../../../utils/helpers'; import { severityOptions } from '../../../pages/Alerts/utils/constants'; -import _ from 'lodash'; -import { NotificationChannelOption, NotificationChannelTypeOptions } from '../../CreateDetector/components/ConfigureAlerts/models/interfaces'; -import { getEmptyAlertCondition, getNotificationChannels, parseAlertSeverityToOption, parseNotificationChannelsToOptions } from '../../CreateDetector/components/ConfigureAlerts/utils/helpers'; +import { + getEmptyAlertCondition, + getNotificationChannels, + parseAlertSeverityToOption, + parseNotificationChannelsToOptions, +} 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'; @@ -66,8 +87,8 @@ export interface CreateCorrelationRuleProps extends DataSourceProps { { rule: CorrelationRuleModel; isReadOnly: boolean } >['history']; notifications: NotificationsStart | null; - notificationsService: NotificationsService - opensearchService: OpenSearchService + notificationsService: NotificationsService; + opensearchService: OpenSearchService; } export interface CorrelationOption { @@ -99,7 +120,7 @@ const unitOptions: EuiSelectOption[] = [ { value: 'HOURS', text: 'Hours' }, ]; -const ruleSeverityComboBoxOptions = severityOptions.map(option => ({ +const ruleSeverityComboBoxOptions = severityOptions.map((option) => ({ label: option.text, value: option.value, })); @@ -119,7 +140,9 @@ export const CreateCorrelationRule: React.FC = ( const [action, setAction] = useState('Create'); const [hasNotificationPlugin, setHasNotificationPlugin] = useState(false); const [loadingNotifications, setLoadingNotifications] = useState(true); - const [notificationChannels, setNotificationChannels] = useState([]); + const [notificationChannels, setNotificationChannels] = useState< + NotificationChannelTypeOptions[] + >([]); const [logTypeOptions, setLogTypeOptions] = useState([]); const [period, setPeriod] = useState({ interval: 1, unit: 'MINUTES' }); const [dataFilterEnabled, setDataFilterEnabled] = useState(false); @@ -127,7 +150,9 @@ export const CreateCorrelationRule: React.FC = ( const [showForm, setShowForm] = useState(false); const [showNotificationDetails, setShowNotificationDetails] = useState(false); const resetForm = useRef(false); - const [selectedNotificationChannelOption, setSelectedNotificationChannelOption] = useState([]); + const [selectedNotificationChannelOption, setSelectedNotificationChannelOption] = useState< + NotificationChannelOption[] + >([]); const validateCorrelationRule = useCallback( (rule: CorrelationRuleModel) => { @@ -207,7 +232,7 @@ export const CreateCorrelationRule: React.FC = ( const parsedChannels = parseNotificationChannelsToOptions(channels); setNotificationChannels(parsedChannels); setLoadingNotifications(false); - } + }; if (props.history.location.state?.rule) { setAction('Edit'); setInitialValues(props.history.location.state?.rule); @@ -263,7 +288,7 @@ export const CreateCorrelationRule: React.FC = ( if (newActions) { newActions[0].name = subject; newActions[0].subject_template.source = subject; - setInitialValues(prevState => ({ + setInitialValues((prevState) => ({ ...prevState, trigger: { ...prevState.trigger!, @@ -276,7 +301,7 @@ export const CreateCorrelationRule: React.FC = ( const onMessageBodyChange = (message: string) => { const newActions = [...(initialValues?.trigger?.actions ?? [])]; newActions[0].message_template.source = message; - setInitialValues(prevState => ({ + setInitialValues((prevState) => ({ ...prevState, trigger: { ...prevState.trigger!, @@ -292,10 +317,13 @@ export const CreateCorrelationRule: React.FC = ( const lineBreakAndTab = '\n\t'; const alertConditionName = `Triggered alert condition: ${alertCondition.name}`; - const alertConditionSeverity = `Severity: ${parseAlertSeverityToOption(alertCondition.severity)?.label || alertCondition.severity - }`; + const alertConditionSeverity = `Severity: ${ + parseAlertSeverityToOption(alertCondition.severity)?.label || alertCondition.severity + }`; const correlationRuleName = `Correlation Rule name: ${initialValues.name}`; - const defaultSubject = [alertConditionName, alertConditionSeverity, correlationRuleName].join(' - '); + const defaultSubject = [alertConditionName, alertConditionSeverity, correlationRuleName].join( + ' - ' + ); if (alertCondition.actions) { if (updateMessage || !alertCondition.actions[0]?.subject_template.source) @@ -306,7 +334,9 @@ export const CreateCorrelationRule: React.FC = ( const corrRuleQueries = `Detector data sources:${lineBreakAndTab}${initialValues.queries.join( `,${lineBreakAndTab}` )}`; - const ruleNames = `Rule Names:${lineBreakAndTab}${selectedNames?.join(`,${lineBreakAndTab}`) ?? ''}`; + const ruleNames = `Rule Names:${lineBreakAndTab}${ + selectedNames?.join(`,${lineBreakAndTab}`) ?? '' + }`; const ruleSeverities = `Rule Severities:${lineBreakAndTab}${alertCondition.sev_levels.join( `,${lineBreakAndTab}` )}`; @@ -334,11 +364,9 @@ export const CreateCorrelationRule: React.FC = ( onMessageBodyChange(defaultMessageBody); } } - } }; - const submit = async (values: CorrelationRuleModel) => { const randomTriggerId = uuid(); const randomActionId = uuid(); @@ -393,7 +421,7 @@ export const CreateCorrelationRule: React.FC = ( if (dataSourcesRes.ok) { setIndices(dataSourcesRes.dataSources); } - } catch (error: any) { } + } catch (error: any) {} }, [props.indexService, props.notifications]); useEffect(() => { @@ -402,7 +430,7 @@ export const CreateCorrelationRule: React.FC = ( const getLogFields = useCallback( async (indexName: string) => { - return getFieldsForIndex(props.indexService, indexName); + return getFieldsForIndex(props.fieldMappingService, indexName); }, [props.indexService.getIndexFields] ); @@ -586,15 +614,15 @@ export const CreateCorrelationRule: React.FC = ( selectedOptions={ query.logType ? [ - { - value: query.logType, - label: - ruleTypes.find( - (logType) => - logType.value.toLowerCase() === query.logType.toLowerCase() - )?.label || query.logType, - }, - ] + { + value: query.logType, + label: + ruleTypes.find( + (logType) => + logType.value.toLowerCase() === query.logType.toLowerCase() + )?.label || query.logType, + }, + ] : [] } isClearable={true} @@ -960,10 +988,7 @@ export const CreateCorrelationRule: React.FC = ( {createForm(queries, touched, errors, props)} - +

Alert Trigger

@@ -976,9 +1001,7 @@ export const CreateCorrelationRule: React.FC = ( - setShowForm(!showForm)} - > + setShowForm(!showForm)}> Add Alert Trigger @@ -1000,7 +1023,7 @@ export const CreateCorrelationRule: React.FC = ( placeholder="Trigger 1" onChange={(e) => { const triggerName = e.target.value || 'Trigger 1'; - props.setFieldValue('trigger.name', triggerName) + props.setFieldValue('trigger.name', triggerName); }} value={trigger?.name} required={true} @@ -1025,7 +1048,9 @@ export const CreateCorrelationRule: React.FC = ( props.setFieldValue('trigger.severity', selectedSeverity); // Update using setFieldValue }} selectedOptions={ - trigger?.severity ? [parseAlertSeverityToOption(trigger?.severity)] : [ALERT_SEVERITY_OPTIONS.HIGHEST] + trigger?.severity + ? [parseAlertSeverityToOption(trigger?.severity)] + : [ALERT_SEVERITY_OPTIONS.HIGHEST] } isClearable={true} data-test-subj="alert-severity-combo-box" @@ -1033,7 +1058,7 @@ export const CreateCorrelationRule: React.FC = ( - _

}> + _

}> = ( { setShowNotificationDetails(e.target.checked) }} + onChange={(e) => { + setShowNotificationDetails(e.target.checked); + }} /> @@ -1068,21 +1095,34 @@ export const CreateCorrelationRule: React.FC = ( placeholder={'Select notification channel.'} async={true} isLoading={!!loadingNotifications} - options={notificationChannels.flatMap(channelType => - channelType.options.map(option => ({ + options={notificationChannels.flatMap((channelType) => + channelType.options.map((option) => ({ label: option.label, value: option.value, })) )} onChange={(selectedOptions) => { - const newDestinationId = selectedOptions.length > 0 ? selectedOptions[0].value || '' : ''; - props.setFieldValue('trigger.actions[0].destination_id', newDestinationId); + const newDestinationId = + selectedOptions.length > 0 + ? selectedOptions[0].value || '' + : ''; + props.setFieldValue( + 'trigger.actions[0].destination_id', + newDestinationId + ); }} selectedOptions={ - trigger?.actions && trigger.actions.length > 0 && trigger.actions[0]?.destination_id - ? [notificationChannels.flatMap(channelType => - channelType.options.filter(option => option.value === trigger.actions[0]?.destination_id) - )[0]] + trigger?.actions && + trigger.actions.length > 0 && + trigger.actions[0]?.destination_id + ? ([ + notificationChannels.flatMap((channelType) => + channelType.options.filter( + (option) => + option.value === trigger.actions?.[0]?.destination_id + ) + )[0], + ] as EuiComboBoxOptionOption[]) : [] } singleSelection={{ asPlainText: true }} @@ -1134,8 +1174,11 @@ export const CreateCorrelationRule: React.FC = ( { - const subjectBody = e.target.value || ''; - props.setFieldValue('trigger.actions[0].subject_template.source', subjectBody) + const subjectBody = e.target.value || ''; + props.setFieldValue( + 'trigger.actions[0].subject_template.source', + subjectBody + ); }} value={trigger?.actions?.[0]?.subject_template?.source ?? ''} required={true} @@ -1156,8 +1199,11 @@ export const CreateCorrelationRule: React.FC = ( { - const messsageBody = e.target.value || ''; - props.setFieldValue('trigger.actions[0].message_template.source', messsageBody) + const messsageBody = e.target.value || ''; + props.setFieldValue( + 'trigger.actions[0].message_template.source', + messsageBody + ); }} value={trigger?.actions?.[0]?.message_template?.source ?? ''} required={true} @@ -1174,32 +1220,30 @@ export const CreateCorrelationRule: React.FC = ( )}
- { - action === 'Create' || action === 'Edit' ? ( - <> - - - - Cancel - - - { - props.handleSubmit(); - }} - fill={true} - > - {action === 'Edit' ? 'Update' : 'Create '} correlation rule - - - - - ) : null - } + {action === 'Create' || action === 'Edit' ? ( + <> + + + + Cancel + + + { + props.handleSubmit(); + }} + fill={true} + > + {action === 'Edit' ? 'Update' : 'Create '} correlation rule + + + + + ) : null} ); }} - + ); }; diff --git a/public/pages/Detectors/containers/FieldMappings/__snapshots__/EditFieldMappings.test.tsx.snap b/public/pages/Detectors/containers/FieldMappings/__snapshots__/EditFieldMappings.test.tsx.snap index 2bce03557..6f326ceb9 100644 --- a/public/pages/Detectors/containers/FieldMappings/__snapshots__/EditFieldMappings.test.tsx.snap +++ b/public/pages/Detectors/containers/FieldMappings/__snapshots__/EditFieldMappings.test.tsx.snap @@ -188,6 +188,7 @@ exports[` spec renders the component 1`] = ` filedMappingService={ FieldMappingService { "createMappings": [Function], + "getIndexAliasFields": [Function], "getMappings": [Function], "getMappingsView": [Function], "httpClient": [MockFunction], @@ -489,6 +490,7 @@ exports[` spec renders the component 1`] = ` filedMappingService={ FieldMappingService { "createMappings": [Function], + "getIndexAliasFields": [Function], "getMappings": [Function], "getMappingsView": [Function], "httpClient": [MockFunction], diff --git a/public/pages/ThreatIntel/components/SelectLogSourcesForm/SelectThreatIntelLogSourcesForm.tsx b/public/pages/ThreatIntel/components/SelectLogSourcesForm/SelectThreatIntelLogSourcesForm.tsx index 6e4d35cb0..bbcc38a0c 100644 --- a/public/pages/ThreatIntel/components/SelectLogSourcesForm/SelectThreatIntelLogSourcesForm.tsx +++ b/public/pages/ThreatIntel/components/SelectLogSourcesForm/SelectThreatIntelLogSourcesForm.tsx @@ -57,7 +57,7 @@ export const SelectThreatIntelLogSources: React.FC { if (saContext && !logSourceMappingByName[indexName]) { - getFieldsForIndex(saContext.services.indexService, indexName).then((fields) => { + getFieldsForIndex(saContext.services.fieldMappingService, indexName).then((fields) => { setLogSourceMappingByName({ ...logSourceMappingByName, [indexName]: fields, diff --git a/public/services/FieldMappingService.ts b/public/services/FieldMappingService.ts index addbce8c1..c8def908c 100644 --- a/public/services/FieldMappingService.ts +++ b/public/services/FieldMappingService.ts @@ -79,4 +79,13 @@ export default class FieldMappingService { }, })) as ServerResponse; }; + + getIndexAliasFields = async (indexName: string): Promise> => { + const url = `..${API.MAPPINGS_BASE}/fields/${indexName}`; + return (await this.httpClient.get(url, { + query: { + dataSourceId: dataSourceInfo.activeDataSource.id, + }, + })) as ServerResponse; + }; } diff --git a/public/utils/helpers.tsx b/public/utils/helpers.tsx index 7c3f4d10c..199f194b4 100644 --- a/public/utils/helpers.tsx +++ b/public/utils/helpers.tsx @@ -32,7 +32,7 @@ import { } from '../pages/CreateDetector/components/DefineDetector/components/DetectionRules/types/interfaces'; import { RuleInfo } from '../../server/models/interfaces'; import { NotificationsStart } from 'opensearch-dashboards/public'; -import { IndexService, OpenSearchService } from '../services'; +import { FieldMappingService, IndexService, OpenSearchService } from '../services'; import { ruleSeverity, ruleTypes } from '../pages/Rules/utils/constants'; import _ from 'lodash'; import { AlertCondition, DateTimeFilter, Duration, LogType } from '../../types'; @@ -576,14 +576,17 @@ export function getIsNotificationPluginInstalled(): boolean { return isNotificationPluginInstalled; } -export async function getFieldsForIndex(indexService: IndexService, indexName: string) { +export async function getFieldsForIndex( + fieldMappingService: FieldMappingService, + indexName: string +) { let fields: { label: string; value: string; }[] = []; if (indexName) { - const result = await indexService.getIndexFields(indexName); + const result = await fieldMappingService.getIndexAliasFields(indexName); if (result?.ok) { fields = result.response?.map((field) => ({ label: field, diff --git a/server/clusters/addFieldMappingMethods.ts b/server/clusters/addFieldMappingMethods.ts index 784fa89a8..79f95dc80 100644 --- a/server/clusters/addFieldMappingMethods.ts +++ b/server/clusters/addFieldMappingMethods.ts @@ -44,4 +44,18 @@ export function addFieldMappingMethods(securityAnalytics: any, createAction: any needBody: false, method: 'GET', }); + + securityAnalytics[METHOD_NAMES.GET_INDEX_ALIAS_MAPPINGS] = createAction({ + url: { + fmt: `/<%=indexName%>/_mapping/field/*`, + req: { + indexName: { + type: 'string', + required: true, + }, + }, + }, + needBody: false, + method: 'GET', + }); } diff --git a/server/routes/FieldMappingRoutes.ts b/server/routes/FieldMappingRoutes.ts index 24a6f2f07..00741c7dd 100644 --- a/server/routes/FieldMappingRoutes.ts +++ b/server/routes/FieldMappingRoutes.ts @@ -47,4 +47,16 @@ export function setupFieldMappingRoutes(services: NodeServices, router: IRouter) }, fieldMappingService.createMappings ); + + router.get( + { + path: `${API.MAPPINGS_BASE}/fields/{indexName}`, + validate: { + params: schema.object({ + indexName: schema.string(), + }), + }, + }, + fieldMappingService.getIndexAliasFields + ); } diff --git a/server/services/FieldMappingService.ts b/server/services/FieldMappingService.ts index 50d0c27a6..a3871b9c5 100644 --- a/server/services/FieldMappingService.ts +++ b/server/services/FieldMappingService.ts @@ -141,4 +141,43 @@ export default class FieldMappingService extends MDSEnabledClientService { }); } }; + + getIndexAliasFields = async ( + context: RequestHandlerContext, + request: OpenSearchDashboardsRequest<{ indexName: string }, {}>, + response: OpenSearchDashboardsResponseFactory + ) => { + try { + const { indexName } = request.params; + const client = this.getClient(request, context); + const mappingsResponse: { [key: string]: { mappings: any } } = await client( + CLIENT_FIELD_MAPPINGS_METHODS.GET_INDEX_ALIAS_MAPPINGS, + { + indexName, + } + ); + + const fieldMappings = Object.values(mappingsResponse)[0]?.mappings; + const fields = Object.keys(fieldMappings || {}).filter( + (field) => Object.keys(fieldMappings[field].mapping).length > 0 + ); + + return response.custom({ + statusCode: 200, + body: { + ok: true, + response: fields, + }, + }); + } catch (error: any) { + console.error('Security Analytics - FieldMappingService - getIndexAliasFields:', error); + return response.custom({ + statusCode: 200, + body: { + ok: false, + error: error.message, + }, + }); + } + }; } diff --git a/server/utils/constants.ts b/server/utils/constants.ts index a66634765..2d991ac84 100644 --- a/server/utils/constants.ts +++ b/server/utils/constants.ts @@ -79,6 +79,7 @@ export const METHOD_NAMES = { GET_MAPPINGS_VIEW: 'getFieldMappingsView', CREATE_MAPPINGS: 'createMappings', GET_MAPPINGS: 'getMappings', + GET_INDEX_ALIAS_MAPPINGS: 'getIndexAliasMappings', // Alerts methods GET_ALERTS: 'getAlerts', @@ -140,13 +141,14 @@ export const CLIENT_CORRELATION_METHODS = { GET_CORRELATED_FINDINGS: `${PLUGIN_PROPERTY_NAME}.${METHOD_NAMES.GET_CORRELATED_FINDINGS}`, GET_ALL_CORRELATIONS: `${PLUGIN_PROPERTY_NAME}.${METHOD_NAMES.GET_ALL_CORRELATIONS}`, GET_CORRELATION_ALERTS: `${PLUGIN_PROPERTY_NAME}.${METHOD_NAMES.GET_CORRELATION_ALERTS}`, - ACK_CORRELATION_ALERTS: `${PLUGIN_PROPERTY_NAME}.${METHOD_NAMES.ACK_CORRELATION_ALERTS}` + ACK_CORRELATION_ALERTS: `${PLUGIN_PROPERTY_NAME}.${METHOD_NAMES.ACK_CORRELATION_ALERTS}`, }; export const CLIENT_FIELD_MAPPINGS_METHODS = { GET_MAPPINGS_VIEW: `${PLUGIN_PROPERTY_NAME}.${METHOD_NAMES.GET_MAPPINGS_VIEW}`, CREATE_MAPPINGS: `${PLUGIN_PROPERTY_NAME}.${METHOD_NAMES.CREATE_MAPPINGS}`, GET_MAPPINGS: `${PLUGIN_PROPERTY_NAME}.${METHOD_NAMES.GET_MAPPINGS}`, + GET_INDEX_ALIAS_MAPPINGS: `${PLUGIN_PROPERTY_NAME}.${METHOD_NAMES.GET_INDEX_ALIAS_MAPPINGS}`, }; export const CLIENT_ALERTS_METHODS = {