Skip to content

Commit

Permalink
Deprecate outdated detection rules Bulk APIs (#129448)
Browse files Browse the repository at this point in the history
  • Loading branch information
xcrzx authored Apr 12, 2022
1 parent 623070b commit a897747
Show file tree
Hide file tree
Showing 23 changed files with 367 additions and 171 deletions.
1 change: 1 addition & 0 deletions packages/kbn-doc-links/src/get_doc_links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => {
detectionsReq: `${SECURITY_SOLUTION_DOCS}detections-permissions-section.html`,
networkMap: `${SECURITY_SOLUTION_DOCS}conf-map-ui.html`,
troubleshootGaps: `${SECURITY_SOLUTION_DOCS}alerts-ui-monitor.html#troubleshoot-gaps`,
ruleApiOverview: `${SECURITY_SOLUTION_DOCS}rule-api-overview.html`,
},
securitySolution: {
trustedApps: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/trusted-apps-ov.html`,
Expand Down
1 change: 1 addition & 0 deletions packages/kbn-doc-links/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ export interface DocLinks {
readonly detectionsReq: string;
readonly networkMap: string;
readonly troubleshootGaps: string;
readonly ruleApiOverview: string;
};
readonly securitySolution: {
readonly trustedApps: string;
Expand Down
6 changes: 6 additions & 0 deletions x-pack/plugins/security_solution/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,12 @@ export const DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL =
export const DETECTION_ENGINE_RULES_BULK_ACTION =
`${DETECTION_ENGINE_RULES_URL}/_bulk_action` as const;
export const DETECTION_ENGINE_RULES_PREVIEW = `${DETECTION_ENGINE_RULES_URL}/preview` as const;
export const DETECTION_ENGINE_RULES_BULK_DELETE =
`${DETECTION_ENGINE_RULES_URL}/_bulk_delete` as const;
export const DETECTION_ENGINE_RULES_BULK_CREATE =
`${DETECTION_ENGINE_RULES_URL}/_bulk_create` as const;
export const DETECTION_ENGINE_RULES_BULK_UPDATE =
`${DETECTION_ENGINE_RULES_URL}/_bulk_update` as const;

/**
* Internal detection engine routes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import {
DETECTION_ENGINE_SIGNALS_MIGRATION_STATUS_URL,
DETECTION_ENGINE_RULES_BULK_ACTION,
DETECTION_ENGINE_RULE_EXECUTION_EVENTS_URL,
DETECTION_ENGINE_RULES_BULK_UPDATE,
DETECTION_ENGINE_RULES_BULK_DELETE,
DETECTION_ENGINE_RULES_BULK_CREATE,
} from '../../../../../common/constants';
import { GetAggregateRuleExecutionEventsResponse } from '../../../../../common/detection_engine/schemas/response';
import { RuleAlertType, HapiReadableStream } from '../../rules/types';
Expand Down Expand Up @@ -110,21 +113,21 @@ export const getFindRequest = () =>
export const getReadBulkRequest = () =>
requestMock.create({
method: 'post',
path: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`,
path: DETECTION_ENGINE_RULES_BULK_CREATE,
body: [getCreateRulesSchemaMock()],
});

export const getUpdateBulkRequest = () =>
requestMock.create({
method: 'put',
path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`,
path: DETECTION_ENGINE_RULES_BULK_UPDATE,
body: [getCreateRulesSchemaMock()],
});

export const getPatchBulkRequest = () =>
requestMock.create({
method: 'patch',
path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`,
path: DETECTION_ENGINE_RULES_BULK_UPDATE,
body: [getCreateRulesSchemaMock()],
});

Expand All @@ -145,28 +148,28 @@ export const getBulkActionEditRequest = () =>
export const getDeleteBulkRequest = () =>
requestMock.create({
method: 'delete',
path: `${DETECTION_ENGINE_RULES_URL}/_bulk_delete`,
path: DETECTION_ENGINE_RULES_BULK_DELETE,
body: [{ rule_id: 'rule-1' }],
});

