From 3fa346a2a679d1452f0ae11104236f2633e329cb Mon Sep 17 00:00:00 2001 From: Ievgen Sorokopud Date: Wed, 12 Feb 2025 16:35:08 +0100 Subject: [PATCH] [Rules migration][Integration test] Get migration rules API (#11232) --- .../rules/trial_license_complete_tier/get.ts | 662 +++++++++++++++++- .../siem_migrations/utils/rules.ts | 130 +++- 2 files changed, 749 insertions(+), 43 deletions(-) diff --git a/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/rules/trial_license_complete_tier/get.ts b/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/rules/trial_license_complete_tier/get.ts index 4b7fc75f0bdf9..1fa9575d2df59 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/rules/trial_license_complete_tier/get.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/rules/trial_license_complete_tier/get.ts @@ -8,9 +8,17 @@ import expect from 'expect'; import { v4 as uuidv4 } from 'uuid'; import { + RuleTranslationResult, + SiemMigrationStatus, +} from '@kbn/security-solution-plugin/common/siem_migrations/constants'; +import { + RuleMigrationDocument, createMigrationRules, + defaultElasticRule, + defaultOriginalRule, deleteAllMigrationRules, getMigrationRuleDocument, + getMigrationRuleDocuments, migrationRulesRouteHelpersFactory, } from '../../utils'; import { FtrProviderContext } from '../../../../ftr_provider_context'; @@ -25,18 +33,652 @@ export default ({ getService }: FtrProviderContext) => { await deleteAllMigrationRules(es); }); - it('should fetch existing rules within specified migration', async () => { - // create a document - const migrationId = uuidv4(); - const migrationRuleDocument = getMigrationRuleDocument({ migration_id: migrationId }); - await createMigrationRules(es, [migrationRuleDocument]); + describe('Basic', () => { + it('should fetch existing rules within specified migration', async () => { + // create a document + const migrationId = uuidv4(); + const migrationRuleDocument = getMigrationRuleDocument({ migration_id: migrationId }); + await createMigrationRules(es, [migrationRuleDocument]); + + const { '@timestamp': timestamp, updated_at: updatedAt, ...rest } = migrationRuleDocument; + + // fetch migration rule + const response = await migrationRulesRoutes.get({ migrationId }); + expect(response.body.total).toEqual(1); + expect(response.body.data).toEqual(expect.arrayContaining([expect.objectContaining(rest)])); + }); + }); + + describe('Filtering', () => { + it('should fetch rules filtered by `searchTerm`', async () => { + // create a document + const migrationId = uuidv4(); + + const overrideCallback = (index: number): Partial => { + const title = `${index < 5 ? 'Elastic' : 'Splunk'} rule - ${index}`; + const originalRule = { ...defaultOriginalRule, title }; + const elasticRule = { ...defaultElasticRule, title }; + return { + migration_id: migrationId, + original_rule: originalRule, + elastic_rule: elasticRule, + }; + }; + const migrationRuleDocuments = getMigrationRuleDocuments(10, overrideCallback); + await createMigrationRules(es, migrationRuleDocuments); + + // Search by word `Elastic` + let expectedRuleDocuments = expect.arrayContaining( + migrationRuleDocuments + .slice(0, 5) + .map(({ '@timestamp': timestamp, updated_at: updatedAt, ...rest }) => + expect.objectContaining(rest) + ) + ); + + // fetch migration rules + let response = await migrationRulesRoutes.get({ + migrationId, + filters: { searchTerm: 'Elastic' }, + }); + expect(response.body.total).toEqual(5); + expect(response.body.data).toEqual(expectedRuleDocuments); + + // Search by word `Splunk` + expectedRuleDocuments = expect.arrayContaining( + migrationRuleDocuments + .slice(5) + .map(({ '@timestamp': timestamp, updated_at: updatedAt, ...rest }) => + expect.objectContaining(rest) + ) + ); + + // fetch migration rules + response = await migrationRulesRoutes.get({ + migrationId, + filters: { searchTerm: 'Splunk' }, + }); + expect(response.body.total).toEqual(5); + expect(response.body.data).toEqual(expectedRuleDocuments); + }); + + it('should fetch rules filtered by `ids`', async () => { + // create a document + const migrationId = uuidv4(); + + const migrationRuleDocuments = getMigrationRuleDocuments(10, () => ({ + migration_id: migrationId, + })); + const createdDocumentIds = await createMigrationRules(es, migrationRuleDocuments); + + const expectedIds = createdDocumentIds.slice(0, 3).sort(); + + // fetch migration rules by existing ids + let response = await migrationRulesRoutes.get({ + migrationId, + filters: { ids: expectedIds }, + }); + expect(response.body.total).toEqual(3); + expect(response.body.data.map(({ id }) => id).sort()).toEqual(expectedIds); + + // fetch migration rules by non-existing id + response = await migrationRulesRoutes.get({ + migrationId, + filters: { ids: [uuidv4()] }, + }); + expect(response.body.total).toEqual(0); + }); + + it('should fetch rules filtered by `prebuilt`', async () => { + // create a document + const migrationId = uuidv4(); + + const overrideCallback = (index: number): Partial => { + const prebuiltRuleId = index < 3 ? uuidv4() : undefined; + const elasticRule = { ...defaultElasticRule, prebuilt_rule_id: prebuiltRuleId }; + return { + migration_id: migrationId, + elastic_rule: elasticRule, + }; + }; + + const migrationRuleDocuments = getMigrationRuleDocuments(10, overrideCallback); + await createMigrationRules(es, migrationRuleDocuments); + + // fetch migration rules matched Elastic prebuilt rules + let response = await migrationRulesRoutes.get({ + migrationId, + filters: { prebuilt: true }, + }); + expect(response.body.total).toEqual(3); + + // fetch custom translated migration rules + response = await migrationRulesRoutes.get({ + migrationId, + filters: { prebuilt: false }, + }); + expect(response.body.total).toEqual(7); + }); + + it('should fetch rules filtered by `installed`', async () => { + // create a document + const migrationId = uuidv4(); + + const overrideCallback = (index: number): Partial => { + const installedRuleId = index < 2 ? uuidv4() : undefined; + const elasticRule = { ...defaultElasticRule, id: installedRuleId }; + return { + migration_id: migrationId, + elastic_rule: elasticRule, + }; + }; + + const migrationRuleDocuments = getMigrationRuleDocuments(10, overrideCallback); + await createMigrationRules(es, migrationRuleDocuments); + + // fetch installed migration rules + let response = await migrationRulesRoutes.get({ + migrationId, + filters: { installed: true }, + }); + expect(response.body.total).toEqual(2); + + // fetch non-installed migration rules + response = await migrationRulesRoutes.get({ + migrationId, + filters: { installed: false }, + }); + expect(response.body.total).toEqual(8); + }); + + it('should fetch rules filtered by `failed`', async () => { + // create a document + const migrationId = uuidv4(); + + const overrideCallback = (index: number): Partial => { + const status = index < 4 ? SiemMigrationStatus.FAILED : SiemMigrationStatus.COMPLETED; + return { + migration_id: migrationId, + status, + }; + }; + + const migrationRuleDocuments = getMigrationRuleDocuments(10, overrideCallback); + await createMigrationRules(es, migrationRuleDocuments); + + // fetch failed migration rules + let response = await migrationRulesRoutes.get({ + migrationId, + filters: { failed: true }, + }); + expect(response.body.total).toEqual(4); + + // fetch non-failed migration rules + response = await migrationRulesRoutes.get({ + migrationId, + filters: { failed: false }, + }); + expect(response.body.total).toEqual(6); + }); + + it('should fetch rules filtered by `fullyTranslated`', async () => { + // create a document + const migrationId = uuidv4(); + + const overrideCallback = (index: number): Partial => { + const translationResult = + index < 6 + ? RuleTranslationResult.FULL + : index < 8 + ? RuleTranslationResult.PARTIAL + : RuleTranslationResult.UNTRANSLATABLE; + return { + migration_id: migrationId, + translation_result: translationResult, + }; + }; + + const migrationRuleDocuments = getMigrationRuleDocuments(10, overrideCallback); + await createMigrationRules(es, migrationRuleDocuments); + + // fetch failed migration rules + let response = await migrationRulesRoutes.get({ + migrationId, + filters: { fullyTranslated: true }, + }); + expect(response.body.total).toEqual(6); + + // fetch non-failed migration rules + response = await migrationRulesRoutes.get({ + migrationId, + filters: { fullyTranslated: false }, + }); + expect(response.body.total).toEqual(4); + }); + + it('should fetch rules filtered by `partiallyTranslated`', async () => { + // create a document + const migrationId = uuidv4(); + + const overrideCallback = (index: number): Partial => { + const translationResult = + index < 4 + ? RuleTranslationResult.FULL + : index < 8 + ? RuleTranslationResult.PARTIAL + : RuleTranslationResult.UNTRANSLATABLE; + return { + migration_id: migrationId, + translation_result: translationResult, + }; + }; + + const migrationRuleDocuments = getMigrationRuleDocuments(10, overrideCallback); + await createMigrationRules(es, migrationRuleDocuments); + + // fetch failed migration rules + let response = await migrationRulesRoutes.get({ + migrationId, + filters: { partiallyTranslated: true }, + }); + expect(response.body.total).toEqual(4); + + // fetch non-failed migration rules + response = await migrationRulesRoutes.get({ + migrationId, + filters: { partiallyTranslated: false }, + }); + expect(response.body.total).toEqual(6); + }); + + it('should fetch rules filtered by `untranslatable`', async () => { + // create a document + const migrationId = uuidv4(); + + const overrideCallback = (index: number): Partial => { + const translationResult = + index < 3 + ? RuleTranslationResult.FULL + : index < 5 + ? RuleTranslationResult.PARTIAL + : RuleTranslationResult.UNTRANSLATABLE; + return { + migration_id: migrationId, + translation_result: translationResult, + }; + }; + + const migrationRuleDocuments = getMigrationRuleDocuments(10, overrideCallback); + await createMigrationRules(es, migrationRuleDocuments); + + // fetch failed migration rules + let response = await migrationRulesRoutes.get({ + migrationId, + filters: { untranslatable: true }, + }); + expect(response.body.total).toEqual(5); + + // fetch non-failed migration rules + response = await migrationRulesRoutes.get({ + migrationId, + filters: { untranslatable: false }, + }); + expect(response.body.total).toEqual(5); + }); + }); + + describe('Sorting', () => { + it('should fetch rules sorted by `title`', async () => { + const titles = ['Elastic 1', 'Windows', 'Linux', 'Elastic 2']; + + // create a document + const migrationId = uuidv4(); + + const overrideCallback = (index: number): Partial => { + const title = titles[index]; + const originalRule = { ...defaultOriginalRule, title }; + const elasticRule = { ...defaultElasticRule, title }; + return { + migration_id: migrationId, + original_rule: originalRule, + elastic_rule: elasticRule, + }; + }; + const migrationRuleDocuments = getMigrationRuleDocuments(titles.length, overrideCallback); + await createMigrationRules(es, migrationRuleDocuments); + + // fetch migration rules + let response = await migrationRulesRoutes.get({ + migrationId, + sortField: 'elastic_rule.title', + sortDirection: 'asc', + }); + expect(response.body.data.map((rule) => rule.elastic_rule?.title)).toEqual(titles.sort()); + + // fetch migration rules + response = await migrationRulesRoutes.get({ + migrationId, + sortField: 'elastic_rule.title', + sortDirection: 'desc', + }); + expect(response.body.data.map((rule) => rule.elastic_rule?.title)).toEqual( + titles.sort().reverse() + ); + }); + + it('should fetch rules sorted by `severity`', async () => { + const severities = ['critical', 'low', 'medium', 'low', 'critical']; + + // create a document + const migrationId = uuidv4(); + + const overrideCallback = (index: number): Partial => { + const severity = severities[index]; + const elasticRule = { ...defaultElasticRule, severity }; + return { + migration_id: migrationId, + elastic_rule: elasticRule, + }; + }; + const migrationRuleDocuments = getMigrationRuleDocuments( + severities.length, + overrideCallback + ); + await createMigrationRules(es, migrationRuleDocuments); + + // fetch migration rules + let response = await migrationRulesRoutes.get({ + migrationId, + sortField: 'elastic_rule.severity', + sortDirection: 'asc', + }); + expect(response.body.data.map((rule) => rule.elastic_rule?.severity)).toEqual([ + 'low', + 'low', + 'medium', + 'critical', + 'critical', + ]); + + // fetch migration rules + response = await migrationRulesRoutes.get({ + migrationId, + sortField: 'elastic_rule.severity', + sortDirection: 'desc', + }); + expect(response.body.data.map((rule) => rule.elastic_rule?.severity)).toEqual([ + 'critical', + 'critical', + 'medium', + 'low', + 'low', + ]); + }); + + it('should fetch rules sorted by `risk_score`', async () => { + const riskScores = [55, 0, 100, 23]; + + // create a document + const migrationId = uuidv4(); + + const overrideCallback = (index: number): Partial => { + const riskScore = riskScores[index]; + const elasticRule = { ...defaultElasticRule, risk_score: riskScore }; + return { + migration_id: migrationId, + elastic_rule: elasticRule, + }; + }; + const migrationRuleDocuments = getMigrationRuleDocuments( + riskScores.length, + overrideCallback + ); + await createMigrationRules(es, migrationRuleDocuments); + + // fetch migration rules + let response = await migrationRulesRoutes.get({ + migrationId, + sortField: 'elastic_rule.risk_score', + sortDirection: 'asc', + }); + expect(response.body.data.map((rule) => rule.elastic_rule?.risk_score)).toEqual( + riskScores.sort((a, b) => { + return a - b; + }) + ); + + // fetch migration rules + response = await migrationRulesRoutes.get({ + migrationId, + sortField: 'elastic_rule.risk_score', + sortDirection: 'desc', + }); + expect(response.body.data.map((rule) => rule.elastic_rule?.risk_score)).toEqual( + riskScores + .sort((a, b) => { + return a - b; + }) + .reverse() + ); + }); + + it('should fetch rules sorted by `prebuilt_rule_id`', async () => { + const prebuiltRuleIds = ['rule-1', undefined, undefined, 'rule-2', undefined]; + + // create a document + const migrationId = uuidv4(); + + const overrideCallback = (index: number): Partial => { + const prebuiltRuleId = prebuiltRuleIds[index]; + const elasticRule = { ...defaultElasticRule, prebuilt_rule_id: prebuiltRuleId }; + return { + migration_id: migrationId, + elastic_rule: elasticRule, + }; + }; + const migrationRuleDocuments = getMigrationRuleDocuments( + prebuiltRuleIds.length, + overrideCallback + ); + await createMigrationRules(es, migrationRuleDocuments); + + // fetch migration rules + let response = await migrationRulesRoutes.get({ + migrationId, + sortField: 'elastic_rule.prebuilt_rule_id', + sortDirection: 'asc', + }); + expect(response.body.data.map((rule) => rule.elastic_rule?.prebuilt_rule_id)).toEqual([ + undefined, + undefined, + undefined, + 'rule-1', + 'rule-2', + ]); + + // fetch migration rules + response = await migrationRulesRoutes.get({ + migrationId, + sortField: 'elastic_rule.prebuilt_rule_id', + sortDirection: 'desc', + }); + expect(response.body.data.map((rule) => rule.elastic_rule?.prebuilt_rule_id)).toEqual([ + 'rule-2', + 'rule-1', + undefined, + undefined, + undefined, + ]); + }); + + it('should fetch rules sorted by `translation_result`', async () => { + const translationResults = [ + RuleTranslationResult.UNTRANSLATABLE, + RuleTranslationResult.FULL, + RuleTranslationResult.PARTIAL, + ]; + + // create a document + const migrationId = uuidv4(); + + const overrideCallback = (index: number): Partial => { + return { + migration_id: migrationId, + translation_result: translationResults[index], + }; + }; + const migrationRuleDocuments = getMigrationRuleDocuments( + translationResults.length, + overrideCallback + ); + await createMigrationRules(es, migrationRuleDocuments); + + // fetch migration rules + let response = await migrationRulesRoutes.get({ + migrationId, + sortField: 'translation_result', + sortDirection: 'asc', + }); + expect(response.body.data.map((rule) => rule.translation_result)).toEqual([ + RuleTranslationResult.UNTRANSLATABLE, + RuleTranslationResult.PARTIAL, + RuleTranslationResult.FULL, + ]); + + // fetch migration rules + response = await migrationRulesRoutes.get({ + migrationId, + sortField: 'translation_result', + sortDirection: 'desc', + }); + expect(response.body.data.map((rule) => rule.translation_result)).toEqual([ + RuleTranslationResult.FULL, + RuleTranslationResult.PARTIAL, + RuleTranslationResult.UNTRANSLATABLE, + ]); + }); + + it('should fetch rules sorted by `updated_at`', async () => { + // create a document + const migrationId = uuidv4(); + + // Creating documents separately to have different `update_at` timestamps + await createMigrationRules(es, [getMigrationRuleDocument({ migration_id: migrationId })]); + await createMigrationRules(es, [getMigrationRuleDocument({ migration_id: migrationId })]); + await createMigrationRules(es, [getMigrationRuleDocument({ migration_id: migrationId })]); + await createMigrationRules(es, [getMigrationRuleDocument({ migration_id: migrationId })]); + await createMigrationRules(es, [getMigrationRuleDocument({ migration_id: migrationId })]); + + // fetch migration rules + let response = await migrationRulesRoutes.get({ + migrationId, + sortField: 'updated_at', + sortDirection: 'asc', + }); + const ascSorted = response.body.data.map((rule) => rule.updated_at); + + // fetch migration rules + response = await migrationRulesRoutes.get({ + migrationId, + sortField: 'updated_at', + sortDirection: 'desc', + }); + const descSorted = response.body.data.map((rule) => rule.updated_at); + + expect(ascSorted).toEqual(descSorted.reverse()); + }); + }); + + describe('Pagination', () => { + it('should fetch rules within specific page', async () => { + const titles = Array.from({ length: 50 }, (_, index) => `Migration rule - ${index}`); + + // create a document + const migrationId = uuidv4(); + + const overrideCallback = (index: number): Partial => { + const title = titles[index]; + const originalRule = { ...defaultOriginalRule, title }; + const elasticRule = { ...defaultElasticRule, title }; + return { + migration_id: migrationId, + original_rule: originalRule, + elastic_rule: elasticRule, + }; + }; + const migrationRuleDocuments = getMigrationRuleDocuments(titles.length, overrideCallback); + await createMigrationRules(es, migrationRuleDocuments); + + // fetch migration rules + const response = await migrationRulesRoutes.get({ + migrationId, + page: 3, + perPage: 7, + }); + const start = 3 * 7; + expect(response.body.data.map((rule) => rule.elastic_rule?.title)).toEqual( + titles.slice(start, start + 7) + ); + }); + + it('should fetch rules within very first page if `perPage` is not specified', async () => { + const titles = Array.from({ length: 50 }, (_, index) => `Migration rule - ${index}`); + + // create a document + const migrationId = uuidv4(); + + const overrideCallback = (index: number): Partial => { + const title = titles[index]; + const originalRule = { ...defaultOriginalRule, title }; + const elasticRule = { ...defaultElasticRule, title }; + return { + migration_id: migrationId, + original_rule: originalRule, + elastic_rule: elasticRule, + }; + }; + const migrationRuleDocuments = getMigrationRuleDocuments(titles.length, overrideCallback); + await createMigrationRules(es, migrationRuleDocuments); + + // fetch migration rules + const response = await migrationRulesRoutes.get({ + migrationId, + page: 3, + }); + const defaultSize = 10; + expect(response.body.data.map((rule) => rule.elastic_rule?.title)).toEqual( + titles.slice(0, defaultSize) + ); + }); + + it('should fetch rules within very first page of a specified size if `perPage` is specified', async () => { + const titles = Array.from({ length: 50 }, (_, index) => `Migration rule - ${index}`); + + // create a document + const migrationId = uuidv4(); - const { '@timestamp': timestamp, updated_at: updatedAt, ...rest } = migrationRuleDocument; + const overrideCallback = (index: number): Partial => { + const title = titles[index]; + const originalRule = { ...defaultOriginalRule, title }; + const elasticRule = { ...defaultElasticRule, title }; + return { + migration_id: migrationId, + original_rule: originalRule, + elastic_rule: elasticRule, + }; + }; + const migrationRuleDocuments = getMigrationRuleDocuments(titles.length, overrideCallback); + await createMigrationRules(es, migrationRuleDocuments); - // fetch migration rule - const response = await migrationRulesRoutes.get(migrationId); - expect(response.body.total).toEqual(1); - expect(response.body.data).toEqual(expect.arrayContaining([expect.objectContaining(rest)])); + // fetch migration rules + const response = await migrationRulesRoutes.get({ + migrationId, + perPage: 18, + }); + expect(response.body.data.map((rule) => rule.elastic_rule?.title)).toEqual( + titles.slice(0, 18) + ); + }); }); }); }; 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 25789dd382589..b51cf75255725 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 @@ -10,32 +10,51 @@ import type { Client } from '@elastic/elasticsearch'; import { X_ELASTIC_INTERNAL_ORIGIN_REQUEST } from '@kbn/core-http-common'; import { replaceParams } from '@kbn/openapi-common/shared'; -import { RuleMigration } from '@kbn/security-solution-plugin/common/siem_migrations/model/rule_migration.gen'; +import { + ElasticRule, + OriginalRule, + RuleMigration, +} from '@kbn/security-solution-plugin/common/siem_migrations/model/rule_migration.gen'; import { INDEX_PATTERN as SIEM_MIGRATIONS_INDEX_PATTERN } from '@kbn/security-solution-plugin/server/lib/siem_migrations/rules/data/rule_migrations_data_service'; import { SIEM_RULE_MIGRATION_PATH } from '@kbn/security-solution-plugin/common/siem_migrations/constants'; import { GetRuleMigrationResponse } from '@kbn/security-solution-plugin/common/siem_migrations/model/api/rules/rule_migration.gen'; import { generateAssistantComment } from '@kbn/security-solution-plugin/server/lib/siem_migrations/rules/task/util/comments'; +import { RuleMigrationFilters } from '@kbn/security-solution-plugin/common/siem_migrations/types'; const SIEM_MIGRATIONS_RULES_INDEX_PATTERN = `${SIEM_MIGRATIONS_INDEX_PATTERN}-rules-default`; export type RuleMigrationDocument = Omit; -const migrationRuleDocument: RuleMigrationDocument = { +export const defaultOriginalRule: OriginalRule = { + id: 'https://127.0.0.1:8089/servicesNS/nobody/SA-AccessProtection/saved/searches/Access%20-%20Default%20Account%20Usage%20-%20Rule', + vendor: 'splunk', + title: 'Access - Default Account Usage - Rule', + description: + 'Discovers use of default accounts (such as admin, administrator, etc.). Default accounts have default passwords and are therefore commonly targeted by attackers using brute force attack tools.', + query: + '| from datamodel:"Authentication"."Successful_Default_Authentication" | stats max("_time") as "lastTime",values("tag") as "tag",count by "dest","user","app"', + query_language: 'spl', + annotations: { + mitre_attack: ['T1078'], + }, +}; + +export const defaultElasticRule: ElasticRule = { + severity: 'low', + risk_score: 21, + integration_ids: [''], + query: + 'FROM [indexPattern]\n| STATS lastTime = max(_time), tag = values(tag), count BY dest, user, app', + description: + 'Discovers use of default accounts (such as admin, administrator, etc.). Default accounts have default passwords and are therefore commonly targeted by attackers using brute force attack tools.', + query_language: 'esql', + title: 'Access - Default Account Usage - Rule', +}; + +const defaultMigrationRuleDocument: RuleMigrationDocument = { '@timestamp': '2025-01-13T15:17:43.571Z', migration_id: '25a24356-3aab-401b-a73c-905cb8bf7a6d', - original_rule: { - id: 'https://127.0.0.1:8089/servicesNS/nobody/SA-AccessProtection/saved/searches/Access%20-%20Default%20Account%20Usage%20-%20Rule', - vendor: 'splunk', - title: 'Access - Default Account Usage - Rule', - description: - 'Discovers use of default accounts (such as admin, administrator, etc.). Default accounts have default passwords and are therefore commonly targeted by attackers using brute force attack tools.', - query: - '| from datamodel:"Authentication"."Successful_Default_Authentication" | stats max("_time") as "lastTime",values("tag") as "tag",count by "dest","user","app"', - query_language: 'spl', - annotations: { - mitre_attack: ['T1078'], - }, - }, + original_rule: defaultOriginalRule, status: 'completed', created_by: 'u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0', updated_by: 'u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0', @@ -50,32 +69,34 @@ const migrationRuleDocument: RuleMigrationDocument = { ), ], translation_result: 'partial', - elastic_rule: { - severity: 'low', - risk_score: 21, - integration_ids: [''], - query: - 'FROM [indexPattern]\n| STATS lastTime = max(_time), tag = values(tag), count BY dest, user, app', - description: - 'Discovers use of default accounts (such as admin, administrator, etc.). Default accounts have default passwords and are therefore commonly targeted by attackers using brute force attack tools.', - query_language: 'esql', - title: 'Access - Default Account Usage - Rule', - }, + elastic_rule: defaultElasticRule, }; export const getMigrationRuleDocument = ( overrideParams: Partial ): RuleMigrationDocument => ({ - ...migrationRuleDocument, + ...defaultMigrationRuleDocument, ...overrideParams, }); +export const getMigrationRuleDocuments = ( + count: number, + overrideCallback: (index: number) => Partial +): RuleMigrationDocument[] => { + const docs: RuleMigrationDocument[] = []; + for (let i = 0; i < count; i++) { + const overrideParams = overrideCallback(i); + docs.push(getMigrationRuleDocument(overrideParams)); + } + return docs; +}; + export const createMigrationRules = async ( es: Client, rules: RuleMigrationDocument[] -): Promise => { +): Promise => { const createdAt = new Date().toISOString(); - await es.bulk({ + const res = await es.bulk({ refresh: 'wait_for', operations: rules.flatMap((ruleMigration) => [ { create: { _index: SIEM_MIGRATIONS_RULES_INDEX_PATTERN } }, @@ -86,6 +107,13 @@ export const createMigrationRules = async ( }, ]), }); + const ids = res.items.reduce((acc, item) => { + if (item.create?._id) { + acc.push(item.create._id); + } + return acc; + }, [] as string[]); + return ids; }; export const deleteAllMigrationRules = async (es: Client): Promise => { @@ -109,14 +137,50 @@ const assertStatusCode = (statusCode: number, response: SuperTest.Response) => { } }; +export interface GetRuleMigrationParams { + /** `id` of the migration to get rules documents for */ + migrationId: string; + /** Optional page number to retrieve */ + page?: number; + /** Optional number of documents per page to retrieve */ + perPage?: number; + /** Optional field of the rule migration object to sort results by */ + sortField?: string; + /** Optional direction to sort results by */ + sortDirection?: 'asc' | 'desc'; + /** Optional parameter to filter documents */ + filters?: RuleMigrationFilters; + /** Optional expected status code parameter */ + expectStatusCode?: number; +} + export const migrationRulesRouteHelpersFactory = (supertest: SuperTest.Agent) => { return { - get: async ( - migrationId: string, - expectStatusCode: number = 200 - ): Promise<{ body: GetRuleMigrationResponse }> => { + get: async ({ + migrationId, + page, + perPage, + sortField, + sortDirection, + filters, + expectStatusCode = 200, + }: GetRuleMigrationParams): Promise<{ body: GetRuleMigrationResponse }> => { const response = await supertest .get(replaceParams(SIEM_RULE_MIGRATION_PATH, { migration_id: migrationId })) + .query({ + page, + per_page: perPage, + sort_field: sortField, + sort_direction: sortDirection, + search_term: filters?.searchTerm, + ids: filters?.ids, + is_prebuilt: filters?.prebuilt, + is_installed: filters?.installed, + is_fully_translated: filters?.fullyTranslated, + is_partially_translated: filters?.partiallyTranslated, + is_untranslatable: filters?.untranslatable, + is_failed: filters?.failed, + }) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')