Skip to content

Commit 4071c0b

Browse files
[8.x] [Rules migration][Integration test] Install APIs (#11232) (#211339) (#211402)
# Backport This will backport the following commits from `main` to `8.x`: - [[Rules migration][Integration test] Install APIs (#11232) (#211339)](#211339) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Ievgen Sorokopud","email":"[email protected]"},"sourceCommit":{"committedDate":"2025-02-17T10:25:33Z","message":"[Rules migration][Integration test] Install APIs (#11232) (#211339)\n\n## Summary\r\n\r\n[Internal link](https://github.com/elastic/security-team/issues/10820)\r\nto the feature details\r\n\r\nPart of https://github.com/elastic/security-team/issues/11232\r\n\r\nThis PR covers SIEM Migrations Install API (route: `POST\r\n/internal/siem_migrations/rules/{migration_id}/install`) integration\r\ntest:\r\n* install all installable custom migration rules\r\n* install all installable migration rules matched with prebuilt rules\r\n* install and enable all installable migration rules\r\n* install migration rules by ids\r\n* install rules of non-existing migration - nothing should be installed\r\n* Error handling: an error if body payload is not passed","sha":"cd502acea12979979497f62897be663044ade3aa","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:Threat Hunting","Team: SecuritySolution","backport:version","v8.18.0","v9.1.0","v8.19.0"],"title":"[Rules migration][Integration test] Install APIs (#11232)","number":211339,"url":"https://github.com/elastic/kibana/pull/211339","mergeCommit":{"message":"[Rules migration][Integration test] Install APIs (#11232) (#211339)\n\n## Summary\r\n\r\n[Internal link](https://github.com/elastic/security-team/issues/10820)\r\nto the feature details\r\n\r\nPart of https://github.com/elastic/security-team/issues/11232\r\n\r\nThis PR covers SIEM Migrations Install API (route: `POST\r\n/internal/siem_migrations/rules/{migration_id}/install`) integration\r\ntest:\r\n* install all installable custom migration rules\r\n* install all installable migration rules matched with prebuilt rules\r\n* install and enable all installable migration rules\r\n* install migration rules by ids\r\n* install rules of non-existing migration - nothing should be installed\r\n* Error handling: an error if body payload is not passed","sha":"cd502acea12979979497f62897be663044ade3aa"}},"sourceBranch":"main","suggestedTargetBranches":["9.0","8.18","8.x"],"targetPullRequestStates":[{"branch":"9.0","label":"v9.0.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.18","label":"v8.18.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/211339","number":211339,"mergeCommit":{"message":"[Rules migration][Integration test] Install APIs (#11232) (#211339)\n\n## Summary\r\n\r\n[Internal link](https://github.com/elastic/security-team/issues/10820)\r\nto the feature details\r\n\r\nPart of https://github.com/elastic/security-team/issues/11232\r\n\r\nThis PR covers SIEM Migrations Install API (route: `POST\r\n/internal/siem_migrations/rules/{migration_id}/install`) integration\r\ntest:\r\n* install all installable custom migration rules\r\n* install all installable migration rules matched with prebuilt rules\r\n* install and enable all installable migration rules\r\n* install migration rules by ids\r\n* install rules of non-existing migration - nothing should be installed\r\n* Error handling: an error if body payload is not passed","sha":"cd502acea12979979497f62897be663044ade3aa"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Ievgen Sorokopud <[email protected]>
1 parent fdfbb02 commit 4071c0b

File tree

4 files changed

+253
-12
lines changed

4 files changed

+253
-12
lines changed

x-pack/test/security_solution_api_integration/test_suites/siem_migrations/rules/trial_license_complete_tier/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export default function ({ loadTestFile }: FtrProviderContext) {
1010
describe('@ess SecuritySolution SIEM Migrations', () => {
1111
loadTestFile(require.resolve('./create'));
1212
loadTestFile(require.resolve('./get'));
13+
loadTestFile(require.resolve('./install'));
1314
loadTestFile(require.resolve('./stats'));
1415
loadTestFile(require.resolve('./update'));
1516
});
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import expect from 'expect';
9+
import { v4 as uuidv4 } from 'uuid';
10+
import { ElasticRule } from '@kbn/security-solution-plugin/common/siem_migrations/model/rule_migration.gen';
11+
import { RuleTranslationResult } from '@kbn/security-solution-plugin/common/siem_migrations/constants';
12+
import { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine';
13+
import { deleteAllRules } from '../../../../../common/utils/security_solution';
14+
import {
15+
RuleMigrationDocument,
16+
createMigrationRules,
17+
defaultElasticRule,
18+
deleteAllMigrationRules,
19+
getMigrationRuleDocuments,
20+
migrationRulesRouteHelpersFactory,
21+
statsOverrideCallbackFactory,
22+
} from '../../utils';
23+
import { FtrProviderContext } from '../../../../ftr_provider_context';
24+
import {
25+
createPrebuiltRuleAssetSavedObjects,
26+
createRuleAssetSavedObject,
27+
deleteAllPrebuiltRuleAssets,
28+
deleteAllTimelines,
29+
} from '../../../detections_response/utils';
30+
31+
export default ({ getService }: FtrProviderContext) => {
32+
const es = getService('es');
33+
const log = getService('log');
34+
const supertest = getService('supertest');
35+
const securitySolutionApi = getService('securitySolutionApi');
36+
const migrationRulesRoutes = migrationRulesRouteHelpersFactory(supertest);
37+
38+
describe('@ess @serverless @serverlessQA Install API', () => {
39+
beforeEach(async () => {
40+
await deleteAllRules(supertest, log);
41+
await deleteAllTimelines(es, log);
42+
await deleteAllPrebuiltRuleAssets(es, log);
43+
await deleteAllMigrationRules(es);
44+
});
45+
46+
it('should install all installable custom migration rules', async () => {
47+
const migrationId = uuidv4();
48+
49+
const overrideCallback = (index: number): Partial<RuleMigrationDocument> => {
50+
const title = `Rule - ${index}`;
51+
const elasticRule = { ...defaultElasticRule, title };
52+
return {
53+
migration_id: migrationId,
54+
elastic_rule: elasticRule,
55+
translation_result: index < 2 ? RuleTranslationResult.FULL : undefined,
56+
};
57+
};
58+
59+
const migrationRuleDocuments = getMigrationRuleDocuments(5, overrideCallback);
60+
await createMigrationRules(es, migrationRuleDocuments);
61+
62+
const installResponse = await migrationRulesRoutes.install({ migrationId, payload: {} });
63+
expect(installResponse.body).toEqual({ installed: 2 });
64+
65+
// fetch installed migration rules information
66+
const response = await migrationRulesRoutes.get({ migrationId });
67+
const installedMigrationRules = response.body.data.reduce((acc, item) => {
68+
if (item.elastic_rule?.id) {
69+
acc.push(item.elastic_rule);
70+
}
71+
return acc;
72+
}, [] as ElasticRule[]);
73+
expect(installedMigrationRules.length).toEqual(2);
74+
75+
// fetch installed rules
76+
const { body: rulesResponse } = await securitySolutionApi
77+
.findRules({ query: {} })
78+
.expect(200);
79+
80+
const expectedRulesData = expect.arrayContaining(
81+
installedMigrationRules.map((migrationRule) =>
82+
expect.objectContaining({
83+
id: migrationRule.id,
84+
name: migrationRule.title,
85+
})
86+
)
87+
);
88+
89+
expect(rulesResponse.data).toEqual(expectedRulesData);
90+
91+
// Installed rules should be disabled
92+
rulesResponse.data.forEach((rule: RuleResponse) => {
93+
expect(rule.enabled).toEqual(false);
94+
});
95+
});
96+
97+
it('should install all installable migration rules matched with prebuilt rules', async () => {
98+
const ruleAssetSavedObject = createRuleAssetSavedObject({ rule_id: 'rule-1', version: 1 });
99+
await createPrebuiltRuleAssetSavedObjects(es, [ruleAssetSavedObject]);
100+
101+
const migrationId = uuidv4();
102+
103+
const overrideCallback = (index: number): Partial<RuleMigrationDocument> => {
104+
const { query_language: queryLanguage, query, ...rest } = defaultElasticRule;
105+
return {
106+
migration_id: migrationId,
107+
elastic_rule: index < 2 ? { ...rest, prebuilt_rule_id: 'rule-1' } : undefined,
108+
translation_result: index < 2 ? RuleTranslationResult.FULL : undefined,
109+
};
110+
};
111+
const migrationRuleDocuments = getMigrationRuleDocuments(4, overrideCallback);
112+
await createMigrationRules(es, migrationRuleDocuments);
113+
114+
const installResponse = await migrationRulesRoutes.install({ migrationId, payload: {} });
115+
expect(installResponse.body).toEqual({ installed: 2 });
116+
117+
// fetch installed rules
118+
const { body: rulesResponse } = await securitySolutionApi
119+
.findRules({ query: {} })
120+
.expect(200);
121+
122+
const expectedInstalledRules = expect.arrayContaining([
123+
expect.objectContaining(ruleAssetSavedObject['security-rule']),
124+
]);
125+
expect(rulesResponse.data.length).toEqual(1);
126+
expect(rulesResponse.data).toEqual(expectedInstalledRules);
127+
128+
// Installed rules should be disabled
129+
rulesResponse.data.forEach((rule: RuleResponse) => {
130+
expect(rule.enabled).toEqual(false);
131+
});
132+
});
133+
134+
it('should install and enable all installable migration rules', async () => {
135+
const migrationId = uuidv4();
136+
137+
const overrideCallback = statsOverrideCallbackFactory({
138+
migrationId,
139+
completed: 2,
140+
fullyTranslated: 2,
141+
});
142+
const migrationRuleDocuments = getMigrationRuleDocuments(2, overrideCallback);
143+
await createMigrationRules(es, migrationRuleDocuments);
144+
145+
const installResponse = await migrationRulesRoutes.install({
146+
migrationId,
147+
payload: { enabled: true },
148+
});
149+
expect(installResponse.body).toEqual({ installed: 2 });
150+
151+
// fetch installed rules
152+
const { body: rulesResponse } = await securitySolutionApi
153+
.findRules({ query: {} })
154+
.expect(200);
155+
156+
expect(rulesResponse.data.length).toEqual(2);
157+
158+
// Installed rules should be enabled
159+
rulesResponse.data.forEach((rule: RuleResponse) => {
160+
expect(rule.enabled).toEqual(true);
161+
});
162+
});
163+
164+
it('should install migration rules by ids', async () => {
165+
const migrationId = uuidv4();
166+
167+
const overrideCallback = statsOverrideCallbackFactory({
168+
migrationId,
169+
completed: 5,
170+
fullyTranslated: 5,
171+
});
172+
const migrationRuleDocuments = getMigrationRuleDocuments(5, overrideCallback);
173+
const createdDocumentIds = await createMigrationRules(es, migrationRuleDocuments);
174+
175+
// Migration rules to install by ids
176+
const ids = createdDocumentIds.slice(0, 3);
177+
178+
const installResponse = await migrationRulesRoutes.install({
179+
migrationId,
180+
payload: { ids, enabled: true },
181+
});
182+
expect(installResponse.body).toEqual({ installed: 3 });
183+
184+
// fetch installed rules
185+
const { body: rulesResponse } = await securitySolutionApi
186+
.findRules({ query: {} })
187+
.expect(200);
188+
189+
expect(rulesResponse.data.length).toEqual(3);
190+
191+
// Installed rules should be enabled
192+
rulesResponse.data.forEach((rule: RuleResponse) => {
193+
expect(rule.enabled).toEqual(true);
194+
});
195+
});
196+
197+
it('should return zero installed rules as a response for the non-existing migration', async () => {
198+
const migrationId = uuidv4();
199+
const installResponse = await migrationRulesRoutes.install({ migrationId, payload: {} });
200+
expect(installResponse.body).toEqual({ installed: 0 });
201+
});
202+
203+
it('should return an error if body payload is not passed', async () => {
204+
const migrationId = uuidv4();
205+
const installResponse = await migrationRulesRoutes.install({
206+
migrationId,
207+
expectStatusCode: 400,
208+
});
209+
expect(installResponse.body).toEqual({
210+
statusCode: 400,
211+
error: 'Bad Request',
212+
message: '[request body]: Expected object, received null',
213+
});
214+
});
215+
});
216+
};

x-pack/test/security_solution_api_integration/test_suites/siem_migrations/utils/mocks.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -101,20 +101,20 @@ export const getMigrationRuleDocuments = (
101101

102102
export const statsOverrideCallbackFactory = ({
103103
migrationId,
104-
failed,
105-
pending,
106-
processing,
107-
completed,
108-
fullyTranslated,
109-
partiallyTranslated,
104+
failed = 0,
105+
pending = 0,
106+
processing = 0,
107+
completed = 0,
108+
fullyTranslated = 0,
109+
partiallyTranslated = 0,
110110
}: {
111111
migrationId: string;
112-
failed: number;
113-
pending: number;
114-
processing: number;
115-
completed: number;
116-
fullyTranslated: number;
117-
partiallyTranslated: number;
112+
failed?: number;
113+
pending?: number;
114+
processing?: number;
115+
completed?: number;
116+
fullyTranslated?: number;
117+
partiallyTranslated?: number;
118118
}) => {
119119
const overrideCallback = (index: number): Partial<RuleMigrationDocument> => {
120120
let translationResult;

x-pack/test/security_solution_api_integration/test_suites/siem_migrations/utils/rules.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { replaceParams } from '@kbn/openapi-common/shared';
1515
import {
1616
SIEM_RULE_MIGRATIONS_ALL_STATS_PATH,
1717
SIEM_RULE_MIGRATIONS_PATH,
18+
SIEM_RULE_MIGRATION_INSTALL_PATH,
1819
SIEM_RULE_MIGRATION_PATH,
1920
SIEM_RULE_MIGRATION_STATS_PATH,
2021
SIEM_RULE_MIGRATION_TRANSLATION_STATS_PATH,
@@ -25,6 +26,7 @@ import {
2526
GetRuleMigrationRequestQuery,
2627
GetRuleMigrationResponse,
2728
GetRuleMigrationStatsResponse,
29+
InstallMigrationRulesResponse,
2830
UpdateRuleMigrationResponse,
2931
} from '@kbn/security-solution-plugin/common/siem_migrations/model/api/rules/rule_migration.gen';
3032
import { API_VERSIONS } from '@kbn/security-solution-plugin/common/constants';
@@ -58,6 +60,11 @@ export interface UpdateRulesParams extends MigrationRequestParams {
5860
payload?: any;
5961
}
6062

63+
export interface InstallRulesParams extends MigrationRequestParams {
64+
/** Optional payload to send */
65+
payload?: any;
66+
}
67+
6168
export const migrationRulesRouteHelpersFactory = (supertest: SuperTest.Agent) => {
6269
return {
6370
get: async ({
@@ -112,6 +119,23 @@ export const migrationRulesRouteHelpersFactory = (supertest: SuperTest.Agent) =>
112119
return response;
113120
},
114121

122+
install: async ({
123+
migrationId,
124+
payload,
125+
expectStatusCode = 200,
126+
}: InstallRulesParams): Promise<{ body: InstallMigrationRulesResponse }> => {
127+
const response = await supertest
128+
.post(replaceParams(SIEM_RULE_MIGRATION_INSTALL_PATH, { migration_id: migrationId }))
129+
.set('kbn-xsrf', 'true')
130+
.set(ELASTIC_HTTP_VERSION_HEADER, API_VERSIONS.internal.v1)
131+
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
132+
.send(payload);
133+
134+
assertStatusCode(expectStatusCode, response);
135+
136+
return response;
137+
},
138+
115139
stats: async ({
116140
migrationId,
117141
expectStatusCode = 200,

0 commit comments

Comments
 (0)