mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[SIEM][Detection Engine] Adds critical missing status route to prepackaged rules (#55080)
## Summary * Fixes a critical bug where the missing status for the REST route was missing * Fixes a bug with the 400 not being used for the missing index in some cases * Changes create and update to NO LONGER allow immutable to be passed * Fixes a bug with the add prepackaged schema to where you could use `immutable: false`. Now it is required to be missing or set to `immutable: true` within it. * Cleans up unit tests To use the critical bug missing status for the REST route: ```ts GET /api/detection_engine/rules/prepackaged/_status ``` And you will get back: ```ts { "rules_installed": 252, "rules_not_installed": 87, "rules_not_updated": 0 } ``` See the script: ```ts get_prepackaged_rules_status.sh ``` for more details ### Checklist Use ~~strikethroughs~~ to remove checklist items you don't feel are applicable to this PR. ~~- [ ] This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility)~~ ~~- [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md)~~ ~~- [ ] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials~~ - [x] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios ~~- [ ] This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist)~~ ### For maintainers ~~- [ ] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~~ - [x] This includes a feature addition or change that requires a release note and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)
This commit is contained in:
parent
40ec30a367
commit
6559224c51
38 changed files with 788 additions and 312 deletions
|
@ -28,6 +28,7 @@ import { deleteRulesBulkRoute } from './lib/detection_engine/routes/rules/delete
|
|||
import { importRulesRoute } from './lib/detection_engine/routes/rules/import_rules_route';
|
||||
import { exportRulesRoute } from './lib/detection_engine/routes/rules/export_rules_route';
|
||||
import { findRulesStatusesRoute } from './lib/detection_engine/routes/rules/find_rules_status_route';
|
||||
import { getPrepackagedRulesStatusRoute } from './lib/detection_engine/routes/rules/get_prepackaged_rules_status_route';
|
||||
|
||||
const APP_ID = 'siem';
|
||||
|
||||
|
@ -49,12 +50,16 @@ export const initServerWithKibana = (context: PluginInitializerContext, __legacy
|
|||
updateRulesRoute(__legacy);
|
||||
deleteRulesRoute(__legacy);
|
||||
findRulesRoute(__legacy);
|
||||
|
||||
addPrepackedRulesRoute(__legacy);
|
||||
getPrepackagedRulesStatusRoute(__legacy);
|
||||
createRulesBulkRoute(__legacy);
|
||||
updateRulesBulkRoute(__legacy);
|
||||
deleteRulesBulkRoute(__legacy);
|
||||
|
||||
importRulesRoute(__legacy);
|
||||
exportRulesRoute(__legacy);
|
||||
|
||||
findRulesStatusesRoute(__legacy);
|
||||
|
||||
// Detection Engine Signals routes that have the REST endpoints of /api/detection_engine/signals
|
||||
|
|
|
@ -10,9 +10,12 @@ import { ElasticsearchPlugin } from 'src/legacy/core_plugins/elasticsearch';
|
|||
import { savedObjectsClientMock } from '../../../../../../../../../src/core/server/mocks';
|
||||
import { alertsClientMock } from '../../../../../../alerting/server/alerts_client.mock';
|
||||
import { actionsClientMock } from '../../../../../../actions/server/actions_client.mock';
|
||||
import { APP_ID, SIGNALS_INDEX_KEY } from '../../../../../common/constants';
|
||||
import { ServerFacade } from '../../../../types';
|
||||
|
||||
const defaultConfig = {
|
||||
'kibana.index': '.kibana',
|
||||
[`xpack.${APP_ID}.${SIGNALS_INDEX_KEY}`]: '.siem-signals',
|
||||
};
|
||||
|
||||
const isKibanaConfig = (config: unknown): config is KibanaConfig =>
|
||||
|
@ -58,10 +61,10 @@ export const createMockServer = (config: Record<string, string> = defaultConfig)
|
|||
server.decorate('request', 'getBasePath', () => '/s/default');
|
||||
server.decorate('request', 'getActionsClient', () => actionsClient);
|
||||
server.plugins.elasticsearch = (elasticsearch as unknown) as ElasticsearchPlugin;
|
||||
server.plugins.spaces = { getSpaceId: () => 'default' };
|
||||
server.decorate('request', 'getSavedObjectsClient', () => savedObjectsClient);
|
||||
|
||||
return {
|
||||
server,
|
||||
server: server as ServerFacade & Hapi.Server,
|
||||
alertsClient,
|
||||
actionsClient,
|
||||
elasticsearch,
|
||||
|
@ -82,7 +85,10 @@ export const createMockServerWithoutAlertClientDecoration = (
|
|||
serverWithoutAlertClient.decorate('request', 'getBasePath', () => '/s/default');
|
||||
serverWithoutAlertClient.decorate('request', 'getActionsClient', () => actionsClient);
|
||||
|
||||
return { serverWithoutAlertClient, actionsClient };
|
||||
return {
|
||||
serverWithoutAlertClient: serverWithoutAlertClient as ServerFacade & Hapi.Server,
|
||||
actionsClient,
|
||||
};
|
||||
};
|
||||
|
||||
export const createMockServerWithoutActionClientDecoration = (
|
||||
|
@ -98,7 +104,10 @@ export const createMockServerWithoutActionClientDecoration = (
|
|||
serverWithoutActionClient.decorate('request', 'getBasePath', () => '/s/default');
|
||||
serverWithoutActionClient.decorate('request', 'getAlertsClient', () => alertsClient);
|
||||
|
||||
return { serverWithoutActionClient, alertsClient };
|
||||
return {
|
||||
serverWithoutActionClient: serverWithoutActionClient as ServerFacade & Hapi.Server,
|
||||
alertsClient,
|
||||
};
|
||||
};
|
||||
|
||||
export const createMockServerWithoutActionOrAlertClientDecoration = (
|
||||
|
@ -111,6 +120,22 @@ export const createMockServerWithoutActionOrAlertClientDecoration = (
|
|||
serverWithoutActionOrAlertClient.config = () => createMockKibanaConfig(config);
|
||||
|
||||
return {
|
||||
serverWithoutActionOrAlertClient,
|
||||
serverWithoutActionOrAlertClient: serverWithoutActionOrAlertClient as ServerFacade &
|
||||
Hapi.Server,
|
||||
};
|
||||
};
|
||||
|
||||
export const getMockIndexName = () =>
|
||||
jest.fn().mockImplementation(() => ({
|
||||
callWithRequest: jest.fn().mockImplementationOnce(() => 'index-name'),
|
||||
}));
|
||||
|
||||
export const getMockEmptyIndex = () =>
|
||||
jest.fn().mockImplementation(() => ({
|
||||
callWithRequest: jest.fn().mockImplementation(() => ({ _shards: { total: 0 } })),
|
||||
}));
|
||||
|
||||
export const getMockNonEmptyIndex = () =>
|
||||
jest.fn().mockImplementation(() => ({
|
||||
callWithRequest: jest.fn().mockImplementation(() => ({ _shards: { total: 1 } })),
|
||||
}));
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
DETECTION_ENGINE_QUERY_SIGNALS_URL,
|
||||
INTERNAL_RULE_ID_KEY,
|
||||
INTERNAL_IMMUTABLE_KEY,
|
||||
DETECTION_ENGINE_PREPACKAGED_URL,
|
||||
} from '../../../../../common/constants';
|
||||
import { RuleAlertType, IRuleSavedAttributesSavedObjectAttributes } from '../../rules/types';
|
||||
import { RuleAlertParamsRest } from '../../types';
|
||||
|
@ -157,7 +158,17 @@ export const getDeleteAsPostBulkRequest = (): ServerInjectOptions => ({
|
|||
|
||||
export const getPrivilegeRequest = (): ServerInjectOptions => ({
|
||||
method: 'GET',
|
||||
url: `${DETECTION_ENGINE_PRIVILEGES_URL}`,
|
||||
url: DETECTION_ENGINE_PRIVILEGES_URL,
|
||||
});
|
||||
|
||||
export const addPrepackagedRulesRequest = (): ServerInjectOptions => ({
|
||||
method: 'PUT',
|
||||
url: DETECTION_ENGINE_PREPACKAGED_URL,
|
||||
});
|
||||
|
||||
export const getPrepackagedRulesStatusRequest = (): ServerInjectOptions => ({
|
||||
method: 'GET',
|
||||
url: `${DETECTION_ENGINE_PREPACKAGED_URL}/_status`,
|
||||
});
|
||||
|
||||
export interface FindHit {
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
import { createMockServer } from '../__mocks__/_mock_server';
|
||||
import { getPrivilegeRequest, getMockPrivileges } from '../__mocks__/request_responses';
|
||||
import { readPrivilegesRoute } from './read_privileges_route';
|
||||
import { ServerFacade } from '../../../../types';
|
||||
import * as myUtils from '../utils';
|
||||
|
||||
describe('read_privileges', () => {
|
||||
|
@ -19,7 +18,7 @@ describe('read_privileges', () => {
|
|||
elasticsearch.getCluster = jest.fn(() => ({
|
||||
callWithRequest: jest.fn(() => getMockPrivileges()),
|
||||
}));
|
||||
readPrivilegesRoute((server as unknown) as ServerFacade);
|
||||
readPrivilegesRoute(server);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
createMockServer,
|
||||
createMockServerWithoutActionClientDecoration,
|
||||
createMockServerWithoutAlertClientDecoration,
|
||||
createMockServerWithoutActionOrAlertClientDecoration,
|
||||
getMockEmptyIndex,
|
||||
getMockNonEmptyIndex,
|
||||
} from '../__mocks__/_mock_server';
|
||||
import { createRulesRoute } from './create_rules_route';
|
||||
import {
|
||||
getFindResult,
|
||||
getResult,
|
||||
createActionResult,
|
||||
addPrepackagedRulesRequest,
|
||||
getFindResultWithSingleHit,
|
||||
} from '../__mocks__/request_responses';
|
||||
|
||||
jest.mock('../../rules/get_prepackaged_rules', () => {
|
||||
return {
|
||||
getPrepackagedRules: () => {
|
||||
return [
|
||||
{
|
||||
rule_id: 'rule-1',
|
||||
output_index: '.siem-signals',
|
||||
risk_score: 50,
|
||||
description: 'some description',
|
||||
from: 'now-5m',
|
||||
to: 'now',
|
||||
index: ['index-1'],
|
||||
name: 'some-name',
|
||||
severity: 'severity',
|
||||
interval: '5m',
|
||||
type: 'query',
|
||||
version: 2, // set one higher than the mocks which is set to 1 to trigger updates
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
import { addPrepackedRulesRoute } from './add_prepackaged_rules_route';
|
||||
|
||||
describe('add_prepackaged_rules_route', () => {
|
||||
let { server, alertsClient, actionsClient, elasticsearch } = createMockServer();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
({ server, alertsClient, actionsClient, elasticsearch } = createMockServer());
|
||||
elasticsearch.getCluster = getMockNonEmptyIndex();
|
||||
|
||||
addPrepackedRulesRoute(server);
|
||||
});
|
||||
|
||||
describe('status codes with actionClient and alertClient', () => {
|
||||
test('returns 200 when creating a with a valid actionClient and alertClient', async () => {
|
||||
alertsClient.find.mockResolvedValue(getFindResult());
|
||||
alertsClient.get.mockResolvedValue(getResult());
|
||||
actionsClient.create.mockResolvedValue(createActionResult());
|
||||
alertsClient.create.mockResolvedValue(getResult());
|
||||
const { statusCode } = await server.inject(addPrepackagedRulesRequest());
|
||||
expect(statusCode).toBe(200);
|
||||
});
|
||||
|
||||
test('returns 404 if actionClient is not available on the route', async () => {
|
||||
const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration();
|
||||
createRulesRoute(serverWithoutActionClient);
|
||||
const { statusCode } = await serverWithoutActionClient.inject(addPrepackagedRulesRequest());
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
|
||||
test('returns 404 if alertClient is not available on the route', async () => {
|
||||
const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration();
|
||||
createRulesRoute(serverWithoutAlertClient);
|
||||
const { statusCode } = await serverWithoutAlertClient.inject(addPrepackagedRulesRequest());
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
|
||||
test('returns 404 if alertClient and actionClient are both not available on the route', async () => {
|
||||
const {
|
||||
serverWithoutActionOrAlertClient,
|
||||
} = createMockServerWithoutActionOrAlertClientDecoration();
|
||||
createRulesRoute(serverWithoutActionOrAlertClient);
|
||||
const { statusCode } = await serverWithoutActionOrAlertClient.inject(
|
||||
addPrepackagedRulesRequest()
|
||||
);
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validation', () => {
|
||||
test('it returns a 400 if the index does not exist', async () => {
|
||||
elasticsearch.getCluster = getMockEmptyIndex();
|
||||
alertsClient.find.mockResolvedValue(getFindResult());
|
||||
alertsClient.get.mockResolvedValue(getResult());
|
||||
actionsClient.create.mockResolvedValue(createActionResult());
|
||||
alertsClient.create.mockResolvedValue(getResult());
|
||||
const { payload } = await server.inject(addPrepackagedRulesRequest());
|
||||
expect(JSON.parse(payload)).toEqual({
|
||||
error: 'Bad Request',
|
||||
message:
|
||||
'Pre-packaged rules cannot be installed until the space index is created: .siem-signals-default',
|
||||
statusCode: 400,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('payload', () => {
|
||||
test('1 rule is installed and 0 are updated when find results are empty', async () => {
|
||||
alertsClient.find.mockResolvedValue(getFindResult());
|
||||
alertsClient.get.mockResolvedValue(getResult());
|
||||
actionsClient.create.mockResolvedValue(createActionResult());
|
||||
alertsClient.create.mockResolvedValue(getResult());
|
||||
const { payload } = await server.inject(addPrepackagedRulesRequest());
|
||||
expect(JSON.parse(payload)).toEqual({
|
||||
rules_installed: 1,
|
||||
rules_updated: 0,
|
||||
});
|
||||
});
|
||||
|
||||
test('1 rule is updated and 0 are installed when we return a single find and the versions are different', async () => {
|
||||
alertsClient.find.mockResolvedValue(getFindResultWithSingleHit());
|
||||
alertsClient.get.mockResolvedValue(getResult());
|
||||
actionsClient.create.mockResolvedValue(createActionResult());
|
||||
alertsClient.create.mockResolvedValue(getResult());
|
||||
const { payload } = await server.inject(addPrepackagedRulesRequest());
|
||||
expect(JSON.parse(payload)).toEqual({
|
||||
rules_installed: 0,
|
||||
rules_updated: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -45,15 +45,15 @@ export const createAddPrepackedRulesRoute = (server: ServerFacade): Hapi.ServerR
|
|||
const callWithRequest = callWithRequestFactory(request, server);
|
||||
const rulesFromFileSystem = getPrepackagedRules();
|
||||
|
||||
const prepackedRules = await getExistingPrepackagedRules({ alertsClient });
|
||||
const rulesToInstall = getRulesToInstall(rulesFromFileSystem, prepackedRules);
|
||||
const rulesToUpdate = getRulesToUpdate(rulesFromFileSystem, prepackedRules);
|
||||
const prepackagedRules = await getExistingPrepackagedRules({ alertsClient });
|
||||
const rulesToInstall = getRulesToInstall(rulesFromFileSystem, prepackagedRules);
|
||||
const rulesToUpdate = getRulesToUpdate(rulesFromFileSystem, prepackagedRules);
|
||||
|
||||
const spaceIndex = getIndex(request, server);
|
||||
if (rulesToInstall.length !== 0 || rulesToUpdate.length !== 0) {
|
||||
const spaceIndexExists = await getIndexExists(callWithRequest, spaceIndex);
|
||||
if (!spaceIndexExists) {
|
||||
throw new Boom(
|
||||
return Boom.badRequest(
|
||||
`Pre-packaged rules cannot be installed until the space index is created: ${spaceIndex}`
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,10 +9,10 @@ import {
|
|||
createMockServerWithoutActionClientDecoration,
|
||||
createMockServerWithoutAlertClientDecoration,
|
||||
createMockServerWithoutActionOrAlertClientDecoration,
|
||||
getMockEmptyIndex,
|
||||
} from '../__mocks__/_mock_server';
|
||||
import { createRulesRoute } from './create_rules_route';
|
||||
import { ServerInjectOptions } from 'hapi';
|
||||
import { ServerFacade } from '../../../../types';
|
||||
import {
|
||||
getFindResult,
|
||||
getResult,
|
||||
|
@ -29,11 +29,7 @@ describe('create_rules_bulk', () => {
|
|||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
({ server, alertsClient, actionsClient, elasticsearch } = createMockServer());
|
||||
elasticsearch.getCluster = jest.fn().mockImplementation(() => ({
|
||||
callWithRequest: jest.fn().mockImplementation(() => true),
|
||||
}));
|
||||
|
||||
createRulesBulkRoute((server as unknown) as ServerFacade);
|
||||
createRulesBulkRoute(server);
|
||||
});
|
||||
|
||||
describe('status codes with actionClient and alertClient', () => {
|
||||
|
@ -48,14 +44,14 @@ describe('create_rules_bulk', () => {
|
|||
|
||||
test('returns 404 if actionClient is not available on the route', async () => {
|
||||
const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration();
|
||||
createRulesRoute((serverWithoutActionClient as unknown) as ServerFacade);
|
||||
createRulesRoute(serverWithoutActionClient);
|
||||
const { statusCode } = await serverWithoutActionClient.inject(getReadBulkRequest());
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
|
||||
test('returns 404 if alertClient is not available on the route', async () => {
|
||||
const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration();
|
||||
createRulesRoute((serverWithoutAlertClient as unknown) as ServerFacade);
|
||||
createRulesRoute(serverWithoutAlertClient);
|
||||
const { statusCode } = await serverWithoutAlertClient.inject(getReadBulkRequest());
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
|
@ -64,13 +60,32 @@ describe('create_rules_bulk', () => {
|
|||
const {
|
||||
serverWithoutActionOrAlertClient,
|
||||
} = createMockServerWithoutActionOrAlertClientDecoration();
|
||||
createRulesRoute((serverWithoutActionOrAlertClient as unknown) as ServerFacade);
|
||||
createRulesRoute(serverWithoutActionOrAlertClient);
|
||||
const { statusCode } = await serverWithoutActionOrAlertClient.inject(getReadBulkRequest());
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validation', () => {
|
||||
test('it gets a 409 if the index does not exist', async () => {
|
||||
elasticsearch.getCluster = getMockEmptyIndex();
|
||||
alertsClient.find.mockResolvedValue(getFindResult());
|
||||
alertsClient.get.mockResolvedValue(getResult());
|
||||
actionsClient.create.mockResolvedValue(createActionResult());
|
||||
alertsClient.create.mockResolvedValue(getResult());
|
||||
const { payload } = await server.inject(getReadBulkRequest());
|
||||
expect(JSON.parse(payload)).toEqual([
|
||||
{
|
||||
error: {
|
||||
message:
|
||||
'To create a rule, the index must exist first. Index .siem-signals does not exist',
|
||||
status_code: 400,
|
||||
},
|
||||
rule_id: 'rule-1',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('returns 200 if rule_id is not given as the id is auto generated from the alert framework', async () => {
|
||||
alertsClient.find.mockResolvedValue(getFindResult());
|
||||
alertsClient.get.mockResolvedValue(getResult());
|
||||
|
|
|
@ -87,7 +87,7 @@ export const createCreateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou
|
|||
if (!indexExists) {
|
||||
return createBulkErrorObject({
|
||||
ruleId: ruleIdOrUuid,
|
||||
statusCode: 409,
|
||||
statusCode: 400,
|
||||
message: `To create a rule, the index must exist first. Index ${finalIndex} does not exist`,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -9,10 +9,11 @@ import {
|
|||
createMockServerWithoutActionClientDecoration,
|
||||
createMockServerWithoutAlertClientDecoration,
|
||||
createMockServerWithoutActionOrAlertClientDecoration,
|
||||
getMockNonEmptyIndex,
|
||||
getMockEmptyIndex,
|
||||
} from '../__mocks__/_mock_server';
|
||||
import { createRulesRoute } from './create_rules_route';
|
||||
import { ServerInjectOptions } from 'hapi';
|
||||
import { ServerFacade } from '../../../../types';
|
||||
|
||||
import {
|
||||
getFindResult,
|
||||
|
@ -42,13 +43,8 @@ describe('create_rules', () => {
|
|||
elasticsearch,
|
||||
savedObjectsClient,
|
||||
} = createMockServer());
|
||||
elasticsearch.getCluster = jest.fn().mockImplementation(() => ({
|
||||
callWithRequest: jest
|
||||
.fn()
|
||||
.mockImplementation((endpoint, params) => ({ _shards: { total: 1 } })),
|
||||
}));
|
||||
|
||||
createRulesRoute((server as unknown) as ServerFacade);
|
||||
elasticsearch.getCluster = getMockNonEmptyIndex();
|
||||
createRulesRoute(server);
|
||||
});
|
||||
|
||||
describe('status codes with actionClient and alertClient', () => {
|
||||
|
@ -64,14 +60,14 @@ describe('create_rules', () => {
|
|||
|
||||
test('returns 404 if actionClient is not available on the route', async () => {
|
||||
const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration();
|
||||
createRulesRoute((serverWithoutActionClient as unknown) as ServerFacade);
|
||||
createRulesRoute(serverWithoutActionClient);
|
||||
const { statusCode } = await serverWithoutActionClient.inject(getCreateRequest());
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
|
||||
test('returns 404 if alertClient is not available on the route', async () => {
|
||||
const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration();
|
||||
createRulesRoute((serverWithoutAlertClient as unknown) as ServerFacade);
|
||||
createRulesRoute(serverWithoutAlertClient);
|
||||
const { statusCode } = await serverWithoutAlertClient.inject(getCreateRequest());
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
|
@ -80,13 +76,27 @@ describe('create_rules', () => {
|
|||
const {
|
||||
serverWithoutActionOrAlertClient,
|
||||
} = createMockServerWithoutActionOrAlertClientDecoration();
|
||||
createRulesRoute((serverWithoutActionOrAlertClient as unknown) as ServerFacade);
|
||||
createRulesRoute(serverWithoutActionOrAlertClient);
|
||||
const { statusCode } = await serverWithoutActionOrAlertClient.inject(getCreateRequest());
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validation', () => {
|
||||
test('it returns a 400 if the index does not exist', async () => {
|
||||
elasticsearch.getCluster = getMockEmptyIndex();
|
||||
alertsClient.find.mockResolvedValue(getFindResult());
|
||||
alertsClient.get.mockResolvedValue(getResult());
|
||||
actionsClient.create.mockResolvedValue(createActionResult());
|
||||
alertsClient.create.mockResolvedValue(getResult());
|
||||
const { payload } = await server.inject(getCreateRequest());
|
||||
expect(JSON.parse(payload)).toEqual({
|
||||
error: 'Bad Request',
|
||||
message: 'To create a rule, the index must exist first. Index .siem-signals does not exist',
|
||||
statusCode: 400,
|
||||
});
|
||||
});
|
||||
|
||||
test('returns 200 if rule_id is not given as the id is auto generated from the alert framework', async () => {
|
||||
alertsClient.find.mockResolvedValue(getFindResult());
|
||||
alertsClient.get.mockResolvedValue(getResult());
|
||||
|
|
|
@ -78,17 +78,14 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute =
|
|||
const callWithRequest = callWithRequestFactory(request, server);
|
||||
const indexExists = await getIndexExists(callWithRequest, finalIndex);
|
||||
if (!indexExists) {
|
||||
return new Boom(
|
||||
`To create a rule, the index must exist first. Index ${finalIndex} does not exist`,
|
||||
{
|
||||
statusCode: 400,
|
||||
}
|
||||
return Boom.badRequest(
|
||||
`To create a rule, the index must exist first. Index ${finalIndex} does not exist`
|
||||
);
|
||||
}
|
||||
if (ruleId != null) {
|
||||
const rule = await readRules({ alertsClient, ruleId });
|
||||
if (rule != null) {
|
||||
return new Boom(`rule_id: "${ruleId}" already exists`, { statusCode: 409 });
|
||||
return Boom.conflict(`rule_id: "${ruleId}" already exists`);
|
||||
}
|
||||
}
|
||||
const createdRule = await createRules({
|
||||
|
|
|
@ -22,7 +22,6 @@ import {
|
|||
getDeleteAsPostBulkRequestById,
|
||||
getFindResultStatus,
|
||||
} from '../__mocks__/request_responses';
|
||||
import { ServerFacade } from '../../../../types';
|
||||
import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants';
|
||||
|
||||
import { deleteRulesBulkRoute } from './delete_rules_bulk_route';
|
||||
|
@ -33,7 +32,7 @@ describe('delete_rules', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
({ server, alertsClient, savedObjectsClient } = createMockServer());
|
||||
deleteRulesBulkRoute((server as unknown) as ServerFacade);
|
||||
deleteRulesBulkRoute(server);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -100,14 +99,14 @@ describe('delete_rules', () => {
|
|||
|
||||
test('returns 404 if actionClient is not available on the route', async () => {
|
||||
const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration();
|
||||
deleteRulesBulkRoute((serverWithoutActionClient as unknown) as ServerFacade);
|
||||
deleteRulesBulkRoute(serverWithoutActionClient);
|
||||
const { statusCode } = await serverWithoutActionClient.inject(getDeleteBulkRequest());
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
|
||||
test('returns 404 if alertClient is not available on the route', async () => {
|
||||
const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration();
|
||||
deleteRulesBulkRoute((serverWithoutAlertClient as unknown) as ServerFacade);
|
||||
deleteRulesBulkRoute(serverWithoutAlertClient);
|
||||
const { statusCode } = await serverWithoutAlertClient.inject(getDeleteBulkRequest());
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
|
@ -116,7 +115,7 @@ describe('delete_rules', () => {
|
|||
const {
|
||||
serverWithoutActionOrAlertClient,
|
||||
} = createMockServerWithoutActionOrAlertClientDecoration();
|
||||
deleteRulesBulkRoute((serverWithoutActionOrAlertClient as unknown) as ServerFacade);
|
||||
deleteRulesBulkRoute(serverWithoutActionOrAlertClient);
|
||||
const { statusCode } = await serverWithoutActionOrAlertClient.inject(getDeleteBulkRequest());
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
|
|
|
@ -13,7 +13,6 @@ import {
|
|||
|
||||
import { deleteRulesRoute } from './delete_rules_route';
|
||||
import { ServerInjectOptions } from 'hapi';
|
||||
import { ServerFacade } from '../../../../types';
|
||||
|
||||
import {
|
||||
getFindResult,
|
||||
|
@ -30,7 +29,7 @@ describe('delete_rules', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
({ server, alertsClient, savedObjectsClient } = createMockServer());
|
||||
deleteRulesRoute((server as unknown) as ServerFacade);
|
||||
deleteRulesRoute(server);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -70,14 +69,14 @@ describe('delete_rules', () => {
|
|||
|
||||
test('returns 404 if actionClient is not available on the route', async () => {
|
||||
const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration();
|
||||
deleteRulesRoute((serverWithoutActionClient as unknown) as ServerFacade);
|
||||
deleteRulesRoute(serverWithoutActionClient);
|
||||
const { statusCode } = await serverWithoutActionClient.inject(getDeleteRequest());
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
|
||||
test('returns 404 if alertClient is not available on the route', async () => {
|
||||
const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration();
|
||||
deleteRulesRoute((serverWithoutAlertClient as unknown) as ServerFacade);
|
||||
deleteRulesRoute(serverWithoutAlertClient);
|
||||
const { statusCode } = await serverWithoutAlertClient.inject(getDeleteRequest());
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
|
@ -86,7 +85,7 @@ describe('delete_rules', () => {
|
|||
const {
|
||||
serverWithoutActionOrAlertClient,
|
||||
} = createMockServerWithoutActionOrAlertClientDecoration();
|
||||
deleteRulesRoute((serverWithoutActionOrAlertClient as unknown) as ServerFacade);
|
||||
deleteRulesRoute(serverWithoutActionOrAlertClient);
|
||||
const { statusCode } = await serverWithoutActionOrAlertClient.inject(getDeleteRequest());
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
|
|
|
@ -13,7 +13,6 @@ import {
|
|||
|
||||
import { findRulesRoute } from './find_rules_route';
|
||||
import { ServerInjectOptions } from 'hapi';
|
||||
import { ServerFacade } from '../../../../types';
|
||||
|
||||
import { getFindResult, getResult, getFindRequest } from '../__mocks__/request_responses';
|
||||
import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants';
|
||||
|
@ -23,7 +22,7 @@ describe('find_rules', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
({ server, alertsClient, actionsClient } = createMockServer());
|
||||
findRulesRoute((server as unknown) as ServerFacade);
|
||||
findRulesRoute(server);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -46,14 +45,14 @@ describe('find_rules', () => {
|
|||
|
||||
test('returns 404 if actionClient is not available on the route', async () => {
|
||||
const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration();
|
||||
findRulesRoute((serverWithoutActionClient as unknown) as ServerFacade);
|
||||
findRulesRoute(serverWithoutActionClient);
|
||||
const { statusCode } = await serverWithoutActionClient.inject(getFindRequest());
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
|
||||
test('returns 404 if alertClient is not available on the route', async () => {
|
||||
const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration();
|
||||
findRulesRoute((serverWithoutAlertClient as unknown) as ServerFacade);
|
||||
findRulesRoute(serverWithoutAlertClient);
|
||||
const { statusCode } = await serverWithoutAlertClient.inject(getFindRequest());
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
|
@ -62,7 +61,7 @@ describe('find_rules', () => {
|
|||
const {
|
||||
serverWithoutActionOrAlertClient,
|
||||
} = createMockServerWithoutActionOrAlertClientDecoration();
|
||||
findRulesRoute((serverWithoutActionOrAlertClient as unknown) as ServerFacade);
|
||||
findRulesRoute(serverWithoutActionOrAlertClient);
|
||||
const { statusCode } = await serverWithoutActionOrAlertClient.inject(getFindRequest());
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
createMockServer,
|
||||
createMockServerWithoutActionClientDecoration,
|
||||
createMockServerWithoutAlertClientDecoration,
|
||||
createMockServerWithoutActionOrAlertClientDecoration,
|
||||
getMockNonEmptyIndex,
|
||||
} from '../__mocks__/_mock_server';
|
||||
import { createRulesRoute } from './create_rules_route';
|
||||
import {
|
||||
getFindResult,
|
||||
getResult,
|
||||
createActionResult,
|
||||
getFindResultWithSingleHit,
|
||||
getPrepackagedRulesStatusRequest,
|
||||
} from '../__mocks__/request_responses';
|
||||
|
||||
jest.mock('../../rules/get_prepackaged_rules', () => {
|
||||
return {
|
||||
getPrepackagedRules: () => {
|
||||
return [
|
||||
{
|
||||
rule_id: 'rule-1',
|
||||
output_index: '.siem-signals',
|
||||
risk_score: 50,
|
||||
description: 'some description',
|
||||
from: 'now-5m',
|
||||
to: 'now',
|
||||
index: ['index-1'],
|
||||
name: 'some-name',
|
||||
severity: 'severity',
|
||||
interval: '5m',
|
||||
type: 'query',
|
||||
version: 2, // set one higher than the mocks which is set to 1 to trigger updates
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
import { getPrepackagedRulesStatusRoute } from './get_prepackaged_rules_status_route';
|
||||
|
||||
describe('get_prepackaged_rule_status_route', () => {
|
||||
let { server, alertsClient, actionsClient, elasticsearch } = createMockServer();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
({ server, alertsClient, actionsClient, elasticsearch } = createMockServer());
|
||||
elasticsearch.getCluster = getMockNonEmptyIndex();
|
||||
getPrepackagedRulesStatusRoute(server);
|
||||
});
|
||||
|
||||
describe('status codes with actionClient and alertClient', () => {
|
||||
test('returns 200 when creating a with a valid actionClient and alertClient', async () => {
|
||||
alertsClient.find.mockResolvedValue(getFindResult());
|
||||
alertsClient.get.mockResolvedValue(getResult());
|
||||
actionsClient.create.mockResolvedValue(createActionResult());
|
||||
alertsClient.create.mockResolvedValue(getResult());
|
||||
const { statusCode } = await server.inject(getPrepackagedRulesStatusRequest());
|
||||
expect(statusCode).toBe(200);
|
||||
});
|
||||
|
||||
test('returns 404 if actionClient is not available on the route', async () => {
|
||||
const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration();
|
||||
createRulesRoute(serverWithoutActionClient);
|
||||
const { statusCode } = await serverWithoutActionClient.inject(
|
||||
getPrepackagedRulesStatusRequest()
|
||||
);
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
|
||||
test('returns 404 if alertClient is not available on the route', async () => {
|
||||
const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration();
|
||||
createRulesRoute(serverWithoutAlertClient);
|
||||
const { statusCode } = await serverWithoutAlertClient.inject(
|
||||
getPrepackagedRulesStatusRequest()
|
||||
);
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
|
||||
test('returns 404 if alertClient and actionClient are both not available on the route', async () => {
|
||||
const {
|
||||
serverWithoutActionOrAlertClient,
|
||||
} = createMockServerWithoutActionOrAlertClientDecoration();
|
||||
createRulesRoute(serverWithoutActionOrAlertClient);
|
||||
const { statusCode } = await serverWithoutActionOrAlertClient.inject(
|
||||
getPrepackagedRulesStatusRequest()
|
||||
);
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe('payload', () => {
|
||||
test('0 rules installed, 1 rules not installed, and 1 rule not updated', async () => {
|
||||
alertsClient.find.mockResolvedValue(getFindResult());
|
||||
alertsClient.get.mockResolvedValue(getResult());
|
||||
actionsClient.create.mockResolvedValue(createActionResult());
|
||||
alertsClient.create.mockResolvedValue(getResult());
|
||||
const { payload } = await server.inject(getPrepackagedRulesStatusRequest());
|
||||
expect(JSON.parse(payload)).toEqual({
|
||||
rules_installed: 0,
|
||||
rules_not_installed: 1,
|
||||
rules_not_updated: 0,
|
||||
});
|
||||
});
|
||||
|
||||
test('1 rule installed, 0 rules not installed, and 1 rule to not updated', async () => {
|
||||
alertsClient.find.mockResolvedValue(getFindResultWithSingleHit());
|
||||
alertsClient.get.mockResolvedValue(getResult());
|
||||
actionsClient.create.mockResolvedValue(createActionResult());
|
||||
alertsClient.create.mockResolvedValue(getResult());
|
||||
const { payload } = await server.inject(getPrepackagedRulesStatusRequest());
|
||||
expect(JSON.parse(payload)).toEqual({
|
||||
rules_installed: 1,
|
||||
rules_not_installed: 0,
|
||||
rules_not_updated: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import Hapi from 'hapi';
|
||||
import { isFunction } from 'lodash/fp';
|
||||
|
||||
import { DETECTION_ENGINE_PREPACKAGED_URL } from '../../../../../common/constants';
|
||||
import { ServerFacade, RequestFacade } from '../../../../types';
|
||||
import { transformError } from '../utils';
|
||||
import { getPrepackagedRules } from '../../rules/get_prepackaged_rules';
|
||||
import { getRulesToInstall } from '../../rules/get_rules_to_install';
|
||||
import { getRulesToUpdate } from '../../rules/get_rules_to_update';
|
||||
import { getExistingPrepackagedRules } from '../../rules/get_existing_prepackaged_rules';
|
||||
|
||||
export const createGetPrepackagedRulesStatusRoute = (): Hapi.ServerRoute => {
|
||||
return {
|
||||
method: 'GET',
|
||||
path: `${DETECTION_ENGINE_PREPACKAGED_URL}/_status`,
|
||||
options: {
|
||||
tags: ['access:siem'],
|
||||
validate: {
|
||||
options: {
|
||||
abortEarly: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
async handler(request: RequestFacade, headers) {
|
||||
const alertsClient = isFunction(request.getAlertsClient) ? request.getAlertsClient() : null;
|
||||
const actionsClient = isFunction(request.getActionsClient)
|
||||
? request.getActionsClient()
|
||||
: null;
|
||||
|
||||
if (!alertsClient || !actionsClient) {
|
||||
return headers.response().code(404);
|
||||
}
|
||||
|
||||
try {
|
||||
const rulesFromFileSystem = getPrepackagedRules();
|
||||
const prepackagedRules = await getExistingPrepackagedRules({ alertsClient });
|
||||
const rulesToInstall = getRulesToInstall(rulesFromFileSystem, prepackagedRules);
|
||||
const rulesToUpdate = getRulesToUpdate(rulesFromFileSystem, prepackagedRules);
|
||||
return {
|
||||
rules_installed: prepackagedRules.length,
|
||||
rules_not_installed: rulesToInstall.length,
|
||||
rules_not_updated: rulesToUpdate.length,
|
||||
};
|
||||
} catch (err) {
|
||||
return transformError(err);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getPrepackagedRulesStatusRoute = (server: ServerFacade): void => {
|
||||
server.route(createGetPrepackagedRulesStatusRoute());
|
||||
};
|
|
@ -13,7 +13,6 @@ import {
|
|||
|
||||
import { readRulesRoute } from './read_rules_route';
|
||||
import { ServerInjectOptions } from 'hapi';
|
||||
import { ServerFacade } from '../../../../types';
|
||||
|
||||
import {
|
||||
getFindResult,
|
||||
|
@ -29,7 +28,7 @@ describe('read_signals', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
({ server, alertsClient, savedObjectsClient } = createMockServer());
|
||||
readRulesRoute((server as unknown) as ServerFacade);
|
||||
readRulesRoute(server);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -47,14 +46,14 @@ describe('read_signals', () => {
|
|||
|
||||
test('returns 404 if actionClient is not available on the route', async () => {
|
||||
const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration();
|
||||
readRulesRoute((serverWithoutActionClient as unknown) as ServerFacade);
|
||||
readRulesRoute(serverWithoutActionClient);
|
||||
const { statusCode } = await serverWithoutActionClient.inject(getReadRequest());
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
|
||||
test('returns 404 if alertClient is not available on the route', async () => {
|
||||
const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration();
|
||||
readRulesRoute((serverWithoutAlertClient as unknown) as ServerFacade);
|
||||
readRulesRoute(serverWithoutAlertClient);
|
||||
const { statusCode } = await serverWithoutAlertClient.inject(getReadRequest());
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
|
@ -63,7 +62,7 @@ describe('read_signals', () => {
|
|||
const {
|
||||
serverWithoutActionOrAlertClient,
|
||||
} = createMockServerWithoutActionOrAlertClientDecoration();
|
||||
readRulesRoute((serverWithoutActionOrAlertClient as unknown) as ServerFacade);
|
||||
readRulesRoute(serverWithoutActionOrAlertClient);
|
||||
const { statusCode } = await serverWithoutActionOrAlertClient.inject(getReadRequest());
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
|
|
|
@ -13,7 +13,6 @@ import {
|
|||
|
||||
import { updateRulesRoute } from './update_rules_route';
|
||||
import { ServerInjectOptions } from 'hapi';
|
||||
import { ServerFacade } from '../../../../types';
|
||||
|
||||
import {
|
||||
getFindResult,
|
||||
|
@ -33,7 +32,7 @@ describe('update_rules_bulk', () => {
|
|||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
({ server, alertsClient, actionsClient } = createMockServer());
|
||||
updateRulesBulkRoute((server as unknown) as ServerFacade);
|
||||
updateRulesBulkRoute(server);
|
||||
});
|
||||
|
||||
describe('status codes with actionClient and alertClient', () => {
|
||||
|
@ -73,14 +72,14 @@ describe('update_rules_bulk', () => {
|
|||
|
||||
test('returns 404 if actionClient is not available on the route', async () => {
|
||||
const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration();
|
||||
updateRulesRoute((serverWithoutActionClient as unknown) as ServerFacade);
|
||||
updateRulesRoute(serverWithoutActionClient);
|
||||
const { statusCode } = await serverWithoutActionClient.inject(getUpdateBulkRequest());
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
|
||||
test('returns 404 if alertClient is not available on the route', async () => {
|
||||
const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration();
|
||||
updateRulesRoute((serverWithoutAlertClient as unknown) as ServerFacade);
|
||||
updateRulesRoute(serverWithoutAlertClient);
|
||||
const { statusCode } = await serverWithoutAlertClient.inject(getUpdateBulkRequest());
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
|
@ -89,7 +88,7 @@ describe('update_rules_bulk', () => {
|
|||
const {
|
||||
serverWithoutActionOrAlertClient,
|
||||
} = createMockServerWithoutActionOrAlertClientDecoration();
|
||||
updateRulesRoute((serverWithoutActionOrAlertClient as unknown) as ServerFacade);
|
||||
updateRulesRoute(serverWithoutActionOrAlertClient);
|
||||
const { statusCode } = await serverWithoutActionOrAlertClient.inject(getUpdateBulkRequest());
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
|
|
|
@ -13,7 +13,6 @@ import {
|
|||
|
||||
import { updateRulesRoute } from './update_rules_route';
|
||||
import { ServerInjectOptions } from 'hapi';
|
||||
import { ServerFacade } from '../../../../types';
|
||||
|
||||
import {
|
||||
getFindResult,
|
||||
|
@ -32,7 +31,7 @@ describe('update_rules', () => {
|
|||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
({ server, alertsClient, actionsClient, savedObjectsClient } = createMockServer());
|
||||
updateRulesRoute((server as unknown) as ServerFacade);
|
||||
updateRulesRoute(server);
|
||||
});
|
||||
|
||||
describe('status codes with actionClient and alertClient', () => {
|
||||
|
@ -58,14 +57,14 @@ describe('update_rules', () => {
|
|||
|
||||
test('returns 404 if actionClient is not available on the route', async () => {
|
||||
const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration();
|
||||
updateRulesRoute((serverWithoutActionClient as unknown) as ServerFacade);
|
||||
updateRulesRoute(serverWithoutActionClient);
|
||||
const { statusCode } = await serverWithoutActionClient.inject(getUpdateRequest());
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
|
||||
test('returns 404 if alertClient is not available on the route', async () => {
|
||||
const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration();
|
||||
updateRulesRoute((serverWithoutAlertClient as unknown) as ServerFacade);
|
||||
updateRulesRoute(serverWithoutAlertClient);
|
||||
const { statusCode } = await serverWithoutAlertClient.inject(getUpdateRequest());
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
|
@ -74,7 +73,7 @@ describe('update_rules', () => {
|
|||
const {
|
||||
serverWithoutActionOrAlertClient,
|
||||
} = createMockServerWithoutActionOrAlertClientDecoration();
|
||||
updateRulesRoute((serverWithoutActionOrAlertClient as unknown) as ServerFacade);
|
||||
updateRulesRoute(serverWithoutActionOrAlertClient);
|
||||
const { statusCode } = await serverWithoutActionOrAlertClient.inject(getUpdateRequest());
|
||||
expect(statusCode).toBe(404);
|
||||
});
|
||||
|
|
|
@ -34,11 +34,11 @@ export const getIdError = ({
|
|||
ruleId: string | undefined | null;
|
||||
}) => {
|
||||
if (id != null) {
|
||||
return new Boom(`id: "${id}" not found`, { statusCode: 404 });
|
||||
return Boom.notFound(`id: "${id}" not found`);
|
||||
} else if (ruleId != null) {
|
||||
return new Boom(`rule_id: "${ruleId}" not found`, { statusCode: 404 });
|
||||
return Boom.notFound(`rule_id: "${ruleId}" not found`);
|
||||
} else {
|
||||
return new Boom(`id or rule_id should have been defined`, { statusCode: 404 });
|
||||
return Boom.notFound('id or rule_id should have been defined');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -205,8 +205,8 @@ describe('add prepackaged rules schema', () => {
|
|||
query: 'some query',
|
||||
language: 'kuery',
|
||||
version: 1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('"output_index" is not allowed');
|
||||
});
|
||||
|
||||
test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, version] does validate', () => {
|
||||
|
@ -345,6 +345,48 @@ describe('add prepackaged rules schema', () => {
|
|||
).toEqual(true);
|
||||
});
|
||||
|
||||
test('immutable cannot be false', () => {
|
||||
expect(
|
||||
addPrepackagedRulesSchema.validate<Partial<RuleAlertParamsRest>>({
|
||||
rule_id: 'rule-1',
|
||||
risk_score: 50,
|
||||
description: 'some description',
|
||||
from: 'now-5m',
|
||||
to: 'now',
|
||||
index: ['index-1'],
|
||||
immutable: false,
|
||||
name: 'some-name',
|
||||
severity: 'severity',
|
||||
interval: '5m',
|
||||
type: 'query',
|
||||
query: 'some-query',
|
||||
language: 'kuery',
|
||||
version: 1,
|
||||
}).error.message
|
||||
).toEqual('child "immutable" fails because ["immutable" must be one of [true]]');
|
||||
});
|
||||
|
||||
test('immutable can be true', () => {
|
||||
expect(
|
||||
addPrepackagedRulesSchema.validate<Partial<RuleAlertParamsRest>>({
|
||||
rule_id: 'rule-1',
|
||||
risk_score: 50,
|
||||
description: 'some description',
|
||||
from: 'now-5m',
|
||||
to: 'now',
|
||||
index: ['index-1'],
|
||||
immutable: true,
|
||||
name: 'some-name',
|
||||
severity: 'severity',
|
||||
interval: '5m',
|
||||
type: 'query',
|
||||
query: 'some-query',
|
||||
language: 'kuery',
|
||||
version: 1,
|
||||
}).error
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
test('defaults enabled to false', () => {
|
||||
expect(
|
||||
addPrepackagedRulesSchema.validate<Partial<RuleAlertParamsRest>>({
|
||||
|
@ -380,8 +422,8 @@ describe('add prepackaged rules schema', () => {
|
|||
query: 'some-query',
|
||||
language: 'kuery',
|
||||
version: 1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "rule_id" fails because ["rule_id" is required]');
|
||||
});
|
||||
|
||||
test('references cannot be numbers', () => {
|
||||
|
@ -403,8 +445,10 @@ describe('add prepackaged rules schema', () => {
|
|||
language: 'kuery',
|
||||
references: [5],
|
||||
version: 1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "references" fails because ["references" at position 0 fails because ["0" must be a string]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('indexes cannot be numbers', () => {
|
||||
|
@ -425,8 +469,10 @@ describe('add prepackaged rules schema', () => {
|
|||
query: 'some-query',
|
||||
language: 'kuery',
|
||||
version: 1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "index" fails because ["index" at position 0 fails because ["0" must be a string]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('defaults interval to 5 min', () => {
|
||||
|
@ -478,8 +524,8 @@ describe('add prepackaged rules schema', () => {
|
|||
interval: '5m',
|
||||
type: 'saved_query',
|
||||
version: 1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "saved_id" fails because ["saved_id" is required]');
|
||||
});
|
||||
|
||||
test('saved_id is required when type is saved_query and validates with it', () => {
|
||||
|
@ -539,8 +585,8 @@ describe('add prepackaged rules schema', () => {
|
|||
saved_id: 'some id',
|
||||
filters: 'some string',
|
||||
version: 1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "filters" fails because ["filters" must be an array]');
|
||||
});
|
||||
|
||||
test('language validates with kuery', () => {
|
||||
|
@ -602,8 +648,8 @@ describe('add prepackaged rules schema', () => {
|
|||
query: 'some query',
|
||||
language: 'something-made-up',
|
||||
version: 1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "language" fails because ["language" must be one of [kuery, lucene]]');
|
||||
});
|
||||
|
||||
test('max_signals cannot be negative', () => {
|
||||
|
@ -624,8 +670,8 @@ describe('add prepackaged rules schema', () => {
|
|||
language: 'kuery',
|
||||
max_signals: -1,
|
||||
version: 1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "max_signals" fails because ["max_signals" must be greater than 0]');
|
||||
});
|
||||
|
||||
test('max_signals cannot be zero', () => {
|
||||
|
@ -646,8 +692,8 @@ describe('add prepackaged rules schema', () => {
|
|||
language: 'kuery',
|
||||
max_signals: 0,
|
||||
version: 1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "max_signals" fails because ["max_signals" must be greater than 0]');
|
||||
});
|
||||
|
||||
test('max_signals can be 1', () => {
|
||||
|
@ -716,8 +762,10 @@ describe('add prepackaged rules schema', () => {
|
|||
max_signals: 1,
|
||||
tags: [0, 1, 2],
|
||||
version: 1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "tags" fails because ["tags" at position 0 fails because ["0" must be a string]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('You cannot send in an array of threats that are missing "framework"', () => {
|
||||
|
@ -758,9 +806,12 @@ describe('add prepackaged rules schema', () => {
|
|||
},
|
||||
],
|
||||
version: 1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "threats" fails because ["threats" at position 0 fails because [child "framework" fails because ["framework" is required]]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('You cannot send in an array of threats that are missing "tactic"', () => {
|
||||
expect(
|
||||
addPrepackagedRulesSchema.validate<
|
||||
|
@ -795,9 +846,12 @@ describe('add prepackaged rules schema', () => {
|
|||
},
|
||||
],
|
||||
version: 1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "threats" fails because ["threats" at position 0 fails because [child "tactic" fails because ["tactic" is required]]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('You cannot send in an array of threats that are missing "techniques"', () => {
|
||||
expect(
|
||||
addPrepackagedRulesSchema.validate<
|
||||
|
@ -830,8 +884,10 @@ describe('add prepackaged rules schema', () => {
|
|||
},
|
||||
],
|
||||
version: 1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "threats" fails because ["threats" at position 0 fails because [child "techniques" fails because ["techniques" is required]]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('You can optionally send in an array of false positives', () => {
|
||||
|
@ -878,8 +934,10 @@ describe('add prepackaged rules schema', () => {
|
|||
language: 'kuery',
|
||||
max_signals: 1,
|
||||
version: 1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "false_positives" fails because ["false_positives" at position 0 fails because ["0" must be a string]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('You can optionally set the immutable to be true', () => {
|
||||
|
@ -926,8 +984,8 @@ describe('add prepackaged rules schema', () => {
|
|||
language: 'kuery',
|
||||
max_signals: 1,
|
||||
version: 1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "immutable" fails because ["immutable" must be a boolean]');
|
||||
});
|
||||
|
||||
test('You cannot set the risk_score to 101', () => {
|
||||
|
@ -949,8 +1007,8 @@ describe('add prepackaged rules schema', () => {
|
|||
language: 'kuery',
|
||||
max_signals: 1,
|
||||
version: 1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "risk_score" fails because ["risk_score" must be less than 101]');
|
||||
});
|
||||
|
||||
test('You cannot set the risk_score to -1', () => {
|
||||
|
@ -972,8 +1030,8 @@ describe('add prepackaged rules schema', () => {
|
|||
language: 'kuery',
|
||||
max_signals: 1,
|
||||
version: 1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "risk_score" fails because ["risk_score" must be greater than -1]');
|
||||
});
|
||||
|
||||
test('You can set the risk_score to 0', () => {
|
||||
|
@ -1070,8 +1128,8 @@ describe('add prepackaged rules schema', () => {
|
|||
max_signals: 1,
|
||||
meta: 'should not work',
|
||||
version: 1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "meta" fails because ["meta" must be an object]');
|
||||
});
|
||||
|
||||
test('You can omit the query string when filters are present', () => {
|
||||
|
@ -1140,8 +1198,8 @@ describe('add prepackaged rules schema', () => {
|
|||
max_signals: 1,
|
||||
version: 1,
|
||||
timeline_id: 'timeline-id',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "timeline_title" fails because ["timeline_title" is required]');
|
||||
});
|
||||
|
||||
test('You cannot have a null value for timeline_title when timeline_id is present', () => {
|
||||
|
@ -1165,8 +1223,8 @@ describe('add prepackaged rules schema', () => {
|
|||
version: 1,
|
||||
timeline_id: 'timeline-id',
|
||||
timeline_title: null,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "timeline_title" fails because ["timeline_title" must be a string]');
|
||||
});
|
||||
|
||||
test('You cannot have empty string for timeline_title when timeline_id is present', () => {
|
||||
|
@ -1190,8 +1248,8 @@ describe('add prepackaged rules schema', () => {
|
|||
version: 1,
|
||||
timeline_id: 'timeline-id',
|
||||
timeline_title: '',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "timeline_title" fails because ["timeline_title" is not allowed to be empty]');
|
||||
});
|
||||
|
||||
test('You cannot have timeline_title with an empty timeline_id', () => {
|
||||
|
@ -1215,8 +1273,8 @@ describe('add prepackaged rules schema', () => {
|
|||
version: 1,
|
||||
timeline_id: '',
|
||||
timeline_title: 'some-title',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "timeline_id" fails because ["timeline_id" is not allowed to be empty]');
|
||||
});
|
||||
|
||||
test('You cannot have timeline_title without timeline_id', () => {
|
||||
|
@ -1239,7 +1297,7 @@ describe('add prepackaged rules schema', () => {
|
|||
max_signals: 1,
|
||||
version: 1,
|
||||
timeline_title: 'some-title',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "timeline_title" fails because ["timeline_title" is not allowed]');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -42,7 +42,7 @@ import { DEFAULT_MAX_SIGNALS } from '../../../../../common/constants';
|
|||
* Big differences between this schema and the createRulesSchema
|
||||
* - rule_id is required here
|
||||
* - output_index is not allowed (and instead the space index must be used)
|
||||
* - immutable defaults to true instead of to false
|
||||
* - immutable defaults to true instead of to false and if it is there can only be true
|
||||
* - enabled defaults to false instead of true
|
||||
* - version is a required field that must exist
|
||||
*/
|
||||
|
@ -53,7 +53,7 @@ export const addPrepackagedRulesSchema = Joi.object({
|
|||
filters,
|
||||
from: from.required(),
|
||||
rule_id: rule_id.required(),
|
||||
immutable: immutable.default(true),
|
||||
immutable: immutable.default(true).valid(true),
|
||||
index,
|
||||
interval: interval.default('5m'),
|
||||
query: query.allow('').default(''),
|
||||
|
|
|
@ -238,6 +238,7 @@ describe('create rules schema', () => {
|
|||
}).error
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
test('You can send in an empty array to threats', () => {
|
||||
expect(
|
||||
createRulesSchema.validate<Partial<RuleAlertParamsRest>>({
|
||||
|
@ -260,6 +261,7 @@ describe('create rules schema', () => {
|
|||
}).error
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index, threats] does validate', () => {
|
||||
expect(
|
||||
createRulesSchema.validate<Partial<RuleAlertParamsRest>>({
|
||||
|
@ -355,8 +357,10 @@ describe('create rules schema', () => {
|
|||
query: 'some-query',
|
||||
language: 'kuery',
|
||||
references: [5],
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "references" fails because ["references" at position 0 fails because ["0" must be a string]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('indexes cannot be numbers', () => {
|
||||
|
@ -377,8 +381,10 @@ describe('create rules schema', () => {
|
|||
query: 'some-query',
|
||||
language: 'kuery',
|
||||
}
|
||||
).error
|
||||
).toBeTruthy();
|
||||
).error.message
|
||||
).toEqual(
|
||||
'child "index" fails because ["index" at position 0 fails because ["0" must be a string]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('defaults interval to 5 min', () => {
|
||||
|
@ -430,8 +436,8 @@ describe('create rules schema', () => {
|
|||
severity: 'severity',
|
||||
interval: '5m',
|
||||
type: 'saved_query',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "saved_id" fails because ["saved_id" is required]');
|
||||
});
|
||||
|
||||
test('saved_id is required when type is saved_query and validates with it', () => {
|
||||
|
@ -491,8 +497,8 @@ describe('create rules schema', () => {
|
|||
type: 'saved_query',
|
||||
saved_id: 'some id',
|
||||
filters: 'some string',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "filters" fails because ["filters" must be an array]');
|
||||
});
|
||||
|
||||
test('language validates with kuery', () => {
|
||||
|
@ -554,8 +560,8 @@ describe('create rules schema', () => {
|
|||
references: ['index-1'],
|
||||
query: 'some query',
|
||||
language: 'something-made-up',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "language" fails because ["language" must be one of [kuery, lucene]]');
|
||||
});
|
||||
|
||||
test('max_signals cannot be negative', () => {
|
||||
|
@ -576,8 +582,8 @@ describe('create rules schema', () => {
|
|||
query: 'some query',
|
||||
language: 'kuery',
|
||||
max_signals: -1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "max_signals" fails because ["max_signals" must be greater than 0]');
|
||||
});
|
||||
|
||||
test('max_signals cannot be zero', () => {
|
||||
|
@ -598,8 +604,8 @@ describe('create rules schema', () => {
|
|||
query: 'some query',
|
||||
language: 'kuery',
|
||||
max_signals: 0,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "max_signals" fails because ["max_signals" must be greater than 0]');
|
||||
});
|
||||
|
||||
test('max_signals can be 1', () => {
|
||||
|
@ -666,8 +672,10 @@ describe('create rules schema', () => {
|
|||
language: 'kuery',
|
||||
max_signals: 1,
|
||||
tags: [0, 1, 2],
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "tags" fails because ["tags" at position 0 fails because ["0" must be a string]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('You cannot send in an array of threats that are missing "framework"', () => {
|
||||
|
@ -708,9 +716,12 @@ describe('create rules schema', () => {
|
|||
],
|
||||
},
|
||||
],
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "threats" fails because ["threats" at position 0 fails because [child "framework" fails because ["framework" is required]]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('You cannot send in an array of threats that are missing "tactic"', () => {
|
||||
expect(
|
||||
createRulesSchema.validate<
|
||||
|
@ -745,9 +756,12 @@ describe('create rules schema', () => {
|
|||
],
|
||||
},
|
||||
],
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "threats" fails because ["threats" at position 0 fails because [child "tactic" fails because ["tactic" is required]]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('You cannot send in an array of threats that are missing "techniques"', () => {
|
||||
expect(
|
||||
createRulesSchema.validate<
|
||||
|
@ -780,8 +794,10 @@ describe('create rules schema', () => {
|
|||
},
|
||||
},
|
||||
],
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "threats" fails because ["threats" at position 0 fails because [child "techniques" fails because ["techniques" is required]]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('You can optionally send in an array of false positives', () => {
|
||||
|
@ -828,34 +844,13 @@ describe('create rules schema', () => {
|
|||
query: 'some query',
|
||||
language: 'kuery',
|
||||
max_signals: 1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "false_positives" fails because ["false_positives" at position 0 fails because ["0" must be a string]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('You can optionally set the immutable to be true', () => {
|
||||
expect(
|
||||
createRulesSchema.validate<Partial<RuleAlertParamsRest>>({
|
||||
rule_id: 'rule-1',
|
||||
output_index: '.siem-signals',
|
||||
risk_score: 50,
|
||||
description: 'some description',
|
||||
from: 'now-5m',
|
||||
to: 'now',
|
||||
immutable: true,
|
||||
index: ['index-1'],
|
||||
name: 'some-name',
|
||||
severity: 'severity',
|
||||
interval: '5m',
|
||||
type: 'query',
|
||||
references: ['index-1'],
|
||||
query: 'some query',
|
||||
language: 'kuery',
|
||||
max_signals: 1,
|
||||
}).error
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
test('You cannot set the immutable to be a number', () => {
|
||||
test('You cannot set the immutable when trying to create a rule', () => {
|
||||
expect(
|
||||
createRulesSchema.validate<
|
||||
Partial<Omit<RuleAlertParamsRest, 'immutable'>> & { immutable: number }
|
||||
|
@ -876,8 +871,8 @@ describe('create rules schema', () => {
|
|||
query: 'some query',
|
||||
language: 'kuery',
|
||||
max_signals: 1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('"immutable" is not allowed');
|
||||
});
|
||||
|
||||
test('You cannot set the risk_score to 101', () => {
|
||||
|
@ -899,8 +894,8 @@ describe('create rules schema', () => {
|
|||
query: 'some query',
|
||||
language: 'kuery',
|
||||
max_signals: 1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "risk_score" fails because ["risk_score" must be less than 101]');
|
||||
});
|
||||
|
||||
test('You cannot set the risk_score to -1', () => {
|
||||
|
@ -922,8 +917,8 @@ describe('create rules schema', () => {
|
|||
query: 'some query',
|
||||
language: 'kuery',
|
||||
max_signals: 1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "risk_score" fails because ["risk_score" must be greater than -1]');
|
||||
});
|
||||
|
||||
test('You can set the risk_score to 0', () => {
|
||||
|
@ -935,7 +930,6 @@ describe('create rules schema', () => {
|
|||
description: 'some description',
|
||||
from: 'now-5m',
|
||||
to: 'now',
|
||||
immutable: true,
|
||||
index: ['index-1'],
|
||||
name: 'some-name',
|
||||
severity: 'severity',
|
||||
|
@ -958,7 +952,6 @@ describe('create rules schema', () => {
|
|||
description: 'some description',
|
||||
from: 'now-5m',
|
||||
to: 'now',
|
||||
immutable: true,
|
||||
index: ['index-1'],
|
||||
name: 'some-name',
|
||||
severity: 'severity',
|
||||
|
@ -981,7 +974,6 @@ describe('create rules schema', () => {
|
|||
description: 'some description',
|
||||
from: 'now-5m',
|
||||
to: 'now',
|
||||
immutable: true,
|
||||
index: ['index-1'],
|
||||
name: 'some-name',
|
||||
severity: 'severity',
|
||||
|
@ -1018,8 +1010,8 @@ describe('create rules schema', () => {
|
|||
language: 'kuery',
|
||||
max_signals: 1,
|
||||
meta: 'should not work',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "meta" fails because ["meta" must be an object]');
|
||||
});
|
||||
|
||||
test('You can omit the query string when filters are present', () => {
|
||||
|
@ -1031,7 +1023,6 @@ describe('create rules schema', () => {
|
|||
description: 'some description',
|
||||
from: 'now-5m',
|
||||
to: 'now',
|
||||
immutable: true,
|
||||
index: ['index-1'],
|
||||
name: 'some-name',
|
||||
severity: 'severity',
|
||||
|
@ -1086,8 +1077,8 @@ describe('create rules schema', () => {
|
|||
query: 'some query',
|
||||
language: 'kuery',
|
||||
timeline_id: 'some_id',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "timeline_title" fails because ["timeline_title" is required]');
|
||||
});
|
||||
|
||||
test('You cannot have a null value for timeline_title when timeline_id is present', () => {
|
||||
|
@ -1109,8 +1100,8 @@ describe('create rules schema', () => {
|
|||
language: 'kuery',
|
||||
timeline_id: 'some_id',
|
||||
timeline_title: null,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "timeline_title" fails because ["timeline_title" must be a string]');
|
||||
});
|
||||
|
||||
test('You cannot have empty string for timeline_title when timeline_id is present', () => {
|
||||
|
@ -1132,8 +1123,8 @@ describe('create rules schema', () => {
|
|||
language: 'kuery',
|
||||
timeline_id: 'some_id',
|
||||
timeline_title: '',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "timeline_title" fails because ["timeline_title" is not allowed to be empty]');
|
||||
});
|
||||
|
||||
test('You cannot have timeline_title with an empty timeline_id', () => {
|
||||
|
@ -1155,8 +1146,8 @@ describe('create rules schema', () => {
|
|||
language: 'kuery',
|
||||
timeline_id: '',
|
||||
timeline_title: 'some-title',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "timeline_id" fails because ["timeline_id" is not allowed to be empty]');
|
||||
});
|
||||
|
||||
test('You cannot have timeline_title without timeline_id', () => {
|
||||
|
@ -1177,7 +1168,7 @@ describe('create rules schema', () => {
|
|||
query: 'some query',
|
||||
language: 'kuery',
|
||||
timeline_title: 'some-title',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "timeline_title" fails because ["timeline_title" is not allowed]');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,7 +13,6 @@ import {
|
|||
false_positives,
|
||||
filters,
|
||||
from,
|
||||
immutable,
|
||||
index,
|
||||
rule_id,
|
||||
interval,
|
||||
|
@ -46,7 +45,6 @@ export const createRulesSchema = Joi.object({
|
|||
filters,
|
||||
from: from.required(),
|
||||
rule_id,
|
||||
immutable: immutable.default(false),
|
||||
index,
|
||||
interval: interval.default('5m'),
|
||||
query: query.allow('').default(''),
|
||||
|
|
|
@ -37,8 +37,10 @@ describe('create rules schema', () => {
|
|||
expect(
|
||||
exportRulesSchema.validate<Omit<ExportRulesRequest['payload'], 'objects'>>({
|
||||
objects: [{ id: 'test-1' }],
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "objects" fails because ["objects" at position 0 fails because ["id" is not allowed]]'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -70,8 +72,8 @@ describe('create rules schema', () => {
|
|||
Partial<Omit<ExportRulesRequest['query'], 'file_name'> & { file_name: number }>
|
||||
>({
|
||||
file_name: 5,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "file_name" fails because ["file_name" must be a string]');
|
||||
});
|
||||
|
||||
test('exclude_export_details validates with a boolean true', () => {
|
||||
|
@ -92,8 +94,10 @@ describe('create rules schema', () => {
|
|||
>
|
||||
>({
|
||||
exclude_export_details: 'blah',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "exclude_export_details" fails because ["exclude_export_details" must be a boolean]'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -69,8 +69,10 @@ describe('find rules schema', () => {
|
|||
expect(
|
||||
findRulesSchema.validate<Partial<Omit<FindParamsRest, 'fields'>> & { fields: number[] }>({
|
||||
fields: [5],
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "fields" fails because ["fields" at position 0 fails because ["0" must be a string]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('per page has a default of 20', () => {
|
||||
|
@ -93,16 +95,16 @@ describe('find rules schema', () => {
|
|||
expect(
|
||||
findRulesSchema.validate<Partial<Omit<FindParamsRest, 'filter'>> & { filter: number }>({
|
||||
filter: 5,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "filter" fails because ["filter" must be a string]');
|
||||
});
|
||||
|
||||
test('sort_order requires sort_field to work', () => {
|
||||
expect(
|
||||
findRulesSchema.validate<Partial<FindParamsRest>>({
|
||||
sort_order: 'asc',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "sort_field" fails because ["sort_field" is required]');
|
||||
});
|
||||
|
||||
test('sort_order and sort_field validate together', () => {
|
||||
|
@ -130,7 +132,7 @@ describe('find rules schema', () => {
|
|||
>({
|
||||
sort_order: 'some other string',
|
||||
sort_field: 'some field',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "sort_order" fails because ["sort_order" must be one of [asc, desc]]');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -243,6 +243,7 @@ describe('import rules schema', () => {
|
|||
}).error
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
test('You can send in an empty array to threats', () => {
|
||||
expect(
|
||||
importRulesSchema.validate<Partial<RuleAlertParamsRest>>({
|
||||
|
@ -265,6 +266,7 @@ describe('import rules schema', () => {
|
|||
}).error
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index, threats] does validate', () => {
|
||||
expect(
|
||||
importRulesSchema.validate<Partial<RuleAlertParamsRest>>({
|
||||
|
@ -360,8 +362,10 @@ describe('import rules schema', () => {
|
|||
query: 'some-query',
|
||||
language: 'kuery',
|
||||
references: [5],
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "references" fails because ["references" at position 0 fails because ["0" must be a string]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('indexes cannot be numbers', () => {
|
||||
|
@ -382,8 +386,10 @@ describe('import rules schema', () => {
|
|||
type: 'query',
|
||||
query: 'some-query',
|
||||
language: 'kuery',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "index" fails because ["index" at position 0 fails because ["0" must be a string]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('defaults interval to 5 min', () => {
|
||||
|
@ -435,8 +441,8 @@ describe('import rules schema', () => {
|
|||
severity: 'severity',
|
||||
interval: '5m',
|
||||
type: 'saved_query',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "saved_id" fails because ["saved_id" is required]');
|
||||
});
|
||||
|
||||
test('saved_id is required when type is saved_query and validates with it', () => {
|
||||
|
@ -496,8 +502,8 @@ describe('import rules schema', () => {
|
|||
type: 'saved_query',
|
||||
saved_id: 'some id',
|
||||
filters: 'some string',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "filters" fails because ["filters" must be an array]');
|
||||
});
|
||||
|
||||
test('language validates with kuery', () => {
|
||||
|
@ -559,8 +565,8 @@ describe('import rules schema', () => {
|
|||
references: ['index-1'],
|
||||
query: 'some query',
|
||||
language: 'something-made-up',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "language" fails because ["language" must be one of [kuery, lucene]]');
|
||||
});
|
||||
|
||||
test('max_signals cannot be negative', () => {
|
||||
|
@ -581,8 +587,8 @@ describe('import rules schema', () => {
|
|||
query: 'some query',
|
||||
language: 'kuery',
|
||||
max_signals: -1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "max_signals" fails because ["max_signals" must be greater than 0]');
|
||||
});
|
||||
|
||||
test('max_signals cannot be zero', () => {
|
||||
|
@ -603,8 +609,8 @@ describe('import rules schema', () => {
|
|||
query: 'some query',
|
||||
language: 'kuery',
|
||||
max_signals: 0,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "max_signals" fails because ["max_signals" must be greater than 0]');
|
||||
});
|
||||
|
||||
test('max_signals can be 1', () => {
|
||||
|
@ -673,8 +679,10 @@ describe('import rules schema', () => {
|
|||
max_signals: 1,
|
||||
tags: [0, 1, 2],
|
||||
}
|
||||
).error
|
||||
).toBeTruthy();
|
||||
).error.message
|
||||
).toEqual(
|
||||
'child "tags" fails because ["tags" at position 0 fails because ["0" must be a string]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('You cannot send in an array of threats that are missing "framework"', () => {
|
||||
|
@ -715,9 +723,12 @@ describe('import rules schema', () => {
|
|||
],
|
||||
},
|
||||
],
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "threats" fails because ["threats" at position 0 fails because [child "framework" fails because ["framework" is required]]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('You cannot send in an array of threats that are missing "tactic"', () => {
|
||||
expect(
|
||||
importRulesSchema.validate<
|
||||
|
@ -752,9 +763,12 @@ describe('import rules schema', () => {
|
|||
],
|
||||
},
|
||||
],
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "threats" fails because ["threats" at position 0 fails because [child "tactic" fails because ["tactic" is required]]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('You cannot send in an array of threats that are missing "techniques"', () => {
|
||||
expect(
|
||||
importRulesSchema.validate<
|
||||
|
@ -787,8 +801,10 @@ describe('import rules schema', () => {
|
|||
},
|
||||
},
|
||||
],
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "threats" fails because ["threats" at position 0 fails because [child "techniques" fails because ["techniques" is required]]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('You can optionally send in an array of false positives', () => {
|
||||
|
@ -835,8 +851,10 @@ describe('import rules schema', () => {
|
|||
query: 'some query',
|
||||
language: 'kuery',
|
||||
max_signals: 1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "false_positives" fails because ["false_positives" at position 0 fails because ["0" must be a string]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('You can optionally set the immutable to be true', () => {
|
||||
|
@ -883,8 +901,8 @@ describe('import rules schema', () => {
|
|||
query: 'some query',
|
||||
language: 'kuery',
|
||||
max_signals: 1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "immutable" fails because ["immutable" must be a boolean]');
|
||||
});
|
||||
|
||||
test('You cannot set the risk_score to 101', () => {
|
||||
|
@ -906,8 +924,8 @@ describe('import rules schema', () => {
|
|||
query: 'some query',
|
||||
language: 'kuery',
|
||||
max_signals: 1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "risk_score" fails because ["risk_score" must be less than 101]');
|
||||
});
|
||||
|
||||
test('You cannot set the risk_score to -1', () => {
|
||||
|
@ -929,8 +947,8 @@ describe('import rules schema', () => {
|
|||
query: 'some query',
|
||||
language: 'kuery',
|
||||
max_signals: 1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "risk_score" fails because ["risk_score" must be greater than -1]');
|
||||
});
|
||||
|
||||
test('You can set the risk_score to 0', () => {
|
||||
|
@ -1025,8 +1043,8 @@ describe('import rules schema', () => {
|
|||
language: 'kuery',
|
||||
max_signals: 1,
|
||||
meta: 'should not work',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "meta" fails because ["meta" must be an object]');
|
||||
});
|
||||
|
||||
test('You can omit the query string when filters are present', () => {
|
||||
|
@ -1093,8 +1111,8 @@ describe('import rules schema', () => {
|
|||
query: 'some query',
|
||||
language: 'kuery',
|
||||
timeline_id: 'some_id',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "timeline_title" fails because ["timeline_title" is required]');
|
||||
});
|
||||
|
||||
test('You cannot have a null value for timeline_title when timeline_id is present', () => {
|
||||
|
@ -1116,8 +1134,8 @@ describe('import rules schema', () => {
|
|||
language: 'kuery',
|
||||
timeline_id: 'some_id',
|
||||
timeline_title: null,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "timeline_title" fails because ["timeline_title" must be a string]');
|
||||
});
|
||||
|
||||
test('You cannot have empty string for timeline_title when timeline_id is present', () => {
|
||||
|
@ -1139,8 +1157,10 @@ describe('import rules schema', () => {
|
|||
language: 'kuery',
|
||||
timeline_id: 'some_id',
|
||||
timeline_title: '',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "timeline_title" fails because ["timeline_title" is not allowed to be empty]'
|
||||
);
|
||||
});
|
||||
|
||||
test('You cannot have timeline_title with an empty timeline_id', () => {
|
||||
|
@ -1162,8 +1182,8 @@ describe('import rules schema', () => {
|
|||
language: 'kuery',
|
||||
timeline_id: '',
|
||||
timeline_title: 'some-title',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "timeline_id" fails because ["timeline_id" is not allowed to be empty]');
|
||||
});
|
||||
|
||||
test('You cannot have timeline_title without timeline_id', () => {
|
||||
|
@ -1184,8 +1204,8 @@ describe('import rules schema', () => {
|
|||
query: 'some query',
|
||||
language: 'kuery',
|
||||
timeline_title: 'some-title',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "timeline_title" fails because ["timeline_title" is not allowed]');
|
||||
});
|
||||
|
||||
test('rule_id is required and you cannot get by with just id', () => {
|
||||
|
@ -1205,8 +1225,8 @@ describe('import rules schema', () => {
|
|||
references: ['index-1'],
|
||||
query: 'some query',
|
||||
language: 'kuery',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "rule_id" fails because ["rule_id" is required]');
|
||||
});
|
||||
|
||||
test('it validates with created_at, updated_at, created_by, updated_by values', () => {
|
||||
|
@ -1255,8 +1275,8 @@ describe('import rules schema', () => {
|
|||
updated_at: '2020-01-09T06:15:24.749Z',
|
||||
created_by: 'Braden Hassanabad',
|
||||
updated_by: 'Evan Hassanabad',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "created_at" fails because ["created_at" must be a valid ISO 8601 date]');
|
||||
});
|
||||
|
||||
test('it does not validate with epoch strings for updated_at', () => {
|
||||
|
@ -1280,8 +1300,8 @@ describe('import rules schema', () => {
|
|||
updated_at: '1578550728650',
|
||||
created_by: 'Braden Hassanabad',
|
||||
updated_by: 'Evan Hassanabad',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "updated_at" fails because ["updated_at" must be a valid ISO 8601 date]');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -24,8 +24,10 @@ describe('query_rules_bulk_schema', () => {
|
|||
rule_id: '1',
|
||||
id: '1',
|
||||
},
|
||||
]).error
|
||||
).toBeTruthy();
|
||||
]).error.message
|
||||
).toEqual(
|
||||
'"value" at position 0 fails because ["value" contains a conflict between exclusive peers [id, rule_id]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('both rule_id and id being supplied do not validate if one array element works but the second does not', () => {
|
||||
|
@ -38,8 +40,10 @@ describe('query_rules_bulk_schema', () => {
|
|||
rule_id: '1',
|
||||
id: '1',
|
||||
},
|
||||
]).error
|
||||
).toBeTruthy();
|
||||
]).error.message
|
||||
).toEqual(
|
||||
'"value" at position 1 fails because ["value" contains a conflict between exclusive peers [id, rule_id]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('only id validates', () => {
|
||||
|
|
|
@ -12,10 +12,11 @@ describe('queryRulesSchema', () => {
|
|||
expect(queryRulesSchema.validate<Partial<UpdateRuleAlertParamsRest>>({}).error).toBeTruthy();
|
||||
});
|
||||
|
||||
test('both rule_id and id being supplied dot not validate', () => {
|
||||
test('both rule_id and id being supplied do not validate', () => {
|
||||
expect(
|
||||
queryRulesSchema.validate<Partial<UpdateRuleAlertParamsRest>>({ rule_id: '1', id: '1' }).error
|
||||
).toBeTruthy();
|
||||
.message
|
||||
).toEqual('"value" contains a conflict between exclusive peers [id, rule_id]');
|
||||
});
|
||||
|
||||
test('only id validates', () => {
|
||||
|
|
|
@ -30,24 +30,24 @@ describe('set signal status schema', () => {
|
|||
expect(
|
||||
setSignalsStatusSchema.validate<Partial<SignalsStatusRestParams>>({
|
||||
signal_ids: ['somefakeid'],
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "status" fails because ["status" is required]');
|
||||
});
|
||||
|
||||
test('query and missing status is invalid', () => {
|
||||
expect(
|
||||
setSignalsStatusSchema.validate<Partial<SignalsStatusRestParams>>({
|
||||
query: {},
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "status" fails because ["status" is required]');
|
||||
});
|
||||
|
||||
test('status is present but query or signal_ids is missing is invalid', () => {
|
||||
expect(
|
||||
setSignalsStatusSchema.validate<Partial<SignalsStatusRestParams>>({
|
||||
status: 'closed',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('"value" must contain at least one of [signal_ids, query]');
|
||||
});
|
||||
|
||||
test('signal_ids is present but status has wrong value', () => {
|
||||
|
@ -60,7 +60,7 @@ describe('set signal status schema', () => {
|
|||
>
|
||||
>({
|
||||
status: 'fakeVal',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "status" fails because ["status" must be one of [open, closed]]');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -442,8 +442,10 @@ describe('update rules schema', () => {
|
|||
query: 'some-query',
|
||||
language: 'kuery',
|
||||
references: [5],
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "references" fails because ["references" at position 0 fails because ["0" must be a string]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('indexes cannot be numbers', () => {
|
||||
|
@ -462,8 +464,10 @@ describe('update rules schema', () => {
|
|||
type: 'query',
|
||||
query: 'some-query',
|
||||
language: 'kuery',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "index" fails because ["index" at position 0 fails because ["0" must be a string]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('saved_id is not required when type is saved_query and will validate without it', () => {
|
||||
|
@ -570,8 +574,8 @@ describe('update rules schema', () => {
|
|||
references: ['index-1'],
|
||||
query: 'some query',
|
||||
language: 'something-made-up',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "language" fails because ["language" must be one of [kuery, lucene]]');
|
||||
});
|
||||
|
||||
test('max_signals cannot be negative', () => {
|
||||
|
@ -590,8 +594,8 @@ describe('update rules schema', () => {
|
|||
query: 'some query',
|
||||
language: 'kuery',
|
||||
max_signals: -1,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "max_signals" fails because ["max_signals" must be greater than 0]');
|
||||
});
|
||||
|
||||
test('max_signals cannot be zero', () => {
|
||||
|
@ -610,8 +614,8 @@ describe('update rules schema', () => {
|
|||
query: 'some query',
|
||||
language: 'kuery',
|
||||
max_signals: 0,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "max_signals" fails because ["max_signals" must be greater than 0]');
|
||||
});
|
||||
|
||||
test('max_signals can be 1', () => {
|
||||
|
@ -643,15 +647,15 @@ describe('update rules schema', () => {
|
|||
).toBeFalsy();
|
||||
});
|
||||
|
||||
test('You update meta as a string', () => {
|
||||
test('You cannot update meta as a string', () => {
|
||||
expect(
|
||||
updateRulesSchema.validate<
|
||||
Partial<Omit<UpdateRuleAlertParamsRest, 'meta'> & { meta: string }>
|
||||
>({
|
||||
id: 'rule-1',
|
||||
meta: 'should not work',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "meta" fails because ["meta" must be an object]');
|
||||
});
|
||||
|
||||
test('filters cannot be a string', () => {
|
||||
|
@ -662,8 +666,8 @@ describe('update rules schema', () => {
|
|||
rule_id: 'rule-1',
|
||||
type: 'query',
|
||||
filters: 'some string',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "filters" fails because ["filters" must be an array]');
|
||||
});
|
||||
|
||||
test('threats is not defaulted to empty array on update', () => {
|
||||
|
@ -706,6 +710,7 @@ describe('update rules schema', () => {
|
|||
}).value.threats
|
||||
).toMatchObject([]);
|
||||
});
|
||||
|
||||
test('threats is valid when updated with all sub-objects', () => {
|
||||
const expected: ThreatParams[] = [
|
||||
{
|
||||
|
@ -759,6 +764,7 @@ describe('update rules schema', () => {
|
|||
}).value.threats
|
||||
).toMatchObject(expected);
|
||||
});
|
||||
|
||||
test('threats is invalid when updated with missing property framework', () => {
|
||||
expect(
|
||||
updateRulesSchema.validate<
|
||||
|
@ -795,9 +801,12 @@ describe('update rules schema', () => {
|
|||
],
|
||||
},
|
||||
],
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "threats" fails because ["threats" at position 0 fails because [child "framework" fails because ["framework" is required]]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('threats is invalid when updated with missing tactic sub-object', () => {
|
||||
expect(
|
||||
updateRulesSchema.validate<
|
||||
|
@ -830,9 +839,12 @@ describe('update rules schema', () => {
|
|||
],
|
||||
},
|
||||
],
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "threats" fails because ["threats" at position 0 fails because [child "tactic" fails because ["tactic" is required]]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('threats is invalid when updated with missing techniques', () => {
|
||||
expect(
|
||||
updateRulesSchema.validate<
|
||||
|
@ -863,8 +875,10 @@ describe('update rules schema', () => {
|
|||
},
|
||||
},
|
||||
],
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual(
|
||||
'child "threats" fails because ["threats" at position 0 fails because [child "techniques" fails because ["techniques" is required]]]'
|
||||
);
|
||||
});
|
||||
|
||||
test('validates with timeline_id and timeline_title', () => {
|
||||
|
@ -900,8 +914,8 @@ describe('update rules schema', () => {
|
|||
type: 'saved_query',
|
||||
saved_id: 'some id',
|
||||
timeline_id: 'some-id',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "timeline_title" fails because ["timeline_title" is required]');
|
||||
});
|
||||
|
||||
test('You cannot have a null value for timeline_title when timeline_id is present', () => {
|
||||
|
@ -919,8 +933,8 @@ describe('update rules schema', () => {
|
|||
saved_id: 'some id',
|
||||
timeline_id: 'timeline-id',
|
||||
timeline_title: null,
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "timeline_title" fails because ["timeline_title" must be a string]');
|
||||
});
|
||||
|
||||
test('You cannot have empty string for timeline_title when timeline_id is present', () => {
|
||||
|
@ -938,8 +952,8 @@ describe('update rules schema', () => {
|
|||
saved_id: 'some id',
|
||||
timeline_id: 'some-id',
|
||||
timeline_title: '',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "timeline_title" fails because ["timeline_title" is not allowed to be empty]');
|
||||
});
|
||||
|
||||
test('You cannot have timeline_title with an empty timeline_id', () => {
|
||||
|
@ -957,8 +971,8 @@ describe('update rules schema', () => {
|
|||
saved_id: 'some id',
|
||||
timeline_id: '',
|
||||
timeline_title: 'some-title',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "timeline_id" fails because ["timeline_id" is not allowed to be empty]');
|
||||
});
|
||||
|
||||
test('You cannot have timeline_title without timeline_id', () => {
|
||||
|
@ -975,7 +989,7 @@ describe('update rules schema', () => {
|
|||
type: 'saved_query',
|
||||
saved_id: 'some id',
|
||||
timeline_title: 'some-title',
|
||||
}).error
|
||||
).toBeTruthy();
|
||||
}).error.message
|
||||
).toEqual('child "timeline_title" fails because ["timeline_title" is not allowed]');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,7 +13,6 @@ import {
|
|||
false_positives,
|
||||
filters,
|
||||
from,
|
||||
immutable,
|
||||
index,
|
||||
rule_id,
|
||||
interval,
|
||||
|
@ -46,7 +45,6 @@ export const updateRulesSchema = Joi.object({
|
|||
from,
|
||||
rule_id,
|
||||
id,
|
||||
immutable,
|
||||
index,
|
||||
interval,
|
||||
query: query.allow(''),
|
||||
|
|
|
@ -8,7 +8,6 @@ import { createMockServer } from '../__mocks__/_mock_server';
|
|||
import { setSignalsStatusRoute } from './open_close_signals_route';
|
||||
import * as myUtils from '../utils';
|
||||
import { ServerInjectOptions } from 'hapi';
|
||||
import { ServerFacade } from '../../../../types';
|
||||
|
||||
import {
|
||||
getSetSignalStatusByIdsRequest,
|
||||
|
@ -29,7 +28,7 @@ describe('set signal status', () => {
|
|||
elasticsearch.getCluster = jest.fn(() => ({
|
||||
callWithRequest: jest.fn(() => true),
|
||||
}));
|
||||
setSignalsStatusRoute((server as unknown) as ServerFacade);
|
||||
setSignalsStatusRoute(server);
|
||||
});
|
||||
|
||||
describe('status on signal', () => {
|
||||
|
|
|
@ -8,7 +8,6 @@ import { createMockServer } from '../__mocks__/_mock_server';
|
|||
import { querySignalsRoute } from './query_signals_route';
|
||||
import * as myUtils from '../utils';
|
||||
import { ServerInjectOptions } from 'hapi';
|
||||
import { ServerFacade } from '../../../../types';
|
||||
|
||||
import {
|
||||
getSignalsQueryRequest,
|
||||
|
@ -28,7 +27,7 @@ describe('query for signal', () => {
|
|||
elasticsearch.getCluster = jest.fn(() => ({
|
||||
callWithRequest: jest.fn(() => true),
|
||||
}));
|
||||
querySignalsRoute((server as unknown) as ServerFacade);
|
||||
querySignalsRoute(server);
|
||||
});
|
||||
|
||||
describe('query and agg on signals index', () => {
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
# or more contributor license agreements. Licensed under the Elastic License;
|
||||
# you may not use this file except in compliance with the Elastic License.
|
||||
#
|
||||
|
||||
set -e
|
||||
./check_env_variables.sh
|
||||
|
||||
# Example: ./get_prepackaged_rules_status.sh
|
||||
curl -s -k \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'kbn-xsrf: 123' \
|
||||
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
|
||||
-X GET ${KIBANA_URL}${SPACE_URL}/api/detection_engine/rules/prepackaged/_status | jq .
|
|
@ -21,7 +21,6 @@
|
|||
}
|
||||
],
|
||||
"enabled": false,
|
||||
"immutable": false,
|
||||
"index": ["auditbeat-*", "filebeat-*"],
|
||||
"interval": "5m",
|
||||
"query": "user.name: root or user.name: admin",
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
}
|
||||
],
|
||||
"enabled": false,
|
||||
"immutable": true,
|
||||
"index": ["auditbeat-*", "filebeat-*"],
|
||||
"interval": "5m",
|
||||
"query": "user.name: root or user.name: admin",
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"rule_id": "query-rule-id",
|
||||
"immutable": true
|
||||
}
|
|
@ -21,7 +21,6 @@
|
|||
}
|
||||
],
|
||||
"enabled": false,
|
||||
"immutable": true,
|
||||
"index": ["auditbeat-*", "filebeat-*"],
|
||||
"interval": "5m",
|
||||
"query": "user.name: root or user.name: admin",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue