mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 05:29:44 +02:00
fix(core): Propagate proxy environment to task runner processes
This commit is contained in:
parent
c40ac64ea2
commit
05dfb59448
|
|
@ -126,6 +126,9 @@ class TaskRunner:
|
|||
self.websocket_url,
|
||||
additional_headers=headers,
|
||||
max_size=self.config.max_payload_size,
|
||||
# The broker connection must stay direct, never routed
|
||||
# through a proxy configured via environment variables.
|
||||
proxy=None,
|
||||
)
|
||||
self.logger.info("Connected to broker")
|
||||
await self._listen_for_messages()
|
||||
|
|
|
|||
|
|
@ -94,6 +94,25 @@ class TestTaskRunnerConnectionRetry:
|
|||
|
||||
assert mock_connect.call_count == 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_broker_connection_is_never_proxied(self, config):
|
||||
runner = TaskRunner(config)
|
||||
|
||||
def connection_side_effect(*args, **kwargs):
|
||||
runner.is_shutting_down = True
|
||||
raise ConnectionRefusedError("Connection refused")
|
||||
|
||||
with (
|
||||
patch("src.task_runner.websockets.connect") as mock_connect,
|
||||
patch.object(runner, "logger"),
|
||||
patch("src.task_runner.asyncio.sleep"),
|
||||
):
|
||||
mock_connect.side_effect = connection_side_effect
|
||||
|
||||
await runner.start()
|
||||
|
||||
assert mock_connect.call_args.kwargs["proxy"] is None
|
||||
|
||||
|
||||
class TestTaskRunnerDrain:
|
||||
@pytest.fixture
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@n8n/backend-network": "workspace:*",
|
||||
"@n8n/config": "workspace:*",
|
||||
"@n8n/di": "workspace:*",
|
||||
"@n8n/errors": "workspace:*",
|
||||
|
|
|
|||
46
packages/@n8n/task-runner/src/__tests__/start.test.ts
Normal file
46
packages/@n8n/task-runner/src/__tests__/start.test.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { Container } from '@n8n/di';
|
||||
import http from 'node:http';
|
||||
import https from 'node:https';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
|
||||
import { TaskRunnerSentry } from '../task-runner-sentry';
|
||||
|
||||
vi.mock('../js-task-runner/js-task-runner', () => ({
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
JsTaskRunner: class {
|
||||
on() {}
|
||||
|
||||
async stop() {}
|
||||
},
|
||||
}));
|
||||
|
||||
describe('start', () => {
|
||||
it('should install env-proxy global agents and exempt the broker host when proxy env vars are set', async () => {
|
||||
const previousHttpAgent = http.globalAgent;
|
||||
const previousHttpsAgent = https.globalAgent;
|
||||
const previousNoProxy = process.env.NO_PROXY;
|
||||
process.env.HTTPS_PROXY = 'http://proxy.host.invalid:3128';
|
||||
// The NODE_PATH shim in start.ts relies on a CJS-only internal API.
|
||||
vi.stubEnv('NODE_PATH', undefined);
|
||||
Container.set(TaskRunnerSentry, mock<TaskRunnerSentry>());
|
||||
|
||||
try {
|
||||
await import('../start.js');
|
||||
|
||||
expect(http.globalAgent.constructor.name).toBe('EnvProxyHttpAgent');
|
||||
expect(https.globalAgent.constructor.name).toBe('EnvProxyHttpsAgent');
|
||||
expect(process.env.NO_PROXY?.split(',')).toContain('127.0.0.1');
|
||||
} finally {
|
||||
delete process.env.HTTPS_PROXY;
|
||||
if (previousNoProxy === undefined) {
|
||||
delete process.env.NO_PROXY;
|
||||
delete process.env.no_proxy;
|
||||
} else {
|
||||
process.env.NO_PROXY = previousNoProxy;
|
||||
}
|
||||
vi.unstubAllEnvs();
|
||||
http.globalAgent = previousHttpAgent;
|
||||
https.globalAgent = previousHttpsAgent;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
import { installGlobalProxyAgent } from '@n8n/backend-network';
|
||||
import { ensureHostsBypassProxy } from '@n8n/backend-network/proxy';
|
||||
import { Container } from '@n8n/di';
|
||||
import { ensureError } from '@n8n/utils/errors/ensure-error';
|
||||
import { setGlobalState } from 'n8n-workflow';
|
||||
|
|
@ -70,6 +72,12 @@ function createSignalHandler(
|
|||
void (async function start() {
|
||||
const config = Container.get(MainConfig);
|
||||
|
||||
// Env-proxy global agents must be installed before any outbound traffic
|
||||
// (Sentry, user task code). The task broker connection must stay direct,
|
||||
// so its host is exempted from proxying for the process lifetime.
|
||||
ensureHostsBypassProxy([new URL(config.baseRunnerConfig.taskBrokerUri).hostname]);
|
||||
installGlobalProxyAgent();
|
||||
|
||||
setGlobalState({
|
||||
defaultTimezone: config.baseRunnerConfig.timezone,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -50,6 +50,14 @@ describe('PyTaskRunnerProcess', () => {
|
|||
'N8N_RUNNERS_ALLOW_TRANSITIVE_IMPORTS',
|
||||
'N8N_RUNNERS_BUILTINS_DENY',
|
||||
'N8N_BLOCK_RUNNER_ENV_ACCESS',
|
||||
'HTTP_PROXY',
|
||||
'http_proxy',
|
||||
'HTTPS_PROXY',
|
||||
'https_proxy',
|
||||
'ALL_PROXY',
|
||||
'all_proxy',
|
||||
'NO_PROXY',
|
||||
'no_proxy',
|
||||
])('should propagate %s from env as is', async (envVar) => {
|
||||
process.env[envVar] = 'custom value';
|
||||
|
||||
|
|
|
|||
|
|
@ -114,6 +114,14 @@ describe('TaskRunnerProcess', () => {
|
|||
'NODE_PATH',
|
||||
'GENERIC_TIMEZONE',
|
||||
'N8N_RUNNERS_INSECURE_MODE',
|
||||
'HTTP_PROXY',
|
||||
'http_proxy',
|
||||
'HTTPS_PROXY',
|
||||
'https_proxy',
|
||||
'ALL_PROXY',
|
||||
'all_proxy',
|
||||
'NO_PROXY',
|
||||
'no_proxy',
|
||||
])('should propagate %s from env as is', async (envVar) => {
|
||||
authService.createGrantToken.mockResolvedValue('grantToken');
|
||||
process.env[envVar] = 'custom value';
|
||||
|
|
|
|||
|
|
@ -62,6 +62,16 @@ export class JsTaskRunnerProcess extends TaskRunnerProcessBase {
|
|||
NODE_FUNCTION_ALLOW_BUILTIN: process.env.NODE_FUNCTION_ALLOW_BUILTIN,
|
||||
NODE_FUNCTION_ALLOW_EXTERNAL: process.env.NODE_FUNCTION_ALLOW_EXTERNAL,
|
||||
|
||||
// proxy
|
||||
HTTP_PROXY: process.env.HTTP_PROXY,
|
||||
http_proxy: process.env.http_proxy,
|
||||
HTTPS_PROXY: process.env.HTTPS_PROXY,
|
||||
https_proxy: process.env.https_proxy,
|
||||
ALL_PROXY: process.env.ALL_PROXY,
|
||||
all_proxy: process.env.all_proxy,
|
||||
NO_PROXY: process.env.NO_PROXY,
|
||||
no_proxy: process.env.no_proxy,
|
||||
|
||||
// sentry
|
||||
N8N_SENTRY_DSN: process.env.N8N_SENTRY_DSN,
|
||||
N8N_VERSION: process.env.N8N_VERSION,
|
||||
|
|
|
|||
|
|
@ -49,6 +49,16 @@ export class PyTaskRunnerProcess extends TaskRunnerProcessBase {
|
|||
N8N_RUNNERS_TASK_TIMEOUT: this.runnerConfig.taskTimeout.toString(),
|
||||
N8N_RUNNERS_HEARTBEAT_INTERVAL: this.runnerConfig.heartbeatInterval.toString(),
|
||||
|
||||
// proxy
|
||||
HTTP_PROXY: process.env.HTTP_PROXY,
|
||||
http_proxy: process.env.http_proxy,
|
||||
HTTPS_PROXY: process.env.HTTPS_PROXY,
|
||||
https_proxy: process.env.https_proxy,
|
||||
ALL_PROXY: process.env.ALL_PROXY,
|
||||
all_proxy: process.env.all_proxy,
|
||||
NO_PROXY: process.env.NO_PROXY,
|
||||
no_proxy: process.env.no_proxy,
|
||||
|
||||
// n8n
|
||||
N8N_RUNNERS_STDLIB_ALLOW: process.env.N8N_RUNNERS_STDLIB_ALLOW,
|
||||
N8N_RUNNERS_EXTERNAL_ALLOW: process.env.N8N_RUNNERS_EXTERNAL_ALLOW,
|
||||
|
|
|
|||
|
|
@ -1128,7 +1128,7 @@ importers:
|
|||
version: 1.0.27(@aws-sdk/credential-provider-node@3.972.80)(@langchain/core@1.2.8(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.221.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.10.0(@opentelemetry/api@1.9.1))(openai@6.46.0(@aws-sdk/credential-provider-node@3.972.80)(@smithy/signature-v4@5.7.2)(ws@8.21.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.1(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.221.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.10.0(@opentelemetry/api@1.9.1))(@smithy/signature-v4@5.7.2)(cheerio@1.1.0)(openai@6.46.0(@aws-sdk/credential-provider-node@3.972.80)(@smithy/signature-v4@5.7.2)(ws@8.21.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))
|
||||
'@langchain/community':
|
||||
specifier: 'catalog:'
|
||||
version: 1.1.27(6cc31e45a82b311205f2bf99fec67d91)
|
||||
version: 1.1.27(2af036a866615d639e1d6409476fa500)
|
||||
'@langchain/core':
|
||||
specifier: 'catalog:'
|
||||
version: 1.2.8(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.221.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.10.0(@opentelemetry/api@1.9.1))(openai@6.46.0(@aws-sdk/credential-provider-node@3.972.80)(@smithy/signature-v4@5.7.2)(ws@8.21.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))
|
||||
|
|
@ -3718,6 +3718,9 @@ importers:
|
|||
|
||||
packages/@n8n/task-runner:
|
||||
dependencies:
|
||||
'@n8n/backend-network':
|
||||
specifier: workspace:*
|
||||
version: link:../backend-network
|
||||
'@n8n/config':
|
||||
specifier: workspace:*
|
||||
version: link:../config
|
||||
|
|
@ -14296,6 +14299,7 @@ packages:
|
|||
'@xmldom/xmldom@0.8.14':
|
||||
resolution: {integrity: sha512-T4EDRUBVZYRldYApjEJiU0e1stYWaRAX7CuSnKzrpwdZKo53zGV8/pqfzV6FfwNl9YThD2OumQYvqtvjvgG7aQ==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
deprecated: this version has critical issues, please update to the latest version
|
||||
|
||||
'@yuku-codegen/binding-darwin-arm64@0.6.1':
|
||||
resolution: {integrity: sha512-LDJtpOKtcv9f3V0eDUwFmmy47t2VC+DAuN+gq80R1IA+fa0d408i6sHsVtt6n+g5rf8f86ySoPSAe94lHt6Ixw==}
|
||||
|
|
@ -28512,7 +28516,7 @@ snapshots:
|
|||
- aws-crt
|
||||
- encoding
|
||||
|
||||
'@langchain/community@1.1.27(6cc31e45a82b311205f2bf99fec67d91)':
|
||||
'@langchain/community@1.1.27(2af036a866615d639e1d6409476fa500)':
|
||||
dependencies:
|
||||
'@browserbasehq/stagehand': empty-npm-package@1.0.0
|
||||
'@ibm-cloud/watsonx-ai': 1.1.2(supports-color@8.1.1)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user