Skip to content

Commit 9cccd30

Browse files
authored
[Security Solution][Detection Engine] add deprecation warning for non-migrated signals (#204247)
## Summary - addresses partly elastic/security-team#10878 - shows deprecation warning if siem index was not migrated ### How to test #### How to create legacy siem index? run script that used for FTR tests ```bash node 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 node 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_non_default_space ``` These would create legacy siem indices. But be aware, it might break Kibana .alerts indices creation. But sufficient for testing Visit also detection rules page, to ensure alerts index created. Otherwise, https://www.elastic.co/guide/en/security/current/signals-migration-api.html#migration-1 API might not show these indices outdated #### How to test deprecated feature? 1. Observe warning feature deprecation on Kibana Upgrade page, if you set up legacy siem signals <details> <summary> Kibana Upgrade feature deprecation flyout </summary> <img width="2540" alt="Screenshot 2024-12-17 at 16 59 04" src="https://github.com/user-attachments/assets/c6aa420f-af69-4545-8400-6a6513f613a9" /> </details> #### Test outdated indices created in 7.x 1. Create cloud env of 7.x version 2. Create rule, generate alerts for .siem-signals 3. Create cloud env of 8.18 from existing 7.x snapshot (from previous steps) 4. Connect local Kibana to 8.18 from mirror branch of this one(#204621) 5. Add to Kibana dev config following options to enable Upgrade assistant(UA) showing outdated indices ```yml xpack.upgrade_assistant.featureSet: mlSnapshots: true migrateDataStreams: true migrateSystemIndices: true reindexCorrectiveActions: true ``` 6. Go to Detection rules page, ensure rule is running and new .alerts index has been created (visiting rules table page should be enough) 7. Open UA, ensure Kibana deprecations show signals are not migrated 8. Open UA, check Elasticsearch deprecations 9. Find outdated siem-signals index 10. Migrate it 11. Check Kibana deprecations still signals are not migrated 12. Migrate signals using https://www.elastic.co/guide/en/security/current/signals-migration-api.html API 13. Ensure Kibana deprecations does not show that space as not migrated Demo video of migration .siem-signal from another-3 Kibana space https://github.com/user-attachments/assets/d2729482-d2c8-4a23-a780-ad19d4f52c73
1 parent 1ef638a commit 9cccd30

File tree

10 files changed

+381
-1
lines changed

10 files changed

+381
-1
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
import type { CoreSetup, Logger } from '@kbn/core/server';
8+
import type { ConfigType } from '../config';
9+
10+
import { getSignalsMigrationDeprecationsInfo } from './signals_migration';
11+
12+
export const registerDeprecations = ({
13+
core,
14+
config,
15+
logger,
16+
}: {
17+
core: CoreSetup;
18+
config: ConfigType;
19+
logger: Logger;
20+
}) => {
21+
core.deprecations.registerDeprecations({
22+
getDeprecations: async (ctx) => {
23+
return [...(await getSignalsMigrationDeprecationsInfo(ctx, config, logger, core.docLinks))];
24+
},
25+
});
26+
};
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import type {
9+
DeprecationsDetails,
10+
GetDeprecationsContext,
11+
Logger,
12+
DocLinksServiceSetup,
13+
} from '@kbn/core/server';
14+
15+
import { i18n } from '@kbn/i18n';
16+
import { DETECTION_ENGINE_SIGNALS_MIGRATION_STATUS_URL } from '../../common/constants';
17+
import type { ConfigType } from '../config';
18+
19+
import { getNonMigratedSignalsInfo } from '../lib/detection_engine/migrations/get_non_migrated_signals_info';
20+
21+
const constructMigrationApiCall = (space: string, range: string) =>
22+
`GET <kibana host>:<port>${
23+
space === 'default' ? '' : `/s/${space}`
24+
}${DETECTION_ENGINE_SIGNALS_MIGRATION_STATUS_URL}?from=${range}`;
25+
26+
export const getSignalsMigrationDeprecationsInfo = async (
27+
ctx: GetDeprecationsContext,
28+
config: ConfigType,
29+
logger: Logger,
30+
docLinks: DocLinksServiceSetup
31+
): Promise<DeprecationsDetails[]> => {
32+
const esClient = ctx.esClient.asInternalUser;
33+
const { isMigrationRequired, spaces } = await getNonMigratedSignalsInfo({
34+
esClient,
35+
signalsIndex: config.signalsIndex,
36+
logger,
37+
});
38+
// Deprecation API requires time range to be part of request (https://www.elastic.co/guide/en/security/current/signals-migration-api.html#migration-1)
39+
// Return the earliest date, so it would capture the oldest possible signals
40+
const fromRange = new Date(0).toISOString();
41+
42+
if (isMigrationRequired) {
43+
return [
44+
{
45+
deprecationType: 'feature',
46+
title: i18n.translate('xpack.securitySolution.deprecations.signalsMigrationTitle', {
47+
defaultMessage: 'Found not migrated detection alerts',
48+
}),
49+
level: 'warning',
50+
message: i18n.translate('xpack.securitySolution.deprecations.signalsMigrationMessage', {
51+
defaultMessage: `After upgrading Kibana, the latest Elastic Security features will be available for any newly generated detection alerts. However, in order to enable new features for existing detection alerts, migration may be necessary.`,
52+
}),
53+
documentationUrl: docLinks.links.securitySolution.signalsMigrationApi,
54+
correctiveActions: {
55+
manualSteps: [
56+
i18n.translate(
57+
'xpack.securitySolution.deprecations.migrateIndexIlmPolicy.signalsMigrationManualStepOne',
58+
{
59+
defaultMessage: `Visit "Learn more" link for instructions how to migrate detection alerts. Migrate indices for each space.`,
60+
}
61+
),
62+
i18n.translate(
63+
'xpack.securitySolution.deprecations.migrateIndexIlmPolicy.signalsMigrationManualStepTwo',
64+
{
65+
defaultMessage: 'Spaces with at least one non-migrated signals index: {spaces}.',
66+
values: {
67+
spaces: spaces.join(', '),
68+
},
69+
}
70+
),
71+
i18n.translate(
72+
'xpack.securitySolution.deprecations.migrateIndexIlmPolicy.signalsMigrationManualStepFour',
73+
{
74+
defaultMessage: 'Example of migration API calls:',
75+
}
76+
),
77+
...spaces.map((space) => constructMigrationApiCall(space, fromRange)),
78+
],
79+
},
80+
},
81+
];
82+
}
83+
84+
return [];
85+
};

x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/migrations/create_migration_index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ export const createMigrationIndex = async ({
4242
},
4343
},
4444
},
45+
mappings: {
46+
_meta: {
47+
version,
48+
},
49+
},
4550
},
4651
});
4752

