From e44efed3072d71be1122c7b895a11bc8ecf8d12f Mon Sep 17 00:00:00 2001 From: Sergi Massaneda Date: Fri, 7 Mar 2025 18:43:24 +0100 Subject: [PATCH] [Security Solution][Connectors] Torq connector allow EU hooks hostname (#212563) ## Summary From: https://github.com/elastic/kibana/issues/212511 Add support for EU domains --------- Co-authored-by: Elastic Machine (cherry picked from commit 723a33b7de7b776f39ae816c42eaf2abd07734ba) # Conflicts: # x-pack/plugins/stack_connectors/server/connector_types/torq/index.ts --- .../stack_connectors/common/torq/index.ts | 12 ++++++ .../torq/torq_connectors.test.tsx | 37 ++++++++++++++++++- .../connector_types/torq/torq_connectors.tsx | 3 +- .../server/connector_types/torq/index.test.ts | 16 +++++++- .../server/connector_types/torq/index.ts | 5 ++- 5 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 x-pack/plugins/stack_connectors/common/torq/index.ts diff --git a/x-pack/plugins/stack_connectors/common/torq/index.ts b/x-pack/plugins/stack_connectors/common/torq/index.ts new file mode 100644 index 0000000000000..76c6dcd71db14 --- /dev/null +++ b/x-pack/plugins/stack_connectors/common/torq/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +const hostNameRegExp = /^hooks\.(eu\.)?torq\.io$/; + +export const isValidTorqHostName = (hostName: string) => { + return hostNameRegExp.test(hostName); +}; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/torq/torq_connectors.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/torq/torq_connectors.test.tsx index db11c00e03589..3929e0a0432b4 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/torq/torq_connectors.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/torq/torq_connectors.test.tsx @@ -93,6 +93,41 @@ describe('TorqActionConnectorFields renders', () => { }); }); + it('connector validation succeeds when using a EU torq webhook URL', async () => { + const connector = { + ...actionConnector, + config: { webhookIntegrationUrl: 'https://hooks.eu.torq.io/v1/webhooks/fjdksla' }, + }; + const { getByTestId } = render( + + + + ); + + await act(async () => { + await userEvent.click(getByTestId('form-test-provide-submit')); + }); + + expect(onSubmit).toBeCalledWith({ + data: { + actionTypeId: '.torq', + name: 'torq', + config: { + webhookIntegrationUrl: 'https://hooks.eu.torq.io/v1/webhooks/fjdksla', + }, + secrets: { + token: 'testtoken', + }, + isDeprecated: false, + }, + isValid: true, + }); + }); + it('connector validation fails when there is no token', async () => { const connector = { ...actionConnector, @@ -153,7 +188,7 @@ describe('TorqActionConnectorFields renders', () => { const connector = { ...actionConnector, config: { - webhookIntegrationUrl: 'https://test.com', + webhookIntegrationUrl: 'https://hooks.not-torq.io/v1/webhooks/fjdksla', }, }; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/torq/torq_connectors.tsx b/x-pack/plugins/stack_connectors/public/connector_types/torq/torq_connectors.tsx index e6edceb68b9a8..235a54d771246 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/torq/torq_connectors.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/torq/torq_connectors.tsx @@ -17,6 +17,7 @@ import { import { isUrl } from '@kbn/es-ui-shared-plugin/static/validators/string'; import { ActionConnectorFieldsProps } from '@kbn/triggers-actions-ui-plugin/public'; import React from 'react'; +import { isValidTorqHostName } from '../../../common/torq'; import * as i18n from './translations'; const { urlField, emptyField } = fieldValidators; @@ -42,7 +43,7 @@ const torqWebhookEndpoint = }; if (!isUrl(value)) return error; const hostname = new URL(value).hostname; - return hostname === 'hooks.torq.io' ? undefined : error; + return isValidTorqHostName(hostname) ? undefined : error; }; const TorqActionConnectorFields: React.FunctionComponent = ({ diff --git a/x-pack/plugins/stack_connectors/server/connector_types/torq/index.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/torq/index.test.ts index 39bbdd44a918d..9fad686414150 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/torq/index.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/torq/index.test.ts @@ -85,6 +85,15 @@ describe('config validation', () => { ...config, }); }); + test('config validation passes with the EU endpoint', () => { + const config: Record = { + webhookIntegrationUrl: 'https://hooks.eu.torq.io/v1/test', + }; + expect(validateConfig(actionType, config, { configurationUtilities })).toEqual({ + ...defaultValues, + ...config, + }); + }); const errorCases: Array<{ name: string; url: string; errorMsg: string }> = [ { @@ -100,7 +109,12 @@ describe('config validation', () => { { name: 'fails when URL is not a Torq webhook endpoint', url: 'http://mylisteningserver:9200/endpoint', - errorMsg: `"error validating action type config: error configuring send to Torq action: url must begin with https://hooks.torq.io"`, + errorMsg: `"error validating action type config: error configuring send to Torq action: url must begin with https://hooks.torq.io or https://hooks.eu.torq.io"`, + }, + { + name: 'fails when URL is an unsupported Torq webhook subdomain', + url: 'https://hooks.anothersubdomain.torq.io/v1/test', + errorMsg: `"error validating action type config: error configuring send to Torq action: url must begin with https://hooks.torq.io or https://hooks.eu.torq.io"`, }, ]; errorCases.forEach(({ name, url, errorMsg }) => { diff --git a/x-pack/plugins/stack_connectors/server/connector_types/torq/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/torq/index.ts index b60237dd7991e..5488be1805b6a 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/torq/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/torq/index.ts @@ -22,6 +22,7 @@ import { import { renderMustacheObject } from '@kbn/actions-plugin/server/lib/mustache_renderer'; import { request } from '@kbn/actions-plugin/server/lib/axios_utils'; import { ValidatorServices } from '@kbn/actions-plugin/server/types'; +import { isValidTorqHostName } from '../../../common/torq'; import { getRetryAfterIntervalFromHeaders } from '../lib/http_response_retry_header'; import { promiseResult, isOk, Result } from '../lib/result_type'; @@ -128,11 +129,11 @@ function validateActionTypeConfig( ); } - if (configureUrlObj.hostname !== 'hooks.torq.io' && configureUrlObj.hostname !== 'localhost') { + if (!isValidTorqHostName(configureUrlObj.hostname) && configureUrlObj.hostname !== 'localhost') { throw new Error( i18n.translate('xpack.stackConnectors.torq.torqConfigurationErrorInvalidHostname', { defaultMessage: - 'error configuring send to Torq action: url must begin with https://hooks.torq.io', + 'error configuring send to Torq action: url must begin with https://hooks.torq.io or https://hooks.eu.torq.io', }) ); }