kibana/x-pack/plugins/actions/server/plugin.ts
Mike Côté da8ce374cf
Make xpack.actions.rejectUnauthorized setting work (#88690)
* Remove ActionsConfigType due to being a duplicate

* Fix rejectUnauthorized not being configured

* Move proxySettings to configurationUtilities

* Fix isAxiosError check to code

* Add functional test

* Remove comment

* Close webhook server

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
2021-01-28 13:44:25 -05:00

507 lines
18 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;
* you may not use this file except in compliance with the Elastic License.
*/
import type { PublicMethodsOf } from '@kbn/utility-types';
import { first } from 'rxjs/operators';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { Observable } from 'rxjs';
import {
PluginInitializerContext,
Plugin,
CoreSetup,
CoreStart,
KibanaRequest,
Logger,
IContextProvider,
ElasticsearchServiceStart,
ILegacyClusterClient,
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 } from '../../spaces/server';
import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server';
import { SecurityPluginSetup } from '../../security/server';
import { ActionsConfig } from './config';
import { ActionExecutor, TaskRunnerFactory, LicenseState, ILicenseState } from './lib';
import { ActionsClient } from './actions_client';
import { ActionTypeRegistry } from './action_type_registry';
import { createExecutionEnqueuerFunction } from './create_execute_function';
import { registerBuiltInActionTypes } from './builtin_action_types';
import { registerActionsUsageCollector } from './usage';
import {
Services,
ActionType,
PreConfiguredAction,
ActionTypeConfig,
ActionTypeSecrets,
ActionTypeParams,
ActionsRequestHandlerContext,
} from './types';
import { getActionsConfigurationUtilities } from './actions_config';
import {
createActionRoute,
deleteActionRoute,
getAllActionRoute,
getActionRoute,
updateActionRoute,
listActionTypesRoute,
executeActionRoute,
} from './routes';
import { IEventLogger, IEventLogService } from '../../event_log/server';
import { initializeActionsTelemetry, scheduleActionsTelemetry } from './usage/task';
import {
setupSavedObjects,
ACTION_SAVED_OBJECT_TYPE,
ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE,
ALERT_SAVED_OBJECT_TYPE,
} 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';
const EVENT_LOG_PROVIDER = 'actions';
export const EVENT_LOG_ACTIONS = {
execute: 'execute',
executeViaHttp: 'execute-via-http',
};
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,
params: Params,
variables: Record<string, unknown>
): Params;
}
export interface ActionsPluginsSetup {
taskManager: TaskManagerSetupContract;
encryptedSavedObjects: EncryptedSavedObjectsPluginSetup;
licensing: LicensingPluginSetup;
eventLog: IEventLogService;
usageCollection?: UsageCollectionSetup;
security?: SecurityPluginSetup;
features: FeaturesPluginSetup;
}
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<Promise<PluginSetupContract>, PluginStartContract> {
private readonly config: Promise<ActionsConfig>;
private readonly logger: Logger;
private 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 isESOUsingEphemeralEncryptionKey?: boolean;
private readonly telemetryLogger: Logger;
private readonly preconfiguredActions: PreConfiguredAction[];
private readonly kibanaIndexConfig: Observable<{ kibana: { index: string } }>;
constructor(initContext: PluginInitializerContext) {
this.config = initContext.config.create<ActionsConfig>().pipe(first()).toPromise();
this.logger = initContext.logger.get('actions');
this.telemetryLogger = initContext.logger.get('usage');
this.preconfiguredActions = [];
this.kibanaIndexConfig = initContext.config.legacy.globalConfig$;
}
public async setup(
core: CoreSetup<ActionsPluginsStart>,
plugins: ActionsPluginsSetup
): Promise<PluginSetupContract> {
this.licenseState = new LicenseState(plugins.licensing.license$);
this.isESOUsingEphemeralEncryptionKey =
plugins.encryptedSavedObjects.usingEphemeralEncryptionKey;
if (this.isESOUsingEphemeralEncryptionKey) {
this.logger.warn(
'APIs are disabled because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.'
);
}
plugins.features.registerKibanaFeature(ACTIONS_FEATURE);
setupSavedObjects(core.savedObjects, plugins.encryptedSavedObjects);
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({
isESOUsingEphemeralEncryptionKey: this.isESOUsingEphemeralEncryptionKey,
});
// get executions count
const taskRunnerFactory = new TaskRunnerFactory(actionExecutor);
this.actionsConfig = (await this.config) as ActionsConfig;
const actionsConfigUtils = getActionsConfigurationUtilities(this.actionsConfig);
for (const preconfiguredId of Object.keys(this.actionsConfig.preconfigured)) {
this.preconfiguredActions.push({
...this.actionsConfig.preconfigured[preconfiguredId],
id: preconfiguredId,
isPreconfigured: true,
});
}
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;
registerBuiltInActionTypes({
logger: this.logger,
actionTypeRegistry,
actionsConfigUtils,
publicBaseUrl: core.http.basePath.publicBaseUrl,
});
const usageCollection = plugins.usageCollection;
if (usageCollection) {
registerActionsUsageCollector(
usageCollection,
core.getStartServices().then(([_, { taskManager }]) => taskManager)
);
}
this.kibanaIndexConfig.subscribe((config) => {
core.http.registerRouteHandlerContext<ActionsRequestHandlerContext, 'actions'>(
'actions',
this.createRouteHandlerContext(core, config.kibana.index)
);
if (usageCollection) {
initializeActionsTelemetry(
this.telemetryLogger,
plugins.taskManager,
core,
config.kibana.index
);
}
});
// Routes
const router = core.http.createRouter<ActionsRequestHandlerContext>();
createActionRoute(router, this.licenseState);
deleteActionRoute(router, this.licenseState);
getActionRoute(router, this.licenseState);
getAllActionRoute(router, this.licenseState);
updateActionRoute(router, this.licenseState);
listActionTypesRoute(router, this.licenseState);
executeActionRoute(router, this.licenseState);
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,
isESOUsingEphemeralEncryptionKey,
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 (isESOUsingEphemeralEncryptionKey === true) {
throw new Error(
`Unable to create actions client because the Encrypted Saved Objects plugin uses an ephemeral 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 = (await kibanaIndexConfig.pipe(first()).toPromise()).kibana.index;
return new ActionsClient({
unsecuredSavedObjectsClient,
actionTypeRegistry: actionTypeRegistry!,
defaultKibanaIndex: kibanaIndex,
scopedClusterClient: core.elasticsearch.legacy.client.asScoped(request),
preconfiguredActions,
request,
authorization: instantiateAuthorization(
request,
await getAuthorizationModeBySource(unsecuredSavedObjectsClient, authorizationContext)
),
actionExecutor: actionExecutor!,
executionEnqueuer: createExecutionEnqueuerFunction({
taskManager: plugins.taskManager,
actionTypeRegistry: actionTypeRegistry!,
isESOUsingEphemeralEncryptionKey: isESOUsingEphemeralEncryptionKey!,
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,
});
const spaceIdToNamespace = (spaceId?: string) => {
return plugins.spaces && spaceId
? plugins.spaces.spacesService.spaceIdToNamespace(spaceId)
: undefined;
};
taskRunnerFactory!.initialize({
logger,
actionTypeRegistry: actionTypeRegistry!,
encryptedSavedObjectsClient,
basePathService: core.http.basePath,
spaceIdToNamespace,
getUnsecuredSavedObjectsClient: (request: KibanaRequest) =>
this.getUnsecuredSavedObjectsClient(core.savedObjects, request),
});
scheduleActionsTelemetry(this.telemetryLogger, plugins.taskManager);
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) => ({
callCluster: elasticsearch.legacy.client.asScoped(request).callAsCurrentUser,
savedObjectsClient: getScopedClient(request),
scopedClusterClient: elasticsearch.client.asScoped(request).asCurrentUser,
getLegacyScopedClusterClient(clusterClient: ILegacyClusterClient) {
return clusterClient.asScoped(request);
},
});
}
private createRouteHandlerContext = (
core: CoreSetup<ActionsPluginsStart>,
defaultKibanaIndex: string
): IContextProvider<ActionsRequestHandlerContext, 'actions'> => {
const {
actionTypeRegistry,
isESOUsingEphemeralEncryptionKey,
preconfiguredActions,
actionExecutor,
instantiateAuthorization,
security,
} = this;
return async function actionsRouteHandlerContext(context, request) {
const [{ savedObjects }, { taskManager }] = await core.getStartServices();
return {
getActionsClient: () => {
if (isESOUsingEphemeralEncryptionKey === true) {
throw new Error(
`Unable to create actions client because the Encrypted Saved Objects plugin uses an ephemeral 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.legacy.client,
preconfiguredActions,
request,
authorization: instantiateAuthorization(request),
actionExecutor: actionExecutor!,
executionEnqueuer: createExecutionEnqueuerFunction({
taskManager,
actionTypeRegistry: actionTypeRegistry!,
isESOUsingEphemeralEncryptionKey: isESOUsingEphemeralEncryptionKey!,
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,
params: Params,
variables: Record<string, unknown>
): Params {
const actionType = actionTypeRegistry?.get(actionTypeId);
if (actionType?.renderParameterTemplates) {
return actionType.renderParameterTemplates(params, variables) as Params;
} else {
return renderMustacheObject(params, variables);
}
}