Skip to content

Commit

Permalink
[actions] adds config allowing per-host networking options
Browse files Browse the repository at this point in the history
resolves: #80120

Adds a new Kibana configuration key xpack.actions.customHostSettings which
allows per-host configuration of connection settings for https and smtp for
alerting actions. Initially this is just for TLS settings, expandable to other
settings in the future.

The purpose of these is to allow customers to provide server certificates for
servers accessed by actions, whose certificate authority is not available
publicly. Alternatively, a per-server rejectUnauthorized: false configuration
may be used to bypass the verification step for specific servers, but require it
for other servers that do not have per-host customization.

Support was also added to allow per-host customization of ignoreTLS and
requireTLS flags for use with the email action.
  • Loading branch information
pmuellr committed Apr 15, 2021
1 parent fdb7be3 commit c2bb7d6
Show file tree
Hide file tree
Showing 18 changed files with 1,402 additions and 7 deletions.
61 changes: 61 additions & 0 deletions docs/settings/alert-action-settings.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,67 @@ You can configure the following settings in the `kibana.yml` file.
+
Note that hosts associated with built-in actions, such as Slack and PagerDuty, are not automatically added to allowed hosts. If you are not using the default `[*]` setting, you must ensure that the corresponding endpoints are added to the allowed hosts as well.

| `xpack.actions.customHostSettings` {ess-icon}
| A list of custom host settings to override existing global settings. It
defaults to an empty list. In the example below, a custom host setting for a
mail server is configured to not bypass certificate validation, provide
server certificate data from both a file and inline, and require TLS for the
connection.

|===

[source,yaml]
--
xpack.actions.customHostSettings:
- url: smtp://mail.example.com
tls:
rejectUnauthorized: false
certificateAuthoritiesFiles: [ 'one.crt' ]
certificateAuthoritiesData: |
-----BEGIN CERTIFICATE-----
... multiple lines of certificate data here ...
-----END CERTIFICATE-----
smtp:
requireTLS: true
--

[cols="2*<"]
|===

| `xpack.actions.customHostSettings[n].url` {ess-icon}
| A URL associated with this custom host setting. Should be in form
`protocol://hostname:port`, where `protocol` is `https` or `smtp`. If the
port is not provided, 443 will be used for `https` and 25 will be used for
`smtp`. The `smtp` URLs will be used for the Email actions which use this
server, and the `https` URLs will be used for actions which use `https` to
connect to services.

Note that no other URL values should be part of this URL, including paths,
query strings, and authentication information. When an http or smtp request
is being made as part of executing an action, only the protocol, hostname and
port of the URL for that request are used to look up these configuration
values.

| `xpack.actions.customHostSettings[n].smtp.ignoreTLS` {ess-icon}
| A boolean value indicatting that TLS must not be used for this connection.

| `xpack.actions.customHostSettings[n].smtp.requireTLS` {ess-icon}
| A boolean value indicatting that TLS must be used for this connection.

| `xpack.actions.customHostSettings[n].tls.rejectUnauthorized` {ess-icon}
| A boolean value indicating whether to bypass to certificate validation
and overrides the general `xpack.actions.rejectUnauthorized` configuration,
just for requests made for this hostname/port.

| `xpack.actions.customHostSettings[n].tls.certificateAuthoritiesFiles` {ess-icon}
| A file name or list of file names of PEM-encoded certificate files which
should be used to validate the server.

| `xpack.actions.customHostSettings[n].tls.certificateAuthoritiesData` {ess-icon}
| The contents of a PEM-encoded certificate file, or multiple files appended
into a single string. This configuration can be used for environments where
the files themselves cannot be made available.

| `xpack.actions.enabledActionTypes` {ess-icon}
| A list of action types that are enabled. It defaults to `[*]`, enabling all types. The names for built-in {kib} action types are prefixed with a `.` and include: `.server-log`, `.slack`, `.email`, `.index`, `.pagerduty`, and `.webhook`. An empty list `[]` will disable all action types. +
+
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ kibana_vars=(
timelion.enabled
vega.enableExternalUrls
xpack.actions.allowedHosts
xpack.actions.customHostSettings
xpack.actions.enabled
xpack.actions.enabledActionTypes
xpack.actions.preconfiguredAlertHistoryEsIndex
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/actions/server/actions_config.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const createActionsConfigMock = () => {
maxContentLength: 1000000,
timeout: 360000,
}),
getCustomHostSettings: jest.fn().mockReturnValue(undefined),
};
return mocked;
};
Expand Down
81 changes: 81 additions & 0 deletions x-pack/plugins/actions/server/actions_config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,14 @@ import {
AllowedHosts,
EnabledActionTypes,
} from './actions_config';
import { resolveCustomHosts } from './lib/custom_host_settings';
import { Logger } from '../../../../src/core/server';
import { loggingSystemMock } from '../../../../src/core/server/mocks';

import moment from 'moment';

const mockLogger = loggingSystemMock.create().get() as jest.Mocked<Logger>;

