Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ import { searchAfterAndBulkCreateSuppressedAlerts } from '../../utils/search_aft
import { threatEnrichmentFactory } from './threat_enrichment_factory';
import { FAILED_CREATE_QUERY_MAX_CLAUSE, MANY_NESTED_CLAUSES_ERR } from './utils';
import { alertSuppressionTypeGuard } from '../../utils/get_is_alert_suppression_active';
import { createSearchAfterReturnType } from '../../utils/utils';

export const createEventSignal = async ({
sharedParams,
currentResult,
currentEventList,
eventsTelemetry,
filters,
Expand Down Expand Up @@ -72,16 +72,17 @@ export const createEventSignal = async ({
exc.message.includes(MANY_NESTED_CLAUSES_ERR) ||
exc.message.includes(FAILED_CREATE_QUERY_MAX_CLAUSE)
) {
currentResult.errors.push(exc.message);
return currentResult;
const result = createSearchAfterReturnType();
result.errors.push(exc.message);
return result;
} else {
throw exc;
}
}

const ids = Array.from(signalIdToMatchedQueriesMap.keys());
if (ids.length === 0) {
return currentResult;
return createSearchAfterReturnType();
}
const indexFilter = {
query: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import { searchAfterAndBulkCreateSuppressedAlerts } from '../../utils/search_aft

import { buildThreatEnrichment } from './build_threat_enrichment';
import { alertSuppressionTypeGuard } from '../../utils/get_is_alert_suppression_active';
import { createSearchAfterReturnType } from '../../utils/utils';
export const createThreatSignal = async ({
sharedParams,
currentResult,
currentThreatList,
eventsTelemetry,
filters,
Expand Down Expand Up @@ -59,7 +59,7 @@ export const createThreatSignal = async ({
ruleExecutionLogger.trace(
'Indicator items are empty after filtering for missing data, returning without attempting a match'
);
return currentResult;
return createSearchAfterReturnType();
} else {
const esFilter = await getFilter({
type,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,6 @@ export const createThreatSignals = async ({
createEventSignal({
sharedParams,
currentEventList: slicedChunk,
currentResult: results,
eventsTelemetry,
filters: allEventFilters,
reassignThreatPitId,
Expand Down Expand Up @@ -373,7 +372,6 @@ export const createThreatSignals = async ({
createSignal: (slicedChunk) =>
createThreatSignal({
sharedParams,
currentResult: results,
currentThreatList: slicedChunk,
eventsTelemetry,
filters: allEventFilters,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ export interface CreateThreatSignalsOptions {

export interface CreateThreatSignalOptions {
sharedParams: SecuritySharedParams<ThreatRuleParams>;
currentResult: SearchAfterAndBulkCreateReturnType;
currentThreatList: ThreatListItem[];
eventsTelemetry: ITelemetryEventsSender | undefined;
filters: unknown[];
Expand All @@ -66,7 +65,6 @@ export interface CreateThreatSignalOptions {

export interface CreateEventSignalOptions {
sharedParams: SecuritySharedParams<ThreatRuleParams>;
currentResult: SearchAfterAndBulkCreateReturnType;
currentEventList: EventItem[];
eventsTelemetry: ITelemetryEventsSender | undefined;
filters: unknown[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1092,6 +1092,72 @@ export default ({ getService }: FtrProviderContext) => {
const previewAlerts = await getPreviewAlerts({ es, previewId });
expect(previewAlerts).toHaveLength(2);
});

// Similar to https://github.com/elastic/kibana/issues/259169, but with indicators first
// We seed 2 matching events followed by 10 non-matching events and force 1 event/page.
// The first page of indicators creates 2 alerts; later no-op pages verify that the count of created alerts doesn't
// get inflated and trigger a false max-signals warning despite only 2 created preview alerts.
it('reproduces false max alerts warning when later event pages have no threat matches', async () => {
const id = uuidv4();
const baseTs = moment();

const matchingEvents = [
{
id,
user: { name: 'matchuser' },
'@timestamp': baseTs.clone().subtract(1, 's').toISOString(),
'event.ingested': baseTs.clone().subtract(1, 's').toISOString(),
},
{
id,
user: { name: 'matchuser' },
'@timestamp': baseTs.clone().subtract(2, 's').toISOString(),
'event.ingested': baseTs.clone().subtract(2, 's').toISOString(),
},
];
const nonMatchingEvents = Array.from({ length: 100 }, (_, i) => ({
id,
user: { name: `eventmiss${i + 1}` },
'@timestamp': baseTs
.clone()
.subtract(i + 3, 's')
.toISOString(),
'event.ingested': baseTs
.clone()
.subtract(i + 3, 's')
.toISOString(),
}));
const numThreats = 20;
const threats = [
{
...threatDoc(id, baseTs.clone().subtract(numThreats, 'm').toISOString()),
user: { name: 'matchuser' },
},
...Array.from({ length: numThreats - 1 }, (_, i) => ({
...threatDoc(id, baseTs.clone().subtract(i, 'm').toISOString()),
})),
];

await indexListOfDocuments([...matchingEvents, ...nonMatchingEvents, ...threats]);

const rule: ThreatMatchRuleCreateProps = {
...threatMatchRuleEcsComplaint(id),
threat_mapping: [
{
entries: [{ field: 'user.name', value: 'user.name', type: 'mapping' }],
},
],
items_per_search: 1,
concurrent_searches: 1,
};

const { logs, previewId } = await previewRule({ supertest, rule });
const previewAlerts = await getPreviewAlerts({ es, previewId, size: 1000 });
const allWarnings = logs.flatMap((l) => l.warnings ?? []);

expect(previewAlerts.length).toEqual(2);
expect(allWarnings).not.toContain(getMaxAlertsWarning());
});
});

describe('indicator enrichment: event-first search', () => {
Expand Down Expand Up @@ -1605,6 +1671,79 @@ export default ({ getService }: FtrProviderContext) => {
const previewAlerts = await getPreviewAlerts({ es, previewId });
expect(previewAlerts).toHaveLength(2);
});

// https://github.com/elastic/kibana/issues/259169
// We seed 2 matching events followed by 10 non-matching events and force 1 event/page.
// The first two pages create 2 alerts; later no-op pages verify that the count of created alerts doesn't
// get inflated and trigger a false max-signals warning despite only 2 created preview alerts.
it('reproduces false max alerts warning when later event pages have no threat matches', async () => {
const id = uuidv4();
const baseTs = moment();
const timestamp = baseTs.toISOString();

const matchingEvents = [
{
id,
user: { name: 'matchuser' },
'@timestamp': baseTs.clone().subtract(1, 's').toISOString(),
'event.ingested': baseTs.clone().subtract(1, 's').toISOString(),
},
{
id,
user: { name: 'matchuser' },
'@timestamp': baseTs.clone().subtract(2, 's').toISOString(),
'event.ingested': baseTs.clone().subtract(2, 's').toISOString(),
},
];
const nonMatchingEvents = Array.from({ length: 10 }, (_, i) => ({
id,
user: { name: `eventmiss${i + 1}` },
'@timestamp': baseTs
.clone()
.subtract(i + 3, 's')
.toISOString(),
'event.ingested': baseTs
.clone()
.subtract(i + 3, 's')
.toISOString(),
}));
const threats = [
{
...threatDoc(id, timestamp),
user: { name: 'matchuser' },
},
...Array.from({ length: 19 }, (_, i) => ({
...threatDoc(
id,
baseTs
.clone()
.subtract(i + 1, 'm')
.toISOString()
),
user: { name: `threatfiller${i + 1}` },
})),
];

await indexListOfDocuments([...matchingEvents, ...nonMatchingEvents, ...threats]);

const rule: ThreatMatchRuleCreateProps = {
...threatMatchRuleEcsComplaint(id),
threat_mapping: [
{
entries: [{ field: 'user.name', value: 'user.name', type: 'mapping' }],
},
],
items_per_search: 1,
concurrent_searches: 1,
};

const { logs, previewId } = await previewRule({ supertest, rule });
const previewAlerts = await getPreviewAlerts({ es, previewId, size: 1000 });
const allWarnings = logs.flatMap((l) => l.warnings ?? []);

expect(previewAlerts.length).toEqual(2);
expect(allWarnings).not.toContain(getMaxAlertsWarning());
});
});

// skips serverless MKI due to feature flag
Expand Down
Loading