[eem] return 403 if user is not authorized to enable entity discovery (#189423)

Return 403 if user is not authorized to enable or disable entity
discovery
This commit is contained in:
Kevin Lacabane 2024-07-31 12:41:19 +02:00 committed by GitHub
parent 1949fa6c4b
commit c0fa819927
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 55 additions and 36 deletions

View file

@ -6,7 +6,6 @@
*/
import React, { useState } from 'react';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { ERROR_USER_NOT_AUTHORIZED } from '@kbn/entityManager-plugin/public';
import useToggle from 'react-use/lib/useToggle';
import {
EuiButtonIcon,
@ -22,6 +21,7 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import type { IHttpFetchError, ResponseErrorBody } from '@kbn/core/public';
import { EntityManagerUnauthorizedError } from '@kbn/entityManager-plugin/public';
import { TechnicalPreviewBadge } from '../technical_preview_badge';
import { ApmPluginStartDeps } from '../../../plugin';
import { useEntityManagerEnablementContext } from '../../../context/entity_manager_context/use_entity_manager_enablement_context';
@ -77,16 +77,16 @@ export function EntityEnablement({ label, tooltip }: { label: string; tooltip?:
}
refetch();
} else {
if (response.reason === ERROR_USER_NOT_AUTHORIZED) {
setIsLoading(false);
setsIsUnauthorizedModalVisible(true);
return;
}
throw new Error(response.message);
}
} catch (error) {
setIsLoading(false);
if (error instanceof EntityManagerUnauthorizedError) {
setsIsUnauthorizedModalVisible(true);
return;
}
const err = error as Error | IHttpFetchError<ResponseErrorBody>;
notifications.toasts.danger({
title: i18n.translate('xpack.apm.eemEnablement.errorTitle', {

View file

@ -7,7 +7,6 @@
export const ERROR_API_KEY_NOT_FOUND = 'api_key_not_found';
export const ERROR_API_KEY_NOT_VALID = 'api_key_not_valid';
export const ERROR_USER_NOT_AUTHORIZED = 'user_not_authorized';
export const ERROR_API_KEY_SERVICE_DISABLED = 'api_key_service_disabled';
export const ERROR_PARTIAL_BUILTIN_INSTALLATION = 'partial_builtin_installation';
export const ERROR_DEFINITION_STOPPED = 'error_definition_stopped';

View file

@ -22,8 +22,9 @@ export type EntityManagerAppId = 'entityManager';
export {
ERROR_API_KEY_NOT_FOUND,
ERROR_API_KEY_NOT_VALID,
ERROR_USER_NOT_AUTHORIZED,
ERROR_API_KEY_SERVICE_DISABLED,
ERROR_PARTIAL_BUILTIN_INSTALLATION,
ERROR_DEFINITION_STOPPED,
} from '../common/errors';
export { EntityManagerUnauthorizedError } from './lib/errors';

View file

@ -6,6 +6,7 @@
*/
import { HttpStart } from '@kbn/core/public';
import { EntityManagerUnauthorizedError } from './errors';
import { IEntityClient } from '../types';
import {
ManagedEntityEnabledResponse,
@ -21,10 +22,24 @@ export class EntityClient implements IEntityClient {
}
async enableManagedEntityDiscovery(): Promise<EnableManagedEntityResponse> {
return await this.http.put('/internal/entities/managed/enablement');
try {
return await this.http.put('/internal/entities/managed/enablement');
} catch (err) {
if (err.body?.statusCode === 403) {
throw new EntityManagerUnauthorizedError(err.body.message);
}
throw err;
}
}
async disableManagedEntityDiscovery(): Promise<DisableManagedEntityResponse> {
return await this.http.delete('/internal/entities/managed/enablement');
try {
return await this.http.delete('/internal/entities/managed/enablement');
} catch (err) {
if (err.body?.statusCode === 403) {
throw new EntityManagerUnauthorizedError(err.body.message);
}
throw err;
}
}
}

View file

@ -0,0 +1,12 @@
/*
* 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.
*/
export class EntityManagerUnauthorizedError extends Error {
constructor(message: string) {
super(message);
}
}

View file

@ -9,7 +9,6 @@ import { RequestHandlerContext } from '@kbn/core/server';
import { schema } from '@kbn/config-schema';
import { SetupRouteOptions } from '../types';
import { deleteEntityDiscoveryAPIKey, readEntityDiscoveryAPIKey } from '../../lib/auth';
import { ERROR_USER_NOT_AUTHORIZED } from '../../../common/errors';
import { uninstallBuiltInEntityDefinitions } from '../../lib/entities/uninstall_entity_definition';
import { canDisableEntityDiscovery } from '../../lib/auth/privileges';
import { EntityDiscoveryApiKeyType } from '../../saved_objects';
@ -33,10 +32,8 @@ export function disableEntityDiscoveryRoute<T extends RequestHandlerContext>({
const esClient = (await context.core).elasticsearch.client.asCurrentUser;
const canDisable = await canDisableEntityDiscovery(esClient);
if (!canDisable) {
return res.ok({
return res.forbidden({
body: {
success: false,
reason: ERROR_USER_NOT_AUTHORIZED,
message:
'Current Kibana user does not have the required permissions to disable entity discovery',
},

View file

@ -18,7 +18,7 @@ import {
} from '../../lib/auth';
import { builtInDefinitions } from '../../lib/entities/built_in';
import { installBuiltInEntityDefinitions } from '../../lib/entities/install_entity_definition';
import { ERROR_API_KEY_SERVICE_DISABLED, ERROR_USER_NOT_AUTHORIZED } from '../../../common/errors';
import { ERROR_API_KEY_SERVICE_DISABLED } from '../../../common/errors';
import { EntityDiscoveryApiKeyType } from '../../saved_objects';
export function enableEntityDiscoveryRoute<T extends RequestHandlerContext>({
@ -48,10 +48,8 @@ export function enableEntityDiscoveryRoute<T extends RequestHandlerContext>({
const esClient = (await context.core).elasticsearch.client.asCurrentUser;
const canEnable = await canEnableEntityDiscovery(esClient);
if (!canEnable) {
return res.ok({
return res.forbidden({
body: {
success: false,
reason: ERROR_USER_NOT_AUTHORIZED,
message:
'Current Kibana user does not have the required permissions to enable entity discovery',
},
@ -75,7 +73,6 @@ export function enableEntityDiscoveryRoute<T extends RequestHandlerContext>({
}
const apiKey = await generateEntityDiscoveryAPIKey(server, req);
if (apiKey === undefined) {
return res.customError({
statusCode: 500,

View file

@ -6,7 +6,6 @@
*/
import expect from '@kbn/expect';
import { ERROR_USER_NOT_AUTHORIZED } from '@kbn/entityManager-plugin/common/errors';
import { builtInDefinitions } from '@kbn/entityManager-plugin/server/lib/entities/built_in';
import { EntityDefinitionWithState } from '@kbn/entityManager-plugin/server/lib/entities/types';
import { FtrProviderContext } from '../../ftr_provider_context';
@ -18,13 +17,14 @@ export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertestWithoutAuth');
const enablementRequest =
(method: 'get' | 'put' | 'delete') => async (auth: Auth, query?: { [key: string]: any }) => {
(method: 'get' | 'put' | 'delete') =>
async (auth: Auth, expectedCode: number, query: { [key: string]: any } = {}) => {
const response = await supertest[method]('/internal/entities/managed/enablement')
.auth(auth.username, auth.password)
.query(query)
.set('kbn-xsrf', 'xxx')
.send()
.expect(200);
.expect(expectedCode);
return response.body;
};
@ -45,7 +45,7 @@ export default function ({ getService }: FtrProviderContext) {
describe('with authorized user', () => {
it('should enable and disable entity discovery', async () => {
const enableResponse = await enableEntityDiscovery(authorizedUser);
const enableResponse = await enableEntityDiscovery(authorizedUser, 200);
expect(enableResponse.success).to.eql(true, "authorized user can't enable EEM");
let definitionsResponse = await getInstalledDefinitions(supertest, authorizedUser);
@ -64,19 +64,21 @@ export default function ({ getService }: FtrProviderContext) {
})
).to.eql(true, 'all builtin definitions are not installed/running');
let stateResponse = await entityDiscoveryState(authorizedUser);
let stateResponse = await entityDiscoveryState(authorizedUser, 200);
expect(stateResponse.enabled).to.eql(
true,
`EEM is not enabled; response: ${JSON.stringify(stateResponse)}`
);
const disableResponse = await disableEntityDiscovery(authorizedUser, { deleteData: false });
const disableResponse = await disableEntityDiscovery(authorizedUser, 200, {
deleteData: false,
});
expect(disableResponse.success).to.eql(
true,
`authorized user failed to disable EEM; response: ${JSON.stringify(disableResponse)}`
);
stateResponse = await entityDiscoveryState(authorizedUser);
stateResponse = await entityDiscoveryState(authorizedUser, 200);
expect(stateResponse.enabled).to.eql(false, 'EEM is not disabled');
definitionsResponse = await getInstalledDefinitions(supertest, authorizedUser);
@ -86,11 +88,9 @@ export default function ({ getService }: FtrProviderContext) {
describe('with unauthorized user', () => {
it('should fail to enable entity discovery', async () => {
const enableResponse = await enableEntityDiscovery(unauthorizedUser);
expect(enableResponse.success).to.eql(false, 'unauthorized user can enable EEM');
expect(enableResponse.reason).to.eql(ERROR_USER_NOT_AUTHORIZED);
await enableEntityDiscovery(unauthorizedUser, 403);
const stateResponse = await entityDiscoveryState(unauthorizedUser);
const stateResponse = await entityDiscoveryState(unauthorizedUser, 200);
expect(stateResponse.enabled).to.eql(false, 'EEM is enabled');
const definitionsResponse = await getInstalledDefinitions(supertest, unauthorizedUser);
@ -98,14 +98,12 @@ export default function ({ getService }: FtrProviderContext) {
});
it('should fail to disable entity discovery', async () => {
const enableResponse = await enableEntityDiscovery(authorizedUser);
const enableResponse = await enableEntityDiscovery(authorizedUser, 200);
expect(enableResponse.success).to.eql(true, "authorized user can't enable EEM");
let disableResponse = await disableEntityDiscovery(unauthorizedUser);
expect(disableResponse.success).to.eql(false, 'unauthorized user can disable EEM');
expect(disableResponse.reason).to.eql(ERROR_USER_NOT_AUTHORIZED);
let disableResponse = await disableEntityDiscovery(unauthorizedUser, 403);
disableResponse = await disableEntityDiscovery(authorizedUser);
disableResponse = await disableEntityDiscovery(authorizedUser, 200);
expect(disableResponse.success).to.eql(true, "authorized user can't disable EEM");
});
});