mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[8.x] [SecuritySolution] Fix Entity Store init API doesn't check for indices privileges (#215329) (#215864)
# Backport This will backport the following commits from `main` to `8.x`: - [[SecuritySolution] Fix Entity Store init API doesn't check for indices privileges (#215329)](https://github.com/elastic/kibana/pull/215329) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Pablo Machado","email":"pablo.nevesmachado@elastic.co"},"sourceCommit":{"committedDate":"2025-03-25T10:48:20Z","message":"[SecuritySolution] Fix Entity Store init API doesn't check for indices privileges (#215329)\n\n## Summary\n\n* Add privileges check to the entity store init API\n* Refactor privileges check code to be reusable\n* Move privilege check code to the entity store API client\n\n### How to test it?\n* Create a new instance with security solution data\n* Create a new user with all cluster and kibana credentials but no index\nprivileges.\n* Login with the unprivileged and call the init API\n* It should return a long error msg with all required index patterns.\n\n### Checklist\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios","sha":"ef7fe99f42c513c36001a254e6b8120e043a1d2e","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:fix","v9.0.0","Team: SecuritySolution","Theme: entity_analytics","Feature:Entity Analytics","Team:Entity Analytics","backport:version","v8.18.0","v9.1.0","v8.19.0"],"title":"[SecuritySolution] Fix Entity Store init API doesn't check for indices privileges","number":215329,"url":"https://github.com/elastic/kibana/pull/215329","mergeCommit":{"message":"[SecuritySolution] Fix Entity Store init API doesn't check for indices privileges (#215329)\n\n## Summary\n\n* Add privileges check to the entity store init API\n* Refactor privileges check code to be reusable\n* Move privilege check code to the entity store API client\n\n### How to test it?\n* Create a new instance with security solution data\n* Create a new user with all cluster and kibana credentials but no index\nprivileges.\n* Login with the unprivileged and call the init API\n* It should return a long error msg with all required index patterns.\n\n### Checklist\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios","sha":"ef7fe99f42c513c36001a254e6b8120e043a1d2e"}},"sourceBranch":"main","suggestedTargetBranches":["9.0","8.18","8.x"],"targetPullRequestStates":[{"branch":"9.0","label":"v9.0.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.18","label":"v8.18.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/215329","number":215329,"mergeCommit":{"message":"[SecuritySolution] Fix Entity Store init API doesn't check for indices privileges (#215329)\n\n## Summary\n\n* Add privileges check to the entity store init API\n* Refactor privileges check code to be reusable\n* Move privilege check code to the entity store API client\n\n### How to test it?\n* Create a new instance with security solution data\n* Create a new user with all cluster and kibana credentials but no index\nprivileges.\n* Login with the unprivileged and call the init API\n* It should return a long error msg with all required index patterns.\n\n### Checklist\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios","sha":"ef7fe99f42c513c36001a254e6b8120e043a1d2e"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT-->
This commit is contained in:
parent
2add2b1548
commit
f417bd1ec0
8 changed files with 290 additions and 168 deletions
|
@ -5,97 +5,169 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { getAllMissingPrivileges } from './privileges';
|
||||
import type { EntityAnalyticsPrivileges } from '../api/entity_analytics';
|
||||
import { getMissingPrivilegesErrorMessage, getAllMissingPrivileges } from './privileges';
|
||||
import type { MissingPrivileges } from './privileges';
|
||||
|
||||
describe('getAllMissingPrivileges', () => {
|
||||
it('should return all missing privileges for elasticsearch and kibana', () => {
|
||||
const privileges: EntityAnalyticsPrivileges = {
|
||||
privileges: {
|
||||
describe('privileges', () => {
|
||||
describe('getAllMissingPrivileges', () => {
|
||||
it('should return all missing privileges for elasticsearch and kibana', () => {
|
||||
const privileges: EntityAnalyticsPrivileges = {
|
||||
privileges: {
|
||||
elasticsearch: {
|
||||
index: {
|
||||
'logs-*': { read: true, view_index_metadata: true },
|
||||
'auditbeat-*': { read: false, view_index_metadata: false },
|
||||
},
|
||||
cluster: {
|
||||
manage_enrich: false,
|
||||
manage_ingest_pipelines: true,
|
||||
},
|
||||
},
|
||||
kibana: {
|
||||
'saved_object:entity-engine-status/all': false,
|
||||
'saved_object:entity-definition/all': true,
|
||||
},
|
||||
},
|
||||
has_all_required: false,
|
||||
has_read_permissions: false,
|
||||
has_write_permissions: false,
|
||||
};
|
||||
|
||||
const result = getAllMissingPrivileges(privileges);
|
||||
|
||||
expect(result).toEqual({
|
||||
elasticsearch: {
|
||||
index: {
|
||||
'logs-*': { read: true, view_index_metadata: true },
|
||||
'auditbeat-*': { read: false, view_index_metadata: false },
|
||||
index: [{ indexName: 'auditbeat-*', privileges: ['read', 'view_index_metadata'] }],
|
||||
cluster: ['manage_enrich'],
|
||||
},
|
||||
kibana: ['saved_object:entity-engine-status/all'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return empty lists if all privileges are true', () => {
|
||||
const privileges: EntityAnalyticsPrivileges = {
|
||||
privileges: {
|
||||
elasticsearch: {
|
||||
index: {
|
||||
'logs-*': { read: true, view_index_metadata: true },
|
||||
},
|
||||
cluster: {
|
||||
manage_enrich: true,
|
||||
},
|
||||
},
|
||||
cluster: {
|
||||
manage_enrich: false,
|
||||
manage_ingest_pipelines: true,
|
||||
kibana: {
|
||||
'saved_object:entity-engine-status/all': true,
|
||||
},
|
||||
},
|
||||
kibana: {
|
||||
'saved_object:entity-engine-status/all': false,
|
||||
'saved_object:entity-definition/all': true,
|
||||
has_all_required: true,
|
||||
has_read_permissions: true,
|
||||
has_write_permissions: true,
|
||||
};
|
||||
|
||||
const result = getAllMissingPrivileges(privileges);
|
||||
|
||||
expect(result).toEqual({
|
||||
elasticsearch: {
|
||||
index: [],
|
||||
cluster: [],
|
||||
},
|
||||
},
|
||||
has_all_required: false,
|
||||
has_read_permissions: false,
|
||||
has_write_permissions: false,
|
||||
};
|
||||
kibana: [],
|
||||
});
|
||||
});
|
||||
|
||||
const result = getAllMissingPrivileges(privileges);
|
||||
it('should handle empty privileges object', () => {
|
||||
const privileges: EntityAnalyticsPrivileges = {
|
||||
privileges: {
|
||||
elasticsearch: {
|
||||
index: {},
|
||||
cluster: {},
|
||||
},
|
||||
kibana: {},
|
||||
},
|
||||
has_all_required: false,
|
||||
has_read_permissions: false,
|
||||
has_write_permissions: false,
|
||||
};
|
||||
|
||||
expect(result).toEqual({
|
||||
elasticsearch: {
|
||||
index: [{ indexName: 'auditbeat-*', privileges: ['read', 'view_index_metadata'] }],
|
||||
cluster: ['manage_enrich'],
|
||||
},
|
||||
kibana: ['saved_object:entity-engine-status/all'],
|
||||
const result = getAllMissingPrivileges(privileges);
|
||||
|
||||
expect(result).toEqual({
|
||||
elasticsearch: {
|
||||
index: [],
|
||||
cluster: [],
|
||||
},
|
||||
kibana: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should return empty lists if all privileges are true', () => {
|
||||
const privileges: EntityAnalyticsPrivileges = {
|
||||
privileges: {
|
||||
describe('getMissingPrivilegesErrorMessage', () => {
|
||||
it('should return error messages for missing index, cluster, and kibana privileges', () => {
|
||||
const missingPrivileges: MissingPrivileges = {
|
||||
elasticsearch: {
|
||||
index: {
|
||||
'logs-*': { read: true, view_index_metadata: true },
|
||||
},
|
||||
cluster: {
|
||||
manage_enrich: true,
|
||||
},
|
||||
index: [
|
||||
{ indexName: 'auditbeat-*', privileges: ['read', 'view_index_metadata'] },
|
||||
{ indexName: 'logs-*', privileges: ['write'] },
|
||||
],
|
||||
cluster: ['manage_enrich', 'monitor'],
|
||||
},
|
||||
kibana: {
|
||||
'saved_object:entity-engine-status/all': true,
|
||||
},
|
||||
},
|
||||
has_all_required: true,
|
||||
has_read_permissions: true,
|
||||
has_write_permissions: true,
|
||||
};
|
||||
kibana: ['saved_object:entity-engine-status/all', 'saved_object:entity-definition/all'],
|
||||
};
|
||||
|
||||
const result = getAllMissingPrivileges(privileges);
|
||||
const result = getMissingPrivilegesErrorMessage(missingPrivileges);
|
||||
|
||||
expect(result).toEqual({
|
||||
elasticsearch: {
|
||||
index: [],
|
||||
cluster: [],
|
||||
},
|
||||
kibana: [],
|
||||
expect(result).toBe(
|
||||
`Missing [read, view_index_metadata] privileges for index 'auditbeat-*'.\n` +
|
||||
`Missing [write] privileges for index 'logs-*'.\n` +
|
||||
`Missing [manage_enrich, monitor] cluster privileges.\n` +
|
||||
`Missing [saved_object:entity-engine-status/all, saved_object:entity-definition/all] Kibana privileges.`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty privileges object', () => {
|
||||
const privileges: EntityAnalyticsPrivileges = {
|
||||
privileges: {
|
||||
it('should return error messages for missing index and cluster privileges only', () => {
|
||||
const missingPrivileges: MissingPrivileges = {
|
||||
elasticsearch: {
|
||||
index: {},
|
||||
cluster: {},
|
||||
index: [{ indexName: 'auditbeat-*', privileges: ['read'] }],
|
||||
cluster: ['manage_enrich'],
|
||||
},
|
||||
kibana: {},
|
||||
},
|
||||
has_all_required: false,
|
||||
has_read_permissions: false,
|
||||
has_write_permissions: false,
|
||||
};
|
||||
kibana: [],
|
||||
};
|
||||
|
||||
const result = getAllMissingPrivileges(privileges);
|
||||
const result = getMissingPrivilegesErrorMessage(missingPrivileges);
|
||||
|
||||
expect(result).toEqual({
|
||||
elasticsearch: {
|
||||
index: [],
|
||||
cluster: [],
|
||||
},
|
||||
kibana: [],
|
||||
expect(result).toBe(
|
||||
`Missing [read] privileges for index 'auditbeat-*'.\n` +
|
||||
`Missing [manage_enrich] cluster privileges.`
|
||||
);
|
||||
});
|
||||
|
||||
it('should return error messages for missing kibana privileges only', () => {
|
||||
const missingPrivileges: MissingPrivileges = {
|
||||
elasticsearch: {
|
||||
index: [],
|
||||
cluster: [],
|
||||
},
|
||||
kibana: ['saved_object:entity-engine-status/all'],
|
||||
};
|
||||
|
||||
const result = getMissingPrivilegesErrorMessage(missingPrivileges);
|
||||
|
||||
expect(result).toBe(`Missing [saved_object:entity-engine-status/all] Kibana privileges.`);
|
||||
});
|
||||
|
||||
it('should return an empty string if there are no missing privileges', () => {
|
||||
const missingPrivileges: MissingPrivileges = {
|
||||
elasticsearch: {
|
||||
index: [],
|
||||
cluster: [],
|
||||
},
|
||||
kibana: [],
|
||||
};
|
||||
|
||||
const result = getMissingPrivilegesErrorMessage(missingPrivileges);
|
||||
|
||||
expect(result).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,7 +7,17 @@
|
|||
|
||||
import type { EntityAnalyticsPrivileges } from '../api/entity_analytics';
|
||||
|
||||
export const getAllMissingPrivileges = (privilege: EntityAnalyticsPrivileges) => {
|
||||
export interface MissingPrivileges {
|
||||
elasticsearch: {
|
||||
index: Array<{ indexName: string; privileges: string[] }>;
|
||||
cluster: string[];
|
||||
};
|
||||
kibana: string[];
|
||||
}
|
||||
|
||||
export const getAllMissingPrivileges = (
|
||||
privilege: EntityAnalyticsPrivileges
|
||||
): MissingPrivileges => {
|
||||
const esPrivileges = privilege.privileges.elasticsearch;
|
||||
const kbnPrivileges = privilege.privileges.kibana;
|
||||
|
||||
|
@ -24,6 +34,20 @@ export const getAllMissingPrivileges = (privilege: EntityAnalyticsPrivileges) =>
|
|||
};
|
||||
};
|
||||
|
||||
export const getMissingPrivilegesErrorMessage = ({ elasticsearch, kibana }: MissingPrivileges) =>
|
||||
[
|
||||
...elasticsearch.index.map(
|
||||
({ indexName, privileges }) =>
|
||||
`Missing [${privileges.join(', ')}] privileges for index '${indexName}'.`
|
||||
),
|
||||
|
||||
...(elasticsearch.cluster.length > 0
|
||||
? [`Missing [${elasticsearch.cluster.join(', ')}] cluster privileges.`]
|
||||
: []),
|
||||
|
||||
...(kibana.length > 0 ? [`Missing [${kibana.join(', ')}] Kibana privileges.`] : []),
|
||||
].join('\n');
|
||||
|
||||
const filterUnauthorized = (obj: Record<string, boolean> | undefined) =>
|
||||
Object.entries(obj ?? {})
|
||||
.filter(([_, authorized]) => !authorized)
|
||||
|
|
|
@ -25,7 +25,17 @@ import moment from 'moment';
|
|||
import type { EntityDefinitionWithState } from '@kbn/entityManager-plugin/server/lib/entities/types';
|
||||
import type { EntityDefinition } from '@kbn/entities-schema';
|
||||
import type { estypes } from '@elastic/elasticsearch';
|
||||
import { getAllMissingPrivileges } from '../../../../common/entity_analytics/privileges';
|
||||
import { SO_ENTITY_DEFINITION_TYPE } from '@kbn/entityManager-plugin/server/saved_objects';
|
||||
import { RISK_SCORE_INDEX_PATTERN } from '../../../../common/constants';
|
||||
import {
|
||||
ENTITY_STORE_INDEX_PATTERN,
|
||||
ENTITY_STORE_REQUIRED_ES_CLUSTER_PRIVILEGES,
|
||||
ENTITY_STORE_SOURCE_REQUIRED_ES_INDEX_PRIVILEGES,
|
||||
} from '../../../../common/entity_analytics/entity_store/constants';
|
||||
import {
|
||||
getAllMissingPrivileges,
|
||||
getMissingPrivilegesErrorMessage,
|
||||
} from '../../../../common/entity_analytics/privileges';
|
||||
import { merge } from '../../../../common/utils/objects/merge';
|
||||
import { getEnabledStoreEntityTypes } from '../../../../common/entity_analytics/entity_store/utils';
|
||||
import { EntityType } from '../../../../common/entity_analytics/types';
|
||||
|
@ -101,8 +111,9 @@ import {
|
|||
import { CRITICALITY_VALUES } from '../asset_criticality/constants';
|
||||
import { createEngineDescription } from './installation/engine_description';
|
||||
import { convertToEntityManagerDefinition } from './entity_definitions/entity_manager_conversion';
|
||||
import { getEntityStoreSourceIndicesPrivileges } from './utils/get_entity_store_privileges';
|
||||
import type { ApiKeyManager } from './auth/api_key';
|
||||
import { checkAndFormatPrivileges } from '../utils/check_and_format_privileges';
|
||||
import { entityEngineDescriptorTypeName } from './saved_object';
|
||||
|
||||
// Workaround. TransformState type is wrong. The health type should be: TransformHealth from '@kbn/transform-plugin/common/types/transform_stats'
|
||||
export interface TransformHealth extends estypes.TransformGetTransformStatsTransformStatsHealth {
|
||||
|
@ -838,22 +849,15 @@ export class EntityStoreDataClient {
|
|||
);
|
||||
}
|
||||
|
||||
const privileges = await getEntityStoreSourceIndicesPrivileges(
|
||||
this.options.request,
|
||||
this.options.security,
|
||||
indexPatterns
|
||||
);
|
||||
const privileges = await this.getEntityStoreSourceIndicesPrivileges(indexPatterns);
|
||||
|
||||
if (!privileges.has_all_required) {
|
||||
const missingPrivilegesMsg = getAllMissingPrivileges(privileges).elasticsearch.index.map(
|
||||
({ indexName, privileges: missingPrivileges }) =>
|
||||
`Missing [${missingPrivileges.join(', ')}] privileges for index '${indexName}'.`
|
||||
const missingPrivilegesMsg = getMissingPrivilegesErrorMessage(
|
||||
getAllMissingPrivileges(privileges)
|
||||
);
|
||||
|
||||
throw new Error(
|
||||
`The current user does not have the required indices privileges for updating the '${
|
||||
engine.type
|
||||
}' entity store.\n${missingPrivilegesMsg.join('\n')}`
|
||||
`The current user does not have the required indices privileges for updating the '${engine.type}' entity store.\n${missingPrivilegesMsg}`
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -899,6 +903,61 @@ export class EntityStoreDataClient {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index privileges required for installing all entity store resources
|
||||
*/
|
||||
public getEntityStoreInitPrivileges = async (indices: string[]) => {
|
||||
const security = this.options.security;
|
||||
|
||||
// The entity store needs access to all security solution indices
|
||||
const indicesPrivileges = this.getEntityStoreSourceRequiredIndicesPrivileges(indices);
|
||||
|
||||
// The entity store has to create the following indices
|
||||
indicesPrivileges[ENTITY_STORE_INDEX_PATTERN] = ['read', 'manage'];
|
||||
indicesPrivileges[RISK_SCORE_INDEX_PATTERN] = ['read', 'manage'];
|
||||
|
||||
return checkAndFormatPrivileges({
|
||||
request: this.options.request,
|
||||
security,
|
||||
privilegesToCheck: {
|
||||
kibana: [
|
||||
security.authz.actions.savedObject.get(entityEngineDescriptorTypeName, 'create'),
|
||||
security.authz.actions.savedObject.get(SO_ENTITY_DEFINITION_TYPE, 'create'),
|
||||
],
|
||||
elasticsearch: {
|
||||
cluster: ENTITY_STORE_REQUIRED_ES_CLUSTER_PRIVILEGES,
|
||||
index: indicesPrivileges,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the index privileges required for running the transform
|
||||
*/
|
||||
public getEntityStoreSourceIndicesPrivileges = (indexPatterns: string[]) => {
|
||||
const requiredIndicesPrivileges =
|
||||
this.getEntityStoreSourceRequiredIndicesPrivileges(indexPatterns);
|
||||
|
||||
return checkAndFormatPrivileges({
|
||||
request: this.options.request,
|
||||
security: this.options.security,
|
||||
privilegesToCheck: {
|
||||
elasticsearch: {
|
||||
cluster: [],
|
||||
index: requiredIndicesPrivileges,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
private getEntityStoreSourceRequiredIndicesPrivileges(securitySolutionIndices: string[]) {
|
||||
return securitySolutionIndices.reduce<Record<string, string[]>>((acc, index) => {
|
||||
acc[index] = ENTITY_STORE_SOURCE_REQUIRED_ES_INDEX_PRIVILEGES;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
private log(
|
||||
level: Exclude<keyof Logger, 'get' | 'log' | 'isLevelEnabled'>,
|
||||
entityType: EntityType,
|
||||
|
|
|
@ -10,6 +10,10 @@ import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils';
|
|||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
|
||||
|
||||
import {
|
||||
getAllMissingPrivileges,
|
||||
getMissingPrivilegesErrorMessage,
|
||||
} from '../../../../../common/entity_analytics/privileges';
|
||||
import { EntityType } from '../../../../../common/search_strategy';
|
||||
import type { InitEntityEngineResponse } from '../../../../../common/api/entity_analytics/entity_store/engine/init.gen';
|
||||
import {
|
||||
|
@ -20,6 +24,7 @@ import { API_VERSIONS, APP_ID } from '../../../../../common/constants';
|
|||
import type { EntityAnalyticsRoutesDeps } from '../../types';
|
||||
import { checkAndInitAssetCriticalityResources } from '../../asset_criticality/check_and_init_asset_criticality_resources';
|
||||
import { buildInitRequestBodyValidation } from './validation';
|
||||
import { buildIndexPatterns } from '../utils';
|
||||
|
||||
export const initEntityEngineRoute = (
|
||||
router: EntityAnalyticsRoutesDeps['router'],
|
||||
|
@ -51,15 +56,40 @@ export const initEntityEngineRoute = (
|
|||
const siemResponse = buildSiemResponse(response);
|
||||
const secSol = await context.securitySolution;
|
||||
const { pipelineDebugMode } = config.entityAnalytics.entityStore.developer;
|
||||
|
||||
await checkAndInitAssetCriticalityResources(context, logger);
|
||||
const { getSpaceId, getAppClient, getDataViewsService } = await context.securitySolution;
|
||||
const entityStoreClient = secSol.getEntityStoreDataClient();
|
||||
|
||||
try {
|
||||
const body: InitEntityEngineResponse = await secSol
|
||||
.getEntityStoreDataClient()
|
||||
.init(EntityType[request.params.entityType], request.body, {
|
||||
pipelineDebugMode,
|
||||
const securitySolutionIndices = await buildIndexPatterns(
|
||||
getSpaceId(),
|
||||
getAppClient(),
|
||||
getDataViewsService()
|
||||
);
|
||||
|
||||
const privileges = await entityStoreClient.getEntityStoreInitPrivileges(
|
||||
securitySolutionIndices
|
||||
);
|
||||
|
||||
if (!privileges.has_all_required) {
|
||||
const missingPrivilegesMsg = getMissingPrivilegesErrorMessage(
|
||||
getAllMissingPrivileges(privileges)
|
||||
);
|
||||
|
||||
return siemResponse.error({
|
||||
statusCode: 403,
|
||||
body: `User does not have the required privileges to initialize the entity engine\n${missingPrivilegesMsg}`,
|
||||
});
|
||||
}
|
||||
|
||||
await checkAndInitAssetCriticalityResources(context, logger);
|
||||
|
||||
const body: InitEntityEngineResponse = await entityStoreClient.init(
|
||||
EntityType[request.params.entityType],
|
||||
request.body,
|
||||
{
|
||||
pipelineDebugMode,
|
||||
}
|
||||
);
|
||||
|
||||
return response.ok({ body });
|
||||
} catch (e) {
|
||||
|
|
|
@ -13,7 +13,6 @@ import { APP_ID, API_VERSIONS } from '../../../../../common/constants';
|
|||
|
||||
import type { EntityAnalyticsRoutesDeps } from '../../types';
|
||||
import { AUDIT_CATEGORY, AUDIT_OUTCOME, AUDIT_TYPE } from '../../audit';
|
||||
import { getEntityStorePrivileges } from '../utils/get_entity_store_privileges';
|
||||
import { buildIndexPatterns } from '../utils';
|
||||
|
||||
export const entityStoreInternalPrivilegesRoute = (
|
||||
|
@ -43,7 +42,6 @@ export const entityStoreInternalPrivilegesRoute = (
|
|||
): Promise<IKibanaResponse<EntityStoreGetPrivilegesResponse>> => {
|
||||
const siemResponse = buildSiemResponse(response);
|
||||
try {
|
||||
const [_, { security }] = await getStartServices();
|
||||
const { getSpaceId, getAppClient, getDataViewsService } = await context.securitySolution;
|
||||
|
||||
const securitySolution = await context.securitySolution;
|
||||
|
@ -62,7 +60,10 @@ export const entityStoreInternalPrivilegesRoute = (
|
|||
getAppClient(),
|
||||
getDataViewsService()
|
||||
);
|
||||
const body = await getEntityStorePrivileges(request, security, securitySolutionIndices);
|
||||
|
||||
const body = await securitySolution
|
||||
.getEntityStoreDataClient()
|
||||
.getEntityStoreInitPrivileges(securitySolutionIndices);
|
||||
|
||||
return response.ok({ body });
|
||||
} catch (e) {
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
/*
|
||||
* 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 { KibanaRequest } from '@kbn/core/server';
|
||||
import type { SecurityPluginStart } from '@kbn/security-plugin/server';
|
||||
import { SO_ENTITY_DEFINITION_TYPE } from '@kbn/entityManager-plugin/server/saved_objects';
|
||||
import { RISK_SCORE_INDEX_PATTERN } from '../../../../../common/constants';
|
||||
import {
|
||||
ENTITY_STORE_INDEX_PATTERN,
|
||||
ENTITY_STORE_REQUIRED_ES_CLUSTER_PRIVILEGES,
|
||||
ENTITY_STORE_SOURCE_REQUIRED_ES_INDEX_PRIVILEGES,
|
||||
} from '../../../../../common/entity_analytics/entity_store/constants';
|
||||
import { checkAndFormatPrivileges } from '../../utils/check_and_format_privileges';
|
||||
import { entityEngineDescriptorTypeName } from '../saved_object';
|
||||
|
||||
export const getEntityStorePrivileges = (
|
||||
request: KibanaRequest,
|
||||
security: SecurityPluginStart,
|
||||
securitySolutionIndices: string[]
|
||||
) => {
|
||||
// The entity store needs access to all security solution indices
|
||||
const indicesPrivileges = getEntityStoreSourceRequiredIndicesPrivileges(securitySolutionIndices);
|
||||
|
||||
// The entity store has to create the following indices
|
||||
indicesPrivileges[ENTITY_STORE_INDEX_PATTERN] = ['read', 'manage'];
|
||||
indicesPrivileges[RISK_SCORE_INDEX_PATTERN] = ['read', 'manage'];
|
||||
|
||||
return checkAndFormatPrivileges({
|
||||
request,
|
||||
security,
|
||||
privilegesToCheck: {
|
||||
kibana: [
|
||||
security.authz.actions.savedObject.get(entityEngineDescriptorTypeName, 'create'),
|
||||
security.authz.actions.savedObject.get(SO_ENTITY_DEFINITION_TYPE, 'create'),
|
||||
],
|
||||
elasticsearch: {
|
||||
cluster: ENTITY_STORE_REQUIRED_ES_CLUSTER_PRIVILEGES,
|
||||
index: indicesPrivileges,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Get the index privileges required for running the transform
|
||||
export const getEntityStoreSourceIndicesPrivileges = (
|
||||
request: KibanaRequest,
|
||||
security: SecurityPluginStart,
|
||||
indexPatterns: string[]
|
||||
) => {
|
||||
const requiredIndicesPrivileges = getEntityStoreSourceRequiredIndicesPrivileges(indexPatterns);
|
||||
|
||||
return checkAndFormatPrivileges({
|
||||
request,
|
||||
security,
|
||||
privilegesToCheck: {
|
||||
elasticsearch: {
|
||||
cluster: [],
|
||||
index: requiredIndicesPrivileges,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const getEntityStoreSourceRequiredIndicesPrivileges = (securitySolutionIndices: string[]) => {
|
||||
return securitySolutionIndices.reduce<Record<string, string[]>>((acc, index) => {
|
||||
acc[index] = ENTITY_STORE_SOURCE_REQUIRED_ES_INDEX_PRIVILEGES;
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
|
@ -36,7 +36,7 @@ export const riskEngineInitRoute = (
|
|||
{ version: '1', validate: {} },
|
||||
withRiskEnginePrivilegeCheck(
|
||||
getStartServices,
|
||||
async (context, request, response): Promise<IKibanaResponse<InitRiskEngineResponse>> => {
|
||||
async (context, _request, response): Promise<IKibanaResponse<InitRiskEngineResponse>> => {
|
||||
const securitySolution = await context.securitySolution;
|
||||
|
||||
securitySolution.getAuditLogger()?.log({
|
||||
|
|
|
@ -55,8 +55,17 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
|
||||
it('should return "error" when the security data view does not exist', async () => {
|
||||
await dataView.delete('security-solution');
|
||||
await utils.initEntityEngineForEntityType('host');
|
||||
await utils.waitForEngineStatus('host', 'error');
|
||||
|
||||
const { body, status } = await api.initEntityEngine(
|
||||
{
|
||||
params: { entityType: 'host' },
|
||||
body: {},
|
||||
},
|
||||
'default'
|
||||
);
|
||||
|
||||
expect(status).toEqual(500);
|
||||
expect(body.message).toContain('Data view not found');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue