diff --git a/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/rules/trial_license_complete_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/rules/trial_license_complete_tier/index.ts index c5d53b96bafb1..e8c70cbd7a7c4 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/rules/trial_license_complete_tier/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/rules/trial_license_complete_tier/index.ts @@ -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')); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/rules/trial_license_complete_tier/install.ts b/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/rules/trial_license_complete_tier/install.ts new file mode 100644 index 0000000000000..684db1939e279 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/rules/trial_license_complete_tier/install.ts @@ -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 => { + 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 => { + 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', + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/utils/mocks.ts b/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/utils/mocks.ts index ee75f3e4a5dd3..184e19f8d0c9c 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/utils/mocks.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/utils/mocks.ts @@ -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 => { let translationResult; diff --git a/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/utils/rules.ts b/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/utils/rules.ts index 07e845805cda2..1ac078b045467 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/utils/rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/utils/rules.ts @@ -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,