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>
This commit is contained in:
Mike Côté 2021-01-28 13:44:25 -05:00 committed by GitHub
parent 074003d4b4
commit da8ce374cf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 597 additions and 371 deletions

View file

@ -402,6 +402,9 @@ describe('create()', () => {
enabled: true,
enabledActionTypes: ['some-not-ignored-action-type'],
allowedHosts: ['*'],
preconfigured: {},
proxyRejectUnauthorizedCertificates: true,
rejectUnauthorized: true,
});
const localActionTypeRegistryParams = {

View file

@ -14,6 +14,8 @@ const createActionsConfigMock = () => {
ensureHostnameAllowed: jest.fn().mockReturnValue({}),
ensureUriAllowed: jest.fn().mockReturnValue({}),
ensureActionTypeEnabled: jest.fn().mockReturnValue({}),
isRejectUnauthorizedCertificatesEnabled: jest.fn().mockReturnValue(true),
getProxySettings: jest.fn().mockReturnValue(undefined),
};
return mocked;
};

View file

@ -4,22 +4,26 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { ActionsConfigType } from './types';
import { ActionsConfig } from './config';
import {
getActionsConfigurationUtilities,
AllowedHosts,
EnabledActionTypes,
} from './actions_config';
const DefaultActionsConfig: ActionsConfigType = {
const defaultActionsConfig: ActionsConfig = {
enabled: false,
allowedHosts: [],
enabledActionTypes: [],
preconfigured: {},
proxyRejectUnauthorizedCertificates: true,
rejectUnauthorized: true,
};
describe('ensureUriAllowed', () => {
test('returns true when "any" hostnames are allowed', () => {
const config: ActionsConfigType = {
const config: ActionsConfig = {
...defaultActionsConfig,
enabled: false,
allowedHosts: [AllowedHosts.Any],
enabledActionTypes: [],
@ -30,7 +34,7 @@ describe('ensureUriAllowed', () => {
});
test('throws when the hostname in the requested uri is not in the allowedHosts', () => {
const config: ActionsConfigType = DefaultActionsConfig;
const config: ActionsConfig = defaultActionsConfig;
expect(() =>
getActionsConfigurationUtilities(config).ensureUriAllowed('https://github.com/elastic/kibana')
).toThrowErrorMatchingInlineSnapshot(
@ -39,7 +43,7 @@ describe('ensureUriAllowed', () => {
});
test('throws when the uri cannot be parsed as a valid URI', () => {
const config: ActionsConfigType = DefaultActionsConfig;
const config: ActionsConfig = defaultActionsConfig;
expect(() =>
getActionsConfigurationUtilities(config).ensureUriAllowed('github.com/elastic')
).toThrowErrorMatchingInlineSnapshot(
@ -48,7 +52,8 @@ describe('ensureUriAllowed', () => {
});
test('returns true when the hostname in the requested uri is in the allowedHosts', () => {
const config: ActionsConfigType = {
const config: ActionsConfig = {
...defaultActionsConfig,
enabled: false,
allowedHosts: ['github.com'],
enabledActionTypes: [],
@ -61,7 +66,8 @@ describe('ensureUriAllowed', () => {
describe('ensureHostnameAllowed', () => {
test('returns true when "any" hostnames are allowed', () => {
const config: ActionsConfigType = {
const config: ActionsConfig = {
...defaultActionsConfig,
enabled: false,
allowedHosts: [AllowedHosts.Any],
enabledActionTypes: [],
@ -72,7 +78,7 @@ describe('ensureHostnameAllowed', () => {
});
test('throws when the hostname in the requested uri is not in the allowedHosts', () => {
const config: ActionsConfigType = DefaultActionsConfig;
const config: ActionsConfig = defaultActionsConfig;
expect(() =>
getActionsConfigurationUtilities(config).ensureHostnameAllowed('github.com')
).toThrowErrorMatchingInlineSnapshot(
@ -81,7 +87,8 @@ describe('ensureHostnameAllowed', () => {
});
test('returns true when the hostname in the requested uri is in the allowedHosts', () => {
const config: ActionsConfigType = {
const config: ActionsConfig = {
...defaultActionsConfig,
enabled: false,
allowedHosts: ['github.com'],
enabledActionTypes: [],
@ -94,7 +101,8 @@ describe('ensureHostnameAllowed', () => {
describe('isUriAllowed', () => {
test('returns true when "any" hostnames are allowed', () => {
const config: ActionsConfigType = {
const config: ActionsConfig = {
...defaultActionsConfig,
enabled: false,
allowedHosts: [AllowedHosts.Any],
enabledActionTypes: [],
@ -105,21 +113,22 @@ describe('isUriAllowed', () => {
});
test('throws when the hostname in the requested uri is not in the allowedHosts', () => {
const config: ActionsConfigType = DefaultActionsConfig;
const config: ActionsConfig = defaultActionsConfig;
expect(
getActionsConfigurationUtilities(config).isUriAllowed('https://github.com/elastic/kibana')
).toEqual(false);
});
test('throws when the uri cannot be parsed as a valid URI', () => {
const config: ActionsConfigType = DefaultActionsConfig;
const config: ActionsConfig = defaultActionsConfig;
expect(getActionsConfigurationUtilities(config).isUriAllowed('github.com/elastic')).toEqual(
false
);
});
test('returns true when the hostname in the requested uri is in the allowedHosts', () => {
const config: ActionsConfigType = {
const config: ActionsConfig = {
...defaultActionsConfig,
enabled: false,
allowedHosts: ['github.com'],
enabledActionTypes: [],
@ -132,7 +141,8 @@ describe('isUriAllowed', () => {
describe('isHostnameAllowed', () => {
test('returns true when "any" hostnames are allowed', () => {
const config: ActionsConfigType = {
const config: ActionsConfig = {
...defaultActionsConfig,
enabled: false,
allowedHosts: [AllowedHosts.Any],
enabledActionTypes: [],
@ -141,12 +151,13 @@ describe('isHostnameAllowed', () => {
});
test('throws when the hostname in the requested uri is not in the allowedHosts', () => {
const config: ActionsConfigType = DefaultActionsConfig;
const config: ActionsConfig = defaultActionsConfig;
expect(getActionsConfigurationUtilities(config).isHostnameAllowed('github.com')).toEqual(false);
});
test('returns true when the hostname in the requested uri is in the allowedHosts', () => {
const config: ActionsConfigType = {
const config: ActionsConfig = {
...defaultActionsConfig,
enabled: false,
allowedHosts: ['github.com'],
enabledActionTypes: [],
@ -157,7 +168,8 @@ describe('isHostnameAllowed', () => {
describe('isActionTypeEnabled', () => {
test('returns true when "any" actionTypes are allowed', () => {
const config: ActionsConfigType = {
const config: ActionsConfig = {
...defaultActionsConfig,
enabled: false,
allowedHosts: [],
enabledActionTypes: ['ignore', EnabledActionTypes.Any],
@ -166,7 +178,8 @@ describe('isActionTypeEnabled', () => {
});
test('returns false when no actionType is allowed', () => {
const config: ActionsConfigType = {
const config: ActionsConfig = {
...defaultActionsConfig,
enabled: false,
allowedHosts: [],
enabledActionTypes: [],
@ -175,7 +188,8 @@ describe('isActionTypeEnabled', () => {
});
test('returns false when the actionType is not in the enabled list', () => {
const config: ActionsConfigType = {
const config: ActionsConfig = {
...defaultActionsConfig,
enabled: false,
allowedHosts: [],
enabledActionTypes: ['foo'],
@ -184,7 +198,8 @@ describe('isActionTypeEnabled', () => {
});
test('returns true when the actionType is in the enabled list', () => {
const config: ActionsConfigType = {
const config: ActionsConfig = {
...defaultActionsConfig,
enabled: false,
allowedHosts: [],
enabledActionTypes: ['ignore', 'foo'],
@ -195,7 +210,8 @@ describe('isActionTypeEnabled', () => {
describe('ensureActionTypeEnabled', () => {
test('does not throw when any actionType is allowed', () => {
const config: ActionsConfigType = {
const config: ActionsConfig = {
...defaultActionsConfig,
enabled: false,
allowedHosts: [],
enabledActionTypes: ['ignore', EnabledActionTypes.Any],
@ -204,7 +220,7 @@ describe('ensureActionTypeEnabled', () => {
});
test('throws when no actionType is not allowed', () => {
const config: ActionsConfigType = DefaultActionsConfig;
const config: ActionsConfig = defaultActionsConfig;
expect(() =>
getActionsConfigurationUtilities(config).ensureActionTypeEnabled('foo')
).toThrowErrorMatchingInlineSnapshot(
@ -213,7 +229,8 @@ describe('ensureActionTypeEnabled', () => {
});
test('throws when actionType is not enabled', () => {
const config: ActionsConfigType = {
const config: ActionsConfig = {
...defaultActionsConfig,
enabled: false,
allowedHosts: [],
enabledActionTypes: ['ignore'],
@ -226,7 +243,8 @@ describe('ensureActionTypeEnabled', () => {
});
test('does not throw when actionType is enabled', () => {
const config: ActionsConfigType = {
const config: ActionsConfig = {
...defaultActionsConfig,
enabled: false,
allowedHosts: [],
enabledActionTypes: ['ignore', 'foo'],

View file

@ -10,8 +10,9 @@ import url from 'url';
import { curry } from 'lodash';
import { pipe } from 'fp-ts/lib/pipeable';
import { ActionsConfigType } from './types';
import { ActionsConfig } from './config';
import { ActionTypeDisabledError } from './lib';
import { ProxySettings } from './types';
export enum AllowedHosts {
Any = '*',
@ -33,6 +34,8 @@ export interface ActionsConfigurationUtilities {
ensureHostnameAllowed: (hostname: string) => void;
ensureUriAllowed: (uri: string) => void;
ensureActionTypeEnabled: (actionType: string) => void;
isRejectUnauthorizedCertificatesEnabled: () => boolean;
getProxySettings: () => undefined | ProxySettings;
}
function allowListErrorMessage(field: AllowListingField, value: string) {
@ -56,14 +59,14 @@ function disabledActionTypeErrorMessage(actionType: string) {
});
}
function isAllowed({ allowedHosts }: ActionsConfigType, hostname: string | null): boolean {
function isAllowed({ allowedHosts }: ActionsConfig, hostname: string | null): boolean {
const allowed = new Set(allowedHosts);
if (allowed.has(AllowedHosts.Any)) return true;
if (hostname && allowed.has(hostname)) return true;
return false;
}
function isHostnameAllowedInUri(config: ActionsConfigType, uri: string): boolean {
function isHostnameAllowedInUri(config: ActionsConfig, uri: string): boolean {
return pipe(
tryCatch(() => url.parse(uri)),
map((parsedUrl) => parsedUrl.hostname),
@ -73,7 +76,7 @@ function isHostnameAllowedInUri(config: ActionsConfigType, uri: string): boolean
}
function isActionTypeEnabledInConfig(
{ enabledActionTypes }: ActionsConfigType,
{ enabledActionTypes }: ActionsConfig,
actionType: string
): boolean {
const enabled = new Set(enabledActionTypes);
@ -82,8 +85,20 @@ function isActionTypeEnabledInConfig(
return false;
}
function getProxySettingsFromConfig(config: ActionsConfig): undefined | ProxySettings {
if (!config.proxyUrl) {
return undefined;
}
return {
proxyUrl: config.proxyUrl,
proxyHeaders: config.proxyHeaders,
proxyRejectUnauthorizedCertificates: config.proxyRejectUnauthorizedCertificates,
};
}
export function getActionsConfigurationUtilities(
config: ActionsConfigType
config: ActionsConfig
): ActionsConfigurationUtilities {
const isHostnameAllowed = curry(isAllowed)(config);
const isUriAllowed = curry(isHostnameAllowedInUri)(config);
@ -92,6 +107,8 @@ export function getActionsConfigurationUtilities(
isHostnameAllowed,
isUriAllowed,
isActionTypeEnabled,
getProxySettings: () => getProxySettingsFromConfig(config),
isRejectUnauthorizedCertificatesEnabled: () => config.rejectUnauthorized,
ensureUriAllowed(uri: string) {
if (!isUriAllowed(uri)) {
throw new Error(allowListErrorMessage(AllowListingField.URL, uri));

View file

@ -277,6 +277,16 @@ describe('execute()', () => {
`);
expect(sendEmailMock.mock.calls[0][1]).toMatchInlineSnapshot(`
Object {
"configurationUtilities": Object {
"ensureActionTypeEnabled": [MockFunction],
"ensureHostnameAllowed": [MockFunction],
"ensureUriAllowed": [MockFunction],
"getProxySettings": [MockFunction],
"isActionTypeEnabled": [MockFunction],
"isHostnameAllowed": [MockFunction],
"isRejectUnauthorizedCertificatesEnabled": [MockFunction],
"isUriAllowed": [MockFunction],
},
"content": Object {
"message": "a message to you
@ -286,7 +296,6 @@ describe('execute()', () => {
"subject": "the subject",
},
"hasAuth": true,
"proxySettings": undefined,
"routing": Object {
"bcc": Array [
"jimmy@example.com",
@ -327,6 +336,16 @@ describe('execute()', () => {
await actionType.executor(customExecutorOptions);
expect(sendEmailMock.mock.calls[0][1]).toMatchInlineSnapshot(`
Object {
"configurationUtilities": Object {
"ensureActionTypeEnabled": [MockFunction],
"ensureHostnameAllowed": [MockFunction],
"ensureUriAllowed": [MockFunction],
"getProxySettings": [MockFunction],
"isActionTypeEnabled": [MockFunction],
"isHostnameAllowed": [MockFunction],
"isRejectUnauthorizedCertificatesEnabled": [MockFunction],
"isUriAllowed": [MockFunction],
},
"content": Object {
"message": "a message to you
@ -336,7 +355,6 @@ describe('execute()', () => {
"subject": "the subject",
},
"hasAuth": false,
"proxySettings": undefined,
"routing": Object {
"bcc": Array [
"jimmy@example.com",

View file

@ -156,7 +156,7 @@ export function getActionType(params: GetActionTypeParams): EmailActionType {
params: ParamsSchema,
},
renderParameterTemplates,
executor: curry(executor)({ logger, publicBaseUrl }),
executor: curry(executor)({ logger, publicBaseUrl, configurationUtilities }),
};
}
@ -178,7 +178,12 @@ async function executor(
{
logger,
publicBaseUrl,
}: { logger: GetActionTypeParams['logger']; publicBaseUrl: GetActionTypeParams['publicBaseUrl'] },
configurationUtilities,
}: {
logger: GetActionTypeParams['logger'];
publicBaseUrl: GetActionTypeParams['publicBaseUrl'];
configurationUtilities: ActionsConfigurationUtilities;
},
execOptions: EmailActionTypeExecutorOptions
): Promise<ActionTypeExecutorResult<unknown>> {
const actionId = execOptions.actionId;
@ -221,8 +226,8 @@ async function executor(
subject: params.subject,
message: `${params.message}${EMAIL_FOOTER_DIVIDER}${footerMessage}`,
},
proxySettings: execOptions.proxySettings,
hasAuth: config.hasAuth,
configurationUtilities,
};
let result;

View file

@ -72,13 +72,16 @@ export function getActionType(
}),
params: ExecutorParamsSchema,
},
executor: curry(executor)({ logger }),
executor: curry(executor)({ logger, configurationUtilities }),
};
}
// action executor
async function executor(
{ logger }: { logger: Logger },
{
logger,
configurationUtilities,
}: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities },
execOptions: ActionTypeExecutorOptions<
JiraPublicConfigurationType,
JiraSecretConfigurationType,
@ -95,7 +98,7 @@ async function executor(
secrets,
},
logger,
execOptions.proxySettings
configurationUtilities
);
if (!api[subAction]) {

View file

@ -11,6 +11,7 @@ import * as utils from '../lib/axios_utils';
import { ExternalService } from './types';
import { Logger } from '../../../../../../src/core/server';
import { loggingSystemMock } from '../../../../../../src/core/server/mocks';
import { actionsConfigMock } from '../../actions_config.mock';
const logger = loggingSystemMock.create().get() as jest.Mocked<Logger>;
interface ResponseError extends Error {
@ -28,6 +29,7 @@ jest.mock('../lib/axios_utils', () => {
axios.create = jest.fn(() => axios);
const requestMock = utils.request as jest.Mock;
const configurationUtilities = actionsConfigMock.create();
const issueTypesResponse = {
data: {
@ -116,7 +118,8 @@ describe('Jira service', () => {
config: { apiUrl: 'https://siem-kibana.atlassian.net/', projectKey: 'CK' },
secrets: { apiToken: 'token', email: 'elastic@elastic.com' },
},
logger
logger,
configurationUtilities
);
});
@ -132,7 +135,8 @@ describe('Jira service', () => {
config: { apiUrl: null, projectKey: 'CK' },
secrets: { apiToken: 'token', email: 'elastic@elastic.com' },
},
logger
logger,
configurationUtilities
)
).toThrow();
});
@ -144,7 +148,8 @@ describe('Jira service', () => {
config: { apiUrl: 'test.com', projectKey: null },
secrets: { apiToken: 'token', email: 'elastic@elastic.com' },
},
logger
logger,
configurationUtilities
)
).toThrow();
});
@ -156,7 +161,8 @@ describe('Jira service', () => {
config: { apiUrl: 'test.com' },
secrets: { apiToken: '', email: 'elastic@elastic.com' },
},
logger
logger,
configurationUtilities
)
).toThrow();
});
@ -168,7 +174,8 @@ describe('Jira service', () => {
config: { apiUrl: 'test.com' },
secrets: { apiToken: '', email: undefined },
},
logger
logger,
configurationUtilities
)
).toThrow();
});
@ -193,6 +200,7 @@ describe('Jira service', () => {
axios,
url: 'https://siem-kibana.atlassian.net/rest/api/2/issue/1',
logger,
configurationUtilities,
});
});
@ -293,6 +301,7 @@ describe('Jira service', () => {
url: 'https://siem-kibana.atlassian.net/rest/api/2/issue',
logger,
method: 'post',
configurationUtilities,
data: {
fields: {
summary: 'title',
@ -331,6 +340,7 @@ describe('Jira service', () => {
url: 'https://siem-kibana.atlassian.net/rest/api/2/issue',
logger,
method: 'post',
configurationUtilities,
data: {
fields: {
summary: 'title',
@ -424,6 +434,7 @@ describe('Jira service', () => {
axios,
logger,
method: 'put',
configurationUtilities,
url: 'https://siem-kibana.atlassian.net/rest/api/2/issue/1',
data: {
fields: {
@ -510,6 +521,7 @@ describe('Jira service', () => {
axios,
logger,
method: 'post',
configurationUtilities,
url: 'https://siem-kibana.atlassian.net/rest/api/2/issue/1/comment',
data: { body: 'comment' },
});
@ -568,6 +580,7 @@ describe('Jira service', () => {
axios,
logger,
method: 'get',
configurationUtilities,
url: 'https://siem-kibana.atlassian.net/rest/capabilities',
});
});
@ -642,6 +655,7 @@ describe('Jira service', () => {
axios,
logger,
method: 'get',
configurationUtilities,
url:
'https://siem-kibana.atlassian.net/rest/api/2/issue/createmeta?projectKeys=CK&expand=projects.issuetypes.fields',
});
@ -724,6 +738,7 @@ describe('Jira service', () => {
axios,
logger,
method: 'get',
configurationUtilities,
url: 'https://siem-kibana.atlassian.net/rest/api/2/issue/createmeta/CK/issuetypes',
});
});
@ -807,6 +822,7 @@ describe('Jira service', () => {
axios,
logger,
method: 'get',
configurationUtilities,
url:
'https://siem-kibana.atlassian.net/rest/api/2/issue/createmeta?projectKeys=CK&issuetypeIds=10006&expand=projects.issuetypes.fields',
});
@ -928,6 +944,7 @@ describe('Jira service', () => {
axios,
logger,
method: 'get',
configurationUtilities,
url: 'https://siem-kibana.atlassian.net/rest/api/2/issue/createmeta/CK/issuetypes/10006',
});
});
@ -988,6 +1005,7 @@ describe('Jira service', () => {
axios,
logger,
method: 'get',
configurationUtilities,
url: `https://siem-kibana.atlassian.net/rest/api/2/search?jql=project%3D%22CK%22%20and%20summary%20~%22Test%20title%22`,
});
});
@ -1032,6 +1050,7 @@ describe('Jira service', () => {
axios,
logger,
method: 'get',
configurationUtilities,
url: `https://siem-kibana.atlassian.net/rest/api/2/issue/RJ-107`,
});
});

View file

@ -26,7 +26,7 @@ import {
import * as i18n from './translations';
import { request, getErrorMessage } from '../lib/axios_utils';
import { ProxySettings } from '../../types';
import { ActionsConfigurationUtilities } from '../../actions_config';
const VERSION = '2';
const BASE_URL = `rest/api/${VERSION}`;
@ -39,7 +39,7 @@ const createMetaCapabilities = ['list-project-issuetypes', 'list-issuetype-field
export const createExternalService = (
{ config, secrets }: ExternalServiceCredentials,
logger: Logger,
proxySettings?: ProxySettings
configurationUtilities: ActionsConfigurationUtilities
): ExternalService => {
const { apiUrl: url, projectKey } = config as JiraPublicConfigurationType;
const { apiToken, email } = secrets as JiraSecretConfigurationType;
@ -173,7 +173,7 @@ export const createExternalService = (
axios: axiosInstance,
url: `${incidentUrl}/${id}`,
logger,
proxySettings,
configurationUtilities,
});
const { fields, ...rest } = res.data;
@ -222,7 +222,7 @@ export const createExternalService = (
data: {
fields,
},
proxySettings,
configurationUtilities,
});
const updatedIncident = await getIncident(res.data.id);
@ -263,7 +263,7 @@ export const createExternalService = (
url: `${incidentUrl}/${incidentId}`,
logger,
data: { fields },
proxySettings,
configurationUtilities,
});
const updatedIncident = await getIncident(incidentId as string);
@ -297,7 +297,7 @@ export const createExternalService = (
url: getCommentsURL(incidentId),
logger,
data: { body: comment.comment },
proxySettings,
configurationUtilities,
});
return {
@ -324,7 +324,7 @@ export const createExternalService = (
method: 'get',
url: capabilitiesUrl,
logger,
proxySettings,
configurationUtilities,
});
return { ...res.data };
@ -350,7 +350,7 @@ export const createExternalService = (
method: 'get',
url: getIssueTypesOldAPIURL,
logger,
proxySettings,
configurationUtilities,
});
const issueTypes = res.data.projects[0]?.issuetypes ?? [];
@ -361,7 +361,7 @@ export const createExternalService = (
method: 'get',
url: getIssueTypesUrl,
logger,
proxySettings,
configurationUtilities,
});
const issueTypes = res.data.values;
@ -389,7 +389,7 @@ export const createExternalService = (
method: 'get',
url: createGetIssueTypeFieldsUrl(getIssueTypeFieldsOldAPIURL, issueTypeId),
logger,
proxySettings,
configurationUtilities,
});
const fields = res.data.projects[0]?.issuetypes[0]?.fields || {};
@ -400,7 +400,7 @@ export const createExternalService = (
method: 'get',
url: createGetIssueTypeFieldsUrl(getIssueTypeFieldsUrl, issueTypeId),
logger,
proxySettings,
configurationUtilities,
});
const fields = res.data.values.reduce(
@ -459,7 +459,7 @@ export const createExternalService = (
method: 'get',
url: query,
logger,
proxySettings,
configurationUtilities,
});
return normalizeSearchResults(res.data?.issues ?? []);
@ -483,7 +483,7 @@ export const createExternalService = (
method: 'get',
url: getIssueUrl,
logger,
proxySettings,
configurationUtilities,
});
return normalizeIssue(res.data ?? {});

View file

@ -5,12 +5,15 @@
*/
import axios from 'axios';
import { Agent as HttpsAgent } from 'https';
import { Logger } from '../../../../../../src/core/server';
import { addTimeZoneToDate, request, patch, getErrorMessage } from './axios_utils';
import { loggingSystemMock } from '../../../../../../src/core/server/mocks';
import { actionsConfigMock } from '../../actions_config.mock';
import { getProxyAgents } from './get_proxy_agents';
const logger = loggingSystemMock.create().get() as jest.Mocked<Logger>;
const configurationUtilities = actionsConfigMock.create();
jest.mock('axios');
const axiosMock = (axios as unknown) as jest.Mock;
@ -41,13 +44,14 @@ describe('request', () => {
axios,
url: '/test',
logger,
configurationUtilities,
});
expect(axiosMock).toHaveBeenCalledWith('/test', {
method: 'get',
data: {},
httpAgent: undefined,
httpsAgent: undefined,
httpsAgent: expect.any(HttpsAgent),
proxy: false,
});
expect(res).toEqual({
@ -58,20 +62,17 @@ describe('request', () => {
});
test('it have been called with proper proxy agent for a valid url', async () => {
const proxySettings = {
configurationUtilities.getProxySettings.mockReturnValue({
proxyRejectUnauthorizedCertificates: true,
proxyUrl: 'https://localhost:1212',
};
const { httpAgent, httpsAgent } = getProxyAgents(proxySettings, logger);
});
const { httpAgent, httpsAgent } = getProxyAgents(configurationUtilities, logger);
const res = await request({
axios,
url: 'http://testProxy',
logger,
proxySettings: {
proxyUrl: 'https://localhost:1212',
proxyRejectUnauthorizedCertificates: true,
},
configurationUtilities,
});
expect(axiosMock).toHaveBeenCalledWith('http://testProxy', {
@ -89,21 +90,22 @@ describe('request', () => {
});
test('it have been called with proper proxy agent for an invalid url', async () => {
configurationUtilities.getProxySettings.mockReturnValue({
proxyUrl: ':nope:',
proxyRejectUnauthorizedCertificates: false,
});
const res = await request({
axios,
url: 'https://testProxy',
logger,
proxySettings: {
proxyUrl: ':nope:',
proxyRejectUnauthorizedCertificates: false,
},
configurationUtilities,
});
expect(axiosMock).toHaveBeenCalledWith('https://testProxy', {
method: 'get',
data: {},
httpAgent: undefined,
httpsAgent: undefined,
httpsAgent: expect.any(HttpsAgent),
proxy: false,
});
expect(res).toEqual({
@ -114,13 +116,20 @@ describe('request', () => {
});
test('it fetch correctly', async () => {
const res = await request({ axios, url: '/test', method: 'post', logger, data: { id: '123' } });
const res = await request({
axios,
url: '/test',
method: 'post',
logger,
data: { id: '123' },
configurationUtilities,
});
expect(axiosMock).toHaveBeenCalledWith('/test', {
method: 'post',
data: { id: '123' },
httpAgent: undefined,
httpsAgent: undefined,
httpsAgent: expect.any(HttpsAgent),
proxy: false,
});
expect(res).toEqual({
@ -140,12 +149,12 @@ describe('patch', () => {
});
test('it fetch correctly', async () => {
await patch({ axios, url: '/test', data: { id: '123' }, logger });
await patch({ axios, url: '/test', data: { id: '123' }, logger, configurationUtilities });
expect(axiosMock).toHaveBeenCalledWith('/test', {
method: 'patch',
data: { id: '123' },
httpAgent: undefined,
httpsAgent: undefined,
httpsAgent: expect.any(HttpsAgent),
proxy: false,
});
});

View file

@ -6,8 +6,8 @@
import { AxiosInstance, Method, AxiosResponse, AxiosBasicCredentials } from 'axios';
import { Logger } from '../../../../../../src/core/server';
import { ProxySettings } from '../../types';
import { getProxyAgents } from './get_proxy_agents';
import { ActionsConfigurationUtilities } from '../../actions_config';
export const request = async <T = unknown>({
axios,
@ -15,7 +15,7 @@ export const request = async <T = unknown>({
logger,
method = 'get',
data,
proxySettings,
configurationUtilities,
...rest
}: {
axios: AxiosInstance;
@ -24,12 +24,12 @@ export const request = async <T = unknown>({
method?: Method;
data?: T;
params?: unknown;
proxySettings?: ProxySettings;
configurationUtilities: ActionsConfigurationUtilities;
headers?: Record<string, string> | null;
validateStatus?: (status: number) => boolean;
auth?: AxiosBasicCredentials;
}): Promise<AxiosResponse> => {
const { httpAgent, httpsAgent } = getProxyAgents(proxySettings, logger);
const { httpAgent, httpsAgent } = getProxyAgents(configurationUtilities, logger);
return await axios(url, {
...rest,
@ -47,13 +47,13 @@ export const patch = async <T = unknown>({
url,
data,
logger,
proxySettings,
configurationUtilities,
}: {
axios: AxiosInstance;
url: string;
data: T;
logger: Logger;
proxySettings?: ProxySettings;
configurationUtilities: ActionsConfigurationUtilities;
}): Promise<AxiosResponse> => {
return request({
axios,
@ -61,7 +61,7 @@ export const patch = async <T = unknown>({
logger,
method: 'patch',
data,
proxySettings,
configurationUtilities,
});
};

View file

@ -4,41 +4,41 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { Agent as HttpsAgent } from 'https';
import HttpProxyAgent from 'http-proxy-agent';
import { HttpsProxyAgent } from 'https-proxy-agent';
import { Logger } from '../../../../../../src/core/server';
import { getProxyAgents } from './get_proxy_agents';
import { loggingSystemMock } from '../../../../../../src/core/server/mocks';
import { actionsConfigMock } from '../../actions_config.mock';
const logger = loggingSystemMock.create().get() as jest.Mocked<Logger>;
describe('getProxyAgents', () => {
const configurationUtilities = actionsConfigMock.create();
test('get agents for valid proxy URL', () => {
const { httpAgent, httpsAgent } = getProxyAgents(
{ proxyUrl: 'https://someproxyhost', proxyRejectUnauthorizedCertificates: false },
logger
);
configurationUtilities.getProxySettings.mockReturnValue({
proxyUrl: 'https://someproxyhost',
proxyRejectUnauthorizedCertificates: false,
});
const { httpAgent, httpsAgent } = getProxyAgents(configurationUtilities, logger);
expect(httpAgent instanceof HttpProxyAgent).toBeTruthy();
expect(httpsAgent instanceof HttpsProxyAgent).toBeTruthy();
});
test('return undefined agents for invalid proxy URL', () => {
const { httpAgent, httpsAgent } = getProxyAgents(
{ proxyUrl: ':nope: not a valid URL', proxyRejectUnauthorizedCertificates: false },
logger
);
test('return default agents for invalid proxy URL', () => {
configurationUtilities.getProxySettings.mockReturnValue({
proxyUrl: ':nope: not a valid URL',
proxyRejectUnauthorizedCertificates: false,
});
const { httpAgent, httpsAgent } = getProxyAgents(configurationUtilities, logger);
expect(httpAgent).toBe(undefined);
expect(httpsAgent).toBe(undefined);
expect(httpsAgent instanceof HttpsAgent).toBeTruthy();
});
test('return undefined agents for null proxy options', () => {
const { httpAgent, httpsAgent } = getProxyAgents(null, logger);
test('return default agents for undefined proxy options', () => {
const { httpAgent, httpsAgent } = getProxyAgents(configurationUtilities, logger);
expect(httpAgent).toBe(undefined);
expect(httpsAgent).toBe(undefined);
});
test('return undefined agents for undefined proxy options', () => {
const { httpAgent, httpsAgent } = getProxyAgents(undefined, logger);
expect(httpAgent).toBe(undefined);
expect(httpsAgent).toBe(undefined);
expect(httpsAgent instanceof HttpsAgent).toBeTruthy();
});
});

View file

@ -4,28 +4,32 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { Agent } from 'http';
import { Agent as HttpAgent } from 'http';
import { Agent as HttpsAgent } from 'https';
import HttpProxyAgent from 'http-proxy-agent';
import { HttpsProxyAgent } from 'https-proxy-agent';
import { Logger } from '../../../../../../src/core/server';
import { ProxySettings } from '../../types';
import { ActionsConfigurationUtilities } from '../../actions_config';
interface GetProxyAgentsResponse {
httpAgent: Agent | undefined;
httpsAgent: Agent | undefined;
httpAgent: HttpAgent | undefined;
httpsAgent: HttpsAgent | undefined;
}
export function getProxyAgents(
proxySettings: ProxySettings | undefined | null,
configurationUtilities: ActionsConfigurationUtilities,
logger: Logger
): GetProxyAgentsResponse {
const undefinedResponse = {
const proxySettings = configurationUtilities.getProxySettings();
const defaultResponse = {
httpAgent: undefined,
httpsAgent: undefined,
httpsAgent: new HttpsAgent({
rejectUnauthorized: configurationUtilities.isRejectUnauthorizedCertificatesEnabled(),
}),
};
if (!proxySettings) {
return undefinedResponse;
return defaultResponse;
}
logger.debug(`Creating proxy agents for proxy: ${proxySettings.proxyUrl}`);
@ -34,7 +38,7 @@ export function getProxyAgents(
proxyUrl = new URL(proxySettings.proxyUrl);
} catch (err) {
logger.warn(`invalid proxy URL "${proxySettings.proxyUrl}" ignored`);
return undefinedResponse;
return defaultResponse;
}
const httpAgent = new HttpProxyAgent(proxySettings.proxyUrl);
@ -45,8 +49,8 @@ export function getProxyAgents(
headers: proxySettings.proxyHeaders,
// do not fail on invalid certs if value is false
rejectUnauthorized: proxySettings.proxyRejectUnauthorizedCertificates,
}) as unknown) as Agent;
// vsCode wasn't convinced HttpsProxyAgent is an http.Agent, so we convinced it
}) as unknown) as HttpsAgent;
// vsCode wasn't convinced HttpsProxyAgent is an https.Agent, so we convinced it
return { httpAgent, httpsAgent };
}

View file

@ -6,23 +6,24 @@
import axios, { AxiosResponse } from 'axios';
import { Logger } from '../../../../../../src/core/server';
import { Services, ProxySettings } from '../../types';
import { Services } from '../../types';
import { request } from './axios_utils';
import { ActionsConfigurationUtilities } from '../../actions_config';
interface PostPagerdutyOptions {
apiUrl: string;
data: unknown;
headers: Record<string, string>;
services: Services;
proxySettings?: ProxySettings;
}
// post an event to pagerduty
export async function postPagerduty(
options: PostPagerdutyOptions,
logger: Logger
logger: Logger,
configurationUtilities: ActionsConfigurationUtilities
): Promise<AxiosResponse> {
const { apiUrl, data, headers, proxySettings } = options;
const { apiUrl, data, headers } = options;
const axiosInstance = axios.create();
return await request({
@ -31,8 +32,8 @@ export async function postPagerduty(
method: 'post',
logger,
data,
proxySettings,
headers,
configurationUtilities,
validateStatus: () => true,
});
}

View file

@ -13,6 +13,7 @@ import { sendEmail } from './send_email';
import { loggingSystemMock } from '../../../../../../src/core/server/mocks';
import nodemailer from 'nodemailer';
import { ProxySettings } from '../../types';
import { actionsConfigMock } from '../../actions_config.mock';
const createTransportMock = nodemailer.createTransport as jest.Mock;
const sendMailMockResult = { result: 'does not matter' };
@ -136,7 +137,7 @@ describe('send_email module', () => {
"port": 1025,
"secure": false,
"tls": Object {
"rejectUnauthorized": undefined,
"rejectUnauthorized": true,
},
},
]
@ -223,6 +224,10 @@ function getSendEmailOptions(
{ content = {}, routing = {}, transport = {} } = {},
proxySettings?: ProxySettings
) {
const configurationUtilities = actionsConfigMock.create();
if (proxySettings) {
configurationUtilities.getProxySettings.mockReturnValue(proxySettings);
}
return {
content: {
...content,
@ -242,8 +247,8 @@ function getSendEmailOptions(
user: 'elastic',
password: 'changeme',
},
proxySettings,
hasAuth: true,
configurationUtilities,
};
}
@ -251,6 +256,10 @@ function getSendEmailOptionsNoAuth(
{ content = {}, routing = {}, transport = {} } = {},
proxySettings?: ProxySettings
) {
const configurationUtilities = actionsConfigMock.create();
if (proxySettings) {
configurationUtilities.getProxySettings.mockReturnValue(proxySettings);
}
return {
content: {
...content,
@ -267,7 +276,7 @@ function getSendEmailOptionsNoAuth(
transport: {
...transport,
},
proxySettings,
hasAuth: false,
configurationUtilities,
};
}

View file

@ -9,7 +9,7 @@ import nodemailer from 'nodemailer';
import { default as MarkdownIt } from 'markdown-it';
import { Logger } from '../../../../../../src/core/server';
import { ProxySettings } from '../../types';
import { ActionsConfigurationUtilities } from '../../actions_config';
// an email "service" which doesn't actually send, just returns what it would send
export const JSON_TRANSPORT_SERVICE = '__json';
@ -18,9 +18,8 @@ export interface SendEmailOptions {
transport: Transport;
routing: Routing;
content: Content;
proxySettings?: ProxySettings;
rejectUnauthorized?: boolean;
hasAuth: boolean;
configurationUtilities: ActionsConfigurationUtilities;
}
// config validation ensures either service is set or host/port are set
@ -47,12 +46,14 @@ export interface Content {
// send an email
export async function sendEmail(logger: Logger, options: SendEmailOptions): Promise<unknown> {
const { transport, routing, content, proxySettings, rejectUnauthorized, hasAuth } = options;
const { transport, routing, content, configurationUtilities, hasAuth } = options;
const { service, host, port, secure, user, password } = transport;
const { from, to, cc, bcc } = routing;
const { subject, message } = content;
const transportConfig: Record<string, unknown> = {};
const proxySettings = configurationUtilities.getProxySettings();
const rejectUnauthorized = configurationUtilities.isRejectUnauthorizedCertificatesEnabled();
if (hasAuth && user != null && password != null) {
transportConfig.auth = {

View file

@ -139,7 +139,7 @@ export function getActionType({
secrets: SecretsSchema,
params: ParamsSchema,
},
executor: curry(executor)({ logger }),
executor: curry(executor)({ logger, configurationUtilities }),
};
}
@ -166,7 +166,10 @@ function getPagerDutyApiUrl(config: ActionTypeConfigType): string {
// action executor
async function executor(
{ logger }: { logger: Logger },
{
logger,
configurationUtilities,
}: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities },
execOptions: PagerDutyActionTypeExecutorOptions
): Promise<ActionTypeExecutorResult<unknown>> {
const actionId = execOptions.actionId;
@ -174,7 +177,6 @@ async function executor(
const secrets = execOptions.secrets;
const params = execOptions.params;
const services = execOptions.services;
const proxySettings = execOptions.proxySettings;
const apiUrl = getPagerDutyApiUrl(config);
const headers = {
@ -185,7 +187,11 @@ async function executor(
let response;
try {
response = await postPagerduty({ apiUrl, data, headers, services, proxySettings }, logger);
response = await postPagerduty(
{ apiUrl, data, headers, services },
logger,
configurationUtilities
);
} catch (err) {
const message = i18n.translate('xpack.actions.builtin.pagerduty.postingErrorMessage', {
defaultMessage: 'error posting pagerduty event',

View file

@ -63,13 +63,16 @@ export function getActionType(
}),
params: ExecutorParamsSchema,
},
executor: curry(executor)({ logger }),
executor: curry(executor)({ logger, configurationUtilities }),
};
}
// action executor
async function executor(
{ logger }: { logger: Logger },
{
logger,
configurationUtilities,
}: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities },
execOptions: ActionTypeExecutorOptions<
ResilientPublicConfigurationType,
ResilientSecretConfigurationType,
@ -86,7 +89,7 @@ async function executor(
secrets,
},
logger,
execOptions.proxySettings
configurationUtilities
);
if (!api[subAction]) {

View file

@ -12,6 +12,7 @@ import { ExternalService } from './types';
import { Logger } from '../../../../../../src/core/server';
import { loggingSystemMock } from '../../../../../../src/core/server/mocks';
import { incidentTypes, resilientFields, severity } from './mocks';
import { actionsConfigMock } from '../../actions_config.mock';
const logger = loggingSystemMock.create().get() as jest.Mocked<Logger>;
@ -28,6 +29,7 @@ axios.create = jest.fn(() => axios);
const requestMock = utils.request as jest.Mock;
const now = Date.now;
const TIMESTAMP = 1589391874472;
const configurationUtilities = actionsConfigMock.create();
// Incident update makes three calls to the API.
// The function below mocks this calls.
@ -86,7 +88,8 @@ describe('IBM Resilient service', () => {
config: { apiUrl: 'https://resilient.elastic.co/', orgId: '201' },
secrets: { apiKeyId: 'keyId', apiKeySecret: 'secret' },
},
logger
logger,
configurationUtilities
);
});
@ -155,7 +158,8 @@ describe('IBM Resilient service', () => {
config: { apiUrl: null, orgId: '201' },
secrets: { apiKeyId: 'token', apiKeySecret: 'secret' },
},
logger
logger,
configurationUtilities
)
).toThrow();
});
@ -167,7 +171,8 @@ describe('IBM Resilient service', () => {
config: { apiUrl: 'test.com', orgId: null },
secrets: { apiKeyId: 'token', apiKeySecret: 'secret' },
},
logger
logger,
configurationUtilities
)
).toThrow();
});
@ -179,7 +184,8 @@ describe('IBM Resilient service', () => {
config: { apiUrl: 'test.com', orgId: '201' },
secrets: { apiKeyId: '', apiKeySecret: 'secret' },
},
logger
logger,
configurationUtilities
)
).toThrow();
});
@ -191,7 +197,8 @@ describe('IBM Resilient service', () => {
config: { apiUrl: 'test.com', orgId: '201' },
secrets: { apiKeyId: '', apiKeySecret: undefined },
},
logger
logger,
configurationUtilities
)
).toThrow();
});
@ -226,6 +233,7 @@ describe('IBM Resilient service', () => {
params: {
text_content_output_format: 'objects_convert',
},
configurationUtilities,
});
});
@ -294,6 +302,7 @@ describe('IBM Resilient service', () => {
'https://resilient.elastic.co/rest/orgs/201/incidents?text_content_output_format=objects_convert',
logger,
method: 'post',
configurationUtilities,
data: {
name: 'title',
description: {
@ -367,6 +376,7 @@ describe('IBM Resilient service', () => {
axios,
logger,
method: 'patch',
configurationUtilities,
url: 'https://resilient.elastic.co/rest/orgs/201/incidents/1',
data: {
changes: [
@ -480,7 +490,7 @@ describe('IBM Resilient service', () => {
axios,
logger,
method: 'post',
proxySettings: undefined,
configurationUtilities,
url: 'https://resilient.elastic.co/rest/orgs/201/incidents/1/comments',
data: {
text: {
@ -584,6 +594,7 @@ describe('IBM Resilient service', () => {
expect(requestMock).toHaveBeenCalledWith({
axios,
logger,
configurationUtilities,
url: 'https://resilient.elastic.co/rest/orgs/201/types/incident/fields',
});
});

View file

@ -24,7 +24,7 @@ import {
import * as i18n from './translations';
import { getErrorMessage, request } from '../lib/axios_utils';
import { ProxySettings } from '../../types';
import { ActionsConfigurationUtilities } from '../../actions_config';
const VIEW_INCIDENT_URL = `#incidents`;
@ -93,7 +93,7 @@ export const formatUpdateRequest = ({
export const createExternalService = (
{ config, secrets }: ExternalServiceCredentials,
logger: Logger,
proxySettings?: ProxySettings
configurationUtilities: ActionsConfigurationUtilities
): ExternalService => {
const { apiUrl: url, orgId } = config as ResilientPublicConfigurationType;
const { apiKeyId, apiKeySecret } = secrets as ResilientSecretConfigurationType;
@ -130,7 +130,7 @@ export const createExternalService = (
params: {
text_content_output_format: 'objects_convert',
},
proxySettings,
configurationUtilities,
});
return { ...res.data, description: res.data.description?.content ?? '' };
@ -178,7 +178,7 @@ export const createExternalService = (
method: 'post',
logger,
data,
proxySettings,
configurationUtilities,
});
return {
@ -208,7 +208,7 @@ export const createExternalService = (
url: `${incidentUrl}/${incidentId}`,
logger,
data,
proxySettings,
configurationUtilities,
});
if (!res.data.success) {
@ -241,7 +241,7 @@ export const createExternalService = (
url: getCommentsURL(incidentId),
logger,
data: { text: { format: 'text', content: comment.comment } },
proxySettings,
configurationUtilities,
});
return {
@ -266,7 +266,7 @@ export const createExternalService = (
method: 'get',
url: incidentTypesUrl,
logger,
proxySettings,
configurationUtilities,
});
const incidentTypes = res.data?.values ?? [];
@ -288,7 +288,7 @@ export const createExternalService = (
method: 'get',
url: severityUrl,
logger,
proxySettings,
configurationUtilities,
});
const incidentTypes = res.data?.values ?? [];
@ -309,7 +309,7 @@ export const createExternalService = (
axios: axiosInstance,
url: incidentFieldsUrl,
logger,
proxySettings,
configurationUtilities,
});
return res.data ?? [];
} catch (error) {

View file

@ -60,14 +60,17 @@ export function getActionType(
}),
params: ExecutorParamsSchema,
},
executor: curry(executor)({ logger }),
executor: curry(executor)({ logger, configurationUtilities }),
};
}
// action executor
const supportedSubActions: string[] = ['getFields', 'pushToService'];
async function executor(
{ logger }: { logger: Logger },
{
logger,
configurationUtilities,
}: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities },
execOptions: ActionTypeExecutorOptions<
ServiceNowPublicConfigurationType,
ServiceNowSecretConfigurationType,
@ -84,7 +87,7 @@ async function executor(
secrets,
},
logger,
execOptions.proxySettings
configurationUtilities
);
if (!api[subAction]) {

View file

@ -11,6 +11,7 @@ import * as utils from '../lib/axios_utils';
import { ExternalService } from './types';
import { Logger } from '../../../../../../src/core/server';
import { loggingSystemMock } from '../../../../../../src/core/server/mocks';
import { actionsConfigMock } from '../../actions_config.mock';
import { serviceNowCommonFields } from './mocks';
const logger = loggingSystemMock.create().get() as jest.Mocked<Logger>;
@ -27,6 +28,7 @@ jest.mock('../lib/axios_utils', () => {
axios.create = jest.fn(() => axios);
const requestMock = utils.request as jest.Mock;
const patchMock = utils.patch as jest.Mock;
const configurationUtilities = actionsConfigMock.create();
describe('ServiceNow service', () => {
let service: ExternalService;
@ -39,7 +41,8 @@ describe('ServiceNow service', () => {
config: { apiUrl: 'https://dev102283.service-now.com/' },
secrets: { username: 'admin', password: 'admin' },
},
logger
logger,
configurationUtilities
);
});
@ -55,7 +58,8 @@ describe('ServiceNow service', () => {
config: { apiUrl: null },
secrets: { username: 'admin', password: 'admin' },
},
logger
logger,
configurationUtilities
)
).toThrow();
});
@ -67,7 +71,8 @@ describe('ServiceNow service', () => {
config: { apiUrl: 'test.com' },
secrets: { username: '', password: 'admin' },
},
logger
logger,
configurationUtilities
)
).toThrow();
});
@ -79,7 +84,8 @@ describe('ServiceNow service', () => {
config: { apiUrl: 'test.com' },
secrets: { username: '', password: undefined },
},
logger
logger,
configurationUtilities
)
).toThrow();
});
@ -103,6 +109,7 @@ describe('ServiceNow service', () => {
expect(requestMock).toHaveBeenCalledWith({
axios,
logger,
configurationUtilities,
url: 'https://dev102283.service-now.com/api/now/v2/table/incident/1',
});
});
@ -147,6 +154,7 @@ describe('ServiceNow service', () => {
expect(requestMock).toHaveBeenCalledWith({
axios,
logger,
configurationUtilities,
url: 'https://dev102283.service-now.com/api/now/v2/table/incident',
method: 'post',
data: { short_description: 'title', description: 'desc' },
@ -200,6 +208,7 @@ describe('ServiceNow service', () => {
expect(patchMock).toHaveBeenCalledWith({
axios,
logger,
configurationUtilities,
url: 'https://dev102283.service-now.com/api/now/v2/table/incident/1',
data: { short_description: 'title', description: 'desc' },
});
@ -248,6 +257,7 @@ describe('ServiceNow service', () => {
expect(requestMock).toHaveBeenCalledWith({
axios,
logger,
configurationUtilities,
url:
'https://dev102283.service-now.com/api/now/v2/table/sys_dictionary?sysparm_query=name=task^internal_type=string&active=true&array=false&read_only=false&sysparm_fields=max_length,element,column_label,mandatory',
});

View file

@ -12,7 +12,7 @@ import * as i18n from './translations';
import { Logger } from '../../../../../../src/core/server';
import { ServiceNowPublicConfigurationType, ServiceNowSecretConfigurationType } from './types';
import { request, getErrorMessage, addTimeZoneToDate, patch } from '../lib/axios_utils';
import { ProxySettings } from '../../types';
import { ActionsConfigurationUtilities } from '../../actions_config';
const API_VERSION = 'v2';
const INCIDENT_URL = `api/now/${API_VERSION}/table/incident`;
@ -24,7 +24,7 @@ const VIEW_INCIDENT_URL = `nav_to.do?uri=incident.do?sys_id=`;
export const createExternalService = (
{ config, secrets }: ExternalServiceCredentials,
logger: Logger,
proxySettings?: ProxySettings
configurationUtilities: ActionsConfigurationUtilities
): ExternalService => {
const { apiUrl: url } = config as ServiceNowPublicConfigurationType;
const { username, password } = secrets as ServiceNowSecretConfigurationType;
@ -58,7 +58,7 @@ export const createExternalService = (
axios: axiosInstance,
url: `${incidentUrl}/${id}`,
logger,
proxySettings,
configurationUtilities,
});
checkInstance(res);
return { ...res.data.result };
@ -75,8 +75,8 @@ export const createExternalService = (
axios: axiosInstance,
url: incidentUrl,
logger,
proxySettings,
params,
configurationUtilities,
});
checkInstance(res);
return res.data.result.length > 0 ? { ...res.data.result } : undefined;
@ -93,9 +93,9 @@ export const createExternalService = (
axios: axiosInstance,
url: `${incidentUrl}`,
logger,
proxySettings,
method: 'post',
data: { ...(incident as Record<string, unknown>) },
configurationUtilities,
});
checkInstance(res);
return {
@ -118,7 +118,7 @@ export const createExternalService = (
url: `${incidentUrl}/${incidentId}`,
logger,
data: { ...(incident as Record<string, unknown>) },
proxySettings,
configurationUtilities,
});
checkInstance(res);
return {
@ -143,7 +143,7 @@ export const createExternalService = (
axios: axiosInstance,
url: fieldsUrl,
logger,
proxySettings,
configurationUtilities,
});
checkInstance(res);
return res.data.result.length > 0 ? res.data.result : [];

View file

@ -165,10 +165,6 @@ describe('execute()', () => {
config: {},
secrets: { webhookUrl: 'http://example.com' },
params: { message: 'this invocation should succeed' },
proxySettings: {
proxyUrl: 'https://someproxyhost',
proxyRejectUnauthorizedCertificates: false,
},
});
expect(response).toMatchInlineSnapshot(`
Object {
@ -194,9 +190,14 @@ describe('execute()', () => {
});
test('calls the mock executor with success proxy', async () => {
const configurationUtilities = actionsConfigMock.create();
configurationUtilities.getProxySettings.mockReturnValue({
proxyUrl: 'https://someproxyhost',
proxyRejectUnauthorizedCertificates: false,
});
const actionTypeProxy = getActionType({
logger: mockedLogger,
configurationUtilities: actionsConfigMock.create(),
configurationUtilities,
});
await actionTypeProxy.executor({
actionId: 'some-id',
@ -204,10 +205,6 @@ describe('execute()', () => {
config: {},
secrets: { webhookUrl: 'http://example.com' },
params: { message: 'this invocation should succeed' },
proxySettings: {
proxyUrl: 'https://someproxyhost',
proxyRejectUnauthorizedCertificates: false,
},
});
expect(mockedLogger.debug).toHaveBeenCalledWith(
'IncomingWebhook was called with proxyUrl https://someproxyhost'

View file

@ -6,7 +6,6 @@
import { URL } from 'url';
import { curry } from 'lodash';
import { Agent } from 'http';
import { i18n } from '@kbn/i18n';
import { schema, TypeOf } from '@kbn/config-schema';
import { IncomingWebhook, IncomingWebhookResult } from '@slack/webhook';
@ -56,7 +55,7 @@ export const ActionTypeId = '.slack';
export function getActionType({
logger,
configurationUtilities,
executor = curry(slackExecutor)({ logger }),
executor = curry(slackExecutor)({ logger, configurationUtilities }),
}: {
logger: Logger;
configurationUtilities: ActionsConfigurationUtilities;
@ -116,7 +115,10 @@ function validateActionTypeConfig(
// action executor
async function slackExecutor(
{ logger }: { logger: Logger },
{
logger,
configurationUtilities,
}: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities },
execOptions: SlackActionTypeExecutorOptions
): Promise<ActionTypeExecutorResult<unknown>> {
const actionId = execOptions.actionId;
@ -126,15 +128,15 @@ async function slackExecutor(
let result: IncomingWebhookResult;
const { webhookUrl } = secrets;
const { message } = params;
const proxySettings = configurationUtilities.getProxySettings();
let httpProxyAgent: Agent | undefined;
if (execOptions.proxySettings) {
const httpProxyAgents = getProxyAgents(execOptions.proxySettings, logger);
httpProxyAgent = webhookUrl.toLowerCase().startsWith('https')
? httpProxyAgents.httpsAgent
: httpProxyAgents.httpAgent;
const proxyAgents = getProxyAgents(configurationUtilities, logger);
const httpProxyAgent = webhookUrl.toLowerCase().startsWith('https')
? proxyAgents.httpsAgent
: proxyAgents.httpAgent;
logger.debug(`IncomingWebhook was called with proxyUrl ${execOptions.proxySettings.proxyUrl}`);
if (proxySettings) {
logger.debug(`IncomingWebhook was called with proxyUrl ${proxySettings.proxyUrl}`);
}
try {

View file

@ -160,38 +160,47 @@ describe('execute()', () => {
params: { message: 'this invocation should succeed' },
});
expect(requestMock.mock.calls[0][0]).toMatchInlineSnapshot(`
Object {
"axios": undefined,
"data": Object {
"text": "this invocation should succeed",
},
"logger": Object {
"context": Array [],
"debug": [MockFunction] {
"calls": Array [
Array [
"response from teams action \\"some-id\\": [HTTP 200] ",
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
Object {
"axios": undefined,
"configurationUtilities": Object {
"ensureActionTypeEnabled": [MockFunction],
"ensureHostnameAllowed": [MockFunction],
"ensureUriAllowed": [MockFunction],
"getProxySettings": [MockFunction],
"isActionTypeEnabled": [MockFunction],
"isHostnameAllowed": [MockFunction],
"isRejectUnauthorizedCertificatesEnabled": [MockFunction],
"isUriAllowed": [MockFunction],
},
"data": Object {
"text": "this invocation should succeed",
},
"logger": Object {
"context": Array [],
"debug": [MockFunction] {
"calls": Array [
Array [
"response from teams action \\"some-id\\": [HTTP 200] ",
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
"error": [MockFunction],
"fatal": [MockFunction],
"get": [MockFunction],
"info": [MockFunction],
"log": [MockFunction],
"trace": [MockFunction],
"warn": [MockFunction],
},
"method": "post",
"proxySettings": undefined,
"url": "http://example.com",
}
],
},
"error": [MockFunction],
"fatal": [MockFunction],
"get": [MockFunction],
"info": [MockFunction],
"log": [MockFunction],
"trace": [MockFunction],
"warn": [MockFunction],
},
"method": "post",
"url": "http://example.com",
}
`);
expect(response).toMatchInlineSnapshot(`
Object {
@ -211,47 +220,49 @@ describe('execute()', () => {
config: {},
secrets: { webhookUrl: 'http://example.com' },
params: { message: 'this invocation should succeed' },
proxySettings: {
proxyUrl: 'https://someproxyhost',
proxyRejectUnauthorizedCertificates: false,
},
});
expect(requestMock.mock.calls[0][0]).toMatchInlineSnapshot(`
Object {
"axios": undefined,
"data": Object {
"text": "this invocation should succeed",
},
"logger": Object {
"context": Array [],
"debug": [MockFunction] {
"calls": Array [
Array [
"response from teams action \\"some-id\\": [HTTP 200] ",
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
Object {
"axios": undefined,
"configurationUtilities": Object {
"ensureActionTypeEnabled": [MockFunction],
"ensureHostnameAllowed": [MockFunction],
"ensureUriAllowed": [MockFunction],
"getProxySettings": [MockFunction],
"isActionTypeEnabled": [MockFunction],
"isHostnameAllowed": [MockFunction],
"isRejectUnauthorizedCertificatesEnabled": [MockFunction],
"isUriAllowed": [MockFunction],
},
"data": Object {
"text": "this invocation should succeed",
},
"logger": Object {
"context": Array [],
"debug": [MockFunction] {
"calls": Array [
Array [
"response from teams action \\"some-id\\": [HTTP 200] ",
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
"error": [MockFunction],
"fatal": [MockFunction],
"get": [MockFunction],
"info": [MockFunction],
"log": [MockFunction],
"trace": [MockFunction],
"warn": [MockFunction],
},
"method": "post",
"proxySettings": Object {
"proxyRejectUnauthorizedCertificates": false,
"proxyUrl": "https://someproxyhost",
},
"url": "http://example.com",
}
],
},
"error": [MockFunction],
"fatal": [MockFunction],
"get": [MockFunction],
"info": [MockFunction],
"log": [MockFunction],
"trace": [MockFunction],
"warn": [MockFunction],
},
"method": "post",
"url": "http://example.com",
}
`);
expect(response).toMatchInlineSnapshot(`
Object {

View file

@ -63,7 +63,7 @@ export function getActionType({
}),
params: ParamsSchema,
},
executor: curry(teamsExecutor)({ logger }),
executor: curry(teamsExecutor)({ logger, configurationUtilities }),
};
}
@ -95,7 +95,10 @@ function validateActionTypeConfig(
// action executor
async function teamsExecutor(
{ logger }: { logger: Logger },
{
logger,
configurationUtilities,
}: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities },
execOptions: TeamsActionTypeExecutorOptions
): Promise<ActionTypeExecutorResult<unknown>> {
const actionId = execOptions.actionId;
@ -114,7 +117,7 @@ async function teamsExecutor(
url: webhookUrl,
logger,
data,
proxySettings: execOptions.proxySettings,
configurationUtilities,
})
);

View file

@ -279,43 +279,52 @@ describe('execute()', () => {
});
expect(requestMock.mock.calls[0][0]).toMatchInlineSnapshot(`
Object {
"auth": Object {
"password": "123",
"username": "abc",
},
"axios": undefined,
"data": "some data",
"headers": Object {
"aheader": "a value",
},
"logger": Object {
"context": Array [],
"debug": [MockFunction] {
"calls": Array [
Array [
"response from webhook action \\"some-id\\": [HTTP 200] ",
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
Object {
"auth": Object {
"password": "123",
"username": "abc",
},
"axios": undefined,
"configurationUtilities": Object {
"ensureActionTypeEnabled": [MockFunction],
"ensureHostnameAllowed": [MockFunction],
"ensureUriAllowed": [MockFunction],
"getProxySettings": [MockFunction],
"isActionTypeEnabled": [MockFunction],
"isHostnameAllowed": [MockFunction],
"isRejectUnauthorizedCertificatesEnabled": [MockFunction],
"isUriAllowed": [MockFunction],
},
"data": "some data",
"headers": Object {
"aheader": "a value",
},
"logger": Object {
"context": Array [],
"debug": [MockFunction] {
"calls": Array [
Array [
"response from webhook action \\"some-id\\": [HTTP 200] ",
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
"error": [MockFunction],
"fatal": [MockFunction],
"get": [MockFunction],
"info": [MockFunction],
"log": [MockFunction],
"trace": [MockFunction],
"warn": [MockFunction],
},
"method": "post",
"proxySettings": undefined,
"url": "https://abc.def/my-webhook",
}
],
},
"error": [MockFunction],
"fatal": [MockFunction],
"get": [MockFunction],
"info": [MockFunction],
"log": [MockFunction],
"trace": [MockFunction],
"warn": [MockFunction],
},
"method": "post",
"url": "https://abc.def/my-webhook",
}
`);
});
@ -338,39 +347,48 @@ describe('execute()', () => {
});
expect(requestMock.mock.calls[0][0]).toMatchInlineSnapshot(`
Object {
"axios": undefined,
"data": "some data",
"headers": Object {
"aheader": "a value",
},
"logger": Object {
"context": Array [],
"debug": [MockFunction] {
"calls": Array [
Array [
"response from webhook action \\"some-id\\": [HTTP 200] ",
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
Object {
"axios": undefined,
"configurationUtilities": Object {
"ensureActionTypeEnabled": [MockFunction],
"ensureHostnameAllowed": [MockFunction],
"ensureUriAllowed": [MockFunction],
"getProxySettings": [MockFunction],
"isActionTypeEnabled": [MockFunction],
"isHostnameAllowed": [MockFunction],
"isRejectUnauthorizedCertificatesEnabled": [MockFunction],
"isUriAllowed": [MockFunction],
},
"data": "some data",
"headers": Object {
"aheader": "a value",
},
"logger": Object {
"context": Array [],
"debug": [MockFunction] {
"calls": Array [
Array [
"response from webhook action \\"some-id\\": [HTTP 200] ",
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
"error": [MockFunction],
"fatal": [MockFunction],
"get": [MockFunction],
"info": [MockFunction],
"log": [MockFunction],
"trace": [MockFunction],
"warn": [MockFunction],
},
"method": "post",
"proxySettings": undefined,
"url": "https://abc.def/my-webhook",
}
],
},
"error": [MockFunction],
"fatal": [MockFunction],
"get": [MockFunction],
"info": [MockFunction],
"log": [MockFunction],
"trace": [MockFunction],
"warn": [MockFunction],
},
"method": "post",
"url": "https://abc.def/my-webhook",
}
`);
});

View file

@ -94,7 +94,7 @@ export function getActionType({
params: ParamsSchema,
},
renderParameterTemplates,
executor: curry(executor)({ logger }),
executor: curry(executor)({ logger, configurationUtilities }),
};
}
@ -138,7 +138,10 @@ function validateActionTypeConfig(
// action executor
export async function executor(
{ logger }: { logger: Logger },
{
logger,
configurationUtilities,
}: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities },
execOptions: WebhookActionTypeExecutorOptions
): Promise<ActionTypeExecutorResult<unknown>> {
const actionId = execOptions.actionId;
@ -162,7 +165,7 @@ export async function executor(
...basicAuth,
headers,
data,
proxySettings: execOptions.proxySettings,
configurationUtilities,
})
);
@ -202,7 +205,7 @@ export async function executor(
);
}
return errorResultInvalid(actionId, message);
} else if (error.isAxiosError) {
} else if (error.code) {
const message = `[${error.code}] ${error.message}`;
logger.error(`error on ${actionId} webhook event: ${message}`);
return errorResultRequestFailed(actionId, message);

View file

@ -6,10 +6,9 @@
import type { PublicMethodsOf } from '@kbn/utility-types';
import { PluginInitializerContext, PluginConfigDescriptor } from '../../../../src/core/server';
import { ActionsPlugin } from './plugin';
import { configSchema } from './config';
import { configSchema, ActionsConfig } from './config';
import { ActionsClient as ActionsClientClass } from './actions_client';
import { ActionsAuthorization as ActionsAuthorizationClass } from './authorization/actions_authorization';
import { ActionsConfigType } from './types';
export type ActionsClient = PublicMethodsOf<ActionsClientClass>;
export type ActionsAuthorization = PublicMethodsOf<ActionsAuthorizationClass>;
@ -52,7 +51,7 @@ export { asSavedObjectExecutionSource, asHttpRequestExecutionSource } from './li
export const plugin = (initContext: PluginInitializerContext) => new ActionsPlugin(initContext);
export const config: PluginConfigDescriptor<ActionsConfigType> = {
export const config: PluginConfigDescriptor<ActionsConfig> = {
schema: configSchema,
deprecations: ({ renameFromRoot }) => [
renameFromRoot('xpack.actions.whitelistedHosts', 'xpack.actions.allowedHosts'),

View file

@ -12,7 +12,6 @@ import {
GetServicesFunction,
RawAction,
PreConfiguredAction,
ProxySettings,
} from '../types';
import { EncryptedSavedObjectsClient } from '../../../encrypted_saved_objects/server';
import { SpacesServiceStart } from '../../../spaces/server';
@ -33,7 +32,6 @@ export interface ActionExecutorContext {
actionTypeRegistry: ActionTypeRegistryContract;
eventLogger: IEventLogger;
preconfiguredActions: PreConfiguredAction[];
proxySettings?: ProxySettings;
}
export interface ExecuteOptions<Source = unknown> {
@ -87,7 +85,6 @@ export class ActionExecutor {
eventLogger,
preconfiguredActions,
getActionsClientWithRequest,
proxySettings,
} = this.actionExecutorContext!;
const services = getServices(request);
@ -145,7 +142,6 @@ export class ActionExecutor {
params: validatedParams,
config: validatedConfig,
secrets: validatedSecrets,
proxySettings,
});
} catch (err) {
rawResult = {

View file

@ -357,15 +357,6 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
encryptedSavedObjectsClient,
actionTypeRegistry: actionTypeRegistry!,
preconfiguredActions,
proxySettings:
this.actionsConfig && this.actionsConfig.proxyUrl
? {
proxyUrl: this.actionsConfig.proxyUrl,
proxyHeaders: this.actionsConfig.proxyHeaders,
proxyRejectUnauthorizedCertificates: this.actionsConfig
.proxyRejectUnauthorizedCertificates,
}
: undefined,
});
const spaceIdToNamespace = (spaceId?: string) => {

View file

@ -55,12 +55,6 @@ export interface ActionsPlugin {
start: PluginStartContract;
}
export interface ActionsConfigType {
enabled: boolean;
allowedHosts: string[];
enabledActionTypes: string[];
}
// the parameters passed to an action type executor function
export interface ActionTypeExecutorOptions<Config, Secrets, Params> {
actionId: string;
@ -68,7 +62,6 @@ export interface ActionTypeExecutorOptions<Config, Secrets, Params> {
config: Config;
secrets: Secrets;
params: Params;
proxySettings?: ProxySettings;
}
export interface ActionResult<Config extends ActionTypeConfig = ActionTypeConfig> {

View file

@ -17,6 +17,7 @@ interface CreateTestConfigOptions {
disabledPlugins?: string[];
ssl?: boolean;
enableActionsProxy: boolean;
rejectUnauthorized?: boolean;
}
// test.not-enabled is specifically not enabled
@ -39,7 +40,12 @@ const enabledActionTypes = [
];
export function createTestConfig(name: string, options: CreateTestConfigOptions) {
const { license = 'trial', disabledPlugins = [], ssl = false } = options;
const {
license = 'trial',
disabledPlugins = [],
ssl = false,
rejectUnauthorized = true,
} = options;
return async ({ readConfigFile }: FtrConfigProviderContext) => {
const xPackApiIntegrationTestsConfig = await readConfigFile(
@ -95,6 +101,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
'--xpack.encryptedSavedObjects.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"',
'--xpack.alerts.invalidateApiKeysTask.interval="15s"',
`--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`,
`--xpack.actions.rejectUnauthorized=${rejectUnauthorized}`,
...actionsProxyUrl,
'--xpack.eventLog.logEntries=true',

View file

@ -5,6 +5,7 @@
*/
import http from 'http';
import https from 'https';
import { Plugin, CoreSetup, IRouter } from 'kibana/server';
import { EncryptedSavedObjectsPluginStart } from '../../../../../../../plugins/encrypted_saved_objects/server';
import { PluginSetupContract as FeaturesPluginSetup } from '../../../../../../../plugins/features/server';
@ -47,7 +48,13 @@ export function getAllExternalServiceSimulatorPaths(): string[] {
}
export async function getWebhookServer(): Promise<http.Server> {
return await initWebhook();
const { httpServer } = await initWebhook();
return httpServer;
}
export async function getHttpsWebhookServer(): Promise<https.Server> {
const { httpsServer } = await initWebhook();
return httpsServer;
}
export async function getSlackServer(): Promise<http.Server> {

View file

@ -3,16 +3,35 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import fs from 'fs';
import expect from '@kbn/expect';
import http from 'http';
import https from 'https';
import { promisify } from 'util';
import { fromNullable, map, filter, getOrElse } from 'fp-ts/lib/Option';
import { pipe } from 'fp-ts/lib/pipeable';
import { constant } from 'fp-ts/lib/function';
import { KBN_KEY_PATH, KBN_CERT_PATH } from '@kbn/dev-utils';
export async function initPlugin() {
const payloads: string[] = [];
const httpsServerKey = await promisify(fs.readFile)(KBN_KEY_PATH, 'utf8');
const httpsServerCert = await promisify(fs.readFile)(KBN_CERT_PATH, 'utf8');
return http.createServer((request, response) => {
return {
httpServer: http.createServer(createServerCallback()),
httpsServer: https.createServer(
{
key: httpsServerKey,
cert: httpsServerCert,
},
createServerCallback()
),
};
}
function createServerCallback() {
const payloads: string[] = [];
return (request: http.IncomingMessage, response: http.ServerResponse) => {
const credentials = pipe(
fromNullable(request.headers.authorization),
map((authorization) => authorization.split(/\s+/)),
@ -77,7 +96,7 @@ export async function initPlugin() {
return;
});
}
});
};
}
function validateAuthentication(credentials: any, res: any) {

View file

@ -11,4 +11,5 @@ export default createTestConfig('spaces_only', {
disabledPlugins: ['security'],
license: 'trial',
enableActionsProxy: false,
rejectUnauthorized: false,
});

View file

@ -5,11 +5,15 @@
*/
import http from 'http';
import https from 'https';
import getPort from 'get-port';
import expect from '@kbn/expect';
import { URL, format as formatUrl } from 'url';
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import { getWebhookServer } from '../../../../common/fixtures/plugins/actions_simulators/server/plugin';
import {
getWebhookServer,
getHttpsWebhookServer,
} from '../../../../common/fixtures/plugins/actions_simulators/server/plugin';
// eslint-disable-next-line import/no-default-export
export default function webhookTest({ getService }: FtrProviderContext) {
@ -43,32 +47,65 @@ export default function webhookTest({ getService }: FtrProviderContext) {
}
describe('webhook action', () => {
let webhookSimulatorURL: string = '';
let webhookServer: http.Server;
before(async () => {
webhookServer = await getWebhookServer();
const availablePort = await getPort({ port: 9000 });
webhookServer.listen(availablePort);
webhookSimulatorURL = `http://localhost:${availablePort}`;
describe('with http endpoint', () => {
let webhookSimulatorURL: string = '';
let webhookServer: http.Server;
before(async () => {
webhookServer = await getWebhookServer();
const availablePort = await getPort({ port: 9000 });
webhookServer.listen(availablePort);
webhookSimulatorURL = `http://localhost:${availablePort}`;
});
it('webhook can be executed without username and password', async () => {
const webhookActionId = await createWebhookAction(webhookSimulatorURL);
const { body: result } = await supertest
.post(`/api/actions/action/${webhookActionId}/_execute`)
.set('kbn-xsrf', 'test')
.send({
params: {
body: 'success',
},
})
.expect(200);
expect(result.status).to.eql('ok');
});
after(() => {
webhookServer.close();
});
});
it('webhook can be executed without username and password', async () => {
const webhookActionId = await createWebhookAction(webhookSimulatorURL);
const { body: result } = await supertest
.post(`/api/actions/action/${webhookActionId}/_execute`)
.set('kbn-xsrf', 'test')
.send({
params: {
body: 'success',
},
})
.expect(200);
describe('with https endpoint and rejectUnauthorized=false', () => {
let webhookSimulatorURL: string = '';
let webhookServer: https.Server;
expect(result.status).to.eql('ok');
});
before(async () => {
webhookServer = await getHttpsWebhookServer();
const availablePort = await getPort({ port: getPort.makeRange(9000, 9100) });
webhookServer.listen(availablePort);
webhookSimulatorURL = `https://localhost:${availablePort}`;
});
after(() => {
webhookServer.close();
it('should support the POST method against webhook target', async () => {
const webhookActionId = await createWebhookAction(webhookSimulatorURL, { method: 'post' });
const { body: result } = await supertest
.post(`/api/actions/action/${webhookActionId}/_execute`)
.set('kbn-xsrf', 'test')
.send({
params: {
body: 'success_post_method',
},
})
.expect(200);
expect(result.status).to.eql('ok');
});
after(() => {
webhookServer.close();
});
});
});
}