Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SIEM] [Detection Engine] Reject if duplicate rule_id in request payload #57057

Merged
merged 10 commits into from
Feb 13, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {
} from '../__mocks__/request_responses';
import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants';
import { createRulesBulkRoute } from './create_rules_bulk_route';
import { BulkError } from '../utils';
import { OutputRuleAlertRest } from '../../types';

describe('create_rules_bulk', () => {
let { server, alertsClient, actionsClient, elasticsearch } = createMockServer();
Expand Down Expand Up @@ -124,4 +126,19 @@ describe('create_rules_bulk', () => {
expect(statusCode).toBe(400);
});
});

test('returns 409 if duplicate rule_ids found in request payload', async () => {
alertsClient.find.mockResolvedValue(getFindResult());
alertsClient.get.mockResolvedValue(getResult());
actionsClient.create.mockResolvedValue(createActionResult());
alertsClient.create.mockResolvedValue(getResult());
const request: ServerInjectOptions = {
method: 'POST',
url: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`,
payload: [typicalPayload(), typicalPayload()],
};
const { payload } = await server.inject(request);
const output: Array<BulkError | Partial<OutputRuleAlertRest>> = JSON.parse(payload);
expect(output.some(item => item.error?.status_code === 409)).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { createRules } from '../../rules/create_rules';
import { BulkRulesRequest } from '../../rules/types';
import { ServerFacade } from '../../../../types';
import { readRules } from '../../rules/read_rules';
import { transformOrBulkError } from './utils';
import { transformOrBulkError, getMapDuplicates, getDuplicates } from './utils';
import { getIndexExists } from '../../index/get_index_exists';
import {
callWithRequestFactory,
Expand Down Expand Up @@ -48,6 +48,9 @@ export const createCreateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou
return headers.response().code(404);
}

const mappedDuplicates = getMapDuplicates(request.payload, 'rule_id');
const dupes = getDuplicates(mappedDuplicates);

const rules = await Promise.all(
request.payload.map(async payloadRule => {
const {
Expand Down Expand Up @@ -77,6 +80,13 @@ export const createCreateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou
timeline_title: timelineTitle,
version,
} = payloadRule;
if (ruleId != null && dupes?.includes(ruleId)) {
return createBulkErrorObject({
ruleId,
statusCode: 409,
message: `rule_id: "${ruleId}" already exists`,
});
}
const ruleIdOrUuid = ruleId ?? uuid.v4();
try {
const finalIndex = outputIndex != null ? outputIndex : getIndex(request, server);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,32 @@ export const transformOrImportError = (
});
}
};

export const getMapDuplicates = (
dhurley14 marked this conversation as resolved.
Show resolved Hide resolved
arr: Array<{ [key: string]: string }>,
prop: string
): Map<string, number> =>
arr.reduce<Map<string, number>>((acc, item) => {
if (item[prop] != null && acc.has(item[prop])) {
const totalView = acc.get(item[prop]) ?? 1;
acc.set(item[prop], totalView + 1);
} else if (item[prop] != null) {
acc.set(item[prop], 1);
}
return acc;
}, new Map());

export const getDuplicates = (someMap: Map<string, number>): string | null => {
const hasDuplicates = Array.from(someMap.values()).some(i => i > 1);
if (hasDuplicates) {
return Array.from(someMap.entries())
dhurley14 marked this conversation as resolved.
Show resolved Hide resolved
.reduce<string[]>((acc, [key, val]) => {
if (val > 1) {
return [...acc, key];
}
return acc;
}, [])
.join(', ');
}
return null;
};
dhurley14 marked this conversation as resolved.
Show resolved Hide resolved