[8.0] [Security Solution] Disable legacy rules on upgrade to 8.x (#121442) (#122695)

* [Security Solution] Disable legacy rules on upgrade to 8.x (#121442)

* Disable legacy rule and notify user to upgrade

* Ensure rules are disabled on upgrade

* Fix dupe detection on upgrade

* Revert "Fix dupe detection on upgrade"

This reverts commit 021ec0fac4.

* Add legacy notification

* Add tests for 8.0 security_solution rule migration

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
(cherry picked from commit 1ddb64758f)

# Conflicts:
#	x-pack/plugins/alerting/server/saved_objects/migrations.ts
#	x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts
#	x-pack/plugins/security_solution/server/plugin.ts

* RawRule should be RawAlert in 8.0
This commit is contained in:
Madison Caldwell 2022-01-11 16:53:27 -05:00 committed by GitHub
parent 9e80a2f6c8
commit ef1b80f43e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 49 additions and 618 deletions

View file

@ -11,6 +11,7 @@ import { RawAlert } from '../types';
import { SavedObjectUnsanitizedDoc } from 'kibana/server';
import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks';
import { migrationMocks } from 'src/core/server/mocks';
import { RuleType, ruleTypeMappings } from '@kbn/securitysolution-rules';
const migrationContext = migrationMocks.createContext();
const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup();
@ -2056,6 +2057,37 @@ describe('successful migrations', () => {
);
});
test('doesnt change AAD rule params if not a siem.signals rule', () => {
const migration800 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.0.0'];
const alert = getMockData(
{ params: { outputIndex: 'output-index', type: 'query' }, alertTypeId: 'not.siem.signals' },
true
);
expect(migration800(alert, migrationContext).attributes.alertTypeId).toEqual(
'not.siem.signals'
);
expect(migration800(alert, migrationContext).attributes.enabled).toEqual(true);
expect(migration800(alert, migrationContext).attributes.params.outputIndex).toEqual(
'output-index'
);
});
test.each(Object.keys(ruleTypeMappings) as RuleType[])(
'Changes AAD rule params accordingly if rule is a siem.signals %p rule',
(ruleType) => {
const migration800 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.0.0'];
const alert = getMockData(
{ params: { outputIndex: 'output-index', type: ruleType }, alertTypeId: 'siem.signals' },
true
);
expect(migration800(alert, migrationContext).attributes.alertTypeId).toEqual(
ruleTypeMappings[ruleType]
);
expect(migration800(alert, migrationContext).attributes.enabled).toEqual(false);
expect(migration800(alert, migrationContext).attributes.params.outputIndex).toEqual('');
}
);
describe('Metrics Inventory Threshold rule', () => {
test('Migrates incorrect action group spelling', () => {
const migration800 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.0.0'];

View file

@ -131,7 +131,7 @@ export function getMigrations(
(doc: SavedObjectUnsanitizedDoc<RawAlert>): doc is SavedObjectUnsanitizedDoc<RawAlert> => true,
pipeMigrations(
addThreatIndicatorPathToThreatMatchRules,
addRACRuleTypes,
addSecuritySolutionAADRuleTypes,
fixInventoryThresholdGroupId
)
);
@ -654,7 +654,7 @@ function setLegacyId(
};
}
function addRACRuleTypes(
function addSecuritySolutionAADRuleTypes(
doc: SavedObjectUnsanitizedDoc<RawAlert>
): SavedObjectUnsanitizedDoc<RawAlert> {
const ruleType = doc.attributes.params.type;
@ -664,6 +664,7 @@ function addRACRuleTypes(
attributes: {
...doc.attributes,
alertTypeId: ruleTypeMappings[ruleType],
enabled: false,
params: {
...doc.attributes.params,
outputIndex: '',

View file

@ -1,599 +0,0 @@
/*
* 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 moment from 'moment';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { loggingSystemMock } from 'src/core/server/mocks';
import { getAlertMock } from '../routes/__mocks__/request_responses';
import { signalRulesAlertType } from './signal_rule_alert_type';
import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks';
import {
getListsClient,
getExceptions,
checkPrivileges,
createSearchAfterReturnType,
} from './utils';
import { parseScheduleDates } from '@kbn/securitysolution-io-ts-utils';
import { RuleExecutorOptions, SearchAfterAndBulkCreateReturnType } from './types';
import { RuleAlertType } from '../rules/types';
import { listMock } from '../../../../../lists/server/mocks';
import { getListClientMock } from '../../../../../lists/server/services/lists/list_client.mock';
import { getExceptionListClientMock } from '../../../../../lists/server/services/exception_lists/exception_list_client.mock';
import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock';
import type { TransportResult } from '@elastic/elasticsearch';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks';
import { queryExecutor } from './executors/query';
import { mlExecutor } from './executors/ml';
import { getMlRuleParams, getQueryRuleParams } from '../schemas/rule_schemas.mock';
import { errors } from '@elastic/elasticsearch';
import { allowedExperimentalValues } from '../../../../common/experimental_features';
import { scheduleNotificationActions } from '../notifications/schedule_notification_actions';
import { ruleExecutionLogClientMock } from '../rule_execution_log/__mocks__/rule_execution_log_client';
import { RuleExecutionStatus } from '../../../../common/detection_engine/schemas/common/schemas';
import { scheduleThrottledNotificationActions } from '../notifications/schedule_throttle_notification_actions';
import { eventLogServiceMock } from '../../../../../event_log/server/mocks';
import { createMockConfig } from '../routes/__mocks__';
jest.mock('./utils', () => {
const original = jest.requireActual('./utils');
return {
...original,
getListsClient: jest.fn(),
getExceptions: jest.fn(),
sortExceptionItems: jest.fn(),
checkPrivileges: jest.fn(),
};
});
jest.mock('../notifications/schedule_notification_actions');
jest.mock('./executors/query');
jest.mock('./executors/ml');
jest.mock('@kbn/securitysolution-io-ts-utils', () => {
const original = jest.requireActual('@kbn/securitysolution-io-ts-utils');
return {
...original,
parseScheduleDates: jest.fn(),
};
});
jest.mock('../notifications/schedule_throttle_notification_actions');
const mockRuleExecutionLogClient = ruleExecutionLogClientMock.create();
jest.mock('../rule_execution_log/rule_execution_log_client', () => ({
RuleExecutionLogClient: jest.fn().mockImplementation(() => mockRuleExecutionLogClient),
}));
const getPayload = (
ruleAlert: RuleAlertType,
services: AlertServicesMock
): RuleExecutorOptions => ({
alertId: ruleAlert.id,
services,
name: ruleAlert.name,
tags: ruleAlert.tags,
params: {
...ruleAlert.params,
},
state: {},
spaceId: '',
startedAt: new Date('2019-12-13T16:50:33.400Z'),
previousStartedAt: new Date('2019-12-13T16:40:33.400Z'),
createdBy: 'elastic',
updatedBy: 'elastic',
rule: {
name: ruleAlert.name,
tags: ruleAlert.tags,
consumer: 'foo',
producer: 'foo',
ruleTypeId: 'ruleType',
ruleTypeName: 'Name of rule',
enabled: true,
schedule: {
interval: '5m',
},
actions: ruleAlert.actions,
createdBy: 'elastic',
updatedBy: 'elastic',
createdAt: new Date('2019-12-13T16:50:33.400Z'),
updatedAt: new Date('2019-12-13T16:50:33.400Z'),
throttle: null,
notifyWhen: null,
},
});
// Deprecated
describe.skip('signal_rule_alert_type', () => {
const version = '8.0.0';
const jobsSummaryMock = jest.fn();
const mlMock = {
mlClient: {
callAsInternalUser: jest.fn(),
close: jest.fn(),
asScoped: jest.fn(),
},
jobServiceProvider: jest.fn().mockReturnValue({
jobsSummary: jobsSummaryMock,
}),
anomalyDetectorsProvider: jest.fn(),
mlSystemProvider: jest.fn(),
modulesProvider: jest.fn(),
resultsServiceProvider: jest.fn(),
alertingServiceProvider: jest.fn(),
};
let payload: jest.Mocked<RuleExecutorOptions>;
let alert: ReturnType<typeof signalRulesAlertType>;
let logger: ReturnType<typeof loggingSystemMock.createLogger>;
let alertServices: AlertServicesMock;
let eventLogService: ReturnType<typeof eventLogServiceMock.create>;
beforeEach(() => {
alertServices = alertsMock.createAlertServices();
logger = loggingSystemMock.createLogger();
eventLogService = eventLogServiceMock.create();
(getListsClient as jest.Mock).mockReturnValue({
listClient: getListClientMock(),
exceptionsClient: getExceptionListClientMock(),
});
(getExceptions as jest.Mock).mockReturnValue([getExceptionListItemSchemaMock()]);
(checkPrivileges as jest.Mock).mockImplementation(async (_, indices) => {
return {
index: indices.reduce(
(acc: { index: { [x: string]: { read: boolean } } }, index: string) => {
return {
[index]: {
read: true,
},
...acc,
};
},
{}
),
};
});
const executorReturnValue = createSearchAfterReturnType({
createdSignalsCount: 10,
});
(queryExecutor as jest.Mock).mockClear();
(queryExecutor as jest.Mock).mockResolvedValue(executorReturnValue);
(mlExecutor as jest.Mock).mockClear();
(mlExecutor as jest.Mock).mockResolvedValue(executorReturnValue);
(parseScheduleDates as jest.Mock).mockReturnValue(moment(100));
const value: Partial<TransportResult<estypes.FieldCapsResponse>> = {
statusCode: 200,
body: {
indices: ['index1', 'index2', 'index3', 'index4'],
fields: {
'@timestamp': {
// @ts-expect-error not full interface
date: {
indices: ['index1', 'index2', 'index3', 'index4'],
searchable: true,
aggregatable: false,
},
},
},
},
};
alertServices.scopedClusterClient.asCurrentUser.fieldCaps.mockResolvedValue(
value as TransportResult<estypes.FieldCapsResponse>
);
const ruleAlert = getAlertMock(false, getQueryRuleParams());
alertServices.savedObjectsClient.get.mockResolvedValue({
id: 'id',
type: 'type',
references: [],
attributes: ruleAlert,
});
payload = getPayload(ruleAlert, alertServices) as jest.Mocked<RuleExecutorOptions>;
alert = signalRulesAlertType({
experimentalFeatures: allowedExperimentalValues,
logger,
eventsTelemetry: undefined,
version,
ml: mlMock,
lists: listMock.createSetup(),
config: createMockConfig(),
eventLogService,
});
mockRuleExecutionLogClient.logStatusChange.mockClear();
(scheduleThrottledNotificationActions as jest.Mock).mockClear();
});
describe('executor', () => {
it('should log success status if signals were created', async () => {
payload.previousStartedAt = null;
await alert.executor(payload);
expect(mockRuleExecutionLogClient.logStatusChange).toHaveBeenLastCalledWith(
expect.objectContaining({
newStatus: RuleExecutionStatus.succeeded,
})
);
});
it('should warn about the gap between runs if gap is very large', async () => {
payload.previousStartedAt = moment(payload.startedAt).subtract(100, 'm').toDate();
await alert.executor(payload);
expect(logger.warn).toHaveBeenCalled();
expect(mockRuleExecutionLogClient.logStatusChange).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
newStatus: RuleExecutionStatus['going to run'],
})
);
expect(mockRuleExecutionLogClient.logStatusChange).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
newStatus: RuleExecutionStatus.failed,
metrics: {
executionGap: expect.any(Object),
},
})
);
});
it('should set a warning for when rules cannot read ALL provided indices', async () => {
(checkPrivileges as jest.Mock).mockResolvedValueOnce({
username: 'elastic',
has_all_requested: false,
cluster: {},
index: {
'myfa*': {
read: true,
},
'anotherindex*': {
read: true,
},
'some*': {
read: false,
},
},
application: {},
});
const newRuleAlert = getAlertMock(false, getQueryRuleParams());
newRuleAlert.params.index = ['some*', 'myfa*', 'anotherindex*'];
payload = getPayload(newRuleAlert, alertServices) as jest.Mocked<RuleExecutorOptions>;
await alert.executor(payload);
expect(mockRuleExecutionLogClient.logStatusChange).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
newStatus: RuleExecutionStatus['partial failure'],
message:
'This rule may not have the required read privileges to the following indices/index patterns: ["some*"]',
})
);
});
it('should set a failure status for when rules cannot read ANY provided indices', async () => {
(checkPrivileges as jest.Mock).mockResolvedValueOnce({
username: 'elastic',
has_all_requested: false,
cluster: {},
index: {
'myfa*': {
read: false,
},
'some*': {
read: false,
},
},
application: {},
});
const newRuleAlert = getAlertMock(false, getQueryRuleParams());
newRuleAlert.params.index = ['some*', 'myfa*', 'anotherindex*'];
payload = getPayload(newRuleAlert, alertServices) as jest.Mocked<RuleExecutorOptions>;
await alert.executor(payload);
expect(mockRuleExecutionLogClient.logStatusChange).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
newStatus: RuleExecutionStatus['partial failure'],
message:
'This rule may not have the required read privileges to the following indices/index patterns: ["myfa*","some*"]',
})
);
});
it('should NOT warn about the gap between runs if gap small', async () => {
payload.previousStartedAt = moment().subtract(10, 'm').toDate();
await alert.executor(payload);
expect(logger.warn).toHaveBeenCalledTimes(0);
expect(mockRuleExecutionLogClient.logStatusChange).toHaveBeenCalledTimes(2);
expect(mockRuleExecutionLogClient.logStatusChange).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
newStatus: RuleExecutionStatus['going to run'],
})
);
expect(mockRuleExecutionLogClient.logStatusChange).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
newStatus: RuleExecutionStatus.succeeded,
})
);
});
it('should call scheduleActions if signalsCount was greater than 0 and rule has actions defined', async () => {
const ruleAlert = getAlertMock(false, getQueryRuleParams());
ruleAlert.actions = [
{
actionTypeId: '.slack',
params: {
message:
'Rule generated {{state.signals_count}} signals\n\n{{context.rule.name}}\n{{{context.results_link}}}',
},
group: 'default',
id: '99403909-ca9b-49ba-9d7a-7e5320e68d05',
},
];
alertServices.savedObjectsClient.get.mockResolvedValue({
id: 'id',
type: 'type',
references: [],
attributes: ruleAlert,
});
await alert.executor(payload);
});
it('should resolve results_link when meta is an empty object to use "/app/security"', async () => {
const ruleAlert = getAlertMock(false, getQueryRuleParams());
ruleAlert.params.meta = {};
ruleAlert.actions = [
{
actionTypeId: '.slack',
params: {
message:
'Rule generated {{state.signals_count}} signals\n\n{{context.rule.name}}\n{{{context.results_link}}}',
},
group: 'default',
id: '99403909-ca9b-49ba-9d7a-7e5320e68d05',
},
];
const modifiedPayload = getPayload(
ruleAlert,
alertServices
) as jest.Mocked<RuleExecutorOptions>;
await alert.executor(modifiedPayload);
expect(scheduleNotificationActions).toHaveBeenCalledWith(
expect.objectContaining({
resultsLink: `/app/security/detections/rules/id/${ruleAlert.id}?timerange=(global:(linkTo:!(timeline),timerange:(from:100,kind:absolute,to:100)),timeline:(linkTo:!(global),timerange:(from:100,kind:absolute,to:100)))`,
})
);
});
it('should resolve results_link when meta is undefined use "/app/security"', async () => {
const ruleAlert = getAlertMock(false, getQueryRuleParams());
delete ruleAlert.params.meta;
ruleAlert.actions = [
{
actionTypeId: '.slack',
params: {
message:
'Rule generated {{state.signals_count}} signals\n\n{{context.rule.name}}\n{{{context.results_link}}}',
},
group: 'default',
id: '99403909-ca9b-49ba-9d7a-7e5320e68d05',
},
];
const modifiedPayload = getPayload(
ruleAlert,
alertServices
) as jest.Mocked<RuleExecutorOptions>;
await alert.executor(modifiedPayload);
expect(scheduleNotificationActions).toHaveBeenCalledWith(
expect.objectContaining({
resultsLink: `/app/security/detections/rules/id/${ruleAlert.id}?timerange=(global:(linkTo:!(timeline),timerange:(from:100,kind:absolute,to:100)),timeline:(linkTo:!(global),timerange:(from:100,kind:absolute,to:100)))`,
})
);
});
it('should resolve results_link with a custom link', async () => {
const ruleAlert = getAlertMock(false, getQueryRuleParams());
ruleAlert.params.meta = { kibana_siem_app_url: 'http://localhost' };
ruleAlert.actions = [
{
actionTypeId: '.slack',
params: {
message:
'Rule generated {{state.signals_count}} signals\n\n{{context.rule.name}}\n{{{context.results_link}}}',
},
group: 'default',
id: '99403909-ca9b-49ba-9d7a-7e5320e68d05',
},
];
const modifiedPayload = getPayload(
ruleAlert,
alertServices
) as jest.Mocked<RuleExecutorOptions>;
await alert.executor(modifiedPayload);
expect(scheduleNotificationActions).toHaveBeenCalledWith(
expect.objectContaining({
resultsLink: `http://localhost/detections/rules/id/${ruleAlert.id}?timerange=(global:(linkTo:!(timeline),timerange:(from:100,kind:absolute,to:100)),timeline:(linkTo:!(global),timerange:(from:100,kind:absolute,to:100)))`,
})
);
});
describe('ML rule', () => {
it('should not call checkPrivileges if ML rule', async () => {
const ruleAlert = getAlertMock(false, getMlRuleParams());
alertServices.savedObjectsClient.get.mockResolvedValue({
id: 'id',
type: 'type',
references: [],
attributes: ruleAlert,
});
payload = getPayload(ruleAlert, alertServices) as jest.Mocked<RuleExecutorOptions>;
payload.previousStartedAt = null;
(checkPrivileges as jest.Mock).mockClear();
await alert.executor(payload);
expect(checkPrivileges).toHaveBeenCalledTimes(0);
expect(mockRuleExecutionLogClient.logStatusChange).toHaveBeenLastCalledWith(
expect.objectContaining({
newStatus: RuleExecutionStatus.succeeded,
})
);
});
});
});
describe('should catch error', () => {
it('when bulk indexing failed', async () => {
const result: SearchAfterAndBulkCreateReturnType = {
success: false,
warning: false,
searchAfterTimes: [],
bulkCreateTimes: [],
lastLookBackDate: null,
createdSignalsCount: 0,
createdSignals: [],
warningMessages: [],
errors: ['Error that bubbled up.'],
};
(queryExecutor as jest.Mock).mockResolvedValue(result);
await alert.executor(payload);
expect(logger.error).toHaveBeenCalled();
expect(logger.error.mock.calls[0][0]).toContain(
'Bulk Indexing of signals failed: Error that bubbled up. name: "Detect Root/Admin Users" id: "04128c15-0d1b-4716-a4c5-46997ac7f3bd" rule id: "rule-1" signals index: ".siem-signals"'
);
expect(mockRuleExecutionLogClient.logStatusChange).toHaveBeenLastCalledWith(
expect.objectContaining({
newStatus: RuleExecutionStatus.failed,
})
);
});
it('when error was thrown', async () => {
(queryExecutor as jest.Mock).mockRejectedValue({});
await alert.executor(payload);
expect(logger.error).toHaveBeenCalled();
expect(logger.error.mock.calls[0][0]).toContain('An error occurred during rule execution');
expect(mockRuleExecutionLogClient.logStatusChange).toHaveBeenLastCalledWith(
expect.objectContaining({
newStatus: RuleExecutionStatus.failed,
})
);
});
it('and log failure with the default message', async () => {
(queryExecutor as jest.Mock).mockReturnValue(
elasticsearchClientMock.createErrorTransportRequestPromise(
new errors.ResponseError(
elasticsearchClientMock.createApiResponse({
statusCode: 400,
body: { error: { type: 'some_error_type' } },
})
)
)
);
await alert.executor(payload);
expect(logger.error).toHaveBeenCalled();
expect(logger.error.mock.calls[0][0]).toContain('An error occurred during rule execution');
expect(mockRuleExecutionLogClient.logStatusChange).toHaveBeenLastCalledWith(
expect.objectContaining({
newStatus: RuleExecutionStatus.failed,
})
);
});
it('should call scheduleThrottledNotificationActions if result is false to prevent the throttle from being reset', async () => {
const result: SearchAfterAndBulkCreateReturnType = {
success: false,
warning: false,
searchAfterTimes: [],
bulkCreateTimes: [],
lastLookBackDate: null,
createdSignalsCount: 0,
createdSignals: [],
warningMessages: [],
errors: ['Error that bubbled up.'],
};
(queryExecutor as jest.Mock).mockResolvedValue(result);
const ruleAlert = getAlertMock(false, getQueryRuleParams());
ruleAlert.throttle = '1h';
const payLoadWithThrottle = getPayload(
ruleAlert,
alertServices
) as jest.Mocked<RuleExecutorOptions>;
payLoadWithThrottle.rule.throttle = '1h';
alertServices.savedObjectsClient.get.mockResolvedValue({
id: 'id',
type: 'type',
references: [],
attributes: ruleAlert,
});
await alert.executor(payLoadWithThrottle);
expect(scheduleThrottledNotificationActions).toHaveBeenCalledTimes(1);
});
it('should NOT call scheduleThrottledNotificationActions if result is false and the throttle is not set', async () => {
const result: SearchAfterAndBulkCreateReturnType = {
success: false,
warning: false,
searchAfterTimes: [],
bulkCreateTimes: [],
lastLookBackDate: null,
createdSignalsCount: 0,
createdSignals: [],
warningMessages: [],
errors: ['Error that bubbled up.'],
};
(queryExecutor as jest.Mock).mockResolvedValue(result);
await alert.executor(payload);
expect(scheduleThrottledNotificationActions).toHaveBeenCalledTimes(0);
});
it('should call scheduleThrottledNotificationActions if an error was thrown to prevent the throttle from being reset', async () => {
(queryExecutor as jest.Mock).mockRejectedValue({});
const ruleAlert = getAlertMock(false, getQueryRuleParams());
ruleAlert.throttle = '1h';
const payLoadWithThrottle = getPayload(
ruleAlert,
alertServices
) as jest.Mocked<RuleExecutorOptions>;
payLoadWithThrottle.rule.throttle = '1h';
alertServices.savedObjectsClient.get.mockResolvedValue({
id: 'id',
type: 'type',
references: [],
attributes: ruleAlert,
});
await alert.executor(payLoadWithThrottle);
expect(scheduleThrottledNotificationActions).toHaveBeenCalledTimes(1);
});
it('should NOT call scheduleThrottledNotificationActions if an error was thrown to prevent the throttle from being reset if throttle is not defined', async () => {
const result: SearchAfterAndBulkCreateReturnType = {
success: false,
warning: false,
searchAfterTimes: [],
bulkCreateTimes: [],
lastLookBackDate: null,
createdSignalsCount: 0,
createdSignals: [],
warningMessages: [],
errors: ['Error that bubbled up.'],
};
(queryExecutor as jest.Mock).mockRejectedValue(result);
await alert.executor(payload);
expect(scheduleThrottledNotificationActions).toHaveBeenCalledTimes(0);
});
});
});

View file

@ -37,8 +37,6 @@ import {
createThresholdAlertType,
} from './lib/detection_engine/rule_types';
import { initRoutes } from './routes';
import { isAlertExecutor } from './lib/detection_engine/signals/types';
import { signalRulesAlertType } from './lib/detection_engine/signals/signal_rule_alert_type';
import { ManifestTask } from './endpoint/lib/artifacts';
import { CheckMetadataTransformsTask } from './endpoint/lib/metadata';
import { initSavedObjects } from './saved_objects';
@ -281,24 +279,9 @@ export class Plugin implements ISecuritySolutionPlugin {
plugins.features.registerKibanaFeature(getKibanaPrivilegesFeaturePrivileges(ruleTypes));
plugins.features.registerKibanaFeature(getCasesKibanaFeature());
// Continue to register legacy rules against alerting client exposed through rule-registry
if (plugins.alerting != null) {
const signalRuleType = signalRulesAlertType({
logger,
eventsTelemetry: this.telemetryEventsSender,
version: pluginContext.env.packageInfo.version,
ml: plugins.ml,
lists: plugins.lists,
config,
experimentalFeatures,
eventLogService,
});
const ruleNotificationType = legacyRulesNotificationAlertType({ logger });
if (isAlertExecutor(signalRuleType)) {
plugins.alerting.registerType(signalRuleType);
}
if (legacyIsNotificationAlertExecutor(ruleNotificationType)) {
plugins.alerting.registerType(ruleNotificationType);
}

View file

@ -360,5 +360,18 @@ export default function createGetTests({ getService }: FtrProviderContext) {
'metrics.inventory_threshold.fired'
);
});
it('8.0 migrates and disables pre-existing rules', async () => {
const response = await es.get<{ alert: RawAlert }>(
{
index: '.kibana',
id: 'alert:38482620-ef1b-11eb-ad71-7de7959be71c',
},
{ meta: true }
);
expect(response.statusCode).to.eql(200);
expect(response.body._source?.alert?.alertTypeId).to.be('siem.queryRule');
expect(response.body._source?.alert?.enabled).to.be(false);
});
});
}

View file

@ -346,6 +346,7 @@
"consumer" : "alertsFixture",
"params" : {
"ruleId" : "4ec223b9-77fa-4895-8539-6b3e586a2858",
"type": "query",
"exceptionsList" : [
{
"id" : "endpoint_list",