const defaultActionsConfig: ActionsConfig = {
enabled: false,
allowedHosts: [],
Expand Down Expand Up @@ -348,4 +354,79 @@ describe('getProxySettings', () => {
const proxySettings = getActionsConfigurationUtilities(config).getProxySettings();
expect(proxySettings?.proxyOnlyHosts).toEqual(new Set(proxyOnlyHosts));
});

test('getCustomHostSettings() returns undefined when no matching config', () => {
const httpsUrl = 'https://elastic.co/foo/bar';
const smtpUrl = 'smtp://elastic.co';
let config: ActionsConfig = resolveCustomHosts(mockLogger, {
...defaultActionsConfig,
});

let chs = getActionsConfigurationUtilities(config).getCustomHostSettings(httpsUrl);
expect(chs).toEqual(undefined);
chs = getActionsConfigurationUtilities(config).getCustomHostSettings(smtpUrl);
expect(chs).toEqual(undefined);

config = resolveCustomHosts(mockLogger, {
...defaultActionsConfig,
customHostSettings: [],
});
chs = getActionsConfigurationUtilities(config).getCustomHostSettings(httpsUrl);
expect(chs).toEqual(undefined);
chs = getActionsConfigurationUtilities(config).getCustomHostSettings(smtpUrl);
expect(chs).toEqual(undefined);

config = resolveCustomHosts(mockLogger, {
...defaultActionsConfig,
customHostSettings: [
{
url: 'https://www.elastic.co:443',
},
],
});
chs = getActionsConfigurationUtilities(config).getCustomHostSettings(httpsUrl);
expect(chs).toEqual(undefined);
chs = getActionsConfigurationUtilities(config).getCustomHostSettings(smtpUrl);
expect(chs).toEqual(undefined);
});

test('getCustomHostSettings() returns matching config', () => {
const httpsUrl = 'https://elastic.co/ignoring/paths/here';
const smtpUrl = 'smtp://elastic.co:123';
const config: ActionsConfig = resolveCustomHosts(mockLogger, {
...defaultActionsConfig,
customHostSettings: [
{
url: 'https://elastic.co',
tls: {
rejectUnauthorized: true,
},
},
{
url: 'smtp://elastic.co:123',
tls: {
rejectUnauthorized: false,
},
smtp: {
ignoreTLS: true,
},
},
],
});

let chs = getActionsConfigurationUtilities(config).getCustomHostSettings(httpsUrl);
expect(chs).toEqual(config.customHostSettings![0]);
chs = getActionsConfigurationUtilities(config).getCustomHostSettings(smtpUrl);
expect(chs).toEqual(config.customHostSettings![1]);
});

test('getCustomHostSettings() returns undefined when bad url is passed in', () => {
const badUrl = 'https://elastic.co/foo/bar';
const config: ActionsConfig = resolveCustomHosts(mockLogger, {
...defaultActionsConfig,
});

const chs = getActionsConfigurationUtilities(config).getCustomHostSettings(badUrl);
expect(chs).toEqual(undefined);
});
});
26 changes: 25 additions & 1 deletion x-pack/plugins/actions/server/actions_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import url from 'url';
import { curry } from 'lodash';
import { pipe } from 'fp-ts/lib/pipeable';

import { ActionsConfig, AllowedHosts, EnabledActionTypes } from './config';
import { ActionsConfig, AllowedHosts, EnabledActionTypes, CustomHostSettings } from './config';
import { getCanonicalCustomHostUrl } from './lib/custom_host_settings';
import { ActionTypeDisabledError } from './lib';
import { ProxySettings, ResponseSettings } from './types';

Expand All @@ -32,6 +33,7 @@ export interface ActionsConfigurationUtilities {
isRejectUnauthorizedCertificatesEnabled: () => boolean;
getProxySettings: () => undefined | ProxySettings;
getResponseSettings: () => ResponseSettings;
getCustomHostSettings: (targetUrl: string) => CustomHostSettings | undefined;
}

function allowListErrorMessage(field: AllowListingField, value: string) {
Expand Down Expand Up @@ -107,6 +109,27 @@ function getResponseSettingsFromConfig(config: ActionsConfig): ResponseSettings
};
}

function getCustomHostSettings(
config: ActionsConfig,
targetUrl: string
): CustomHostSettings | undefined {
const customHostSettings = config.customHostSettings;
if (!customHostSettings) {
return;
}

let parsedUrl: URL | undefined;
try {
parsedUrl = new URL(targetUrl);
} catch (err) {
// presumably this bad URL is reported elsewhere
return;
}

const canonicalUrl = getCanonicalCustomHostUrl(parsedUrl);
return customHostSettings.find((settings) => settings.url === canonicalUrl);
}

export function getActionsConfigurationUtilities(
config: ActionsConfig
): ActionsConfigurationUtilities {
Expand Down Expand Up @@ -135,5 +158,6 @@ export function getActionsConfigurationUtilities(
throw new ActionTypeDisabledError(disabledActionTypeErrorMessage(actionType), 'config');
}
},
getCustomHostSettings: (targetUrl: string) => getCustomHostSettings(config, targetUrl),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ describe('execute()', () => {
"ensureActionTypeEnabled": [MockFunction],
"ensureHostnameAllowed": [MockFunction],
"ensureUriAllowed": [MockFunction],
"getCustomHostSettings": [MockFunction],
"getProxySettings": [MockFunction],
"getResponseSettings": [MockFunction],
"isActionTypeEnabled": [MockFunction],
Expand Down Expand Up @@ -342,6 +343,7 @@ describe('execute()', () => {
"ensureActionTypeEnabled": [MockFunction],
"ensureHostnameAllowed": [MockFunction],
"ensureUriAllowed": [MockFunction],
"getCustomHostSettings": [MockFunction],
"getProxySettings": [MockFunction],
"getResponseSettings": [MockFunction],
"isActionTypeEnabled": [MockFunction],
Expand Down
Loading

0 comments on commit c2bb7d6

Please sign in to comment.