mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
## 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 CREATE API (route: `/internal/siem_migrations/rules/{migration_id?}`) integration test: * Create migration with provided ID * Create migration without provided ID * Create migration with rules that have resources * Error handling ("no content") when no rules provided Also, as part of this PR, I addressed this comment to my previous changes https://github.com/elastic/kibana/pull/210867#discussion_r1954344990
This commit is contained in:
parent
4fe72b62b1
commit
44fdf81bbe
9 changed files with 395 additions and 206 deletions
|
@ -41,11 +41,16 @@ export class RuleMigrationsDataBaseClient {
|
|||
return this.currentUser.profile_uid;
|
||||
}
|
||||
const username = this.currentUser.username;
|
||||
const users = await this.esScopedClient.asCurrentUser.security.getUser({
|
||||
username,
|
||||
with_profile_uid: true,
|
||||
});
|
||||
return users[username].profile_uid;
|
||||
try {
|
||||
const users = await this.esScopedClient.asCurrentUser.security.getUser({
|
||||
username,
|
||||
with_profile_uid: true,
|
||||
});
|
||||
return users[username].profile_uid;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error getting profile_uid for user ${username}: ${error}`);
|
||||
return username;
|
||||
}
|
||||
}
|
||||
|
||||
protected processResponseHits<T extends object>(
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* 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 { SiemMigrationStatus } from '@kbn/security-solution-plugin/common/siem_migrations/constants';
|
||||
import {
|
||||
defaultOriginalRule,
|
||||
deleteAllMigrationRules,
|
||||
migrationResourcesRouteHelpersFactory,
|
||||
migrationRulesRouteHelpersFactory,
|
||||
splunkRuleWithResources,
|
||||
} from '../../utils';
|
||||
import { FtrProviderContext } from '../../../../ftr_provider_context';
|
||||
|
||||
export default ({ getService }: FtrProviderContext) => {
|
||||
const es = getService('es');
|
||||
const supertest = getService('supertest');
|
||||
const migrationRulesRoutes = migrationRulesRouteHelpersFactory(supertest);
|
||||
const migrationResourcesRoutes = migrationResourcesRouteHelpersFactory(supertest);
|
||||
|
||||
describe('@ess @serverless @serverlessQA Create API', () => {
|
||||
beforeEach(async () => {
|
||||
await deleteAllMigrationRules(es);
|
||||
});
|
||||
|
||||
it('should create migrations with provided id', async () => {
|
||||
const migrationId = uuidv4();
|
||||
await migrationRulesRoutes.create({ migrationId, body: [defaultOriginalRule] });
|
||||
|
||||
// fetch migration rule
|
||||
const response = await migrationRulesRoutes.get({ migrationId });
|
||||
expect(response.body.total).toEqual(1);
|
||||
|
||||
const migrationRule = response.body.data[0];
|
||||
expect(migrationRule).toEqual(
|
||||
expect.objectContaining({
|
||||
migration_id: migrationId,
|
||||
original_rule: defaultOriginalRule,
|
||||
status: SiemMigrationStatus.PENDING,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should create migrations without provided id', async () => {
|
||||
const {
|
||||
body: { migration_id: migrationId },
|
||||
} = await migrationRulesRoutes.create({ body: [defaultOriginalRule] });
|
||||
|
||||
// fetch migration rule
|
||||
const response = await migrationRulesRoutes.get({ migrationId });
|
||||
expect(response.body.total).toEqual(1);
|
||||
|
||||
const migrationRule = response.body.data[0];
|
||||
expect(migrationRule).toEqual(
|
||||
expect.objectContaining({
|
||||
migration_id: migrationId,
|
||||
original_rule: defaultOriginalRule,
|
||||
status: SiemMigrationStatus.PENDING,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should create migrations with the rules that have resources', async () => {
|
||||
const migrationId = uuidv4();
|
||||
await migrationRulesRoutes.create({ migrationId, body: [splunkRuleWithResources] });
|
||||
|
||||
// fetch migration rule
|
||||
const response = await migrationRulesRoutes.get({ migrationId });
|
||||
expect(response.body.total).toEqual(1);
|
||||
|
||||
const migrationRule = response.body.data[0];
|
||||
expect(migrationRule).toEqual(
|
||||
expect.objectContaining({
|
||||
migration_id: migrationId,
|
||||
original_rule: splunkRuleWithResources,
|
||||
status: SiemMigrationStatus.PENDING,
|
||||
})
|
||||
);
|
||||
|
||||
// fetch missing resources
|
||||
const resourcesResponse = await migrationResourcesRoutes.getMissingResources({
|
||||
migrationId,
|
||||
});
|
||||
expect(resourcesResponse.body).toEqual([
|
||||
{ type: 'macro', name: 'summariesonly' },
|
||||
{ type: 'macro', name: 'drop_dm_object_name(1)' },
|
||||
{ type: 'lookup', name: 'malware_tracker' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return no content error', async () => {
|
||||
const migrationId = uuidv4();
|
||||
await migrationRulesRoutes.create({ migrationId, body: [], expectStatusCode: 204 });
|
||||
|
||||
// fetch migration rule
|
||||
const response = await migrationRulesRoutes.get({ migrationId });
|
||||
expect(response.body.total).toEqual(0);
|
||||
});
|
||||
});
|
||||
};
|
|
@ -79,7 +79,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
// fetch migration rules
|
||||
let response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
filters: { searchTerm: 'Elastic' },
|
||||
queryParams: { search_term: 'Elastic' },
|
||||
});
|
||||
expect(response.body.total).toEqual(5);
|
||||
expect(response.body.data).toEqual(expectedRuleDocuments);
|
||||
|
@ -96,7 +96,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
// fetch migration rules
|
||||
response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
filters: { searchTerm: 'Splunk' },
|
||||
queryParams: { search_term: 'Splunk' },
|
||||
});
|
||||
expect(response.body.total).toEqual(5);
|
||||
expect(response.body.data).toEqual(expectedRuleDocuments);
|
||||
|
@ -116,7 +116,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
// fetch migration rules by existing ids
|
||||
let response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
filters: { ids: expectedIds },
|
||||
queryParams: { ids: expectedIds },
|
||||
});
|
||||
expect(response.body.total).toEqual(3);
|
||||
expect(response.body.data.map(({ id }) => id).sort()).toEqual(expectedIds);
|
||||
|
@ -124,7 +124,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
// fetch migration rules by non-existing id
|
||||
response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
filters: { ids: [uuidv4()] },
|
||||
queryParams: { ids: [uuidv4()] },
|
||||
});
|
||||
expect(response.body.total).toEqual(0);
|
||||
});
|
||||
|
@ -148,14 +148,14 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
// fetch migration rules matched Elastic prebuilt rules
|
||||
let response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
filters: { prebuilt: true },
|
||||
queryParams: { is_prebuilt: true },
|
||||
});
|
||||
expect(response.body.total).toEqual(3);
|
||||
|
||||
// fetch custom translated migration rules
|
||||
response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
filters: { prebuilt: false },
|
||||
queryParams: { is_prebuilt: false },
|
||||
});
|
||||
expect(response.body.total).toEqual(7);
|
||||
});
|
||||
|
@ -179,14 +179,14 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
// fetch installed migration rules
|
||||
let response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
filters: { installed: true },
|
||||
queryParams: { is_installed: true },
|
||||
});
|
||||
expect(response.body.total).toEqual(2);
|
||||
|
||||
// fetch non-installed migration rules
|
||||
response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
filters: { installed: false },
|
||||
queryParams: { is_installed: false },
|
||||
});
|
||||
expect(response.body.total).toEqual(8);
|
||||
});
|
||||
|
@ -209,14 +209,14 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
// fetch failed migration rules
|
||||
let response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
filters: { failed: true },
|
||||
queryParams: { is_failed: true },
|
||||
});
|
||||
expect(response.body.total).toEqual(4);
|
||||
|
||||
// fetch non-failed migration rules
|
||||
response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
filters: { failed: false },
|
||||
queryParams: { is_failed: false },
|
||||
});
|
||||
expect(response.body.total).toEqual(6);
|
||||
});
|
||||
|
@ -244,14 +244,14 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
// fetch failed migration rules
|
||||
let response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
filters: { fullyTranslated: true },
|
||||
queryParams: { is_fully_translated: true },
|
||||
});
|
||||
expect(response.body.total).toEqual(6);
|
||||
|
||||
// fetch non-failed migration rules
|
||||
response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
filters: { fullyTranslated: false },
|
||||
queryParams: { is_fully_translated: false },
|
||||
});
|
||||
expect(response.body.total).toEqual(4);
|
||||
});
|
||||
|
@ -279,14 +279,14 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
// fetch failed migration rules
|
||||
let response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
filters: { partiallyTranslated: true },
|
||||
queryParams: { is_partially_translated: true },
|
||||
});
|
||||
expect(response.body.total).toEqual(4);
|
||||
|
||||
// fetch non-failed migration rules
|
||||
response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
filters: { partiallyTranslated: false },
|
||||
queryParams: { is_partially_translated: false },
|
||||
});
|
||||
expect(response.body.total).toEqual(6);
|
||||
});
|
||||
|
@ -314,14 +314,14 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
// fetch failed migration rules
|
||||
let response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
filters: { untranslatable: true },
|
||||
queryParams: { is_untranslatable: true },
|
||||
});
|
||||
expect(response.body.total).toEqual(5);
|
||||
|
||||
// fetch non-failed migration rules
|
||||
response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
filters: { untranslatable: false },
|
||||
queryParams: { is_untranslatable: false },
|
||||
});
|
||||
expect(response.body.total).toEqual(5);
|
||||
});
|
||||
|
@ -350,16 +350,14 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
// fetch migration rules
|
||||
let response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
sortField: 'elastic_rule.title',
|
||||
sortDirection: 'asc',
|
||||
queryParams: { sort_field: 'elastic_rule.title', sort_direction: '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',
|
||||
queryParams: { sort_field: 'elastic_rule.title', sort_direction: 'desc' },
|
||||
});
|
||||
expect(response.body.data.map((rule) => rule.elastic_rule?.title)).toEqual(
|
||||
titles.sort().reverse()
|
||||
|
@ -389,8 +387,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
// fetch migration rules
|
||||
let response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
sortField: 'elastic_rule.severity',
|
||||
sortDirection: 'asc',
|
||||
queryParams: { sort_field: 'elastic_rule.severity', sort_direction: 'asc' },
|
||||
});
|
||||
expect(response.body.data.map((rule) => rule.elastic_rule?.severity)).toEqual([
|
||||
'low',
|
||||
|
@ -403,8 +400,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
// fetch migration rules
|
||||
response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
sortField: 'elastic_rule.severity',
|
||||
sortDirection: 'desc',
|
||||
queryParams: { sort_field: 'elastic_rule.severity', sort_direction: 'desc' },
|
||||
});
|
||||
expect(response.body.data.map((rule) => rule.elastic_rule?.severity)).toEqual([
|
||||
'critical',
|
||||
|
@ -438,8 +434,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
// fetch migration rules
|
||||
let response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
sortField: 'elastic_rule.risk_score',
|
||||
sortDirection: 'asc',
|
||||
queryParams: { sort_field: 'elastic_rule.risk_score', sort_direction: 'asc' },
|
||||
});
|
||||
expect(response.body.data.map((rule) => rule.elastic_rule?.risk_score)).toEqual(
|
||||
riskScores.sort((a, b) => {
|
||||
|
@ -450,8 +445,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
// fetch migration rules
|
||||
response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
sortField: 'elastic_rule.risk_score',
|
||||
sortDirection: 'desc',
|
||||
queryParams: { sort_field: 'elastic_rule.risk_score', sort_direction: 'desc' },
|
||||
});
|
||||
expect(response.body.data.map((rule) => rule.elastic_rule?.risk_score)).toEqual(
|
||||
riskScores
|
||||
|
@ -485,8 +479,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
// fetch migration rules
|
||||
let response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
sortField: 'elastic_rule.prebuilt_rule_id',
|
||||
sortDirection: 'asc',
|
||||
queryParams: { sort_field: 'elastic_rule.prebuilt_rule_id', sort_direction: 'asc' },
|
||||
});
|
||||
expect(response.body.data.map((rule) => rule.elastic_rule?.prebuilt_rule_id)).toEqual([
|
||||
undefined,
|
||||
|
@ -499,8 +492,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
// fetch migration rules
|
||||
response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
sortField: 'elastic_rule.prebuilt_rule_id',
|
||||
sortDirection: 'desc',
|
||||
queryParams: { sort_field: 'elastic_rule.prebuilt_rule_id', sort_direction: 'desc' },
|
||||
});
|
||||
expect(response.body.data.map((rule) => rule.elastic_rule?.prebuilt_rule_id)).toEqual([
|
||||
'rule-2',
|
||||
|
@ -536,8 +528,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
// fetch migration rules
|
||||
let response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
sortField: 'translation_result',
|
||||
sortDirection: 'asc',
|
||||
queryParams: { sort_field: 'translation_result', sort_direction: 'asc' },
|
||||
});
|
||||
expect(response.body.data.map((rule) => rule.translation_result)).toEqual([
|
||||
RuleTranslationResult.UNTRANSLATABLE,
|
||||
|
@ -548,8 +539,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
// fetch migration rules
|
||||
response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
sortField: 'translation_result',
|
||||
sortDirection: 'desc',
|
||||
queryParams: { sort_field: 'translation_result', sort_direction: 'desc' },
|
||||
});
|
||||
expect(response.body.data.map((rule) => rule.translation_result)).toEqual([
|
||||
RuleTranslationResult.FULL,
|
||||
|
@ -572,16 +562,14 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
// fetch migration rules
|
||||
let response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
sortField: 'updated_at',
|
||||
sortDirection: 'asc',
|
||||
queryParams: { sort_field: 'updated_at', sort_direction: 'asc' },
|
||||
});
|
||||
const ascSorted = response.body.data.map((rule) => rule.updated_at);
|
||||
|
||||
// fetch migration rules
|
||||
response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
sortField: 'updated_at',
|
||||
sortDirection: 'desc',
|
||||
queryParams: { sort_field: 'updated_at', sort_direction: 'desc' },
|
||||
});
|
||||
const descSorted = response.body.data.map((rule) => rule.updated_at);
|
||||
|
||||
|
@ -612,8 +600,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
// fetch migration rules
|
||||
const response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
page: 3,
|
||||
perPage: 7,
|
||||
queryParams: { page: 3, per_page: 7 },
|
||||
});
|
||||
const start = 3 * 7;
|
||||
expect(response.body.data.map((rule) => rule.elastic_rule?.title)).toEqual(
|
||||
|
@ -643,7 +630,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
// fetch migration rules
|
||||
const response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
page: 3,
|
||||
queryParams: { page: 3 },
|
||||
});
|
||||
const defaultSize = 10;
|
||||
expect(response.body.data.map((rule) => rule.elastic_rule?.title)).toEqual(
|
||||
|
@ -673,7 +660,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
// fetch migration rules
|
||||
const response = await migrationRulesRoutes.get({
|
||||
migrationId,
|
||||
perPage: 18,
|
||||
queryParams: { per_page: 18 },
|
||||
});
|
||||
expect(response.body.data.map((rule) => rule.elastic_rule?.title)).toEqual(
|
||||
titles.slice(0, 18)
|
||||
|
|
|
@ -8,6 +8,7 @@ import { FtrProviderContext } from '../../../../ftr_provider_context';
|
|||
|
||||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('@ess SecuritySolution SIEM Migrations', () => {
|
||||
loadTestFile(require.resolve('./create'));
|
||||
loadTestFile(require.resolve('./get'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 SuperTest from 'supertest';
|
||||
|
||||
export const assertStatusCode = (statusCode: number, response: SuperTest.Response) => {
|
||||
if (response.status !== statusCode) {
|
||||
throw new Error(
|
||||
`Expected status code ${statusCode}, but got ${response.statusCode} \n` + response.text
|
||||
);
|
||||
}
|
||||
};
|
|
@ -5,4 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export * from './mocks';
|
||||
export * from './resources';
|
||||
export * from './rules';
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* 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 type { Client } from '@elastic/elasticsearch';
|
||||
|
||||
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 { generateAssistantComment } from '@kbn/security-solution-plugin/server/lib/siem_migrations/rules/task/util/comments';
|
||||
|
||||
const SIEM_MIGRATIONS_RULES_INDEX_PATTERN = `${SIEM_MIGRATIONS_INDEX_PATTERN}-rules-default`;
|
||||
|
||||
export type RuleMigrationDocument = Omit<RuleMigration, 'id'>;
|
||||
|
||||
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 splunkRuleWithResources: OriginalRule = {
|
||||
id: 'https://127.0.0.1:8089/servicesNS/nobody/DA-ESS-EndpointProtection/saved/searches/Endpoint%20-%20Old%20Malware%20Infection%20-%20Rule',
|
||||
vendor: 'splunk',
|
||||
title: 'Endpoint - Old Malware Infection - Rule',
|
||||
query:
|
||||
'| tstats `summariesonly` max(_time) as lastTime from datamodel=Malware.Malware_Attacks by Malware_Attacks.signature,Malware_Attacks.dest | `drop_dm_object_name("Malware_Attacks")` | lookup local=true malware_tracker dest,signature OUTPUT firstTime | eval dayDiff=round((lastTime-firstTime)/86400,1) | search dayDiff>30',
|
||||
query_language: 'spl',
|
||||
description: 'Alerts when a host with an old infection is discovered (likely a re-infection).',
|
||||
};
|
||||
|
||||
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: defaultOriginalRule,
|
||||
status: 'completed',
|
||||
created_by: 'u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0',
|
||||
updated_by: 'u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0',
|
||||
updated_at: '2025-01-13T15:39:48.729Z',
|
||||
comments: [
|
||||
generateAssistantComment(
|
||||
'## Prebuilt Rule Matching Summary\nThe Splunk rule "Access - Default Account Usage - Rule" is intended to discover the use of default accounts, which are commonly targeted by attackers using brute force attack tools. However, none of the provided Elastic Prebuilt Rules specifically address the detection of default account usage. The closest matches involve brute force attacks in general, but they do not specifically focus on default accounts. Therefore, no suitable match was found.'
|
||||
),
|
||||
generateAssistantComment('## Integration Matching Summary\nNo related integration found.'),
|
||||
generateAssistantComment(
|
||||
'## Translation Summary\n\nThe provided Splunk SPL query was analyzed and translated into the equivalent ES|QL query. Here is a breakdown of the process:\n\n### Original SPL Query\n```splunk-spl\n| from datamodel:"Authentication"."Successful_Default_Authentication" \n| stats max("_time") as "lastTime",\nvalues("tag") as "tag",\ncount by "dest","user","app"\n```\n\n### Key SPL Components and Their ES|QL Equivalents:\n1. **Data Model**: `from datamodel:"Authentication"."Successful_Default_Authentication"` is not directly translatable to ES|QL. Instead, we use `FROM logs-*` to define the data source.\n2. **Stats Aggregation**: The `stats max("_time") as "lastTime", values("tag") as "tag", count by "dest","user","app"` was translated as follows:\n - `max(_time) as lastTime` to find the latest time.\n - `values(tag) as tag` to collect all values in the `tag` field.\n - `count by dest, user, app` to count the occurrences grouped by `dest`, `user`, and `app`.\n\nBy analyzing these key components and their ES|QL equivalents, the translated query achieves the same results as the SPL query while adhering to the ES|QL syntax and structure.'
|
||||
),
|
||||
],
|
||||
translation_result: 'partial',
|
||||
elastic_rule: defaultElasticRule,
|
||||
};
|
||||
|
||||
export const getMigrationRuleDocument = (
|
||||
overrideParams: Partial<RuleMigrationDocument>
|
||||
): RuleMigrationDocument => ({
|
||||
...defaultMigrationRuleDocument,
|
||||
...overrideParams,
|
||||
});
|
||||
|
||||
export const getMigrationRuleDocuments = (
|
||||
count: number,
|
||||
overrideCallback: (index: number) => Partial<RuleMigrationDocument>
|
||||
): 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<string[]> => {
|
||||
const createdAt = new Date().toISOString();
|
||||
const res = await es.bulk({
|
||||
refresh: 'wait_for',
|
||||
operations: rules.flatMap((ruleMigration) => [
|
||||
{ create: { _index: SIEM_MIGRATIONS_RULES_INDEX_PATTERN } },
|
||||
{
|
||||
...ruleMigration,
|
||||
'@timestamp': createdAt,
|
||||
updated_at: createdAt,
|
||||
},
|
||||
]),
|
||||
});
|
||||
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<void> => {
|
||||
await es.deleteByQuery({
|
||||
index: [SIEM_MIGRATIONS_RULES_INDEX_PATTERN],
|
||||
body: {
|
||||
query: {
|
||||
match_all: {},
|
||||
},
|
||||
},
|
||||
ignore_unavailable: true,
|
||||
refresh: true,
|
||||
});
|
||||
};
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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 SuperTest from 'supertest';
|
||||
import {
|
||||
ELASTIC_HTTP_VERSION_HEADER,
|
||||
X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
|
||||
} from '@kbn/core-http-common';
|
||||
import { replaceParams } from '@kbn/openapi-common/shared';
|
||||
|
||||
import { SIEM_RULE_MIGRATION_RESOURCES_MISSING_PATH } from '@kbn/security-solution-plugin/common/siem_migrations/constants';
|
||||
import { GetRuleMigrationResourcesMissingResponse } from '@kbn/security-solution-plugin/common/siem_migrations/model/api/rules/rule_migration.gen';
|
||||
import { API_VERSIONS } from '@kbn/security-solution-plugin/common/constants';
|
||||
import { assertStatusCode } from './asserts';
|
||||
|
||||
export interface GetRuleMigrationMissingResourcesParams {
|
||||
/** `id` of the migration to get missing resources for */
|
||||
migrationId: string;
|
||||
/** Optional expected status code parameter */
|
||||
expectStatusCode?: number;
|
||||
}
|
||||
|
||||
export const migrationResourcesRouteHelpersFactory = (supertest: SuperTest.Agent) => {
|
||||
return {
|
||||
getMissingResources: async ({
|
||||
migrationId,
|
||||
expectStatusCode = 200,
|
||||
}: GetRuleMigrationMissingResourcesParams): Promise<{
|
||||
body: GetRuleMigrationResourcesMissingResponse;
|
||||
}> => {
|
||||
const response = await supertest
|
||||
.get(
|
||||
replaceParams(SIEM_RULE_MIGRATION_RESOURCES_MISSING_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();
|
||||
|
||||
assertStatusCode(expectStatusCode, response);
|
||||
|
||||
return response;
|
||||
},
|
||||
};
|
||||
};
|
|
@ -6,150 +6,40 @@
|
|||
*/
|
||||
|
||||
import SuperTest from 'supertest';
|
||||
import type { Client } from '@elastic/elasticsearch';
|
||||
import { X_ELASTIC_INTERNAL_ORIGIN_REQUEST } from '@kbn/core-http-common';
|
||||
import {
|
||||
ELASTIC_HTTP_VERSION_HEADER,
|
||||
X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
|
||||
} from '@kbn/core-http-common';
|
||||
import { replaceParams } from '@kbn/openapi-common/shared';
|
||||
|
||||
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<RuleMigration, 'id'>;
|
||||
|
||||
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: defaultOriginalRule,
|
||||
status: 'completed',
|
||||
created_by: 'u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0',
|
||||
updated_by: 'u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0',
|
||||
updated_at: '2025-01-13T15:39:48.729Z',
|
||||
comments: [
|
||||
generateAssistantComment(
|
||||
'## Prebuilt Rule Matching Summary\nThe Splunk rule "Access - Default Account Usage - Rule" is intended to discover the use of default accounts, which are commonly targeted by attackers using brute force attack tools. However, none of the provided Elastic Prebuilt Rules specifically address the detection of default account usage. The closest matches involve brute force attacks in general, but they do not specifically focus on default accounts. Therefore, no suitable match was found.'
|
||||
),
|
||||
generateAssistantComment('## Integration Matching Summary\nNo related integration found.'),
|
||||
generateAssistantComment(
|
||||
'## Translation Summary\n\nThe provided Splunk SPL query was analyzed and translated into the equivalent ES|QL query. Here is a breakdown of the process:\n\n### Original SPL Query\n```splunk-spl\n| from datamodel:"Authentication"."Successful_Default_Authentication" \n| stats max("_time") as "lastTime",\nvalues("tag") as "tag",\ncount by "dest","user","app"\n```\n\n### Key SPL Components and Their ES|QL Equivalents:\n1. **Data Model**: `from datamodel:"Authentication"."Successful_Default_Authentication"` is not directly translatable to ES|QL. Instead, we use `FROM logs-*` to define the data source.\n2. **Stats Aggregation**: The `stats max("_time") as "lastTime", values("tag") as "tag", count by "dest","user","app"` was translated as follows:\n - `max(_time) as lastTime` to find the latest time.\n - `values(tag) as tag` to collect all values in the `tag` field.\n - `count by dest, user, app` to count the occurrences grouped by `dest`, `user`, and `app`.\n\nBy analyzing these key components and their ES|QL equivalents, the translated query achieves the same results as the SPL query while adhering to the ES|QL syntax and structure.'
|
||||
),
|
||||
],
|
||||
translation_result: 'partial',
|
||||
elastic_rule: defaultElasticRule,
|
||||
};
|
||||
|
||||
export const getMigrationRuleDocument = (
|
||||
overrideParams: Partial<RuleMigrationDocument>
|
||||
): RuleMigrationDocument => ({
|
||||
...defaultMigrationRuleDocument,
|
||||
...overrideParams,
|
||||
});
|
||||
|
||||
export const getMigrationRuleDocuments = (
|
||||
count: number,
|
||||
overrideCallback: (index: number) => Partial<RuleMigrationDocument>
|
||||
): 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<string[]> => {
|
||||
const createdAt = new Date().toISOString();
|
||||
const res = await es.bulk({
|
||||
refresh: 'wait_for',
|
||||
operations: rules.flatMap((ruleMigration) => [
|
||||
{ create: { _index: SIEM_MIGRATIONS_RULES_INDEX_PATTERN } },
|
||||
{
|
||||
...ruleMigration,
|
||||
'@timestamp': createdAt,
|
||||
updated_at: createdAt,
|
||||
},
|
||||
]),
|
||||
});
|
||||
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<void> => {
|
||||
await es.deleteByQuery({
|
||||
index: [SIEM_MIGRATIONS_RULES_INDEX_PATTERN],
|
||||
body: {
|
||||
query: {
|
||||
match_all: {},
|
||||
},
|
||||
},
|
||||
ignore_unavailable: true,
|
||||
refresh: true,
|
||||
});
|
||||
};
|
||||
|
||||
const assertStatusCode = (statusCode: number, response: SuperTest.Response) => {
|
||||
if (response.status !== statusCode) {
|
||||
throw new Error(
|
||||
`Expected status code ${statusCode}, but got ${response.statusCode} \n` + response.text
|
||||
);
|
||||
}
|
||||
};
|
||||
SIEM_RULE_MIGRATIONS_PATH,
|
||||
SIEM_RULE_MIGRATION_PATH,
|
||||
} from '@kbn/security-solution-plugin/common/siem_migrations/constants';
|
||||
import {
|
||||
CreateRuleMigrationRequestBody,
|
||||
CreateRuleMigrationResponse,
|
||||
GetRuleMigrationRequestQuery,
|
||||
GetRuleMigrationResponse,
|
||||
} from '@kbn/security-solution-plugin/common/siem_migrations/model/api/rules/rule_migration.gen';
|
||||
import { API_VERSIONS } from '@kbn/security-solution-plugin/common/constants';
|
||||
import { assertStatusCode } from './asserts';
|
||||
|
||||
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 query parameters */
|
||||
queryParams?: GetRuleMigrationRequestQuery;
|
||||
/** Optional expected status code parameter */
|
||||
expectStatusCode?: number;
|
||||
}
|
||||
|
||||
export interface CreateRuleMigrationParams {
|
||||
/** Optional `id` of migration to add the rules to.
|
||||
* The id is necessary only for batching the migration creation in multiple requests */
|
||||
migrationId?: string;
|
||||
/** The body containing the `connectorId` to use for the migration */
|
||||
body: CreateRuleMigrationRequestBody;
|
||||
/** Optional expected status code parameter */
|
||||
expectStatusCode?: number;
|
||||
}
|
||||
|
@ -158,31 +48,14 @@ export const migrationRulesRouteHelpersFactory = (supertest: SuperTest.Agent) =>
|
|||
return {
|
||||
get: async ({
|
||||
migrationId,
|
||||
page,
|
||||
perPage,
|
||||
sortField,
|
||||
sortDirection,
|
||||
filters,
|
||||
queryParams = {},
|
||||
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,
|
||||
})
|
||||
.query(queryParams)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.set('elastic-api-version', '1')
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, API_VERSIONS.internal.v1)
|
||||
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
|
||||
.send();
|
||||
|
||||
|
@ -190,5 +63,22 @@ export const migrationRulesRouteHelpersFactory = (supertest: SuperTest.Agent) =>
|
|||
|
||||
return response;
|
||||
},
|
||||
|
||||
create: async ({
|
||||
migrationId,
|
||||
body,
|
||||
expectStatusCode = 200,
|
||||
}: CreateRuleMigrationParams): Promise<{ body: CreateRuleMigrationResponse }> => {
|
||||
const response = await supertest
|
||||
.post(`${SIEM_RULE_MIGRATIONS_PATH}${migrationId ? `/${migrationId}` : ''}`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, API_VERSIONS.internal.v1)
|
||||
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
|
||||
.send(body);
|
||||
|
||||
assertStatusCode(expectStatusCode, response);
|
||||
|
||||
return response;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue