From 94d7df3ae7ae6c482906ec8946e61a62e05b1960 Mon Sep 17 00:00:00 2001 From: Milton Hultgren Date: Thu, 14 Nov 2024 09:32:57 +0100 Subject: [PATCH] [EEM] Disable authorization checks on endpoints (#198695) Disable authorization checks on all entity manager endpoints. Also makes two notable changes to the endpoints/EntityClient behaviour: - previously the EntityClient accepted a `IScopedClusterClient` and abstracted usage of asInternalUser/asCurrentUser in its methods which may result in unwanted behavior for consumers. It now only accepts an `ElasticsearchClient` that is preauthenticated by the consumers - added permissions verifications to custom definition endpoints --------- Co-authored-by: Kevin Lacabane Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../server/lib/auth/api_key/api_key.ts | 3 +- .../server/lib/auth/privileges.ts | 22 ++++---- .../lib/entities/find_entity_definition.ts | 28 ++--------- .../install_entity_definition.test.ts | 50 +++++++------------ .../lib/entities/install_entity_definition.ts | 24 ++++----- .../lib/entities/upgrade_entity_definition.ts | 8 ++- .../server/lib/entity_client.ts | 43 +++++----------- .../entity_manager/server/lib/utils.ts | 8 +-- .../plugins/entity_manager/server/plugin.ts | 4 +- .../server/routes/enablement/check.ts | 13 ++++- .../server/routes/enablement/disable.ts | 9 ++++ .../server/routes/enablement/enable.ts | 15 ++++-- .../server/routes/entities/create.ts | 26 +++++++++- .../server/routes/entities/delete.ts | 23 ++++++++- .../server/routes/entities/get.ts | 9 ++++ .../server/routes/entities/reset.ts | 9 ++++ .../server/routes/entities/update.ts | 39 +++++++++++++-- .../entity_store/entity_store_data_client.ts | 2 +- .../apis/entity_manager/helpers/user.ts | 5 +- 19 files changed, 209 insertions(+), 131 deletions(-) diff --git a/x-pack/plugins/entity_manager/server/lib/auth/api_key/api_key.ts b/x-pack/plugins/entity_manager/server/lib/auth/api_key/api_key.ts index 2535bcc4d64f..8dc499cb7fb4 100644 --- a/x-pack/plugins/entity_manager/server/lib/auth/api_key/api_key.ts +++ b/x-pack/plugins/entity_manager/server/lib/auth/api_key/api_key.ts @@ -9,6 +9,7 @@ import { KibanaRequest } from '@kbn/core-http-server'; import { getFakeKibanaRequest } from '@kbn/security-plugin/server/authentication/api_keys/fake_kibana_request'; import { EntityManagerServerSetup } from '../../../types'; import { canManageEntityDefinition, entityDefinitionRuntimePrivileges } from '../privileges'; +import { BUILT_IN_ALLOWED_INDICES } from '../../entities/built_in/constants'; export interface EntityDiscoveryAPIKey { id: string; @@ -45,7 +46,7 @@ export const checkIfEntityDiscoveryAPIKeyIsValid = async ( server.logger.debug('validating API key has runtime privileges for entity discovery'); - return canManageEntityDefinition(esClient); + return canManageEntityDefinition(esClient, BUILT_IN_ALLOWED_INDICES); }; export const generateEntityDiscoveryAPIKey = async ( diff --git a/x-pack/plugins/entity_manager/server/lib/auth/privileges.ts b/x-pack/plugins/entity_manager/server/lib/auth/privileges.ts index 674e870c898b..87f17b93ea57 100644 --- a/x-pack/plugins/entity_manager/server/lib/auth/privileges.ts +++ b/x-pack/plugins/entity_manager/server/lib/auth/privileges.ts @@ -10,15 +10,18 @@ import { ENTITY_INTERNAL_INDICES_PATTERN } from '../../../common/constants_entit import { SO_ENTITY_DEFINITION_TYPE, SO_ENTITY_DISCOVERY_API_KEY_TYPE } from '../../saved_objects'; import { BUILT_IN_ALLOWED_INDICES } from '../entities/built_in/constants'; -export const canManageEntityDefinition = async (client: ElasticsearchClient) => { +export const canManageEntityDefinition = async ( + client: ElasticsearchClient, + sourceIndices: string[] +) => { const { has_all_requested: hasAllRequested } = await client.security.hasPrivileges({ - body: entityDefinitionRuntimePrivileges, + body: entityDefinitionRuntimePrivileges(sourceIndices), }); return hasAllRequested; }; -const canDeleteEntityDefinition = async (client: ElasticsearchClient) => { +export const canDeleteEntityDefinition = async (client: ElasticsearchClient) => { const { has_all_requested: hasAllRequested } = await client.security.hasPrivileges({ body: entityDefinitionDeletionPrivileges, }); @@ -43,9 +46,10 @@ const canDeleteAPIKey = async (client: ElasticsearchClient) => { }; export const canEnableEntityDiscovery = async (client: ElasticsearchClient) => { - return Promise.all([canManageAPIKey(client), canManageEntityDefinition(client)]).then((results) => - results.every(Boolean) - ); + return Promise.all([ + canManageAPIKey(client), + canManageEntityDefinition(client, BUILT_IN_ALLOWED_INDICES), + ]).then((results) => results.every(Boolean)); }; export const canDisableEntityDiscovery = async (client: ElasticsearchClient) => { @@ -54,7 +58,7 @@ export const canDisableEntityDiscovery = async (client: ElasticsearchClient) => ); }; -export const entityDefinitionRuntimePrivileges = { +export const entityDefinitionRuntimePrivileges = (sourceIndices: string[]) => ({ cluster: ['manage_transform', 'manage_ingest_pipelines', 'manage_index_templates'], index: [ { @@ -62,7 +66,7 @@ export const entityDefinitionRuntimePrivileges = { privileges: ['create_index', 'delete_index', 'index', 'create_doc', 'auto_configure', 'read'], }, { - names: [...BUILT_IN_ALLOWED_INDICES, ENTITY_INTERNAL_INDICES_PATTERN], + names: [...sourceIndices, ENTITY_INTERNAL_INDICES_PATTERN], privileges: ['read', 'view_index_metadata'], }, ], @@ -73,7 +77,7 @@ export const entityDefinitionRuntimePrivileges = { resources: ['*'], }, ], -}; +}); export const entityDefinitionDeletionPrivileges = { cluster: ['manage_transform', 'manage_ingest_pipelines', 'manage_index_templates'], diff --git a/x-pack/plugins/entity_manager/server/lib/entities/find_entity_definition.ts b/x-pack/plugins/entity_manager/server/lib/entities/find_entity_definition.ts index cfbb5a5ef555..2c51f2f627a7 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/find_entity_definition.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/find_entity_definition.ts @@ -5,10 +5,9 @@ * 2.0. */ -import { compact, forEach, reduce } from 'lodash'; +import { compact } from 'lodash'; import { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; import { EntityDefinition } from '@kbn/entities-schema'; -import { NodesIngestTotal } from '@elastic/elasticsearch/lib/api/types'; import { SO_ENTITY_DEFINITION_TYPE } from '../../saved_objects'; import { BUILT_IN_ID_PREFIX } from './built_in'; import { EntityDefinitionState, EntityDefinitionWithState } from './types'; @@ -144,33 +143,14 @@ async function getIngestPipelineState({ .filter(({ type }) => type === 'ingest_pipeline') .map(({ id }) => id); - const [ingestPipelines, ingestPipelinesStats] = await Promise.all([ - esClient.ingest.getPipeline({ id: ingestPipelineIds.join(',') }, { ignore: [404] }), - esClient.nodes.stats({ - metric: 'ingest', - filter_path: ingestPipelineIds.map((id) => `nodes.*.ingest.pipelines.${id}`), - }), - ]); - - const ingestStatsByPipeline = reduce( - ingestPipelinesStats.nodes, - (pipelines, { ingest }) => { - forEach(ingest?.pipelines, (value: NodesIngestTotal, key: string) => { - if (!pipelines[key]) { - pipelines[key] = { count: 0, failed: 0 }; - } - pipelines[key].count += value.count ?? 0; - pipelines[key].failed += value.failed ?? 0; - }); - return pipelines; - }, - {} as Record + const ingestPipelines = await esClient.ingest.getPipeline( + { id: ingestPipelineIds.join(',') }, + { ignore: [404] } ); return ingestPipelineIds.map((id) => ({ id, installed: !!ingestPipelines[id], - stats: ingestStatsByPipeline[id], })); } diff --git a/x-pack/plugins/entity_manager/server/lib/entities/install_entity_definition.test.ts b/x-pack/plugins/entity_manager/server/lib/entities/install_entity_definition.test.ts index 4633885b5138..1d3d0c21bc6d 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/install_entity_definition.test.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/install_entity_definition.test.ts @@ -260,7 +260,7 @@ describe('install_entity_definition', () => { describe('installBuiltInEntityDefinitions', () => { it('should install definition when not found', async () => { const builtInDefinitions = [mockEntityDefinition]; - const clusterClient = elasticsearchClientMock.createScopedClusterClient(); + const esClient = elasticsearchClientMock.createElasticsearchClient(); const soClient = savedObjectsClientMock.create(); soClient.find.mockResolvedValue({ saved_objects: [], total: 0, page: 1, per_page: 10 }); soClient.update.mockResolvedValue({ @@ -271,19 +271,18 @@ describe('install_entity_definition', () => { }); await installBuiltInEntityDefinitions({ - clusterClient, + esClient, soClient, definitions: builtInDefinitions, logger: loggerMock.create(), }); - assertHasCreatedDefinition(mockEntityDefinition, soClient, clusterClient.asSecondaryAuthUser); + assertHasCreatedDefinition(mockEntityDefinition, soClient, esClient); }); it('should reinstall when partial state found', async () => { const builtInDefinitions = [mockEntityDefinition]; - const clusterClient = elasticsearchClientMock.createScopedClusterClient(); - const esClient = clusterClient.asInternalUser; + const esClient = elasticsearchClientMock.createElasticsearchClient(); // mock partially installed definition esClient.ingest.getPipeline.mockResolvedValue({}); esClient.transform.getTransformStats.mockResolvedValue({ transforms: [], count: 0 }); @@ -315,18 +314,14 @@ describe('install_entity_definition', () => { }); await installBuiltInEntityDefinitions({ - clusterClient, + esClient, soClient, definitions: builtInDefinitions, logger: loggerMock.create(), }); - assertHasDeletedTransforms(mockEntityDefinition, clusterClient.asSecondaryAuthUser); - assertHasUpgradedDefinition( - mockEntityDefinition, - soClient, - clusterClient.asSecondaryAuthUser - ); + assertHasDeletedTransforms(mockEntityDefinition, esClient); + assertHasUpgradedDefinition(mockEntityDefinition, soClient, esClient); }); it('should reinstall when outdated version', async () => { @@ -334,8 +329,7 @@ describe('install_entity_definition', () => { ...mockEntityDefinition, version: semver.inc(mockEntityDefinition.version, 'major') ?? '0.0.0', }; - const clusterClient = elasticsearchClientMock.createScopedClusterClient(); - const esClient = clusterClient.asInternalUser; + const esClient = elasticsearchClientMock.createElasticsearchClient(); esClient.transform.getTransformStats.mockResolvedValue({ transforms: [], count: 0 }); const soClient = savedObjectsClientMock.create(); @@ -365,14 +359,14 @@ describe('install_entity_definition', () => { }); await installBuiltInEntityDefinitions({ - clusterClient, + esClient, soClient, definitions: [updatedDefinition], logger: loggerMock.create(), }); - assertHasDeletedTransforms(mockEntityDefinition, clusterClient.asSecondaryAuthUser); - assertHasUpgradedDefinition(updatedDefinition, soClient, clusterClient.asSecondaryAuthUser); + assertHasDeletedTransforms(mockEntityDefinition, esClient); + assertHasUpgradedDefinition(updatedDefinition, soClient, esClient); }); it('should reinstall when stale upgrade', async () => { @@ -380,8 +374,7 @@ describe('install_entity_definition', () => { ...mockEntityDefinition, version: semver.inc(mockEntityDefinition.version, 'major') ?? '0.0.0', }; - const clusterClient = elasticsearchClientMock.createScopedClusterClient(); - const esClient = clusterClient.asInternalUser; + const esClient = elasticsearchClientMock.createElasticsearchClient(); esClient.transform.getTransformStats.mockResolvedValue({ transforms: [], count: 0 }); const soClient = savedObjectsClientMock.create(); @@ -413,19 +406,18 @@ describe('install_entity_definition', () => { }); await installBuiltInEntityDefinitions({ - clusterClient, + esClient, soClient, definitions: [updatedDefinition], logger: loggerMock.create(), }); - assertHasDeletedTransforms(mockEntityDefinition, clusterClient.asSecondaryAuthUser); - assertHasUpgradedDefinition(updatedDefinition, soClient, clusterClient.asSecondaryAuthUser); + assertHasDeletedTransforms(mockEntityDefinition, esClient); + assertHasUpgradedDefinition(updatedDefinition, soClient, esClient); }); it('should reinstall when failed installation', async () => { - const clusterClient = elasticsearchClientMock.createScopedClusterClient(); - const esClient = clusterClient.asInternalUser; + const esClient = elasticsearchClientMock.createElasticsearchClient(); esClient.transform.getTransformStats.mockResolvedValue({ transforms: [], count: 0 }); const soClient = savedObjectsClientMock.create(); @@ -456,18 +448,14 @@ describe('install_entity_definition', () => { }); await installBuiltInEntityDefinitions({ - clusterClient, + esClient, soClient, definitions: [mockEntityDefinition], logger: loggerMock.create(), }); - assertHasDeletedTransforms(mockEntityDefinition, clusterClient.asSecondaryAuthUser); - assertHasUpgradedDefinition( - mockEntityDefinition, - soClient, - clusterClient.asSecondaryAuthUser - ); + assertHasDeletedTransforms(mockEntityDefinition, esClient); + assertHasUpgradedDefinition(mockEntityDefinition, soClient, esClient); }); }); }); diff --git a/x-pack/plugins/entity_manager/server/lib/entities/install_entity_definition.ts b/x-pack/plugins/entity_manager/server/lib/entities/install_entity_definition.ts index bfc37ac52e2c..3396ee3aa231 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/install_entity_definition.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/install_entity_definition.ts @@ -6,7 +6,7 @@ */ import semver from 'semver'; -import { ElasticsearchClient, IScopedClusterClient } from '@kbn/core-elasticsearch-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { EntityDefinition, EntityDefinitionUpdate } from '@kbn/entities-schema'; import { Logger } from '@kbn/logging'; @@ -88,12 +88,12 @@ export async function installEntityDefinition({ } export async function installBuiltInEntityDefinitions({ - clusterClient, + esClient, soClient, logger, definitions, }: Omit & { - clusterClient: IScopedClusterClient; + esClient: ElasticsearchClient; definitions: EntityDefinition[]; }): Promise { if (definitions.length === 0) return []; @@ -102,18 +102,18 @@ export async function installBuiltInEntityDefinitions({ const installPromises = definitions.map(async (builtInDefinition) => { const installedDefinition = await findEntityDefinitionById({ soClient, - esClient: clusterClient.asInternalUser, + esClient, id: builtInDefinition.id, includeState: true, }); if (!installedDefinition) { // clean data from previous installation - await deleteIndices(clusterClient.asCurrentUser, builtInDefinition, logger); + await deleteIndices(esClient, builtInDefinition, logger); return await installEntityDefinition({ definition: builtInDefinition, - esClient: clusterClient.asSecondaryAuthUser, + esClient, soClient, logger, }); @@ -134,7 +134,7 @@ export async function installBuiltInEntityDefinitions({ ); return await reinstallEntityDefinition({ soClient, - clusterClient, + esClient, logger, definition: installedDefinition, definitionUpdate: builtInDefinition, @@ -174,14 +174,14 @@ async function install({ // stop and delete the current transforms and reinstall all the components export async function reinstallEntityDefinition({ - clusterClient, + esClient, soClient, definition, definitionUpdate, logger, deleteData = false, }: Omit & { - clusterClient: IScopedClusterClient; + esClient: ElasticsearchClient; definitionUpdate: EntityDefinitionUpdate; deleteData?: boolean; }): Promise { @@ -202,16 +202,16 @@ export async function reinstallEntityDefinition({ }); logger.debug(`Deleting transforms for definition [${definition.id}] v${definition.version}`); - await stopAndDeleteTransforms(clusterClient.asSecondaryAuthUser, definition, logger); + await stopAndDeleteTransforms(esClient, definition, logger); if (deleteData) { - await deleteIndices(clusterClient.asCurrentUser, definition, logger); + await deleteIndices(esClient, definition, logger); } return await install({ soClient, logger, - esClient: clusterClient.asSecondaryAuthUser, + esClient, definition: updatedDefinition, }); } catch (err) { diff --git a/x-pack/plugins/entity_manager/server/lib/entities/upgrade_entity_definition.ts b/x-pack/plugins/entity_manager/server/lib/entities/upgrade_entity_definition.ts index 66bc6da96619..a4d44cd45ee1 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/upgrade_entity_definition.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/upgrade_entity_definition.ts @@ -35,20 +35,18 @@ export async function upgradeBuiltInEntityDefinitions({ ); } - const { clusterClient, soClient } = getClientsFromAPIKey({ apiKey, server }); + const { esClient, soClient } = getClientsFromAPIKey({ apiKey, server }); logger.debug(`Starting built-in definitions upgrade`); const upgradedDefinitions = await installBuiltInEntityDefinitions({ - clusterClient, + esClient, soClient, definitions, logger, }); await Promise.all( - upgradedDefinitions.map((definition) => - startTransforms(clusterClient.asSecondaryAuthUser, definition, logger) - ) + upgradedDefinitions.map((definition) => startTransforms(esClient, definition, logger)) ); return { success: true, definitions: upgradedDefinitions }; diff --git a/x-pack/plugins/entity_manager/server/lib/entity_client.ts b/x-pack/plugins/entity_manager/server/lib/entity_client.ts index 4e1dd263f9ca..8bb51941092f 100644 --- a/x-pack/plugins/entity_manager/server/lib/entity_client.ts +++ b/x-pack/plugins/entity_manager/server/lib/entity_client.ts @@ -7,7 +7,7 @@ import { EntityDefinition, EntityDefinitionUpdate } from '@kbn/entities-schema'; import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { Logger } from '@kbn/logging'; import { installEntityDefinition, @@ -27,7 +27,7 @@ import { EntityDefinitionUpdateConflict } from './entities/errors/entity_definit export class EntityClient { constructor( private options: { - clusterClient: IScopedClusterClient; + esClient: ElasticsearchClient; soClient: SavedObjectsClientContract; logger: Logger; } @@ -43,16 +43,15 @@ export class EntityClient { this.options.logger.info( `Creating definition [${definition.id}] v${definition.version} (installOnly=${installOnly})` ); - const secondaryAuthClient = this.options.clusterClient.asSecondaryAuthUser; const installedDefinition = await installEntityDefinition({ definition, - esClient: secondaryAuthClient, + esClient: this.options.esClient, soClient: this.options.soClient, logger: this.options.logger, }); if (!installOnly) { - await startTransforms(secondaryAuthClient, installedDefinition, this.options.logger); + await startTransforms(this.options.esClient, installedDefinition, this.options.logger); } return installedDefinition; @@ -68,7 +67,7 @@ export class EntityClient { const definition = await findEntityDefinitionById({ id, soClient: this.options.soClient, - esClient: this.options.clusterClient.asInternalUser, + esClient: this.options.esClient, includeState: true, }); @@ -95,16 +94,12 @@ export class EntityClient { definition, definitionUpdate, soClient: this.options.soClient, - clusterClient: this.options.clusterClient, + esClient: this.options.esClient, logger: this.options.logger, }); if (shouldRestartTransforms) { - await startTransforms( - this.options.clusterClient.asSecondaryAuthUser, - updatedDefinition, - this.options.logger - ); + await startTransforms(this.options.esClient, updatedDefinition, this.options.logger); } return updatedDefinition; } @@ -112,7 +107,7 @@ export class EntityClient { async deleteEntityDefinition({ id, deleteData = false }: { id: string; deleteData?: boolean }) { const definition = await findEntityDefinitionById({ id, - esClient: this.options.clusterClient.asInternalUser, + esClient: this.options.esClient, soClient: this.options.soClient, }); @@ -125,7 +120,7 @@ export class EntityClient { ); await uninstallEntityDefinition({ definition, - esClient: this.options.clusterClient.asSecondaryAuthUser, + esClient: this.options.esClient, soClient: this.options.soClient, logger: this.options.logger, }); @@ -133,11 +128,7 @@ export class EntityClient { if (deleteData) { // delete data with current user as system user does not have // .entities privileges - await deleteIndices( - this.options.clusterClient.asCurrentUser, - definition, - this.options.logger - ); + await deleteIndices(this.options.esClient, definition, this.options.logger); } } @@ -157,7 +148,7 @@ export class EntityClient { builtIn?: boolean; }) { const definitions = await findEntityDefinitions({ - esClient: this.options.clusterClient.asInternalUser, + esClient: this.options.esClient, soClient: this.options.soClient, page, perPage, @@ -172,19 +163,11 @@ export class EntityClient { async startEntityDefinition(definition: EntityDefinition) { this.options.logger.info(`Starting transforms for definition [${definition.id}]`); - return startTransforms( - this.options.clusterClient.asSecondaryAuthUser, - definition, - this.options.logger - ); + return startTransforms(this.options.esClient, definition, this.options.logger); } async stopEntityDefinition(definition: EntityDefinition) { this.options.logger.info(`Stopping transforms for definition [${definition.id}]`); - return stopTransforms( - this.options.clusterClient.asSecondaryAuthUser, - definition, - this.options.logger - ); + return stopTransforms(this.options.esClient, definition, this.options.logger); } } diff --git a/x-pack/plugins/entity_manager/server/lib/utils.ts b/x-pack/plugins/entity_manager/server/lib/utils.ts index dfe14dcf20ab..d1d76e147efb 100644 --- a/x-pack/plugins/entity_manager/server/lib/utils.ts +++ b/x-pack/plugins/entity_manager/server/lib/utils.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { getFakeKibanaRequest } from '@kbn/security-plugin/server/authentication/api_keys/fake_kibana_request'; import { EntityManagerServerSetup } from '../types'; @@ -17,9 +17,9 @@ export const getClientsFromAPIKey = ({ }: { apiKey: EntityDiscoveryAPIKey; server: EntityManagerServerSetup; -}): { clusterClient: IScopedClusterClient; soClient: SavedObjectsClientContract } => { +}): { esClient: ElasticsearchClient; soClient: SavedObjectsClientContract } => { const fakeRequest = getFakeKibanaRequest({ id: apiKey.id, api_key: apiKey.apiKey }); - const clusterClient = server.core.elasticsearch.client.asScoped(fakeRequest); + const esClient = server.core.elasticsearch.client.asScoped(fakeRequest).asCurrentUser; const soClient = server.core.savedObjects.getScopedClient(fakeRequest); - return { clusterClient, soClient }; + return { esClient, soClient }; }; diff --git a/x-pack/plugins/entity_manager/server/plugin.ts b/x-pack/plugins/entity_manager/server/plugin.ts index 152b1b59b310..101fdde95c9d 100644 --- a/x-pack/plugins/entity_manager/server/plugin.ts +++ b/x-pack/plugins/entity_manager/server/plugin.ts @@ -99,9 +99,9 @@ export class EntityManagerServerPlugin request: KibanaRequest; coreStart: CoreStart; }) { - const clusterClient = coreStart.elasticsearch.client.asScoped(request); + const esClient = coreStart.elasticsearch.client.asScoped(request).asCurrentUser; const soClient = coreStart.savedObjects.getScopedClient(request); - return new EntityClient({ clusterClient, soClient, logger: this.logger }); + return new EntityClient({ esClient, soClient, logger: this.logger }); } public start( diff --git a/x-pack/plugins/entity_manager/server/routes/enablement/check.ts b/x-pack/plugins/entity_manager/server/routes/enablement/check.ts index 1c67643c5c90..5373ac9df50f 100644 --- a/x-pack/plugins/entity_manager/server/routes/enablement/check.ts +++ b/x-pack/plugins/entity_manager/server/routes/enablement/check.ts @@ -45,6 +45,15 @@ import { createEntityManagerServerRoute } from '../create_entity_manager_server_ */ export const checkEntityDiscoveryEnabledRoute = createEntityManagerServerRoute({ endpoint: 'GET /internal/entities/managed/enablement', + options: { + security: { + authz: { + enabled: false, + reason: + 'This endpoint leverages the security plugin to evaluate the privileges needed as part of its core flow', + }, + }, + }, handler: async ({ response, logger, server }) => { try { logger.debug('reading entity discovery API key from saved object'); @@ -61,13 +70,13 @@ export const checkEntityDiscoveryEnabledRoute = createEntityManagerServerRoute({ return response.ok({ body: { enabled: false, reason: ERROR_API_KEY_NOT_VALID } }); } - const { clusterClient, soClient } = getClientsFromAPIKey({ apiKey, server }); + const { esClient, soClient } = getClientsFromAPIKey({ apiKey, server }); const entityDiscoveryState = await Promise.all( builtInDefinitions.map(async (builtInDefinition) => { const definitions = await findEntityDefinitions({ soClient, - esClient: clusterClient.asSecondaryAuthUser, + esClient, id: builtInDefinition.id, includeState: true, }); diff --git a/x-pack/plugins/entity_manager/server/routes/enablement/disable.ts b/x-pack/plugins/entity_manager/server/routes/enablement/disable.ts index 01208fe19d7a..a71a317045c4 100644 --- a/x-pack/plugins/entity_manager/server/routes/enablement/disable.ts +++ b/x-pack/plugins/entity_manager/server/routes/enablement/disable.ts @@ -44,6 +44,15 @@ import { createEntityManagerServerRoute } from '../create_entity_manager_server_ */ export const disableEntityDiscoveryRoute = createEntityManagerServerRoute({ endpoint: 'DELETE /internal/entities/managed/enablement', + options: { + security: { + authz: { + enabled: false, + reason: + 'This endpoint leverages the security plugin to evaluate the privileges needed as part of its core flow', + }, + }, + }, params: z.object({ query: z.object({ deleteData: z.optional(BooleanFromString).default(false), diff --git a/x-pack/plugins/entity_manager/server/routes/enablement/enable.ts b/x-pack/plugins/entity_manager/server/routes/enablement/enable.ts index 9a851e08b567..562b798a598a 100644 --- a/x-pack/plugins/entity_manager/server/routes/enablement/enable.ts +++ b/x-pack/plugins/entity_manager/server/routes/enablement/enable.ts @@ -63,6 +63,15 @@ import { startTransforms } from '../../lib/entities/start_transforms'; */ export const enableEntityDiscoveryRoute = createEntityManagerServerRoute({ endpoint: 'PUT /internal/entities/managed/enablement', + options: { + security: { + authz: { + enabled: false, + reason: + 'This endpoint leverages the security plugin to evaluate the privileges needed as part of its core flow', + }, + }, + }, params: z.object({ query: createEntityDefinitionQuerySchema, }), @@ -120,9 +129,9 @@ export const enableEntityDiscoveryRoute = createEntityManagerServerRoute({ await saveEntityDiscoveryAPIKey(soClient, apiKey); - const clusterClient = core.elasticsearch.client; + const esClient = core.elasticsearch.client.asCurrentUser; const installedDefinitions = await installBuiltInEntityDefinitions({ - clusterClient, + esClient, soClient, logger, definitions: builtInDefinitions, @@ -131,7 +140,7 @@ export const enableEntityDiscoveryRoute = createEntityManagerServerRoute({ if (!params.query.installOnly) { await Promise.all( installedDefinitions.map((installedDefinition) => - startTransforms(clusterClient.asSecondaryAuthUser, installedDefinition, logger) + startTransforms(esClient, installedDefinition, logger) ) ); } diff --git a/x-pack/plugins/entity_manager/server/routes/entities/create.ts b/x-pack/plugins/entity_manager/server/routes/entities/create.ts index 114764444632..a22916f3e69f 100644 --- a/x-pack/plugins/entity_manager/server/routes/entities/create.ts +++ b/x-pack/plugins/entity_manager/server/routes/entities/create.ts @@ -12,6 +12,7 @@ import { EntityIdConflict } from '../../lib/entities/errors/entity_id_conflict_e import { EntitySecurityException } from '../../lib/entities/errors/entity_security_exception'; import { InvalidTransformError } from '../../lib/entities/errors/invalid_transform_error'; import { createEntityManagerServerRoute } from '../create_entity_manager_server_route'; +import { canManageEntityDefinition } from '../../lib/auth'; /** * @openapi @@ -49,12 +50,35 @@ import { createEntityManagerServerRoute } from '../create_entity_manager_server_ */ export const createEntityDefinitionRoute = createEntityManagerServerRoute({ endpoint: 'POST /internal/entities/definition', + options: { + security: { + authz: { + enabled: false, + reason: + 'This endpoint mainly manages Elasticsearch resources using the requesting users credentials', + }, + }, + }, params: z.object({ query: createEntityDefinitionQuerySchema, body: entityDefinitionSchema, }), - handler: async ({ request, response, params, logger, getScopedClient }) => { + handler: async ({ context, request, response, params, logger, getScopedClient }) => { try { + const currentUserClient = (await context.core).elasticsearch.client.asCurrentUser; + const isAuthorized = await canManageEntityDefinition( + currentUserClient, + params.body.indexPatterns + ); + if (!isAuthorized) { + return response.forbidden({ + body: { + message: + 'Current Kibana user does not have the required permissions to create the entity definition', + }, + }); + } + const client = await getScopedClient({ request }); const definition = await client.createEntityDefinition({ definition: params.body, diff --git a/x-pack/plugins/entity_manager/server/routes/entities/delete.ts b/x-pack/plugins/entity_manager/server/routes/entities/delete.ts index 840f3746bc9b..ff5b9624dbb3 100644 --- a/x-pack/plugins/entity_manager/server/routes/entities/delete.ts +++ b/x-pack/plugins/entity_manager/server/routes/entities/delete.ts @@ -14,6 +14,7 @@ import { EntityDefinitionNotFound } from '../../lib/entities/errors/entity_not_f import { EntitySecurityException } from '../../lib/entities/errors/entity_security_exception'; import { InvalidTransformError } from '../../lib/entities/errors/invalid_transform_error'; import { createEntityManagerServerRoute } from '../create_entity_manager_server_route'; +import { canDeleteEntityDefinition } from '../../lib/auth/privileges'; /** * @openapi @@ -51,12 +52,32 @@ import { createEntityManagerServerRoute } from '../create_entity_manager_server_ */ export const deleteEntityDefinitionRoute = createEntityManagerServerRoute({ endpoint: 'DELETE /internal/entities/definition/{id}', + options: { + security: { + authz: { + enabled: false, + reason: + 'This endpoint mainly manages Elasticsearch resources using the requesting users credentials', + }, + }, + }, params: z.object({ path: deleteEntityDefinitionParamsSchema, query: deleteEntityDefinitionQuerySchema, }), - handler: async ({ request, response, params, logger, getScopedClient }) => { + handler: async ({ context, request, response, params, logger, getScopedClient }) => { try { + const currentUserClient = (await context.core).elasticsearch.client.asCurrentUser; + const isAuthorized = await canDeleteEntityDefinition(currentUserClient); + if (!isAuthorized) { + return response.forbidden({ + body: { + message: + 'Current Kibana user does not have the required permissions to delete the entity definition', + }, + }); + } + const client = await getScopedClient({ request }); await client.deleteEntityDefinition({ id: params.path.id, diff --git a/x-pack/plugins/entity_manager/server/routes/entities/get.ts b/x-pack/plugins/entity_manager/server/routes/entities/get.ts index 8ec2489136fb..f22e0890e60a 100644 --- a/x-pack/plugins/entity_manager/server/routes/entities/get.ts +++ b/x-pack/plugins/entity_manager/server/routes/entities/get.ts @@ -50,6 +50,15 @@ import { createEntityManagerServerRoute } from '../create_entity_manager_server_ */ export const getEntityDefinitionRoute = createEntityManagerServerRoute({ endpoint: 'GET /internal/entities/definition/{id?}', + options: { + security: { + authz: { + enabled: false, + reason: + 'This endpoint mainly manages Elasticsearch resources using the requesting users credentials', + }, + }, + }, params: z.object({ query: getEntityDefinitionQuerySchema, path: z.object({ id: z.optional(z.string()) }), diff --git a/x-pack/plugins/entity_manager/server/routes/entities/reset.ts b/x-pack/plugins/entity_manager/server/routes/entities/reset.ts index 0b6942e335e5..ab4ba29fa148 100644 --- a/x-pack/plugins/entity_manager/server/routes/entities/reset.ts +++ b/x-pack/plugins/entity_manager/server/routes/entities/reset.ts @@ -25,6 +25,15 @@ import { stopTransforms } from '../../lib/entities/stop_transforms'; export const resetEntityDefinitionRoute = createEntityManagerServerRoute({ endpoint: 'POST /internal/entities/definition/{id}/_reset', + options: { + security: { + authz: { + enabled: false, + reason: + 'This endpoint mainly manages Elasticsearch resources using the requesting users credentials', + }, + }, + }, params: z.object({ path: resetEntityDefinitionParamsSchema, }), diff --git a/x-pack/plugins/entity_manager/server/routes/entities/update.ts b/x-pack/plugins/entity_manager/server/routes/entities/update.ts index 3b7ea8a03830..f1118028cda9 100644 --- a/x-pack/plugins/entity_manager/server/routes/entities/update.ts +++ b/x-pack/plugins/entity_manager/server/routes/entities/update.ts @@ -12,6 +12,8 @@ import { InvalidTransformError } from '../../lib/entities/errors/invalid_transfo import { createEntityManagerServerRoute } from '../create_entity_manager_server_route'; import { EntityDefinitionNotFound } from '../../lib/entities/errors/entity_not_found'; import { EntityDefinitionUpdateConflict } from '../../lib/entities/errors/entity_definition_update_conflict'; +import { findEntityDefinitionById } from '../../lib/entities/find_entity_definition'; +import { canManageEntityDefinition } from '../../lib/auth'; /** * @openapi @@ -52,14 +54,45 @@ import { EntityDefinitionUpdateConflict } from '../../lib/entities/errors/entity */ export const updateEntityDefinitionRoute = createEntityManagerServerRoute({ endpoint: 'PATCH /internal/entities/definition/{id}', + options: { + security: { + authz: { + enabled: false, + reason: + 'This endpoint mainly manages Elasticsearch resources using the requesting users credentials', + }, + }, + }, params: z.object({ path: z.object({ id: z.string() }), body: entityDefinitionUpdateSchema, }), - handler: async ({ request, response, params, logger, getScopedClient }) => { - const entityClient = await getScopedClient({ request }); - + handler: async ({ context, request, response, params, logger, getScopedClient }) => { try { + const core = await context.core; + const definition = await findEntityDefinitionById({ + id: params.path.id, + esClient: core.elasticsearch.client.asCurrentUser, + soClient: core.savedObjects.client, + }); + if (!definition) { + throw new EntityDefinitionNotFound(`Unable to find entity definition [${params.path.id}]`); + } + + const isAuthorized = await canManageEntityDefinition( + core.elasticsearch.client.asCurrentUser, + params.body.indexPatterns ?? definition.indexPatterns + ); + if (!isAuthorized) { + return response.forbidden({ + body: { + message: + 'Current Kibana user does not have the required permissions to update the entity definition', + }, + }); + } + + const entityClient = await getScopedClient({ request }); const updatedDefinition = await entityClient.updateEntityDefinition({ id: params.path.id, definitionUpdate: params.body, diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts index d1d56aa0e08c..7413c365b5da 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts @@ -101,7 +101,7 @@ export class EntityStoreDataClient { this.esClient = clusterClient.asCurrentUser; this.entityClient = new EntityClient({ - clusterClient, + esClient: this.esClient, soClient, logger, }); diff --git a/x-pack/test/api_integration/apis/entity_manager/helpers/user.ts b/x-pack/test/api_integration/apis/entity_manager/helpers/user.ts index 89181b7e0c15..04802a43288e 100644 --- a/x-pack/test/api_integration/apis/entity_manager/helpers/user.ts +++ b/x-pack/test/api_integration/apis/entity_manager/helpers/user.ts @@ -12,6 +12,7 @@ import { entityDefinitionDeletionPrivileges, entityDefinitionRuntimePrivileges, } from '@kbn/entityManager-plugin/server/lib/auth/privileges'; +import { BUILT_IN_ALLOWED_INDICES } from '@kbn/entityManager-plugin/server/lib/entities/built_in/constants'; export const createAdmin = async ({ esClient, @@ -25,7 +26,7 @@ export const createAdmin = async ({ const privileges = mergeWith( { application: [], index: [], cluster: [] }, apiKeyCreationPrivileges, - entityDefinitionRuntimePrivileges, + entityDefinitionRuntimePrivileges(BUILT_IN_ALLOWED_INDICES), entityDefinitionDeletionPrivileges, (src, other) => uniq(src.concat(other)) ); @@ -51,7 +52,7 @@ export const createRuntimeUser = async ({ username?: string; password?: string; }) => { - const privileges = entityDefinitionRuntimePrivileges; + const privileges = entityDefinitionRuntimePrivileges(BUILT_IN_ALLOWED_INDICES); const role = 'entities_runtime'; await esClient.security.putRole({