[RAC][Security Solution] Add RAC support to rule routes (#108053) (#111778)

* prototyping

* how dis

* RAC rules create API

* Find rules (in progress)

* Finalize find_rules route

* A couple more routes, and type error fixes

* Fix integration tests?

* Fix tests

* Fix imports

* Add ref

* Test fixes

* Fix refs

* Type fixes

* Test fixes

* Remove console log

* Update rule changes

* Test and type fixes

* Fix patch rule tests

* Fix types

* Begin removing namespace as required param

* Remove generics

* Support RAC everywhere

* Tests passing

* Types

* Keep on passing isRuleRegistryEnabled around

* Rewrite install_prepackaged_timelines helper tests
This commit is contained in:
Madison Caldwell 2021-09-09 16:04:22 -04:00 committed by GitHub
parent 5dea5bbe5f
commit a9d1b5ce3e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
94 changed files with 1060 additions and 546 deletions

View file

@ -12,6 +12,9 @@ import { PluginInitializerContext } from 'src/core/server';
import { RuleRegistryPlugin } from './plugin';
export type { RuleRegistryPluginSetupContract, RuleRegistryPluginStartContract } from './plugin';
export { RuleDataPluginService } from './rule_data_plugin_service';
export { RuleDataClient } from './rule_data_client';
export { IRuleDataClient } from './rule_data_client/types';
export type {
RacRequestHandlerContext,
RacApiRequestHandlerContext,

View file

@ -187,19 +187,18 @@ export const DEFAULT_TRANSFORMS_SETTING = JSON.stringify(defaultTransformsSettin
/**
* Id for the signals alerting type
*/
export const SIGNALS_ID = `siem.signals`;
export const SIGNALS_ID = `siem.signals` as const;
/**
* Id's for reference rule types
* IDs for RAC rule types
*/
export const REFERENCE_RULE_ALERT_TYPE_ID = `siem.referenceRule`;
export const REFERENCE_RULE_PERSISTENCE_ALERT_TYPE_ID = `siem.referenceRulePersistence`;
export const QUERY_ALERT_TYPE_ID = `siem.queryRule`;
export const EQL_ALERT_TYPE_ID = `siem.eqlRule`;
export const INDICATOR_ALERT_TYPE_ID = `siem.indicatorRule`;
export const ML_ALERT_TYPE_ID = `siem.mlRule`;
export const THRESHOLD_ALERT_TYPE_ID = `siem.thresholdRule`;
const RULE_TYPE_PREFIX = `siem` as const;
export const EQL_RULE_TYPE_ID = `${RULE_TYPE_PREFIX}.eqlRule` as const;
export const INDICATOR_RULE_TYPE_ID = `${RULE_TYPE_PREFIX}.indicatorRule` as const;
export const ML_RULE_TYPE_ID = `${RULE_TYPE_PREFIX}.mlRule` as const;
export const QUERY_RULE_TYPE_ID = `${RULE_TYPE_PREFIX}.queryRule` as const;
export const SAVED_QUERY_RULE_TYPE_ID = `${RULE_TYPE_PREFIX}.savedQueryRule` as const;
export const THRESHOLD_RULE_TYPE_ID = `${RULE_TYPE_PREFIX}.thresholdRule` as const;
/**
* Id for the notifications alerting type

View file

@ -59,6 +59,9 @@ export type FileName = t.TypeOf<typeof file_name>;
export const exclude_export_details = t.boolean;
export type ExcludeExportDetails = t.TypeOf<typeof exclude_export_details>;
export const namespace = t.string;
export type Namespace = t.TypeOf<typeof namespace>;
/**
* TODO: Right now the filters is an "unknown", when it could more than likely
* become the actual ESFilter as a type.
@ -352,6 +355,9 @@ export const timelines_not_updated = PositiveInteger;
export const note = t.string;
export type Note = t.TypeOf<typeof note>;
export const namespaceOrUndefined = t.union([namespace, t.undefined]);
export type NamespaceOrUndefined = t.TypeOf<typeof namespaceOrUndefined>;
export const noteOrUndefined = t.union([note, t.undefined]);
export type NoteOrUndefined = t.TypeOf<typeof noteOrUndefined>;

View file

@ -465,6 +465,23 @@ describe('add prepackaged rules schema', () => {
expect(message.schema).toEqual(expected);
});
test('You can send in a namespace', () => {
const payload: AddPrepackagedRulesSchema = {
...getAddPrepackagedRulesSchemaMock(),
namespace: 'a namespace',
};
const decoded = addPrepackagedRulesSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
const expected: AddPrepackagedRulesSchemaDecoded = {
...getAddPrepackagedRulesSchemaDecodedMock(),
namespace: 'a namespace',
};
expect(message.schema).toEqual(expected);
});
test('You can send in an empty array to threat', () => {
const payload: AddPrepackagedRulesSchema = {
...getAddPrepackagedRulesSchemaMock(),

View file

@ -71,6 +71,7 @@ import {
timestamp_override,
Author,
event_category_override,
namespace,
} from '../common/schemas';
/**
@ -136,10 +137,10 @@ export const addPrepackagedRulesSchema = t.intersection([
threat_indicator_path, // defaults "undefined" if not set during decode
concurrent_searches, // defaults to "undefined" if not set during decode
items_per_search, // defaults to "undefined" if not set during decode
namespace, // defaults to "undefined" if not set during decode
})
),
]);
export type AddPrepackagedRulesSchema = t.TypeOf<typeof addPrepackagedRulesSchema>;
// This type is used after a decode since some things are defaults after a decode.
@ -153,6 +154,7 @@ export type AddPrepackagedRulesSchemaDecoded = Omit<
| 'from'
| 'interval'
| 'max_signals'
| 'namespace'
| 'risk_score_mapping'
| 'severity_mapping'
| 'tags'
@ -176,4 +178,5 @@ export type AddPrepackagedRulesSchemaDecoded = Omit<
threat: Threats;
throttle: ThrottleOrNull;
exceptions_list: ListArray;
namespace?: string;
};

View file

@ -330,6 +330,19 @@ describe('create rules schema', () => {
expect(message.schema).toEqual(payload);
});
test('You can send in a namespace', () => {
const payload: CreateRulesSchema = {
...getCreateRulesSchemaMock(),
namespace: 'a namespace',
};
const decoded = createRulesSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
});
test('You can send in an empty array to threat', () => {
const payload: CreateRulesSchema = {
...getCreateRulesSchemaMock(),

View file

@ -68,6 +68,7 @@ import {
last_success_message,
last_failure_at,
last_failure_message,
namespace,
} from '../common/schemas';
export const createSchema = <
@ -155,6 +156,7 @@ const baseParams = {
meta,
rule_name_override,
timestamp_override,
namespace,
},
defaultable: {
tags,

View file

@ -187,6 +187,18 @@ describe('update_rules_bulk_schema', () => {
expect(output.schema).toEqual({});
});
test('You can set "namespace" to a string', () => {
const payload: UpdateRulesBulkSchema = [
{ ...getUpdateRulesSchemaMock(), namespace: 'a namespace' },
];
const decoded = updateRulesBulkSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const output = foldLeftRight(checked);
expect(formatErrors(output.errors)).toEqual([]);
expect(output.schema).toEqual(payload);
});
test('You can set "note" to a string', () => {
const payload: UpdateRulesBulkSchema = [
{ ...getUpdateRulesSchemaMock(), note: '# test markdown' },

View file

@ -404,6 +404,19 @@ describe('rules_schema', () => {
expect(message.schema).toEqual(expected);
});
test('it should validate a namespace as string', () => {
const payload = {
...getRulesSchemaMock(),
namespace: 'a namespace',
};
const dependents = getDependents(payload);
const decoded = dependents.decode(payload);
const checked = exactCheck(payload, decoded);
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
});
test('it should NOT validate invalid_data for the type', () => {
const payload: Omit<RulesSchema, 'type'> & { type: string } = getRulesSchemaMock();
payload.type = 'invalid_data';

View file

@ -75,6 +75,7 @@ import {
license,
rule_name_override,
timestamp_override,
namespace,
} from '../common/schemas';
import { typeAndTimelineOnlySchema, TypeAndTimelineOnly } from './type_timeline_only_schema';
@ -174,6 +175,7 @@ export const partialRulesSchema = t.partial({
filters,
meta,
index,
namespace,
note,
});

View file

@ -53,6 +53,7 @@ describe('schedule_throttle_notification_actions', () => {
to: 'now',
type: 'query',
references: ['http://www.example.com'],
namespace: 'a namespace',
note: '# sample markdown',
version: 1,
exceptionsList: [],

View file

@ -39,6 +39,7 @@ import { getQueryRuleParams } from '../../schemas/rule_schemas.mock';
import { getPerformBulkActionSchemaMock } from '../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema.mock';
import { RuleExecutionStatus } from '../../../../../common/detection_engine/schemas/common/schemas';
import { FindBulkExecutionLogResponse } from '../../rule_execution_log/types';
import { ruleTypeMappings } from '../../signals/utils';
export const typicalSetStatusSignalByIdsPayload = (): SetSignalsStatusSchemaDecoded => ({
signal_ids: ['somefakeid1', 'somefakeid2'],
@ -179,18 +180,18 @@ export const getEmptyFindResult = (): FindHit => ({
data: [],
});
export const getFindResultWithSingleHit = (): FindHit => ({
export const getFindResultWithSingleHit = (isRuleRegistryEnabled: boolean): FindHit => ({
page: 1,
perPage: 1,
total: 1,
data: [getAlertMock(getQueryRuleParams())],
data: [getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())],
});
export const nonRuleFindResult = (): FindHit => ({
export const nonRuleFindResult = (isRuleRegistryEnabled: boolean): FindHit => ({
page: 1,
perPage: 1,
total: 1,
data: [nonRuleAlert()],
data: [nonRuleAlert(isRuleRegistryEnabled)],
});
export const getFindResultWithMultiHits = ({
@ -348,19 +349,22 @@ export const createActionResult = (): ActionResult => ({
isPreconfigured: false,
});
export const nonRuleAlert = () => ({
export const nonRuleAlert = (isRuleRegistryEnabled: boolean) => ({
// Defaulting to QueryRuleParams because ts doesn't like empty objects
...getAlertMock(getQueryRuleParams()),
...getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()),
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bc',
name: 'Non-Rule Alert',
alertTypeId: 'something',
});
export const getAlertMock = <T extends RuleParams>(params: T): Alert<T> => ({
export const getAlertMock = <T extends RuleParams>(
isRuleRegistryEnabled: boolean,
params: T
): Alert<T> => ({
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
name: 'Detect Root/Admin Users',
tags: [`${INTERNAL_RULE_ID_KEY}:rule-1`, `${INTERNAL_IMMUTABLE_KEY}:false`],
alertTypeId: 'siem.signals',
alertTypeId: isRuleRegistryEnabled ? ruleTypeMappings[params.type] : 'siem.signals',
consumer: 'siem',
params,
createdAt: new Date('2019-12-13T16:40:33.400Z'),

View file

@ -72,12 +72,16 @@ jest.mock('../../../timeline/routes/prepackaged_timelines/install_prepackaged_ti
};
});
describe('add_prepackaged_rules_route', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('add_prepackaged_rules_route - %s', (_, isRuleRegistryEnabled) => {
const siemMockClient = siemMock.createClient();
let server: ReturnType<typeof serverMock.create>;
let { clients, context } = requestContextMock.createTools();
let securitySetup: SecurityPluginSetup;
let mockExceptionsClient: ExceptionListClient;
const testif = isRuleRegistryEnabled ? test.skip : test;
beforeEach(() => {
server = serverMock.create();
@ -91,8 +95,10 @@ describe('add_prepackaged_rules_route', () => {
mockExceptionsClient = listMock.getExceptionListClient();
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit());
clients.rulesClient.update.mockResolvedValue(getAlertMock(getQueryRuleParams()));
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled));
clients.rulesClient.update.mockResolvedValue(
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
);
(installPrepackagedTimelines as jest.Mock).mockReset();
(installPrepackagedTimelines as jest.Mock).mockResolvedValue({
@ -106,7 +112,7 @@ describe('add_prepackaged_rules_route', () => {
context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue(
elasticsearchClientMock.createSuccessTransportRequestPromise({ _shards: { total: 1 } })
);
addPrepackedRulesRoute(server.router, createMockConfig(), securitySetup);
addPrepackedRulesRoute(server.router, createMockConfig(), securitySetup, isRuleRegistryEnabled);
});
describe('status codes', () => {
@ -129,23 +135,25 @@ describe('add_prepackaged_rules_route', () => {
});
});
test('it returns a 400 if the index does not exist', async () => {
test('it returns a 400 if the index does not exist when rule registry not enabled', async () => {
const request = addPrepackagedRulesRequest();
context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise({ _shards: { total: 0 } })
);
const response = await server.inject(request, context);
expect(response.status).toEqual(400);
expect(response.body).toEqual({
status_code: 400,
message: expect.stringContaining(
'Pre-packaged rules cannot be installed until the signals index is created'
),
});
expect(response.status).toEqual(isRuleRegistryEnabled ? 200 : 400);
if (!isRuleRegistryEnabled) {
expect(response.body).toEqual({
status_code: 400,
message: expect.stringContaining(
'Pre-packaged rules cannot be installed until the signals index is created'
),
});
}
});
it('returns 404 if siem client is unavailable', async () => {
test('returns 404 if siem client is unavailable', async () => {
const { securitySolution, ...contextWithoutSecuritySolution } = context;
const response = await server.inject(
addPrepackagedRulesRequest(),
@ -185,16 +193,19 @@ describe('add_prepackaged_rules_route', () => {
});
});
test('catches errors if payloads cause errors to be thrown', async () => {
context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue(
elasticsearchClientMock.createErrorTransportRequestPromise(new Error('Test error'))
);
const request = addPrepackagedRulesRequest();
const response = await server.inject(request, context);
testif(
'catches errors if signals index does not exist when rule registry not enabled',
async () => {
context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue(
elasticsearchClientMock.createErrorTransportRequestPromise(new Error('Test error'))
);
const request = addPrepackagedRulesRequest();
const response = await server.inject(request, context);
expect(response.status).toEqual(500);
expect(response.body).toEqual({ message: 'Test error', status_code: 500 });
});
expect(response.status).toEqual(500);
expect(response.body).toEqual({ message: 'Test error', status_code: 500 });
}
);
});
test('should install prepackaged timelines', async () => {

View file

@ -43,7 +43,8 @@ import { installPrepackagedTimelines } from '../../../timeline/routes/prepackage
export const addPrepackedRulesRoute = (
router: SecuritySolutionPluginRouter,
config: ConfigType,
security: SetupPlugins['security']
security: SetupPlugins['security'],
isRuleRegistryEnabled: boolean
) => {
router.put(
{
@ -79,7 +80,9 @@ export const addPrepackedRulesRoute = (
frameworkRequest,
config.maxTimelineImportExportSize,
config.prebuiltRulesFromFileSystem,
config.prebuiltRulesFromSavedObjects
config.prebuiltRulesFromSavedObjects,
undefined,
isRuleRegistryEnabled
);
return response.ok({ body: validated ?? {} });
} catch (err) {
@ -109,7 +112,8 @@ export const createPrepackagedRules = async (
maxTimelineImportExportSize: ConfigType['maxTimelineImportExportSize'],
prebuiltRulesFromFileSystem: ConfigType['prebuiltRulesFromFileSystem'],
prebuiltRulesFromSavedObjects: ConfigType['prebuiltRulesFromSavedObjects'],
exceptionsClient?: ExceptionListClient
exceptionsClient?: ExceptionListClient,
isRuleRegistryEnabled?: boolean | undefined
): Promise<PrePackagedRulesAndTimelinesSchema | null> => {
const esClient = context.core.elasticsearch.client;
const savedObjectsClient = context.core.savedObjects.client;
@ -131,11 +135,14 @@ export const createPrepackagedRules = async (
prebuiltRulesFromFileSystem,
prebuiltRulesFromSavedObjects
);
const prepackagedRules = await getExistingPrepackagedRules({ rulesClient });
const prepackagedRules = await getExistingPrepackagedRules({
rulesClient,
isRuleRegistryEnabled: isRuleRegistryEnabled ?? false,
});
const rulesToInstall = getRulesToInstall(latestPrepackagedRules, prepackagedRules);
const rulesToUpdate = getRulesToUpdate(latestPrepackagedRules, prepackagedRules);
const signalsIndex = siemClient.getSignalsIndex();
if (rulesToInstall.length !== 0 || rulesToUpdate.length !== 0) {
if (!isRuleRegistryEnabled && (rulesToInstall.length !== 0 || rulesToUpdate.length !== 0)) {
const signalsIndexExists = await getIndexExists(esClient.asCurrentUser, signalsIndex);
if (!signalsIndexExists) {
throw new PrepackagedRulesError(
@ -145,7 +152,14 @@ export const createPrepackagedRules = async (
}
}
await Promise.all(installPrepackagedRules(rulesClient, rulesToInstall, signalsIndex));
await Promise.all(
installPrepackagedRules(
rulesClient,
rulesToInstall,
signalsIndex,
isRuleRegistryEnabled ?? false
)
);
const timeline = await installPrepackagedTimelines(
maxTimelineImportExportSize,
frameworkRequest,
@ -160,7 +174,8 @@ export const createPrepackagedRules = async (
context.securitySolution.getSpaceId(),
ruleStatusClient,
rulesToUpdate,
signalsIndex
signalsIndex,
isRuleRegistryEnabled ?? false
);
const prepackagedRulesOutput: PrePackagedRulesAndTimelinesSchema = {

View file

@ -24,7 +24,10 @@ import { getQueryRuleParams } from '../../schemas/rule_schemas.mock';
jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create());
describe('create_rules_bulk', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('create_rules_bulk - %s', (_, isRuleRegistryEnabled) => {
let server: ReturnType<typeof serverMock.create>;
let { clients, context } = requestContextMock.createTools();
let ml: ReturnType<typeof mlServicesMock.createSetupContract>;
@ -35,12 +38,14 @@ describe('create_rules_bulk', () => {
ml = mlServicesMock.createSetupContract();
clients.rulesClient.find.mockResolvedValue(getEmptyFindResult()); // no existing rules
clients.rulesClient.create.mockResolvedValue(getAlertMock(getQueryRuleParams())); // successful creation
clients.rulesClient.create.mockResolvedValue(
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
); // successful creation
context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue(
elasticsearchClientMock.createSuccessTransportRequestPromise({ _shards: { total: 1 } })
);
createRulesBulkRoute(server.router, ml);
createRulesBulkRoute(server.router, ml, isRuleRegistryEnabled);
});
describe('status codes', () => {
@ -56,7 +61,7 @@ describe('create_rules_bulk', () => {
expect(response.body).toEqual({ message: 'Not Found', status_code: 404 });
});
it('returns 404 if siem client is unavailable', async () => {
test('returns 404 if siem client is unavailable', async () => {
const { securitySolution, ...contextWithoutSecuritySolution } = context;
// @ts-expect-error
const response = await server.inject(getReadBulkRequest(), contextWithoutSecuritySolution);
@ -66,7 +71,7 @@ describe('create_rules_bulk', () => {
});
describe('unhappy paths', () => {
it('returns a 403 error object if ML Authz fails', async () => {
test('returns a 403 error object if ML Authz fails', async () => {
(buildMlAuthz as jest.Mock).mockReturnValueOnce({
validateRuleType: jest
.fn()
@ -86,26 +91,30 @@ describe('create_rules_bulk', () => {
]);
});
it('returns an error object if the index does not exist', async () => {
test('returns an error object if the index does not exist when rule registry not enabled', async () => {
context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise({ _shards: { total: 0 } })
);
const response = await server.inject(getReadBulkRequest(), context);
expect(response.status).toEqual(200);
expect(response.body).toEqual([
{
error: {
message: 'To create a rule, the index must exist first. Index undefined does not exist',
status_code: 400,
if (!isRuleRegistryEnabled) {
expect(response.body).toEqual([
{
error: {
message:
'To create a rule, the index must exist first. Index undefined does not exist',
status_code: 400,
},
rule_id: 'rule-1',
},
rule_id: 'rule-1',
},
]);
]);
}
});
test('returns a duplicate error if rule_id already exists', async () => {
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit());
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled));
const response = await server.inject(getReadBulkRequest(), context);
expect(response.status).toEqual(200);
@ -136,7 +145,7 @@ describe('create_rules_bulk', () => {
]);
});
it('returns an error object if duplicate rule_ids found in request payload', async () => {
test('returns an error object if duplicate rule_ids found in request payload', async () => {
const request = requestMock.create({
method: 'post',
path: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`,

View file

@ -28,7 +28,8 @@ import { convertCreateAPIToInternalSchema } from '../../schemas/rule_converters'
export const createRulesBulkRoute = (
router: SecuritySolutionPluginRouter,
ml: SetupPlugins['ml']
ml: SetupPlugins['ml'],
isRuleRegistryEnabled: boolean
) => {
router.post(
{
@ -67,9 +68,10 @@ export const createRulesBulkRoute = (
.map(async (payloadRule) => {
if (payloadRule.rule_id != null) {
const rule = await readRules({
id: undefined,
isRuleRegistryEnabled,
rulesClient,
ruleId: payloadRule.rule_id,
id: undefined,
});
if (rule != null) {
return createBulkErrorObject({
@ -79,7 +81,11 @@ export const createRulesBulkRoute = (
});
}
}
const internalRule = convertCreateAPIToInternalSchema(payloadRule, siemClient);
const internalRule = convertCreateAPIToInternalSchema(
payloadRule,
siemClient,
isRuleRegistryEnabled
);
try {
const validationErrors = createRuleValidateTypeDependents(payloadRule);
if (validationErrors.length) {
@ -93,7 +99,7 @@ export const createRulesBulkRoute = (
throwHttpError(await mlAuthz.validateRuleType(internalRule.params.type));
const finalIndex = internalRule.params.outputIndex;
const indexExists = await getIndexExists(esClient.asCurrentUser, finalIndex);
if (!indexExists) {
if (!isRuleRegistryEnabled && !indexExists) {
return createBulkErrorObject({
ruleId: internalRule.params.ruleId,
statusCode: 400,
@ -112,7 +118,10 @@ export const createRulesBulkRoute = (
return transformValidateBulkError(internalRule.params.ruleId, createdRule, undefined);
} catch (err) {
return transformBulkError(internalRule.params.ruleId, err);
return transformBulkError(
internalRule.params.ruleId,
err as Error & { statusCode?: number | undefined }
);
}
})
);

View file

@ -24,7 +24,10 @@ import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mo
import { getQueryRuleParams } from '../../schemas/rule_schemas.mock';
jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create());
describe('create_rules', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('create_rules - %s', (_, isRuleRegistryEnabled) => {
let server: ReturnType<typeof serverMock.create>;
let { clients, context } = requestContextMock.createTools();
let ml: ReturnType<typeof mlServicesMock.createSetupContract>;
@ -35,13 +38,15 @@ describe('create_rules', () => {
ml = mlServicesMock.createSetupContract();
clients.rulesClient.find.mockResolvedValue(getEmptyFindResult()); // no current rules
clients.rulesClient.create.mockResolvedValue(getAlertMock(getQueryRuleParams())); // creation succeeds
clients.rulesClient.create.mockResolvedValue(
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
); // creation succeeds
clients.ruleExecutionLogClient.find.mockResolvedValue(getRuleExecutionStatuses()); // needed to transform: ;
context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue(
elasticsearchClientMock.createSuccessTransportRequestPromise({ _shards: { total: 1 } })
);
createRulesRoute(server.router, ml);
createRulesRoute(server.router, ml, isRuleRegistryEnabled);
});
describe('status codes with actionClient and alertClient', () => {
@ -57,7 +62,7 @@ describe('create_rules', () => {
expect(response.body).toEqual({ message: 'Not Found', status_code: 404 });
});
it('returns 404 if siem client is unavailable', async () => {
test('returns 404 if siem client is unavailable', async () => {
const { securitySolution, ...contextWithoutSecuritySolution } = context;
// @ts-expect-error
const response = await server.inject(getCreateRequest(), contextWithoutSecuritySolution);
@ -65,7 +70,7 @@ describe('create_rules', () => {
expect(response.body).toEqual({ message: 'Not Found', status_code: 404 });
});
it('returns 200 if license is not platinum', async () => {
test('returns 200 if license is not platinum', async () => {
(context.licensing.license.hasAtLeast as jest.Mock).mockReturnValue(false);
const response = await server.inject(getCreateRequest(), context);
@ -74,12 +79,12 @@ describe('create_rules', () => {
});
describe('creating an ML Rule', () => {
it('is successful', async () => {
test('is successful', async () => {
const response = await server.inject(createMlRuleRequest(), context);
expect(response.status).toEqual(200);
});
it('returns a 403 if ML Authz fails', async () => {
test('returns a 403 if ML Authz fails', async () => {
(buildMlAuthz as jest.Mock).mockReturnValueOnce({
validateRuleType: jest
.fn()
@ -96,21 +101,24 @@ describe('create_rules', () => {
});
describe('unhappy paths', () => {
test('it returns a 400 if the index does not exist', async () => {
test('it returns a 400 if the index does not exist when rule registry not enabled', async () => {
context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise({ _shards: { total: 0 } })
);
const response = await server.inject(getCreateRequest(), context);
expect(response.status).toEqual(400);
expect(response.body).toEqual({
message: 'To create a rule, the index must exist first. Index undefined does not exist',
status_code: 400,
});
expect(response.status).toEqual(isRuleRegistryEnabled ? 200 : 400);
if (!isRuleRegistryEnabled) {
expect(response.body).toEqual({
message: 'To create a rule, the index must exist first. Index undefined does not exist',
status_code: 400,
});
}
});
test('returns a duplicate error if rule_id already exists', async () => {
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit());
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled));
const response = await server.inject(getCreateRequest(), context);
expect(response.status).toEqual(409);

View file

@ -6,7 +6,6 @@
*/
import { transformError, getIndexExists } from '@kbn/securitysolution-es-utils';
import { IRuleDataClient } from '../../../../../../rule_registry/server';
import { buildRouteValidation } from '../../../../utils/build_validation/route_validation';
import {
DETECTION_ENGINE_RULES_URL,
@ -27,7 +26,7 @@ import { convertCreateAPIToInternalSchema } from '../../schemas/rule_converters'
export const createRulesRoute = (
router: SecuritySolutionPluginRouter,
ml: SetupPlugins['ml'],
ruleDataClient?: IRuleDataClient | null // TODO: Use this for RAC (otherwise delete it)
isRuleRegistryEnabled: boolean
): void => {
router.post(
{
@ -57,6 +56,7 @@ export const createRulesRoute = (
if (request.body.rule_id != null) {
const rule = await readRules({
isRuleRegistryEnabled,
rulesClient,
ruleId: request.body.rule_id,
id: undefined,
@ -69,7 +69,11 @@ export const createRulesRoute = (
}
}
const internalRule = convertCreateAPIToInternalSchema(request.body, siemClient);
const internalRule = convertCreateAPIToInternalSchema(
request.body,
siemClient,
isRuleRegistryEnabled
);
const mlAuthz = buildMlAuthz({
license: context.licensing.license,
@ -83,7 +87,7 @@ export const createRulesRoute = (
esClient.asCurrentUser,
internalRule.params.outputIndex
);
if (!indexExists) {
if (!isRuleRegistryEnabled && !indexExists) {
return siemResponse.error({
statusCode: 400,
body: `To create a rule, the index must exist first. Index ${internalRule.params.outputIndex} does not exist`,
@ -107,14 +111,18 @@ export const createRulesRoute = (
ruleId: createdRule.id,
spaceId: context.securitySolution.getSpaceId(),
});
const [validated, errors] = newTransformValidate(createdRule, ruleStatuses[0]);
const [validated, errors] = newTransformValidate(
createdRule,
ruleStatuses[0],
isRuleRegistryEnabled
);
if (errors != null) {
return siemResponse.error({ statusCode: 500, body: errors });
} else {
return response.ok({ body: validated ?? {} });
}
} catch (err) {
const error = transformError(err);
const error = transformError(err as Error);
return siemResponse.error({
body: error.message,
statusCode: error.statusCode,

View file

@ -18,7 +18,10 @@ import {
import { requestContextMock, serverMock, requestMock } from '../__mocks__';
import { deleteRulesBulkRoute } from './delete_rules_bulk_route';
describe('delete_rules', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('delete_rules - %s', (_, isRuleRegistryEnabled) => {
let server: ReturnType<typeof serverMock.create>;
let { clients, context } = requestContextMock.createTools();
@ -26,11 +29,11 @@ describe('delete_rules', () => {
server = serverMock.create();
({ clients, context } = requestContextMock.createTools());
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); // rule exists
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); // rule exists
clients.rulesClient.delete.mockResolvedValue({}); // successful deletion
clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse()); // rule status request
deleteRulesBulkRoute(server.router);
deleteRulesBulkRoute(server.router, isRuleRegistryEnabled);
});
describe('status codes with actionClient and alertClient', () => {

View file

@ -6,6 +6,7 @@
*/
import { validate } from '@kbn/securitysolution-io-ts-utils';
import { queryRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/query_rules_type_dependents';
import { buildRouteValidation } from '../../../../utils/build_validation/route_validation';
import {
@ -34,7 +35,10 @@ type Handler = RequestHandler<
'delete' | 'post'
>;
export const deleteRulesBulkRoute = (router: SecuritySolutionPluginRouter) => {
export const deleteRulesBulkRoute = (
router: SecuritySolutionPluginRouter,
isRuleRegistryEnabled: boolean
) => {
const config: Config = {
validate: {
body: buildRouteValidation<typeof queryRulesBulkSchema, QueryRulesBulkSchemaDecoded>(
@ -71,7 +75,7 @@ export const deleteRulesBulkRoute = (router: SecuritySolutionPluginRouter) => {
}
try {
const rule = await readRules({ rulesClient, id, ruleId });
const rule = await readRules({ rulesClient, id, ruleId, isRuleRegistryEnabled });
if (!rule) {
return getIdBulkError({ id, ruleId });
}
@ -87,7 +91,12 @@ export const deleteRulesBulkRoute = (router: SecuritySolutionPluginRouter) => {
ruleStatuses,
id: rule.id,
});
return transformValidateBulkError(idOrRuleIdOrUnknown, rule, ruleStatuses);
return transformValidateBulkError(
idOrRuleIdOrUnknown,
rule,
ruleStatuses,
isRuleRegistryEnabled
);
} catch (err) {
return transformBulkError(idOrRuleIdOrUnknown, err);
}

View file

@ -19,7 +19,10 @@ import { requestContextMock, serverMock, requestMock } from '../__mocks__';
import { deleteRulesRoute } from './delete_rules_route';
import { getQueryRuleParams } from '../../schemas/rule_schemas.mock';
describe('delete_rules', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('delete_rules - %s', (_, isRuleRegistryEnabled) => {
let server: ReturnType<typeof serverMock.create>;
let { clients, context } = requestContextMock.createTools();
@ -27,11 +30,11 @@ describe('delete_rules', () => {
server = serverMock.create();
({ clients, context } = requestContextMock.createTools());
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit());
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled));
clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse());
clients.ruleExecutionLogClient.find.mockResolvedValue(getRuleExecutionStatuses());
deleteRulesRoute(server.router);
deleteRulesRoute(server.router, isRuleRegistryEnabled);
});
describe('status codes with actionClient and alertClient', () => {
@ -42,7 +45,9 @@ describe('delete_rules', () => {
});
test('returns 200 when deleting a single rule with a valid actionClient and alertClient by id', async () => {
clients.rulesClient.get.mockResolvedValue(getAlertMock(getQueryRuleParams()));
clients.rulesClient.get.mockResolvedValue(
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
);
const response = await server.inject(getDeleteRequestById(), context);
expect(response.status).toEqual(200);

View file

@ -6,7 +6,6 @@
*/
import { transformError } from '@kbn/securitysolution-es-utils';
import { IRuleDataClient } from '../../../../../../rule_registry/server';
import { queryRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/query_rules_type_dependents';
import {
queryRulesSchema,
@ -23,7 +22,7 @@ import { readRules } from '../../rules/read_rules';
export const deleteRulesRoute = (
router: SecuritySolutionPluginRouter,
ruleDataClient?: IRuleDataClient | null
isRuleRegistryEnabled: boolean
) => {
router.delete(
{
@ -54,7 +53,7 @@ export const deleteRulesRoute = (
}
const ruleStatusClient = context.securitySolution.getExecutionLogClient();
const rule = await readRules({ rulesClient, id, ruleId });
const rule = await readRules({ isRuleRegistryEnabled, rulesClient, id, ruleId });
if (!rule) {
const error = getIdError({ id, ruleId });
return siemResponse.error({
@ -74,7 +73,7 @@ export const deleteRulesRoute = (
ruleStatuses,
id: rule.id,
});
const transformed = transform(rule, ruleStatuses[0]);
const transformed = transform(rule, ruleStatuses[0], isRuleRegistryEnabled);
if (transformed == null) {
return siemResponse.error({ statusCode: 500, body: 'failed to transform alert' });
} else {

View file

@ -21,7 +21,11 @@ import { getExportByObjectIds } from '../../rules/get_export_by_object_ids';
import { getExportAll } from '../../rules/get_export_all';
import { buildSiemResponse } from '../utils';
export const exportRulesRoute = (router: SecuritySolutionPluginRouter, config: ConfigType) => {
export const exportRulesRoute = (
router: SecuritySolutionPluginRouter,
config: ConfigType,
isRuleRegistryEnabled: boolean
) => {
router.post(
{
path: `${DETECTION_ENGINE_RULES_URL}/_export`,
@ -53,7 +57,10 @@ export const exportRulesRoute = (router: SecuritySolutionPluginRouter, config: C
body: `Can't export more than ${exportSizeLimit} rules`,
});
} else {
const nonPackagedRulesCount = await getNonPackagedRulesCount({ rulesClient });
const nonPackagedRulesCount = await getNonPackagedRulesCount({
isRuleRegistryEnabled,
rulesClient,
});
if (nonPackagedRulesCount > exportSizeLimit) {
return siemResponse.error({
statusCode: 400,
@ -64,8 +71,8 @@ export const exportRulesRoute = (router: SecuritySolutionPluginRouter, config: C
const exported =
request.body?.objects != null
? await getExportByObjectIds(rulesClient, request.body.objects)
: await getExportAll(rulesClient);
? await getExportByObjectIds(rulesClient, request.body.objects, isRuleRegistryEnabled)
: await getExportAll(rulesClient, isRuleRegistryEnabled);
const responseBody = request.query.exclude_export_details
? exported.rulesNdjson

View file

@ -17,7 +17,10 @@ import {
} from '../__mocks__/request_responses';
import { findRulesRoute } from './find_rules_route';
describe('find_rules', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('find_rules - %s', (_, isRuleRegistryEnabled) => {
let server: ReturnType<typeof serverMock.create>;
let { clients, context } = requestContextMock.createTools();
@ -25,12 +28,14 @@ describe('find_rules', () => {
server = serverMock.create();
({ clients, context } = requestContextMock.createTools());
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit());
clients.rulesClient.get.mockResolvedValue(getAlertMock(getQueryRuleParams()));
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled));
clients.rulesClient.get.mockResolvedValue(
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
);
clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse());
clients.ruleExecutionLogClient.findBulk.mockResolvedValue(getFindBulkResultStatus());
findRulesRoute(server.router);
findRulesRoute(server.router, isRuleRegistryEnabled);
});
describe('status codes with actionClient and alertClient', () => {

View file

@ -6,7 +6,6 @@
*/
import { transformError } from '@kbn/securitysolution-es-utils';
import { IRuleDataClient } from '../../../../../../rule_registry/server';
import { findRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/find_rules_type_dependents';
import {
findRulesSchema,
@ -21,7 +20,7 @@ import { transformFindAlerts } from './utils';
export const findRulesRoute = (
router: SecuritySolutionPluginRouter,
ruleDataClient?: IRuleDataClient | null
isRuleRegistryEnabled: boolean
) => {
router.get(
{
@ -52,6 +51,7 @@ export const findRulesRoute = (
const execLogClient = context.securitySolution.getExecutionLogClient();
const rules = await findRules({
isRuleRegistryEnabled,
rulesClient,
perPage: query.per_page,
page: query.page,

View file

@ -17,7 +17,10 @@ import { RuleStatusResponse } from '../../rules/types';
import { AlertExecutionStatusErrorReasons } from '../../../../../../alerting/common';
import { getQueryRuleParams } from '../../schemas/rule_schemas.mock';
describe('find_statuses', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('find_statuses - %s', (_, isRuleRegistryEnabled) => {
let server: ReturnType<typeof serverMock.create>;
let { clients, context } = requestContextMock.createTools();
@ -25,7 +28,9 @@ describe('find_statuses', () => {
server = serverMock.create();
({ clients, context } = requestContextMock.createTools());
clients.ruleExecutionLogClient.findBulk.mockResolvedValue(getFindBulkResultStatus()); // successful status search
clients.rulesClient.get.mockResolvedValue(getAlertMock(getQueryRuleParams()));
clients.rulesClient.get.mockResolvedValue(
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
);
findRulesStatusesRoute(server.router);
});
@ -57,7 +62,7 @@ describe('find_statuses', () => {
test('returns success if rule status client writes an error status', async () => {
// 0. task manager tried to run the rule but couldn't, so the alerting framework
// wrote an error to the executionStatus.
const failingExecutionRule = getAlertMock(getQueryRuleParams());
const failingExecutionRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
failingExecutionRule.executionStatus = {
status: 'error',
lastExecutionDate: failingExecutionRule.executionStatus.lastExecutionDate,

View file

@ -51,7 +51,10 @@ jest.mock('../../../timeline/utils/check_timelines_status', () => {
};
});
describe('get_prepackaged_rule_status_route', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('get_prepackaged_rule_status_route - %s', (_, isRuleRegistryEnabled) => {
const mockGetCurrentUser = {
user: {
username: 'mockUser',
@ -63,6 +66,7 @@ describe('get_prepackaged_rule_status_route', () => {
let securitySetup: SecurityPluginSetup;
beforeEach(() => {
jest.clearAllMocks();
server = serverMock.create();
({ clients, context } = requestContextMock.createTools());
@ -75,7 +79,18 @@ describe('get_prepackaged_rule_status_route', () => {
clients.rulesClient.find.mockResolvedValue(getEmptyFindResult());
getPrepackagedRulesStatusRoute(server.router, createMockConfig(), securitySetup);
(checkTimelinesStatus as jest.Mock).mockResolvedValue({
timelinesToInstall: [],
timelinesToUpdate: [],
prepackagedTimelines: [],
});
getPrepackagedRulesStatusRoute(
server.router,
createMockConfig(),
securitySetup,
isRuleRegistryEnabled
);
});
describe('status codes with actionClient and alertClient', () => {
@ -123,7 +138,7 @@ describe('get_prepackaged_rule_status_route', () => {
});
test('1 rule installed, 1 custom rules, 0 rules not installed, and 1 rule to not updated', async () => {
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit());
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled));
const request = getPrepackagedRulesStatusRequest();
const response = await server.inject(request, context);

View file

@ -32,7 +32,8 @@ import {
export const getPrepackagedRulesStatusRoute = (
router: SecuritySolutionPluginRouter,
config: ConfigType,
security: SetupPlugins['security']
security: SetupPlugins['security'],
isRuleRegistryEnabled: boolean
) => {
router.get(
{
@ -59,6 +60,7 @@ export const getPrepackagedRulesStatusRoute = (
config.prebuiltRulesFromSavedObjects
);
const customRules = await findRules({
isRuleRegistryEnabled,
rulesClient,
perPage: 1,
page: 1,
@ -68,7 +70,10 @@ export const getPrepackagedRulesStatusRoute = (
fields: undefined,
});
const frameworkRequest = await buildFrameworkRequest(context, security, request);
const prepackagedRules = await getExistingPrepackagedRules({ rulesClient });
const prepackagedRules = await getExistingPrepackagedRules({
rulesClient,
isRuleRegistryEnabled,
});
const rulesToInstall = getRulesToInstall(latestPrepackagedRules, prepackagedRules);
const rulesToUpdate = getRulesToUpdate(latestPrepackagedRules, prepackagedRules);

View file

@ -29,7 +29,10 @@ import { getQueryRuleParams } from '../../schemas/rule_schemas.mock';
jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create());
describe('import_rules_route', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('import_rules_route - %s', (_, isRuleRegistryEnabled) => {
let config: ReturnType<typeof createMockConfig>;
let server: ReturnType<typeof serverMock.create>;
let request: ReturnType<typeof requestMock.create>;
@ -45,11 +48,13 @@ describe('import_rules_route', () => {
ml = mlServicesMock.createSetupContract();
clients.rulesClient.find.mockResolvedValue(getEmptyFindResult()); // no extant rules
clients.rulesClient.update.mockResolvedValue(getAlertMock(getQueryRuleParams()));
clients.rulesClient.update.mockResolvedValue(
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
);
context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue(
elasticsearchClientMock.createSuccessTransportRequestPromise({ _shards: { total: 1 } })
);
importRulesRoute(server.router, config, ml);
importRulesRoute(server.router, config, ml, isRuleRegistryEnabled);
});
describe('status codes', () => {
@ -60,7 +65,7 @@ describe('import_rules_route', () => {
});
test('returns 500 if more than 10,000 rules are imported', async () => {
const ruleIds = new Array(10001).fill(undefined).map((_, index) => `rule-${index}`);
const ruleIds = new Array(10001).fill(undefined).map((__, index) => `rule-${index}`);
const multiRequest = getImportRulesRequest(buildHapiStream(ruleIdsToNdJsonString(ruleIds)));
const response = await server.inject(multiRequest, context);
@ -125,18 +130,20 @@ describe('import_rules_route', () => {
transformMock.mockRestore();
});
test('returns an error if the index does not exist', async () => {
test('returns an error if the index does not exist when rule registry not enabled', async () => {
clients.appClient.getSignalsIndex.mockReturnValue('mockSignalsIndex');
context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise({ _shards: { total: 0 } })
);
const response = await server.inject(request, context);
expect(response.status).toEqual(400);
expect(response.body).toEqual({
message:
'To create a rule, the index must exist first. Index mockSignalsIndex does not exist',
status_code: 400,
});
expect(response.status).toEqual(isRuleRegistryEnabled ? 200 : 400);
if (!isRuleRegistryEnabled) {
expect(response.body).toEqual({
message:
'To create a rule, the index must exist first. Index mockSignalsIndex does not exist',
status_code: 400,
});
}
});
test('returns an error when cluster throws error', async () => {
@ -166,7 +173,9 @@ describe('import_rules_route', () => {
describe('single rule import', () => {
test('returns 200 if rule imported successfully', async () => {
clients.rulesClient.create.mockResolvedValue(getAlertMock(getQueryRuleParams()));
clients.rulesClient.create.mockResolvedValue(
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
);
const response = await server.inject(request, context);
expect(response.status).toEqual(200);
expect(response.body).toEqual({
@ -199,7 +208,9 @@ describe('import_rules_route', () => {
describe('rule with existing rule_id', () => {
test('returns with reported conflict if `overwrite` is set to `false`', async () => {
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); // extant rule
clients.rulesClient.find.mockResolvedValue(
getFindResultWithSingleHit(isRuleRegistryEnabled)
); // extant rule
const response = await server.inject(request, context);
expect(response.status).toEqual(200);
@ -219,7 +230,9 @@ describe('import_rules_route', () => {
});
test('returns with NO reported conflict if `overwrite` is set to `true`', async () => {
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); // extant rule
clients.rulesClient.find.mockResolvedValue(
getFindResultWithSingleHit(isRuleRegistryEnabled)
); // extant rule
const overwriteRequest = getImportRulesRequestOverwriteTrue(
buildHapiStream(ruleIdsToNdJsonString(['rule-1']))
);
@ -251,7 +264,7 @@ describe('import_rules_route', () => {
});
test('returns 200 if many rules are imported successfully', async () => {
const ruleIds = new Array(9999).fill(undefined).map((_, index) => `rule-${index}`);
const ruleIds = new Array(9999).fill(undefined).map((__, index) => `rule-${index}`);
const multiRequest = getImportRulesRequest(buildHapiStream(ruleIdsToNdJsonString(ruleIds)));
const response = await server.inject(multiRequest, context);
@ -339,7 +352,9 @@ describe('import_rules_route', () => {
describe('rules with existing rule_id', () => {
beforeEach(() => {
clients.rulesClient.find.mockResolvedValueOnce(getFindResultWithSingleHit()); // extant rule
clients.rulesClient.find.mockResolvedValueOnce(
getFindResultWithSingleHit(isRuleRegistryEnabled)
); // extant rule
});
test('returns with reported conflict if `overwrite` is set to `false`', async () => {

View file

@ -53,7 +53,8 @@ const CHUNK_PARSED_OBJECT_SIZE = 50;
export const importRulesRoute = (
router: SecuritySolutionPluginRouter,
config: ConfigType,
ml: SetupPlugins['ml']
ml: SetupPlugins['ml'],
isRuleRegistryEnabled: boolean
) => {
router.post(
{
@ -103,7 +104,7 @@ export const importRulesRoute = (
}
const signalsIndex = siemClient.getSignalsIndex();
const indexExists = await getIndexExists(esClient.asCurrentUser, signalsIndex);
if (!indexExists) {
if (!isRuleRegistryEnabled && !indexExists) {
return siemResponse.error({
statusCode: 400,
body: `To create a rule, the index must exist first. Index ${signalsIndex} does not exist`,
@ -205,6 +206,7 @@ export const importRulesRoute = (
const filters: PartialFilter[] | undefined = filtersRest as PartialFilter[];
throwHttpError(await mlAuthz.validateRuleType(type));
const rule = await readRules({
isRuleRegistryEnabled,
rulesClient,
ruleId,
id: undefined,
@ -212,6 +214,7 @@ export const importRulesRoute = (
if (rule == null) {
await createRules({
isRuleRegistryEnabled,
rulesClient,
anomalyThreshold,
author,

View file

@ -22,7 +22,10 @@ import { getQueryRuleParams } from '../../schemas/rule_schemas.mock';
jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create());
describe('patch_rules_bulk', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('patch_rules_bulk - %s', (_, isRuleRegistryEnabled) => {
let server: ReturnType<typeof serverMock.create>;
let { clients, context } = requestContextMock.createTools();
let ml: ReturnType<typeof mlServicesMock.createSetupContract>;
@ -32,10 +35,12 @@ describe('patch_rules_bulk', () => {
({ clients, context } = requestContextMock.createTools());
ml = mlServicesMock.createSetupContract();
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); // rule exists
clients.rulesClient.update.mockResolvedValue(getAlertMock(getQueryRuleParams())); // update succeeds
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); // rule exists
clients.rulesClient.update.mockResolvedValue(
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
); // update succeeds
patchRulesBulkRoute(server.router, ml);
patchRulesBulkRoute(server.router, ml, isRuleRegistryEnabled);
});
describe('status codes with actionClient and alertClient', () => {

View file

@ -27,7 +27,8 @@ import { PartialFilter } from '../../types';
export const patchRulesBulkRoute = (
router: SecuritySolutionPluginRouter,
ml: SetupPlugins['ml']
ml: SetupPlugins['ml'],
isRuleRegistryEnabled: boolean
) => {
router.patch(
{
@ -121,7 +122,12 @@ export const patchRulesBulkRoute = (
throwHttpError(await mlAuthz.validateRuleType(type));
}
const existingRule = await readRules({ rulesClient, ruleId, id });
const existingRule = await readRules({
isRuleRegistryEnabled,
rulesClient,
ruleId,
id,
});
if (existingRule?.params.type) {
// reject an unauthorized modification of an ML rule
throwHttpError(await mlAuthz.validateRuleType(existingRule?.params.type));
@ -185,7 +191,7 @@ export const patchRulesBulkRoute = (
ruleId: rule.id,
spaceId: context.securitySolution.getSpaceId(),
});
return transformValidateBulkError(rule.id, rule, ruleStatuses);
return transformValidateBulkError(rule.id, rule, ruleStatuses, isRuleRegistryEnabled);
} else {
return getIdBulkError({ id, ruleId });
}

View file

@ -25,7 +25,10 @@ import { getQueryRuleParams } from '../../schemas/rule_schemas.mock';
jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create());
describe('patch_rules', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('patch_rules - %s', (_, isRuleRegistryEnabled) => {
let server: ReturnType<typeof serverMock.create>;
let { clients, context } = requestContextMock.createTools();
let ml: ReturnType<typeof mlServicesMock.createSetupContract>;
@ -35,14 +38,18 @@ describe('patch_rules', () => {
({ clients, context } = requestContextMock.createTools());
ml = mlServicesMock.createSetupContract();
clients.rulesClient.get.mockResolvedValue(getAlertMock(getQueryRuleParams())); // existing rule
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); // existing rule
clients.rulesClient.update.mockResolvedValue(getAlertMock(getQueryRuleParams())); // successful update
clients.rulesClient.get.mockResolvedValue(
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
); // existing rule
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); // existing rule
clients.rulesClient.update.mockResolvedValue(
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
); // successful update
clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse()); // successful transform
clients.savedObjectsClient.create.mockResolvedValue(getRuleExecutionStatuses()[0]); // successful transform
clients.ruleExecutionLogClient.find.mockResolvedValue(getRuleExecutionStatuses());
patchRulesRoute(server.router, ml);
patchRulesRoute(server.router, ml, isRuleRegistryEnabled);
});
describe('status codes with actionClient and alertClient', () => {
@ -69,7 +76,7 @@ describe('patch_rules', () => {
});
test('returns error if requesting a non-rule', async () => {
clients.rulesClient.find.mockResolvedValue(nonRuleFindResult());
clients.rulesClient.find.mockResolvedValue(nonRuleFindResult(isRuleRegistryEnabled));
const response = await server.inject(getPatchRequest(), context);
expect(response.status).toEqual(404);
expect(response.body).toEqual({

View file

@ -6,7 +6,6 @@
*/
import { transformError } from '@kbn/securitysolution-es-utils';
import { IRuleDataClient } from '../../../../../../rule_registry/server';
import { RuleAlertAction } from '../../../../../common/detection_engine/types';
import { patchRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/patch_rules_type_dependents';
import { buildRouteValidation } from '../../../../utils/build_validation/route_validation';
@ -30,7 +29,7 @@ import { PartialFilter } from '../../types';
export const patchRulesRoute = (
router: SecuritySolutionPluginRouter,
ml: SetupPlugins['ml'],
ruleDataClient?: IRuleDataClient | null
isRuleRegistryEnabled: boolean
) => {
router.patch(
{
@ -124,7 +123,12 @@ export const patchRulesRoute = (
throwHttpError(await mlAuthz.validateRuleType(type));
}
const existingRule = await readRules({ rulesClient, ruleId, id });
const existingRule = await readRules({
isRuleRegistryEnabled,
rulesClient,
ruleId,
id,
});
if (existingRule?.params.type) {
// reject an unauthorized modification of an ML rule
throwHttpError(await mlAuthz.validateRuleType(existingRule?.params.type));
@ -189,7 +193,11 @@ export const patchRulesRoute = (
spaceId: context.securitySolution.getSpaceId(),
});
const [validated, errors] = transformValidate(rule, ruleStatuses[0]);
const [validated, errors] = transformValidate(
rule,
ruleStatuses[0],
isRuleRegistryEnabled
);
if (errors != null) {
return siemResponse.error({ statusCode: 500, body: errors });
} else {

View file

@ -20,7 +20,10 @@ import { getPerformBulkActionSchemaMock } from '../../../../../common/detection_
jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create());
describe('perform_bulk_action', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('perform_bulk_action - %s', (_, isRuleRegistryEnabled) => {
let server: ReturnType<typeof serverMock.create>;
let { clients, context } = requestContextMock.createTools();
let ml: ReturnType<typeof mlServicesMock.createSetupContract>;
@ -30,9 +33,9 @@ describe('perform_bulk_action', () => {
({ clients, context } = requestContextMock.createTools());
ml = mlServicesMock.createSetupContract();
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit());
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled));
performBulkActionRoute(server.router, ml);
performBulkActionRoute(server.router, ml, isRuleRegistryEnabled);
});
describe('status codes', () => {

View file

@ -25,7 +25,8 @@ const BULK_ACTION_RULES_LIMIT = 10000;
export const performBulkActionRoute = (
router: SecuritySolutionPluginRouter,
ml: SetupPlugins['ml']
ml: SetupPlugins['ml'],
isRuleRegistryEnabled: boolean
) => {
router.post(
{
@ -58,6 +59,7 @@ export const performBulkActionRoute = (
}
const rules = await findRules({
isRuleRegistryEnabled,
rulesClient,
perPage: BULK_ACTION_RULES_LIMIT,
filter: body.query !== '' ? body.query : undefined,
@ -131,7 +133,8 @@ export const performBulkActionRoute = (
case BulkAction.export:
const exported = await getExportByObjectIds(
rulesClient,
rules.data.map(({ params }) => ({ rule_id: params.ruleId }))
rules.data.map(({ params }) => ({ rule_id: params.ruleId })),
isRuleRegistryEnabled
);
const responseBody = `${exported.rulesNdjson}${exported.exportDetails}`;

View file

@ -16,7 +16,10 @@ import {
} from '../__mocks__/request_responses';
import { requestMock, requestContextMock, serverMock } from '../__mocks__';
describe('read_signals', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('read_rules - %s', (_, isRuleRegistryEnabled) => {
let server: ReturnType<typeof serverMock.create>;
let { clients, context } = requestContextMock.createTools();
@ -24,11 +27,11 @@ describe('read_signals', () => {
server = serverMock.create();
({ clients, context } = requestContextMock.createTools());
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); // rule exists
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); // rule exists
clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse()); // successful transform
clients.ruleExecutionLogClient.find.mockResolvedValue([]);
readRulesRoute(server.router);
readRulesRoute(server.router, isRuleRegistryEnabled);
});
describe('status codes with actionClient and alertClient', () => {
@ -45,7 +48,7 @@ describe('read_signals', () => {
});
test('returns error if requesting a non-rule', async () => {
clients.rulesClient.find.mockResolvedValue(nonRuleFindResult());
clients.rulesClient.find.mockResolvedValue(nonRuleFindResult(isRuleRegistryEnabled));
const response = await server.inject(getReadRequest(), context);
expect(response.status).toEqual(404);
expect(response.body).toEqual({

View file

@ -6,7 +6,6 @@
*/
import { transformError } from '@kbn/securitysolution-es-utils';
import { IRuleDataClient } from '../../../../../../rule_registry/server';
import { queryRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/query_rules_type_dependents';
import {
queryRulesSchema,
@ -23,7 +22,7 @@ import { RuleExecutionStatus } from '../../../../../common/detection_engine/sche
export const readRulesRoute = (
router: SecuritySolutionPluginRouter,
ruleDataClient?: IRuleDataClient | null
isRuleRegistryEnabled: boolean
) => {
router.get(
{
@ -55,8 +54,9 @@ export const readRulesRoute = (
const ruleStatusClient = context.securitySolution.getExecutionLogClient();
const rule = await readRules({
rulesClient,
id,
isRuleRegistryEnabled,
rulesClient,
ruleId,
});
if (rule != null) {
@ -72,7 +72,7 @@ export const readRulesRoute = (
currentStatus.attributes.statusDate = rule.executionStatus.lastExecutionDate.toISOString();
currentStatus.attributes.status = RuleExecutionStatus.failed;
}
const transformed = transform(rule, currentStatus);
const transformed = transform(rule, currentStatus, isRuleRegistryEnabled);
if (transformed == null) {
return siemResponse.error({ statusCode: 500, body: 'Internal error transforming' });
} else {

View file

@ -23,7 +23,10 @@ import { getQueryRuleParams } from '../../schemas/rule_schemas.mock';
jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create());
describe('update_rules_bulk', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('update_rules_bulk - %s', (_, isRuleRegistryEnabled) => {
let server: ReturnType<typeof serverMock.create>;
let { clients, context } = requestContextMock.createTools();
let ml: ReturnType<typeof mlServicesMock.createSetupContract>;
@ -33,10 +36,12 @@ describe('update_rules_bulk', () => {
({ clients, context } = requestContextMock.createTools());
ml = mlServicesMock.createSetupContract();
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit());
clients.rulesClient.update.mockResolvedValue(getAlertMock(getQueryRuleParams()));
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled));
clients.rulesClient.update.mockResolvedValue(
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
);
updateRulesBulkRoute(server.router, ml);
updateRulesBulkRoute(server.router, ml, isRuleRegistryEnabled);
});
describe('status codes with actionClient and alertClient', () => {

View file

@ -22,7 +22,8 @@ import { updateRules } from '../../rules/update_rules';
export const updateRulesBulkRoute = (
router: SecuritySolutionPluginRouter,
ml: SetupPlugins['ml']
ml: SetupPlugins['ml'],
isRuleRegistryEnabled: boolean
) => {
router.put(
{
@ -74,6 +75,7 @@ export const updateRulesBulkRoute = (
ruleStatusClient,
defaultOutputIndex: siemClient.getSignalsIndex(),
ruleUpdate: payloadRule,
isRuleRegistryEnabled,
});
if (rule != null) {
const ruleStatuses = await ruleStatusClient.find({
@ -81,7 +83,7 @@ export const updateRulesBulkRoute = (
ruleId: rule.id,
spaceId: context.securitySolution.getSpaceId(),
});
return transformValidateBulkError(rule.id, rule, ruleStatuses);
return transformValidateBulkError(rule.id, rule, ruleStatuses, isRuleRegistryEnabled);
} else {
return getIdBulkError({ id: payloadRule.id, ruleId: payloadRule.rule_id });
}

View file

@ -23,7 +23,10 @@ import { getQueryRuleParams } from '../../schemas/rule_schemas.mock';
jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create());
describe('update_rules', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('update_rules - %s', (_, isRuleRegistryEnabled) => {
let server: ReturnType<typeof serverMock.create>;
let { clients, context } = requestContextMock.createTools();
let ml: ReturnType<typeof mlServicesMock.createSetupContract>;
@ -33,12 +36,16 @@ describe('update_rules', () => {
({ clients, context } = requestContextMock.createTools());
ml = mlServicesMock.createSetupContract();
clients.rulesClient.get.mockResolvedValue(getAlertMock(getQueryRuleParams())); // existing rule
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); // rule exists
clients.rulesClient.update.mockResolvedValue(getAlertMock(getQueryRuleParams())); // successful update
clients.rulesClient.get.mockResolvedValue(
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
); // existing rule
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); // rule exists
clients.rulesClient.update.mockResolvedValue(
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
); // successful update
clients.ruleExecutionLogClient.find.mockResolvedValue([]); // successful transform: ;
updateRulesRoute(server.router, ml);
updateRulesRoute(server.router, ml, isRuleRegistryEnabled);
});
describe('status codes with actionClient and alertClient', () => {
@ -75,7 +82,7 @@ describe('update_rules', () => {
});
test('returns error when updating non-rule', async () => {
clients.rulesClient.find.mockResolvedValue(nonRuleFindResult());
clients.rulesClient.find.mockResolvedValue(nonRuleFindResult(isRuleRegistryEnabled));
const response = await server.inject(getUpdateRequest(), context);
expect(response.status).toEqual(404);

View file

@ -6,7 +6,6 @@
*/
import { transformError } from '@kbn/securitysolution-es-utils';
import { IRuleDataClient } from '../../../../../../rule_registry/server';
import { updateRulesSchema } from '../../../../../common/detection_engine/schemas/request';
import { updateRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/update_rules_type_dependents';
import type { SecuritySolutionPluginRouter } from '../../../../types';
@ -24,7 +23,7 @@ import { buildRouteValidation } from '../../../../utils/build_validation/route_v
export const updateRulesRoute = (
router: SecuritySolutionPluginRouter,
ml: SetupPlugins['ml'],
ruleDataClient?: IRuleDataClient | null
isRuleRegistryEnabled: boolean
) => {
router.put(
{
@ -61,11 +60,12 @@ export const updateRulesRoute = (
const ruleStatusClient = context.securitySolution.getExecutionLogClient();
const rule = await updateRules({
spaceId: context.securitySolution.getSpaceId(),
defaultOutputIndex: siemClient.getSignalsIndex(),
isRuleRegistryEnabled,
rulesClient,
ruleStatusClient,
defaultOutputIndex: siemClient.getSignalsIndex(),
ruleUpdate: request.body,
spaceId: context.securitySolution.getSpaceId(),
});
if (rule != null) {
@ -74,7 +74,11 @@ export const updateRulesRoute = (
ruleId: rule.id,
spaceId: context.securitySolution.getSpaceId(),
});
const [validated, errors] = transformValidate(rule, ruleStatuses[0]);
const [validated, errors] = transformValidate(
rule,
ruleStatuses[0],
isRuleRegistryEnabled
);
if (errors != null) {
return siemResponse.error({ statusCode: 500, body: errors });
} else {

View file

@ -41,16 +41,19 @@ import {
type PromiseFromStreams = ImportRulesSchemaDecoded | Error;
describe('utils', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('utils - %s', (_, isRuleRegistryEnabled) => {
describe('transformAlertToRule', () => {
test('should work with a full data set', () => {
const fullRule = getAlertMock(getQueryRuleParams());
const fullRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
const rule = transformAlertToRule(fullRule);
expect(rule).toEqual(getOutputRuleAlertForRest());
});
test('should omit note if note is undefined', () => {
const fullRule = getAlertMock(getQueryRuleParams());
const fullRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
fullRule.params.note = undefined;
const rule = transformAlertToRule(fullRule);
const { note, ...expectedWithoutNote } = getOutputRuleAlertForRest();
@ -58,7 +61,7 @@ describe('utils', () => {
});
test('should return enabled is equal to false', () => {
const fullRule = getAlertMock(getQueryRuleParams());
const fullRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
fullRule.enabled = false;
const ruleWithEnabledFalse = transformAlertToRule(fullRule);
const expected = getOutputRuleAlertForRest();
@ -67,7 +70,7 @@ describe('utils', () => {
});
test('should return immutable is equal to false', () => {
const fullRule = getAlertMock(getQueryRuleParams());
const fullRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
fullRule.params.immutable = false;
const ruleWithEnabledFalse = transformAlertToRule(fullRule);
const expected = getOutputRuleAlertForRest();
@ -75,7 +78,7 @@ describe('utils', () => {
});
test('should work with tags but filter out any internal tags', () => {
const fullRule = getAlertMock(getQueryRuleParams());
const fullRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
fullRule.tags = ['tag 1', 'tag 2', `${INTERNAL_IDENTIFIER}_some_other_value`];
const rule = transformAlertToRule(fullRule);
const expected = getOutputRuleAlertForRest();
@ -84,7 +87,7 @@ describe('utils', () => {
});
test('transforms ML Rule fields', () => {
const mlRule = getAlertMock(getMlRuleParams());
const mlRule = getAlertMock(isRuleRegistryEnabled, getMlRuleParams());
mlRule.params.anomalyThreshold = 55;
mlRule.params.machineLearningJobId = ['some_job_id'];
mlRule.params.type = 'machine_learning';
@ -100,7 +103,7 @@ describe('utils', () => {
});
test('transforms threat_matching fields', () => {
const threatRule = getAlertMock(getThreatRuleParams());
const threatRule = getAlertMock(isRuleRegistryEnabled, getThreatRuleParams());
const threatFilters: PartialFilter[] = [
{
query: {
@ -153,7 +156,7 @@ describe('utils', () => {
test('does not leak a lists structure in the transform which would cause validation issues', () => {
const result: RuleAlertType & { lists: [] } = {
lists: [],
...getAlertMock(getQueryRuleParams()),
...getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()),
};
const rule = transformAlertToRule(result);
expect(rule).toEqual(
@ -168,7 +171,7 @@ describe('utils', () => {
test('does not leak an exceptions_list structure in the transform which would cause validation issues', () => {
const result: RuleAlertType & { exceptions_list: [] } = {
exceptions_list: [],
...getAlertMock(getQueryRuleParams()),
...getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()),
};
const rule = transformAlertToRule(result);
expect(rule).toEqual(
@ -265,7 +268,7 @@ describe('utils', () => {
page: 1,
perPage: 0,
total: 0,
data: [getAlertMock(getQueryRuleParams())],
data: [getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())],
},
{}
);
@ -281,14 +284,18 @@ describe('utils', () => {
describe('transform', () => {
test('outputs 200 if the data is of type siem alert', () => {
const output = transform(getAlertMock(getQueryRuleParams()));
const output = transform(
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()),
undefined,
isRuleRegistryEnabled
);
const expected = getOutputRuleAlertForRest();
expect(output).toEqual(expected);
});
test('returns 500 if the data is not of type siem alert', () => {
const unsafeCast = ({ data: [{ random: 1 }] } as unknown) as PartialAlert;
const output = transform(unsafeCast);
const output = transform(unsafeCast, undefined, isRuleRegistryEnabled);
expect(output).toBeNull();
});
});
@ -396,24 +403,34 @@ describe('utils', () => {
describe('transformOrBulkError', () => {
test('outputs 200 if the data is of type siem alert', () => {
const output = transformOrBulkError('rule-1', getAlertMock(getQueryRuleParams()), {
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
actions: [],
ruleThrottle: 'no_actions',
alertThrottle: null,
});
const output = transformOrBulkError(
'rule-1',
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()),
{
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
actions: [],
ruleThrottle: 'no_actions',
alertThrottle: null,
},
isRuleRegistryEnabled
);
const expected = getOutputRuleAlertForRest();
expect(output).toEqual(expected);
});
test('returns 500 if the data is not of type siem alert', () => {
const unsafeCast = ({ name: 'something else' } as unknown) as PartialAlert;
const output = transformOrBulkError('rule-1', unsafeCast, {
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
actions: [],
ruleThrottle: 'no_actions',
alertThrottle: null,
});
const output = transformOrBulkError(
'rule-1',
unsafeCast,
{
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
actions: [],
ruleThrottle: 'no_actions',
alertThrottle: null,
},
isRuleRegistryEnabled
);
const expected: BulkError = {
rule_id: 'rule-1',
error: { message: 'Internal error transforming', status_code: 500 },
@ -428,15 +445,15 @@ describe('utils', () => {
});
test('given single alert will return the alert transformed', () => {
const result1 = getAlertMock(getQueryRuleParams());
const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
const transformed = transformAlertsToRules([result1]);
const expected = getOutputRuleAlertForRest();
expect(transformed).toEqual([expected]);
});
test('given two alerts will return the two alerts transformed', () => {
const result1 = getAlertMock(getQueryRuleParams());
const result2 = getAlertMock(getQueryRuleParams());
const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result2.id = 'some other id';
result2.params.ruleId = 'some other id';
@ -451,11 +468,16 @@ describe('utils', () => {
describe('transformOrImportError', () => {
test('returns 1 given success if the alert is an alert type and the existing success count is 0', () => {
const output = transformOrImportError('rule-1', getAlertMock(getQueryRuleParams()), {
success: true,
success_count: 0,
errors: [],
});
const output = transformOrImportError(
'rule-1',
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()),
{
success: true,
success_count: 0,
errors: [],
},
isRuleRegistryEnabled
);
const expected: ImportSuccessError = {
success: true,
errors: [],
@ -465,11 +487,16 @@ describe('utils', () => {
});
test('returns 2 given successes if the alert is an alert type and the existing success count is 1', () => {
const output = transformOrImportError('rule-1', getAlertMock(getQueryRuleParams()), {
success: true,
success_count: 1,
errors: [],
});
const output = transformOrImportError(
'rule-1',
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()),
{
success: true,
success_count: 1,
errors: [],
},
isRuleRegistryEnabled
);
const expected: ImportSuccessError = {
success: true,
errors: [],
@ -480,11 +507,16 @@ describe('utils', () => {
test('returns 1 error and success of false if the data is not of type siem alert', () => {
const unsafeCast = ({ name: 'something else' } as unknown) as PartialAlert;
const output = transformOrImportError('rule-1', unsafeCast, {
success: true,
success_count: 1,
errors: [],
});
const output = transformOrImportError(
'rule-1',
unsafeCast,
{
success: true,
success_count: 1,
errors: [],
},
isRuleRegistryEnabled
);
const expected: ImportSuccessError = {
success: false,
errors: [

View file

@ -135,9 +135,10 @@ export const transformFindAlerts = (
export const transform = (
alert: PartialAlert<RuleParams>,
ruleStatus?: SavedObject<IRuleSavedAttributesSavedObjectAttributes>
ruleStatus?: SavedObject<IRuleSavedAttributesSavedObjectAttributes>,
isRuleRegistryEnabled?: boolean
): Partial<RulesSchema> | null => {
if (isAlertType(alert)) {
if (isAlertType(isRuleRegistryEnabled ?? false, alert)) {
return transformAlertToRule(
alert,
isRuleStatusSavedObjectType(ruleStatus) ? ruleStatus : undefined
@ -150,9 +151,10 @@ export const transform = (
export const transformOrBulkError = (
ruleId: string,
alert: PartialAlert<RuleParams>,
ruleStatus?: unknown
ruleStatus?: unknown,
isRuleRegistryEnabled?: boolean
): Partial<RulesSchema> | BulkError => {
if (isAlertType(alert)) {
if (isAlertType(isRuleRegistryEnabled ?? false, alert)) {
if (isRuleStatusFindType(ruleStatus) && ruleStatus?.saved_objects.length > 0) {
return transformAlertToRule(alert, ruleStatus?.saved_objects[0] ?? ruleStatus);
} else {
@ -170,9 +172,10 @@ export const transformOrBulkError = (
export const transformOrImportError = (
ruleId: string,
alert: PartialAlert<RuleParams>,
existingImportSuccessError: ImportSuccessError
existingImportSuccessError: ImportSuccessError,
isRuleRegistryEnabled: boolean
): ImportSuccessError => {
if (isAlertType(alert)) {
if (isAlertType(isRuleRegistryEnabled, alert)) {
return createSuccessObject(existingImportSuccessError);
} else {
return createImportErrorObject({

View file

@ -66,20 +66,23 @@ export const ruleOutput = (): RulesSchema => ({
timeline_id: 'some-timeline-id',
});
describe('validate', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('validate - %s', (_, isRuleRegistryEnabled) => {
describe('transformValidate', () => {
test('it should do a validation correctly of a partial alert', () => {
const ruleAlert = getAlertMock(getQueryRuleParams());
const [validated, errors] = transformValidate(ruleAlert);
const ruleAlert = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
const [validated, errors] = transformValidate(ruleAlert, undefined, isRuleRegistryEnabled);
expect(validated).toEqual(ruleOutput());
expect(errors).toEqual(null);
});
test('it should do an in-validation correctly of a partial alert', () => {
const ruleAlert = getAlertMock(getQueryRuleParams());
const ruleAlert = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
// @ts-expect-error
delete ruleAlert.name;
const [validated, errors] = transformValidate(ruleAlert);
const [validated, errors] = transformValidate(ruleAlert, undefined, isRuleRegistryEnabled);
expect(validated).toEqual(null);
expect(errors).toEqual('Invalid value "undefined" supplied to "name"');
});
@ -87,16 +90,26 @@ describe('validate', () => {
describe('transformValidateBulkError', () => {
test('it should do a validation correctly of a rule id', () => {
const ruleAlert = getAlertMock(getQueryRuleParams());
const validatedOrError = transformValidateBulkError('rule-1', ruleAlert);
const ruleAlert = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
const validatedOrError = transformValidateBulkError(
'rule-1',
ruleAlert,
undefined,
isRuleRegistryEnabled
);
expect(validatedOrError).toEqual(ruleOutput());
});
test('it should do an in-validation correctly of a rule id', () => {
const ruleAlert = getAlertMock(getQueryRuleParams());
const ruleAlert = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
// @ts-expect-error
delete ruleAlert.name;
const validatedOrError = transformValidateBulkError('rule-1', ruleAlert);
const validatedOrError = transformValidateBulkError(
'rule-1',
ruleAlert,
undefined,
isRuleRegistryEnabled
);
const expected: BulkError = {
error: {
message: 'Invalid value "undefined" supplied to "name"',
@ -109,8 +122,13 @@ describe('validate', () => {
test('it should do a validation correctly of a rule id with ruleStatus passed in', () => {
const ruleStatuses = getRuleExecutionStatuses();
const ruleAlert = getAlertMock(getQueryRuleParams());
const validatedOrError = transformValidateBulkError('rule-1', ruleAlert, ruleStatuses);
const ruleAlert = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
const validatedOrError = transformValidateBulkError(
'rule-1',
ruleAlert,
ruleStatuses,
isRuleRegistryEnabled
);
const expected: RulesSchema = {
...ruleOutput(),
status: RuleExecutionStatus.succeeded,
@ -122,10 +140,15 @@ describe('validate', () => {
});
test('it should return error object if "alert" is not expected alert type', () => {
const ruleAlert = getAlertMock(getQueryRuleParams());
const ruleAlert = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
// @ts-expect-error
delete ruleAlert.alertTypeId;
const validatedOrError = transformValidateBulkError('rule-1', ruleAlert);
const validatedOrError = transformValidateBulkError(
'rule-1',
ruleAlert,
undefined,
isRuleRegistryEnabled
);
const expected: BulkError = {
error: {
message: 'Internal error transforming',

View file

@ -29,9 +29,10 @@ import { RuleParams } from '../../schemas/rule_schemas';
export const transformValidate = (
alert: PartialAlert<RuleParams>,
ruleStatus?: SavedObject<IRuleSavedAttributesSavedObjectAttributes>
ruleStatus?: SavedObject<IRuleSavedAttributesSavedObjectAttributes>,
isRuleRegistryEnabled?: boolean
): [RulesSchema | null, string | null] => {
const transformed = transform(alert, ruleStatus);
const transformed = transform(alert, ruleStatus, isRuleRegistryEnabled);
if (transformed == null) {
return [null, 'Internal error transforming'];
} else {
@ -41,9 +42,10 @@ export const transformValidate = (
export const newTransformValidate = (
alert: PartialAlert<RuleParams>,
ruleStatus?: SavedObject<IRuleSavedAttributesSavedObjectAttributes>
ruleStatus?: SavedObject<IRuleSavedAttributesSavedObjectAttributes>,
isRuleRegistryEnabled?: boolean
): [FullResponseSchema | null, string | null] => {
const transformed = transform(alert, ruleStatus);
const transformed = transform(alert, ruleStatus, isRuleRegistryEnabled);
if (transformed == null) {
return [null, 'Internal error transforming'];
} else {
@ -54,9 +56,10 @@ export const newTransformValidate = (
export const transformValidateBulkError = (
ruleId: string,
alert: PartialAlert<RuleParams>,
ruleStatus?: Array<SavedObjectsFindResult<IRuleStatusSOAttributes>>
ruleStatus?: Array<SavedObjectsFindResult<IRuleStatusSOAttributes>>,
isRuleRegistryEnabled?: boolean
): RulesSchema | BulkError => {
if (isAlertType(alert)) {
if (isAlertType(isRuleRegistryEnabled ?? false, alert)) {
if (ruleStatus && ruleStatus?.length > 0 && isRuleStatusSavedObjectType(ruleStatus[0])) {
const transformed = transformAlertToRule(alert, ruleStatus[0]);
const [validated, errors] = validateNonExact(transformed, rulesSchema);

View file

@ -12,7 +12,10 @@ import { buildSiemResponse } from '../utils';
import { readTags } from '../../tags/read_tags';
export const readTagsRoute = (router: SecuritySolutionPluginRouter) => {
export const readTagsRoute = (
router: SecuritySolutionPluginRouter,
isRuleRegistryEnabled: boolean
) => {
router.get(
{
path: DETECTION_ENGINE_TAGS_URL,
@ -31,6 +34,7 @@ export const readTagsRoute = (router: SecuritySolutionPluginRouter) => {
try {
const tags = await readTags({
isRuleRegistryEnabled,
rulesClient,
});
return response.ok({ body: tags });

View file

@ -33,7 +33,10 @@ import { RuleExecutionStatus } from '../../../../common/detection_engine/schemas
let rulesClient: ReturnType<typeof rulesClientMock.create>;
describe('utils', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('utils - %s', (_, isRuleRegistryEnabled) => {
describe('transformBulkError', () => {
test('returns transformed object if it is a boom object', () => {
const boom = new Boom.Boom('some boom message', { statusCode: 400 });
@ -390,12 +393,12 @@ describe('utils', () => {
rulesClient = rulesClientMock.create();
});
it('getFailingRules finds no failing rules', async () => {
rulesClient.get.mockResolvedValue(getAlertMock(getQueryRuleParams()));
rulesClient.get.mockResolvedValue(getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()));
const res = await getFailingRules(['my-fake-id'], rulesClient);
expect(res).toEqual({});
});
it('getFailingRules finds a failing rule', async () => {
const foundRule = getAlertMock(getQueryRuleParams());
const foundRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
foundRule.executionStatus = {
status: 'error',
lastExecutionDate: foundRule.executionStatus.lastExecutionDate,

View file

@ -328,6 +328,6 @@ export const getFailingRules = async (
if (Boom.isBoom(exc)) {
throw exc;
}
throw new Error(`Failed to get executionStatus with RulesClient: ${exc.message}`);
throw new Error(`Failed to get executionStatus with RulesClient: ${(exc as Error).message}`);
}
};

View file

@ -7,7 +7,7 @@
import { validateNonExact } from '@kbn/securitysolution-io-ts-utils';
import { PersistenceServices } from '../../../../../../rule_registry/server';
import { INDICATOR_ALERT_TYPE_ID } from '../../../../../common/constants';
import { INDICATOR_RULE_TYPE_ID } from '../../../../../common/constants';
import { threatRuleParams, ThreatRuleParams } from '../../schemas/rule_schemas';
import { threatMatchExecutor } from '../../signals/executors/threat_match';
import { createSecurityRuleTypeFactory } from '../create_security_rule_type_factory';
@ -33,7 +33,7 @@ export const createIndicatorMatchAlertType = (createOptions: CreateRuleOptions)
ruleDataService,
});
return createSecurityRuleType<ThreatRuleParams, {}, PersistenceServices, {}>({
id: INDICATOR_ALERT_TYPE_ID,
id: INDICATOR_RULE_TYPE_ID,
name: 'Indicator Match Rule',
validate: {
params: {

View file

@ -14,7 +14,7 @@ import { SavedObject } from 'src/core/types';
import { buildEsQuery, IIndexPattern } from '../../../../../../../src/plugins/data/common';
import { createPersistenceRuleTypeFactory } from '../../../../../rule_registry/server';
import { ML_ALERT_TYPE_ID } from '../../../../common/constants';
import { ML_RULE_TYPE_ID } from '../../../../common/constants';
import { SecurityRuleRegistry } from '../../../plugin';
const createSecurityMlRuleType = createPersistenceRuleTypeFactory<SecurityRuleRegistry>();
@ -38,7 +38,7 @@ import { MachineLearningRuleAttributes } from '../signals/types';
import { createErrorsFromShard, createSearchAfterReturnType, mergeReturns } from '../signals/utils';
export const mlAlertType = createSecurityMlRuleType({
id: ML_ALERT_TYPE_ID,
id: ML_RULE_TYPE_ID,
name: 'Machine Learning Rule',
validate: {
params: schema.object({

View file

@ -7,7 +7,7 @@
import { validateNonExact } from '@kbn/securitysolution-io-ts-utils';
import { PersistenceServices } from '../../../../../../rule_registry/server';
import { ML_ALERT_TYPE_ID } from '../../../../../common/constants';
import { ML_RULE_TYPE_ID } from '../../../../../common/constants';
import { machineLearningRuleParams, MachineLearningRuleParams } from '../../schemas/rule_schemas';
import { mlExecutor } from '../../signals/executors/ml';
import { createSecurityRuleTypeFactory } from '../create_security_rule_type_factory';
@ -32,7 +32,7 @@ export const createMlAlertType = (createOptions: CreateRuleOptions) => {
ruleDataService,
});
return createSecurityRuleType<MachineLearningRuleParams, {}, PersistenceServices, {}>({
id: ML_ALERT_TYPE_ID,
id: ML_RULE_TYPE_ID,
name: 'Machine Learning Rule',
validate: {
params: {

View file

@ -7,7 +7,7 @@
import { validateNonExact } from '@kbn/securitysolution-io-ts-utils';
import { PersistenceServices } from '../../../../../../rule_registry/server';
import { QUERY_ALERT_TYPE_ID } from '../../../../../common/constants';
import { QUERY_RULE_TYPE_ID } from '../../../../../common/constants';
import { queryRuleParams, QueryRuleParams } from '../../schemas/rule_schemas';
import { queryExecutor } from '../../signals/executors/query';
import { createSecurityRuleTypeFactory } from '../create_security_rule_type_factory';
@ -33,7 +33,7 @@ export const createQueryAlertType = (createOptions: CreateRuleOptions) => {
ruleDataService,
});
return createSecurityRuleType<QueryRuleParams, {}, PersistenceServices, {}>({
id: QUERY_ALERT_TYPE_ID,
id: QUERY_RULE_TYPE_ID,
name: 'Custom Query Rule',
validate: {
params: {

View file

@ -8,7 +8,8 @@
import { CreateRulesOptions } from './types';
import { rulesClientMock } from '../../../../../alerting/server/mocks';
export const getCreateRulesOptionsMock = (): CreateRulesOptions => ({
export const getCreateRulesOptionsMock = (isRuleRegistryEnabled: boolean): CreateRulesOptions => ({
isRuleRegistryEnabled,
author: ['Elastic'],
buildingBlockType: undefined,
rulesClient: rulesClientMock.create(),
@ -61,7 +62,10 @@ export const getCreateRulesOptionsMock = (): CreateRulesOptions => ({
actions: [],
});
export const getCreateMlRulesOptionsMock = (): CreateRulesOptions => ({
export const getCreateMlRulesOptionsMock = (
isRuleRegistryEnabled: boolean
): CreateRulesOptions => ({
isRuleRegistryEnabled,
author: ['Elastic'],
buildingBlockType: undefined,
rulesClient: rulesClientMock.create(),

View file

@ -8,9 +8,12 @@
import { createRules } from './create_rules';
import { getCreateMlRulesOptionsMock } from './create_rules.mock';
describe('createRules', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('createRules - %s', (_, isRuleRegistryEnabled) => {
it('calls the rulesClient with legacy ML params', async () => {
const ruleOptions = getCreateMlRulesOptionsMock();
const ruleOptions = getCreateMlRulesOptionsMock(isRuleRegistryEnabled);
await createRules(ruleOptions);
expect(ruleOptions.rulesClient.create).toHaveBeenCalledWith(
expect.objectContaining({
@ -26,7 +29,7 @@ describe('createRules', () => {
it('calls the rulesClient with ML params', async () => {
const ruleOptions = {
...getCreateMlRulesOptionsMock(),
...getCreateMlRulesOptionsMock(isRuleRegistryEnabled),
machineLearningJobId: ['new_job_1', 'new_job_2'],
};
await createRules(ruleOptions);

View file

@ -20,6 +20,7 @@ import { CreateRulesOptions } from './types';
import { addTags } from './add_tags';
import { PartialFilter, RuleTypeParams } from '../types';
import { transformToAlertThrottle, transformToNotifyWhen } from './utils';
import { ruleTypeMappings } from '../signals/utils';
export const createRules = async ({
rulesClient,
@ -68,16 +69,18 @@ export const createRules = async ({
to,
type,
references,
namespace,
note,
version,
exceptionsList,
actions,
isRuleRegistryEnabled,
}: CreateRulesOptions): Promise<SanitizedAlert<RuleTypeParams>> => {
const rule = await rulesClient.create<RuleTypeParams>({
data: {
name,
tags: addTags(tags, ruleId, immutable),
alertTypeId: SIGNALS_ID,
alertTypeId: isRuleRegistryEnabled ? ruleTypeMappings[type] : SIGNALS_ID,
consumer: SERVER_APP_ID,
params: {
anomalyThreshold,
@ -125,6 +128,7 @@ export const createRules = async ({
to,
type,
references,
namespace,
note,
version,
exceptionsList,

View file

@ -52,6 +52,7 @@ describe('duplicateRule', () => {
query: 'process.args : "chmod"',
filters: [],
buildingBlockType: undefined,
namespace: undefined,
note: undefined,
timelineId: undefined,
timelineTitle: undefined,
@ -99,6 +100,7 @@ describe('duplicateRule', () => {
"license": "",
"maxSignals": 100,
"meta": undefined,
"namespace": undefined,
"note": undefined,
"outputIndex": ".siem-signals-default",
"query": "process.args : \\"chmod\\"",

View file

@ -6,16 +6,38 @@
*/
import { getFilter } from './find_rules';
import { SIGNALS_ID } from '../../../../common/constants';
import {
INDICATOR_RULE_TYPE_ID,
ML_RULE_TYPE_ID,
QUERY_RULE_TYPE_ID,
SIGNALS_ID,
} from '../../../../common/constants';
const allAlertTypeIds = `(alert.attributes.alertTypeId: ${ML_RULE_TYPE_ID}
OR alert.attributes.alertTypeId: ${QUERY_RULE_TYPE_ID}
OR alert.attributes.alertTypeId: ${INDICATOR_RULE_TYPE_ID})`.replace(/[\n\r]/g, '');
describe('find_rules', () => {
test('it returns a full filter with an AND if sent down', () => {
expect(getFilter('alert.attributes.enabled: true')).toEqual(
`alert.attributes.alertTypeId: ${SIGNALS_ID} AND alert.attributes.enabled: true`
);
});
const fullFilterTestCases: Array<[boolean, string]> = [
[false, `alert.attributes.alertTypeId: ${SIGNALS_ID} AND alert.attributes.enabled: true`],
[true, `${allAlertTypeIds} AND alert.attributes.enabled: true`],
];
const nullFilterTestCases: Array<[boolean, string]> = [
[false, `alert.attributes.alertTypeId: ${SIGNALS_ID}`],
[true, allAlertTypeIds],
];
test('it returns existing filter with no AND when not set', () => {
expect(getFilter(null)).toEqual(`alert.attributes.alertTypeId: ${SIGNALS_ID}`);
});
test.each(fullFilterTestCases)(
'it returns a full filter with an AND if sent down [rule registry enabled: %p]',
(isRuleRegistryEnabled, expected) => {
expect(getFilter('alert.attributes.enabled: true', isRuleRegistryEnabled)).toEqual(expected);
}
);
test.each(nullFilterTestCases)(
'it returns existing filter with no AND when not set [rule registry enabled: %p]',
(isRuleRegistryEnabled, expected) => {
expect(getFilter(null, isRuleRegistryEnabled)).toEqual(expected);
}
);
});

View file

@ -8,13 +8,23 @@
import { FindResult } from '../../../../../alerting/server';
import { SIGNALS_ID } from '../../../../common/constants';
import { RuleParams } from '../schemas/rule_schemas';
import { ruleTypeMappings } from '../signals/utils';
import { FindRuleOptions } from './types';
export const getFilter = (filter: string | null | undefined) => {
export const getFilter = (
filter: string | null | undefined,
isRuleRegistryEnabled: boolean = false
) => {
const alertTypeFilter = isRuleRegistryEnabled
? `(${Object.values(ruleTypeMappings)
.map((type) => (type !== SIGNALS_ID ? `alert.attributes.alertTypeId: ${type}` : undefined))
.filter((type) => type != null)
.join(' OR ')})`
: `alert.attributes.alertTypeId: ${SIGNALS_ID}`;
if (filter == null) {
return `alert.attributes.alertTypeId: ${SIGNALS_ID}`;
return alertTypeFilter;
} else {
return `alert.attributes.alertTypeId: ${SIGNALS_ID} AND ${filter}`;
return `${alertTypeFilter} AND ${filter}`;
}
};
@ -26,13 +36,14 @@ export const findRules = ({
filter,
sortField,
sortOrder,
isRuleRegistryEnabled,
}: FindRuleOptions): Promise<FindResult<RuleParams>> => {
return rulesClient.find({
options: {
fields,
page,
perPage,
filter: getFilter(filter),
filter: getFilter(filter, isRuleRegistryEnabled),
sortOrder,
sortField,
},

View file

@ -20,7 +20,10 @@ import {
getNonPackagedRulesCount,
} from './get_existing_prepackaged_rules';
describe('get_existing_prepackaged_rules', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('get_existing_prepackaged_rules - %s', (_, isRuleRegistryEnabled) => {
afterEach(() => {
jest.resetAllMocks();
});
@ -28,23 +31,23 @@ describe('get_existing_prepackaged_rules', () => {
describe('getExistingPrepackagedRules', () => {
test('should return a single item in a single page', async () => {
const rulesClient = rulesClientMock.create();
rulesClient.find.mockResolvedValue(getFindResultWithSingleHit());
const rules = await getExistingPrepackagedRules({ rulesClient });
expect(rules).toEqual([getAlertMock(getQueryRuleParams())]);
rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled));
const rules = await getExistingPrepackagedRules({ isRuleRegistryEnabled, rulesClient });
expect(rules).toEqual([getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())]);
});
test('should return 3 items over 1 page with all on one page', async () => {
const rulesClient = rulesClientMock.create();
const result1 = getAlertMock(getQueryRuleParams());
const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result1.params.immutable = true;
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
const result2 = getAlertMock(getQueryRuleParams());
const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result2.params.immutable = true;
result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d';
const result3 = getAlertMock(getQueryRuleParams());
const result3 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result3.params.immutable = true;
result3.id = 'f3e1bf0b-b95f-43da-b1de-5d2f0af2287a';
@ -68,7 +71,7 @@ describe('get_existing_prepackaged_rules', () => {
})
);
const rules = await getExistingPrepackagedRules({ rulesClient });
const rules = await getExistingPrepackagedRules({ isRuleRegistryEnabled, rulesClient });
expect(rules).toEqual([result1, result2, result3]);
});
});
@ -76,18 +79,18 @@ describe('get_existing_prepackaged_rules', () => {
describe('getNonPackagedRules', () => {
test('should return a single item in a single page', async () => {
const rulesClient = rulesClientMock.create();
rulesClient.find.mockResolvedValue(getFindResultWithSingleHit());
const rules = await getNonPackagedRules({ rulesClient });
expect(rules).toEqual([getAlertMock(getQueryRuleParams())]);
rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled));
const rules = await getNonPackagedRules({ isRuleRegistryEnabled, rulesClient });
expect(rules).toEqual([getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())]);
});
test('should return 2 items over 1 page', async () => {
const rulesClient = rulesClientMock.create();
const result1 = getAlertMock(getQueryRuleParams());
const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
const result2 = getAlertMock(getQueryRuleParams());
const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d';
// first result mock which is for returning the total
@ -105,20 +108,20 @@ describe('get_existing_prepackaged_rules', () => {
getFindResultWithMultiHits({ data: [result1, result2], perPage: 2, page: 1, total: 2 })
);
const rules = await getNonPackagedRules({ rulesClient });
const rules = await getNonPackagedRules({ isRuleRegistryEnabled, rulesClient });
expect(rules).toEqual([result1, result2]);
});
test('should return 3 items over 1 page with all on one page', async () => {
const rulesClient = rulesClientMock.create();
const result1 = getAlertMock(getQueryRuleParams());
const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
const result2 = getAlertMock(getQueryRuleParams());
const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d';
const result3 = getAlertMock(getQueryRuleParams());
const result3 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result3.id = 'f3e1bf0b-b95f-43da-b1de-5d2f0af2287a';
// first result mock which is for returning the total
@ -141,7 +144,7 @@ describe('get_existing_prepackaged_rules', () => {
})
);
const rules = await getNonPackagedRules({ rulesClient });
const rules = await getNonPackagedRules({ isRuleRegistryEnabled, rulesClient });
expect(rules).toEqual([result1, result2, result3]);
});
});
@ -149,18 +152,18 @@ describe('get_existing_prepackaged_rules', () => {
describe('getRules', () => {
test('should return a single item in a single page', async () => {
const rulesClient = rulesClientMock.create();
rulesClient.find.mockResolvedValue(getFindResultWithSingleHit());
const rules = await getRules({ rulesClient, filter: '' });
expect(rules).toEqual([getAlertMock(getQueryRuleParams())]);
rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled));
const rules = await getRules({ isRuleRegistryEnabled, rulesClient, filter: '' });
expect(rules).toEqual([getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())]);
});
test('should return 2 items over two pages, one per page', async () => {
const rulesClient = rulesClientMock.create();
const result1 = getAlertMock(getQueryRuleParams());
const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
const result2 = getAlertMock(getQueryRuleParams());
const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d';
// first result mock which is for returning the total
@ -178,7 +181,7 @@ describe('get_existing_prepackaged_rules', () => {
getFindResultWithMultiHits({ data: [result1, result2], perPage: 2, page: 1, total: 2 })
);
const rules = await getRules({ rulesClient, filter: '' });
const rules = await getRules({ isRuleRegistryEnabled, rulesClient, filter: '' });
expect(rules).toEqual([result1, result2]);
});
});
@ -186,8 +189,8 @@ describe('get_existing_prepackaged_rules', () => {
describe('getRulesCount', () => {
test('it returns a count', async () => {
const rulesClient = rulesClientMock.create();
rulesClient.find.mockResolvedValue(getFindResultWithSingleHit());
const rules = await getRulesCount({ rulesClient, filter: '' });
rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled));
const rules = await getRulesCount({ isRuleRegistryEnabled, rulesClient, filter: '' });
expect(rules).toEqual(1);
});
});
@ -195,8 +198,8 @@ describe('get_existing_prepackaged_rules', () => {
describe('getNonPackagedRulesCount', () => {
test('it returns a count', async () => {
const rulesClient = rulesClientMock.create();
rulesClient.find.mockResolvedValue(getFindResultWithSingleHit());
const rules = await getNonPackagedRulesCount({ rulesClient });
rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled));
const rules = await getNonPackagedRulesCount({ isRuleRegistryEnabled, rulesClient });
expect(rules).toEqual(1);
});
});

View file

@ -14,21 +14,26 @@ export const FILTER_NON_PREPACKED_RULES = `alert.attributes.tags: "${INTERNAL_IM
export const FILTER_PREPACKED_RULES = `alert.attributes.tags: "${INTERNAL_IMMUTABLE_KEY}:true"`;
export const getNonPackagedRulesCount = async ({
isRuleRegistryEnabled,
rulesClient,
}: {
isRuleRegistryEnabled: boolean;
rulesClient: RulesClient;
}): Promise<number> => {
return getRulesCount({ rulesClient, filter: FILTER_NON_PREPACKED_RULES });
return getRulesCount({ isRuleRegistryEnabled, rulesClient, filter: FILTER_NON_PREPACKED_RULES });
};
export const getRulesCount = async ({
rulesClient,
filter,
isRuleRegistryEnabled,
}: {
rulesClient: RulesClient;
filter: string;
isRuleRegistryEnabled: boolean;
}): Promise<number> => {
const firstRule = await findRules({
isRuleRegistryEnabled,
rulesClient,
filter,
perPage: 1,
@ -43,12 +48,15 @@ export const getRulesCount = async ({
export const getRules = async ({
rulesClient,
filter,
isRuleRegistryEnabled,
}: {
rulesClient: RulesClient;
filter: string;
}): Promise<RuleAlertType[]> => {
const count = await getRulesCount({ rulesClient, filter });
isRuleRegistryEnabled: boolean;
}) => {
const count = await getRulesCount({ rulesClient, filter, isRuleRegistryEnabled });
const rules = await findRules({
isRuleRegistryEnabled,
rulesClient,
filter,
perPage: count,
@ -58,7 +66,7 @@ export const getRules = async ({
fields: undefined,
});
if (isAlertTypes(rules.data)) {
if (isAlertTypes(isRuleRegistryEnabled, rules.data)) {
return rules.data;
} else {
// If this was ever true, you have a really messed up system.
@ -69,22 +77,28 @@ export const getRules = async ({
export const getNonPackagedRules = async ({
rulesClient,
isRuleRegistryEnabled,
}: {
rulesClient: RulesClient;
isRuleRegistryEnabled: boolean;
}): Promise<RuleAlertType[]> => {
return getRules({
rulesClient,
filter: FILTER_NON_PREPACKED_RULES,
isRuleRegistryEnabled,
});
};
export const getExistingPrepackagedRules = async ({
rulesClient,
isRuleRegistryEnabled,
}: {
rulesClient: RulesClient;
isRuleRegistryEnabled: boolean;
}): Promise<RuleAlertType[]> => {
return getRules({
rulesClient,
filter: FILTER_PREPACKED_RULES,
isRuleRegistryEnabled,
});
};

View file

@ -16,11 +16,14 @@ import { getListArrayMock } from '../../../../common/detection_engine/schemas/ty
import { getThreatMock } from '../../../../common/detection_engine/schemas/types/threat.mock';
import { getQueryRuleParams } from '../schemas/rule_schemas.mock';
describe('getExportAll', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('getExportAll - %s', (_, isRuleRegistryEnabled) => {
test('it exports everything from the alerts client', async () => {
const rulesClient = rulesClientMock.create();
const result = getFindResultWithSingleHit();
const alert = getAlertMock(getQueryRuleParams());
const result = getFindResultWithSingleHit(isRuleRegistryEnabled);
const alert = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
alert.params = {
...alert.params,
filters: [{ query: { match_phrase: { 'host.name': 'some-host' } } }],
@ -32,7 +35,7 @@ describe('getExportAll', () => {
result.data = [alert];
rulesClient.find.mockResolvedValue(result);
const exports = await getExportAll(rulesClient);
const exports = await getExportAll(rulesClient, isRuleRegistryEnabled);
const rulesJson = JSON.parse(exports.rulesNdjson);
const detailsJson = JSON.parse(exports.exportDetails);
expect(rulesJson).toEqual({
@ -94,7 +97,7 @@ describe('getExportAll', () => {
rulesClient.find.mockResolvedValue(findResult);
const exports = await getExportAll(rulesClient);
const exports = await getExportAll(rulesClient, isRuleRegistryEnabled);
expect(exports).toEqual({
rulesNdjson: '',
exportDetails: '{"exported_count":0,"missing_rules":[],"missing_rules_count":0}\n',

View file

@ -12,12 +12,13 @@ import { transformAlertsToRules } from '../routes/rules/utils';
import { transformDataToNdjson } from '../../../utils/read_stream/create_stream_from_ndjson';
export const getExportAll = async (
rulesClient: RulesClient
rulesClient: RulesClient,
isRuleRegistryEnabled: boolean
): Promise<{
rulesNdjson: string;
exportDetails: string;
}> => {
const ruleAlertTypes = await getNonPackagedRules({ rulesClient });
const ruleAlertTypes = await getNonPackagedRules({ rulesClient, isRuleRegistryEnabled });
const rules = transformAlertsToRules(ruleAlertTypes);
// We do not support importing/exporting actions. When we do, delete this line of code
const rulesWithoutActions = rules.map((rule) => ({ ...rule, actions: [] }));

View file

@ -16,7 +16,10 @@ import { getListArrayMock } from '../../../../common/detection_engine/schemas/ty
import { getThreatMock } from '../../../../common/detection_engine/schemas/types/threat.mock';
import { getQueryRuleParams } from '../schemas/rule_schemas.mock';
describe('get_export_by_object_ids', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('get_export_by_object_ids - %s', (_, isRuleRegistryEnabled) => {
beforeEach(() => {
jest.resetAllMocks();
jest.restoreAllMocks();
@ -25,10 +28,10 @@ describe('get_export_by_object_ids', () => {
describe('getExportByObjectIds', () => {
test('it exports object ids into an expected string with new line characters', async () => {
const rulesClient = rulesClientMock.create();
rulesClient.find.mockResolvedValue(getFindResultWithSingleHit());
rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled));
const objects = [{ rule_id: 'rule-1' }];
const exports = await getExportByObjectIds(rulesClient, objects);
const exports = await getExportByObjectIds(rulesClient, objects, isRuleRegistryEnabled);
const exportsObj = {
rulesNdjson: JSON.parse(exports.rulesNdjson),
exportDetails: JSON.parse(exports.exportDetails),
@ -85,7 +88,7 @@ describe('get_export_by_object_ids', () => {
test('it does not export immutable rules', async () => {
const rulesClient = rulesClientMock.create();
const result = getAlertMock(getQueryRuleParams());
const result = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result.params.immutable = true;
const findResult: FindHit = {
@ -95,11 +98,11 @@ describe('get_export_by_object_ids', () => {
data: [result],
};
rulesClient.get.mockResolvedValue(getAlertMock(getQueryRuleParams()));
rulesClient.get.mockResolvedValue(getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()));
rulesClient.find.mockResolvedValue(findResult);
const objects = [{ rule_id: 'rule-1' }];
const exports = await getExportByObjectIds(rulesClient, objects);
const exports = await getExportByObjectIds(rulesClient, objects, isRuleRegistryEnabled);
expect(exports).toEqual({
rulesNdjson: '',
exportDetails:
@ -111,10 +114,10 @@ describe('get_export_by_object_ids', () => {
describe('getRulesFromObjects', () => {
test('it returns transformed rules from objects sent in', async () => {
const rulesClient = rulesClientMock.create();
rulesClient.find.mockResolvedValue(getFindResultWithSingleHit());
rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled));
const objects = [{ rule_id: 'rule-1' }];
const exports = await getRulesFromObjects(rulesClient, objects);
const exports = await getRulesFromObjects(rulesClient, objects, isRuleRegistryEnabled);
const expected: RulesErrors = {
exportedCount: 1,
missingRules: [],
@ -175,7 +178,7 @@ describe('get_export_by_object_ids', () => {
test('it does not transform the rule if the rule is an immutable rule and designates it as a missing rule', async () => {
const rulesClient = rulesClientMock.create();
const result = getAlertMock(getQueryRuleParams());
const result = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result.params.immutable = true;
const findResult: FindHit = {
@ -189,7 +192,7 @@ describe('get_export_by_object_ids', () => {
rulesClient.find.mockResolvedValue(findResult);
const objects = [{ rule_id: 'rule-1' }];
const exports = await getRulesFromObjects(rulesClient, objects);
const exports = await getRulesFromObjects(rulesClient, objects, isRuleRegistryEnabled);
const expected: RulesErrors = {
exportedCount: 0,
missingRules: [{ rule_id: 'rule-1' }],
@ -212,7 +215,7 @@ describe('get_export_by_object_ids', () => {
rulesClient.find.mockResolvedValue(findResult);
const objects = [{ rule_id: 'rule-1' }];
const exports = await getRulesFromObjects(rulesClient, objects);
const exports = await getRulesFromObjects(rulesClient, objects, isRuleRegistryEnabled);
const expected: RulesErrors = {
exportedCount: 0,
missingRules: [{ rule_id: 'rule-1' }],

View file

@ -34,12 +34,13 @@ export interface RulesErrors {
export const getExportByObjectIds = async (
rulesClient: RulesClient,
objects: Array<{ rule_id: string }>
objects: Array<{ rule_id: string }>,
isRuleRegistryEnabled: boolean
): Promise<{
rulesNdjson: string;
exportDetails: string;
}> => {
const rulesAndErrors = await getRulesFromObjects(rulesClient, objects);
const rulesAndErrors = await getRulesFromObjects(rulesClient, objects, isRuleRegistryEnabled);
// We do not support importing/exporting actions. When we do, delete this line of code
const rulesWithoutActions = rulesAndErrors.rules.map((rule) => ({ ...rule, actions: [] }));
const rulesNdjson = transformDataToNdjson(rulesWithoutActions);
@ -49,7 +50,8 @@ export const getExportByObjectIds = async (
export const getRulesFromObjects = async (
rulesClient: RulesClient,
objects: Array<{ rule_id: string }>
objects: Array<{ rule_id: string }>,
isRuleRegistryEnabled: boolean
): Promise<RulesErrors> => {
// If we put more than 1024 ids in one block like "alert.attributes.tags: (id1 OR id2 OR ... OR id1100)"
// then the KQL -> ES DSL query generator still puts them all in the same "should" array, but ES defaults
@ -67,6 +69,7 @@ export const getRulesFromObjects = async (
})
.join(' OR ');
const rules = await findRules({
isRuleRegistryEnabled,
rulesClient,
filter,
page: 1,
@ -79,7 +82,7 @@ export const getRulesFromObjects = async (
const matchingRule = rules.data.find((rule) => rule.params.ruleId === ruleId);
if (
matchingRule != null &&
isAlertType(matchingRule) &&
isAlertType(isRuleRegistryEnabled, matchingRule) &&
matchingRule.params.immutable !== true
) {
return {

View file

@ -9,57 +9,61 @@ import { getRulesToInstall } from './get_rules_to_install';
import { getAlertMock } from '../routes/__mocks__/request_responses';
import { getAddPrepackagedRulesSchemaDecodedMock } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock';
import { getQueryRuleParams } from '../schemas/rule_schemas.mock';
import { AddPrepackagedRulesSchemaDecoded } from '../../../../common/detection_engine/schemas/request';
describe('get_rules_to_install', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('get_rules_to_install - %s', (_, isRuleRegistryEnabled) => {
test('should return empty array if both rule sets are empty', () => {
const update = getRulesToInstall([], []);
expect(update).toEqual([]);
});
test('should return empty array if the two rule ids match', () => {
const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock();
const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded;
ruleFromFileSystem.rule_id = 'rule-1';
const installedRule = getAlertMock(getQueryRuleParams());
const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule.params.ruleId = 'rule-1';
const update = getRulesToInstall([ruleFromFileSystem], [installedRule]);
expect(update).toEqual([]);
});
test('should return the rule to install if the id of the two rules do not match', () => {
const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock();
const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded;
ruleFromFileSystem.rule_id = 'rule-1';
const installedRule = getAlertMock(getQueryRuleParams());
const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule.params.ruleId = 'rule-2';
const update = getRulesToInstall([ruleFromFileSystem], [installedRule]);
expect(update).toEqual([ruleFromFileSystem]);
});
test('should return two rules to install if both the ids of the two rules do not match', () => {
const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock();
const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded;
ruleFromFileSystem1.rule_id = 'rule-1';
const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaDecodedMock();
const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded;
ruleFromFileSystem2.rule_id = 'rule-2';
const installedRule = getAlertMock(getQueryRuleParams());
const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule.params.ruleId = 'rule-3';
const update = getRulesToInstall([ruleFromFileSystem1, ruleFromFileSystem2], [installedRule]);
expect(update).toEqual([ruleFromFileSystem1, ruleFromFileSystem2]);
});
test('should return two rules of three to install if both the ids of the two rules do not match but the third does', () => {
const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock();
const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded;
ruleFromFileSystem1.rule_id = 'rule-1';
const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaDecodedMock();
const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded;
ruleFromFileSystem2.rule_id = 'rule-2';
const ruleFromFileSystem3 = getAddPrepackagedRulesSchemaDecodedMock();
const ruleFromFileSystem3 = getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded;
ruleFromFileSystem3.rule_id = 'rule-3';
const installedRule = getAlertMock(getQueryRuleParams());
const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule.params.ruleId = 'rule-3';
const update = getRulesToInstall(
[ruleFromFileSystem1, ruleFromFileSystem2, ruleFromFileSystem3],

View file

@ -11,7 +11,7 @@ import { RuleAlertType } from './types';
export const getRulesToInstall = (
rulesFromFileSystem: AddPrepackagedRulesSchemaDecoded[],
installedRules: RuleAlertType[]
): AddPrepackagedRulesSchemaDecoded[] => {
) => {
return rulesFromFileSystem.filter(
(rule) => !installedRules.some((installedRule) => installedRule.params.ruleId === rule.rule_id)
);

View file

@ -10,7 +10,10 @@ import { getAlertMock } from '../routes/__mocks__/request_responses';
import { getAddPrepackagedRulesSchemaDecodedMock } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock';
import { getQueryRuleParams } from '../schemas/rule_schemas.mock';
describe('get_rules_to_update', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('get_rules_to_update - %s', (_, isRuleRegistryEnabled) => {
describe('get_rules_to_update', () => {
test('should return empty array if both rule sets are empty', () => {
const update = getRulesToUpdate([], []);
@ -22,7 +25,7 @@ describe('get_rules_to_update', () => {
ruleFromFileSystem.rule_id = 'rule-1';
ruleFromFileSystem.version = 2;
const installedRule = getAlertMock(getQueryRuleParams());
const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule.params.ruleId = 'rule-2';
installedRule.params.version = 1;
const update = getRulesToUpdate([ruleFromFileSystem], [installedRule]);
@ -34,7 +37,7 @@ describe('get_rules_to_update', () => {
ruleFromFileSystem.rule_id = 'rule-1';
ruleFromFileSystem.version = 1;
const installedRule = getAlertMock(getQueryRuleParams());
const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule.params.ruleId = 'rule-1';
installedRule.params.version = 2;
const update = getRulesToUpdate([ruleFromFileSystem], [installedRule]);
@ -46,7 +49,7 @@ describe('get_rules_to_update', () => {
ruleFromFileSystem.rule_id = 'rule-1';
ruleFromFileSystem.version = 1;
const installedRule = getAlertMock(getQueryRuleParams());
const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule.params.ruleId = 'rule-1';
installedRule.params.version = 1;
const update = getRulesToUpdate([ruleFromFileSystem], [installedRule]);
@ -58,7 +61,7 @@ describe('get_rules_to_update', () => {
ruleFromFileSystem.rule_id = 'rule-1';
ruleFromFileSystem.version = 2;
const installedRule = getAlertMock(getQueryRuleParams());
const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule.params.ruleId = 'rule-1';
installedRule.params.version = 1;
installedRule.params.exceptionsList = [];
@ -72,12 +75,12 @@ describe('get_rules_to_update', () => {
ruleFromFileSystem.rule_id = 'rule-1';
ruleFromFileSystem.version = 2;
const installedRule1 = getAlertMock(getQueryRuleParams());
const installedRule1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule1.params.ruleId = 'rule-1';
installedRule1.params.version = 1;
installedRule1.params.exceptionsList = [];
const installedRule2 = getAlertMock(getQueryRuleParams());
const installedRule2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule2.params.ruleId = 'rule-2';
installedRule2.params.version = 1;
installedRule2.params.exceptionsList = [];
@ -95,12 +98,12 @@ describe('get_rules_to_update', () => {
ruleFromFileSystem2.rule_id = 'rule-2';
ruleFromFileSystem2.version = 2;
const installedRule1 = getAlertMock(getQueryRuleParams());
const installedRule1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule1.params.ruleId = 'rule-1';
installedRule1.params.version = 1;
installedRule1.params.exceptionsList = [];
const installedRule2 = getAlertMock(getQueryRuleParams());
const installedRule2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule2.params.ruleId = 'rule-2';
installedRule2.params.version = 1;
installedRule2.params.exceptionsList = [];
@ -125,7 +128,7 @@ describe('get_rules_to_update', () => {
ruleFromFileSystem1.rule_id = 'rule-1';
ruleFromFileSystem1.version = 2;
const installedRule1 = getAlertMock(getQueryRuleParams());
const installedRule1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule1.params.ruleId = 'rule-1';
installedRule1.params.version = 1;
installedRule1.params.exceptionsList = [];
@ -147,7 +150,7 @@ describe('get_rules_to_update', () => {
ruleFromFileSystem1.rule_id = 'rule-1';
ruleFromFileSystem1.version = 2;
const installedRule1 = getAlertMock(getQueryRuleParams());
const installedRule1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule1.params.ruleId = 'rule-1';
installedRule1.params.version = 1;
installedRule1.params.exceptionsList = [
@ -179,7 +182,7 @@ describe('get_rules_to_update', () => {
ruleFromFileSystem1.rule_id = 'rule-1';
ruleFromFileSystem1.version = 2;
const installedRule1 = getAlertMock(getQueryRuleParams());
const installedRule1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule1.params.ruleId = 'rule-1';
installedRule1.params.version = 1;
installedRule1.params.exceptionsList = [
@ -201,7 +204,7 @@ describe('get_rules_to_update', () => {
ruleFromFileSystem1.rule_id = 'rule-1';
ruleFromFileSystem1.version = 2;
const installedRule1 = getAlertMock(getQueryRuleParams());
const installedRule1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule1.params.ruleId = 'rule-1';
installedRule1.params.version = 1;
installedRule1.params.exceptionsList = [
@ -228,7 +231,7 @@ describe('get_rules_to_update', () => {
ruleFromFileSystem2.rule_id = 'rule-2';
ruleFromFileSystem2.version = 2;
const installedRule1 = getAlertMock(getQueryRuleParams());
const installedRule1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule1.params.ruleId = 'rule-1';
installedRule1.params.version = 1;
installedRule1.params.exceptionsList = [
@ -239,7 +242,7 @@ describe('get_rules_to_update', () => {
type: 'endpoint',
},
];
const installedRule2 = getAlertMock(getQueryRuleParams());
const installedRule2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule2.params.ruleId = 'rule-2';
installedRule2.params.version = 1;
installedRule2.params.exceptionsList = [
@ -278,7 +281,7 @@ describe('get_rules_to_update', () => {
},
];
const installedRule1 = getAlertMock(getQueryRuleParams());
const installedRule1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule1.params.ruleId = 'rule-1';
installedRule1.params.version = 1;
installedRule1.params.exceptionsList = [
@ -290,7 +293,7 @@ describe('get_rules_to_update', () => {
},
];
const installedRule2 = getAlertMock(getQueryRuleParams());
const installedRule2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule2.params.ruleId = 'rule-2';
installedRule2.params.version = 1;
installedRule2.params.exceptionsList = [
@ -320,7 +323,7 @@ describe('get_rules_to_update', () => {
ruleFromFileSystem.rule_id = 'rule-1';
ruleFromFileSystem.version = 2;
const installedRule = getAlertMock(getQueryRuleParams());
const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule.params.ruleId = 'rule-2';
installedRule.params.version = 1;
const shouldUpdate = filterInstalledRules(ruleFromFileSystem, [installedRule]);
@ -332,7 +335,7 @@ describe('get_rules_to_update', () => {
ruleFromFileSystem.rule_id = 'rule-1';
ruleFromFileSystem.version = 1;
const installedRule = getAlertMock(getQueryRuleParams());
const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule.params.ruleId = 'rule-1';
installedRule.params.version = 2;
const shouldUpdate = filterInstalledRules(ruleFromFileSystem, [installedRule]);
@ -344,7 +347,7 @@ describe('get_rules_to_update', () => {
ruleFromFileSystem.rule_id = 'rule-1';
ruleFromFileSystem.version = 1;
const installedRule = getAlertMock(getQueryRuleParams());
const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule.params.ruleId = 'rule-1';
installedRule.params.version = 1;
const shouldUpdate = filterInstalledRules(ruleFromFileSystem, [installedRule]);
@ -356,7 +359,7 @@ describe('get_rules_to_update', () => {
ruleFromFileSystem.rule_id = 'rule-1';
ruleFromFileSystem.version = 2;
const installedRule = getAlertMock(getQueryRuleParams());
const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule.params.ruleId = 'rule-1';
installedRule.params.version = 1;
installedRule.params.exceptionsList = [];
@ -380,7 +383,7 @@ describe('get_rules_to_update', () => {
ruleFromFileSystem1.rule_id = 'rule-1';
ruleFromFileSystem1.version = 2;
const installedRule1 = getAlertMock(getQueryRuleParams());
const installedRule1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule1.params.ruleId = 'rule-1';
installedRule1.params.version = 1;
installedRule1.params.exceptionsList = [];
@ -402,7 +405,7 @@ describe('get_rules_to_update', () => {
ruleFromFileSystem1.rule_id = 'rule-1';
ruleFromFileSystem1.version = 2;
const installedRule1 = getAlertMock(getQueryRuleParams());
const installedRule1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule1.params.ruleId = 'rule-1';
installedRule1.params.version = 1;
installedRule1.params.exceptionsList = [
@ -434,7 +437,7 @@ describe('get_rules_to_update', () => {
ruleFromFileSystem1.rule_id = 'rule-1';
ruleFromFileSystem1.version = 2;
const installedRule1 = getAlertMock(getQueryRuleParams());
const installedRule1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule1.params.ruleId = 'rule-1';
installedRule1.params.version = 1;
installedRule1.params.exceptionsList = [
@ -456,7 +459,7 @@ describe('get_rules_to_update', () => {
ruleFromFileSystem1.rule_id = 'rule-1';
ruleFromFileSystem1.version = 2;
const installedRule1 = getAlertMock(getQueryRuleParams());
const installedRule1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
installedRule1.params.ruleId = 'rule-1';
installedRule1.params.version = 1;
installedRule1.params.exceptionsList = [

View file

@ -18,7 +18,7 @@ import { RuleAlertType } from './types';
export const getRulesToUpdate = (
rulesFromFileSystem: AddPrepackagedRulesSchemaDecoded[],
installedRules: RuleAlertType[]
): AddPrepackagedRulesSchemaDecoded[] => {
) => {
return rulesFromFileSystem
.filter((ruleFromFileSystem) => filterInstalledRules(ruleFromFileSystem, installedRules))
.map((ruleFromFileSystem) => mergeExceptionLists(ruleFromFileSystem, installedRules));

View file

@ -14,7 +14,8 @@ import { PartialFilter } from '../types';
export const installPrepackagedRules = (
rulesClient: RulesClient,
rules: AddPrepackagedRulesSchemaDecoded[],
outputIndex: string
outputIndex: string,
isRuleRegistryEnabled: boolean
): Array<Promise<SanitizedAlert<AlertTypeParams>>> =>
rules.reduce<Array<Promise<SanitizedAlert<AlertTypeParams>>>>((acc, rule) => {
const {
@ -60,6 +61,7 @@ export const installPrepackagedRules = (
threshold,
timestamp_override: timestampOverride,
references,
namespace,
note,
version,
exceptions_list: exceptionsList,
@ -70,6 +72,7 @@ export const installPrepackagedRules = (
return [
...acc,
createRules({
isRuleRegistryEnabled,
rulesClient,
anomalyThreshold,
author,
@ -116,6 +119,7 @@ export const installPrepackagedRules = (
throttle: null, // At this time there is no pre-packaged actions
timestampOverride,
references,
namespace,
note,
version,
exceptionsList,

View file

@ -11,7 +11,7 @@ import { getAlertMock } from '../routes/__mocks__/request_responses';
import { getMlRuleParams, getQueryRuleParams } from '../schemas/rule_schemas.mock';
import { ruleExecutionLogClientMock } from '../rule_execution_log/__mocks__/rule_execution_log_client';
export const getPatchRulesOptionsMock = (): PatchRulesOptions => ({
export const getPatchRulesOptionsMock = (isRuleRegistryEnabled: boolean): PatchRulesOptions => ({
author: ['Elastic'],
buildingBlockType: undefined,
rulesClient: rulesClientMock.create(),
@ -61,10 +61,10 @@ export const getPatchRulesOptionsMock = (): PatchRulesOptions => ({
version: 1,
exceptionsList: [],
actions: [],
rule: getAlertMock(getQueryRuleParams()),
rule: getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()),
});
export const getPatchMlRulesOptionsMock = (): PatchRulesOptions => ({
export const getPatchMlRulesOptionsMock = (isRuleRegistryEnabled: boolean): PatchRulesOptions => ({
author: ['Elastic'],
buildingBlockType: undefined,
rulesClient: rulesClientMock.create(),
@ -114,5 +114,5 @@ export const getPatchMlRulesOptionsMock = (): PatchRulesOptions => ({
version: 1,
exceptionsList: [],
actions: [],
rule: getAlertMock(getMlRuleParams()),
rule: getAlertMock(isRuleRegistryEnabled, getMlRuleParams()),
});

View file

@ -12,15 +12,18 @@ import { RulesClientMock } from '../../../../../alerting/server/rules_client.moc
import { getAlertMock } from '../routes/__mocks__/request_responses';
import { getQueryRuleParams } from '../schemas/rule_schemas.mock';
describe('patchRules', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('patchRules - %s', (_, isRuleRegistryEnabled) => {
it('should call rulesClient.disable if the rule was enabled and enabled is false', async () => {
const rulesOptionsMock = getPatchRulesOptionsMock();
const rulesOptionsMock = getPatchRulesOptionsMock(isRuleRegistryEnabled);
const ruleOptions: PatchRulesOptions = {
...rulesOptionsMock,
enabled: false,
};
((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue(
getAlertMock(getQueryRuleParams())
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
);
await patchRules(ruleOptions);
expect(ruleOptions.rulesClient.disable).toHaveBeenCalledWith(
@ -31,7 +34,7 @@ describe('patchRules', () => {
});
it('should call rulesClient.enable if the rule was disabled and enabled is true', async () => {
const rulesOptionsMock = getPatchRulesOptionsMock();
const rulesOptionsMock = getPatchRulesOptionsMock(isRuleRegistryEnabled);
const ruleOptions: PatchRulesOptions = {
...rulesOptionsMock,
enabled: true,
@ -40,7 +43,7 @@ describe('patchRules', () => {
ruleOptions.rule.enabled = false;
}
((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue(
getAlertMock(getQueryRuleParams())
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
);
await patchRules(ruleOptions);
expect(ruleOptions.rulesClient.enable).toHaveBeenCalledWith(
@ -51,7 +54,7 @@ describe('patchRules', () => {
});
it('calls the rulesClient with legacy ML params', async () => {
const rulesOptionsMock = getPatchMlRulesOptionsMock();
const rulesOptionsMock = getPatchMlRulesOptionsMock(isRuleRegistryEnabled);
const ruleOptions: PatchRulesOptions = {
...rulesOptionsMock,
enabled: true,
@ -60,7 +63,7 @@ describe('patchRules', () => {
ruleOptions.rule.enabled = false;
}
((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue(
getAlertMock(getQueryRuleParams())
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
);
await patchRules(ruleOptions);
expect(ruleOptions.rulesClient.update).toHaveBeenCalledWith(
@ -76,7 +79,7 @@ describe('patchRules', () => {
});
it('calls the rulesClient with new ML params', async () => {
const rulesOptionsMock = getPatchMlRulesOptionsMock();
const rulesOptionsMock = getPatchMlRulesOptionsMock(isRuleRegistryEnabled);
const ruleOptions: PatchRulesOptions = {
...rulesOptionsMock,
machineLearningJobId: ['new_job_1', 'new_job_2'],
@ -86,7 +89,7 @@ describe('patchRules', () => {
ruleOptions.rule.enabled = false;
}
((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue(
getAlertMock(getQueryRuleParams())
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
);
await patchRules(ruleOptions);
expect(ruleOptions.rulesClient.update).toHaveBeenCalledWith(
@ -103,7 +106,7 @@ describe('patchRules', () => {
describe('regression tests', () => {
it("updates the rule's actions if provided", async () => {
const rulesOptionsMock = getPatchRulesOptionsMock();
const rulesOptionsMock = getPatchRulesOptionsMock(isRuleRegistryEnabled);
const ruleOptions: PatchRulesOptions = {
...rulesOptionsMock,
actions: [
@ -118,7 +121,7 @@ describe('patchRules', () => {
],
};
((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue(
getAlertMock(getQueryRuleParams())
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
);
await patchRules(ruleOptions);
expect(ruleOptions.rulesClient.update).toHaveBeenCalledWith(
@ -140,7 +143,7 @@ describe('patchRules', () => {
});
it('does not update actions if none are specified', async () => {
const ruleOptions = getPatchRulesOptionsMock();
const ruleOptions = getPatchRulesOptionsMock(isRuleRegistryEnabled);
delete ruleOptions.actions;
if (ruleOptions.rule != null) {
ruleOptions.rule.actions = [
@ -155,7 +158,7 @@ describe('patchRules', () => {
];
}
((ruleOptions.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue(
getAlertMock(getQueryRuleParams())
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
);
await patchRules(ruleOptions);
expect(ruleOptions.rulesClient.update).toHaveBeenCalledWith(

View file

@ -80,6 +80,7 @@ export const patchRules = async ({
to,
type,
references,
namespace,
note,
version,
exceptionsList,
@ -131,6 +132,7 @@ export const patchRules = async ({
type,
references,
version,
namespace,
note,
exceptionsList,
anomalyThreshold,
@ -176,6 +178,7 @@ export const patchRules = async ({
to,
type,
references,
namespace,
note,
version: calculatedVersion,
exceptionsList,

View file

@ -21,7 +21,10 @@ export class TestError extends Error {
public output: { statusCode: number };
}
describe('read_rules', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('read_rules - %s', (_, isRuleRegistryEnabled) => {
beforeEach(() => {
jest.resetAllMocks();
jest.restoreAllMocks();
@ -30,23 +33,25 @@ describe('read_rules', () => {
describe('readRules', () => {
test('should return the output from rulesClient if id is set but ruleId is undefined', async () => {
const rulesClient = rulesClientMock.create();
rulesClient.get.mockResolvedValue(getAlertMock(getQueryRuleParams()));
rulesClient.get.mockResolvedValue(getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()));
const rule = await readRules({
isRuleRegistryEnabled,
rulesClient,
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
ruleId: undefined,
});
expect(rule).toEqual(getAlertMock(getQueryRuleParams()));
expect(rule).toEqual(getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()));
});
test('should return null if saved object found by alerts client given id is not alert type', async () => {
const rulesClient = rulesClientMock.create();
const result = getAlertMock(getQueryRuleParams());
const result = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
// @ts-expect-error
delete result.alertTypeId;
rulesClient.get.mockResolvedValue(result);
const rule = await readRules({
isRuleRegistryEnabled,
rulesClient,
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
ruleId: undefined,
@ -61,6 +66,7 @@ describe('read_rules', () => {
});
const rule = await readRules({
isRuleRegistryEnabled,
rulesClient,
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
ruleId: undefined,
@ -75,6 +81,7 @@ describe('read_rules', () => {
});
try {
await readRules({
isRuleRegistryEnabled,
rulesClient,
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
ruleId: undefined,
@ -86,23 +93,25 @@ describe('read_rules', () => {
test('should return the output from rulesClient if id is undefined but ruleId is set', async () => {
const rulesClient = rulesClientMock.create();
rulesClient.get.mockResolvedValue(getAlertMock(getQueryRuleParams()));
rulesClient.find.mockResolvedValue(getFindResultWithSingleHit());
rulesClient.get.mockResolvedValue(getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()));
rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled));
const rule = await readRules({
isRuleRegistryEnabled,
rulesClient,
id: undefined,
ruleId: 'rule-1',
});
expect(rule).toEqual(getAlertMock(getQueryRuleParams()));
expect(rule).toEqual(getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()));
});
test('should return null if the output from rulesClient with ruleId set is empty', async () => {
const rulesClient = rulesClientMock.create();
rulesClient.get.mockResolvedValue(getAlertMock(getQueryRuleParams()));
rulesClient.get.mockResolvedValue(getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()));
rulesClient.find.mockResolvedValue({ data: [], page: 0, perPage: 1, total: 0 });
const rule = await readRules({
isRuleRegistryEnabled,
rulesClient,
id: undefined,
ruleId: 'rule-1',
@ -112,23 +121,25 @@ describe('read_rules', () => {
test('should return the output from rulesClient if id is null but ruleId is set', async () => {
const rulesClient = rulesClientMock.create();
rulesClient.get.mockResolvedValue(getAlertMock(getQueryRuleParams()));
rulesClient.find.mockResolvedValue(getFindResultWithSingleHit());
rulesClient.get.mockResolvedValue(getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()));
rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled));
const rule = await readRules({
isRuleRegistryEnabled,
rulesClient,
id: undefined,
ruleId: 'rule-1',
});
expect(rule).toEqual(getAlertMock(getQueryRuleParams()));
expect(rule).toEqual(getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()));
});
test('should return null if id and ruleId are undefined', async () => {
const rulesClient = rulesClientMock.create();
rulesClient.get.mockResolvedValue(getAlertMock(getQueryRuleParams()));
rulesClient.find.mockResolvedValue(getFindResultWithSingleHit());
rulesClient.get.mockResolvedValue(getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()));
rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled));
const rule = await readRules({
isRuleRegistryEnabled,
rulesClient,
id: undefined,
ruleId: undefined,

View file

@ -20,6 +20,7 @@ import { isAlertType, ReadRuleOptions } from './types';
* a filter query against the tags using `alert.attributes.tags: "__internal:${ruleId}"]`
*/
export const readRules = async ({
isRuleRegistryEnabled,
rulesClient,
id,
ruleId,
@ -27,7 +28,7 @@ export const readRules = async ({
if (id != null) {
try {
const rule = await rulesClient.get({ id });
if (isAlertType(rule)) {
if (isAlertType(isRuleRegistryEnabled, rule)) {
return rule;
} else {
return null;
@ -42,6 +43,7 @@ export const readRules = async ({
}
} else if (ruleId != null) {
const ruleFromFind = await findRules({
isRuleRegistryEnabled,
rulesClient,
filter: `alert.attributes.tags: "${INTERNAL_RULE_ID_KEY}:${ruleId}"`,
page: 1,
@ -50,7 +52,10 @@ export const readRules = async ({
sortField: undefined,
sortOrder: undefined,
});
if (ruleFromFind.data.length === 0 || !isAlertType(ruleFromFind.data[0])) {
if (
ruleFromFind.data.length === 0 ||
!isAlertType(isRuleRegistryEnabled, ruleFromFind.data[0])
) {
return null;
} else {
return ruleFromFind.data[0];

View file

@ -101,6 +101,7 @@ import {
BuildingBlockTypeOrUndefined,
RuleNameOverrideOrUndefined,
EventCategoryOverrideOrUndefined,
NamespaceOrUndefined,
} from '../../../../common/detection_engine/schemas/common/schemas';
import { RulesClient, PartialAlert } from '../../../../../alerting/server';
@ -109,6 +110,7 @@ import { SIGNALS_ID } from '../../../../common/constants';
import { PartialFilter } from '../types';
import { RuleParams } from '../schemas/rule_schemas';
import { IRuleExecutionLogClient } from '../rule_execution_log/types';
import { ruleTypeMappings } from '../signals/utils';
export type RuleAlertType = Alert<RuleParams>;
@ -192,15 +194,20 @@ export interface Clients {
}
export const isAlertTypes = (
isRuleRegistryEnabled: boolean,
partialAlert: Array<PartialAlert<RuleParams>>
): partialAlert is RuleAlertType[] => {
return partialAlert.every((rule) => isAlertType(rule));
return partialAlert.every((rule) => isAlertType(isRuleRegistryEnabled, rule));
};
export const isAlertType = (
isRuleRegistryEnabled: boolean,
partialAlert: PartialAlert<RuleParams>
): partialAlert is RuleAlertType => {
return partialAlert.alertTypeId === SIGNALS_ID;
const ruleTypeValues = (Object.values(ruleTypeMappings) as unknown) as string[];
return isRuleRegistryEnabled
? ruleTypeValues.includes(partialAlert.alertTypeId as string)
: partialAlert.alertTypeId === SIGNALS_ID;
};
export const isRuleStatusSavedObjectType = (
@ -266,9 +273,12 @@ export interface CreateRulesOptions {
version: Version;
exceptionsList: ListArray;
actions: RuleAlertAction[];
isRuleRegistryEnabled: boolean;
namespace?: NamespaceOrUndefined;
}
export interface UpdateRulesOptions {
isRuleRegistryEnabled: boolean;
spaceId: string;
ruleStatusClient: IRuleExecutionLogClient;
rulesClient: RulesClient;
@ -327,9 +337,11 @@ export interface PatchRulesOptions {
exceptionsList: ListArrayOrUndefined;
actions: RuleAlertAction[] | undefined;
rule: SanitizedAlert<RuleParams> | null;
namespace?: NamespaceOrUndefined;
}
export interface ReadRuleOptions {
isRuleRegistryEnabled: boolean;
rulesClient: RulesClient;
id: IdOrUndefined;
ruleId: RuleIdOrUndefined;
@ -343,6 +355,7 @@ export interface DeleteRuleOptions {
}
export interface FindRuleOptions {
isRuleRegistryEnabled: boolean;
rulesClient: RulesClient;
perPage: PerPageOrUndefined;
page: PageOrUndefined;

View file

@ -13,7 +13,10 @@ import { getAddPrepackagedRulesSchemaDecodedMock } from '../../../../common/dete
import { ruleExecutionLogClientMock } from '../rule_execution_log/__mocks__/rule_execution_log_client';
jest.mock('./patch_rules');
describe('updatePrepackagedRules', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('updatePrepackagedRules - %s', (_, isRuleRegistryEnabled) => {
let rulesClient: ReturnType<typeof rulesClientMock.create>;
let ruleStatusClient: ReturnType<typeof ruleExecutionLogClientMock.create>;
@ -33,14 +36,15 @@ describe('updatePrepackagedRules', () => {
];
const outputIndex = 'outputIndex';
const prepackagedRule = getAddPrepackagedRulesSchemaDecodedMock();
rulesClient.find.mockResolvedValue(getFindResultWithSingleHit());
rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled));
await updatePrepackagedRules(
rulesClient,
'default',
ruleStatusClient,
[{ ...prepackagedRule, actions }],
outputIndex
outputIndex,
isRuleRegistryEnabled
);
expect(patchRules).toHaveBeenCalledWith(

View file

@ -54,7 +54,8 @@ export const updatePrepackagedRules = async (
spaceId: string,
ruleStatusClient: IRuleExecutionLogClient,
rules: AddPrepackagedRulesSchemaDecoded[],
outputIndex: string
outputIndex: string,
isRuleRegistryEnabled: boolean
): Promise<void> => {
const ruleChunks = chunk(UPDATE_CHUNK_SIZE, rules);
for (const ruleChunk of ruleChunks) {
@ -63,7 +64,8 @@ export const updatePrepackagedRules = async (
spaceId,
ruleStatusClient,
ruleChunk,
outputIndex
outputIndex,
isRuleRegistryEnabled
);
await Promise.all(rulePromises);
}
@ -83,7 +85,8 @@ export const createPromises = (
spaceId: string,
ruleStatusClient: IRuleExecutionLogClient,
rules: AddPrepackagedRulesSchemaDecoded[],
outputIndex: string
outputIndex: string,
isRuleRegistryEnabled: boolean
): Array<Promise<PartialAlert<RuleParams> | null>> => {
return rules.map(async (rule) => {
const {
@ -133,7 +136,12 @@ export const createPromises = (
exceptions_list: exceptionsList,
} = rule;
const existingRule = await readRules({ rulesClient, ruleId, id: undefined });
const existingRule = await readRules({
isRuleRegistryEnabled,
rulesClient,
ruleId,
id: undefined,
});
// TODO: Fix these either with an is conversion or by better typing them within io-ts
const filters: PartialFilter[] | undefined = filtersObject as PartialFilter[];

View file

@ -11,20 +11,21 @@ import {
getUpdateRulesSchemaMock,
} from '../../../../common/detection_engine/schemas/request/rule_schemas.mock';
import { ruleExecutionLogClientMock } from '../rule_execution_log/__mocks__/rule_execution_log_client';
import { UpdateRulesOptions } from './types';
export const getUpdateRulesOptionsMock = (): UpdateRulesOptions => ({
export const getUpdateRulesOptionsMock = (isRuleRegistryEnabled: boolean) => ({
spaceId: 'default',
rulesClient: rulesClientMock.create(),
ruleStatusClient: ruleExecutionLogClientMock.create(),
defaultOutputIndex: '.siem-signals-default',
ruleUpdate: getUpdateRulesSchemaMock(),
isRuleRegistryEnabled,
});
export const getUpdateMlRulesOptionsMock = (): UpdateRulesOptions => ({
export const getUpdateMlRulesOptionsMock = (isRuleRegistryEnabled: boolean) => ({
spaceId: 'default',
rulesClient: rulesClientMock.create(),
ruleStatusClient: ruleExecutionLogClientMock.create(),
defaultOutputIndex: '.siem-signals-default',
ruleUpdate: getUpdateMachineLearningSchemaMock(),
isRuleRegistryEnabled,
});

View file

@ -11,15 +11,18 @@ import { getUpdateRulesOptionsMock, getUpdateMlRulesOptionsMock } from './update
import { RulesClientMock } from '../../../../../alerting/server/rules_client.mock';
import { getMlRuleParams, getQueryRuleParams } from '../schemas/rule_schemas.mock';
describe('updateRules', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('updateRules - %s', (_, isRuleRegistryEnabled) => {
it('should call rulesClient.disable if the rule was enabled and enabled is false', async () => {
const rulesOptionsMock = getUpdateRulesOptionsMock();
const rulesOptionsMock = getUpdateRulesOptionsMock(isRuleRegistryEnabled);
rulesOptionsMock.ruleUpdate.enabled = false;
((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).get.mockResolvedValue(
getAlertMock(getQueryRuleParams())
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
);
((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue(
getAlertMock(getQueryRuleParams())
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
);
await updateRules(rulesOptionsMock);
@ -32,15 +35,15 @@ describe('updateRules', () => {
});
it('should call rulesClient.enable if the rule was disabled and enabled is true', async () => {
const rulesOptionsMock = getUpdateRulesOptionsMock();
const rulesOptionsMock = getUpdateRulesOptionsMock(isRuleRegistryEnabled);
rulesOptionsMock.ruleUpdate.enabled = true;
((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).get.mockResolvedValue({
...getAlertMock(getQueryRuleParams()),
...getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()),
enabled: false,
});
((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue(
getAlertMock(getQueryRuleParams())
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
);
await updateRules(rulesOptionsMock);
@ -53,15 +56,15 @@ describe('updateRules', () => {
});
it('calls the rulesClient with params', async () => {
const rulesOptionsMock = getUpdateMlRulesOptionsMock();
const rulesOptionsMock = getUpdateMlRulesOptionsMock(isRuleRegistryEnabled);
rulesOptionsMock.ruleUpdate.enabled = true;
((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue(
getAlertMock(getMlRuleParams())
getAlertMock(isRuleRegistryEnabled, getMlRuleParams())
);
((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).get.mockResolvedValue(
getAlertMock(getMlRuleParams())
getAlertMock(isRuleRegistryEnabled, getMlRuleParams())
);
await updateRules(rulesOptionsMock);

View file

@ -14,11 +14,12 @@ import { readRules } from './read_rules';
import { UpdateRulesOptions } from './types';
import { addTags } from './add_tags';
import { typeSpecificSnakeToCamel } from '../schemas/rule_converters';
import { InternalRuleUpdate, RuleParams } from '../schemas/rule_schemas';
import { RuleParams } from '../schemas/rule_schemas';
import { enableRule } from './enable_rule';
import { maybeMute, transformToAlertThrottle, transformToNotifyWhen } from './utils';
export const updateRules = async ({
isRuleRegistryEnabled,
spaceId,
rulesClient,
ruleStatusClient,
@ -26,6 +27,7 @@ export const updateRules = async ({
ruleUpdate,
}: UpdateRulesOptions): Promise<PartialAlert<RuleParams> | null> => {
const existingRule = await readRules({
isRuleRegistryEnabled,
rulesClient,
ruleId: ruleUpdate.rule_id,
id: ruleUpdate.id,
@ -36,7 +38,7 @@ export const updateRules = async ({
const typeSpecificParams = typeSpecificSnakeToCamel(ruleUpdate);
const enabled = ruleUpdate.enabled ?? true;
const newInternalRule: InternalRuleUpdate = {
const newInternalRule = {
name: ruleUpdate.name,
tags: addTags(ruleUpdate.tags ?? [], existingRule.params.ruleId, existingRule.params.immutable),
params: {
@ -63,6 +65,7 @@ export const updateRules = async ({
timestampOverride: ruleUpdate.timestamp_override,
to: ruleUpdate.to ?? 'now',
references: ruleUpdate.references ?? [],
namespace: ruleUpdate.namespace,
note: ruleUpdate.note,
// Always use the version from the request if specified. If it isn't specified, leave immutable rules alone and
// increment the version of mutable rules by 1.

View file

@ -81,6 +81,7 @@ describe('utils', () => {
type: undefined,
references: undefined,
version: undefined,
namespace: undefined,
note: undefined,
anomalyThreshold: undefined,
machineLearningJobId: undefined,
@ -131,6 +132,7 @@ describe('utils', () => {
type: undefined,
references: undefined,
version: undefined,
namespace: undefined,
note: undefined,
anomalyThreshold: undefined,
machineLearningJobId: undefined,
@ -181,6 +183,7 @@ describe('utils', () => {
type: undefined,
references: undefined,
version: undefined,
namespace: undefined,
note: undefined,
anomalyThreshold: undefined,
machineLearningJobId: undefined,

View file

@ -52,6 +52,7 @@ import {
RuleNameOverrideOrUndefined,
TimestampOverrideOrUndefined,
EventCategoryOverrideOrUndefined,
NamespaceOrUndefined,
} from '../../../../common/detection_engine/schemas/common/schemas';
import { PartialFilter } from '../types';
import { RuleParams } from '../schemas/rule_schemas';
@ -118,6 +119,7 @@ export interface UpdateProperties {
version: VersionOrUndefined;
exceptionsList: ListArrayOrUndefined;
anomalyThreshold: AnomalyThresholdOrUndefined;
namespace: NamespaceOrUndefined;
}
export const calculateVersion = (

View file

@ -36,6 +36,7 @@ import {
transformToAlertThrottle,
transformToNotifyWhen,
} from '../rules/utils';
import { ruleTypeMappings } from '../signals/utils';
// These functions provide conversions from the request API schema to the internal rule schema and from the internal rule schema
// to the response API schema. This provides static type-check assurances that the internal schema is in sync with the API schema for
@ -121,14 +122,15 @@ export const typeSpecificSnakeToCamel = (params: CreateTypeSpecific): TypeSpecif
export const convertCreateAPIToInternalSchema = (
input: CreateRulesSchema,
siemClient: AppClient
siemClient: AppClient,
isRuleRegistryEnabled: boolean
): InternalRuleCreate => {
const typeSpecificParams = typeSpecificSnakeToCamel(input);
const newRuleId = input.rule_id ?? uuid.v4();
return {
name: input.name,
tags: addTags(input.tags ?? [], newRuleId, false),
alertTypeId: SIGNALS_ID,
alertTypeId: isRuleRegistryEnabled ? ruleTypeMappings[input.type] : SIGNALS_ID,
consumer: SERVER_APP_ID,
params: {
author: input.author ?? [],
@ -153,6 +155,7 @@ export const convertCreateAPIToInternalSchema = (
timestampOverride: input.timestamp_override,
to: input.to ?? 'now',
references: input.references ?? [],
namespace: input.namespace,
note: input.note,
version: input.version ?? 1,
exceptionsList: input.exceptions_list ?? [],
@ -249,6 +252,7 @@ export const commonParamsCamelToSnake = (params: BaseRuleParams) => {
risk_score: params.riskScore,
severity: params.severity,
building_block_type: params.buildingBlockType,
namespace: params.namespace,
note: params.note,
license: params.license,
output_index: params.outputIndex,

View file

@ -36,6 +36,7 @@ const getBaseRuleParams = (): BaseRuleParams => {
riskScoreMapping: [],
ruleNameOverride: undefined,
maxSignals: 10000,
namespace: undefined,
note: '# Investigative notes',
timelineId: 'some-timeline-id',
timelineTitle: 'some-timeline-title',

View file

@ -32,6 +32,7 @@ import {
buildingBlockTypeOrUndefined,
description,
enabled,
namespaceOrUndefined,
noteOrUndefined,
false_positives,
rule_id,
@ -62,7 +63,13 @@ import {
updated_at,
} from '../../../../common/detection_engine/schemas/common/schemas';
import { SIGNALS_ID, SERVER_APP_ID } from '../../../../common/constants';
import {
SIGNALS_ID,
SERVER_APP_ID,
INDICATOR_RULE_TYPE_ID,
ML_RULE_TYPE_ID,
QUERY_RULE_TYPE_ID,
} from '../../../../common/constants';
const nonEqlLanguages = t.keyof({ kuery: null, lucene: null });
export const baseRuleParams = t.exact(
@ -70,6 +77,7 @@ export const baseRuleParams = t.exact(
author,
buildingBlockType: buildingBlockTypeOrUndefined,
description,
namespace: namespaceOrUndefined,
note: noteOrUndefined,
falsePositives: false_positives,
from,
@ -196,10 +204,21 @@ export const notifyWhen = t.union([
t.null,
]);
export const allRuleTypes = t.union([
t.literal(SIGNALS_ID),
// t.literal(EQL_RULE_TYPE_ID),
t.literal(ML_RULE_TYPE_ID),
t.literal(QUERY_RULE_TYPE_ID),
// t.literal(SAVED_QUERY_RULE_TYPE_ID),
t.literal(INDICATOR_RULE_TYPE_ID),
// t.literal(THRESHOLD_RULE_TYPE_ID),
]);
export type AllRuleTypes = t.TypeOf<typeof allRuleTypes>;
export const internalRuleCreate = t.type({
name,
tags,
alertTypeId: t.literal(SIGNALS_ID),
alertTypeId: allRuleTypes,
consumer: t.literal(SERVER_APP_ID),
schedule: t.type({
interval: t.string,

View file

@ -182,7 +182,7 @@ export const searchAfterAndBulkCreate = async ({
break;
}
} catch (exc: unknown) {
logger.error(buildRuleMessage(`[-] search_after and bulk threw an error ${exc}`));
logger.error(buildRuleMessage(`[-] search_after_bulk_create threw an error ${exc}`));
return mergeReturns([
toReturn,
createSearchAfterReturnType({

View file

@ -177,7 +177,7 @@ describe('signal_rule_alert_type', () => {
alertServices.scopedClusterClient.asCurrentUser.fieldCaps.mockResolvedValue(
value as ApiResponse<estypes.FieldCapsResponse>
);
const ruleAlert = getAlertMock(getQueryRuleParams());
const ruleAlert = getAlertMock(false, getQueryRuleParams());
alertServices.savedObjectsClient.get.mockResolvedValue({
id: 'id',
type: 'type',
@ -245,7 +245,7 @@ describe('signal_rule_alert_type', () => {
},
application: {},
});
const newRuleAlert = getAlertMock(getQueryRuleParams());
const newRuleAlert = getAlertMock(false, getQueryRuleParams());
newRuleAlert.params.index = ['some*', 'myfa*', 'anotherindex*'];
payload = getPayload(newRuleAlert, alertServices) as jest.Mocked<RuleExecutorOptions>;
@ -274,7 +274,7 @@ describe('signal_rule_alert_type', () => {
},
application: {},
});
const newRuleAlert = getAlertMock(getQueryRuleParams());
const newRuleAlert = getAlertMock(false, getQueryRuleParams());
newRuleAlert.params.index = ['some*', 'myfa*', 'anotherindex*'];
payload = getPayload(newRuleAlert, alertServices) as jest.Mocked<RuleExecutorOptions>;
@ -309,7 +309,7 @@ describe('signal_rule_alert_type', () => {
});
it('should call scheduleActions if signalsCount was greater than 0 and rule has actions defined', async () => {
const ruleAlert = getAlertMock(getQueryRuleParams());
const ruleAlert = getAlertMock(false, getQueryRuleParams());
ruleAlert.actions = [
{
actionTypeId: '.slack',
@ -333,7 +333,7 @@ describe('signal_rule_alert_type', () => {
});
it('should resolve results_link when meta is an empty object to use "/app/security"', async () => {
const ruleAlert = getAlertMock(getQueryRuleParams());
const ruleAlert = getAlertMock(false, getQueryRuleParams());
ruleAlert.params.meta = {};
ruleAlert.actions = [
{
@ -366,7 +366,7 @@ describe('signal_rule_alert_type', () => {
});
it('should resolve results_link when meta is undefined use "/app/security"', async () => {
const ruleAlert = getAlertMock(getQueryRuleParams());
const ruleAlert = getAlertMock(false, getQueryRuleParams());
delete ruleAlert.params.meta;
ruleAlert.actions = [
{
@ -399,7 +399,7 @@ describe('signal_rule_alert_type', () => {
});
it('should resolve results_link with a custom link', async () => {
const ruleAlert = getAlertMock(getQueryRuleParams());
const ruleAlert = getAlertMock(false, getQueryRuleParams());
ruleAlert.params.meta = { kibana_siem_app_url: 'http://localhost' };
ruleAlert.actions = [
{
@ -433,7 +433,7 @@ describe('signal_rule_alert_type', () => {
describe('ML rule', () => {
it('should not call checkPrivileges if ML rule', async () => {
const ruleAlert = getAlertMock(getMlRuleParams());
const ruleAlert = getAlertMock(false, getMlRuleParams());
alertServices.savedObjectsClient.get.mockResolvedValue({
id: 'id',
type: 'type',

View file

@ -62,6 +62,12 @@ import {
import { WrappedRACAlert } from '../rule_types/types';
import { SearchTypes } from '../../../../common/detection_engine/types';
import { IRuleExecutionLogClient } from '../rule_execution_log/types';
import {
INDICATOR_RULE_TYPE_ID,
ML_RULE_TYPE_ID,
QUERY_RULE_TYPE_ID,
SIGNALS_ID,
} from '../../../../common/constants';
interface SortExceptionsReturn {
exceptionsWithValueLists: ExceptionListItemSchema[];
@ -999,3 +1005,15 @@ export const getField = <T extends SearchTypes>(event: SimpleHit, field: string)
return get(event._source, field) as T;
}
};
/**
* Maps legacy rule types to RAC rule type IDs.
*/
export const ruleTypeMappings = {
eql: SIGNALS_ID,
machine_learning: ML_RULE_TYPE_ID,
query: QUERY_RULE_TYPE_ID,
saved_query: SIGNALS_ID,
threat_match: INDICATOR_RULE_TYPE_ID,
threshold: SIGNALS_ID,
};

View file

@ -11,19 +11,22 @@ import { INTERNAL_RULE_ID_KEY, INTERNAL_IDENTIFIER } from '../../../../common/co
import { readRawTags, readTags, convertTagsToSet, convertToTags, isTags } from './read_tags';
import { getQueryRuleParams } from '../schemas/rule_schemas.mock';
describe('read_tags', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('read_tags - %s', (_, isRuleRegistryEnabled) => {
afterEach(() => {
jest.resetAllMocks();
});
describe('readRawTags', () => {
test('it should return the intersection of tags to where none are repeating', async () => {
const result1 = getAlertMock(getQueryRuleParams());
const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
result1.params.ruleId = 'rule-1';
result1.tags = ['tag 1', 'tag 2', 'tag 3'];
const result2 = getAlertMock(getQueryRuleParams());
const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d';
result2.params.ruleId = 'rule-2';
result2.tags = ['tag 1', 'tag 2', 'tag 3', 'tag 4'];
@ -31,17 +34,17 @@ describe('read_tags', () => {
const rulesClient = rulesClientMock.create();
rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1, result2] }));
const tags = await readRawTags({ rulesClient });
const tags = await readRawTags({ isRuleRegistryEnabled, rulesClient });
expect(tags).toEqual(['tag 1', 'tag 2', 'tag 3', 'tag 4']);
});
test('it should return the intersection of tags to where some are repeating values', async () => {
const result1 = getAlertMock(getQueryRuleParams());
const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
result1.params.ruleId = 'rule-1';
result1.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3'];
const result2 = getAlertMock(getQueryRuleParams());
const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d';
result2.params.ruleId = 'rule-2';
result2.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3', 'tag 4'];
@ -49,17 +52,17 @@ describe('read_tags', () => {
const rulesClient = rulesClientMock.create();
rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1, result2] }));
const tags = await readRawTags({ rulesClient });
const tags = await readRawTags({ isRuleRegistryEnabled, rulesClient });
expect(tags).toEqual(['tag 1', 'tag 2', 'tag 3', 'tag 4']);
});
test('it should work with no tags defined between two results', async () => {
const result1 = getAlertMock(getQueryRuleParams());
const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
result1.params.ruleId = 'rule-1';
result1.tags = [];
const result2 = getAlertMock(getQueryRuleParams());
const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d';
result2.params.ruleId = 'rule-2';
result2.tags = [];
@ -67,12 +70,12 @@ describe('read_tags', () => {
const rulesClient = rulesClientMock.create();
rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1, result2] }));
const tags = await readRawTags({ rulesClient });
const tags = await readRawTags({ isRuleRegistryEnabled, rulesClient });
expect(tags).toEqual([]);
});
test('it should work with a single tag which has repeating values in it', async () => {
const result1 = getAlertMock(getQueryRuleParams());
const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
result1.params.ruleId = 'rule-1';
result1.tags = ['tag 1', 'tag 1', 'tag 1', 'tag 2'];
@ -80,12 +83,12 @@ describe('read_tags', () => {
const rulesClient = rulesClientMock.create();
rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1] }));
const tags = await readRawTags({ rulesClient });
const tags = await readRawTags({ isRuleRegistryEnabled, rulesClient });
expect(tags).toEqual(['tag 1', 'tag 2']);
});
test('it should work with a single tag which has empty tags', async () => {
const result1 = getAlertMock(getQueryRuleParams());
const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
result1.params.ruleId = 'rule-1';
result1.tags = [];
@ -93,19 +96,19 @@ describe('read_tags', () => {
const rulesClient = rulesClientMock.create();
rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1] }));
const tags = await readRawTags({ rulesClient });
const tags = await readRawTags({ isRuleRegistryEnabled, rulesClient });
expect(tags).toEqual([]);
});
});
describe('readTags', () => {
test('it should return the intersection of tags to where none are repeating', async () => {
const result1 = getAlertMock(getQueryRuleParams());
const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
result1.params.ruleId = 'rule-1';
result1.tags = ['tag 1', 'tag 2', 'tag 3'];
const result2 = getAlertMock(getQueryRuleParams());
const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d';
result2.params.ruleId = 'rule-2';
result2.tags = ['tag 1', 'tag 2', 'tag 3', 'tag 4'];
@ -113,17 +116,17 @@ describe('read_tags', () => {
const rulesClient = rulesClientMock.create();
rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1, result2] }));
const tags = await readTags({ rulesClient });
const tags = await readTags({ isRuleRegistryEnabled, rulesClient });
expect(tags).toEqual(['tag 1', 'tag 2', 'tag 3', 'tag 4']);
});
test('it should return the intersection of tags to where some are repeating values', async () => {
const result1 = getAlertMock(getQueryRuleParams());
const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
result1.params.ruleId = 'rule-1';
result1.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3'];
const result2 = getAlertMock(getQueryRuleParams());
const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d';
result2.params.ruleId = 'rule-2';
result2.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3', 'tag 4'];
@ -131,17 +134,17 @@ describe('read_tags', () => {
const rulesClient = rulesClientMock.create();
rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1, result2] }));
const tags = await readTags({ rulesClient });
const tags = await readTags({ isRuleRegistryEnabled, rulesClient });
expect(tags).toEqual(['tag 1', 'tag 2', 'tag 3', 'tag 4']);
});
test('it should work with no tags defined between two results', async () => {
const result1 = getAlertMock(getQueryRuleParams());
const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
result1.params.ruleId = 'rule-1';
result1.tags = [];
const result2 = getAlertMock(getQueryRuleParams());
const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d';
result2.params.ruleId = 'rule-2';
result2.tags = [];
@ -149,12 +152,12 @@ describe('read_tags', () => {
const rulesClient = rulesClientMock.create();
rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1, result2] }));
const tags = await readTags({ rulesClient });
const tags = await readTags({ isRuleRegistryEnabled, rulesClient });
expect(tags).toEqual([]);
});
test('it should work with a single tag which has repeating values in it', async () => {
const result1 = getAlertMock(getQueryRuleParams());
const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
result1.params.ruleId = 'rule-1';
result1.tags = ['tag 1', 'tag 1', 'tag 1', 'tag 2'];
@ -162,12 +165,12 @@ describe('read_tags', () => {
const rulesClient = rulesClientMock.create();
rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1] }));
const tags = await readTags({ rulesClient });
const tags = await readTags({ isRuleRegistryEnabled, rulesClient });
expect(tags).toEqual(['tag 1', 'tag 2']);
});
test('it should work with a single tag which has empty tags', async () => {
const result1 = getAlertMock(getQueryRuleParams());
const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
result1.params.ruleId = 'rule-1';
result1.tags = [];
@ -175,12 +178,12 @@ describe('read_tags', () => {
const rulesClient = rulesClientMock.create();
rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1] }));
const tags = await readTags({ rulesClient });
const tags = await readTags({ isRuleRegistryEnabled, rulesClient });
expect(tags).toEqual([]);
});
test('it should filter out any __internal tags for things such as alert_id', async () => {
const result1 = getAlertMock(getQueryRuleParams());
const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
result1.params.ruleId = 'rule-1';
result1.tags = [
@ -192,12 +195,12 @@ describe('read_tags', () => {
const rulesClient = rulesClientMock.create();
rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1] }));
const tags = await readTags({ rulesClient });
const tags = await readTags({ isRuleRegistryEnabled, rulesClient });
expect(tags).toEqual(['tag 1']);
});
test('it should filter out any __internal tags with two different results', async () => {
const result1 = getAlertMock(getQueryRuleParams());
const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
result1.params.ruleId = 'rule-1';
result1.tags = [
@ -210,7 +213,7 @@ describe('read_tags', () => {
'tag 5',
];
const result2 = getAlertMock(getQueryRuleParams());
const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d';
result2.params.ruleId = 'rule-2';
result2.tags = [
@ -225,19 +228,19 @@ describe('read_tags', () => {
const rulesClient = rulesClientMock.create();
rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1] }));
const tags = await readTags({ rulesClient });
const tags = await readTags({ isRuleRegistryEnabled, rulesClient });
expect(tags).toEqual(['tag 1', 'tag 2', 'tag 3', 'tag 4', 'tag 5']);
});
});
describe('convertTagsToSet', () => {
test('it should convert the intersection of two tag systems without duplicates', () => {
const result1 = getAlertMock(getQueryRuleParams());
const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
result1.params.ruleId = 'rule-1';
result1.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3'];
const result2 = getAlertMock(getQueryRuleParams());
const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d';
result2.params.ruleId = 'rule-2';
result2.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3', 'tag 4'];
@ -255,12 +258,12 @@ describe('read_tags', () => {
describe('convertToTags', () => {
test('it should convert the two tag systems together with duplicates', () => {
const result1 = getAlertMock(getQueryRuleParams());
const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
result1.params.ruleId = 'rule-1';
result1.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3'];
const result2 = getAlertMock(getQueryRuleParams());
const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d';
result2.params.ruleId = 'rule-2';
result2.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3', 'tag 4'];
@ -281,18 +284,18 @@ describe('read_tags', () => {
});
test('it should filter out anything that is not a tag', () => {
const result1 = getAlertMock(getQueryRuleParams());
const result1 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
result1.params.ruleId = 'rule-1';
result1.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3'];
const result2 = getAlertMock(getQueryRuleParams());
const result2 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result2.id = '99979e67-19a7-455f-b452-8eded6135716';
result2.params.ruleId = 'rule-2';
// @ts-expect-error
delete result2.tags;
const result3 = getAlertMock(getQueryRuleParams());
const result3 = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
result3.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d';
result3.params.ruleId = 'rule-2';
result3.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3', 'tag 4'];

View file

@ -40,22 +40,27 @@ export const convertTagsToSet = (tagObjects: object[]): Set<string> => {
// then this should be replaced with a an aggregation call.
// Ref: https://www.elastic.co/guide/en/kibana/master/saved-objects-api.html
export const readTags = async ({
isRuleRegistryEnabled,
rulesClient,
}: {
isRuleRegistryEnabled: boolean;
rulesClient: RulesClient;
}): Promise<string[]> => {
const tags = await readRawTags({ rulesClient });
const tags = await readRawTags({ isRuleRegistryEnabled, rulesClient });
return tags.filter((tag) => !tag.startsWith(INTERNAL_IDENTIFIER));
};
export const readRawTags = async ({
isRuleRegistryEnabled,
rulesClient,
}: {
isRuleRegistryEnabled: boolean;
rulesClient: RulesClient;
perPage?: number;
}): Promise<string[]> => {
// Get just one record so we can get the total count
const firstTags = await findRules({
isRuleRegistryEnabled,
rulesClient,
fields: ['tags'],
perPage: 1,
@ -66,6 +71,7 @@ export const readRawTags = async ({
});
// Get all the rules to aggregate over all the tags of the rules
const rules = await findRules({
isRuleRegistryEnabled,
rulesClient,
fields: ['tags'],
perPage: firstTags.total,

View file

@ -5,8 +5,6 @@
* 2.0.
*/
import { join, resolve } from 'path';
import { createPromiseFromStreams } from '@kbn/utils';
import { SecurityPluginSetup } from '../../../../../../../security/server';
@ -21,17 +19,20 @@ import {
getFindResultWithSingleHit,
} from '../../../../detection_engine/routes/__mocks__/request_responses';
import * as lib from './helpers';
import { importTimelines } from '../../timelines/import_timelines';
import * as helpers from './helpers';
import { importTimelines } from '../../timelines/import_timelines/helpers';
import { buildFrameworkRequest } from '../../../utils/common';
import { ImportTimelineResultSchema } from '../../../../../../common/types/timeline';
jest.mock('../../timelines/import_timelines');
jest.mock('../../timelines/import_timelines/helpers');
describe('installPrepackagedTimelines', () => {
describe.each([
['Legacy', false],
['RAC', true],
])('installPrepackagedTimelines - %s', (_, isRuleRegistryEnabled) => {
let securitySetup: SecurityPluginSetup;
let frameworkRequest: FrameworkRequest;
const spyInstallPrepackagedTimelines = jest.spyOn(lib, 'installPrepackagedTimelines');
const spyInstallPrepackagedTimelines = jest.spyOn(helpers, 'installPrepackagedTimelines');
const { clients, context } = requestContextMock.createTools();
const config = createMockConfig();
@ -46,11 +47,11 @@ describe('installPrepackagedTimelines', () => {
authz: {},
} as unknown) as SecurityPluginSetup;
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit());
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled));
jest.doMock('./helpers', () => {
return {
...lib,
...helpers,
installPrepackagedTimelines: spyInstallPrepackagedTimelines,
};
});
@ -60,7 +61,7 @@ describe('installPrepackagedTimelines', () => {
});
afterEach(() => {
spyInstallPrepackagedTimelines.mockClear();
jest.clearAllMocks();
});
afterAll(() => {
@ -68,10 +69,10 @@ describe('installPrepackagedTimelines', () => {
});
test('should call importTimelines', async () => {
await lib.installPrepackagedTimelines(
await helpers.installPrepackagedTimelines(
config.maxTimelineImportExportSize,
frameworkRequest,
true,
false,
mockFilePath,
mockFileName
);
@ -80,14 +81,12 @@ describe('installPrepackagedTimelines', () => {
});
test('should call importTimelines with Readables', async () => {
const dir = resolve(join(__dirname, mockFilePath));
const file = mockFileName;
await lib.installPrepackagedTimelines(
await helpers.installPrepackagedTimelines(
config.maxTimelineImportExportSize,
frameworkRequest,
true,
dir,
file
mockFilePath,
mockFileName
);
const args = await createPromiseFromStreams([(importTimelines as jest.Mock).mock.calls[0][0]]);
const expected = JSON.stringify({
@ -194,14 +193,12 @@ describe('installPrepackagedTimelines', () => {
});
test('should call importTimelines with maxTimelineImportExportSize', async () => {
const dir = resolve(join(__dirname, mockFilePath));
const file = mockFileName;
await lib.installPrepackagedTimelines(
await helpers.installPrepackagedTimelines(
config.maxTimelineImportExportSize,
frameworkRequest,
true,
dir,
file
mockFilePath,
mockFileName
);
expect((importTimelines as jest.Mock).mock.calls[0][1]).toEqual(
@ -210,14 +207,12 @@ describe('installPrepackagedTimelines', () => {
});
test('should call importTimelines with frameworkRequest', async () => {
const dir = resolve(join(__dirname, mockFilePath));
const file = mockFileName;
await lib.installPrepackagedTimelines(
await helpers.installPrepackagedTimelines(
config.maxTimelineImportExportSize,
frameworkRequest,
true,
dir,
file
mockFilePath,
mockFileName
);
expect(JSON.stringify((importTimelines as jest.Mock).mock.calls[0][2])).toEqual(
@ -226,21 +221,19 @@ describe('installPrepackagedTimelines', () => {
});
test('should call importTimelines with immutable', async () => {
const dir = resolve(join(__dirname, mockFilePath));
const file = mockFileName;
await lib.installPrepackagedTimelines(
await helpers.installPrepackagedTimelines(
config.maxTimelineImportExportSize,
frameworkRequest,
true,
dir,
file
mockFilePath,
mockFileName
);
expect((importTimelines as jest.Mock).mock.calls[0][3]).toEqual(true);
});
test('should handle errors from getReadables', async () => {
const result = await lib.installPrepackagedTimelines(
const result = await helpers.installPrepackagedTimelines(
config.maxTimelineImportExportSize,
frameworkRequest,
true,

View file

@ -63,10 +63,10 @@ import {
SERVER_APP_ID,
SIGNALS_ID,
NOTIFICATIONS_ID,
QUERY_ALERT_TYPE_ID,
QUERY_RULE_TYPE_ID,
DEFAULT_SPACE_ID,
INDICATOR_ALERT_TYPE_ID,
ML_ALERT_TYPE_ID,
INDICATOR_RULE_TYPE_ID,
ML_RULE_TYPE_ID,
} from '../common/constants';
import { registerEndpointRoutes } from './endpoint/routes/metadata';
import { registerLimitedConcurrencyRoutes } from './endpoint/routes/limited_concurrency';
@ -272,7 +272,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
plugins.security,
plugins.ml,
ruleDataService,
ruleDataClient
isRuleRegistryEnabled
);
registerEndpointRoutes(router, endpointContext);
registerLimitedConcurrencyRoutes(core);
@ -281,7 +281,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
registerTrustedAppsRoutes(router, endpointContext);
registerActionRoutes(router, endpointContext);
const racRuleTypes = [QUERY_ALERT_TYPE_ID, INDICATOR_ALERT_TYPE_ID, ML_ALERT_TYPE_ID];
const racRuleTypes = [QUERY_RULE_TYPE_ID, INDICATOR_RULE_TYPE_ID, ML_RULE_TYPE_ID];
const ruleTypes = [
SIGNALS_ID,
NOTIFICATIONS_ID,

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { IRuleDataClient, RuleDataPluginService } from '../../../rule_registry/server';
import { RuleDataPluginService } from '../../../rule_registry/server';
import { SecuritySolutionPluginRouter } from '../types';
@ -64,31 +64,31 @@ export const initRoutes = (
security: SetupPlugins['security'],
ml: SetupPlugins['ml'],
ruleDataService: RuleDataPluginService,
ruleDataClient: IRuleDataClient | null
isRuleRegistryEnabled: boolean
) => {
// Detection Engine Rule routes that have the REST endpoints of /api/detection_engine/rules
// All REST rule creation, deletion, updating, etc......
createRulesRoute(router, ml, ruleDataClient);
readRulesRoute(router, ruleDataClient);
updateRulesRoute(router, ml, ruleDataClient);
patchRulesRoute(router, ml, ruleDataClient);
deleteRulesRoute(router, ruleDataClient);
findRulesRoute(router, ruleDataClient);
createRulesRoute(router, ml, isRuleRegistryEnabled);
readRulesRoute(router, isRuleRegistryEnabled);
updateRulesRoute(router, ml, isRuleRegistryEnabled);
patchRulesRoute(router, ml, isRuleRegistryEnabled);
deleteRulesRoute(router, isRuleRegistryEnabled);
findRulesRoute(router, isRuleRegistryEnabled);
// TODO: pass ruleDataClient to all relevant routes
// TODO: pass isRuleRegistryEnabled to all relevant routes
addPrepackedRulesRoute(router, config, security);
getPrepackagedRulesStatusRoute(router, config, security);
createRulesBulkRoute(router, ml);
updateRulesBulkRoute(router, ml);
patchRulesBulkRoute(router, ml);
deleteRulesBulkRoute(router);
performBulkActionRoute(router, ml);
addPrepackedRulesRoute(router, config, security, isRuleRegistryEnabled);
getPrepackagedRulesStatusRoute(router, config, security, isRuleRegistryEnabled);
createRulesBulkRoute(router, ml, isRuleRegistryEnabled);
updateRulesBulkRoute(router, ml, isRuleRegistryEnabled);
patchRulesBulkRoute(router, ml, isRuleRegistryEnabled);
deleteRulesBulkRoute(router, isRuleRegistryEnabled);
performBulkActionRoute(router, ml, isRuleRegistryEnabled);
createTimelinesRoute(router, config, security);
patchTimelinesRoute(router, config, security);
importRulesRoute(router, config, ml);
exportRulesRoute(router, config);
importRulesRoute(router, config, ml, isRuleRegistryEnabled);
exportRulesRoute(router, config, isRuleRegistryEnabled);
importTimelinesRoute(router, config, security);
exportTimelinesRoute(router, config, security);
@ -123,7 +123,7 @@ export const initRoutes = (
deleteIndexRoute(router);
// Detection Engine tags routes that have the REST endpoints of /api/detection_engine/tags
readTagsRoute(router);
readTagsRoute(router, isRuleRegistryEnabled);
// Privileges API to get the generic user privileges
readPrivilegesRoute(router, hasEncryptionKey);