From ab991ed140e1e701a35cc6b255827df66e0335b9 Mon Sep 17 00:00:00 2001 From: Garrett Spong Date: Tue, 17 Jun 2025 16:11:43 -0600 Subject: [PATCH] [9.0] [Connectors] Fix Bedrock connector not using the action proxy configuration (#224130) (#224326) # Backport This will backport the following commits from `main` to `9.0`: - [[Connectors] Fix Bedrock connector not using the action proxy configuration (#224130)](https://github.com/elastic/kibana/pull/224130) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) --- package.json | 1 + renovate.json | 1 + .../integration_tests/connector_types.test.ts | 4 + .../bedrock/bedrock.proxy.test.ts | 95 +++++++++++++++++++ .../server/connector_types/bedrock/bedrock.ts | 9 ++ yarn.lock | 54 +++++------ 6 files changed, 137 insertions(+), 27 deletions(-) create mode 100644 x-pack/platform/plugins/shared/stack_connectors/server/connector_types/bedrock/bedrock.proxy.test.ts diff --git a/package.json b/package.json index c88792099f59..25090c49eeb8 100644 --- a/package.json +++ b/package.json @@ -1060,6 +1060,7 @@ "@smithy/eventstream-codec": "^4.0.1", "@smithy/eventstream-serde-node": "^4.0.1", "@smithy/middleware-stack": "^4.0.1", + "@smithy/node-http-handler": "^4.0.1", "@smithy/types": "^4.1.0", "@smithy/util-utf8": "^4.0.0", "@tanstack/react-query": "^4.29.12", diff --git a/renovate.json b/renovate.json index 3d3c98737184..26a296d86c5e 100644 --- a/renovate.json +++ b/renovate.json @@ -158,6 +158,7 @@ "@langtrase/trace-attributes", "@opentelemetry/sdk-trace-base", "@smithy/eventstream-serde-node", + "@smithy/node-http-handler", "@smithy/types", "@types/aws4", "ajv", diff --git a/x-pack/platform/plugins/shared/actions/server/integration_tests/connector_types.test.ts b/x-pack/platform/plugins/shared/actions/server/integration_tests/connector_types.test.ts index 2faf62627c42..26c0c09b6f8e 100644 --- a/x-pack/platform/plugins/shared/actions/server/integration_tests/connector_types.test.ts +++ b/x-pack/platform/plugins/shared/actions/server/integration_tests/connector_types.test.ts @@ -74,6 +74,10 @@ describe('Connector type config checks', () => { oAuthScope: 'some-scope', apiUrl: 'https://_face_api_.com', }; + } else if (connectorTypeId === '.bedrock') { + connectorConfig = { + apiUrl: 'https://_face_api_.com', + }; } const subActions = getService({ diff --git a/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/bedrock/bedrock.proxy.test.ts b/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/bedrock/bedrock.proxy.test.ts new file mode 100644 index 000000000000..e8b578c79878 --- /dev/null +++ b/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/bedrock/bedrock.proxy.test.ts @@ -0,0 +1,95 @@ +/* + * 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. + */ + +import { DEFAULT_BEDROCK_URL } from '../../../common/bedrock/constants'; +import { actionsMock } from '@kbn/actions-plugin/server/mocks'; +import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; +import { BedrockConnector } from './bedrock'; +import { loggingSystemMock } from '@kbn/core/server/mocks'; + +const logger = loggingSystemMock.createLogger(); + +describe('Bedrock with proxy config', () => { + const configurationUtilities = actionsConfigMock.create(); + const PROXY_HOST = 'proxy.custom.elastic.co'; + const PROXY_URL_HTTP = `http://${PROXY_HOST}:99`; + const PROXY_URL_HTTPS = `https://${PROXY_HOST}:99`; + + let connector: BedrockConnector; + beforeEach(() => { + jest.clearAllMocks(); + configurationUtilities.getProxySettings.mockReturnValue({ + proxyUrl: PROXY_URL_HTTP, + proxySSLSettings: { + verificationMode: 'none', + }, + proxyBypassHosts: undefined, + proxyOnlyHosts: undefined, + }); + + connector = new BedrockConnector({ + configurationUtilities, + connector: { id: '1', type: '.bedrock' }, + config: { + apiUrl: DEFAULT_BEDROCK_URL, + defaultModel: 'claude', + }, + secrets: { accessKey: '123', secret: '567' }, + logger, + services: actionsMock.createServices(), + }); + }); + + it('verifies that the Bedrock client is initialized with the custom proxy HTTP agent', async () => { + // @ts-ignore .bedrockClient is private + const bedrockClient = connector.bedrockClient; + + // Verify the client was initialized with the custom agent configuration + expect(bedrockClient).toBeDefined(); + expect(bedrockClient.config.requestHandler).toBeDefined(); + // @ts-ignore configProvider is private, but we need it to access the agent + const config = await bedrockClient.config.requestHandler.configProvider; + // Since DEFAULT_BEDROCK_URL is https, httpsAgent will be set, see: https://github.com/elastic/kibana/pull/224130#discussion_r2152632806 + expect(config.httpsAgent.proxy.host).toBe(PROXY_HOST); + expect(config.httpsAgent.proxy.port).toBe(99); + expect(config.httpAgent.proxy).toBeUndefined(); + }); + + it('verifies that the Bedrock client is initialized with the custom proxy HTTPS agent', async () => { + configurationUtilities.getProxySettings.mockReturnValue({ + proxyUrl: PROXY_URL_HTTPS, + proxySSLSettings: { + verificationMode: 'none', + }, + proxyBypassHosts: undefined, + proxyOnlyHosts: undefined, + }); + + connector = new BedrockConnector({ + configurationUtilities, + connector: { id: '1', type: '.bedrock' }, + config: { + apiUrl: DEFAULT_BEDROCK_URL, + defaultModel: 'claude', + }, + secrets: { accessKey: '123', secret: '567' }, + logger, + services: actionsMock.createServices(), + }); + // @ts-ignore .bedrockClient is private + const bedrockClient = connector.bedrockClient; + + // Verify the client was initialized with the custom agent configuration + expect(bedrockClient).toBeDefined(); + expect(bedrockClient.config.requestHandler).toBeDefined(); + // @ts-ignore configProvider is private, but we need it to access the agent + const config = await bedrockClient.config.requestHandler.configProvider; + expect(config.httpsAgent.proxy.host).toBe(PROXY_HOST); + expect(config.httpsAgent.proxy.port).toBe(99); + expect(config.httpAgent.proxy).not.toBeDefined(); + }); +}); diff --git a/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/bedrock/bedrock.ts b/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/bedrock/bedrock.ts index 84b5c07088f9..d547f601a39e 100644 --- a/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/bedrock/bedrock.ts +++ b/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/bedrock/bedrock.ts @@ -9,9 +9,11 @@ import { ServiceParams, SubActionConnector } from '@kbn/actions-plugin/server'; import aws from 'aws4'; import { BedrockRuntimeClient } from '@aws-sdk/client-bedrock-runtime'; import type { SmithyMessageDecoderStream } from '@smithy/eventstream-codec'; +import { NodeHttpHandler } from '@smithy/node-http-handler'; import type { AxiosError, Method } from 'axios'; import type { IncomingMessage } from 'http'; import { PassThrough } from 'stream'; +import { getCustomAgents } from '@kbn/actions-plugin/server/lib/get_custom_agents'; import { SubActionRequestParams } from '@kbn/actions-plugin/server/sub_action_framework/types'; import { ConnectorUsageCollector } from '@kbn/actions-plugin/server/types'; import { initDashboard } from '../lib/gen_ai/create_gen_ai_dashboard'; @@ -73,12 +75,19 @@ export class BedrockConnector extends SubActionConnector { this.url = this.config.apiUrl; this.model = this.config.defaultModel; + const { httpAgent, httpsAgent } = getCustomAgents( + this.configurationUtilities, + this.logger, + this.url + ); + const isHttps = this.url.toLowerCase().startsWith('https'); this.bedrockClient = new BedrockRuntimeClient({ region: extractRegionId(this.config.apiUrl), credentials: { accessKeyId: this.secrets.accessKey, secretAccessKey: this.secrets.secret, }, + requestHandler: new NodeHttpHandler(isHttps ? { httpsAgent } : { httpAgent }), }); this.registerSubActions(); } diff --git a/yarn.lock b/yarn.lock index 25d42d340e67..5a0c5f6175c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9416,12 +9416,12 @@ "@types/node" ">=18.0.0" axios "^1.6.0" -"@smithy/abort-controller@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@smithy/abort-controller/-/abort-controller-4.0.1.tgz#7c5e73690c4105ad264c2896bd1ea822450c3819" - integrity sha512-fiUIYgIgRjMWznk6iLJz35K2YxSLHzLBA/RC6lBrKfQ8fHbPfvk7Pk9UvpKoHgJjI18MnbPuEju53zcVy6KF1g== +"@smithy/abort-controller@^4.0.4": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@smithy/abort-controller/-/abort-controller-4.0.4.tgz#ab991d521fc78b5c7f24907fcd6803c0f2da51d9" + integrity sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA== dependencies: - "@smithy/types" "^4.1.0" + "@smithy/types" "^4.3.1" tslib "^2.6.2" "@smithy/config-resolver@^4.0.1": @@ -9612,15 +9612,15 @@ "@smithy/types" "^4.1.0" tslib "^2.6.2" -"@smithy/node-http-handler@^4.0.2": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@smithy/node-http-handler/-/node-http-handler-4.0.2.tgz#48d47a046cf900ab86bfbe7f5fd078b52c82fab6" - integrity sha512-X66H9aah9hisLLSnGuzRYba6vckuFtGE+a5DcHLliI/YlqKrGoxhisD5XbX44KyoeRzoNlGr94eTsMVHFAzPOw== +"@smithy/node-http-handler@^4.0.1", "@smithy/node-http-handler@^4.0.2": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz#a022da499ba3af4b6b4c815104fde973c0eccc40" + integrity sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA== dependencies: - "@smithy/abort-controller" "^4.0.1" - "@smithy/protocol-http" "^5.0.1" - "@smithy/querystring-builder" "^4.0.1" - "@smithy/types" "^4.1.0" + "@smithy/abort-controller" "^4.0.4" + "@smithy/protocol-http" "^5.1.2" + "@smithy/querystring-builder" "^4.0.4" + "@smithy/types" "^4.3.1" tslib "^2.6.2" "@smithy/property-provider@^4.0.1": @@ -9631,20 +9631,20 @@ "@smithy/types" "^4.1.0" tslib "^2.6.2" -"@smithy/protocol-http@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@smithy/protocol-http/-/protocol-http-5.0.1.tgz#37c248117b29c057a9adfad4eb1d822a67079ff1" - integrity sha512-TE4cpj49jJNB/oHyh/cRVEgNZaoPaxd4vteJNB0yGidOCVR0jCw/hjPVsT8Q8FRmj8Bd3bFZt8Dh7xGCT+xMBQ== +"@smithy/protocol-http@^5.0.1", "@smithy/protocol-http@^5.1.2": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@smithy/protocol-http/-/protocol-http-5.1.2.tgz#8094860c2407f250b80c95899e0385112d6eb98b" + integrity sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ== dependencies: - "@smithy/types" "^4.1.0" + "@smithy/types" "^4.3.1" tslib "^2.6.2" -"@smithy/querystring-builder@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@smithy/querystring-builder/-/querystring-builder-4.0.1.tgz#37e1e05d0d33c6f694088abc3e04eafb65cb6976" - integrity sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg== +"@smithy/querystring-builder@^4.0.1", "@smithy/querystring-builder@^4.0.4": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz#f7546efd59d457b3d2525a330c6137e5f907864c" + integrity sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w== dependencies: - "@smithy/types" "^4.1.0" + "@smithy/types" "^4.3.1" "@smithy/util-uri-escape" "^4.0.0" tslib "^2.6.2" @@ -9698,10 +9698,10 @@ "@smithy/util-stream" "^4.0.2" tslib "^2.6.2" -"@smithy/types@^4.1.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@smithy/types/-/types-4.1.0.tgz#19de0b6087bccdd4182a334eb5d3d2629699370f" - integrity sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw== +"@smithy/types@^4.1.0", "@smithy/types@^4.3.1": + version "4.3.1" + resolved "https://registry.yarnpkg.com/@smithy/types/-/types-4.3.1.tgz#c11276ea16235d798f47a68aef9f44d3dbb70dd4" + integrity sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA== dependencies: tslib "^2.6.2"