x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/migrations/get_non_migrated_signals_info.test.ts

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
import { elasticsearchServiceMock } from '@kbn/core/server/mocks';
88
import { loggerMock } from '@kbn/logging-mocks';
99

10-
import { getNonMigratedSignalsInfo } from './get_non_migrated_signals_info';
10+
import {
11+
getNonMigratedSignalsInfo,
12+
checkIfMigratedIndexOutdated,
13+
} from './get_non_migrated_signals_info';
1114
import { getIndexVersionsByIndex } from './get_index_versions_by_index';
1215
import { getSignalVersionsByIndex } from './get_signal_versions_by_index';
1316
import { getLatestIndexTemplateVersion } from './get_latest_index_template_version';
@@ -132,6 +135,39 @@ describe('getNonMigratedSignalsInfo', () => {
132135
spaces: ['default'],
133136
});
134137
});
138+
it('return empty result for migrated in v8 index', async () => {
139+
getIndexAliasPerSpaceMock.mockReturnValue({
140+
'.reindexed-v8-siem-signals-another-1-000001': {
141+
alias: '.siem-signals-another-1',
142+
indexName: '.reindexed-v8-siem-signals-another-1-000001',
143+
space: 'another-1-000001',
144+
},
145+
'.siem-signals-another-1-000002': {
146+
alias: '.siem-signals-another-1',
147+
indexName: '.siem-signals-another-1-000002',
148+
space: 'another-1',
149+
},
150+
});
151+
152+
getIndexVersionsByIndexMock.mockReturnValue({
153+
'.reindexed-v8-siem-signals-another-1-000001': 57,
154+
'.siem-signals-another-1-000002': TEMPLATE_VERSION,
155+
'.reindexed-v8-siem-signals-another-1-000001-r000077': TEMPLATE_VERSION, // outdated .reindexed-v8-siem-signals-another-1-000001 is already migrated
156+
});
157+
getSignalVersionsByIndexMock.mockReturnValue({});
158+
159+
const result = await getNonMigratedSignalsInfo({
160+
esClient,
161+
signalsIndex: 'siem-signals',
162+
logger,
163+
});
164+
165+
expect(result).toEqual({
166+
indices: [],
167+
isMigrationRequired: false,
168+
spaces: [],
169+
});
170+
});
135171
it('returns results for outdated signals in index', async () => {
136172
getIndexVersionsByIndexMock.mockReturnValue({
137173
'.siem-signals-another-1-legacy': TEMPLATE_VERSION,
@@ -175,3 +211,49 @@ describe('getNonMigratedSignalsInfo', () => {
175211
});
176212
});
177213
});
214+
215+
describe('checkIfMigratedIndexOutdated', () => {
216+
const indexVersionsByIndex = {
217+
'.siem-signals-default-000001': 57,
218+
'.siem-signals-another-6-000001': 57,
219+
'.siem-signals-default-000002': 77,
220+
'.siem-signals-another-5-000001': 57,
221+
'.reindexed-v8-siem-signals-another-1-000001': 57,
222+
'.siem-signals-another-7-000001': 57,
223+
'.reindexed-v8-siem-signals-another-2-000001': 57,
224+
'.siem-signals-another-3-000001': 57,
225+
'.reindexed-v8-siem-signals-another-4-000001': 57,
226+
'.siem-signals-another-3-000002': 77,
227+
'.siem-signals-another-9-000001': 57,
228+
'.siem-signals-another-8-000001': 57,
229+
'.siem-signals-another-2-000002': 77,
230+
'.siem-signals-another-10-000001': 57,
231+
'.siem-signals-another-1-000002': 77,
232+
'.siem-signals-another-2-000001-r000077': 77,
233+
'.reindexed-v8-siem-signals-another-1-000001-r000077': 77,
234+
};
235+
236+
const migratedIndices = [
237+
'.reindexed-v8-siem-signals-another-1-000001',
238+
'.reindexed-v8-siem-signals-another-2-000001',
239+
'.reindexed-v8-siem-signals-another-1-000001-r000077',
240+
];
241+
242+
migratedIndices.forEach((index) => {
243+
it(`should correctly find index "${index}" is migrated`, () => {
244+
expect(checkIfMigratedIndexOutdated(index, indexVersionsByIndex, TEMPLATE_VERSION)).toBe(
245+
false
246+
);
247+
});
248+
});
249+
250+
it('should find non migrated index', () => {
251+
expect(
252+
checkIfMigratedIndexOutdated(
253+
'.reindexed-v8-siem-signals-another-4-000001',
254+
indexVersionsByIndex,
255+
TEMPLATE_VERSION
256+
)
257+
).toBe(true);
258+
});
259+
});

