Skip to content

Commit

Permalink
adds per-actionType enablement via config xpack.actions.enabledAction…
Browse files Browse the repository at this point in the history
…Types (#52967)

* adds per-actionType enablement via config xpack.actions.enabledTypes

resolves: #52326
  • Loading branch information
pmuellr authored Dec 17, 2019
1 parent 341630d commit 4f5db14
Show file tree
Hide file tree
Showing 28 changed files with 502 additions and 49 deletions.
6 changes: 5 additions & 1 deletion docs/setup/settings.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -390,12 +390,16 @@ Rollup user interface.

`i18n.locale`:: *Default: en* Set this value to change the Kibana interface language. Valid locales are: `en`, `zh-CN`, `ja-JP`.

`xpack.actions.enabledActionTypes:`:: *Default: +[ {asterisk} ]+* Set this value
to an array of action types that are enabled. An element of `*` indicates all
action types registered are enabled. The action types provided by Kibana are:
`.server-log`, `.slack`, `.email`, `.index`, `.pagerduty`, `.webhook`.

`xpack.actions.whitelistedHosts:`:: *Default: +[ {asterisk} ]+* Set this value
to an array of host names which actions such as email, slack, pagerduty, and
webhook can connect to. An element of `*` indicates any host can be connected
to. An empty array indicates no hosts can be connected to.


include::{docdir}/settings/apm-settings.asciidoc[]
include::{docdir}/settings/dev-settings.asciidoc[]
include::{docdir}/settings/graph-settings.asciidoc[]
Expand Down
3 changes: 3 additions & 0 deletions x-pack/legacy/plugins/actions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Built-In-Actions are configured using the _xpack.actions_ namespoace under _kiba
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
| _xpack.actions._**enabled** | Feature toggle which enabled Actions in Kibana. | boolean |
| _xpack.actions._**whitelistedHosts** | Which _hostnames_ are whitelisted for the Built-In-Action? This list should contain hostnames of every external service you wish to interact with using Webhooks, Email or any other built in Action. Note that you may use the string "\*" in place of a specific hostname to enable Kibana to target any URL, but keep in mind the potential use of such a feature to execute [SSRF](https://www.owasp.org/index.php/Server_Side_Request_Forgery) attacks from your server. | Array<String> |
| _xpack.actions._**enabledActionTypes** | A list of _actionTypes_ id's that are enabled. A "\*" may be used as an element to indicate all registered actionTypes should be enabled. The actionTypes registered for Kibana are `.server-log`, `.slack`, `.email`, `.index`, `.pagerduty`, `.webhook`. Default: `["*"]` | Array<String> |

#### Whitelisting Built-in Action Types
It is worth noting that the **whitelistedHosts** configuation applies to built-in action types (such as Slack, or PagerDuty) as well.
Expand All @@ -49,8 +50,10 @@ This module provides a Utilities for interacting with the configuration.
| --------------------- | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| isWhitelistedUri | _uri_: The URI you wish to validate is whitelisted | Validates whether the URI is whitelisted. This checks the configuration and validates that the hostname of the URI is in the list of whitelisted Hosts and returns `true` if it is whitelisted. If the configuration says that all URI's are whitelisted (using an "\*") then it will always return `true`. | Boolean |
| isWhitelistedHostname | _hostname_: The Hostname you wish to validate is whitelisted | Validates whether the Hostname is whitelisted. This checks the configuration and validates that the hostname is in the list of whitelisted Hosts and returns `true` if it is whitelisted. If the configuration says that all Hostnames are whitelisted (using an "\*") then it will always return `true`. | Boolean |
| isActionTypeEnabled | _actionType_: The actionType to check to see if it's enabled | Returns true if the actionType is enabled, otherwise false. | Boolean |
| ensureWhitelistedUri | _uri_: The URI you wish to validate is whitelisted | Validates whether the URI is whitelisted. This checks the configuration and validates that the hostname of the URI is in the list of whitelisted Hosts and throws an error if it is not whitelisted. If the configuration says that all URI's are whitelisted (using an "\*") then it will never throw. | No return value, throws if URI isn't whitelisted |
| ensureWhitelistedHostname | _hostname_: The Hostname you wish to validate is whitelisted | Validates whether the Hostname is whitelisted. This checks the configuration and validates that the hostname is in the list of whitelisted Hosts and throws an error if it is not whitelisted. If the configuration says that all Hostnames are whitelisted (using an "\*") then it will never throw | No return value, throws if Hostname isn't whitelisted |
| ensureActionTypeEnabled | _actionType_: The actionType to check to see if it's enabled | Throws an error if the actionType is not enabled | No return value, throws if actionType isn't enabled |

## Action types

Expand Down
9 changes: 7 additions & 2 deletions x-pack/legacy/plugins/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Legacy } from 'kibana';
import { Root } from 'joi';
import mappings from './mappings.json';
import { init } from './server';
import { WhitelistedHosts, EnabledActionTypes } from './server/actions_config';

export {
ActionsPlugin,
Expand Down Expand Up @@ -38,10 +39,14 @@ export function actions(kibana: any) {
.items(
Joi.string()
.hostname()
.allow('*')
.allow(WhitelistedHosts.Any)
)
.sparse(false)
.default(['*']),
.default([WhitelistedHosts.Any]),
enabledActionTypes: Joi.array()
.items(Joi.string())
.sparse(false)
.default([EnabledActionTypes.Any]),
})
.default();
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const createActionTypeRegistryMock = () => {
register: jest.fn(),
get: jest.fn(),
list: jest.fn(),
ensureActionTypeEnabled: jest.fn(),
};
return mocked;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import { taskManagerMock } from '../../task_manager/task_manager.mock';
import { ActionTypeRegistry } from './action_type_registry';
import { ExecutorType } from './types';
import { ActionExecutor, ExecutorError, TaskRunnerFactory } from './lib';
import { configUtilsMock } from './actions_config.mock';

const mockTaskManager = taskManagerMock.create();
const actionTypeRegistryParams = {
taskManager: mockTaskManager,
taskRunnerFactory: new TaskRunnerFactory(new ActionExecutor()),
actionsConfigUtils: configUtilsMock,
};

beforeEach(() => jest.resetAllMocks());
Expand Down Expand Up @@ -123,6 +125,7 @@ describe('list()', () => {
{
id: 'my-action-type',
name: 'My action type',
enabled: true,
},
]);
});
Expand Down
21 changes: 16 additions & 5 deletions x-pack/legacy/plugins/actions/server/action_type_registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,23 @@ import { TaskManagerSetupContract } from './shim';
import { RunContext } from '../../task_manager';
import { ExecutorError, TaskRunnerFactory } from './lib';
import { ActionType } from './types';

