Skip to content

Commit

Permalink
[8.x] [Security Solution][Detection Engine] adds legacy siem signals …
Browse files Browse the repository at this point in the history
…telemetry (#202671) (#203744)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Security Solution][Detection Engine] adds legacy siem signals
telemetry (#202671)](#202671)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Vitalii
Dmyterko","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-12-11T10:03:19Z","message":"[Security
Solution][Detection Engine] adds legacy siem signals telemetry
(#202671)\n\n## Summary\r\n\r\n- partly addresses
https://github.com/elastic/kibana/issues/195523\r\n- adds snapshot
telemetry that shows number of legacy siem signals and\r\nnumber of
spaces they are in\r\n- while working on PR, discovered and fixed few
issues in APIs\r\n- get migration status API did not work correctly with
new `.alerts-*`\r\nindices, listing them as outdated\r\n- finalize
migration API did account for spaces, when adding alias to\r\nmigrated
index\r\n- remove migration API failed due to lack of permissions to
removed\r\nmigration task from `.tasks` index\r\n\r\n### How to
test\r\n\r\n#### How to create legacy siem index?\r\n\r\nrun script that
used for FTR tests\r\n\r\n```bash\r\nnode scripts/es_archiver
--kibana-url=http://elastic:changeme@localhost:5601
--es-url=http://elastic:changeme@localhost:9200 load
x-pack/test/functional/es_archives/signals/legacy_signals_index\r\n\r\n```\r\nThese
would create legacy siem indices. But be aware, it might break\r\nKibana
.alerts indices creation. But sufficient for testing\r\n\r\n\r\n#### How
to test snapshot telemetry\r\n\r\nSnapshot\r\nFor snapshot telemetry
use\r\n[API](https://docs.elastic.dev/telemetry/collection/snapshot-telemetry#telemetry-usage-payload-api)\r\ncall\r\nOR\r\nCheck
snapshots in Kibana adv settings -> Global Settings Tab ->
Usage\r\ncollection section -> Click on cluster data example link ->
Check\r\n`legacy_siem_signals ` fields in
flyout\r\n\r\n<details>\r\n<summary> Snapshot telemetry
</summary>\r\n\r\n\r\n<img width=\"2549\" alt=\"Screenshot 2024-12-03 at
13 08
03\"\r\nsrc=\"https://github.com/user-attachments/assets/28ffe983-01c7-4435-a82a-9a968d32d5e0\">\r\n\r\n\r\n
</details>\r\n\r\n---------\r\n\r\nCo-authored-by: Ryland Herrick
<[email protected]>","sha":"8821e034e9c6cc4ad42915e54b429defd6b970b5","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:Detections
and Resp","Team: SecuritySolution","backport:prev-minor","Team:Detection
Engine"],"title":"[Security Solution][Detection Engine] adds legacy siem
signals
telemetry","number":202671,"url":"https://github.com/elastic/kibana/pull/202671","mergeCommit":{"message":"[Security
Solution][Detection Engine] adds legacy siem signals telemetry
(#202671)\n\n## Summary\r\n\r\n- partly addresses
https://github.com/elastic/kibana/issues/195523\r\n- adds snapshot
telemetry that shows number of legacy siem signals and\r\nnumber of
spaces they are in\r\n- while working on PR, discovered and fixed few
issues in APIs\r\n- get migration status API did not work correctly with
new `.alerts-*`\r\nindices, listing them as outdated\r\n- finalize
migration API did account for spaces, when adding alias to\r\nmigrated
index\r\n- remove migration API failed due to lack of permissions to
removed\r\nmigration task from `.tasks` index\r\n\r\n### How to
test\r\n\r\n#### How to create legacy siem index?\r\n\r\nrun script that
used for FTR tests\r\n\r\n```bash\r\nnode scripts/es_archiver
--kibana-url=http://elastic:changeme@localhost:5601
--es-url=http://elastic:changeme@localhost:9200 load
x-pack/test/functional/es_archives/signals/legacy_signals_index\r\n\r\n```\r\nThese
would create legacy siem indices. But be aware, it might break\r\nKibana
.alerts indices creation. But sufficient for testing\r\n\r\n\r\n#### How
to test snapshot telemetry\r\n\r\nSnapshot\r\nFor snapshot telemetry
use\r\n[API](https://docs.elastic.dev/telemetry/collection/snapshot-telemetry#telemetry-usage-payload-api)\r\ncall\r\nOR\r\nCheck
snapshots in Kibana adv settings -> Global Settings Tab ->
Usage\r\ncollection section -> Click on cluster data example link ->
Check\r\n`legacy_siem_signals ` fields in
flyout\r\n\r\n<details>\r\n<summary> Snapshot telemetry
</summary>\r\n\r\n\r\n<img width=\"2549\" alt=\"Screenshot 2024-12-03 at
13 08
03\"\r\nsrc=\"https://github.com/user-attachments/assets/28ffe983-01c7-4435-a82a-9a968d32d5e0\">\r\n\r\n\r\n
</details>\r\n\r\n---------\r\n\r\nCo-authored-by: Ryland Herrick
<[email protected]>","sha":"8821e034e9c6cc4ad42915e54b429defd6b970b5"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/202671","number":202671,"mergeCommit":{"message":"[Security
Solution][Detection Engine] adds legacy siem signals telemetry
(#202671)\n\n## Summary\r\n\r\n- partly addresses
https://github.com/elastic/kibana/issues/195523\r\n- adds snapshot
telemetry that shows number of legacy siem signals and\r\nnumber of
spaces they are in\r\n- while working on PR, discovered and fixed few
issues in APIs\r\n- get migration status API did not work correctly with
new `.alerts-*`\r\nindices, listing them as outdated\r\n- finalize
migration API did account for spaces, when adding alias to\r\nmigrated
index\r\n- remove migration API failed due to lack of permissions to
removed\r\nmigration task from `.tasks` index\r\n\r\n### How to
test\r\n\r\n#### How to create legacy siem index?\r\n\r\nrun script that
used for FTR tests\r\n\r\n```bash\r\nnode scripts/es_archiver
--kibana-url=http://elastic:changeme@localhost:5601
--es-url=http://elastic:changeme@localhost:9200 load
x-pack/test/functional/es_archives/signals/legacy_signals_index\r\n\r\n```\r\nThese
would create legacy siem indices. But be aware, it might break\r\nKibana
.alerts indices creation. But sufficient for testing\r\n\r\n\r\n#### How
to test snapshot telemetry\r\n\r\nSnapshot\r\nFor snapshot telemetry
use\r\n[API](https://docs.elastic.dev/telemetry/collection/snapshot-telemetry#telemetry-usage-payload-api)\r\ncall\r\nOR\r\nCheck
snapshots in Kibana adv settings -> Global Settings Tab ->
Usage\r\ncollection section -> Click on cluster data example link ->
Check\r\n`legacy_siem_signals ` fields in
flyout\r\n\r\n<details>\r\n<summary> Snapshot telemetry
</summary>\r\n\r\n\r\n<img width=\"2549\" alt=\"Screenshot 2024-12-03 at
13 08
03\"\r\nsrc=\"https://github.com/user-attachments/assets/28ffe983-01c7-4435-a82a-9a968d32d5e0\">\r\n\r\n\r\n
</details>\r\n\r\n---------\r\n\r\nCo-authored-by: Ryland Herrick
<[email protected]>","sha":"8821e034e9c6cc4ad42915e54b429defd6b970b5"}}]}]
BACKPORT-->

Co-authored-by: Vitalii Dmyterko <[email protected]>
  • Loading branch information
kibanamachine and vitaliidm authored Dec 11, 2024
1 parent e0f9cf7 commit cb68426
Show file tree
Hide file tree
Showing 27 changed files with 714 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,30 @@ interface IndexAlias {
*
* @param esClient An {@link ElasticsearchClient}
* @param alias alias name used to filter results
* @param index index name used to filter results
*
* @returns an array of {@link IndexAlias} objects
*/
export const getIndexAliases = async ({
esClient,
alias,
index,
}: {
esClient: ElasticsearchClient;
alias: string;
index?: string;
}): Promise<IndexAlias[]> => {
const response = await esClient.indices.getAlias(
{
name: alias,
...(index ? { index } : {}),
},
{ meta: true }
);

return Object.keys(response.body).map((index) => ({
return Object.keys(response.body).map((indexName) => ({
alias,
index,
isWriteIndex: response.body[index].aliases[alias]?.is_write_index === true,
index: indexName,
isWriteIndex: response.body[indexName].aliases[alias]?.is_write_index === true,
}));
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import type { SignalsMigrationSO } from './saved_objects_schema';
/**
* Deletes a completed migration:
* * deletes the migration SO
* * deletes the underlying task document
* * applies deletion policy to the relevant index
*
* @param esClient An {@link ElasticsearchClient}
Expand All @@ -40,7 +39,7 @@ export const deleteMigration = async ({
return migration;
}

const { destinationIndex, sourceIndex, taskId } = migration.attributes;
const { destinationIndex, sourceIndex } = migration.attributes;

if (isMigrationFailed(migration)) {
await applyMigrationCleanupPolicy({
Expand All @@ -57,7 +56,6 @@ export const deleteMigration = async ({
});
}

await esClient.delete({ index: '.tasks', id: taskId });
await deleteMigrationSavedObject({ id: migration.id, soClient });

return migration;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ describe('finalizeMigration', () => {
signalsAlias: 'my-signals-alias',
soClient,
username: 'username',
legacySiemSignalsAlias: '.siem-signals-default',
});

expect(updateMigrationSavedObject).not.toHaveBeenCalled();
Expand All @@ -54,6 +55,7 @@ describe('finalizeMigration', () => {
signalsAlias: 'my-signals-alias',
soClient,
username: 'username',
legacySiemSignalsAlias: '.siem-signals-default',
});

expect(updateMigrationSavedObject).not.toHaveBeenCalled();
Expand All @@ -72,6 +74,7 @@ describe('finalizeMigration', () => {
signalsAlias: 'my-signals-alias',
soClient,
username: 'username',
legacySiemSignalsAlias: '.siem-signals-default',
});

expect(updateMigrationSavedObject).toHaveBeenCalledWith(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@ export const finalizeMigration = async ({
signalsAlias,
soClient,
username,
legacySiemSignalsAlias,
}: {
esClient: ElasticsearchClient;
migration: SignalsMigrationSO;
signalsAlias: string;
soClient: SavedObjectsClientContract;
username: string;
legacySiemSignalsAlias: string;
}): Promise<SignalsMigrationSO> => {
if (!isMigrationPending(migration)) {
return migration;
Expand Down Expand Up @@ -86,6 +88,7 @@ export const finalizeMigration = async ({
esClient,
newIndex: destinationIndex,
oldIndex: sourceIndex,
legacySiemSignalsAlias,
});

const updatedMigration = await updateMigrationSavedObject({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* 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 { elasticsearchServiceMock } from '@kbn/core/server/mocks';
import { getIndexAliasPerSpace } from './get_index_alias_per_space';

describe('getIndexAliasPerSpace', () => {
let esClient: ReturnType<typeof elasticsearchServiceMock.createElasticsearchClient>;

beforeEach(() => {
esClient = elasticsearchServiceMock.createElasticsearchClient();
});

it('returns object with index alias and space', async () => {
esClient.indices.getAlias.mockResponseOnce({
'.siem-signals-default-old-one': {
aliases: {
'.siem-signals-default': {
is_write_index: false,
},
},
},
'.siem-signals-another-1-legacy': {
aliases: {
'.siem-signals-another-1': {
is_write_index: false,
},
},
},
});

const result = await getIndexAliasPerSpace({
esClient,
signalsIndex: '.siem-signals',
signalsAliasAllSpaces: '.siem-signals-*',
});

expect(result).toEqual({
'.siem-signals-another-1-legacy': {
alias: '.siem-signals-another-1',
indexName: '.siem-signals-another-1-legacy',
space: 'another-1',
},
'.siem-signals-default-old-one': {
alias: '.siem-signals-default',
indexName: '.siem-signals-default-old-one',
space: 'default',
},
});
});

it('filters out .internal.alert indices', async () => {
esClient.indices.getAlias.mockResponseOnce({
'.siem-signals-default-old-one': {
aliases: {
'.siem-signals-default': {
is_write_index: false,
},
},
},
'.internal.alerts-security.alerts-another-2-000001': {
aliases: {
'.siem-signals-another-2': {
is_write_index: false,
},
},
},
});

const result = await getIndexAliasPerSpace({
esClient,
signalsIndex: '.siem-signals',
signalsAliasAllSpaces: '.siem-signals-*',
});

expect(result).toEqual({
'.siem-signals-default-old-one': {
alias: '.siem-signals-default',
indexName: '.siem-signals-default-old-one',
space: 'default',
},
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* 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 { ElasticsearchClient } from '@kbn/core/server';

interface IndexAlias {
alias: string;
space: string;
indexName: string;
}

/**
* Retrieves index, its alias and Kibana space
*/
export const getIndexAliasPerSpace = async ({
esClient,
signalsIndex,
signalsAliasAllSpaces,
}: {
esClient: ElasticsearchClient;
signalsIndex: string;
signalsAliasAllSpaces: string;
}): Promise<Record<string, IndexAlias>> => {
const response = await esClient.indices.getAlias(
{
name: signalsAliasAllSpaces,
},
{ meta: true }
);

const indexAliasesMap = Object.keys(response.body).reduce<Record<string, IndexAlias>>(
(acc, indexName) => {
if (!indexName.startsWith('.internal.alerts-')) {
const alias = Object.keys(response.body[indexName].aliases)[0];

acc[indexName] = {
alias,
space: alias.replace(`${signalsIndex}-`, ''),
indexName,
};
}

return acc;
},
{}
);

return indexAliasesMap;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* 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 { IndicesGetIndexTemplateResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { elasticsearchServiceMock } from '@kbn/core/server/mocks';
import { getLatestIndexTemplateVersion } from './get_latest_index_template_version';

describe('getIndexAliasPerSpace', () => {
let esClient: ReturnType<typeof elasticsearchServiceMock.createElasticsearchClient>;

beforeEach(() => {
esClient = elasticsearchServiceMock.createElasticsearchClient();
});

it('returns latest index template version', async () => {
esClient.indices.getIndexTemplate.mockResponseOnce({
index_templates: [
{ index_template: { version: 77 } },
{ index_template: { version: 10 } },
{ index_template: { version: 23 } },
{ index_template: { version: 0 } },
],
} as IndicesGetIndexTemplateResponse);

const version = await getLatestIndexTemplateVersion({
esClient,
name: '.siem-signals-*',
});

expect(version).toBe(77);
});

it('returns 0 if templates empty', async () => {
esClient.indices.getIndexTemplate.mockResponseOnce({
index_templates: [],
});

const version = await getLatestIndexTemplateVersion({
esClient,
name: '.siem-signals-*',
});

expect(version).toBe(0);
});

it('returns 0 if request fails', async () => {
esClient.indices.getIndexTemplate.mockRejectedValueOnce('Failure');

const version = await getLatestIndexTemplateVersion({
esClient,
name: '.siem-signals-*',
});

expect(version).toBe(0);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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 { ElasticsearchClient } from '@kbn/core/server';

/**
* Retrieves the latest version of index template
* There are can be multiple index templates across different Kibana spaces,
* so we get them all and return the latest(greatest) number
*/
export const getLatestIndexTemplateVersion = async ({
esClient,
name,
}: {
esClient: ElasticsearchClient;
name: string;
}): Promise<number> => {
let latestTemplateVersion: number;
try {
const response = await esClient.indices.getIndexTemplate({ name });
const versions = response.index_templates.map(
(template) => template.index_template.version ?? 0
);

latestTemplateVersion = versions.length ? Math.max(...versions) : 0;
} catch (e) {
latestTemplateVersion = 0;
}

return latestTemplateVersion;
};
Loading

0 comments on commit cb68426

Please sign in to comment.