From 10443a667027fb673b6f1045885305548f696bb8 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
Date: Tue, 2 Jul 2024 21:50:54 +0000
Subject: [PATCH] Show fields for aliases when selected in correlation rule and
threat intel monitor scan (#1064)
* get fields for aliases in correlation rules and threat intel monitor
Signed-off-by: Amardeepsingh Siglani
* updated snapshot
Signed-off-by: Amardeepsingh Siglani
---------
Signed-off-by: Amardeepsingh Siglani
(cherry picked from commit 0cfb24e1908b645d537c852e6122a5639dff9742)
Signed-off-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 = {