import { ActionsConfigurationUtilities } from './actions_config';
interface ConstructorOptions {
taskManager: TaskManagerSetupContract;
taskRunnerFactory: TaskRunnerFactory;
actionsConfigUtils: ActionsConfigurationUtilities;
}

export class ActionTypeRegistry {
private readonly taskManager: TaskManagerSetupContract;
private readonly actionTypes: Map<string, ActionType> = new Map();
private readonly taskRunnerFactory: TaskRunnerFactory;
private readonly actionsConfigUtils: ActionsConfigurationUtilities;

constructor({ taskManager, taskRunnerFactory }: ConstructorOptions) {
this.taskManager = taskManager;
this.taskRunnerFactory = taskRunnerFactory;
constructor(constructorParams: ConstructorOptions) {
this.taskManager = constructorParams.taskManager;
this.taskRunnerFactory = constructorParams.taskRunnerFactory;
this.actionsConfigUtils = constructorParams.actionsConfigUtils;
}

/**
Expand All @@ -33,6 +36,13 @@ export class ActionTypeRegistry {
return this.actionTypes.has(id);
}

/**
* Throws error if action type is not enabled.
*/
public ensureActionTypeEnabled(id: string) {
this.actionsConfigUtils.ensureActionTypeEnabled(id);
}

/**
* Registers an action type to the action type registry
*/
Expand Down Expand Up @@ -86,12 +96,13 @@ export class ActionTypeRegistry {
}

/**
* Returns a list of registered action types [{ id, name }]
* Returns a list of registered action types [{ id, name, enabled }]
*/
public list() {
return Array.from(this.actionTypes).map(([actionTypeId, actionType]) => ({
id: actionTypeId,
name: actionType.name,
enabled: this.actionsConfigUtils.isActionTypeEnabled(actionTypeId),
}));
}
}
56 changes: 56 additions & 0 deletions x-pack/legacy/plugins/actions/server/actions_client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import { ActionsClient } from './actions_client';
import { ExecutorType } from './types';
import { ActionExecutor, TaskRunnerFactory } from './lib';
import { taskManagerMock } from '../../task_manager/task_manager.mock';
import { configUtilsMock } from './actions_config.mock';
import { getActionsConfigurationUtilities } from './actions_config';

import {
elasticsearchServiceMock,
savedObjectsClientMock,
Expand All @@ -25,6 +28,7 @@ const mockTaskManager = taskManagerMock.create();
const actionTypeRegistryParams = {
taskManager: mockTaskManager,
taskRunnerFactory: new TaskRunnerFactory(new ActionExecutor()),
actionsConfigUtils: configUtilsMock,
};

let actionsClient: ActionsClient;
Expand Down Expand Up @@ -190,6 +194,58 @@ describe('create()', () => {
]
`);
});

test('throws error creating action with disabled actionType', async () => {
const localConfigUtils = getActionsConfigurationUtilities({
enabled: true,
enabledActionTypes: ['some-not-ignored-action-type'],
whitelistedHosts: ['*'],
});

const localActionTypeRegistryParams = {
taskManager: mockTaskManager,
taskRunnerFactory: new TaskRunnerFactory(new ActionExecutor()),
actionsConfigUtils: localConfigUtils,
};

actionTypeRegistry = new ActionTypeRegistry(localActionTypeRegistryParams);
actionsClient = new ActionsClient({
actionTypeRegistry,
savedObjectsClient,
scopedClusterClient,
defaultKibanaIndex,
});

const savedObjectCreateResult = {
id: '1',
type: 'type',
attributes: {
name: 'my name',
actionTypeId: 'my-action-type',
config: {},
},
references: [],
};
actionTypeRegistry.register({
id: 'my-action-type',
name: 'My action type',
executor,
});
savedObjectsClient.create.mockResolvedValueOnce(savedObjectCreateResult);

await expect(
actionsClient.create({
action: {
name: 'my name',
actionTypeId: 'my-action-type',
config: {},
secrets: {},
},
})
).rejects.toThrowErrorMatchingInlineSnapshot(
`"action type \\"my-action-type\\" is not enabled in the Kibana config xpack.actions.enabledActionTypes"`
);
});
});

describe('get()', () => {
Expand Down
7 changes: 7 additions & 0 deletions x-pack/legacy/plugins/actions/server/actions_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import Boom from 'boom';
import {
IScopedClusterClient,
SavedObjectsClientContract,
Expand Down Expand Up @@ -92,6 +93,12 @@ export class ActionsClient {
const validatedActionTypeConfig = validateConfig(actionType, config);
const validatedActionTypeSecrets = validateSecrets(actionType, secrets);

try {
this.actionTypeRegistry.ensureActionTypeEnabled(actionTypeId);
} catch (err) {
throw Boom.badRequest(err.message);
}

const result = await this.savedObjectsClient.create('action', {
actionTypeId,
name,
Expand Down
2 changes: 2 additions & 0 deletions x-pack/legacy/plugins/actions/server/actions_config.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { ActionsConfigurationUtilities } from './actions_config';
export const configUtilsMock: ActionsConfigurationUtilities = {
isWhitelistedHostname: _ => true,
isWhitelistedUri: _ => true,
isActionTypeEnabled: _ => true,
ensureWhitelistedHostname: _ => {},
ensureWhitelistedUri: _ => {},
ensureActionTypeEnabled: _ => {},
};
Loading

0 comments on commit 4f5db14

Please sign in to comment.