[Rules migration][Integration test] Install APIs (#11232) (#211339)

## Summary

[Internal link](https://github.com/elastic/security-team/issues/10820)
to the feature details

Part of https://github.com/elastic/security-team/issues/11232

This PR covers SIEM Migrations Install API (route: `POST
/internal/siem_migrations/rules/{migration_id}/install`) integration
test:
* install all installable custom migration rules
* install all installable migration rules matched with prebuilt rules
* install and enable all installable migration rules
* install migration rules by ids
* install rules of non-existing migration - nothing should be installed
* Error handling: an error if body payload is not passed
This commit is contained in:
Ievgen Sorokopud 2025-02-17 11:25:33 +01:00 committed by GitHub
parent 0ae28aa8bc
commit cd502acea1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 253 additions and 12 deletions

View file

@ -10,6 +10,7 @@ export default function ({ loadTestFile }: FtrProviderContext) {
describe('@ess SecuritySolution SIEM Migrations', () => {
loadTestFile(require.resolve('./create'));
loadTestFile(require.resolve('./get'));
loadTestFile(require.resolve('./install'));
loadTestFile(require.resolve('./stats'));
loadTestFile(require.resolve('./update'));
});

View file

@ -0,0 +1,216 @@
/*
* 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 expect from 'expect';
import { v4 as uuidv4 } from 'uuid';
import { ElasticRule } from '@kbn/security-solution-plugin/common/siem_migrations/model/rule_migration.gen';
import { RuleTranslationResult } from '@kbn/security-solution-plugin/common/siem_migrations/constants';
import { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine';
import { deleteAllRules } from '../../../../../common/utils/security_solution';
import {
RuleMigrationDocument,
createMigrationRules,
defaultElasticRule,
deleteAllMigrationRules,
getMigrationRuleDocuments,
migrationRulesRouteHelpersFactory,
statsOverrideCallbackFactory,
} from '../../utils';
import { FtrProviderContext } from '../../../../ftr_provider_context';
import {
createPrebuiltRuleAssetSavedObjects,
createRuleAssetSavedObject,
deleteAllPrebuiltRuleAssets,
deleteAllTimelines,
} from '../../../detections_response/utils';
export default ({ getService }: FtrProviderContext) => {
const es = getService('es');
const log = getService('log');
const supertest = getService('supertest');
const securitySolutionApi = getService('securitySolutionApi');
const migrationRulesRoutes = migrationRulesRouteHelpersFactory(supertest);
describe('@ess @serverless @serverlessQA Install API', () => {
beforeEach(async () => {
await deleteAllRules(supertest, log);
await deleteAllTimelines(es, log);
await deleteAllPrebuiltRuleAssets(es, log);
await deleteAllMigrationRules(es);
});
it('should install all installable custom migration rules', async () => {
const migrationId = uuidv4();
const overrideCallback = (index: number): Partial<RuleMigrationDocument> => {
const title = `Rule - ${index}`;
const elasticRule = { ...defaultElasticRule, title };
return {
migration_id: migrationId,
elastic_rule: elasticRule,
translation_result: index < 2 ? RuleTranslationResult.FULL : undefined,
};
};
const migrationRuleDocuments = getMigrationRuleDocuments(5, overrideCallback);
await createMigrationRules(es, migrationRuleDocuments);
const installResponse = await migrationRulesRoutes.install({ migrationId, payload: {} });
expect(installResponse.body).toEqual({ installed: 2 });
// fetch installed migration rules information
const response = await migrationRulesRoutes.get({ migrationId });
const installedMigrationRules = response.body.data.reduce((acc, item) => {
if (item.elastic_rule?.id) {
acc.push(item.elastic_rule);
}
return acc;
}, [] as ElasticRule[]);
expect(installedMigrationRules.length).toEqual(2);
// fetch installed rules
const { body: rulesResponse } = await securitySolutionApi
.findRules({ query: {} })
.expect(200);
const expectedRulesData = expect.arrayContaining(
installedMigrationRules.map((migrationRule) =>
expect.objectContaining({
id: migrationRule.id,
name: migrationRule.title,
})
)
);
expect(rulesResponse.data).toEqual(expectedRulesData);
// Installed rules should be disabled
rulesResponse.data.forEach((rule: RuleResponse) => {
expect(rule.enabled).toEqual(false);
});
});
it('should install all installable migration rules matched with prebuilt rules', async () => {
const ruleAssetSavedObject = createRuleAssetSavedObject({ rule_id: 'rule-1', version: 1 });
await createPrebuiltRuleAssetSavedObjects(es, [ruleAssetSavedObject]);
const migrationId = uuidv4();
const overrideCallback = (index: number): Partial<RuleMigrationDocument> => {
const { query_language: queryLanguage, query, ...rest } = defaultElasticRule;
return {
migration_id: migrationId,
elastic_rule: index < 2 ? { ...rest, prebuilt_rule_id: 'rule-1' } : undefined,
translation_result: index < 2 ? RuleTranslationResult.FULL : undefined,
};
};
const migrationRuleDocuments = getMigrationRuleDocuments(4, overrideCallback);
await createMigrationRules(es, migrationRuleDocuments);
const installResponse = await migrationRulesRoutes.install({ migrationId, payload: {} });
expect(installResponse.body).toEqual({ installed: 2 });
// fetch installed rules
const { body: rulesResponse } = await securitySolutionApi
.findRules({ query: {} })
.expect(200);
const expectedInstalledRules = expect.arrayContaining([
expect.objectContaining(ruleAssetSavedObject['security-rule']),
]);
expect(rulesResponse.data.length).toEqual(1);
expect(rulesResponse.data).toEqual(expectedInstalledRules);
// Installed rules should be disabled
rulesResponse.data.forEach((rule: RuleResponse) => {
expect(rule.enabled).toEqual(false);
});
});
it('should install and enable all installable migration rules', async () => {
const migrationId = uuidv4();
const overrideCallback = statsOverrideCallbackFactory({
migrationId,
completed: 2,
fullyTranslated: 2,
});
const migrationRuleDocuments = getMigrationRuleDocuments(2, overrideCallback);
await createMigrationRules(es, migrationRuleDocuments);
const installResponse = await migrationRulesRoutes.install({
migrationId,
payload: { enabled: true },
});
expect(installResponse.body).toEqual({ installed: 2 });
// fetch installed rules
const { body: rulesResponse } = await securitySolutionApi
.findRules({ query: {} })
.expect(200);
expect(rulesResponse.data.length).toEqual(2);
// Installed rules should be enabled
rulesResponse.data.forEach((rule: RuleResponse) => {
expect(rule.enabled).toEqual(true);
});
});
it('should install migration rules by ids', async () => {
const migrationId = uuidv4();
const overrideCallback = statsOverrideCallbackFactory({
migrationId,
completed: 5,
fullyTranslated: 5,
});
const migrationRuleDocuments = getMigrationRuleDocuments(5, overrideCallback);
const createdDocumentIds = await createMigrationRules(es, migrationRuleDocuments);
// Migration rules to install by ids
const ids = createdDocumentIds.slice(0, 3);
const installResponse = await migrationRulesRoutes.install({
migrationId,
payload: { ids, enabled: true },
});
expect(installResponse.body).toEqual({ installed: 3 });
// fetch installed rules
const { body: rulesResponse } = await securitySolutionApi
.findRules({ query: {} })
.expect(200);
expect(rulesResponse.data.length).toEqual(3);
// Installed rules should be enabled
rulesResponse.data.forEach((rule: RuleResponse) => {
expect(rule.enabled).toEqual(true);
});
});
it('should return zero installed rules as a response for the non-existing migration', async () => {
const migrationId = uuidv4();
const installResponse = await migrationRulesRoutes.install({ migrationId, payload: {} });
expect(installResponse.body).toEqual({ installed: 0 });
});
it('should return an error if body payload is not passed', async () => {
const migrationId = uuidv4();
const installResponse = await migrationRulesRoutes.install({
migrationId,
expectStatusCode: 400,
});
expect(installResponse.body).toEqual({
statusCode: 400,
error: 'Bad Request',
message: '[request body]: Expected object, received null',
});
});
});
};

View file

@ -101,20 +101,20 @@ export const getMigrationRuleDocuments = (
export const statsOverrideCallbackFactory = ({
migrationId,
failed,
pending,
processing,
completed,
fullyTranslated,
partiallyTranslated,
failed = 0,
pending = 0,
processing = 0,
completed = 0,
fullyTranslated = 0,
partiallyTranslated = 0,
}: {
migrationId: string;
failed: number;
pending: number;
processing: number;
completed: number;
fullyTranslated: number;
partiallyTranslated: number;
failed?: number;
pending?: number;
processing?: number;
completed?: number;
fullyTranslated?: number;
partiallyTranslated?: number;
}) => {
const overrideCallback = (index: number): Partial<RuleMigrationDocument> => {
let translationResult;

View file

@ -15,6 +15,7 @@ import { replaceParams } from '@kbn/openapi-common/shared';
import {
SIEM_RULE_MIGRATIONS_ALL_STATS_PATH,
SIEM_RULE_MIGRATIONS_PATH,
SIEM_RULE_MIGRATION_INSTALL_PATH,
SIEM_RULE_MIGRATION_PATH,
SIEM_RULE_MIGRATION_STATS_PATH,
SIEM_RULE_MIGRATION_TRANSLATION_STATS_PATH,
@ -25,6 +26,7 @@ import {
GetRuleMigrationRequestQuery,
GetRuleMigrationResponse,
GetRuleMigrationStatsResponse,
InstallMigrationRulesResponse,
UpdateRuleMigrationResponse,
} from '@kbn/security-solution-plugin/common/siem_migrations/model/api/rules/rule_migration.gen';
import { API_VERSIONS } from '@kbn/security-solution-plugin/common/constants';
@ -58,6 +60,11 @@ export interface UpdateRulesParams extends MigrationRequestParams {
payload?: any;
}
export interface InstallRulesParams extends MigrationRequestParams {
/** Optional payload to send */
payload?: any;
}
export const migrationRulesRouteHelpersFactory = (supertest: SuperTest.Agent) => {
return {
get: async ({
@ -112,6 +119,23 @@ export const migrationRulesRouteHelpersFactory = (supertest: SuperTest.Agent) =>
return response;
},
install: async ({
migrationId,
payload,
expectStatusCode = 200,
}: InstallRulesParams): Promise<{ body: InstallMigrationRulesResponse }> => {
const response = await supertest
.post(replaceParams(SIEM_RULE_MIGRATION_INSTALL_PATH, { migration_id: migrationId }))
.set('kbn-xsrf', 'true')
.set(ELASTIC_HTTP_VERSION_HEADER, API_VERSIONS.internal.v1)
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send(payload);
assertStatusCode(expectStatusCode, response);
return response;
},
stats: async ({
migrationId,
expectStatusCode = 200,