export const getDeleteBulkRequestById = () =>
requestMock.create({
method: 'delete',
path: `${DETECTION_ENGINE_RULES_URL}/_bulk_delete`,
path: DETECTION_ENGINE_RULES_BULK_DELETE,
body: [{ id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd' }],
});

export const getDeleteAsPostBulkRequestById = () =>
requestMock.create({
method: 'post',
path: `${DETECTION_ENGINE_RULES_URL}/_bulk_delete`,
path: DETECTION_ENGINE_RULES_BULK_DELETE,
body: [{ id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd' }],
});

export const getDeleteAsPostBulkRequest = () =>
requestMock.create({
method: 'post',
path: `${DETECTION_ENGINE_RULES_URL}/_bulk_delete`,
path: DETECTION_ENGINE_RULES_BULK_DELETE,
body: [{ rule_id: 'rule-1' }],
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants';
import { DETECTION_ENGINE_RULES_BULK_CREATE } from '../../../../../common/constants';
import { mlServicesMock, mlAuthzMock as mockMlAuthzFactory } from '../../../machine_learning/mocks';
import { buildMlAuthz } from '../../../machine_learning/authz';
import {
Expand All @@ -23,6 +23,7 @@ import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks';
import { getQueryRuleParams } from '../../schemas/rule_schemas.mock';
import { loggingSystemMock } from '../../../../../../../../src/core/server/mocks';

jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create());

Expand All @@ -38,6 +39,7 @@ describe.each([
server = serverMock.create();
({ clients, context } = requestContextMock.createTools());
ml = mlServicesMock.createSetupContract();
const logger = loggingSystemMock.createLogger();

clients.rulesClient.find.mockResolvedValue(getEmptyFindResult()); // no existing rules
clients.rulesClient.create.mockResolvedValue(
Expand All @@ -47,7 +49,7 @@ describe.each([
context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue(
elasticsearchClientMock.createSuccessTransportRequestPromise(getBasicEmptySearchResponse())
);
createRulesBulkRoute(server.router, ml, isRuleRegistryEnabled);
createRulesBulkRoute(server.router, ml, isRuleRegistryEnabled, logger);
});

describe('status codes', () => {
Expand Down Expand Up @@ -137,7 +139,7 @@ describe.each([
test('returns an error object if duplicate rule_ids found in request payload', async () => {
const request = requestMock.create({
method: 'post',
path: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`,
path: DETECTION_ENGINE_RULES_BULK_CREATE,
body: [getCreateRulesSchemaMock(), getCreateRulesSchemaMock()],
});
const response = await server.inject(request, context);
Expand All @@ -158,7 +160,7 @@ describe.each([
test('allows rule type of query', async () => {
const request = requestMock.create({
method: 'post',
path: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`,
path: DETECTION_ENGINE_RULES_BULK_CREATE,
body: [{ ...getCreateRulesSchemaMock(), type: 'query' }],
});
const result = server.validate(request);
Expand All @@ -169,7 +171,7 @@ describe.each([
test('allows rule type of query and custom from and interval', async () => {
const request = requestMock.create({
method: 'post',
path: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`,
path: DETECTION_ENGINE_RULES_BULK_CREATE,
body: [{ from: 'now-7m', interval: '5m', ...getCreateRulesSchemaMock() }],
});
const result = server.validate(request);
Expand All @@ -180,7 +182,7 @@ describe.each([
test('disallows unknown rule type', async () => {
const request = requestMock.create({
method: 'post',
path: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`,
path: DETECTION_ENGINE_RULES_BULK_CREATE,
body: [{ ...getCreateRulesSchemaMock(), type: 'unexpected_type' }],
});
const result = server.validate(request);
Expand All @@ -191,7 +193,7 @@ describe.each([
test('disallows invalid "from" param on rule', async () => {
const request = requestMock.create({
method: 'post',
path: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`,
path: DETECTION_ENGINE_RULES_BULK_CREATE,
body: [
{
from: 'now-3755555555555555.67s',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { createRulesBulkSchema } from '../../../../../common/detection_engine/sc
import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema';
import type { SecuritySolutionPluginRouter } from '../../../../types';
import {
DETECTION_ENGINE_RULES_URL,
DETECTION_ENGINE_RULES_BULK_CREATE,
NOTIFICATION_THROTTLE_NO_ACTIONS,
} from '../../../../../common/constants';
import { SetupPlugins } from '../../../../plugin';
Expand All @@ -25,15 +25,21 @@ import { buildRouteValidation } from '../../../../utils/build_validation/route_v

import { transformBulkError, createBulkErrorObject, buildSiemResponse } from '../utils';
import { convertCreateAPIToInternalSchema } from '../../schemas/rule_converters';
import { getDeprecatedBulkEndpointHeader, logDeprecatedBulkEndpoint } from './utils/deprecation';
import { Logger } from '../../../../../../../../src/core/server';

/**
* @deprecated since version 8.2.0. Use the detection_engine/rules/_bulk_action API instead
*/
export const createRulesBulkRoute = (
router: SecuritySolutionPluginRouter,
ml: SetupPlugins['ml'],
isRuleRegistryEnabled: boolean
isRuleRegistryEnabled: boolean,
logger: Logger
) => {
router.post(
{
path: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`,
path: DETECTION_ENGINE_RULES_BULK_CREATE,
validate: {
body: buildRouteValidation(createRulesBulkSchema),
},
Expand All @@ -42,6 +48,8 @@ export const createRulesBulkRoute = (
},
},
async (context, request, response) => {
logDeprecatedBulkEndpoint(logger, DETECTION_ENGINE_RULES_BULK_CREATE);

const siemResponse = buildSiemResponse(response);
const rulesClient = context.alerting.getRulesClient();
const esClient = context.core.elasticsearch.client;
Expand Down Expand Up @@ -138,9 +146,16 @@ export const createRulesBulkRoute = (
];
const [validated, errors] = validate(rulesBulk, rulesBulkSchema);
if (errors != null) {
return siemResponse.error({ statusCode: 500, body: errors });
return siemResponse.error({
statusCode: 500,
body: errors,
headers: getDeprecatedBulkEndpointHeader(DETECTION_ENGINE_RULES_BULK_CREATE),
});
} else {
return response.ok({ body: validated ?? {} });
return response.ok({
body: validated ?? {},
headers: getDeprecatedBulkEndpointHeader(DETECTION_ENGINE_RULES_BULK_CREATE),
});
}
}
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants';
import { DETECTION_ENGINE_RULES_BULK_DELETE } from '../../../../../common/constants';
import {
getEmptyFindResult,
getFindResultWithSingleHit,
Expand All @@ -17,6 +17,7 @@ import {
} from '../__mocks__/request_responses';
import { requestContextMock, serverMock, requestMock } from '../__mocks__';
import { deleteRulesBulkRoute } from './delete_rules_bulk_route';
import { loggingSystemMock } from '../../../../../../../../src/core/server/mocks';

describe.each([
['Legacy', false],
Expand All @@ -28,12 +29,13 @@ describe.each([
beforeEach(() => {
server = serverMock.create();
({ clients, context } = requestContextMock.createTools());
const logger = loggingSystemMock.createLogger();

clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); // rule exists
clients.rulesClient.delete.mockResolvedValue({}); // successful deletion
clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse()); // rule status request

deleteRulesBulkRoute(server.router, isRuleRegistryEnabled);
deleteRulesBulkRoute(server.router, isRuleRegistryEnabled, logger);
});

describe('status codes with actionClient and alertClient', () => {
Expand All @@ -42,7 +44,7 @@ describe.each([
expect(response.status).toEqual(200);
});

test('resturns 200 when deleting a single rule and related rule status', async () => {
test('returns 200 when deleting a single rule and related rule status', async () => {
const response = await server.inject(getDeleteBulkRequest(), context);
expect(response.status).toEqual(200);
});
Expand Down Expand Up @@ -88,7 +90,7 @@ describe.each([
test('rejects requests without IDs', async () => {
const request = requestMock.create({
method: 'post',
path: `${DETECTION_ENGINE_RULES_URL}/_bulk_delete`,
path: DETECTION_ENGINE_RULES_BULK_DELETE,
body: [{}],
});
const response = await server.inject(request, context);
Expand All @@ -104,7 +106,7 @@ describe.each([
test('rejects requests with both id and rule_id', async () => {
const request = requestMock.create({
method: 'post',
path: `${DETECTION_ENGINE_RULES_URL}/_bulk_delete`,
path: DETECTION_ENGINE_RULES_BULK_DELETE,
body: [{ id: 'c1e1b359-7ac1-4e96-bc81-c683c092436f', rule_id: 'rule_1' }],
});
const response = await server.inject(request, context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,19 @@ import {
QueryRulesBulkSchemaDecoded,
} from '../../../../../common/detection_engine/schemas/request/query_rules_bulk_schema';
import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema';
import type { RouteConfig, RequestHandler } from '../../../../../../../../src/core/server';
import type { RouteConfig, RequestHandler, Logger } from '../../../../../../../../src/core/server';
import type {
SecuritySolutionPluginRouter,
SecuritySolutionRequestHandlerContext,
} from '../../../../types';
import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants';
import { DETECTION_ENGINE_RULES_BULK_DELETE } from '../../../../../common/constants';
import { getIdBulkError } from './utils';
import { transformValidateBulkError } from './validate';
import { transformBulkError, buildSiemResponse, createBulkErrorObject } from '../utils';
import { deleteRules } from '../../rules/delete_rules';
import { readRules } from '../../rules/read_rules';
import { legacyMigrate } from '../../rules/utils';
import { getDeprecatedBulkEndpointHeader, logDeprecatedBulkEndpoint } from './utils/deprecation';

type Config = RouteConfig<unknown, unknown, QueryRulesBulkSchemaDecoded, 'delete' | 'post'>;
type Handler = RequestHandler<
Expand All @@ -36,22 +37,28 @@ type Handler = RequestHandler<
'delete' | 'post'
>;

/**
* @deprecated since version 8.2.0. Use the detection_engine/rules/_bulk_action API instead
*/
export const deleteRulesBulkRoute = (
router: SecuritySolutionPluginRouter,
isRuleRegistryEnabled: boolean
isRuleRegistryEnabled: boolean,
logger: Logger
) => {
const config: Config = {
validate: {
body: buildRouteValidation<typeof queryRulesBulkSchema, QueryRulesBulkSchemaDecoded>(
queryRulesBulkSchema
),
},
path: `${DETECTION_ENGINE_RULES_URL}/_bulk_delete`,
path: DETECTION_ENGINE_RULES_BULK_DELETE,
options: {
tags: ['access:securitySolution'],
},
};
const handler: Handler = async (context, request, response) => {
logDeprecatedBulkEndpoint(logger, DETECTION_ENGINE_RULES_BULK_DELETE);

const siemResponse = buildSiemResponse(response);
const rulesClient = context.alerting.getRulesClient();
const ruleExecutionLog = context.securitySolution.getRuleExecutionLog();
Expand Down Expand Up @@ -102,9 +109,16 @@ export const deleteRulesBulkRoute = (
);
const [validated, errors] = validate(rules, rulesBulkSchema);
if (errors != null) {
return siemResponse.error({ statusCode: 500, body: errors });
return siemResponse.error({
statusCode: 500,
body: errors,
headers: getDeprecatedBulkEndpointHeader(DETECTION_ENGINE_RULES_BULK_DELETE),
});
} else {
return response.ok({ body: validated ?? {} });
return response.ok({
body: validated ?? {},
headers: getDeprecatedBulkEndpointHeader(DETECTION_ENGINE_RULES_BULK_DELETE),
});
}
};

Expand Down
Loading

0 comments on commit a897747

Please sign in to comment.