[SecuritySolution] Fix 8.17.3 branch by reverting bugfix (#212729)

## Summary

Revert https://github.com/elastic/kibana/pull/212378


The Entity Analytics team backported a broken fix to 8.17.3, preventing
users from installing the risk engine on multiple spaces.
This commit is contained in:
Pablo Machado 2025-02-28 12:18:27 +01:00 committed by GitHub
parent 45321f8b9a
commit faabb4e47a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 353 additions and 501 deletions

View file

@ -129,9 +129,6 @@ export const riskScoreFieldMap: FieldMap = {
} as const;
export const mappingComponentName = '.risk-score-mappings';
export const nameSpaceAwareMappingsComponentName = (namespace: string): string => {
return `${mappingComponentName}-${namespace}`;
};
export const totalFieldsLimit = 1000;
export const getIndexPatternDataStream = (namespace: string): IIndexPatternString => ({

View file

@ -40,7 +40,6 @@ jest.spyOn(transforms, 'scheduleTransformNow').mockResolvedValue(Promise.resolve
describe('RiskScoreDataClient', () => {
let riskScoreDataClient: RiskScoreDataClient;
let riskScoreDataClientWithNameSpace: RiskScoreDataClient;
let mockSavedObjectClient: ReturnType<typeof savedObjectsClientMock.create>;
let logger: ReturnType<typeof loggingSystemMock.createLogger>;
const esClient = elasticsearchServiceMock.createScopedClusterClient().asCurrentUser;
@ -57,8 +56,6 @@ describe('RiskScoreDataClient', () => {
namespace: 'default',
};
riskScoreDataClient = new RiskScoreDataClient(options);
const optionsWithNamespace = { ...options, namespace: 'space-1' };
riskScoreDataClientWithNameSpace = new RiskScoreDataClient(optionsWithNamespace);
});
afterEach(() => {
@ -83,396 +80,369 @@ describe('RiskScoreDataClient', () => {
});
describe('init success', () => {
it('should initialize risk engine resources in the appropriate space', async () => {
const assertComponentTemplate = (namespace: string) => {
expect(createOrUpdateComponentTemplate).toHaveBeenCalledWith(
expect.objectContaining({
logger,
esClient,
template: expect.objectContaining({
name: `.risk-score-mappings-${namespace}`,
_meta: {
managed: true,
},
}),
totalFieldsLimit: 1000,
})
);
};
const assertIndexTemplate = (namespace: string) => {
expect(createOrUpdateIndexTemplate).toHaveBeenCalledWith({
logger,
esClient,
template: {
name: `.risk-score.risk-score-${namespace}-index-template`,
body: {
data_stream: { hidden: true },
index_patterns: [`risk-score.risk-score-${namespace}`],
composed_of: [`.risk-score-mappings-${namespace}`],
template: {
lifecycle: {},
settings: {
'index.mapping.total_fields.limit': totalFieldsLimit,
},
mappings: {
dynamic: false,
_meta: {
kibana: {
version: '8.9.0',
},
managed: true,
namespace,
},
},
},
_meta: {
kibana: {
version: '8.9.0',
},
managed: true,
namespace,
},
},
},
});
};
const assertDataStream = (namespace: string) => {
expect(createDataStream).toHaveBeenCalledWith({
logger,
esClient,
totalFieldsLimit,
indexPatterns: {
template: `.risk-score.risk-score-${namespace}-index-template`,
alias: `risk-score.risk-score-${namespace}`,
},
});
};
const assertIndex = (namespace: string) => {
expect(createOrUpdateIndex).toHaveBeenCalledWith({
logger,
esClient,
options: {
index: `risk-score.risk-score-latest-${namespace}`,
mappings: {
dynamic: false,
properties: {
'@timestamp': {
ignore_malformed: false,
type: 'date',
},
host: {
properties: {
name: {
type: 'keyword',
},
risk: {
properties: {
calculated_level: {
type: 'keyword',
},
calculated_score: {
type: 'float',
},
calculated_score_norm: {
type: 'float',
},
category_1_count: {
type: 'long',
},
category_1_score: {
type: 'float',
},
id_field: {
type: 'keyword',
},
id_value: {
type: 'keyword',
},
inputs: {
properties: {
category: {
type: 'keyword',
},
description: {
type: 'keyword',
},
id: {
type: 'keyword',
},
index: {
type: 'keyword',
},
risk_score: {
type: 'float',
},
timestamp: {
type: 'date',
},
},
type: 'object',
},
notes: {
type: 'keyword',
},
},
type: 'object',
},
},
},
user: {
properties: {
name: {
type: 'keyword',
},
risk: {
properties: {
calculated_level: {
type: 'keyword',
},
calculated_score: {
type: 'float',
},
calculated_score_norm: {
type: 'float',
},
category_1_count: {
type: 'long',
},
category_1_score: {
type: 'float',
},
id_field: {
type: 'keyword',
},
id_value: {
type: 'keyword',
},
inputs: {
properties: {
category: {
type: 'keyword',
},
description: {
type: 'keyword',
},
id: {
type: 'keyword',
},
index: {
type: 'keyword',
},
risk_score: {
type: 'float',
},
timestamp: {
type: 'date',
},
},
type: 'object',
},
notes: {
type: 'keyword',
},
},
type: 'object',
},
},
},
},
},
},
});
};
const assertTransform = (namespace: string) => {
expect(transforms.createTransform).toHaveBeenCalledWith({
logger,
esClient,
transform: {
dest: {
index: `risk-score.risk-score-latest-${namespace}`,
},
frequency: '1h',
latest: {
sort: '@timestamp',
unique_key: ['host.name', 'user.name'],
},
source: {
index: [`risk-score.risk-score-${namespace}`],
},
sync: {
time: {
delay: '0s',
field: '@timestamp',
},
},
transform_id: `risk_score_latest_transform_${namespace}`,
settings: {
unattended: true,
},
_meta: {
version: 2,
managed: true,
managed_by: 'security-entity-analytics',
},
},
});
};
// Default namespace
esClient.cluster.existsComponentTemplate.mockResolvedValue(false);
it('should initialize risk engine resources', async () => {
await riskScoreDataClient.init();
assertComponentTemplate('default');
assertIndexTemplate('default');
assertDataStream('default');
assertIndex('default');
assertTransform('default');
// Space-1 namespace
esClient.cluster.existsComponentTemplate.mockResolvedValue(false);
await riskScoreDataClientWithNameSpace.init();
assertComponentTemplate('space-1');
assertIndexTemplate('space-1');
assertDataStream('space-1');
assertIndex('space-1');
assertTransform('space-1');
expect(createOrUpdateComponentTemplate).toHaveBeenCalledWith(
expect.objectContaining({
logger,
esClient,
template: expect.objectContaining({
name: '.risk-score-mappings',
_meta: {
managed: true,
},
}),
totalFieldsLimit: 1000,
})
);
expect((createOrUpdateComponentTemplate as jest.Mock).mock.lastCall[0].template.template)
.toMatchInlineSnapshot(`
Object {
"mappings": Object {
"dynamic": "strict",
"properties": Object {
"@timestamp": Object {
"ignore_malformed": false,
"type": "date",
},
"host": Object {
"properties": Object {
"name": Object {
"type": "keyword",
},
"risk": Object {
"properties": Object {
"calculated_level": Object {
"type": "keyword",
},
"calculated_score": Object {
"type": "float",
},
"calculated_score_norm": Object {
"type": "float",
},
"category_1_count": Object {
"type": "long",
},
"category_1_score": Object {
"type": "float",
},
"id_field": Object {
"type": "keyword",
},
"id_value": Object {
"type": "keyword",
},
"inputs": Object {
"properties": Object {
"category": Object {
"type": "keyword",
},
"description": Object {
"type": "keyword",
},
"id": Object {
"type": "keyword",
},
"index": Object {
"type": "keyword",
},
"risk_score": Object {
"type": "float",
},
"timestamp": Object {
"type": "date",
},
},
"type": "object",
},
"notes": Object {
"type": "keyword",
},
Object {
"mappings": Object {
"dynamic": "strict",
"properties": Object {
"@timestamp": Object {
"ignore_malformed": false,
"type": "date",
},
"host": Object {
"properties": Object {
"name": Object {
"type": "keyword",
},
"risk": Object {
"properties": Object {
"calculated_level": Object {
"type": "keyword",
},
"calculated_score": Object {
"type": "float",
},
"calculated_score_norm": Object {
"type": "float",
},
"category_1_count": Object {
"type": "long",
},
"category_1_score": Object {
"type": "float",
},
"id_field": Object {
"type": "keyword",
},
"id_value": Object {
"type": "keyword",
},
"inputs": Object {
"properties": Object {
"category": Object {
"type": "keyword",
},
"description": Object {
"type": "keyword",
},
"id": Object {
"type": "keyword",
},
"index": Object {
"type": "keyword",
},
"risk_score": Object {
"type": "float",
},
"timestamp": Object {
"type": "date",
},
},
"type": "object",
},
"notes": Object {
"type": "keyword",
},
},
"type": "object",
},
"type": "object",
},
},
},
"user": Object {
"properties": Object {
"name": Object {
"type": "keyword",
},
"risk": Object {
"properties": Object {
"calculated_level": Object {
"type": "keyword",
},
"calculated_score": Object {
"type": "float",
},
"calculated_score_norm": Object {
"type": "float",
},
"category_1_count": Object {
"type": "long",
},
"category_1_score": Object {
"type": "float",
},
"id_field": Object {
"type": "keyword",
},
"id_value": Object {
"type": "keyword",
},
"inputs": Object {
"properties": Object {
"category": Object {
"type": "keyword",
},
"description": Object {
"type": "keyword",
},
"id": Object {
"type": "keyword",
},
"index": Object {
"type": "keyword",
},
"risk_score": Object {
"type": "float",
},
"timestamp": Object {
"type": "date",
},
},
"type": "object",
},
"notes": Object {
"type": "keyword",
},
"user": Object {
"properties": Object {
"name": Object {
"type": "keyword",
},
"risk": Object {
"properties": Object {
"calculated_level": Object {
"type": "keyword",
},
"calculated_score": Object {
"type": "float",
},
"calculated_score_norm": Object {
"type": "float",
},
"category_1_count": Object {
"type": "long",
},
"category_1_score": Object {
"type": "float",
},
"id_field": Object {
"type": "keyword",
},
"id_value": Object {
"type": "keyword",
},
"inputs": Object {
"properties": Object {
"category": Object {
"type": "keyword",
},
"description": Object {
"type": "keyword",
},
"id": Object {
"type": "keyword",
},
"index": Object {
"type": "keyword",
},
"risk_score": Object {
"type": "float",
},
"timestamp": Object {
"type": "date",
},
},
"type": "object",
},
"notes": Object {
"type": "keyword",
},
},
"type": "object",
},
"type": "object",
},
},
},
},
"settings": Object {},
}
`);
expect(createOrUpdateIndexTemplate).toHaveBeenCalledWith({
logger,
esClient,
template: {
name: '.risk-score.risk-score-default-index-template',
body: {
data_stream: { hidden: true },
index_patterns: ['risk-score.risk-score-default'],
composed_of: ['.risk-score-mappings'],
template: {
lifecycle: {},
settings: {
'index.mapping.total_fields.limit': totalFieldsLimit,
},
mappings: {
dynamic: false,
_meta: {
kibana: {
version: '8.9.0',
},
managed: true,
namespace: 'default',
},
},
},
"settings": Object {},
}
`);
_meta: {
kibana: {
version: '8.9.0',
},
managed: true,
namespace: 'default',
},
},
},
});
expect(createDataStream).toHaveBeenCalledWith({
logger,
esClient,
totalFieldsLimit,
indexPatterns: {
template: `.risk-score.risk-score-default-index-template`,
alias: `risk-score.risk-score-default`,
},
});
expect(createOrUpdateIndex).toHaveBeenCalledWith({
logger,
esClient,
options: {
index: `risk-score.risk-score-latest-default`,
mappings: {
dynamic: false,
properties: {
'@timestamp': {
ignore_malformed: false,
type: 'date',
},
host: {
properties: {
name: {
type: 'keyword',
},
risk: {
properties: {
calculated_level: {
type: 'keyword',
},
calculated_score: {
type: 'float',
},
calculated_score_norm: {
type: 'float',
},
category_1_count: {
type: 'long',
},
category_1_score: {
type: 'float',
},
id_field: {
type: 'keyword',
},
id_value: {
type: 'keyword',
},
inputs: {
properties: {
category: {
type: 'keyword',
},
description: {
type: 'keyword',
},
id: {
type: 'keyword',
},
index: {
type: 'keyword',
},
risk_score: {
type: 'float',
},
timestamp: {
type: 'date',
},
},
type: 'object',
},
notes: {
type: 'keyword',
},
},
type: 'object',
},
},
},
user: {
properties: {
name: {
type: 'keyword',
},
risk: {
properties: {
calculated_level: {
type: 'keyword',
},
calculated_score: {
type: 'float',
},
calculated_score_norm: {
type: 'float',
},
category_1_count: {
type: 'long',
},
category_1_score: {
type: 'float',
},
id_field: {
type: 'keyword',
},
id_value: {
type: 'keyword',
},
inputs: {
properties: {
category: {
type: 'keyword',
},
description: {
type: 'keyword',
},
id: {
type: 'keyword',
},
index: {
type: 'keyword',
},
risk_score: {
type: 'float',
},
timestamp: {
type: 'date',
},
},
type: 'object',
},
notes: {
type: 'keyword',
},
},
type: 'object',
},
},
},
},
},
},
});
expect(transforms.createTransform).toHaveBeenCalledWith({
logger,
esClient,
transform: {
dest: {
index: 'risk-score.risk-score-latest-default',
},
frequency: '1h',
latest: {
sort: '@timestamp',
unique_key: ['host.name', 'user.name'],
},
source: {
index: ['risk-score.risk-score-default'],
},
sync: {
time: {
delay: '0s',
field: '@timestamp',
},
},
transform_id: 'risk_score_latest_transform_default',
settings: {
unattended: true,
},
_meta: {
version: 2,
managed: true,
managed_by: 'security-entity-analytics',
},
},
});
});
});

View file

@ -22,7 +22,6 @@ import {
getIndexPatternDataStream,
getTransformOptions,
mappingComponentName,
nameSpaceAwareMappingsComponentName,
riskScoreFieldMap,
totalFieldsLimit,
} from './configurations';
@ -115,22 +114,12 @@ export class RiskScoreDataClient {
namespace,
};
// Check if there are any existing component templates with the namespace in the name
const oldComponentTemplateExists = await esClient.cluster.existsComponentTemplate({
name: mappingComponentName,
});
if (oldComponentTemplateExists) {
await this.updateComponentTemplateNamewithNamespace(namespace);
}
// Update the new component template with the required data
await Promise.all([
createOrUpdateComponentTemplate({
logger: this.options.logger,
esClient,
template: {
name: nameSpaceAwareMappingsComponentName(namespace),
name: mappingComponentName,
_meta: {
managed: true,
},
@ -151,7 +140,7 @@ export class RiskScoreDataClient {
body: {
data_stream: { hidden: true },
index_patterns: [indexPatterns.alias],
composed_of: [nameSpaceAwareMappingsComponentName(namespace)],
composed_of: [mappingComponentName],
template: {
lifecycle: {},
settings: {
@ -167,14 +156,6 @@ export class RiskScoreDataClient {
},
});
// Delete the component template without the namespace in the name
await esClient.cluster.deleteComponentTemplate(
{
name: mappingComponentName,
},
{ ignore: [404] }
);
await createDataStream({
logger: this.options.logger,
esClient,
@ -305,20 +286,4 @@ export class RiskScoreDataClient {
{ logger: this.options.logger }
);
}
private async updateComponentTemplateNamewithNamespace(namespace: string): Promise<void> {
const esClient = this.options.esClient;
const oldComponentTemplateResponse = await esClient.cluster.getComponentTemplate(
{
name: mappingComponentName,
},
{ ignore: [404] }
);
const oldComponentTemplate = oldComponentTemplateResponse?.component_templates[0];
const newComponentTemplateName = nameSpaceAwareMappingsComponentName(namespace);
await esClient.cluster.putComponentTemplate({
name: newComponentTemplateName,
body: oldComponentTemplate.component_template,
});
}
}

View file

@ -303,87 +303,7 @@ export default ({ getService }: FtrProviderContext) => {
);
});
it('should update the existing component template and index template without any errors', async () => {
const componentTemplateName = '.risk-score-mappings';
const indexTemplateName = '.risk-score.risk-score-default-index-template';
const newComponentTemplateName = '.risk-score-mappings-default';
// Call API to put the component template and index template
await es.cluster.putComponentTemplate({
name: componentTemplateName,
body: {
template: {
settings: {
number_of_shards: 1,
},
mappings: {
properties: {
timestamp: {
type: 'date',
},
user: {
properties: {
id: {
type: 'keyword',
},
name: {
type: 'text',
},
},
},
},
},
},
version: 1,
},
});
// Call an API to put the index template
await es.indices.putIndexTemplate({
name: indexTemplateName,
body: {
index_patterns: [indexTemplateName],
composed_of: [componentTemplateName],
template: {
settings: {
number_of_shards: 1,
},
mappings: {
properties: {
timestamp: {
type: 'date',
},
user: {
properties: {
id: {
type: 'keyword',
},
name: {
type: 'text',
},
},
},
},
},
},
},
});
const response = await riskEngineRoutes.init();
expect(response.status).to.eql(200);
expect(response.body.result.errors).to.eql([]);
const response2 = await es.cluster.getComponentTemplate({
name: newComponentTemplateName,
});
expect(response2.component_templates.length).to.eql(1);
expect(response2.component_templates[0].name).to.eql(newComponentTemplateName);
});
// Failing: See https://github.com/elastic/kibana/issues/191637
describe.skip('remove legacy risk score transform', function () {
describe('remove legacy risk score transform', function () {
this.tags('skipFIPS');
it('should remove legacy risk score transform if it exists', async () => {
await installLegacyRiskScore({ supertest });