Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Discover] Migrate esQuery searchType param #3

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions x-pack/plugins/alerting/server/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { eventLogMock } from '../../event_log/server/mocks';
import { actionsMock } from '../../actions/server/mocks';
import { dataPluginMock } from '../../../../src/plugins/data/server/mocks';
import { monitoringCollectionMock } from '../../monitoring_collection/server/mocks';
import { PluginSetup as DataPluginSetup } from 'src/plugins/data/server';

const generateAlertingConfig = (): AlertingConfig => ({
healthCheck: {
Expand Down Expand Up @@ -73,6 +74,7 @@ describe('Alerting Plugin', () => {
actions: actionsMock.createSetup(),
statusService: statusServiceMock.createSetupContract(),
monitoringCollection: monitoringCollectionMock.createSetup(),
data: dataPluginMock.createSetupContract() as unknown as DataPluginSetup,
};

let plugin: AlertingPlugin;
Expand Down Expand Up @@ -265,6 +267,7 @@ describe('Alerting Plugin', () => {
actions: actionsMock.createSetup(),
statusService: statusServiceMock.createSetupContract(),
monitoringCollection: monitoringCollectionMock.createSetup(),
data: dataPluginMock.createSetupContract() as unknown as DataPluginSetup,
});

const startContract = plugin.start(coreMock.createStart(), {
Expand Down Expand Up @@ -303,6 +306,7 @@ describe('Alerting Plugin', () => {
actions: actionsMock.createSetup(),
statusService: statusServiceMock.createSetupContract(),
monitoringCollection: monitoringCollectionMock.createSetup(),
data: dataPluginMock.createSetupContract() as unknown as DataPluginSetup,
});

const startContract = plugin.start(coreMock.createStart(), {
Expand Down Expand Up @@ -352,6 +356,7 @@ describe('Alerting Plugin', () => {
actions: actionsMock.createSetup(),
statusService: statusServiceMock.createSetupContract(),
monitoringCollection: monitoringCollectionMock.createSetup(),
data: dataPluginMock.createSetupContract() as unknown as DataPluginSetup,
});

const startContract = plugin.start(coreMock.createStart(), {
Expand Down
8 changes: 7 additions & 1 deletion x-pack/plugins/alerting/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { PublicMethodsOf } from '@kbn/utility-types';
import { BehaviorSubject } from 'rxjs';
import { pick } from 'lodash';
import { UsageCollectionSetup, UsageCounter } from 'src/plugins/usage_collection/server';
import { PluginSetup as DataPluginSetup } from 'src/plugins/data/server';
import { SecurityPluginSetup, SecurityPluginStart } from '../../security/server';
import {
EncryptedSavedObjectsPluginSetup,
Expand Down Expand Up @@ -128,6 +129,7 @@ export interface AlertingPluginsSetup {
eventLog: IEventLogService;
statusService: StatusServiceSetup;
monitoringCollection: MonitoringCollectionSetup;
data: DataPluginSetup;
}

export interface AlertingPluginsStart {
Expand Down Expand Up @@ -235,12 +237,16 @@ export class AlertingPlugin {
// Usage counter for telemetry
this.usageCounter = plugins.usageCollection?.createUsageCounter(ALERTS_FEATURE_ID);

const getSearchSourceMigrations = plugins.data.search.searchSource.getAllMigrations.bind(
plugins.data.search.searchSource
);
setupSavedObjects(
core.savedObjects,
plugins.encryptedSavedObjects,
this.ruleTypeRegistry,
this.logger,
plugins.actions.isPreconfiguredConnector
plugins.actions.isPreconfiguredConnector,
getSearchSourceMigrations
);

initializeApiKeyInvalidator(
Expand Down
11 changes: 8 additions & 3 deletions x-pack/plugins/alerting/server/saved_objects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import type {
SavedObjectsServiceSetup,
SavedObjectsTypeMappingDefinition,
} from 'kibana/server';
import { MigrateFunctionsObject } from 'src/plugins/kibana_utils/common';
import mappings from './mappings.json';
import { getMigrations } from './migrations';
import { getAllMigrations, getMigrations } from './migrations';
import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server';
import { transformRulesForExport } from './transform_rule_for_export';
import { RawRule } from '../types';
Expand Down Expand Up @@ -52,14 +53,18 @@ export function setupSavedObjects(
encryptedSavedObjects: EncryptedSavedObjectsPluginSetup,
ruleTypeRegistry: RuleTypeRegistry,
logger: Logger,
isPreconfigured: (connectorId: string) => boolean
isPreconfigured: (connectorId: string) => boolean,
getSearchSourceMigrations: () => MigrateFunctionsObject
) {
savedObjects.registerType({
name: 'alert',
hidden: true,
namespaceType: 'multiple-isolated',
convertToMultiNamespaceTypeVersion: '8.0.0',
migrations: getMigrations(encryptedSavedObjects, isPreconfigured),
migrations: getAllMigrations(
getSearchSourceMigrations(),
getMigrations(encryptedSavedObjects, isPreconfigured)
),
mappings: mappings.alert as SavedObjectsTypeMappingDefinition,
management: {
displayName: 'rule',
Expand Down
58 changes: 56 additions & 2 deletions x-pack/plugins/alerting/server/saved_objects/migrations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
*/

import uuid from 'uuid';
import { getMigrations, isAnyActionSupportIncidents } from './migrations';
import { getAllMigrations, getMigrations, isAnyActionSupportIncidents } from './migrations';
import { RawRule } from '../types';
import { SavedObjectUnsanitizedDoc } from 'kibana/server';
import { SavedObjectMigrationContext, 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';
Expand Down Expand Up @@ -2251,6 +2251,24 @@ describe('successful migrations', () => {
severity: '60-high',
});
});

test('migrates es_query alert params', () => {
const migration820 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.2.0'];
const alert = getMockData(
{
params: { esQuery: '{ "query": "test-query" }' },
alertTypeId: '.es-query',
},
true
);

const migratedAlert820 = migration820(alert, migrationContext);

expect(migratedAlert820.attributes.params).toEqual({
esQuery: '{ "query": "test-query" }',
searchType: 'esQuery',
});
});
});

describe('Metrics Inventory Threshold rule', () => {
Expand Down Expand Up @@ -2311,6 +2329,42 @@ describe('successful migrations', () => {
});
});

describe('search source migration', () => {
it('should apply migration within es query alert rule', () => {
const esQueryRuleSavedObject = {
attributes: {
params: {
searchConfiguration: {
some: 'prop',
migrated: false,
},
},
},
} as SavedObjectUnsanitizedDoc;

const versionToTest = '9.1.1';
const migrations = getAllMigrations(
{
[versionToTest]: (state) => ({ ...state, migrated: true }),
},
getMigrations(encryptedSavedObjectsSetup, isPreconfigured)
);

expect(
migrations[versionToTest](esQueryRuleSavedObject, {} as SavedObjectMigrationContext)
).toEqual({
attributes: {
params: {
searchConfiguration: {
some: 'prop',
migrated: true,
},
},
},
});
});
});

describe('handles errors during migrations', () => {
beforeEach(() => {
jest.resetAllMocks();
Expand Down
68 changes: 67 additions & 1 deletion x-pack/plugins/alerting/server/saved_objects/migrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { isRuleType, ruleTypeMappings } from '@kbn/securitysolution-rules';
import { isString } from 'lodash/fp';
import { mapValues } from 'lodash';
import {
LogMeta,
SavedObjectMigrationMap,
Expand All @@ -21,7 +22,13 @@ import { RawRule, RawAlertAction, RawRuleExecutionStatus } from '../types';
import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server';
import type { IsMigrationNeededPredicate } from '../../../encrypted_saved_objects/server';
import { extractRefsFromGeoContainmentAlert } from './geo_containment/migrations';
import {
MigrateFunctionsObject,
MigrateFunction,
} from '../../../../../src/plugins/kibana_utils/common';
import { mergeSavedObjectMigrationMaps } from '../../../../../src/core/server';
import { getMappedParams } from '../../server/rules_client/lib/mapped_params_utils';
import { SerializedSearchSourceFields } from '../../../../../src/plugins/data/common';

const SIEM_APP_ID = 'securitySolution';
const SIEM_SERVER_APP_ID = 'siem';
Expand Down Expand Up @@ -59,6 +66,9 @@ export const isAnyActionSupportIncidents = (doc: SavedObjectUnsanitizedDoc<RawRu
export const isSiemSignalsRuleType = (doc: SavedObjectUnsanitizedDoc<RawRule>): boolean =>
doc.attributes.alertTypeId === 'siem.signals';

export const isEsQueryRuleType = (doc: SavedObjectUnsanitizedDoc<RawRule>) =>
doc.attributes.alertTypeId === '.es-query';

export const isDetectionEngineAADRuleType = (doc: SavedObjectUnsanitizedDoc<RawRule>): boolean =>
(Object.values(ruleTypeMappings) as string[]).includes(doc.attributes.alertTypeId);

Expand Down Expand Up @@ -149,7 +159,7 @@ export function getMigrations(
const migrationRules820 = createEsoMigration(
encryptedSavedObjects,
(doc: SavedObjectUnsanitizedDoc<RawRule>): doc is SavedObjectUnsanitizedDoc<RawRule> => true,
pipeMigrations(addMappedParams)
pipeMigrations(addMappedParams, addSearchType)
);

return {
Expand Down Expand Up @@ -690,6 +700,23 @@ function addSecuritySolutionAADRuleTypes(
: doc;
}

function addSearchType(doc: SavedObjectUnsanitizedDoc<RawRule>) {
const searchType = doc.attributes.params.searchType;

return isEsQueryRuleType(doc) && !searchType
? {
...doc,
attributes: {
...doc.attributes,
params: {
...doc.attributes.params,
searchType: 'esQuery',
},
},
}
: doc;
}

function addSecuritySolutionAADRuleTypeTags(
doc: SavedObjectUnsanitizedDoc<RawRule>
): SavedObjectUnsanitizedDoc<RawRule> {
Expand Down Expand Up @@ -869,3 +896,42 @@ function pipeMigrations(...migrations: AlertMigration[]): AlertMigration {
return (doc: SavedObjectUnsanitizedDoc<RawRule>) =>
migrations.reduce((migratedDoc, nextMigration) => nextMigration(migratedDoc), doc);
}

/**
* This creates a migration map that applies search source migrations to legacy es query rules
*/
const getEsQueryAlertSearchSourceMigrations = (searchSourceMigrations: MigrateFunctionsObject) =>
mapValues<MigrateFunctionsObject, MigrateFunction>(
searchSourceMigrations,
(migrate: MigrateFunction<SerializedSearchSourceFields>): MigrateFunction =>
(state) => {
const _state = state as unknown as { attributes: RawRule };

const serializedSearchSource = _state.attributes.params
.searchConfiguration as SerializedSearchSourceFields;

if (!serializedSearchSource) return _state;

return {
..._state,
attributes: {
..._state.attributes,
params: {
..._state.attributes.params,
searchConfiguration: migrate(serializedSearchSource),
},
},
};
}
);

export const getAllMigrations = (
searchSourceMigrations: MigrateFunctionsObject,
alertingSavedObjectTypeMigrations: SavedObjectMigrationMap
): SavedObjectMigrationMap =>
mergeSavedObjectMigrationMaps(
alertingSavedObjectTypeMigrations,
getEsQueryAlertSearchSourceMigrations(
searchSourceMigrations
) as unknown as SavedObjectMigrationMap
);
Original file line number Diff line number Diff line change
Expand Up @@ -423,5 +423,17 @@ export default function createGetTests({ getService }: FtrProviderContext) {
severity: '80-critical',
});
});

it('8.2.0 migrates existing esQuery alerts to contain searchType param', async () => {
const response = await es.get<{ alert: RawRule }>(
{
index: '.kibana',
id: 'alert:776cb5c0-ad1e-11ec-ab9e-5f5932f4fad8',
},
{ meta: true }
);
expect(response.statusCode).to.equal(200);
expect(response.body._source?.alert?.params.searchType).to.eql('esQuery');
});
});
}
51 changes: 51 additions & 0 deletions x-pack/test/functional/es_archives/alerts/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -895,4 +895,55 @@
]
}
}
}

{
"type": "doc",
"value": {
"id": "alert:776cb5c0-ad1e-11ec-ab9e-5f5932f4fad8",
"index": ".kibana_1",
"source": {
"alert": {
"name": "123",
"alertTypeId": ".es-query",
"consumer": "alerts",
"params": {
"esQuery": "{\n \"query\":{\n \"match_all\" : {}\n }\n}",
"size": 100,
"timeWindowSize": 5,
"timeWindowUnit": "m",
"threshold": [
1000
],
"thresholdComparator": ">",
"index": [
"kibana_sample_data_ecommerce"
],
"timeField": "order_date"
},
"schedule": {
"interval": "1m"
},
"enabled": true,
"actions": [
],
"throttle": null,
"apiKeyOwner": null,
"createdBy" : "elastic",
"updatedBy" : "elastic",
"createdAt": "2022-03-26T16:04:50.698Z",
"muteAll": false,
"mutedInstanceIds": [],
"scheduledTaskId": "776cb5c0-ad1e-11ec-ab9e-5f5932f4fad8",
"tags": []
},
"type": "alert",
"updated_at": "2022-03-26T16:05:55.957Z",
"migrationVersion": {
"alert": "8.0.1"
},
"references": [
]
}
}
}