mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Alerting] Hides the action
and action_task_params
SavedObjects types (#67109)
As part of the work towards adding RBAC & Feature Controls support in Alerting (https://github.com/elastic/kibana/issues/43994), we've decided that the ActionsClient will handle authorisation against Actions instead of relying on the SavedObjectsClient on its own. To prevent (or at least, minimise the chances of) bypassing this auth model by using the SavedObjects client this PR makes the `action` and `action_task_params` SavedObject types _hidden_ types and given the ActionsClient permission to interact with it.
This commit is contained in:
parent
edee6543be
commit
def6526384
7 changed files with 50 additions and 28 deletions
|
@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema';
|
|||
import { ActionExecutor } from './action_executor';
|
||||
import { actionTypeRegistryMock } from '../action_type_registry.mock';
|
||||
import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks';
|
||||
import { loggingServiceMock } from '../../../../../src/core/server/mocks';
|
||||
import { loggingServiceMock, savedObjectsClientMock } from '../../../../../src/core/server/mocks';
|
||||
import { eventLoggerMock } from '../../../event_log/server/mocks';
|
||||
import { spacesServiceMock } from '../../../spaces/server/spaces_service/spaces_service.mock';
|
||||
import { ActionType } from '../types';
|
||||
|
@ -17,7 +17,7 @@ import { actionsMock } from '../mocks';
|
|||
|
||||
const actionExecutor = new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false });
|
||||
const services = actionsMock.createServices();
|
||||
const savedObjectsClient = services.savedObjectsClient;
|
||||
const savedObjectsClientWithHidden = savedObjectsClientMock.create();
|
||||
const encryptedSavedObjectsClient = encryptedSavedObjectsMock.createClient();
|
||||
const actionTypeRegistry = actionTypeRegistryMock.create();
|
||||
|
||||
|
@ -34,6 +34,7 @@ actionExecutor.initialize({
|
|||
logger: loggingServiceMock.create().get(),
|
||||
spaces: spacesMock,
|
||||
getServices: () => services,
|
||||
getScopedSavedObjectsClient: () => savedObjectsClientWithHidden,
|
||||
actionTypeRegistry,
|
||||
encryptedSavedObjectsClient,
|
||||
eventLogger: eventLoggerMock.create(),
|
||||
|
@ -66,7 +67,7 @@ test('successfully executes', async () => {
|
|||
},
|
||||
references: [],
|
||||
};
|
||||
savedObjectsClient.get.mockResolvedValueOnce(actionSavedObject);
|
||||
savedObjectsClientWithHidden.get.mockResolvedValueOnce(actionSavedObject);
|
||||
encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject);
|
||||
actionTypeRegistry.get.mockReturnValueOnce(actionType);
|
||||
await actionExecutor.execute(executeParams);
|
||||
|
@ -107,7 +108,7 @@ test('provides empty config when config and / or secrets is empty', async () =>
|
|||
},
|
||||
references: [],
|
||||
};
|
||||
savedObjectsClient.get.mockResolvedValueOnce(actionSavedObject);
|
||||
savedObjectsClientWithHidden.get.mockResolvedValueOnce(actionSavedObject);
|
||||
encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject);
|
||||
actionTypeRegistry.get.mockReturnValueOnce(actionType);
|
||||
await actionExecutor.execute(executeParams);
|
||||
|
@ -137,7 +138,7 @@ test('throws an error when config is invalid', async () => {
|
|||
},
|
||||
references: [],
|
||||
};
|
||||
savedObjectsClient.get.mockResolvedValueOnce(actionSavedObject);
|
||||
savedObjectsClientWithHidden.get.mockResolvedValueOnce(actionSavedObject);
|
||||
encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject);
|
||||
actionTypeRegistry.get.mockReturnValueOnce(actionType);
|
||||
|
||||
|
@ -170,7 +171,7 @@ test('throws an error when params is invalid', async () => {
|
|||
},
|
||||
references: [],
|
||||
};
|
||||
savedObjectsClient.get.mockResolvedValueOnce(actionSavedObject);
|
||||
savedObjectsClientWithHidden.get.mockResolvedValueOnce(actionSavedObject);
|
||||
encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject);
|
||||
actionTypeRegistry.get.mockReturnValueOnce(actionType);
|
||||
|
||||
|
@ -184,7 +185,7 @@ test('throws an error when params is invalid', async () => {
|
|||
});
|
||||
|
||||
test('throws an error when failing to load action through savedObjectsClient', async () => {
|
||||
savedObjectsClient.get.mockRejectedValueOnce(new Error('No access'));
|
||||
savedObjectsClientWithHidden.get.mockRejectedValueOnce(new Error('No access'));
|
||||
await expect(actionExecutor.execute(executeParams)).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"No access"`
|
||||
);
|
||||
|
@ -205,7 +206,7 @@ test('throws an error if actionType is not enabled', async () => {
|
|||
},
|
||||
references: [],
|
||||
};
|
||||
savedObjectsClient.get.mockResolvedValueOnce(actionSavedObject);
|
||||
savedObjectsClientWithHidden.get.mockResolvedValueOnce(actionSavedObject);
|
||||
encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject);
|
||||
actionTypeRegistry.get.mockReturnValueOnce(actionType);
|
||||
actionTypeRegistry.ensureActionTypeEnabled.mockImplementationOnce(() => {
|
||||
|
@ -239,7 +240,7 @@ test('should not throws an error if actionType is preconfigured', async () => {
|
|||
},
|
||||
references: [],
|
||||
};
|
||||
savedObjectsClient.get.mockResolvedValueOnce(actionSavedObject);
|
||||
savedObjectsClientWithHidden.get.mockResolvedValueOnce(actionSavedObject);
|
||||
encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject);
|
||||
actionTypeRegistry.get.mockReturnValueOnce(actionType);
|
||||
actionTypeRegistry.ensureActionTypeEnabled.mockImplementationOnce(() => {
|
||||
|
@ -267,6 +268,7 @@ test('throws an error when passing isESOUsingEphemeralEncryptionKey with value o
|
|||
customActionExecutor.initialize({
|
||||
logger: loggingServiceMock.create().get(),
|
||||
spaces: spacesMock,
|
||||
getScopedSavedObjectsClient: () => savedObjectsClientWithHidden,
|
||||
getServices: () => services,
|
||||
actionTypeRegistry,
|
||||
encryptedSavedObjectsClient,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Logger, KibanaRequest } from '../../../../../src/core/server';
|
||||
import { Logger, KibanaRequest, SavedObjectsClientContract } from '../../../../../src/core/server';
|
||||
import { validateParams, validateConfig, validateSecrets } from './validate_with_schema';
|
||||
import {
|
||||
ActionTypeExecutorResult,
|
||||
|
@ -12,7 +12,6 @@ import {
|
|||
GetServicesFunction,
|
||||
RawAction,
|
||||
PreConfiguredAction,
|
||||
Services,
|
||||
} from '../types';
|
||||
import { EncryptedSavedObjectsClient } from '../../../encrypted_saved_objects/server';
|
||||
import { SpacesServiceSetup } from '../../../spaces/server';
|
||||
|
@ -23,6 +22,7 @@ export interface ActionExecutorContext {
|
|||
logger: Logger;
|
||||
spaces?: SpacesServiceSetup;
|
||||
getServices: GetServicesFunction;
|
||||
getScopedSavedObjectsClient: (req: KibanaRequest) => SavedObjectsClientContract;
|
||||
encryptedSavedObjectsClient: EncryptedSavedObjectsClient;
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
eventLogger: IEventLogger;
|
||||
|
@ -76,6 +76,7 @@ export class ActionExecutor {
|
|||
actionTypeRegistry,
|
||||
eventLogger,
|
||||
preconfiguredActions,
|
||||
getScopedSavedObjectsClient,
|
||||
} = this.actionExecutorContext!;
|
||||
|
||||
const services = getServices(request);
|
||||
|
@ -83,7 +84,7 @@ export class ActionExecutor {
|
|||
const namespace = spaceId && spaceId !== 'default' ? { namespace: spaceId } : {};
|
||||
|
||||
const { actionTypeId, name, config, secrets } = await getActionInfo(
|
||||
services,
|
||||
getScopedSavedObjectsClient(request),
|
||||
encryptedSavedObjectsClient,
|
||||
preconfiguredActions,
|
||||
actionId,
|
||||
|
@ -195,7 +196,7 @@ interface ActionInfo {
|
|||
}
|
||||
|
||||
async function getActionInfo(
|
||||
services: Services,
|
||||
savedObjectsClient: SavedObjectsClientContract,
|
||||
encryptedSavedObjectsClient: EncryptedSavedObjectsClient,
|
||||
preconfiguredActions: PreConfiguredAction[],
|
||||
actionId: string,
|
||||
|
@ -218,7 +219,7 @@ async function getActionInfo(
|
|||
// ensure user can read the action before processing
|
||||
const {
|
||||
attributes: { actionTypeId, config, name },
|
||||
} = await services.savedObjectsClient.get<RawAction>('action', actionId);
|
||||
} = await savedObjectsClient.get<RawAction>('action', actionId);
|
||||
|
||||
const {
|
||||
attributes: { secrets },
|
||||
|
|
|
@ -59,6 +59,7 @@ const actionExecutorInitializerParams = {
|
|||
logger: loggingServiceMock.create().get(),
|
||||
getServices: jest.fn().mockReturnValue(services),
|
||||
actionTypeRegistry,
|
||||
getScopedSavedObjectsClient: () => savedObjectsClientMock.create(),
|
||||
encryptedSavedObjectsClient: mockedEncryptedSavedObjectsClient,
|
||||
eventLogger: eventLoggerMock.create(),
|
||||
preconfiguredActions: [],
|
||||
|
|
|
@ -16,9 +16,9 @@ import {
|
|||
SharedGlobalConfig,
|
||||
RequestHandler,
|
||||
IContextProvider,
|
||||
SavedObjectsServiceStart,
|
||||
ElasticsearchServiceStart,
|
||||
IClusterClient,
|
||||
SavedObjectsClientContract,
|
||||
} from '../../../../src/core/server';
|
||||
|
||||
import {
|
||||
|
@ -86,6 +86,8 @@ export interface ActionsPluginsStart {
|
|||
taskManager: TaskManagerStartContract;
|
||||
}
|
||||
|
||||
const includedHiddenTypes = ['action', 'action_task_params'];
|
||||
|
||||
export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, PluginStartContract> {
|
||||
private readonly kibanaIndex: Promise<string>;
|
||||
private readonly config: Promise<ActionsConfig>;
|
||||
|
@ -190,7 +192,7 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
|
|||
|
||||
core.http.registerRouteHandlerContext(
|
||||
'actions',
|
||||
this.createRouteHandlerContext(await this.kibanaIndex)
|
||||
this.createRouteHandlerContext(core, await this.kibanaIndex)
|
||||
);
|
||||
|
||||
// Routes
|
||||
|
@ -229,13 +231,27 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
|
|||
preconfiguredActions,
|
||||
} = this;
|
||||
|
||||
const encryptedSavedObjectsClient = plugins.encryptedSavedObjects.getClient();
|
||||
const encryptedSavedObjectsClient = plugins.encryptedSavedObjects.getClient({
|
||||
includedHiddenTypes,
|
||||
});
|
||||
|
||||
const getScopedSavedObjectsClient = (request: KibanaRequest) =>
|
||||
core.savedObjects.getScopedClient(request, {
|
||||
includedHiddenTypes,
|
||||
});
|
||||
|
||||
const getScopedSavedObjectsClientWithoutAccessToActions = (request: KibanaRequest) =>
|
||||
core.savedObjects.getScopedClient(request);
|
||||
|
||||
actionExecutor!.initialize({
|
||||
logger,
|
||||
eventLogger: this.eventLogger!,
|
||||
spaces: this.spaces,
|
||||
getServices: this.getServicesFactory(core.savedObjects, core.elasticsearch),
|
||||
getScopedSavedObjectsClient,
|
||||
getServices: this.getServicesFactory(
|
||||
getScopedSavedObjectsClientWithoutAccessToActions,
|
||||
core.elasticsearch
|
||||
),
|
||||
encryptedSavedObjectsClient,
|
||||
actionTypeRegistry: actionTypeRegistry!,
|
||||
preconfiguredActions,
|
||||
|
@ -247,7 +263,7 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
|
|||
encryptedSavedObjectsClient,
|
||||
getBasePath: this.getBasePath,
|
||||
spaceIdToNamespace: this.spaceIdToNamespace,
|
||||
getScopedSavedObjectsClient: core.savedObjects.getScopedClient,
|
||||
getScopedSavedObjectsClient,
|
||||
});
|
||||
|
||||
scheduleActionsTelemetry(this.telemetryLogger, plugins.taskManager);
|
||||
|
@ -256,7 +272,7 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
|
|||
execute: createExecuteFunction({
|
||||
taskManager: plugins.taskManager,
|
||||
actionTypeRegistry: actionTypeRegistry!,
|
||||
getScopedSavedObjectsClient: core.savedObjects.getScopedClient,
|
||||
getScopedSavedObjectsClient,
|
||||
getBasePath: this.getBasePath,
|
||||
isESOUsingEphemeralEncryptionKey: isESOUsingEphemeralEncryptionKey!,
|
||||
preconfiguredActions,
|
||||
|
@ -275,7 +291,7 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
|
|||
);
|
||||
}
|
||||
return new ActionsClient({
|
||||
savedObjectsClient: core.savedObjects.getScopedClient(request),
|
||||
savedObjectsClient: getScopedSavedObjectsClient(request),
|
||||
actionTypeRegistry: actionTypeRegistry!,
|
||||
defaultKibanaIndex: await kibanaIndex,
|
||||
scopedClusterClient: core.elasticsearch.legacy.client.asScoped(request),
|
||||
|
@ -287,12 +303,12 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
|
|||
}
|
||||
|
||||
private getServicesFactory(
|
||||
savedObjects: SavedObjectsServiceStart,
|
||||
getScopedClient: (request: KibanaRequest) => SavedObjectsClientContract,
|
||||
elasticsearch: ElasticsearchServiceStart
|
||||
): (request: KibanaRequest) => Services {
|
||||
return (request) => ({
|
||||
callCluster: elasticsearch.legacy.client.asScoped(request).callAsCurrentUser,
|
||||
savedObjectsClient: savedObjects.getScopedClient(request),
|
||||
savedObjectsClient: getScopedClient(request),
|
||||
getScopedCallCluster(clusterClient: IClusterClient) {
|
||||
return clusterClient.asScoped(request).callAsCurrentUser;
|
||||
},
|
||||
|
@ -300,11 +316,13 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
|
|||
}
|
||||
|
||||
private createRouteHandlerContext = (
|
||||
core: CoreSetup<ActionsPluginsStart>,
|
||||
defaultKibanaIndex: string
|
||||
): IContextProvider<RequestHandler<unknown, unknown, unknown>, 'actions'> => {
|
||||
const { actionTypeRegistry, isESOUsingEphemeralEncryptionKey, preconfiguredActions } = this;
|
||||
|
||||
return async function actionsRouteHandlerContext(context, request) {
|
||||
const [{ savedObjects }] = await core.getStartServices();
|
||||
return {
|
||||
getActionsClient: () => {
|
||||
if (isESOUsingEphemeralEncryptionKey === true) {
|
||||
|
@ -313,7 +331,7 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
|
|||
);
|
||||
}
|
||||
return new ActionsClient({
|
||||
savedObjectsClient: context.core.savedObjects.client,
|
||||
savedObjectsClient: savedObjects.getScopedClient(request, { includedHiddenTypes }),
|
||||
actionTypeRegistry: actionTypeRegistry!,
|
||||
defaultKibanaIndex,
|
||||
scopedClusterClient: context.core.elasticsearch.adminClient,
|
||||
|
|
|
@ -14,7 +14,7 @@ export function setupSavedObjects(
|
|||
) {
|
||||
savedObjects.registerType({
|
||||
name: 'action',
|
||||
hidden: false,
|
||||
hidden: true,
|
||||
namespaceType: 'single',
|
||||
mappings: mappings.action,
|
||||
});
|
||||
|
@ -31,7 +31,7 @@ export function setupSavedObjects(
|
|||
|
||||
savedObjects.registerType({
|
||||
name: 'action_task_params',
|
||||
hidden: false,
|
||||
hidden: true,
|
||||
namespaceType: 'single',
|
||||
mappings: mappings.action_task_params,
|
||||
});
|
||||
|
|
|
@ -291,7 +291,7 @@ export class AlertingPlugin {
|
|||
savedObjects: SavedObjectsServiceStart,
|
||||
request: KibanaRequest
|
||||
) {
|
||||
return savedObjects.getScopedClient(request, { includedHiddenTypes: ['alert'] });
|
||||
return savedObjects.getScopedClient(request, { includedHiddenTypes: ['alert', 'action'] });
|
||||
}
|
||||
|
||||
public stop() {
|
||||
|
|
|
@ -49,7 +49,7 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
|
|||
const [, { encryptedSavedObjects }] = await core.getStartServices();
|
||||
await encryptedSavedObjects
|
||||
.getClient({
|
||||
includedHiddenTypes: ['alert'],
|
||||
includedHiddenTypes: ['alert', 'action'],
|
||||
})
|
||||
.getDecryptedAsInternalUser(req.body.type, req.body.id, {
|
||||
namespace,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue