From 91cc23b854355c2e6b4495f170630a5f5643da7c Mon Sep 17 00:00:00 2001 From: Gidi Morris Date: Wed, 28 Aug 2019 15:55:52 +0100 Subject: [PATCH] feat(webhook-whitelisting): extracted the schema for action specific Kiabana configs making them an action specific concern --- .../actions/server/action_type_registry.ts | 18 +++++++------- .../plugins/actions/server/actions_config.ts | 9 +------ .../builtin_action_types/webhook.test.ts | 19 ++++++++------- .../server/builtin_action_types/webhook.ts | 24 +++++++++++++++---- x-pack/legacy/plugins/actions/server/types.ts | 5 ++-- 5 files changed, 42 insertions(+), 33 deletions(-) diff --git a/x-pack/legacy/plugins/actions/server/action_type_registry.ts b/x-pack/legacy/plugins/actions/server/action_type_registry.ts index 6c2d0b9a89e61..3ebd05f2d868b 100644 --- a/x-pack/legacy/plugins/actions/server/action_type_registry.ts +++ b/x-pack/legacy/plugins/actions/server/action_type_registry.ts @@ -17,7 +17,7 @@ import { GetServicesFunction, SpaceIdToNamespaceFunction, } from './types'; -import { ActionKibanaConfig } from './actions_config'; +import { ActionsConfigType } from './actions_config'; interface ConstructorOptions { isSecurityEnabled: boolean; @@ -26,22 +26,22 @@ interface ConstructorOptions { encryptedSavedObjectsPlugin: EncryptedSavedObjectsPlugin; spaceIdToNamespace: SpaceIdToNamespaceFunction; getBasePath: GetBasePathFunction; - actionKibanaConfigurations: Option>; + actionKibanaConfigurations: Option; } function getKibanaConfigurationByActionType( actionType: string, - configs: Option> -): Option { + configs: Option> +): Option { return configs.mapNullable(allConfigs => allConfigs[actionType]); } function isConfigurable( - actionType: ActionType | ConfigureableActionType -): actionType is ConfigureableActionType { + actionType: ActionType | ConfigureableActionType +): actionType is ConfigureableActionType { return ( actionType.hasOwnProperty('configure') && - typeof (actionType as ConfigureableActionType).configure === 'function' + typeof (actionType as ConfigureableActionType).configure === 'function' ); } @@ -49,7 +49,7 @@ export class ActionTypeRegistry { private readonly taskRunCreatorFunction: TaskRunCreatorFunction; private readonly taskManager: TaskManager; private readonly actionTypes: Map = new Map(); - private readonly actionKibanaConfigurations: Option> = none; + private readonly actionKibanaConfigurations: Option = none; constructor({ getServices, @@ -82,7 +82,7 @@ export class ActionTypeRegistry { /** * Registers an action type to the action type registry */ - public register(providedActionType: ActionType | ConfigureableActionType) { + public register(providedActionType: ActionType | ConfigureableActionType) { const actionType = isConfigurable(providedActionType) ? providedActionType.configure( getKibanaConfigurationByActionType( diff --git a/x-pack/legacy/plugins/actions/server/actions_config.ts b/x-pack/legacy/plugins/actions/server/actions_config.ts index 678ef87308d94..651d07edfe9d6 100644 --- a/x-pack/legacy/plugins/actions/server/actions_config.ts +++ b/x-pack/legacy/plugins/actions/server/actions_config.ts @@ -7,11 +7,4 @@ import { schema, TypeOf } from '@kbn/config-schema'; export type ActionsConfigType = TypeOf; - -export type ActionKibanaConfig = TypeOf; - -export const actionConfig = schema.object({ - whitelistedEndpoints: schema.oneOf([schema.arrayOf(schema.string()), schema.literal('any')]), -}); - -export const config = schema.mapOf(schema.string(), actionConfig); +export const config = schema.mapOf(schema.string(), schema.object({}, { allowUnknowns: true })); diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.test.ts index 60793d8621a69..6b55001b91f42 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.test.ts @@ -5,9 +5,12 @@ */ import { Option, none, some } from 'fp-ts/lib/Option'; -import { actionType, validateConfig as validateWebhookConfig } from './webhook'; +import { + actionType, + validateConfig as validateWebhookConfig, + WebhookActionKibanaConfig, +} from './webhook'; import { validateConfig, validateSecrets, validateParams } from '../lib'; -import { ActionKibanaConfig } from '../actions_config'; function actionTypeWithDefaults() { return actionType.configure( @@ -140,7 +143,7 @@ describe('config validation', () => { }; expect( - validateWebhookConfig(kiabanaActionConfig as Option)(config) + validateWebhookConfig(kiabanaActionConfig as Option)(config) ).toMatchInlineSnapshot( `"an error occurred configuring webhook with unwhitelisted target url \\"http://mylisteningserver:9200/endpoint\\""` ); @@ -158,7 +161,7 @@ describe('config validation', () => { }; expect( - validateWebhookConfig(kiabanaActionConfig as Option)(config) + validateWebhookConfig(kiabanaActionConfig as Option)(config) ).toBeUndefined(); }); @@ -174,7 +177,7 @@ describe('config validation', () => { }; expect( - validateWebhookConfig(kiabanaActionConfig as Option)(config) + validateWebhookConfig(kiabanaActionConfig as Option)(config) ).toBeUndefined(); }); @@ -190,7 +193,7 @@ describe('config validation', () => { }; expect( - validateWebhookConfig(kiabanaActionConfig as Option)(config) + validateWebhookConfig(kiabanaActionConfig as Option)(config) ).toBeUndefined(); }); @@ -206,7 +209,7 @@ describe('config validation', () => { }; expect( - validateWebhookConfig(kiabanaActionConfig as Option)(config) + validateWebhookConfig(kiabanaActionConfig as Option)(config) ).toBeUndefined(); }); @@ -222,7 +225,7 @@ describe('config validation', () => { }; expect( - validateWebhookConfig(kiabanaActionConfig as Option)(config) + validateWebhookConfig(kiabanaActionConfig as Option)(config) ).toMatchInlineSnapshot( `"an error occurred configuring webhook with unwhitelisted target url \\"http://mylisteningserver:9200/endpoint\\""` ); diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.ts index 3434cdce53e28..b1e2871fef4b2 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.ts @@ -5,6 +5,7 @@ */ import { i18n } from '@kbn/i18n'; import { URL } from 'url'; +import Boom from 'boom'; import { Option, fromNullable } from 'fp-ts/lib/Option'; import axios, { AxiosError, AxiosResponse } from 'axios'; import { schema, TypeOf } from '@kbn/config-schema'; @@ -17,7 +18,6 @@ import { ActionTypeExecutorResult, ActionType, } from '../types'; -import { ActionKibanaConfig } from '../actions_config'; // config definition enum WebhookMethods { @@ -26,7 +26,6 @@ enum WebhookMethods { } const HeadersSchema = schema.recordOf(schema.string(), schema.string()); -export type ActionTypeConfigType = TypeOf; const configSchemaProps = { url: schema.string(), method: schema.oneOf([schema.literal(WebhookMethods.POST), schema.literal(WebhookMethods.PUT)], { @@ -34,15 +33,30 @@ const configSchemaProps = { }), headers: nullableType(HeadersSchema), }; +export type ActionTypeConfigType = TypeOf; const UnvalidatedConfigSchema = schema.object(configSchemaProps); +export type WebhookActionKibanaConfig = TypeOf; +const ActionTypeConfigSchema = schema.object({ + whitelistedEndpoints: schema.oneOf([schema.arrayOf(schema.string()), schema.literal('any')]), +}); + function asArray(list: string | string[]): string[] { return Array.isArray(list) ? list : [list]; } export function validateConfig( - kibanaConfig: Option + kibanaConfig: Option ): (configObject: any) => string | void { + try { + kibanaConfig.map(config => ActionTypeConfigSchema.validate(config)); + } catch (err) { + throw Boom.badRequest( + `error validating the Webhook Action Kiaban configuration: ${err.message} ${JSON.stringify( + kibanaConfig + )}` + ); + } return configObject => { const { url }: ActionTypeConfigType = configObject; @@ -101,13 +115,13 @@ const ParamsSchema = schema.object({ }); // action type definition -export const actionType: ConfigureableActionType = { +export const actionType: ConfigureableActionType = { id: '.webhook', name: 'webhook', configure: configureActionType, }; -function configureActionType(kibanaConfig: Option): ActionType { +function configureActionType(kibanaConfig: Option): ActionType { const ConfigSchema = schema.object(configSchemaProps, { validate: validateConfig(kibanaConfig), }); diff --git a/x-pack/legacy/plugins/actions/server/types.ts b/x-pack/legacy/plugins/actions/server/types.ts index 96b08b279d3f9..b3376c6638335 100644 --- a/x-pack/legacy/plugins/actions/server/types.ts +++ b/x-pack/legacy/plugins/actions/server/types.ts @@ -8,7 +8,6 @@ import { Option } from 'fp-ts/lib/Option'; import { SavedObjectsClientContract, SavedObjectAttributes } from 'src/core/server'; import { ActionTypeRegistry } from './action_type_registry'; import { ExecuteOptions } from './create_execute_function'; -import { ActionKibanaConfig } from './actions_config'; export type WithoutQueryAndParams = Pick>; export type GetServicesFunction = (request: any) => Services; @@ -90,8 +89,8 @@ export interface ActionTypeBehavior { export interface ActionType extends ActionTypeBehavior, ActionTypeIdentifiers {} -export interface ConfigureableActionType extends ActionTypeIdentifiers { - configure: (config: Option) => ActionType; +export interface ConfigureableActionType extends ActionTypeIdentifiers { + configure: (config: Option) => ActionType; } export interface RawAction extends SavedObjectAttributes {