kibana/x-pack/plugins/actions/server/plugin.ts
Josh Dover 20c4716ced
Add ability for types to define an excludeOnUpgrade hook (#106534)
* Add ability for types to define an exclude from upgrade hook

* Update test to use 7.13 fixture

* Add 404 handling to oldest action task query

* Update api docs

* Update and add unit tests

* Rename deleteOnUpgrade to excludeOnUpgrade

* Disable reporting browser download for integration test

* Update exports and TSDocs

* Disable reporting plugin entirely

* Typo in config key name

* Update api docs

* Fix eslint

* Add timeouts for hooks

* Make adjustments to getOldestIdleActionTask

* Add type name to log messages for failed hooks

* Update api docs
2021-07-27 17:32:35 -04:00

550 lines
19 KiB
TypeScript

/*
* 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 { PublicMethodsOf } from '@kbn/utility-types';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import {
PluginInitializerContext,
Plugin,
CoreSetup,
CoreStart,
KibanaRequest,
Logger,
IContextProvider,
ElasticsearchServiceStart,
SavedObjectsClientContract,
SavedObjectsBulkGetObject,
} from '../../../../src/core/server';
import {
EncryptedSavedObjectsPluginSetup,
EncryptedSavedObjectsPluginStart,
} from '../../encrypted_saved_objects/server';
import { TaskManagerSetupContract, TaskManagerStartContract } from '../../task_manager/server';
import { LicensingPluginSetup, LicensingPluginStart } from '../../licensing/server';
import { SpacesPluginStart, SpacesPluginSetup } from '../../spaces/server';
import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server';
import { SecurityPluginSetup } from '../../security/server';
import {
ensureCleanupFailedExecutionsTaskScheduled,
registerCleanupFailedExecutionsTaskDefinition,
} from './cleanup_failed_executions';
import { ActionsConfig, getValidatedConfig } from './config';
import { resolveCustomHosts } from './lib/custom_host_settings';
import { ActionsClient } from './actions_client';
import { ActionTypeRegistry } from './action_type_registry';
import {
createExecutionEnqueuerFunction,
createEphemeralExecutionEnqueuerFunction,
} from './create_execute_function';
import { registerBuiltInActionTypes } from './builtin_action_types';
import { registerActionsUsageCollector } from './usage';
import {
ActionExecutor,
TaskRunnerFactory,
LicenseState,
ILicenseState,
spaceIdToNamespace,
} from './lib';
import {
Services,
ActionType,
PreConfiguredAction,
ActionTypeConfig,
ActionTypeSecrets,
ActionTypeParams,
ActionsRequestHandlerContext,
} from './types';
import { getActionsConfigurationUtilities } from './actions_config';
import { defineRoutes } from './routes';
import { IEventLogger, IEventLogService } from '../../event_log/server';
import { initializeActionsTelemetry, scheduleActionsTelemetry } from './usage/task';
import {
ACTION_SAVED_OBJECT_TYPE,
ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE,
ALERT_SAVED_OBJECT_TYPE,
} from './constants/saved_objects';
import { setupSavedObjects } from './saved_objects';
import { ACTIONS_FEATURE } from './feature';
import { ActionsAuthorization } from './authorization/actions_authorization';
import { ActionsAuthorizationAuditLogger } from './authorization/audit_logger';
import { ActionExecutionSource } from './lib/action_execution_source';
import {
getAuthorizationModeBySource,
AuthorizationMode,
} from './authorization/get_authorization_mode_by_source';
import { ensureSufficientLicense } from './lib/ensure_sufficient_license';
import { renderMustacheObject } from './lib/mustache_renderer';
import { getAlertHistoryEsIndex } from './preconfigured_connectors/alert_history_es_index/alert_history_es_index';
import { createAlertHistoryIndexTemplate } from './preconfigured_connectors/alert_history_es_index/create_alert_history_index_template';
import { AlertHistoryEsIndexConnectorId } from '../common';
import { EVENT_LOG_ACTIONS, EVENT_LOG_PROVIDER } from './constants/event_log';
export interface PluginSetupContract {
registerType<
Config extends ActionTypeConfig = ActionTypeConfig,
Secrets extends ActionTypeSecrets = ActionTypeSecrets,
Params extends ActionTypeParams = ActionTypeParams,
ExecutorResultData = void
>(
actionType: ActionType<Config, Secrets, Params, ExecutorResultData>
): void;
}
export interface PluginStartContract {
isActionTypeEnabled(id: string, options?: { notifyUsage: boolean }): boolean;
isActionExecutable(
actionId: string,
actionTypeId: string,
options?: { notifyUsage: boolean }
): boolean;
getActionsClientWithRequest(request: KibanaRequest): Promise<PublicMethodsOf<ActionsClient>>;
getActionsAuthorizationWithRequest(request: KibanaRequest): PublicMethodsOf<ActionsAuthorization>;
preconfiguredActions: PreConfiguredAction[];
renderActionParameterTemplates<Params extends ActionTypeParams = ActionTypeParams>(
actionTypeId: string,
actionId: string,
params: Params,
variables: Record<string, unknown>
): Params;
}
export interface ActionsPluginsSetup {
taskManager: TaskManagerSetupContract;
encryptedSavedObjects: EncryptedSavedObjectsPluginSetup;
licensing: LicensingPluginSetup;
eventLog: IEventLogService;
usageCollection?: UsageCollectionSetup;
security?: SecurityPluginSetup;
features: FeaturesPluginSetup;
spaces?: SpacesPluginSetup;
}
export interface ActionsPluginsStart {
encryptedSavedObjects: EncryptedSavedObjectsPluginStart;
taskManager: TaskManagerStartContract;
licensing: LicensingPluginStart;
spaces?: SpacesPluginStart;
}
const includedHiddenTypes = [
ACTION_SAVED_OBJECT_TYPE,
ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE,
ALERT_SAVED_OBJECT_TYPE,
];
export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartContract> {
private readonly logger: Logger;
private readonly actionsConfig: ActionsConfig;
private taskRunnerFactory?: TaskRunnerFactory;
private actionTypeRegistry?: ActionTypeRegistry;
private actionExecutor?: ActionExecutor;
private licenseState: ILicenseState | null = null;
private security?: SecurityPluginSetup;
private eventLogService?: IEventLogService;
private eventLogger?: IEventLogger;
private isESOCanEncrypt?: boolean;
private readonly telemetryLogger: Logger;
private readonly preconfiguredActions: PreConfiguredAction[];
private readonly kibanaIndexConfig: { kibana: { index: string } };
constructor(initContext: PluginInitializerContext) {
this.logger = initContext.logger.get();
this.actionsConfig = getValidatedConfig(
this.logger,
resolveCustomHosts(this.logger, initContext.config.get<ActionsConfig>())
);
this.telemetryLogger = initContext.logger.get('usage');
this.preconfiguredActions = [];
this.kibanaIndexConfig = initContext.config.legacy.get();
}
public setup(
core: CoreSetup<ActionsPluginsStart>,
plugins: ActionsPluginsSetup
): PluginSetupContract {
this.licenseState = new LicenseState(plugins.licensing.license$);
this.isESOCanEncrypt = plugins.encryptedSavedObjects.canEncrypt;
if (!this.isESOCanEncrypt) {
this.logger.warn(
'APIs are disabled because the Encrypted Saved Objects plugin is missing encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.'
);
}
plugins.features.registerKibanaFeature(ACTIONS_FEATURE);
this.eventLogService = plugins.eventLog;
plugins.eventLog.registerProviderActions(EVENT_LOG_PROVIDER, Object.values(EVENT_LOG_ACTIONS));
this.eventLogger = plugins.eventLog.getLogger({
event: { provider: EVENT_LOG_PROVIDER },
});
const actionExecutor = new ActionExecutor({
isESOCanEncrypt: this.isESOCanEncrypt,
});
// get executions count
const taskRunnerFactory = new TaskRunnerFactory(actionExecutor);
const actionsConfigUtils = getActionsConfigurationUtilities(this.actionsConfig);
if (this.actionsConfig.preconfiguredAlertHistoryEsIndex) {
this.preconfiguredActions.push(getAlertHistoryEsIndex());
}
for (const preconfiguredId of Object.keys(this.actionsConfig.preconfigured)) {
if (preconfiguredId !== AlertHistoryEsIndexConnectorId) {
this.preconfiguredActions.push({
...this.actionsConfig.preconfigured[preconfiguredId],
id: preconfiguredId,
isPreconfigured: true,
});
} else {
this.logger.warn(
`Preconfigured connectors cannot have the id "${AlertHistoryEsIndexConnectorId}" because this is a reserved id.`
);
}
}
const actionTypeRegistry = new ActionTypeRegistry({
licensing: plugins.licensing,
taskRunnerFactory,
taskManager: plugins.taskManager,
actionsConfigUtils,
licenseState: this.licenseState,
preconfiguredActions: this.preconfiguredActions,
});
this.taskRunnerFactory = taskRunnerFactory;
this.actionTypeRegistry = actionTypeRegistry;
this.actionExecutor = actionExecutor;
this.security = plugins.security;
setupSavedObjects(
core.savedObjects,
plugins.encryptedSavedObjects,
this.actionTypeRegistry!,
plugins.taskManager.index
);
registerBuiltInActionTypes({
logger: this.logger,
actionTypeRegistry,
actionsConfigUtils,
publicBaseUrl: core.http.basePath.publicBaseUrl,
});
const usageCollection = plugins.usageCollection;
if (usageCollection) {
registerActionsUsageCollector(
usageCollection,
this.actionsConfig,
core.getStartServices().then(([_, { taskManager }]) => taskManager)
);
}
core.http.registerRouteHandlerContext<ActionsRequestHandlerContext, 'actions'>(
'actions',
this.createRouteHandlerContext(core, this.kibanaIndexConfig.kibana.index)
);
if (usageCollection) {
initializeActionsTelemetry(
this.telemetryLogger,
plugins.taskManager,
core,
this.kibanaIndexConfig.kibana.index
);
}
// Routes
defineRoutes(core.http.createRouter<ActionsRequestHandlerContext>(), this.licenseState);
// Cleanup failed execution task definition
if (this.actionsConfig.cleanupFailedExecutionsTask.enabled) {
registerCleanupFailedExecutionsTaskDefinition(plugins.taskManager, {
actionTypeRegistry,
logger: this.logger,
coreStartServices: core.getStartServices(),
config: this.actionsConfig.cleanupFailedExecutionsTask,
kibanaIndex: this.kibanaIndexConfig.kibana.index,
taskManagerIndex: plugins.taskManager.index,
});
}
return {
registerType: <
Config extends ActionTypeConfig = ActionTypeConfig,
Secrets extends ActionTypeSecrets = ActionTypeSecrets,
Params extends ActionTypeParams = ActionTypeParams,
ExecutorResultData = void
>(
actionType: ActionType<Config, Secrets, Params, ExecutorResultData>
) => {
ensureSufficientLicense(actionType);
actionTypeRegistry.register(actionType);
},
};
}
public start(core: CoreStart, plugins: ActionsPluginsStart): PluginStartContract {
const {
logger,
licenseState,
actionExecutor,
actionTypeRegistry,
taskRunnerFactory,
kibanaIndexConfig,
isESOCanEncrypt,
preconfiguredActions,
instantiateAuthorization,
getUnsecuredSavedObjectsClient,
} = this;
licenseState?.setNotifyUsage(plugins.licensing.featureUsage.notifyUsage);
const encryptedSavedObjectsClient = plugins.encryptedSavedObjects.getClient({
includedHiddenTypes,
});
const getActionsClientWithRequest = async (
request: KibanaRequest,
authorizationContext?: ActionExecutionSource<unknown>
) => {
if (isESOCanEncrypt !== true) {
throw new Error(
`Unable to create actions client because the Encrypted Saved Objects plugin is missing encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.`
);
}
const unsecuredSavedObjectsClient = getUnsecuredSavedObjectsClient(
core.savedObjects,
request
);
const kibanaIndex = kibanaIndexConfig.kibana.index;
return new ActionsClient({
unsecuredSavedObjectsClient,
actionTypeRegistry: actionTypeRegistry!,
defaultKibanaIndex: kibanaIndex,
scopedClusterClient: core.elasticsearch.client.asScoped(request),
preconfiguredActions,
request,
authorization: instantiateAuthorization(
request,
await getAuthorizationModeBySource(unsecuredSavedObjectsClient, authorizationContext)
),
actionExecutor: actionExecutor!,
ephemeralExecutionEnqueuer: createEphemeralExecutionEnqueuerFunction({
taskManager: plugins.taskManager,
actionTypeRegistry: actionTypeRegistry!,
isESOCanEncrypt: isESOCanEncrypt!,
preconfiguredActions,
}),
executionEnqueuer: createExecutionEnqueuerFunction({
taskManager: plugins.taskManager,
actionTypeRegistry: actionTypeRegistry!,
isESOCanEncrypt: isESOCanEncrypt!,
preconfiguredActions,
}),
auditLogger: this.security?.audit.asScoped(request),
});
};
// Ensure the public API cannot be used to circumvent authorization
// using our legacy exemption mechanism by passing in a legacy SO
// as authorizationContext which would then set a Legacy AuthorizationMode
const secureGetActionsClientWithRequest = (request: KibanaRequest) =>
getActionsClientWithRequest(request);
this.eventLogService!.registerSavedObjectProvider('action', (request) => {
const client = secureGetActionsClientWithRequest(request);
return (objects?: SavedObjectsBulkGetObject[]) =>
objects
? Promise.all(
objects.map(async (objectItem) => await (await client).get({ id: objectItem.id }))
)
: Promise.resolve([]);
});
const getScopedSavedObjectsClientWithoutAccessToActions = (request: KibanaRequest) =>
core.savedObjects.getScopedClient(request);
actionExecutor!.initialize({
logger,
eventLogger: this.eventLogger!,
spaces: plugins.spaces?.spacesService,
getActionsClientWithRequest,
getServices: this.getServicesFactory(
getScopedSavedObjectsClientWithoutAccessToActions,
core.elasticsearch
),
encryptedSavedObjectsClient,
actionTypeRegistry: actionTypeRegistry!,
preconfiguredActions,
});
taskRunnerFactory!.initialize({
logger,
actionTypeRegistry: actionTypeRegistry!,
encryptedSavedObjectsClient,
basePathService: core.http.basePath,
spaceIdToNamespace: (spaceId?: string) => spaceIdToNamespace(plugins.spaces, spaceId),
getUnsecuredSavedObjectsClient: (request: KibanaRequest) =>
this.getUnsecuredSavedObjectsClient(core.savedObjects, request),
});
scheduleActionsTelemetry(this.telemetryLogger, plugins.taskManager);
if (this.actionsConfig.preconfiguredAlertHistoryEsIndex) {
createAlertHistoryIndexTemplate({
client: core.elasticsearch.client.asInternalUser,
logger: this.logger,
});
}
// Cleanup failed execution task
if (this.actionsConfig.cleanupFailedExecutionsTask.enabled) {
ensureCleanupFailedExecutionsTaskScheduled(
plugins.taskManager,
this.logger,
this.actionsConfig.cleanupFailedExecutionsTask
);
}
return {
isActionTypeEnabled: (id, options = { notifyUsage: false }) => {
return this.actionTypeRegistry!.isActionTypeEnabled(id, options);
},
isActionExecutable: (
actionId: string,
actionTypeId: string,
options = { notifyUsage: false }
) => {
return this.actionTypeRegistry!.isActionExecutable(actionId, actionTypeId, options);
},
getActionsAuthorizationWithRequest(request: KibanaRequest) {
return instantiateAuthorization(request);
},
getActionsClientWithRequest: secureGetActionsClientWithRequest,
preconfiguredActions,
renderActionParameterTemplates: (...args) =>
renderActionParameterTemplates(actionTypeRegistry, ...args),
};
}
private getUnsecuredSavedObjectsClient = (
savedObjects: CoreStart['savedObjects'],
request: KibanaRequest
) =>
savedObjects.getScopedClient(request, {
excludedWrappers: ['security'],
includedHiddenTypes,
});
private instantiateAuthorization = (
request: KibanaRequest,
authorizationMode?: AuthorizationMode
) => {
return new ActionsAuthorization({
request,
authorizationMode,
authorization: this.security?.authz,
authentication: this.security?.authc,
auditLogger: new ActionsAuthorizationAuditLogger(
this.security?.audit.getLogger(ACTIONS_FEATURE.id)
),
});
};
private getServicesFactory(
getScopedClient: (request: KibanaRequest) => SavedObjectsClientContract,
elasticsearch: ElasticsearchServiceStart
): (request: KibanaRequest) => Services {
return (request) => ({
savedObjectsClient: getScopedClient(request),
scopedClusterClient: elasticsearch.client.asScoped(request).asCurrentUser,
});
}
private createRouteHandlerContext = (
core: CoreSetup<ActionsPluginsStart>,
defaultKibanaIndex: string
): IContextProvider<ActionsRequestHandlerContext, 'actions'> => {
const {
actionTypeRegistry,
isESOCanEncrypt,
preconfiguredActions,
actionExecutor,
instantiateAuthorization,
security,
} = this;
return async function actionsRouteHandlerContext(context, request) {
const [{ savedObjects }, { taskManager }] = await core.getStartServices();
return {
getActionsClient: () => {
if (isESOCanEncrypt !== true) {
throw new Error(
`Unable to create actions client because the Encrypted Saved Objects plugin is missing encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.`
);
}
return new ActionsClient({
unsecuredSavedObjectsClient: savedObjects.getScopedClient(request, {
excludedWrappers: ['security'],
includedHiddenTypes,
}),
actionTypeRegistry: actionTypeRegistry!,
defaultKibanaIndex,
scopedClusterClient: context.core.elasticsearch.client,
preconfiguredActions,
request,
authorization: instantiateAuthorization(request),
actionExecutor: actionExecutor!,
ephemeralExecutionEnqueuer: createEphemeralExecutionEnqueuerFunction({
taskManager,
actionTypeRegistry: actionTypeRegistry!,
isESOCanEncrypt: isESOCanEncrypt!,
preconfiguredActions,
}),
executionEnqueuer: createExecutionEnqueuerFunction({
taskManager,
actionTypeRegistry: actionTypeRegistry!,
isESOCanEncrypt: isESOCanEncrypt!,
preconfiguredActions,
}),
auditLogger: security?.audit.asScoped(request),
});
},
listTypes: actionTypeRegistry!.list.bind(actionTypeRegistry!),
};
};
};
public stop() {
if (this.licenseState) {
this.licenseState.clean();
}
}
}
export function renderActionParameterTemplates<Params extends ActionTypeParams = ActionTypeParams>(
actionTypeRegistry: ActionTypeRegistry | undefined,
actionTypeId: string,
actionId: string,
params: Params,
variables: Record<string, unknown>
): Params {
const actionType = actionTypeRegistry?.get(actionTypeId);
if (actionType?.renderParameterTemplates) {
return actionType.renderParameterTemplates(params, variables, actionId) as Params;
} else {
return renderMustacheObject(params, variables);
}
}