[Security Solution] [Detections] Adds metrics to some warning messages written to rule execution logger (#167551)

## Summary

Ref: https://github.com/elastic/kibana/issues/166971

Warning messages such as "max signals reached" or "rule may not have
read access to these indices" can now write metrics (if available) to
the rule execution logger.

Warning message:
<img width="1124" alt="warning_no_access_to_logs_index"
src="70b63a47-3e54-45b1-ba49-da531595e47e">

Metrics:
<img width="1669" alt="metrics_no_access_to_logs_index"
src="eac91dff-316a-4587-bab9-c816947be00e">
This commit is contained in:
Devin W. Hurley 2023-09-29 10:53:46 -04:00 committed by GitHub
parent 1980beecf3
commit 5364b9f887
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 55 additions and 20 deletions

View file

@ -182,6 +182,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
let result = createResultObject(state);
let wroteWarningStatus = false;
let warningMessage;
let hasError = false;
const primaryTimestamp = timestampOverride ?? TIMESTAMP;
@ -250,11 +251,15 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
if (!isMachineLearningParams(params)) {
const privileges = await checkPrivilegesFromEsClient(esClient, inputIndex);
wroteWarningStatus = await hasReadIndexPrivileges({
privileges,
ruleExecutionLogger,
uiSettingsClient,
});
const { wroteWarningMessage, warningStatusMessage: readIndexWarningMessage } =
await hasReadIndexPrivileges({
privileges,
ruleExecutionLogger,
uiSettingsClient,
});
wroteWarningStatus = wroteWarningMessage;
warningMessage = readIndexWarningMessage;
if (!wroteWarningStatus) {
const timestampFieldCaps = await withSecuritySpan('fieldCaps', () =>
@ -272,14 +277,18 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
)
);
const { wroteWarningStatus: wroteWarningStatusResult, foundNoIndices } =
await hasTimestampFields({
timestampField: primaryTimestamp,
timestampFieldCapsResponse: timestampFieldCaps,
inputIndices: inputIndex,
ruleExecutionLogger,
});
const {
wroteWarningStatus: wroteWarningStatusResult,
foundNoIndices,
warningMessage: warningMissingTimestampFieldsMessage,
} = await hasTimestampFields({
timestampField: primaryTimestamp,
timestampFieldCapsResponse: timestampFieldCaps,
inputIndices: inputIndex,
ruleExecutionLogger,
});
wroteWarningStatus = wroteWarningStatusResult;
warningMessage = warningMissingTimestampFieldsMessage;
skipExecution = foundNoIndices;
}
}
@ -447,6 +456,11 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
await ruleExecutionLogger.logStatusChange({
newStatus: RuleExecutionStatus['partial failure'],
message: truncateList(result.warningMessages).join(),
metrics: {
searchDurations: result.searchAfterTimes,
indexingDurations: result.bulkCreateTimes,
enrichmentDurations: result.enrichmentTimes,
},
});
}
@ -474,6 +488,16 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
enrichmentDurations: result.enrichmentTimes,
},
});
} else if (wroteWarningStatus && !hasError && !result.warning) {
await ruleExecutionLogger.logStatusChange({
newStatus: RuleExecutionStatus['partial failure'],
message: warningMessage,
metrics: {
searchDurations: result.searchAfterTimes,
indexingDurations: result.bulkCreateTimes,
enrichmentDurations: result.enrichmentTimes,
},
});
}
} else {
await ruleExecutionLogger.logStatusChange({

View file

@ -71,7 +71,7 @@ export const hasReadIndexPrivileges = async (args: {
privileges: Privilege;
ruleExecutionLogger: IRuleExecutionLogForExecutors;
uiSettingsClient: IUiSettingsClient;
}): Promise<boolean> => {
}): Promise<{ wroteWarningMessage: boolean; warningStatusMessage: string | undefined }> => {
const { privileges, ruleExecutionLogger, uiSettingsClient } = args;
const isCcsPermissionWarningEnabled = await uiSettingsClient.get(ENABLE_CCS_READ_WARNING_SETTING);
@ -86,17 +86,20 @@ export const hasReadIndexPrivileges = async (args: {
(indexName) => privileges.index[indexName].read
);
let warningStatusMessage;
// Some indices have read privileges others do not.
if (indexesWithNoReadPrivileges.length > 0) {
const indexesString = JSON.stringify(indexesWithNoReadPrivileges);
warningStatusMessage = `This rule may not have the required read privileges to the following index patterns: ${indexesString}`;
await ruleExecutionLogger.logStatusChange({
newStatus: RuleExecutionStatus['partial failure'],
message: `This rule may not have the required read privileges to the following index patterns: ${indexesString}`,
message: warningStatusMessage,
});
return true;
return { wroteWarningMessage: true, warningStatusMessage };
}
return false;
return { wroteWarningMessage: false, warningStatusMessage };
};
export const hasTimestampFields = async (args: {
@ -107,7 +110,11 @@ export const hasTimestampFields = async (args: {
timestampFieldCapsResponse: TransportResult<Record<string, any>, unknown>;
inputIndices: string[];
ruleExecutionLogger: IRuleExecutionLogForExecutors;
}): Promise<{ wroteWarningStatus: boolean; foundNoIndices: boolean }> => {
}): Promise<{
wroteWarningStatus: boolean;
foundNoIndices: boolean;
warningMessage: string | undefined;
}> => {
const { timestampField, timestampFieldCapsResponse, inputIndices, ruleExecutionLogger } = args;
const { ruleName } = ruleExecutionLogger.context;
@ -125,7 +132,11 @@ export const hasTimestampFields = async (args: {
message: errorString.trimEnd(),
});
return { wroteWarningStatus: true, foundNoIndices: true };
return {
wroteWarningStatus: true,
foundNoIndices: true,
warningMessage: errorString.trimEnd(),
};
} else if (
isEmpty(timestampFieldCapsResponse.body.fields) ||
timestampFieldCapsResponse.body.fields[timestampField] == null ||
@ -149,10 +160,10 @@ export const hasTimestampFields = async (args: {
message: errorString,
});
return { wroteWarningStatus: true, foundNoIndices: false };
return { wroteWarningStatus: true, foundNoIndices: false, warningMessage: errorString };
}
return { wroteWarningStatus: false, foundNoIndices: false };
return { wroteWarningStatus: false, foundNoIndices: false, warningMessage: undefined };
};
export const checkPrivileges = async (