Skip to content

Commit 9aca126

Browse files
shaharglcursoragent
authored andcommitted
[Actions] Fix HTTP connector TLS options through proxies (elastic#269898)
## Summary - For HTTPS requests through an HTTP proxy, forward target TLS options to the CONNECT-upgraded request created by `HttpsProxyAgent`. - Ensures connector `verificationMode: none` and per-request SSL overrides like `fetcher.skip_ssl_verification` are honored when the proxy performs TLS inspection. - Adds a regression test covering target SSL overrides through the proxy agent callback. ## Test plan - `node scripts/jest src/platform/packages/shared/kbn-actions-utils/utils/get_custom_agents.test.ts` - `node scripts/check_changes.ts` - Manually reproduced with local mitmproxy before the fix and verified `_execute` succeeds after the fix. ## References Closes elastic/security-team#17454 Closes elastic#196602 Made with [Cursor](https://cursor.com) Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 8e4388b commit 9aca126

2 files changed

Lines changed: 78 additions & 12 deletions

File tree

src/platform/packages/shared/kbn-actions-utils/utils/get_custom_agents.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,50 @@ describe('getCustomAgents', () => {
5252
expect(httpsAgent instanceof HttpsProxyAgent).toBeTruthy();
5353
});
5454

55+
test('passes target SSL overrides to the CONNECT-upgraded TLS request', async () => {
56+
const callbackSpy = jest
57+
.spyOn(HttpsProxyAgent.prototype, 'callback')
58+
.mockResolvedValue({} as Awaited<ReturnType<HttpsProxyAgent['callback']>>);
59+
const proxySettings = {
60+
proxyUrl: 'http://someproxyhost',
61+
proxySSLSettings: {
62+
verificationMode: 'full',
63+
},
64+
proxyBypassHosts: undefined,
65+
proxyOnlyHosts: undefined,
66+
} as ProxySettings;
67+
68+
const { httpsAgent } = getCustomAgents({
69+
logger,
70+
proxySettings,
71+
sslOverrides: {
72+
verificationMode: 'none',
73+
},
74+
sslSettings: defaultSSLSettings,
75+
url: targetUrl,
76+
});
77+
78+
try {
79+
await (httpsAgent as unknown as HttpsProxyAgent).callback(
80+
{} as Parameters<HttpsProxyAgent['callback']>[0],
81+
{
82+
host: targetHost,
83+
port: 443,
84+
secureEndpoint: true,
85+
} as Parameters<HttpsProxyAgent['callback']>[1]
86+
);
87+
88+
expect(callbackSpy).toHaveBeenCalledWith(
89+
expect.anything(),
90+
expect.objectContaining({
91+
rejectUnauthorized: false,
92+
})
93+
);
94+
} finally {
95+
callbackSpy.mockRestore();
96+
}
97+
});
98+
5599
test('return default agents for invalid proxy URL', () => {
56100
const proxySettings = {
57101
proxyUrl: ':nope: not a valid URL',

src/platform/packages/shared/kbn-actions-utils/utils/get_custom_agents.ts

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,24 @@ interface GetCustomAgentsOpts {
3333
url: string;
3434
}
3535

36+
class TargetSslHttpsProxyAgent extends HttpsProxyAgent {
37+
constructor(
38+
proxyOptions: ConstructorParameters<typeof HttpsProxyAgent>[0],
39+
private readonly targetSSLOptions: AgentOptions
40+
) {
41+
super(proxyOptions);
42+
}
43+
44+
callback(
45+
req: Parameters<HttpsProxyAgent['callback']>[0],
46+
opts: Parameters<HttpsProxyAgent['callback']>[1]
47+
) {
48+
// HttpsProxyAgent constructor options configure the proxy connection. Target TLS
49+
// options must be merged into the CONNECT-upgraded request options instead.
50+
return super.callback(req, { ...opts, ...this.targetSSLOptions });
51+
}
52+
}
53+
3654
export function getCustomAgents(opts: GetCustomAgentsOpts): GetCustomAgentsResponse {
3755
const {
3856
customHostSettings,
@@ -137,22 +155,26 @@ export function getCustomAgents(opts: GetCustomAgentsOpts): GetCustomAgentsRespo
137155
// We will though, copy over the calculated ssl options from above, into
138156
// the https agent.
139157
const httpAgent = new HttpProxyAgent(proxySettings.proxyUrl) as unknown as HttpAgent;
140-
const httpsAgent = new HttpsProxyAgent({
141-
host: proxyUrl.hostname,
142-
port: Number(proxyUrl.port),
143-
protocol: proxyUrl.protocol,
144-
headers: proxySettings.proxyHeaders,
145-
...(proxyUrl.username &&
146-
proxyUrl.password && { auth: `${proxyUrl.username}:${proxyUrl.password}` }),
147-
// do not fail on invalid certs if value is false
148-
...proxyNodeSSLOptions,
149-
}) as unknown as HttpsAgent;
158+
const targetSSLOptions = agentOptions ?? agentSSLOptions;
159+
const httpsAgent = new TargetSslHttpsProxyAgent(
160+
{
161+
host: proxyUrl.hostname,
162+
port: Number(proxyUrl.port),
163+
protocol: proxyUrl.protocol,
164+
headers: proxySettings.proxyHeaders,
165+
...(proxyUrl.username &&
166+
proxyUrl.password && { auth: `${proxyUrl.username}:${proxyUrl.password}` }),
167+
// do not fail on invalid certs if value is false
168+
...proxyNodeSSLOptions,
169+
},
170+
targetSSLOptions
171+
) as unknown as HttpsAgent;
150172
// vsCode wasn't convinced HttpsProxyAgent is an https.Agent, so we convinced it
151173

152-
if (agentOptions) {
174+
if (targetSSLOptions) {
153175
httpsAgent.options = {
154176
...httpsAgent.options,
155-
...agentOptions,
177+
...targetSSLOptions,
156178
};
157179
}
158180

0 commit comments

Comments
 (0)