x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/migrations/get_non_migrated_signals_info.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,41 @@ import { isOutdated as getIsOutdated, signalsAreOutdated } from './helpers';
1717
import { getLatestIndexTemplateVersion } from './get_latest_index_template_version';
1818
import { getIndexAliasPerSpace } from './get_index_alias_per_space';
1919

20+
const REINDEXED_PREFIX = '.reindexed-v8-';
21+
22+
export const checkIfMigratedIndexOutdated = (
23+
indexName: string,
24+
indexVersionsByIndex: IndexVersionsByIndex,
25+
latestTemplateVersion: number
26+
) => {
27+
const isIndexOutdated = getIsOutdated({
28+
current: indexVersionsByIndex[indexName] ?? 0,
29+
target: latestTemplateVersion,
30+
});
31+
32+
if (!isIndexOutdated) {
33+
return false;
34+
}
35+
36+
const nameWithoutPrefix = indexName.replace(REINDEXED_PREFIX, '.');
37+
38+
const hasOutdatedMigratedIndices = Object.entries(indexVersionsByIndex).every(
39+
([index, version]) => {
40+
if (index === indexName) {
41+
return true;
42+
}
43+
44+
if (index.startsWith(nameWithoutPrefix) || index.startsWith(indexName)) {
45+
return getIsOutdated({ current: version ?? 0, target: latestTemplateVersion });
46+
}
47+
48+
return true;
49+
}
50+
);
51+
52+
return hasOutdatedMigratedIndices;
53+
};
54+
2055
interface OutdatedSpaces {
2156
isMigrationRequired: boolean;
2257
spaces: string[];
@@ -85,6 +120,14 @@ export const getNonMigratedSignalsInfo = async ({
85120
const version = indexVersionsByIndex[indexName] ?? 0;
86121
const signalVersions = signalVersionsByIndex[indexName] ?? [];
87122

123+
// filter out migrated from 7.x to 8 indices
124+
if (
125+
indexName.startsWith(REINDEXED_PREFIX) &&
126+
!checkIfMigratedIndexOutdated(indexName, indexVersionsByIndex, latestTemplateVersion)
127+
) {
128+
return acc;
129+
}
130+
88131
const isOutdated =
89132
getIsOutdated({ current: version, target: latestTemplateVersion }) ||
90133
signalsAreOutdated({ signalVersions, target: latestTemplateVersion });

x-pack/solutions/security/plugins/security_solution/server/plugin.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import { AppClientFactory } from './client';
4545
import type { ConfigType } from './config';
4646
import { createConfig } from './config';
4747
import { initUiSettings } from './ui_settings';
48+
import { registerDeprecations } from './deprecations';
4849
import {
4950
APP_ID,
5051
APP_UI_ID,
@@ -212,6 +213,8 @@ export class Plugin implements ISecuritySolutionPlugin {
212213

213214
this.ruleMonitoringService.setup(core, plugins);
214215

216+
registerDeprecations({ core, config: this.config, logger: this.logger });
217+
215218
if (experimentalFeatures.riskScoringPersistence) {
216219
registerRiskScoringTask({
217220
getStartServices: core.getStartServices,
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"type": "doc",
3+
"value": {
4+
"id": "1",
5+
"index": ".siem-signals-another-space-legacy",
6+
"source": {
7+
"@timestamp": "2020-10-10T00:00:00.000Z",
8+
"signal": {}
9+
},
10+
"type": "_doc"
11+
}
12+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"type": "index",
3+
"value": {
4+
"aliases": {
5+
".siem-signals-another-space": {
6+
"is_write_index": false
7+
}
8+
},
9+
"index": ".siem-signals-another-space-legacy",
10+
"mappings": {
11+
"_meta": {
12+
"version": 1
13+
},
14+
"properties": {
15+
"@timestamp": {
16+
"type": "date"
17+
},
18+
"signal": { "type": "object" }
19+
}
20+
},
21+
"settings": {
22+
"index": {
23+
"lifecycle": {
24+
"indexing_complete": true
25+
}
26+
}
27+
}
28+
}
29+
}

0 commit comments

Comments
 (0)