mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution] [Detections] Support user error tagging for eql rule types (#213470)
## Summary adds support for tagging user errors in eql rule type and tags missing data view id as user error
This commit is contained in:
parent
3f90203406
commit
b64be404b0
8 changed files with 43 additions and 38 deletions
|
@ -18,6 +18,7 @@ import type { FieldMap } from '@kbn/alerts-as-data-utils';
|
|||
import { parseScheduleDates } from '@kbn/securitysolution-io-ts-utils';
|
||||
import { getIndexListFromEsqlQuery } from '@kbn/securitysolution-utils';
|
||||
import type { FormatAlert } from '@kbn/alerting-plugin/server/types';
|
||||
import { SavedObjectsErrorHelpers } from '@kbn/core/server';
|
||||
import {
|
||||
checkPrivilegesFromEsClient,
|
||||
getExceptions,
|
||||
|
@ -42,7 +43,7 @@ import { truncateList } from '../rule_monitoring';
|
|||
import aadFieldConversion from '../routes/index/signal_aad_mapping.json';
|
||||
import { extractReferences, injectReferences } from './saved_object_references';
|
||||
import { withSecuritySpan } from '../../../utils/with_security_span';
|
||||
import { getInputIndex, DataViewError } from './utils/get_input_output_index';
|
||||
import { getInputIndex } from './utils/get_input_output_index';
|
||||
import { TIMESTAMP_RUNTIME_FIELD } from './constants';
|
||||
import { buildTimestampRuntimeMapping } from './utils/build_timestamp_runtime_mapping';
|
||||
import { alertsFieldMap, rulesFieldMap } from '../../../../common/field_maps';
|
||||
|
@ -238,15 +239,18 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
|
|||
inputIndex = index ?? [];
|
||||
runtimeMappings = dataViewRuntimeMappings;
|
||||
} catch (exc) {
|
||||
const errorMessage =
|
||||
exc instanceof DataViewError
|
||||
? `Data View not found ${exc}`
|
||||
: `Check for indices to search failed ${exc}`;
|
||||
|
||||
await ruleExecutionLogger.logStatusChange({
|
||||
newStatus: RuleExecutionStatusEnum.failed,
|
||||
message: errorMessage,
|
||||
});
|
||||
if (SavedObjectsErrorHelpers.isNotFoundError(exc)) {
|
||||
await ruleExecutionLogger.logStatusChange({
|
||||
newStatus: RuleExecutionStatusEnum.failed,
|
||||
message: `Data View not found ${exc}`,
|
||||
userError: true,
|
||||
});
|
||||
} else {
|
||||
await ruleExecutionLogger.logStatusChange({
|
||||
newStatus: RuleExecutionStatusEnum.failed,
|
||||
message: `Check for indices to search failed ${exc}`,
|
||||
});
|
||||
}
|
||||
|
||||
return { state: result.state };
|
||||
}
|
||||
|
|
|
@ -66,11 +66,11 @@ describe('eql_executor', () => {
|
|||
});
|
||||
|
||||
it('should classify EQL verification exceptions as "user errors" when reporting to the framework', async () => {
|
||||
alertServices.scopedClusterClient.asCurrentUser.eql.search.mockRejectedValue({
|
||||
name: 'ResponseError',
|
||||
message:
|
||||
'verification_exception\n\tRoot causes:\n\t\tverification_exception: Found 1 problem\nline 1:1: Unknown column [event.category]',
|
||||
});
|
||||
alertServices.scopedClusterClient.asCurrentUser.eql.search.mockRejectedValue(
|
||||
new Error(
|
||||
'verification_exception\n\tRoot causes:\n\t\tverification_exception: Found 1 problem\nline 1:1: Unknown column [event.category]'
|
||||
)
|
||||
);
|
||||
const { result } = await eqlExecutor({
|
||||
sharedParams,
|
||||
services: alertServices,
|
||||
|
|
|
@ -51,6 +51,7 @@ import * as i18n from '../translations';
|
|||
import { alertSuppressionTypeGuard } from '../utils/get_is_alert_suppression_active';
|
||||
import { isEqlSequenceQuery } from '../../../../../common/detection_engine/utils';
|
||||
import { logShardFailures } from '../utils/log_shard_failure';
|
||||
import { checkErrorDetails } from '../utils/check_error_details';
|
||||
import { wrapSequences } from './wrap_sequences';
|
||||
|
||||
interface EqlExecutorParams {
|
||||
|
@ -214,12 +215,7 @@ export const eqlExecutor = async ({
|
|||
});
|
||||
return { result, ...(isLoggedRequestsEnabled ? { loggedRequests } : {}) };
|
||||
} catch (error) {
|
||||
if (
|
||||
typeof error.message === 'string' &&
|
||||
(error.message as string).includes('verification_exception')
|
||||
) {
|
||||
// We report errors that are more related to user configuration of rules rather than system outages as "user errors"
|
||||
// so SLO dashboards can show less noise around system outages
|
||||
if (checkErrorDetails(error).isUserError) {
|
||||
result.userError = true;
|
||||
}
|
||||
result.errors.push(error.message);
|
||||
|
|
|
@ -31,7 +31,7 @@ import type { RulePreviewLoggedRequest } from '../../../../../common/api/detecti
|
|||
import type { CreateRuleOptions, SecuritySharedParams, SignalSource } from '../types';
|
||||
import { logEsqlRequest } from '../utils/logged_requests';
|
||||
import { getDataTierFilter } from '../utils/get_data_tier_filter';
|
||||
import { checkErrorDetails } from './utils/check_error_details';
|
||||
import { checkErrorDetails } from '../utils/check_error_details';
|
||||
import * as i18n from '../translations';
|
||||
|
||||
import {
|
||||
|
|
|
@ -44,6 +44,12 @@ describe('checkErrorDetails', () => {
|
|||
|
||||
expect(checkErrorDetails(new Error(errorMessage))).toHaveProperty('isUserError', true);
|
||||
});
|
||||
it('should mark string literal error as user error from error message', () => {
|
||||
const errorMessage = `parsing_exception
|
||||
Root causes:
|
||||
parsing_exception: line 1:28: Use double quotes ["] to define string literals, not single quotes [']`;
|
||||
expect(checkErrorDetails(new Error(errorMessage))).toHaveProperty('isUserError', true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('data source verification errors', () => {
|
|
@ -9,10 +9,11 @@ import type { RuleExecutorServicesMock } from '@kbn/alerting-plugin/server/mocks
|
|||
import { alertsMock } from '@kbn/alerting-plugin/server/mocks';
|
||||
import type { MockedLogger } from '@kbn/logging-mocks';
|
||||
import { loggerMock } from '@kbn/logging-mocks';
|
||||
import { SavedObjectsErrorHelpers } from '@kbn/core/server';
|
||||
|
||||
import { DEFAULT_INDEX_KEY, DEFAULT_INDEX_PATTERN } from '../../../../../common/constants';
|
||||
import type { GetInputIndex } from './get_input_output_index';
|
||||
import { getInputIndex, DataViewError } from './get_input_output_index';
|
||||
import { getInputIndex } from './get_input_output_index';
|
||||
|
||||
describe('get_input_output_index', () => {
|
||||
let servicesMock: RuleExecutorServicesMock;
|
||||
|
@ -181,7 +182,7 @@ describe('get_input_output_index', () => {
|
|||
|
||||
test('Returns error if no matching data view found', async () => {
|
||||
servicesMock.savedObjectsClient.get.mockRejectedValue(
|
||||
new Error('Saved object [index-pattern/12345] not found')
|
||||
SavedObjectsErrorHelpers.createGenericNotFoundError('index-pattern', '12345')
|
||||
);
|
||||
await expect(
|
||||
getInputIndex({
|
||||
|
@ -199,18 +200,21 @@ describe('get_input_output_index', () => {
|
|||
|
||||
test('Returns error of DataViewErrorType', async () => {
|
||||
servicesMock.savedObjectsClient.get.mockRejectedValue(
|
||||
new Error('Saved object [index-pattern/12345] not found')
|
||||
SavedObjectsErrorHelpers.createGenericNotFoundError('index-pattern', '12345')
|
||||
);
|
||||
await expect(
|
||||
getInputIndex({
|
||||
expect.assertions(1);
|
||||
try {
|
||||
await getInputIndex({
|
||||
services: servicesMock,
|
||||
version: '8.0.0',
|
||||
index: [],
|
||||
dataViewId: '12345',
|
||||
ruleId: 'rule_1',
|
||||
logger,
|
||||
})
|
||||
).rejects.toBeInstanceOf(DataViewError);
|
||||
});
|
||||
} catch (exc) {
|
||||
expect(SavedObjectsErrorHelpers.isNotFoundError(exc)).toBeTruthy();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -47,15 +47,10 @@ export const getInputIndex = async ({
|
|||
// If data views defined, use it
|
||||
if (dataViewId != null && dataViewId !== '') {
|
||||
// Check to see that the selected dataView exists
|
||||
let dataView;
|
||||
try {
|
||||
dataView = await services.savedObjectsClient.get<DataViewAttributes>(
|
||||
'index-pattern',
|
||||
dataViewId
|
||||
);
|
||||
} catch (exc) {
|
||||
throw new DataViewError(exc.message);
|
||||
}
|
||||
const dataView = await services.savedObjectsClient.get<DataViewAttributes>(
|
||||
'index-pattern',
|
||||
dataViewId
|
||||
);
|
||||
const indices = dataView.attributes.title.split(',');
|
||||
const runtimeMappings =
|
||||
dataView.attributes.runtimeFieldMap != null
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue