mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution] DetectionRulesClient
: move public methods out and add APM spans (#184820)
**Partially addresses: https://github.com/elastic/kibana/issues/184364**
## Summary
This PR is second step in refactoring our newly added
`detectionRulesClient`.
Changes in this PR:
- every public method was extracted into its own file for readability
- `_createRule`, `_updateRule`, `_patchRule` and
`_upgradePrebuiltRuleWithTypeChange` private methods were removed, their
code inlined into the public methods
- `toggleRuleEnabledOnUpdate`, `validateMlAuth` and `ClientError` were
moved to `utils.ts`
- methods are now wrapped in `withSecuritySpan` to report perf stats to
APM
- renamed `*.rules_management_client.test.ts` ->
`*.detection_rules_client.test.ts`
- now using the whole `detectionRulesClient` in tests, not just separate
methods
- simplified parameters of `createDetectionRulesClient`. Now 2
parameters are needed instead of 5,
**DetectionRulesClient method showing up in APM**
<img width="918" alt="Schermafbeelding 2024-06-05 om 14 00 36"
src="c8b469f7
-9d0b-4534-a1c9-f35327ec2c4c">
**Extracted methods**
Upon reviewing the private methods in `detection_rules_client.ts`, it
became apparent that extracting these methods into separate files may
not be the most effective approach to improve readability. The primary
reason is that these private methods do not provide clear abstractions,
making them difficult to name appropriately.
Take `_updateRule` as an example. This method combines an existing rule
with a rule update to create an InternalRuleUpdate object, which is then
passed to `rulesClient.update`. If we were to extract this into a
separate file, we would need to import it for use in the public
`updateRule` method. This would result in an `updateRule` method that
calls `_updateRule`, creating confusion about what the inner
`_updateRule` does.
Also, extracting only private methods does not significantly improve
readability, as these methods do not contain a large amount of code.
So I ended up inlining the code from most of these private methods
directly into the public methods.
This commit is contained in:
parent
78b31bbeaf
commit
4ddec38be0
19 changed files with 585 additions and 590 deletions
|
@ -5,19 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type {
|
||||
IDetectionRulesClient,
|
||||
CreateRuleOptions,
|
||||
_UpdateRuleProps,
|
||||
_PatchRuleProps,
|
||||
} from '../detection_rules_client';
|
||||
import type { RulesClient } from '@kbn/alerting-plugin/server';
|
||||
import type {
|
||||
RuleCreateProps,
|
||||
RuleObjectId,
|
||||
} from '../../../../../../../common/api/detection_engine';
|
||||
import type { PrebuiltRuleAsset } from '../../../../prebuilt_rules';
|
||||
import type { RuleAlertType } from '../../../../rule_schema';
|
||||
import type { IDetectionRulesClient } from '../detection_rules_client';
|
||||
|
||||
export type DetectionRulesClientMock = jest.Mocked<IDetectionRulesClient>;
|
||||
|
||||
|
@ -39,36 +27,3 @@ export const detectionRulesClientMock: {
|
|||
} = {
|
||||
create: createDetectionRulesClientMock,
|
||||
};
|
||||
|
||||
/* Mocks for internal methods */
|
||||
export const _createRule: jest.Mock<
|
||||
(
|
||||
rulesClient: RulesClient,
|
||||
params: RuleCreateProps,
|
||||
options: CreateRuleOptions
|
||||
) => Promise<RuleAlertType>
|
||||
> = jest.fn();
|
||||
|
||||
export const _updateRule: jest.Mock<
|
||||
(rulesClient: RulesClient, updateRulePayload: _UpdateRuleProps) => Promise<RuleAlertType>
|
||||
> = jest.fn();
|
||||
|
||||
export const patchRuleMock: jest.Mock<
|
||||
(rulesClient: RulesClient, patchRulePayload: _PatchRuleProps) => Promise<RuleAlertType>
|
||||
> = jest.fn();
|
||||
|
||||
export const _upgradePrebuiltRuleWithTypeChange: jest.Mock<
|
||||
(
|
||||
rulesClient: RulesClient,
|
||||
ruleAsset: PrebuiltRuleAsset,
|
||||
existingRule: RuleAlertType
|
||||
) => Promise<RuleAlertType>
|
||||
> = jest.fn();
|
||||
|
||||
export const _toggleRuleEnabledOnUpdate: jest.Mock<
|
||||
(rulesClient: RulesClient, existingRule: RuleAlertType, enabled: boolean) => Promise<void>
|
||||
> = jest.fn();
|
||||
|
||||
export const _deleteRule: jest.Mock<
|
||||
(rulesClient: RulesClient, deleteRulePayload: { ruleId: RuleObjectId }) => Promise<void>
|
||||
> = jest.fn();
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
import { rulesClientMock } from '@kbn/alerting-plugin/server/mocks';
|
||||
|
||||
import { createCustomRule } from './detection_rules_client';
|
||||
|
||||
import {
|
||||
getCreateRulesSchemaMock,
|
||||
getCreateMachineLearningRulesSchemaMock,
|
||||
|
@ -17,23 +15,28 @@ import {
|
|||
import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../../../../common/constants';
|
||||
import { buildMlAuthz } from '../../../../machine_learning/authz';
|
||||
import { throwAuthzError } from '../../../../machine_learning/validation';
|
||||
import { createDetectionRulesClient } from './detection_rules_client';
|
||||
import type { IDetectionRulesClient } from './detection_rules_client';
|
||||
|
||||
jest.mock('../../../../machine_learning/authz');
|
||||
jest.mock('../../../../machine_learning/validation');
|
||||
|
||||
describe('DetectionRulesClient.createCustomRule', () => {
|
||||
let rulesClient: ReturnType<typeof rulesClientMock.create>;
|
||||
let detectionRulesClient: IDetectionRulesClient;
|
||||
|
||||
const mlAuthz = (buildMlAuthz as jest.Mock)();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
rulesClient = rulesClientMock.create();
|
||||
detectionRulesClient = createDetectionRulesClient(rulesClient, mlAuthz);
|
||||
});
|
||||
|
||||
it('should create a rule with the correct parameters and options', async () => {
|
||||
const params = getCreateRulesSchemaMock();
|
||||
|
||||
await createCustomRule(rulesClient, { params }, mlAuthz);
|
||||
await detectionRulesClient.createCustomRule({ params });
|
||||
|
||||
expect(rulesClient.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
@ -44,8 +47,6 @@ describe('DetectionRulesClient.createCustomRule', () => {
|
|||
immutable: false,
|
||||
}),
|
||||
}),
|
||||
options: {},
|
||||
allowMissingConnectorSecrets: undefined,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
@ -56,18 +57,16 @@ describe('DetectionRulesClient.createCustomRule', () => {
|
|||
});
|
||||
|
||||
await expect(
|
||||
createCustomRule(rulesClient, { params: getCreateMachineLearningRulesSchemaMock() }, mlAuthz)
|
||||
detectionRulesClient.createCustomRule({ params: getCreateMachineLearningRulesSchemaMock() })
|
||||
).rejects.toThrow('mocked MLAuth error');
|
||||
|
||||
expect(rulesClient.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls the rulesClient with legacy ML params', async () => {
|
||||
await createCustomRule(
|
||||
rulesClient,
|
||||
{ params: getCreateMachineLearningRulesSchemaMock() },
|
||||
mlAuthz
|
||||
);
|
||||
await detectionRulesClient.createCustomRule({
|
||||
params: getCreateMachineLearningRulesSchemaMock(),
|
||||
});
|
||||
|
||||
expect(rulesClient.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
@ -78,23 +77,17 @@ describe('DetectionRulesClient.createCustomRule', () => {
|
|||
immutable: false,
|
||||
}),
|
||||
}),
|
||||
options: {},
|
||||
allowMissingConnectorSecrets: undefined,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('calls the rulesClient with ML params', async () => {
|
||||
await createCustomRule(
|
||||
rulesClient,
|
||||
{
|
||||
params: {
|
||||
...getCreateMachineLearningRulesSchemaMock(),
|
||||
machine_learning_job_id: ['new_job_1', 'new_job_2'],
|
||||
},
|
||||
await detectionRulesClient.createCustomRule({
|
||||
params: {
|
||||
...getCreateMachineLearningRulesSchemaMock(),
|
||||
machine_learning_job_id: ['new_job_1', 'new_job_2'],
|
||||
},
|
||||
mlAuthz
|
||||
);
|
||||
});
|
||||
|
||||
expect(rulesClient.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
@ -105,8 +98,6 @@ describe('DetectionRulesClient.createCustomRule', () => {
|
|||
immutable: false,
|
||||
}),
|
||||
}),
|
||||
options: {},
|
||||
allowMissingConnectorSecrets: undefined,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
@ -115,13 +106,9 @@ describe('DetectionRulesClient.createCustomRule', () => {
|
|||
const params = getCreateThreatMatchRulesSchemaMock();
|
||||
delete params.threat_indicator_path;
|
||||
|
||||
await createCustomRule(
|
||||
rulesClient,
|
||||
{
|
||||
params,
|
||||
},
|
||||
mlAuthz
|
||||
);
|
||||
await detectionRulesClient.createCustomRule({
|
||||
params,
|
||||
});
|
||||
|
||||
expect(rulesClient.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
@ -131,14 +118,13 @@ describe('DetectionRulesClient.createCustomRule', () => {
|
|||
immutable: false,
|
||||
}),
|
||||
}),
|
||||
options: {},
|
||||
allowMissingConnectorSecrets: undefined,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('does not populate a threatIndicatorPath value for other rules if empty', async () => {
|
||||
await createCustomRule(rulesClient, { params: getCreateRulesSchemaMock() }, mlAuthz);
|
||||
await detectionRulesClient.createCustomRule({ params: getCreateRulesSchemaMock() });
|
||||
|
||||
expect(rulesClient.create).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: expect.objectContaining({
|
||||
|
@ -147,8 +133,6 @@ describe('DetectionRulesClient.createCustomRule', () => {
|
|||
immutable: false,
|
||||
}),
|
||||
}),
|
||||
options: {},
|
||||
allowMissingConnectorSecrets: undefined,
|
||||
})
|
||||
);
|
||||
});
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { RulesClient } from '@kbn/alerting-plugin/server';
|
||||
import type { RuleCreateProps } from '../../../../../../common/api/detection_engine';
|
||||
import type { MlAuthz } from '../../../../machine_learning/authz';
|
||||
import type { RuleAlertType, RuleParams } from '../../../rule_schema';
|
||||
import { withSecuritySpan } from '../../../../../utils/with_security_span';
|
||||
import { convertCreateAPIToInternalSchema } from '../../normalization/rule_converters';
|
||||
|
||||
import { validateMlAuth } from './utils';
|
||||
|
||||
export interface CreateCustomRuleProps {
|
||||
params: RuleCreateProps;
|
||||
}
|
||||
|
||||
export const createCustomRule = async (
|
||||
rulesClient: RulesClient,
|
||||
createCustomRulePayload: CreateCustomRuleProps,
|
||||
mlAuthz: MlAuthz
|
||||
): Promise<RuleAlertType> =>
|
||||
withSecuritySpan('DetectionRulesClient.createCustomRule', async () => {
|
||||
const { params } = createCustomRulePayload;
|
||||
await validateMlAuth(mlAuthz, params.type);
|
||||
|
||||
const internalRule = convertCreateAPIToInternalSchema(params, { immutable: false });
|
||||
const rule = await rulesClient.create<RuleParams>({
|
||||
data: internalRule,
|
||||
});
|
||||
|
||||
return rule;
|
||||
});
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
import { rulesClientMock } from '@kbn/alerting-plugin/server/mocks';
|
||||
|
||||
import { createPrebuiltRule } from './detection_rules_client';
|
||||
|
||||
import {
|
||||
getCreateRulesSchemaMock,
|
||||
getCreateMachineLearningRulesSchemaMock,
|
||||
|
@ -17,23 +15,28 @@ import {
|
|||
import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../../../../common/constants';
|
||||
import { buildMlAuthz } from '../../../../machine_learning/authz';
|
||||
import { throwAuthzError } from '../../../../machine_learning/validation';
|
||||
import { createDetectionRulesClient } from './detection_rules_client';
|
||||
import type { IDetectionRulesClient } from './detection_rules_client';
|
||||
|
||||
jest.mock('../../../../machine_learning/authz');
|
||||
jest.mock('../../../../machine_learning/validation');
|
||||
|
||||
describe('DetectionRulesClient.createPrebuiltRule', () => {
|
||||
let rulesClient: ReturnType<typeof rulesClientMock.create>;
|
||||
let detectionRulesClient: IDetectionRulesClient;
|
||||
|
||||
const mlAuthz = (buildMlAuthz as jest.Mock)();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
rulesClient = rulesClientMock.create();
|
||||
detectionRulesClient = createDetectionRulesClient(rulesClient, mlAuthz);
|
||||
});
|
||||
|
||||
it('creates a rule with the correct parameters and options', async () => {
|
||||
const ruleAsset = { ...getCreateRulesSchemaMock(), version: 1, rule_id: 'rule-id' };
|
||||
|
||||
await createPrebuiltRule(rulesClient, { ruleAsset }, mlAuthz);
|
||||
await detectionRulesClient.createPrebuiltRule({ ruleAsset });
|
||||
|
||||
expect(rulesClient.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
@ -45,8 +48,6 @@ describe('DetectionRulesClient.createPrebuiltRule', () => {
|
|||
immutable: true,
|
||||
}),
|
||||
}),
|
||||
options: {},
|
||||
allowMissingConnectorSecrets: undefined,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
@ -57,7 +58,7 @@ describe('DetectionRulesClient.createPrebuiltRule', () => {
|
|||
throw new Error('mocked MLAuth error');
|
||||
});
|
||||
|
||||
await expect(createPrebuiltRule(rulesClient, { ruleAsset }, mlAuthz)).rejects.toThrow(
|
||||
await expect(detectionRulesClient.createPrebuiltRule({ ruleAsset })).rejects.toThrow(
|
||||
'mocked MLAuth error'
|
||||
);
|
||||
|
||||
|
@ -70,13 +71,9 @@ describe('DetectionRulesClient.createPrebuiltRule', () => {
|
|||
version: 1,
|
||||
rule_id: 'rule-id',
|
||||
};
|
||||
await createPrebuiltRule(
|
||||
rulesClient,
|
||||
{
|
||||
ruleAsset,
|
||||
},
|
||||
mlAuthz
|
||||
);
|
||||
await detectionRulesClient.createPrebuiltRule({
|
||||
ruleAsset,
|
||||
});
|
||||
|
||||
expect(rulesClient.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
@ -88,8 +85,6 @@ describe('DetectionRulesClient.createPrebuiltRule', () => {
|
|||
immutable: true,
|
||||
}),
|
||||
}),
|
||||
options: {},
|
||||
allowMissingConnectorSecrets: undefined,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
@ -101,13 +96,9 @@ describe('DetectionRulesClient.createPrebuiltRule', () => {
|
|||
version: 1,
|
||||
rule_id: 'rule-id',
|
||||
};
|
||||
await createPrebuiltRule(
|
||||
rulesClient,
|
||||
{
|
||||
ruleAsset,
|
||||
},
|
||||
mlAuthz
|
||||
);
|
||||
await detectionRulesClient.createPrebuiltRule({
|
||||
ruleAsset,
|
||||
});
|
||||
|
||||
expect(rulesClient.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
@ -119,8 +110,6 @@ describe('DetectionRulesClient.createPrebuiltRule', () => {
|
|||
immutable: true,
|
||||
}),
|
||||
}),
|
||||
options: {},
|
||||
allowMissingConnectorSecrets: undefined,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
@ -129,13 +118,9 @@ describe('DetectionRulesClient.createPrebuiltRule', () => {
|
|||
const ruleAsset = { ...getCreateThreatMatchRulesSchemaMock(), version: 1, rule_id: 'rule-id' };
|
||||
delete ruleAsset.threat_indicator_path;
|
||||
|
||||
await createPrebuiltRule(
|
||||
rulesClient,
|
||||
{
|
||||
ruleAsset,
|
||||
},
|
||||
mlAuthz
|
||||
);
|
||||
await detectionRulesClient.createPrebuiltRule({
|
||||
ruleAsset,
|
||||
});
|
||||
|
||||
expect(rulesClient.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
@ -146,15 +131,13 @@ describe('DetectionRulesClient.createPrebuiltRule', () => {
|
|||
immutable: true,
|
||||
}),
|
||||
}),
|
||||
options: {},
|
||||
allowMissingConnectorSecrets: undefined,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('does not populate a threatIndicatorPath value for other rules if empty', async () => {
|
||||
const ruleAsset = { ...getCreateRulesSchemaMock(), version: 1, rule_id: 'rule-id' };
|
||||
await createPrebuiltRule(rulesClient, { ruleAsset }, mlAuthz);
|
||||
await detectionRulesClient.createPrebuiltRule({ ruleAsset });
|
||||
expect(rulesClient.create).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: expect.objectContaining({
|
||||
|
@ -164,8 +147,6 @@ describe('DetectionRulesClient.createPrebuiltRule', () => {
|
|||
immutable: true,
|
||||
}),
|
||||
}),
|
||||
options: {},
|
||||
allowMissingConnectorSecrets: undefined,
|
||||
})
|
||||
);
|
||||
});
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { RulesClient } from '@kbn/alerting-plugin/server';
|
||||
import type { PrebuiltRuleAsset } from '../../../prebuilt_rules';
|
||||
import type { MlAuthz } from '../../../../machine_learning/authz';
|
||||
import type { RuleAlertType, RuleParams } from '../../../rule_schema';
|
||||
import { withSecuritySpan } from '../../../../../utils/with_security_span';
|
||||
import { convertCreateAPIToInternalSchema } from '../../normalization/rule_converters';
|
||||
|
||||
import { validateMlAuth } from './utils';
|
||||
|
||||
export interface CreatePrebuiltRuleProps {
|
||||
ruleAsset: PrebuiltRuleAsset;
|
||||
}
|
||||
|
||||
export const createPrebuiltRule = async (
|
||||
rulesClient: RulesClient,
|
||||
createPrebuiltRulePayload: CreatePrebuiltRuleProps,
|
||||
mlAuthz: MlAuthz
|
||||
): Promise<RuleAlertType> =>
|
||||
withSecuritySpan('DetectionRulesClient.createPrebuiltRule', async () => {
|
||||
const { ruleAsset } = createPrebuiltRulePayload;
|
||||
|
||||
await validateMlAuth(mlAuthz, ruleAsset.type);
|
||||
|
||||
const internalRule = convertCreateAPIToInternalSchema(ruleAsset, {
|
||||
immutable: true,
|
||||
defaultEnabled: false,
|
||||
});
|
||||
|
||||
const rule = await rulesClient.create<RuleParams>({
|
||||
data: internalRule,
|
||||
});
|
||||
|
||||
return rule;
|
||||
});
|
|
@ -6,18 +6,26 @@
|
|||
*/
|
||||
|
||||
import { rulesClientMock } from '@kbn/alerting-plugin/server/mocks';
|
||||
import { deleteRule } from './detection_rules_client';
|
||||
import { buildMlAuthz } from '../../../../machine_learning/authz';
|
||||
import { createDetectionRulesClient } from './detection_rules_client';
|
||||
import type { IDetectionRulesClient } from './detection_rules_client';
|
||||
|
||||
jest.mock('../../../../machine_learning/authz');
|
||||
|
||||
describe('DetectionRulesClient.deleteRule', () => {
|
||||
let rulesClient: ReturnType<typeof rulesClientMock.create>;
|
||||
let detectionRulesClient: IDetectionRulesClient;
|
||||
|
||||
const mlAuthz = (buildMlAuthz as jest.Mock)();
|
||||
|
||||
beforeEach(() => {
|
||||
rulesClient = rulesClientMock.create();
|
||||
detectionRulesClient = createDetectionRulesClient(rulesClient, mlAuthz);
|
||||
});
|
||||
|
||||
it('should call rulesClient.delete passing the expected ruleId', async () => {
|
||||
const ruleId = 'ruleId';
|
||||
await deleteRule(rulesClient, {
|
||||
await detectionRulesClient.deleteRule({
|
||||
ruleId,
|
||||
});
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { RulesClient } from '@kbn/alerting-plugin/server';
|
||||
import type { RuleObjectId } from '../../../../../../common/api/detection_engine';
|
||||
import { withSecuritySpan } from '../../../../../utils/with_security_span';
|
||||
|
||||
export interface DeleteRuleProps {
|
||||
ruleId: RuleObjectId;
|
||||
}
|
||||
|
||||
export const deleteRule = async (
|
||||
rulesClient: RulesClient,
|
||||
deleteRulePayload: DeleteRuleProps
|
||||
): Promise<void> =>
|
||||
withSecuritySpan('DetectionRulesClient.deleteRule', async () => {
|
||||
const { ruleId } = deleteRulePayload;
|
||||
await rulesClient.delete({ id: ruleId });
|
||||
});
|
|
@ -6,97 +6,25 @@
|
|||
*/
|
||||
|
||||
import type { RulesClient } from '@kbn/alerting-plugin/server';
|
||||
|
||||
import type { KibanaRequest } from '@kbn/core-http-server';
|
||||
import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
|
||||
import type { LicensingApiRequestHandlerContext } from '@kbn/licensing-plugin/server';
|
||||
import type { SharedServices } from '@kbn/ml-plugin/server/shared_services';
|
||||
import type { Type } from '@kbn/securitysolution-io-ts-alerting-types';
|
||||
import type { MlAuthz } from '../../../../machine_learning/authz';
|
||||
import { buildMlAuthz } from '../../../../machine_learning/authz';
|
||||
import type {
|
||||
RuleCreateProps,
|
||||
RuleObjectId,
|
||||
RuleToImport,
|
||||
PatchRuleRequestBody,
|
||||
RuleUpdateProps,
|
||||
} from '../../../../../../common/api/detection_engine';
|
||||
|
||||
import type { PrebuiltRuleAsset } from '../../../prebuilt_rules';
|
||||
import type { RuleAlertType } from '../../../rule_schema';
|
||||
|
||||
import { readRules } from './read_rules';
|
||||
import type { CreateCustomRuleProps } from './create_custom_rule';
|
||||
import type { CreatePrebuiltRuleProps } from './create_prebuilt_rule';
|
||||
import type { UpdateRuleProps } from './update_rule';
|
||||
import type { PatchRuleProps } from './patch_rule';
|
||||
import type { DeleteRuleProps } from './delete_rule';
|
||||
import type { UpgradePrebuiltRuleProps } from './upgrade_prebuilt_rule';
|
||||
import type { ImportRuleProps } from './import_rule';
|
||||
|
||||
import {
|
||||
convertPatchAPIToInternalSchema,
|
||||
convertUpdateAPIToInternalSchema,
|
||||
convertCreateAPIToInternalSchema,
|
||||
} from '../../normalization/rule_converters';
|
||||
import { transformAlertToRuleAction } from '../../../../../../common/detection_engine/transform_actions';
|
||||
import type { RuleAlertType, RuleParams } from '../../../rule_schema';
|
||||
import { createBulkErrorObject } from '../../../routes/utils';
|
||||
import { getIdError } from '../../utils/utils';
|
||||
import { throwAuthzError } from '../../../../machine_learning/validation';
|
||||
|
||||
class ClientError extends Error {
|
||||
public readonly statusCode: number;
|
||||
constructor(message: string, statusCode: number) {
|
||||
super(message);
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
}
|
||||
|
||||
export interface CreateRuleOptions {
|
||||
/* Optionally pass an ID to use for the rule document. If not provided, an ID will be generated. */
|
||||
/* This is the ES document ID, NOT the rule_id */
|
||||
id?: string;
|
||||
immutable?: boolean;
|
||||
defaultEnabled?: boolean;
|
||||
allowMissingConnectorSecrets?: boolean;
|
||||
}
|
||||
|
||||
export interface _UpdateRuleProps {
|
||||
existingRule: RuleAlertType;
|
||||
ruleUpdate: RuleUpdateProps;
|
||||
}
|
||||
|
||||
export interface _PatchRuleProps {
|
||||
existingRule: RuleAlertType;
|
||||
nextParams: PatchRuleRequestBody;
|
||||
}
|
||||
|
||||
interface CreateCustomRuleProps {
|
||||
params: RuleCreateProps;
|
||||
}
|
||||
|
||||
interface CreatePrebuiltRuleProps {
|
||||
ruleAsset: PrebuiltRuleAsset;
|
||||
}
|
||||
|
||||
interface UpdateRuleProps {
|
||||
ruleUpdate: RuleUpdateProps;
|
||||
}
|
||||
|
||||
interface PatchRuleProps {
|
||||
nextParams: PatchRuleRequestBody;
|
||||
}
|
||||
|
||||
interface DeleteRuleProps {
|
||||
ruleId: RuleObjectId;
|
||||
}
|
||||
|
||||
interface UpgradePrebuiltRuleProps {
|
||||
ruleAsset: PrebuiltRuleAsset;
|
||||
}
|
||||
|
||||
interface ImportRuleOptions {
|
||||
allowMissingConnectorSecrets?: boolean;
|
||||
}
|
||||
|
||||
interface ImportRuleProps {
|
||||
ruleToImport: RuleToImport;
|
||||
overwriteRules?: boolean;
|
||||
options: ImportRuleOptions;
|
||||
}
|
||||
import { createCustomRule } from './create_custom_rule';
|
||||
import { createPrebuiltRule } from './create_prebuilt_rule';
|
||||
import { updateRule } from './update_rule';
|
||||
import { patchRule } from './patch_rule';
|
||||
import { deleteRule } from './delete_rule';
|
||||
import { upgradePrebuiltRule } from './upgrade_prebuilt_rule';
|
||||
import { importRule } from './import_rule';
|
||||
|
||||
export interface IDetectionRulesClient {
|
||||
createCustomRule: (createCustomRulePayload: CreateCustomRuleProps) => Promise<RuleAlertType>;
|
||||
|
@ -114,317 +42,39 @@ export interface IDetectionRulesClient {
|
|||
|
||||
export const createDetectionRulesClient = (
|
||||
rulesClient: RulesClient,
|
||||
request: KibanaRequest,
|
||||
savedObjectsClient: SavedObjectsClientContract,
|
||||
licensing: LicensingApiRequestHandlerContext,
|
||||
ml?: SharedServices
|
||||
): IDetectionRulesClient => {
|
||||
const mlAuthz = buildMlAuthz({
|
||||
license: licensing.license,
|
||||
ml,
|
||||
request,
|
||||
savedObjectsClient,
|
||||
});
|
||||
|
||||
const client = {
|
||||
createCustomRule: async (
|
||||
createCustomRulePayload: CreateCustomRuleProps
|
||||
): Promise<RuleAlertType> => {
|
||||
return createCustomRule(rulesClient, createCustomRulePayload, mlAuthz);
|
||||
},
|
||||
|
||||
createPrebuiltRule: async (
|
||||
createPrebuiltRulePayload: CreatePrebuiltRuleProps
|
||||
): Promise<RuleAlertType> => {
|
||||
return createPrebuiltRule(rulesClient, createPrebuiltRulePayload, mlAuthz);
|
||||
},
|
||||
|
||||
updateRule: async (updateRulePayload: UpdateRuleProps): Promise<RuleAlertType> => {
|
||||
return updateRule(rulesClient, updateRulePayload, mlAuthz);
|
||||
},
|
||||
|
||||
patchRule: async (patchRulePayload: PatchRuleProps): Promise<RuleAlertType> => {
|
||||
return patchRule(rulesClient, patchRulePayload, mlAuthz);
|
||||
},
|
||||
|
||||
deleteRule: async (deleteRulePayload: DeleteRuleProps): Promise<void> => {
|
||||
return deleteRule(rulesClient, deleteRulePayload);
|
||||
},
|
||||
|
||||
upgradePrebuiltRule: async (
|
||||
upgradePrebuiltRulePayload: UpgradePrebuiltRuleProps
|
||||
): Promise<RuleAlertType> => {
|
||||
return upgradePrebuiltRule(rulesClient, upgradePrebuiltRulePayload, mlAuthz);
|
||||
},
|
||||
|
||||
importRule: async (importRulePayload: ImportRuleProps): Promise<RuleAlertType> => {
|
||||
return importRule(rulesClient, importRulePayload, mlAuthz);
|
||||
},
|
||||
};
|
||||
|
||||
return client;
|
||||
};
|
||||
|
||||
export const createCustomRule = async (
|
||||
rulesClient: RulesClient,
|
||||
createCustomRulePayload: CreateCustomRuleProps,
|
||||
mlAuthz: MlAuthz
|
||||
): Promise<RuleAlertType> => {
|
||||
const { params } = createCustomRulePayload;
|
||||
await _validateMlAuth(mlAuthz, params.type);
|
||||
): IDetectionRulesClient => ({
|
||||
createCustomRule: async (
|
||||
createCustomRulePayload: CreateCustomRuleProps
|
||||
): Promise<RuleAlertType> => {
|
||||
return createCustomRule(rulesClient, createCustomRulePayload, mlAuthz);
|
||||
},
|
||||
|
||||
const rule = await _createRule(rulesClient, params, { immutable: false });
|
||||
return rule;
|
||||
};
|
||||
createPrebuiltRule: async (
|
||||
createPrebuiltRulePayload: CreatePrebuiltRuleProps
|
||||
): Promise<RuleAlertType> => {
|
||||
return createPrebuiltRule(rulesClient, createPrebuiltRulePayload, mlAuthz);
|
||||
},
|
||||
|
||||
export const createPrebuiltRule = async (
|
||||
rulesClient: RulesClient,
|
||||
createPrebuiltRulePayload: CreatePrebuiltRuleProps,
|
||||
mlAuthz: MlAuthz
|
||||
): Promise<RuleAlertType> => {
|
||||
const { ruleAsset } = createPrebuiltRulePayload;
|
||||
updateRule: async (updateRulePayload: UpdateRuleProps): Promise<RuleAlertType> => {
|
||||
return updateRule(rulesClient, updateRulePayload, mlAuthz);
|
||||
},
|
||||
|
||||
await _validateMlAuth(mlAuthz, ruleAsset.type);
|
||||
patchRule: async (patchRulePayload: PatchRuleProps): Promise<RuleAlertType> => {
|
||||
return patchRule(rulesClient, patchRulePayload, mlAuthz);
|
||||
},
|
||||
|
||||
const rule = await _createRule(rulesClient, ruleAsset, {
|
||||
immutable: true,
|
||||
defaultEnabled: false,
|
||||
});
|
||||
deleteRule: async (deleteRulePayload: DeleteRuleProps): Promise<void> => {
|
||||
return deleteRule(rulesClient, deleteRulePayload);
|
||||
},
|
||||
|
||||
return rule;
|
||||
};
|
||||
upgradePrebuiltRule: async (
|
||||
upgradePrebuiltRulePayload: UpgradePrebuiltRuleProps
|
||||
): Promise<RuleAlertType> => {
|
||||
return upgradePrebuiltRule(rulesClient, upgradePrebuiltRulePayload, mlAuthz);
|
||||
},
|
||||
|
||||
export const updateRule = async (
|
||||
rulesClient: RulesClient,
|
||||
updateRulePayload: UpdateRuleProps,
|
||||
mlAuthz: MlAuthz
|
||||
): Promise<RuleAlertType> => {
|
||||
const { ruleUpdate } = updateRulePayload;
|
||||
const { rule_id: ruleId, id } = ruleUpdate;
|
||||
|
||||
await _validateMlAuth(mlAuthz, ruleUpdate.type);
|
||||
|
||||
const existingRule = await readRules({
|
||||
rulesClient,
|
||||
ruleId,
|
||||
id,
|
||||
});
|
||||
|
||||
if (existingRule == null) {
|
||||
const error = getIdError({ id, ruleId });
|
||||
throw new ClientError(error.message, error.statusCode);
|
||||
}
|
||||
|
||||
const update = await _updateRule(rulesClient, { ruleUpdate, existingRule });
|
||||
|
||||
await _toggleRuleEnabledOnUpdate(rulesClient, existingRule, ruleUpdate.enabled);
|
||||
|
||||
return { ...update, enabled: ruleUpdate.enabled ?? existingRule.enabled };
|
||||
};
|
||||
|
||||
export const patchRule = async (
|
||||
rulesClient: RulesClient,
|
||||
patchRulePayload: PatchRuleProps,
|
||||
mlAuthz: MlAuthz
|
||||
): Promise<RuleAlertType> => {
|
||||
const { nextParams } = patchRulePayload;
|
||||
const { rule_id: ruleId, id } = nextParams;
|
||||
|
||||
const existingRule = await readRules({
|
||||
rulesClient,
|
||||
ruleId,
|
||||
id,
|
||||
});
|
||||
|
||||
if (existingRule == null) {
|
||||
const error = getIdError({ id, ruleId });
|
||||
throw new ClientError(error.message, error.statusCode);
|
||||
}
|
||||
|
||||
await _validateMlAuth(mlAuthz, nextParams.type ?? existingRule.params.type);
|
||||
|
||||
const update = await _patchRule(rulesClient, { existingRule, nextParams });
|
||||
|
||||
await _toggleRuleEnabledOnUpdate(rulesClient, existingRule, nextParams.enabled);
|
||||
|
||||
if (nextParams.enabled != null) {
|
||||
return { ...update, enabled: nextParams.enabled };
|
||||
} else {
|
||||
return update;
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteRule = async (
|
||||
rulesClient: RulesClient,
|
||||
deleteRulePayload: DeleteRuleProps
|
||||
): Promise<void> => {
|
||||
const { ruleId } = deleteRulePayload;
|
||||
await rulesClient.delete({ id: ruleId });
|
||||
};
|
||||
|
||||
export const upgradePrebuiltRule = async (
|
||||
rulesClient: RulesClient,
|
||||
upgradePrebuiltRulePayload: UpgradePrebuiltRuleProps,
|
||||
mlAuthz: MlAuthz
|
||||
): Promise<RuleAlertType> => {
|
||||
const { ruleAsset } = upgradePrebuiltRulePayload;
|
||||
|
||||
await _validateMlAuth(mlAuthz, ruleAsset.type);
|
||||
|
||||
const existingRule = await readRules({
|
||||
rulesClient,
|
||||
ruleId: ruleAsset.rule_id,
|
||||
id: undefined,
|
||||
});
|
||||
|
||||
if (!existingRule) {
|
||||
throw new ClientError(`Failed to find rule ${ruleAsset.rule_id}`, 500);
|
||||
}
|
||||
|
||||
// If rule has change its type during upgrade, delete and recreate it
|
||||
if (ruleAsset.type !== existingRule.params.type) {
|
||||
return _upgradePrebuiltRuleWithTypeChange(rulesClient, ruleAsset, existingRule);
|
||||
}
|
||||
|
||||
// Else, simply patch it.
|
||||
await _patchRule(rulesClient, { existingRule, nextParams: ruleAsset });
|
||||
|
||||
const updatedRule = await readRules({
|
||||
rulesClient,
|
||||
ruleId: ruleAsset.rule_id,
|
||||
id: undefined,
|
||||
});
|
||||
|
||||
if (!updatedRule) {
|
||||
throw new ClientError(`Rule ${ruleAsset.rule_id} not found after upgrade`, 500);
|
||||
}
|
||||
|
||||
return updatedRule;
|
||||
};
|
||||
|
||||
export const importRule = async (
|
||||
rulesClient: RulesClient,
|
||||
importRulePayload: ImportRuleProps,
|
||||
mlAuthz: MlAuthz
|
||||
): Promise<RuleAlertType> => {
|
||||
const { ruleToImport, overwriteRules, options } = importRulePayload;
|
||||
|
||||
await _validateMlAuth(mlAuthz, ruleToImport.type);
|
||||
|
||||
const existingRule = await readRules({
|
||||
rulesClient,
|
||||
ruleId: ruleToImport.rule_id,
|
||||
id: undefined,
|
||||
});
|
||||
|
||||
if (!existingRule) {
|
||||
return _createRule(rulesClient, ruleToImport, {
|
||||
immutable: false,
|
||||
allowMissingConnectorSecrets: options?.allowMissingConnectorSecrets,
|
||||
});
|
||||
} else if (existingRule && overwriteRules) {
|
||||
return _updateRule(rulesClient, {
|
||||
existingRule,
|
||||
ruleUpdate: ruleToImport,
|
||||
});
|
||||
} else {
|
||||
throw createBulkErrorObject({
|
||||
ruleId: existingRule.params.ruleId,
|
||||
statusCode: 409,
|
||||
message: `rule_id: "${existingRule.params.ruleId}" already exists`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/* -------- Internal Methods -------- */
|
||||
const _createRule = async (
|
||||
rulesClient: RulesClient,
|
||||
params: RuleCreateProps,
|
||||
options: CreateRuleOptions
|
||||
) => {
|
||||
const rulesClientCreateRuleOptions = options.id ? { id: options.id } : {};
|
||||
|
||||
const internalRule = convertCreateAPIToInternalSchema(params, options);
|
||||
const rule = await rulesClient.create<RuleParams>({
|
||||
data: internalRule,
|
||||
options: rulesClientCreateRuleOptions,
|
||||
allowMissingConnectorSecrets: options.allowMissingConnectorSecrets,
|
||||
});
|
||||
|
||||
return rule;
|
||||
};
|
||||
|
||||
const _updateRule = async (
|
||||
rulesClient: RulesClient,
|
||||
updateRulePayload: _UpdateRuleProps
|
||||
): Promise<RuleAlertType> => {
|
||||
const { ruleUpdate, existingRule } = updateRulePayload;
|
||||
|
||||
const newInternalRule = convertUpdateAPIToInternalSchema({
|
||||
existingRule,
|
||||
ruleUpdate,
|
||||
});
|
||||
|
||||
const update = await rulesClient.update({
|
||||
id: existingRule.id,
|
||||
data: newInternalRule,
|
||||
});
|
||||
|
||||
return update;
|
||||
};
|
||||
|
||||
const _patchRule = async (
|
||||
rulesClient: RulesClient,
|
||||
patchRulePayload: _PatchRuleProps
|
||||
): Promise<RuleAlertType> => {
|
||||
const { nextParams, existingRule } = patchRulePayload;
|
||||
|
||||
const patchedRule = convertPatchAPIToInternalSchema(nextParams, existingRule);
|
||||
|
||||
const update = await rulesClient.update({
|
||||
id: existingRule.id,
|
||||
data: patchedRule,
|
||||
});
|
||||
|
||||
return update;
|
||||
};
|
||||
|
||||
const _upgradePrebuiltRuleWithTypeChange = async (
|
||||
rulesClient: RulesClient,
|
||||
ruleAsset: PrebuiltRuleAsset,
|
||||
existingRule: RuleAlertType
|
||||
) => {
|
||||
// If we're trying to change the type of a prepackaged rule, we need to delete the old one
|
||||
// and replace it with the new rule, keeping the enabled setting, actions, throttle, id,
|
||||
// and exception lists from the old rule
|
||||
await rulesClient.delete({ id: existingRule.id });
|
||||
|
||||
return _createRule(
|
||||
rulesClient,
|
||||
{
|
||||
...ruleAsset,
|
||||
enabled: existingRule.enabled,
|
||||
exceptions_list: existingRule.params.exceptionsList,
|
||||
actions: existingRule.actions.map(transformAlertToRuleAction),
|
||||
timeline_id: existingRule.params.timelineId,
|
||||
timeline_title: existingRule.params.timelineTitle,
|
||||
},
|
||||
{ immutable: true, defaultEnabled: existingRule.enabled, id: existingRule.id }
|
||||
);
|
||||
};
|
||||
|
||||
const _toggleRuleEnabledOnUpdate = async (
|
||||
rulesClient: RulesClient,
|
||||
existingRule: RuleAlertType,
|
||||
updatedRuleEnabled?: boolean
|
||||
) => {
|
||||
if (existingRule.enabled && updatedRuleEnabled === false) {
|
||||
await rulesClient.disable({ id: existingRule.id });
|
||||
} else if (!existingRule.enabled && updatedRuleEnabled === true) {
|
||||
await rulesClient.enable({ id: existingRule.id });
|
||||
}
|
||||
};
|
||||
|
||||
const _validateMlAuth = async (mlAuthz: MlAuthz, ruleType: Type) => {
|
||||
throwAuthzError(await mlAuthz.validateRuleType(ruleType));
|
||||
};
|
||||
importRule: async (importRulePayload: ImportRuleProps): Promise<RuleAlertType> => {
|
||||
return importRule(rulesClient, importRulePayload, mlAuthz);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -6,13 +6,14 @@
|
|||
*/
|
||||
|
||||
import { rulesClientMock } from '@kbn/alerting-plugin/server/mocks';
|
||||
import { importRule } from './detection_rules_client';
|
||||
import { readRules } from './read_rules';
|
||||
import { getCreateRulesSchemaMock } from '../../../../../../common/api/detection_engine/model/rule_schema/mocks';
|
||||
import { getRuleMock } from '../../../routes/__mocks__/request_responses';
|
||||
import { getQueryRuleParams } from '../../../rule_schema/mocks';
|
||||
import { buildMlAuthz } from '../../../../machine_learning/authz';
|
||||
import { throwAuthzError } from '../../../../machine_learning/validation';
|
||||
import { createDetectionRulesClient } from './detection_rules_client';
|
||||
import type { IDetectionRulesClient } from './detection_rules_client';
|
||||
|
||||
jest.mock('../../../../machine_learning/authz');
|
||||
jest.mock('../../../../machine_learning/validation');
|
||||
|
@ -21,6 +22,8 @@ jest.mock('./read_rules');
|
|||
|
||||
describe('DetectionRulesClient.importRule', () => {
|
||||
let rulesClient: ReturnType<typeof rulesClientMock.create>;
|
||||
let detectionRulesClient: IDetectionRulesClient;
|
||||
|
||||
const mlAuthz = (buildMlAuthz as jest.Mock)();
|
||||
const immutable = false as const; // Can only take value of false
|
||||
const allowMissingConnectorSecrets = true;
|
||||
|
@ -39,19 +42,16 @@ describe('DetectionRulesClient.importRule', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
rulesClient = rulesClientMock.create();
|
||||
detectionRulesClient = createDetectionRulesClient(rulesClient, mlAuthz);
|
||||
});
|
||||
|
||||
it('calls rulesClient.create with the correct parameters when rule_id does not match an installed rule', async () => {
|
||||
(readRules as jest.Mock).mockResolvedValue(null);
|
||||
await importRule(
|
||||
rulesClient,
|
||||
{
|
||||
ruleToImport,
|
||||
overwriteRules: true,
|
||||
options: { allowMissingConnectorSecrets },
|
||||
},
|
||||
mlAuthz
|
||||
);
|
||||
await detectionRulesClient.importRule({
|
||||
ruleToImport,
|
||||
overwriteRules: true,
|
||||
options: { allowMissingConnectorSecrets },
|
||||
});
|
||||
|
||||
expect(rulesClient.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
@ -64,7 +64,6 @@ describe('DetectionRulesClient.importRule', () => {
|
|||
version: ruleToImport.version,
|
||||
}),
|
||||
}),
|
||||
options: {},
|
||||
allowMissingConnectorSecrets,
|
||||
})
|
||||
);
|
||||
|
@ -76,15 +75,11 @@ describe('DetectionRulesClient.importRule', () => {
|
|||
});
|
||||
|
||||
await expect(
|
||||
importRule(
|
||||
rulesClient,
|
||||
{
|
||||
ruleToImport,
|
||||
overwriteRules: true,
|
||||
options: { allowMissingConnectorSecrets },
|
||||
},
|
||||
mlAuthz
|
||||
)
|
||||
detectionRulesClient.importRule({
|
||||
ruleToImport,
|
||||
overwriteRules: true,
|
||||
options: { allowMissingConnectorSecrets },
|
||||
})
|
||||
).rejects.toThrow('mocked MLAuth error');
|
||||
|
||||
expect(rulesClient.create).not.toHaveBeenCalled();
|
||||
|
@ -94,15 +89,11 @@ describe('DetectionRulesClient.importRule', () => {
|
|||
describe('when rule_id matches an installed rule', () => {
|
||||
it('calls rulesClient.update with the correct parameters when overwriteRules is true', async () => {
|
||||
(readRules as jest.Mock).mockResolvedValue(existingRule);
|
||||
await importRule(
|
||||
rulesClient,
|
||||
{
|
||||
ruleToImport,
|
||||
overwriteRules: true,
|
||||
options: { allowMissingConnectorSecrets },
|
||||
},
|
||||
mlAuthz
|
||||
);
|
||||
await detectionRulesClient.importRule({
|
||||
ruleToImport,
|
||||
overwriteRules: true,
|
||||
options: { allowMissingConnectorSecrets },
|
||||
});
|
||||
|
||||
expect(rulesClient.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
@ -136,18 +127,14 @@ describe('DetectionRulesClient.importRule', () => {
|
|||
};
|
||||
(readRules as jest.Mock).mockResolvedValue(existingRuleWithTimestampOverride);
|
||||
|
||||
await importRule(
|
||||
rulesClient,
|
||||
{
|
||||
ruleToImport: {
|
||||
...ruleToImport,
|
||||
timestamp_override: undefined,
|
||||
},
|
||||
overwriteRules: true,
|
||||
options: { allowMissingConnectorSecrets },
|
||||
await detectionRulesClient.importRule({
|
||||
ruleToImport: {
|
||||
...ruleToImport,
|
||||
timestamp_override: undefined,
|
||||
},
|
||||
mlAuthz
|
||||
);
|
||||
overwriteRules: true,
|
||||
options: { allowMissingConnectorSecrets },
|
||||
});
|
||||
|
||||
expect(rulesClient.create).not.toHaveBeenCalled();
|
||||
expect(rulesClient.update).toHaveBeenCalledWith(
|
||||
|
@ -164,15 +151,11 @@ describe('DetectionRulesClient.importRule', () => {
|
|||
it('rejects when overwriteRules is false', async () => {
|
||||
(readRules as jest.Mock).mockResolvedValue(existingRule);
|
||||
await expect(
|
||||
importRule(
|
||||
rulesClient,
|
||||
{
|
||||
ruleToImport,
|
||||
overwriteRules: false,
|
||||
options: { allowMissingConnectorSecrets },
|
||||
},
|
||||
mlAuthz
|
||||
)
|
||||
detectionRulesClient.importRule({
|
||||
ruleToImport,
|
||||
overwriteRules: false,
|
||||
options: { allowMissingConnectorSecrets },
|
||||
})
|
||||
).rejects.toMatchObject({
|
||||
error: {
|
||||
status_code: 409,
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { RulesClient } from '@kbn/alerting-plugin/server';
|
||||
import type { MlAuthz } from '../../../../machine_learning/authz';
|
||||
import type { RuleAlertType, RuleParams } from '../../../rule_schema';
|
||||
import { withSecuritySpan } from '../../../../../utils/with_security_span';
|
||||
import type { RuleToImport } from '../../../../../../common/api/detection_engine';
|
||||
import { createBulkErrorObject } from '../../../routes/utils';
|
||||
import {
|
||||
convertCreateAPIToInternalSchema,
|
||||
convertUpdateAPIToInternalSchema,
|
||||
} from '../../normalization/rule_converters';
|
||||
|
||||
import { validateMlAuth } from './utils';
|
||||
|
||||
import { readRules } from './read_rules';
|
||||
|
||||
interface ImportRuleOptions {
|
||||
allowMissingConnectorSecrets?: boolean;
|
||||
}
|
||||
|
||||
export interface ImportRuleProps {
|
||||
ruleToImport: RuleToImport;
|
||||
overwriteRules?: boolean;
|
||||
options: ImportRuleOptions;
|
||||
}
|
||||
|
||||
export const importRule = async (
|
||||
rulesClient: RulesClient,
|
||||
importRulePayload: ImportRuleProps,
|
||||
mlAuthz: MlAuthz
|
||||
): Promise<RuleAlertType> =>
|
||||
withSecuritySpan('DetectionRulesClient.importRule', async () => {
|
||||
const { ruleToImport, overwriteRules, options } = importRulePayload;
|
||||
|
||||
await validateMlAuth(mlAuthz, ruleToImport.type);
|
||||
|
||||
const existingRule = await readRules({
|
||||
rulesClient,
|
||||
ruleId: ruleToImport.rule_id,
|
||||
id: undefined,
|
||||
});
|
||||
|
||||
if (!existingRule) {
|
||||
const internalRule = convertCreateAPIToInternalSchema(ruleToImport, {
|
||||
immutable: false,
|
||||
});
|
||||
|
||||
return rulesClient.create<RuleParams>({
|
||||
data: internalRule,
|
||||
allowMissingConnectorSecrets: options.allowMissingConnectorSecrets,
|
||||
});
|
||||
} else if (existingRule && overwriteRules) {
|
||||
const newInternalRule = convertUpdateAPIToInternalSchema({
|
||||
existingRule,
|
||||
ruleUpdate: ruleToImport,
|
||||
});
|
||||
|
||||
return rulesClient.update({
|
||||
id: existingRule.id,
|
||||
data: newInternalRule,
|
||||
});
|
||||
} else {
|
||||
throw createBulkErrorObject({
|
||||
ruleId: existingRule.params.ruleId,
|
||||
statusCode: 409,
|
||||
message: `rule_id: "${existingRule.params.ruleId}" already exists`,
|
||||
});
|
||||
}
|
||||
});
|
|
@ -13,10 +13,11 @@ import {
|
|||
getCreateMachineLearningRulesSchemaMock,
|
||||
getCreateRulesSchemaMock,
|
||||
} from '../../../../../../common/api/detection_engine/model/rule_schema/mocks';
|
||||
import { patchRule } from './detection_rules_client';
|
||||
import { readRules } from './read_rules';
|
||||
import { buildMlAuthz } from '../../../../machine_learning/authz';
|
||||
import { throwAuthzError } from '../../../../machine_learning/validation';
|
||||
import { createDetectionRulesClient } from './detection_rules_client';
|
||||
import type { IDetectionRulesClient } from './detection_rules_client';
|
||||
|
||||
jest.mock('../../../../machine_learning/authz');
|
||||
jest.mock('../../../../machine_learning/validation');
|
||||
|
@ -25,10 +26,13 @@ jest.mock('./read_rules');
|
|||
|
||||
describe('DetectionRulesClient.patchRule', () => {
|
||||
let rulesClient: ReturnType<typeof rulesClientMock.create>;
|
||||
let detectionRulesClient: IDetectionRulesClient;
|
||||
|
||||
const mlAuthz = (buildMlAuthz as jest.Mock)();
|
||||
|
||||
beforeEach(() => {
|
||||
rulesClient = rulesClientMock.create();
|
||||
detectionRulesClient = createDetectionRulesClient(rulesClient, mlAuthz);
|
||||
});
|
||||
|
||||
it('calls the rulesClient with expected params', async () => {
|
||||
|
@ -37,7 +41,7 @@ describe('DetectionRulesClient.patchRule', () => {
|
|||
(readRules as jest.Mock).mockResolvedValueOnce(existingRule);
|
||||
rulesClient.update.mockResolvedValue(getRuleMock(getQueryRuleParams()));
|
||||
|
||||
await patchRule(rulesClient, { nextParams }, mlAuthz);
|
||||
await detectionRulesClient.patchRule({ nextParams });
|
||||
|
||||
expect(rulesClient.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
@ -58,7 +62,7 @@ describe('DetectionRulesClient.patchRule', () => {
|
|||
(readRules as jest.Mock).mockResolvedValueOnce(existingRule);
|
||||
rulesClient.update.mockResolvedValue(getRuleMock(getQueryRuleParams()));
|
||||
|
||||
const rule = await patchRule(rulesClient, { nextParams }, mlAuthz);
|
||||
const rule = await detectionRulesClient.patchRule({ nextParams });
|
||||
|
||||
expect(rule.enabled).toBe(true);
|
||||
});
|
||||
|
@ -69,7 +73,7 @@ describe('DetectionRulesClient.patchRule', () => {
|
|||
(readRules as jest.Mock).mockResolvedValueOnce(existingRule);
|
||||
rulesClient.update.mockResolvedValue(getRuleMock(getMlRuleParams()));
|
||||
|
||||
await patchRule(rulesClient, { nextParams }, mlAuthz);
|
||||
await detectionRulesClient.patchRule({ nextParams });
|
||||
expect(rulesClient.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: expect.objectContaining({
|
||||
|
@ -91,7 +95,7 @@ describe('DetectionRulesClient.patchRule', () => {
|
|||
(readRules as jest.Mock).mockResolvedValueOnce(existingRule);
|
||||
rulesClient.update.mockResolvedValue(getRuleMock(getMlRuleParams()));
|
||||
|
||||
await patchRule(rulesClient, { nextParams }, mlAuthz);
|
||||
await detectionRulesClient.patchRule({ nextParams });
|
||||
|
||||
expect(rulesClient.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
@ -117,7 +121,7 @@ describe('DetectionRulesClient.patchRule', () => {
|
|||
(readRules as jest.Mock).mockResolvedValueOnce(existingRule);
|
||||
rulesClient.update.mockResolvedValue(getRuleMock(getQueryRuleParams()));
|
||||
|
||||
await patchRule(rulesClient, { nextParams }, mlAuthz);
|
||||
await detectionRulesClient.patchRule({ nextParams });
|
||||
|
||||
expect(rulesClient.disable).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
@ -138,7 +142,7 @@ describe('DetectionRulesClient.patchRule', () => {
|
|||
(readRules as jest.Mock).mockResolvedValueOnce(existingRule);
|
||||
rulesClient.update.mockResolvedValue(getRuleMock(getQueryRuleParams()));
|
||||
|
||||
await patchRule(rulesClient, { nextParams }, mlAuthz);
|
||||
await detectionRulesClient.patchRule({ nextParams });
|
||||
|
||||
expect(rulesClient.enable).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
@ -157,7 +161,7 @@ describe('DetectionRulesClient.patchRule', () => {
|
|||
enabled: true,
|
||||
};
|
||||
|
||||
await expect(patchRule(rulesClient, { nextParams }, mlAuthz)).rejects.toThrow(
|
||||
await expect(detectionRulesClient.patchRule({ nextParams })).rejects.toThrow(
|
||||
'mocked MLAuth error'
|
||||
);
|
||||
|
||||
|
@ -183,7 +187,7 @@ describe('DetectionRulesClient.patchRule', () => {
|
|||
(readRules as jest.Mock).mockResolvedValueOnce(existingRule);
|
||||
rulesClient.update.mockResolvedValue(getRuleMock(getQueryRuleParams()));
|
||||
|
||||
await patchRule(rulesClient, { nextParams }, mlAuthz);
|
||||
await detectionRulesClient.patchRule({ nextParams });
|
||||
|
||||
expect(rulesClient.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
@ -221,7 +225,7 @@ describe('DetectionRulesClient.patchRule', () => {
|
|||
(readRules as jest.Mock).mockResolvedValueOnce(existingRule);
|
||||
rulesClient.update.mockResolvedValue(getRuleMock(getQueryRuleParams()));
|
||||
|
||||
await patchRule(rulesClient, { nextParams }, mlAuthz);
|
||||
await detectionRulesClient.patchRule({ nextParams });
|
||||
|
||||
expect(rulesClient.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { RulesClient } from '@kbn/alerting-plugin/server';
|
||||
import type { PatchRuleRequestBody } from '../../../../../../common/api/detection_engine';
|
||||
import type { MlAuthz } from '../../../../machine_learning/authz';
|
||||
import type { RuleAlertType } from '../../../rule_schema';
|
||||
import { withSecuritySpan } from '../../../../../utils/with_security_span';
|
||||
import { getIdError } from '../../utils/utils';
|
||||
import { convertPatchAPIToInternalSchema } from '../../normalization/rule_converters';
|
||||
|
||||
import { validateMlAuth, ClientError, toggleRuleEnabledOnUpdate } from './utils';
|
||||
|
||||
import { readRules } from './read_rules';
|
||||
|
||||
export interface PatchRuleProps {
|
||||
nextParams: PatchRuleRequestBody;
|
||||
}
|
||||
|
||||
export const patchRule = async (
|
||||
rulesClient: RulesClient,
|
||||
patchRulePayload: PatchRuleProps,
|
||||
mlAuthz: MlAuthz
|
||||
): Promise<RuleAlertType> =>
|
||||
withSecuritySpan('DetectionRulesClient.patchRule', async () => {
|
||||
const { nextParams } = patchRulePayload;
|
||||
const { rule_id: ruleId, id } = nextParams;
|
||||
|
||||
const existingRule = await readRules({
|
||||
rulesClient,
|
||||
ruleId,
|
||||
id,
|
||||
});
|
||||
|
||||
if (existingRule == null) {
|
||||
const error = getIdError({ id, ruleId });
|
||||
throw new ClientError(error.message, error.statusCode);
|
||||
}
|
||||
|
||||
await validateMlAuth(mlAuthz, nextParams.type ?? existingRule.params.type);
|
||||
|
||||
const patchedRule = convertPatchAPIToInternalSchema(nextParams, existingRule);
|
||||
|
||||
const update = await rulesClient.update({
|
||||
id: existingRule.id,
|
||||
data: patchedRule,
|
||||
});
|
||||
|
||||
await toggleRuleEnabledOnUpdate(rulesClient, existingRule, nextParams.enabled);
|
||||
|
||||
if (nextParams.enabled != null) {
|
||||
return { ...update, enabled: nextParams.enabled };
|
||||
} else {
|
||||
return update;
|
||||
}
|
||||
});
|
|
@ -13,7 +13,7 @@ import {
|
|||
getCreateMachineLearningRulesSchemaMock,
|
||||
getCreateRulesSchemaMock,
|
||||
} from '../../../../../../common/api/detection_engine/model/rule_schema/mocks';
|
||||
import { updateRule } from './detection_rules_client';
|
||||
import { updateRule } from './update_rule';
|
||||
import { readRules } from './read_rules';
|
||||
import { buildMlAuthz } from '../../../../machine_learning/authz';
|
||||
import { throwAuthzError } from '../../../../machine_learning/validation';
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { RulesClient } from '@kbn/alerting-plugin/server';
|
||||
import type { RuleUpdateProps } from '../../../../../../common/api/detection_engine';
|
||||
import type { MlAuthz } from '../../../../machine_learning/authz';
|
||||
import type { RuleAlertType } from '../../../rule_schema';
|
||||
import { withSecuritySpan } from '../../../../../utils/with_security_span';
|
||||
import { getIdError } from '../../utils/utils';
|
||||
import { convertUpdateAPIToInternalSchema } from '../../normalization/rule_converters';
|
||||
|
||||
import { validateMlAuth, ClientError, toggleRuleEnabledOnUpdate } from './utils';
|
||||
|
||||
import { readRules } from './read_rules';
|
||||
|
||||
export interface UpdateRuleProps {
|
||||
ruleUpdate: RuleUpdateProps;
|
||||
}
|
||||
|
||||
export const updateRule = async (
|
||||
rulesClient: RulesClient,
|
||||
updateRulePayload: UpdateRuleProps,
|
||||
mlAuthz: MlAuthz
|
||||
): Promise<RuleAlertType> =>
|
||||
withSecuritySpan('DetectionRulesClient.updateRule', async () => {
|
||||
const { ruleUpdate } = updateRulePayload;
|
||||
const { rule_id: ruleId, id } = ruleUpdate;
|
||||
|
||||
await validateMlAuth(mlAuthz, ruleUpdate.type);
|
||||
|
||||
const existingRule = await readRules({
|
||||
rulesClient,
|
||||
ruleId,
|
||||
id,
|
||||
});
|
||||
|
||||
if (existingRule == null) {
|
||||
const error = getIdError({ id, ruleId });
|
||||
throw new ClientError(error.message, error.statusCode);
|
||||
}
|
||||
|
||||
const newInternalRule = convertUpdateAPIToInternalSchema({
|
||||
existingRule,
|
||||
ruleUpdate,
|
||||
});
|
||||
|
||||
const update = await rulesClient.update({
|
||||
id: existingRule.id,
|
||||
data: newInternalRule,
|
||||
});
|
||||
|
||||
await toggleRuleEnabledOnUpdate(rulesClient, existingRule, ruleUpdate.enabled);
|
||||
|
||||
return { ...update, enabled: ruleUpdate.enabled ?? existingRule.enabled };
|
||||
});
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
import { rulesClientMock } from '@kbn/alerting-plugin/server/mocks';
|
||||
|
||||
import { upgradePrebuiltRule } from './detection_rules_client';
|
||||
|
||||
import {
|
||||
getCreateEqlRuleSchemaMock,
|
||||
getCreateRulesSchemaMock,
|
||||
|
@ -21,6 +19,8 @@ import { getEqlRuleParams, getQueryRuleParams } from '../../../rule_schema/mocks
|
|||
|
||||
import { buildMlAuthz } from '../../../../machine_learning/authz';
|
||||
import { throwAuthzError } from '../../../../machine_learning/validation';
|
||||
import { createDetectionRulesClient } from './detection_rules_client';
|
||||
import type { IDetectionRulesClient } from './detection_rules_client';
|
||||
|
||||
jest.mock('../../../../machine_learning/authz');
|
||||
jest.mock('../../../../machine_learning/validation');
|
||||
|
@ -28,10 +28,13 @@ jest.mock('./read_rules');
|
|||
|
||||
describe('DetectionRulesClient.upgradePrebuiltRule', () => {
|
||||
let rulesClient: ReturnType<typeof rulesClientMock.create>;
|
||||
let detectionRulesClient: IDetectionRulesClient;
|
||||
|
||||
const mlAuthz = (buildMlAuthz as jest.Mock)();
|
||||
|
||||
beforeEach(() => {
|
||||
rulesClient = rulesClientMock.create();
|
||||
detectionRulesClient = createDetectionRulesClient(rulesClient, mlAuthz);
|
||||
});
|
||||
|
||||
it('throws if no matching rule_id is found', async () => {
|
||||
|
@ -42,7 +45,7 @@ describe('DetectionRulesClient.upgradePrebuiltRule', () => {
|
|||
};
|
||||
|
||||
(readRules as jest.Mock).mockResolvedValue(null);
|
||||
await expect(upgradePrebuiltRule(rulesClient, { ruleAsset }, mlAuthz)).rejects.toThrow(
|
||||
await expect(detectionRulesClient.upgradePrebuiltRule({ ruleAsset })).rejects.toThrow(
|
||||
`Failed to find rule ${ruleAsset.rule_id}`
|
||||
);
|
||||
});
|
||||
|
@ -58,7 +61,7 @@ describe('DetectionRulesClient.upgradePrebuiltRule', () => {
|
|||
rule_id: 'rule-id',
|
||||
};
|
||||
|
||||
await expect(upgradePrebuiltRule(rulesClient, { ruleAsset }, mlAuthz)).rejects.toThrow(
|
||||
await expect(detectionRulesClient.upgradePrebuiltRule({ ruleAsset })).rejects.toThrow(
|
||||
'mocked MLAuth error'
|
||||
);
|
||||
|
||||
|
@ -100,12 +103,12 @@ describe('DetectionRulesClient.upgradePrebuiltRule', () => {
|
|||
});
|
||||
|
||||
it('deletes the old rule ', async () => {
|
||||
await upgradePrebuiltRule(rulesClient, { ruleAsset }, mlAuthz);
|
||||
await detectionRulesClient.upgradePrebuiltRule({ ruleAsset });
|
||||
expect(rulesClient.delete).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('creates a new rule with the new type and expected params of the original rules', async () => {
|
||||
await upgradePrebuiltRule(rulesClient, { ruleAsset }, mlAuthz);
|
||||
await detectionRulesClient.upgradePrebuiltRule({ ruleAsset });
|
||||
expect(rulesClient.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: expect.objectContaining({
|
||||
|
@ -127,7 +130,6 @@ describe('DetectionRulesClient.upgradePrebuiltRule', () => {
|
|||
options: {
|
||||
id: installedRule.id, // id is maintained
|
||||
},
|
||||
allowMissingConnectorSecrets: undefined,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
@ -151,7 +153,7 @@ describe('DetectionRulesClient.upgradePrebuiltRule', () => {
|
|||
});
|
||||
|
||||
it('patches the existing rule with the new params from the rule asset', async () => {
|
||||
await upgradePrebuiltRule(rulesClient, { ruleAsset }, mlAuthz);
|
||||
await detectionRulesClient.upgradePrebuiltRule({ ruleAsset });
|
||||
expect(rulesClient.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: expect.objectContaining({
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { RulesClient } from '@kbn/alerting-plugin/server';
|
||||
import type { PrebuiltRuleAsset } from '../../../prebuilt_rules';
|
||||
import type { MlAuthz } from '../../../../machine_learning/authz';
|
||||
import type { RuleAlertType, RuleParams } from '../../../rule_schema';
|
||||
import { withSecuritySpan } from '../../../../../utils/with_security_span';
|
||||
import {
|
||||
convertPatchAPIToInternalSchema,
|
||||
convertCreateAPIToInternalSchema,
|
||||
} from '../../normalization/rule_converters';
|
||||
import { transformAlertToRuleAction } from '../../../../../../common/detection_engine/transform_actions';
|
||||
|
||||
import { validateMlAuth, ClientError } from './utils';
|
||||
|
||||
import { readRules } from './read_rules';
|
||||
|
||||
export interface UpgradePrebuiltRuleProps {
|
||||
ruleAsset: PrebuiltRuleAsset;
|
||||
}
|
||||
|
||||
export const upgradePrebuiltRule = async (
|
||||
rulesClient: RulesClient,
|
||||
upgradePrebuiltRulePayload: UpgradePrebuiltRuleProps,
|
||||
mlAuthz: MlAuthz
|
||||
): Promise<RuleAlertType> =>
|
||||
withSecuritySpan('DetectionRulesClient.upgradePrebuiltRule', async () => {
|
||||
const { ruleAsset } = upgradePrebuiltRulePayload;
|
||||
|
||||
await validateMlAuth(mlAuthz, ruleAsset.type);
|
||||
|
||||
const existingRule = await readRules({
|
||||
rulesClient,
|
||||
ruleId: ruleAsset.rule_id,
|
||||
id: undefined,
|
||||
});
|
||||
|
||||
if (!existingRule) {
|
||||
throw new ClientError(`Failed to find rule ${ruleAsset.rule_id}`, 500);
|
||||
}
|
||||
|
||||
if (ruleAsset.type !== existingRule.params.type) {
|
||||
// If we're trying to change the type of a prepackaged rule, we need to delete the old one
|
||||
// and replace it with the new rule, keeping the enabled setting, actions, throttle, id,
|
||||
// and exception lists from the old rule
|
||||
await rulesClient.delete({ id: existingRule.id });
|
||||
|
||||
const internalRule = convertCreateAPIToInternalSchema(
|
||||
{
|
||||
...ruleAsset,
|
||||
enabled: existingRule.enabled,
|
||||
exceptions_list: existingRule.params.exceptionsList,
|
||||
actions: existingRule.actions.map(transformAlertToRuleAction),
|
||||
timeline_id: existingRule.params.timelineId,
|
||||
timeline_title: existingRule.params.timelineTitle,
|
||||
},
|
||||
{ immutable: true, defaultEnabled: existingRule.enabled }
|
||||
);
|
||||
|
||||
return rulesClient.create<RuleParams>({
|
||||
data: internalRule,
|
||||
options: { id: existingRule.id },
|
||||
});
|
||||
}
|
||||
|
||||
// Else, simply patch it.
|
||||
const patchedRule = convertPatchAPIToInternalSchema(ruleAsset, existingRule);
|
||||
|
||||
await rulesClient.update({
|
||||
id: existingRule.id,
|
||||
data: patchedRule,
|
||||
});
|
||||
|
||||
const updatedRule = await readRules({
|
||||
rulesClient,
|
||||
ruleId: ruleAsset.rule_id,
|
||||
id: undefined,
|
||||
});
|
||||
|
||||
if (!updatedRule) {
|
||||
throw new ClientError(`Rule ${ruleAsset.rule_id} not found after upgrade`, 500);
|
||||
}
|
||||
|
||||
return updatedRule;
|
||||
});
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { RulesClient } from '@kbn/alerting-plugin/server';
|
||||
|
||||
import type { Type } from '@kbn/securitysolution-io-ts-alerting-types';
|
||||
import type { MlAuthz } from '../../../../machine_learning/authz';
|
||||
|
||||
import type { RuleAlertType } from '../../../rule_schema';
|
||||
import { throwAuthzError } from '../../../../machine_learning/validation';
|
||||
|
||||
export const toggleRuleEnabledOnUpdate = async (
|
||||
rulesClient: RulesClient,
|
||||
existingRule: RuleAlertType,
|
||||
updatedRuleEnabled?: boolean
|
||||
) => {
|
||||
if (existingRule.enabled && updatedRuleEnabled === false) {
|
||||
await rulesClient.disable({ id: existingRule.id });
|
||||
} else if (!existingRule.enabled && updatedRuleEnabled === true) {
|
||||
await rulesClient.enable({ id: existingRule.id });
|
||||
}
|
||||
};
|
||||
|
||||
export const validateMlAuth = async (mlAuthz: MlAuthz, ruleType: Type) => {
|
||||
throwAuthzError(await mlAuthz.validateRuleType(ruleType));
|
||||
};
|
||||
|
||||
export class ClientError extends Error {
|
||||
public readonly statusCode: number;
|
||||
constructor(message: string, statusCode: number) {
|
||||
super(message);
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
}
|
|
@ -28,7 +28,7 @@ export interface MlAuthz {
|
|||
* @param ml {@link MlPluginSetup} ML services to fetch ML capabilities
|
||||
* @param request A {@link KibanaRequest} representing the authenticated user
|
||||
*
|
||||
* @returns A {@link MLAuthz} service object
|
||||
* @returns A {@link MlAuthz} service object
|
||||
*/
|
||||
export const buildMlAuthz = ({
|
||||
license,
|
||||
|
|
|
@ -29,6 +29,7 @@ import { RiskEngineDataClient } from './lib/entity_analytics/risk_engine/risk_en
|
|||
import { RiskScoreDataClient } from './lib/entity_analytics/risk_score/risk_score_data_client';
|
||||
import { AssetCriticalityDataClient } from './lib/entity_analytics/asset_criticality';
|
||||
import { createDetectionRulesClient } from './lib/detection_engine/rule_management/logic/rule_management/detection_rules_client';
|
||||
import { buildMlAuthz } from './lib/machine_learning/authz';
|
||||
|
||||
export interface IRequestContextFactory {
|
||||
create(
|
||||
|
@ -113,14 +114,19 @@ export class RequestContextFactory implements IRequestContextFactory {
|
|||
|
||||
getAuditLogger,
|
||||
|
||||
getDetectionRulesClient: () =>
|
||||
createDetectionRulesClient(
|
||||
startPlugins.alerting.getRulesClientWithRequest(request),
|
||||
getDetectionRulesClient: () => {
|
||||
const mlAuthz = buildMlAuthz({
|
||||
license: licensing.license,
|
||||
ml: plugins.ml,
|
||||
request,
|
||||
coreContext.savedObjects.client,
|
||||
licensing,
|
||||
plugins.ml
|
||||
),
|
||||
savedObjectsClient: coreContext.savedObjects.client,
|
||||
});
|
||||
|
||||
return createDetectionRulesClient(
|
||||
startPlugins.alerting.getRulesClientWithRequest(request),
|
||||
mlAuthz
|
||||
);
|
||||
},
|
||||
|
||||
getDetectionEngineHealthClient: memoize(() =>
|
||||
ruleMonitoringService.createDetectionEngineHealthClient({
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue