mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution] Adds max signals warning to UI propagated rule warnings (#154112)
Adds a warning message that is propagated to the user when a rule execution hits `max_signals`. This will also set the rule in a partial failure state
This commit is contained in:
parent
11a447a1e0
commit
cd180a0323
28 changed files with 261 additions and 49 deletions
|
@ -124,6 +124,8 @@ export const createPersistenceRuleTypeWrapper: CreatePersistenceRuleTypeWrapper
|
|||
|
||||
if (filteredAlerts.length === 0) {
|
||||
return { createdAlerts: [], errors: {}, alertsWereTruncated: false };
|
||||
} else if (maxAlerts === 0) {
|
||||
return { createdAlerts: [], errors: {}, alertsWereTruncated: true };
|
||||
}
|
||||
|
||||
let enrichedAlerts = filteredAlerts;
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
createSearchAfterReturnType,
|
||||
makeFloatString,
|
||||
getUnprocessedExceptionsWarnings,
|
||||
getMaxSignalsWarning,
|
||||
} from '../utils/utils';
|
||||
import { buildReasonMessageForEqlAlert } from '../utils/reason_formatters';
|
||||
import type { CompleteRule, EqlRuleParams } from '../../rule_schema';
|
||||
|
@ -130,6 +131,9 @@ export const eqlExecutor = async ({
|
|||
|
||||
addToSearchAfterReturn({ current: result, next: createResult });
|
||||
}
|
||||
if (response.hits.total && response.hits.total.value >= ruleParams.maxSignals) {
|
||||
result.warningMessages.push(getMaxSignalsWarning());
|
||||
}
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import chunk from 'lodash/fp/chunk';
|
||||
import type { OpenPointInTimeResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
|
||||
import { uniq, chunk } from 'lodash/fp';
|
||||
import { getThreatList, getThreatListCount } from './get_threat_list';
|
||||
import type {
|
||||
CreateThreatSignalsOptions,
|
||||
|
@ -27,6 +27,7 @@ import { getAllowedFieldsForTermQuery } from './get_allowed_fields_for_terms_que
|
|||
import { getEventCount, getEventList } from './get_event_count';
|
||||
import { getMappingFilters } from './get_mapping_filters';
|
||||
import { THREAT_PIT_KEEP_ALIVE } from '../../../../../../common/cti/constants';
|
||||
import { getMaxSignalsWarning } from '../../utils/utils';
|
||||
|
||||
export const createThreatSignals = async ({
|
||||
alertId,
|
||||
|
@ -107,11 +108,6 @@ export const createThreatSignals = async ({
|
|||
|
||||
ruleExecutionLogger.debug(`Total event count: ${eventCount}`);
|
||||
|
||||
// if (eventCount === 0) {
|
||||
// ruleExecutionLogger.debug('Indicator matching rule has completed');
|
||||
// return results;
|
||||
// }
|
||||
|
||||
let threatPitId: OpenPointInTimeResponse['id'] = (
|
||||
await services.scopedClusterClient.asCurrentUser.openPointInTime({
|
||||
index: threatIndex,
|
||||
|
@ -171,6 +167,11 @@ export const createThreatSignals = async ({
|
|||
`all successes are ${results.success}`
|
||||
);
|
||||
if (results.createdSignalsCount >= params.maxSignals) {
|
||||
if (results.warningMessages.includes(getMaxSignalsWarning())) {
|
||||
results.warningMessages = uniq(results.warningMessages);
|
||||
} else if (documentCount > 0) {
|
||||
results.warningMessages.push(getMaxSignalsWarning());
|
||||
}
|
||||
ruleExecutionLogger.debug(
|
||||
`Indicator match has reached its max signals count ${params.maxSignals}. Additional documents not checked are ${documentCount}`
|
||||
);
|
||||
|
|
|
@ -20,6 +20,7 @@ export const findMlSignals = async ({
|
|||
anomalyThreshold,
|
||||
from,
|
||||
to,
|
||||
maxSignals,
|
||||
exceptionFilter,
|
||||
}: {
|
||||
ml: MlPluginSetup;
|
||||
|
@ -29,6 +30,7 @@ export const findMlSignals = async ({
|
|||
anomalyThreshold: number;
|
||||
from: string;
|
||||
to: string;
|
||||
maxSignals: number;
|
||||
exceptionFilter: Filter | undefined;
|
||||
}): Promise<AnomalyResults> => {
|
||||
const { mlAnomalySearch } = ml.mlSystemProvider(request, savedObjectsClient);
|
||||
|
@ -37,6 +39,7 @@ export const findMlSignals = async ({
|
|||
threshold: anomalyThreshold,
|
||||
earliestMs: dateMath.parse(from)?.valueOf() ?? 0,
|
||||
latestMs: dateMath.parse(to)?.valueOf() ?? 0,
|
||||
maxRecords: maxSignals,
|
||||
exceptionFilter,
|
||||
};
|
||||
return getAnomalies(params, mlAnomalySearch);
|
||||
|
|
|
@ -24,6 +24,7 @@ import {
|
|||
addToSearchAfterReturn,
|
||||
createErrorsFromShard,
|
||||
createSearchAfterReturnType,
|
||||
getMaxSignalsWarning,
|
||||
mergeReturns,
|
||||
} from '../utils/utils';
|
||||
import type { SetupPlugins } from '../../../../plugin';
|
||||
|
@ -102,9 +103,18 @@ export const mlExecutor = async ({
|
|||
anomalyThreshold: ruleParams.anomalyThreshold,
|
||||
from: tuple.from.toISOString(),
|
||||
to: tuple.to.toISOString(),
|
||||
maxSignals: tuple.maxSignals,
|
||||
exceptionFilter,
|
||||
});
|
||||
|
||||
if (
|
||||
anomalyResults.hits.total &&
|
||||
typeof anomalyResults.hits.total !== 'number' &&
|
||||
anomalyResults.hits.total.value > tuple.maxSignals
|
||||
) {
|
||||
result.warningMessages.push(getMaxSignalsWarning());
|
||||
}
|
||||
|
||||
const [filteredAnomalyHits, _] = await filterEventsAgainstList({
|
||||
listClient,
|
||||
ruleExecutionLogger,
|
||||
|
|
|
@ -38,6 +38,7 @@ import {
|
|||
import {
|
||||
addToSearchAfterReturn,
|
||||
createSearchAfterReturnType,
|
||||
getMaxSignalsWarning,
|
||||
getUnprocessedExceptionsWarnings,
|
||||
} from '../utils/utils';
|
||||
import { createEnrichEventsFunction } from '../utils/enrichments';
|
||||
|
@ -154,7 +155,7 @@ export const createNewTermsAlertType = (
|
|||
// it's possible for the array to be truncated but alert documents could fail to be created for other reasons,
|
||||
// in which case createdSignalsCount would still be less than maxSignals. Since valid alerts were truncated from
|
||||
// the array in that case, we stop and report the errors.
|
||||
while (result.createdSignalsCount < params.maxSignals) {
|
||||
while (result.createdSignalsCount <= params.maxSignals) {
|
||||
// PHASE 1: Fetch a page of terms using a composite aggregation. This will collect a page from
|
||||
// all of the terms seen over the last rule interval. In the next phase we'll determine which
|
||||
// ones are new.
|
||||
|
@ -316,6 +317,7 @@ export const createNewTermsAlertType = (
|
|||
addToSearchAfterReturn({ current: result, next: bulkCreateResult });
|
||||
|
||||
if (bulkCreateResult.alertsWereTruncated) {
|
||||
result.warningMessages.push(getMaxSignalsWarning());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ Object {
|
|||
},
|
||||
},
|
||||
"composite": Object {
|
||||
"size": 100,
|
||||
"size": 101,
|
||||
"sources": Array [
|
||||
Object {
|
||||
"host.name": Object {
|
||||
|
|
|
@ -42,7 +42,7 @@ export const buildGroupByFieldAggregation = ({
|
|||
},
|
||||
},
|
||||
})),
|
||||
size: maxSignals,
|
||||
size: maxSignals + 1, // Add extra bucket to check if there's more data after max signals
|
||||
},
|
||||
aggs: {
|
||||
topHits: {
|
||||
|
|
|
@ -15,6 +15,7 @@ import type { RuleServices, RunOpts, SearchAfterAndBulkCreateReturnType } from '
|
|||
import {
|
||||
addToSearchAfterReturn,
|
||||
getUnprocessedExceptionsWarnings,
|
||||
getMaxSignalsWarning,
|
||||
mergeReturns,
|
||||
} from '../../utils/utils';
|
||||
import type { SuppressionBucket } from './wrap_suppressed_alerts';
|
||||
|
@ -228,6 +229,13 @@ export const groupAndBulkCreate = async ({
|
|||
return toReturn;
|
||||
}
|
||||
|
||||
if (
|
||||
buckets.length > tuple.maxSignals &&
|
||||
!toReturn.warningMessages.includes(getMaxSignalsWarning()) // If the unsuppressed result didn't already hit max signals, we add the warning here
|
||||
) {
|
||||
toReturn.warningMessages.push(getMaxSignalsWarning());
|
||||
}
|
||||
|
||||
const suppressionBuckets: SuppressionBucket[] = buckets.map((bucket) => ({
|
||||
event: bucket.topHits.hits.hits[0],
|
||||
count: bucket.doc_count,
|
||||
|
|
|
@ -30,6 +30,7 @@ import type {
|
|||
} from './types';
|
||||
import { shouldFilterByCardinality } from './utils';
|
||||
import type { IRuleExecutionLogForExecutors } from '../../rule_monitoring';
|
||||
import { getMaxSignalsWarning } from '../utils/utils';
|
||||
|
||||
interface FindThresholdSignalsParams {
|
||||
from: string;
|
||||
|
@ -70,6 +71,7 @@ export const findThresholdSignals = async ({
|
|||
buckets: ThresholdBucket[];
|
||||
searchDurations: string[];
|
||||
searchErrors: string[];
|
||||
warnings: string[];
|
||||
}> => {
|
||||
// Leaf aggregations used below
|
||||
let sortKeys;
|
||||
|
@ -78,6 +80,7 @@ export const findThresholdSignals = async ({
|
|||
searchDurations: [],
|
||||
searchErrors: [],
|
||||
};
|
||||
const warnings: string[] = [];
|
||||
|
||||
const includeCardinalityFilter = shouldFilterByCardinality(threshold);
|
||||
|
||||
|
@ -166,8 +169,13 @@ export const findThresholdSignals = async ({
|
|||
}
|
||||
}
|
||||
|
||||
if (buckets.length > maxSignals) {
|
||||
warnings.push(getMaxSignalsWarning());
|
||||
}
|
||||
|
||||
return {
|
||||
buckets: buckets.slice(0, maxSignals),
|
||||
...searchAfterResults,
|
||||
warnings,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -128,7 +128,7 @@ export const thresholdExecutor = async ({
|
|||
});
|
||||
|
||||
// Look for new events over threshold
|
||||
const { buckets, searchErrors, searchDurations } = await findThresholdSignals({
|
||||
const { buckets, searchErrors, searchDurations, warnings } = await findThresholdSignals({
|
||||
inputIndexPattern: inputIndex,
|
||||
from: tuple.from.toISOString(),
|
||||
to: tuple.to.toISOString(),
|
||||
|
@ -164,6 +164,7 @@ export const thresholdExecutor = async ({
|
|||
|
||||
result.errors.push(...previousSearchErrors);
|
||||
result.errors.push(...searchErrors);
|
||||
result.warningMessages.push(...warnings);
|
||||
result.searchAfterTimes = searchDurations;
|
||||
|
||||
const createdAlerts = createResult.createdItems.map((alert) => {
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
mergeSearchResults,
|
||||
getSafeSortIds,
|
||||
addToSearchAfterReturn,
|
||||
getMaxSignalsWarning,
|
||||
} from './utils';
|
||||
import type { SearchAfterAndBulkCreateParams, SearchAfterAndBulkCreateReturnType } from '../types';
|
||||
import { withSecuritySpan } from '../../../../utils/with_security_span';
|
||||
|
@ -61,7 +62,7 @@ export const searchAfterAndBulkCreate = async ({
|
|||
});
|
||||
}
|
||||
|
||||
while (toReturn.createdSignalsCount < tuple.maxSignals) {
|
||||
while (toReturn.createdSignalsCount <= tuple.maxSignals) {
|
||||
try {
|
||||
let mergedSearchResults = createSearchResultReturnType();
|
||||
ruleExecutionLogger.debug(`sortIds: ${sortIds}`);
|
||||
|
@ -138,23 +139,23 @@ export const searchAfterAndBulkCreate = async ({
|
|||
// skip the call to bulk create and proceed to the next search_after,
|
||||
// if there is a sort id to continue the search_after with.
|
||||
if (includedEvents.length !== 0) {
|
||||
// make sure we are not going to create more signals than maxSignals allows
|
||||
const limitedEvents = includedEvents.slice(
|
||||
0,
|
||||
tuple.maxSignals - toReturn.createdSignalsCount
|
||||
);
|
||||
const enrichedEvents = await enrichment(limitedEvents);
|
||||
const enrichedEvents = await enrichment(includedEvents);
|
||||
const wrappedDocs = wrapHits(enrichedEvents, buildReasonMessage);
|
||||
|
||||
const bulkCreateResult = await bulkCreate(
|
||||
wrappedDocs,
|
||||
undefined,
|
||||
tuple.maxSignals - toReturn.createdSignalsCount,
|
||||
createEnrichEventsFunction({
|
||||
services,
|
||||
logger: ruleExecutionLogger,
|
||||
})
|
||||
);
|
||||
|
||||
if (bulkCreateResult.alertsWereTruncated) {
|
||||
toReturn.warningMessages.push(getMaxSignalsWarning());
|
||||
break;
|
||||
}
|
||||
|
||||
addToSearchAfterReturn({ current: toReturn, next: bulkCreateResult });
|
||||
|
||||
ruleExecutionLogger.debug(`created ${bulkCreateResult.createdItemsCount} signals`);
|
||||
|
|
|
@ -953,3 +953,7 @@ export const getUnprocessedExceptionsWarnings = (
|
|||
)}`;
|
||||
}
|
||||
};
|
||||
|
||||
export const getMaxSignalsWarning = (): string => {
|
||||
return `This rule reached the maximum alert limit for the rule execution. Some alerts were not created.`;
|
||||
};
|
||||
|
|
|
@ -34,7 +34,10 @@ export const createSecuritySolutionAlerts = async (
|
|||
log: ToolingLog,
|
||||
numberOfSignals: number = 1
|
||||
): Promise<estypes.SearchResponse<DetectionAlert & RiskEnrichmentFields>> => {
|
||||
const rule = getRuleForSignalTesting(['auditbeat-*']);
|
||||
const rule = {
|
||||
...getRuleForSignalTesting(['auditbeat-*']),
|
||||
query: 'process.executable: "/usr/bin/sudo"',
|
||||
};
|
||||
const { id } = await createRule(supertest, log, rule);
|
||||
await waitForRuleSuccess({ supertest, log, id });
|
||||
await waitForSignalsToBePresent(supertest, log, numberOfSignals, [id]);
|
||||
|
|
|
@ -800,7 +800,10 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
});
|
||||
|
||||
it('updates alert status when the status is updated and syncAlerts=true', async () => {
|
||||
const rule = getRuleForSignalTesting(['auditbeat-*']);
|
||||
const rule = {
|
||||
...getRuleForSignalTesting(['auditbeat-*']),
|
||||
query: 'process.executable: "/usr/bin/sudo"',
|
||||
};
|
||||
const postedCase = await createCase(supertest, postCaseReq);
|
||||
|
||||
const { id } = await createRule(supertest, log, rule);
|
||||
|
@ -856,7 +859,10 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
});
|
||||
|
||||
it('does NOT updates alert status when the status is updated and syncAlerts=false', async () => {
|
||||
const rule = getRuleForSignalTesting(['auditbeat-*']);
|
||||
const rule = {
|
||||
...getRuleForSignalTesting(['auditbeat-*']),
|
||||
query: 'process.executable: "/usr/bin/sudo"',
|
||||
};
|
||||
|
||||
const postedCase = await createCase(supertest, {
|
||||
...postCaseReq,
|
||||
|
@ -909,7 +915,10 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
});
|
||||
|
||||
it('it updates alert status when syncAlerts is turned on', async () => {
|
||||
const rule = getRuleForSignalTesting(['auditbeat-*']);
|
||||
const rule = {
|
||||
...getRuleForSignalTesting(['auditbeat-*']),
|
||||
query: 'process.executable: "/usr/bin/sudo"',
|
||||
};
|
||||
|
||||
const postedCase = await createCase(supertest, {
|
||||
...postCaseReq,
|
||||
|
@ -982,7 +991,10 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
});
|
||||
|
||||
it('it does NOT updates alert status when syncAlerts is turned off', async () => {
|
||||
const rule = getRuleForSignalTesting(['auditbeat-*']);
|
||||
const rule = {
|
||||
...getRuleForSignalTesting(['auditbeat-*']),
|
||||
query: 'process.executable: "/usr/bin/sudo"',
|
||||
};
|
||||
|
||||
const postedCase = await createCase(supertest, postCaseReq);
|
||||
const { id } = await createRule(supertest, log, rule);
|
||||
|
|
|
@ -55,7 +55,10 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
it('should be able to execute and get 10 signals', async () => {
|
||||
const rule = getRuleForSignalTesting(['auditbeat-*']);
|
||||
const rule = {
|
||||
...getRuleForSignalTesting(['auditbeat-*']),
|
||||
query: 'process.executable: "/usr/bin/sudo"',
|
||||
};
|
||||
const { id } = await createRule(supertest, log, rule);
|
||||
await waitForRuleSuccess({ supertest, log, id });
|
||||
await waitForSignalsToBePresent(supertest, log, 10, [id]);
|
||||
|
@ -64,7 +67,10 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
it('should be have set the signals in an open state initially', async () => {
|
||||
const rule = getRuleForSignalTesting(['auditbeat-*']);
|
||||
const rule = {
|
||||
...getRuleForSignalTesting(['auditbeat-*']),
|
||||
query: 'process.executable: "/usr/bin/sudo"',
|
||||
};
|
||||
const { id } = await createRule(supertest, log, rule);
|
||||
await waitForRuleSuccess({ supertest, log, id });
|
||||
await waitForSignalsToBePresent(supertest, log, 10, [id]);
|
||||
|
@ -76,7 +82,10 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
it('should be able to get a count of 10 closed signals when closing 10', async () => {
|
||||
const rule = getRuleForSignalTesting(['auditbeat-*']);
|
||||
const rule = {
|
||||
...getRuleForSignalTesting(['auditbeat-*']),
|
||||
query: 'process.executable: "/usr/bin/sudo"',
|
||||
};
|
||||
const { id } = await createRule(supertest, log, rule);
|
||||
await waitForRuleSuccess({ supertest, log, id });
|
||||
await waitForSignalsToBePresent(supertest, log, 10, [id]);
|
||||
|
@ -102,7 +111,10 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
it('should be able close 10 signals immediately and they all should be closed', async () => {
|
||||
const rule = getRuleForSignalTesting(['auditbeat-*']);
|
||||
const rule = {
|
||||
...getRuleForSignalTesting(['auditbeat-*']),
|
||||
query: 'process.executable: "/usr/bin/sudo"',
|
||||
};
|
||||
const { id } = await createRule(supertest, log, rule);
|
||||
await waitForRuleSuccess({ supertest, log, id });
|
||||
await waitForSignalsToBePresent(supertest, log, 10, [id]);
|
||||
|
|
|
@ -58,7 +58,10 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
];
|
||||
indexTestCases.forEach((index) => {
|
||||
it(`for KQL rule with index param: ${index}`, async () => {
|
||||
const rule = getRuleForSignalTesting(index);
|
||||
const rule = {
|
||||
...getRuleForSignalTesting(index),
|
||||
query: 'process.executable: "/usr/bin/sudo"',
|
||||
};
|
||||
await createUserAndRole(getService, ROLES.detections_admin);
|
||||
const { id } = await createRuleWithAuth(supertestWithoutAuth, rule, {
|
||||
user: ROLES.detections_admin,
|
||||
|
|
|
@ -123,11 +123,14 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
this pops up again elsewhere.
|
||||
*/
|
||||
it('should create a single rule with a rule_id and validate it ran successfully', async () => {
|
||||
const simpleRule = getRuleForSignalTesting(['auditbeat-*']);
|
||||
const rule = {
|
||||
...getRuleForSignalTesting(['auditbeat-*']),
|
||||
query: 'process.executable: "/usr/bin/sudo"',
|
||||
};
|
||||
const { body } = await supertest
|
||||
.post(DETECTION_ENGINE_RULES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(simpleRule)
|
||||
.send(rule)
|
||||
.expect(200);
|
||||
|
||||
await waitForRuleSuccess({ supertest, log, id: body.id });
|
||||
|
@ -161,11 +164,14 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
it('should create a single rule with a rule_id and an index pattern that does not match anything and an index pattern that does and the rule should be successful', async () => {
|
||||
const simpleRule = getRuleForSignalTesting(['does-not-exist-*', 'auditbeat-*']);
|
||||
const rule = {
|
||||
...getRuleForSignalTesting(['does-not-exist-*', 'auditbeat-*']),
|
||||
query: 'process.executable: "/usr/bin/sudo"',
|
||||
};
|
||||
const { body } = await supertest
|
||||
.post(DETECTION_ENGINE_RULES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(simpleRule)
|
||||
.send(rule)
|
||||
.expect(200);
|
||||
|
||||
await waitForRuleSuccess({ supertest, log, id: body.id });
|
||||
|
|
|
@ -112,11 +112,14 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
this pops up again elsewhere.
|
||||
*/
|
||||
it('should create a single rule with a rule_id and validate it ran successfully', async () => {
|
||||
const simpleRule = getRuleForSignalTesting(['auditbeat-*']);
|
||||
const rule = {
|
||||
...getRuleForSignalTesting(['auditbeat-*']),
|
||||
query: 'process.executable: "/usr/bin/sudo"',
|
||||
};
|
||||
const { body } = await supertest
|
||||
.post(DETECTION_ENGINE_RULES_BULK_CREATE)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send([simpleRule])
|
||||
.send([rule])
|
||||
.expect(200);
|
||||
|
||||
await waitForRuleSuccess({ supertest, log, id: body[0].id });
|
||||
|
|
|
@ -72,7 +72,10 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
it('should return execution events for a rule that has executed successfully', async () => {
|
||||
const rule = getRuleForSignalTesting(['auditbeat-*']);
|
||||
const rule = {
|
||||
...getRuleForSignalTesting(['auditbeat-*']),
|
||||
query: 'process.executable: "/usr/bin/sudo"',
|
||||
};
|
||||
const { id } = await createRule(supertest, log, rule);
|
||||
await waitForRuleSuccess({ supertest, log, id });
|
||||
await waitForEventLogExecuteComplete(es, log, id);
|
||||
|
|
|
@ -89,7 +89,10 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
it('should be able to execute and get 10 signals', async () => {
|
||||
const rule = getRuleForSignalTesting(['auditbeat-*']);
|
||||
const rule = {
|
||||
...getRuleForSignalTesting(['auditbeat-*']),
|
||||
query: 'process.executable: "/usr/bin/sudo"',
|
||||
};
|
||||
const { id } = await createRule(supertest, log, rule);
|
||||
await waitForRuleSuccess({ supertest, log, id });
|
||||
await waitForSignalsToBePresent(supertest, log, 10, [id]);
|
||||
|
@ -98,7 +101,10 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
it('should be have set the signals in an open state initially', async () => {
|
||||
const rule = getRuleForSignalTesting(['auditbeat-*']);
|
||||
const rule = {
|
||||
...getRuleForSignalTesting(['auditbeat-*']),
|
||||
query: 'process.executable: "/usr/bin/sudo"',
|
||||
};
|
||||
const { id } = await createRule(supertest, log, rule);
|
||||
await waitForRuleSuccess({ supertest, log, id });
|
||||
await waitForSignalsToBePresent(supertest, log, 10, [id]);
|
||||
|
@ -110,7 +116,10 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
it('should be able to get a count of 10 closed signals when closing 10', async () => {
|
||||
const rule = getRuleForSignalTesting(['auditbeat-*']);
|
||||
const rule = {
|
||||
...getRuleForSignalTesting(['auditbeat-*']),
|
||||
query: 'process.executable: "/usr/bin/sudo"',
|
||||
};
|
||||
const { id } = await createRule(supertest, log, rule);
|
||||
await waitForRuleSuccess({ supertest, log, id });
|
||||
await waitForSignalsToBePresent(supertest, log, 10, [id]);
|
||||
|
@ -136,7 +145,10 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
it('should be able close signals immediately and they all should be closed', async () => {
|
||||
const rule = getRuleForSignalTesting(['auditbeat-*']);
|
||||
const rule = {
|
||||
...getRuleForSignalTesting(['auditbeat-*']),
|
||||
query: 'process.executable: "/usr/bin/sudo"',
|
||||
};
|
||||
const { id } = await createRule(supertest, log, rule);
|
||||
await waitForRuleSuccess({ supertest, log, id });
|
||||
await waitForSignalsToBePresent(supertest, log, 1, [id]);
|
||||
|
|
|
@ -558,9 +558,10 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
it('should generate a signal-on-legacy-signal with legacy index pattern', async () => {
|
||||
const rule: SavedQueryRuleCreateProps = getSavedQueryRuleForSignalTesting([
|
||||
`.siem-signals-*`,
|
||||
]);
|
||||
const rule: SavedQueryRuleCreateProps = {
|
||||
...getSavedQueryRuleForSignalTesting([`.siem-signals-*`]),
|
||||
query: 'agent.name: "security-linux-1.example.dev"',
|
||||
};
|
||||
const { id } = await createRule(supertest, log, rule);
|
||||
await waitForRuleSuccess({ supertest, log, id });
|
||||
await waitForSignalsToBePresent(supertest, log, 1, [id]);
|
||||
|
@ -571,9 +572,10 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
it('should generate a signal-on-legacy-signal with AAD index pattern', async () => {
|
||||
const rule: SavedQueryRuleCreateProps = getSavedQueryRuleForSignalTesting([
|
||||
`.alerts-security.alerts-default`,
|
||||
]);
|
||||
const rule: SavedQueryRuleCreateProps = {
|
||||
...getSavedQueryRuleForSignalTesting([`.alerts-security.alerts-default`]),
|
||||
query: 'agent.name: "security-linux-1.example.dev"',
|
||||
};
|
||||
const { id } = await createRule(supertest, log, rule);
|
||||
await waitForRuleSuccess({ supertest, log, id });
|
||||
await waitForSignalsToBePresent(supertest, log, 1, [id]);
|
||||
|
@ -599,7 +601,11 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
it('should generate a signal-on-legacy-signal with legacy index pattern', async () => {
|
||||
const rule: EqlRuleCreateProps = getEqlRuleForSignalTesting(['.siem-signals-*']);
|
||||
const rule: EqlRuleCreateProps = {
|
||||
...getEqlRuleForSignalTesting(['.siem-signals-*']),
|
||||
query: 'any where agent.name == "security-linux-1.example.dev"',
|
||||
max_signals: 1000,
|
||||
};
|
||||
const { id } = await createRule(supertest, log, rule);
|
||||
await waitForRuleSuccess({ supertest, log, id });
|
||||
await waitForSignalsToBePresent(supertest, log, 1, [id]);
|
||||
|
@ -610,9 +616,11 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
it('should generate a signal-on-legacy-signal with AAD index pattern', async () => {
|
||||
const rule: EqlRuleCreateProps = getEqlRuleForSignalTesting([
|
||||
`.alerts-security.alerts-default`,
|
||||
]);
|
||||
const rule: EqlRuleCreateProps = {
|
||||
...getEqlRuleForSignalTesting([`.alerts-security.alerts-default`]),
|
||||
query: 'any where agent.name == "security-linux-1.example.dev"',
|
||||
max_signals: 1000,
|
||||
};
|
||||
const { id } = await createRule(supertest, log, rule);
|
||||
await waitForRuleSuccess({ supertest, log, id });
|
||||
await waitForSignalsToBePresent(supertest, log, 1, [id]);
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
ALERT_ORIGINAL_EVENT_CATEGORY,
|
||||
ALERT_GROUP_ID,
|
||||
} from '@kbn/security-solution-plugin/common/field_maps/field_names';
|
||||
import { getMaxSignalsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils';
|
||||
import {
|
||||
createRule,
|
||||
deleteAllRules,
|
||||
|
@ -175,6 +176,14 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
expect(previewAlerts.length).eql(maxSignals);
|
||||
});
|
||||
|
||||
it('generates max signals warning when circuit breaker is hit', async () => {
|
||||
const rule: EqlRuleCreateProps = {
|
||||
...getEqlRuleForSignalTesting(['auditbeat-*']),
|
||||
};
|
||||
const { logs } = await previewRule({ supertest, rule });
|
||||
expect(logs[0].warnings).contain(getMaxSignalsWarning());
|
||||
});
|
||||
|
||||
it('uses the provided event_category_override', async () => {
|
||||
const rule: EqlRuleCreateProps = {
|
||||
...getEqlRuleForSignalTesting(['auditbeat-*']),
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
ALERT_DEPTH,
|
||||
ALERT_ORIGINAL_TIME,
|
||||
} from '@kbn/security-solution-plugin/common/field_maps/field_names';
|
||||
import { getMaxSignalsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils';
|
||||
import { expect } from 'expect';
|
||||
import {
|
||||
createListsIndex,
|
||||
|
@ -158,6 +159,22 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
);
|
||||
});
|
||||
|
||||
it('generates max signals warning when circuit breaker is exceeded', async () => {
|
||||
const { logs } = await previewRule({
|
||||
supertest,
|
||||
rule: { ...rule, anomaly_threshold: 1, max_signals: 5 }, // This threshold generates 10 alerts with the current esArchive
|
||||
});
|
||||
expect(logs[0].warnings).toContain(getMaxSignalsWarning());
|
||||
});
|
||||
|
||||
it("doesn't generate max signals warning when circuit breaker is met, but not exceeded", async () => {
|
||||
const { logs } = await previewRule({
|
||||
supertest,
|
||||
rule: { ...rule, anomaly_threshold: 1, max_signals: 10 },
|
||||
});
|
||||
expect(logs[0].warnings).not.toContain(getMaxSignalsWarning());
|
||||
});
|
||||
|
||||
it('should create 7 alerts from ML rule when records meet anomaly_threshold', async () => {
|
||||
const { previewId } = await previewRule({
|
||||
supertest,
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
getNewTermsRuntimeMappings,
|
||||
AGG_FIELD_NAME,
|
||||
} from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/new_terms/utils';
|
||||
import { getMaxSignalsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils';
|
||||
import {
|
||||
createRule,
|
||||
deleteAllRules,
|
||||
|
@ -232,6 +233,32 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
});
|
||||
|
||||
it('generates max signals warning when circuit breaker is exceeded', async () => {
|
||||
const rule: NewTermsRuleCreateProps = {
|
||||
...getCreateNewTermsRulesSchemaMock('rule-1', true),
|
||||
new_terms_fields: ['process.pid'],
|
||||
from: '2018-02-19T20:42:00.000Z',
|
||||
// Set the history_window_start close to 'from' so we should alert on all terms in the time range
|
||||
history_window_start: '2018-02-19T20:41:59.000Z',
|
||||
};
|
||||
const { logs } = await previewRule({ supertest, rule });
|
||||
|
||||
expect(logs[0].warnings).contain(getMaxSignalsWarning());
|
||||
});
|
||||
|
||||
it("doesn't generate max signals warning when circuit breaker is met but not exceeded", async () => {
|
||||
const rule: NewTermsRuleCreateProps = {
|
||||
...getCreateNewTermsRulesSchemaMock('rule-1', true),
|
||||
new_terms_fields: ['host.ip'],
|
||||
from: '2019-02-19T20:42:00.000Z',
|
||||
history_window_start: '2019-01-19T20:42:00.000Z',
|
||||
max_signals: 3,
|
||||
};
|
||||
const { logs } = await previewRule({ supertest, rule });
|
||||
|
||||
expect(logs[0].warnings).not.contain(getMaxSignalsWarning());
|
||||
});
|
||||
|
||||
it('should generate 3 alerts when 1 document has 3 new values', async () => {
|
||||
const rule: NewTermsRuleCreateProps = {
|
||||
...getCreateNewTermsRulesSchemaMock('rule-1', true),
|
||||
|
|
|
@ -37,6 +37,7 @@ import {
|
|||
ALERT_ORIGINAL_EVENT,
|
||||
} from '@kbn/security-solution-plugin/common/field_maps/field_names';
|
||||
import { DETECTION_ENGINE_SIGNALS_STATUS_URL } from '@kbn/security-solution-plugin/common/constants';
|
||||
import { getMaxSignalsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils';
|
||||
import { deleteAllExceptions } from '../../../lists_api_integration/utils';
|
||||
import {
|
||||
createExceptionList,
|
||||
|
@ -107,6 +108,24 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
expect(alerts.hits.hits[0]._source?.['kibana.alert.ancestors'][0].id).eql(ID);
|
||||
});
|
||||
|
||||
it('generates max signals warning when circuit breaker is hit', async () => {
|
||||
const rule: QueryRuleCreateProps = {
|
||||
...getRuleForSignalTesting(['auditbeat-*']),
|
||||
};
|
||||
const { logs } = await previewRule({ supertest, rule });
|
||||
expect(logs[0].warnings).contain(getMaxSignalsWarning());
|
||||
});
|
||||
|
||||
it("doesn't generate max signals warning when circuit breaker is met but not exceeded", async () => {
|
||||
const rule = {
|
||||
...getRuleForSignalTesting(['auditbeat-*']),
|
||||
query: 'process.executable: "/usr/bin/sudo"',
|
||||
max_signals: 10,
|
||||
};
|
||||
const { logs } = await previewRule({ supertest, rule });
|
||||
expect(logs[0].warnings).not.contain(getMaxSignalsWarning());
|
||||
});
|
||||
|
||||
it('should abide by max_signals > 100', async () => {
|
||||
const maxSignals = 200;
|
||||
const rule: QueryRuleCreateProps = {
|
||||
|
|
|
@ -34,6 +34,7 @@ import {
|
|||
ALERT_ORIGINAL_TIME,
|
||||
} from '@kbn/security-solution-plugin/common/field_maps/field_names';
|
||||
import { RuleExecutionStatus } from '@kbn/security-solution-plugin/common/detection_engine/rule_monitoring';
|
||||
import { getMaxSignalsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils';
|
||||
import {
|
||||
previewRule,
|
||||
getOpenSignals,
|
||||
|
@ -501,6 +502,12 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
});
|
||||
|
||||
it('generates max signals warning when circuit breaker is hit', async () => {
|
||||
const rule: ThreatMatchRuleCreateProps = { ...createThreatMatchRule(), max_signals: 87 }; // Query generates 88 alerts with current esArchive
|
||||
const { logs } = await previewRule({ supertest, rule });
|
||||
expect(logs[0].warnings).contain(getMaxSignalsWarning());
|
||||
});
|
||||
|
||||
it('terms and match should have the same alerts with pagination', async () => {
|
||||
const termRule: ThreatMatchRuleCreateProps = createThreatMatchRule({
|
||||
override: {
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
ALERT_ORIGINAL_TIME,
|
||||
ALERT_THRESHOLD_RESULT,
|
||||
} from '@kbn/security-solution-plugin/common/field_maps/field_names';
|
||||
import { getMaxSignalsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils';
|
||||
import {
|
||||
createRule,
|
||||
getOpenSignals,
|
||||
|
@ -93,6 +94,32 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
});
|
||||
|
||||
it('generates max signals warning when circuit breaker is exceeded', async () => {
|
||||
const rule: ThresholdRuleCreateProps = {
|
||||
...getThresholdRuleForSignalTesting(['auditbeat-*']),
|
||||
threshold: {
|
||||
field: 'host.id',
|
||||
value: 1, // This value generates 7 alerts with the current esArchive
|
||||
},
|
||||
max_signals: 5,
|
||||
};
|
||||
const { logs } = await previewRule({ supertest, rule });
|
||||
expect(logs[0].warnings).contain(getMaxSignalsWarning());
|
||||
});
|
||||
|
||||
it("doesn't generate max signals warning when circuit breaker is met but not exceeded", async () => {
|
||||
const rule: ThresholdRuleCreateProps = {
|
||||
...getThresholdRuleForSignalTesting(['auditbeat-*']),
|
||||
threshold: {
|
||||
field: 'host.id',
|
||||
value: 1, // This value generates 7 alerts with the current esArchive
|
||||
},
|
||||
max_signals: 7,
|
||||
};
|
||||
const { logs } = await previewRule({ supertest, rule });
|
||||
expect(logs[0].warnings).not.contain(getMaxSignalsWarning());
|
||||
});
|
||||
|
||||
it('generates 2 signals from Threshold rules when threshold is met', async () => {
|
||||
const rule: ThresholdRuleCreateProps = {
|
||||
...getThresholdRuleForSignalTesting(['auditbeat-*']),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue