diff --git a/packages/cli/src/runners/errors/missing-auth-token.error.ts b/packages/cli/src/runners/errors/missing-auth-token.error.ts new file mode 100644 index 0000000000..3c99a09edb --- /dev/null +++ b/packages/cli/src/runners/errors/missing-auth-token.error.ts @@ -0,0 +1,7 @@ +export class MissingAuthTokenError extends Error { + constructor() { + super( + 'Missing auth token. When `N8N_RUNNERS_MODE` is `external`, it is required to set `N8N_RUNNERS_AUTH_TOKEN`. Its value should be a shared secret between the main instance and the launcher.', + ); + } +} diff --git a/packages/cli/src/runners/task-runner-module.ts b/packages/cli/src/runners/task-runner-module.ts index fe476ad341..612f3d4fc1 100644 --- a/packages/cli/src/runners/task-runner-module.ts +++ b/packages/cli/src/runners/task-runner-module.ts @@ -4,6 +4,7 @@ import Container, { Service } from 'typedi'; import type { TaskRunnerProcess } from '@/runners/task-runner-process'; +import { MissingAuthTokenError } from './errors/missing-auth-token.error'; import { TaskRunnerWsServer } from './runner-ws-server'; import type { LocalTaskManager } from './task-managers/local-task-manager'; import type { TaskRunnerServer } from './task-runner-server'; @@ -28,13 +29,14 @@ export class TaskRunnerModule { async start() { a.ok(this.runnerConfig.enabled, 'Task runner is disabled'); + const { mode, authToken } = this.runnerConfig; + + if (mode === 'external' && !authToken) throw new MissingAuthTokenError(); + await this.loadTaskManager(); await this.loadTaskRunnerServer(); - if ( - this.runnerConfig.mode === 'internal_childprocess' || - this.runnerConfig.mode === 'internal_launcher' - ) { + if (mode === 'internal_childprocess' || mode === 'internal_launcher') { await this.startInternalTaskRunner(); } } diff --git a/packages/cli/src/runners/task-runner-server.ts b/packages/cli/src/runners/task-runner-server.ts index 1564195fe2..eb428b52fa 100644 --- a/packages/cli/src/runners/task-runner-server.ts +++ b/packages/cli/src/runners/task-runner-server.ts @@ -181,7 +181,10 @@ export class TaskRunnerServer { const response = new ServerResponse(request); response.writeHead = (statusCode) => { - if (statusCode > 200) ws.close(); + if (statusCode > 200) { + this.logger.error(`Task runner connection attempt failed with status code ${statusCode}`); + ws.close(); + } return response; }; diff --git a/packages/cli/test/integration/runners/task-runner-module.external.test.ts b/packages/cli/test/integration/runners/task-runner-module.external.test.ts index 4974abfb39..bdabdf56ae 100644 --- a/packages/cli/test/integration/runners/task-runner-module.external.test.ts +++ b/packages/cli/test/integration/runners/task-runner-module.external.test.ts @@ -1,6 +1,7 @@ import { TaskRunnersConfig } from '@n8n/config'; import Container from 'typedi'; +import { MissingAuthTokenError } from '@/runners/errors/missing-auth-token.error'; import { TaskRunnerModule } from '@/runners/task-runner-module'; import { DefaultTaskRunnerDisconnectAnalyzer } from '../../../src/runners/default-task-runner-disconnect-analyzer'; @@ -10,6 +11,7 @@ describe('TaskRunnerModule in external mode', () => { const runnerConfig = Container.get(TaskRunnersConfig); runnerConfig.mode = 'external'; runnerConfig.port = 0; + runnerConfig.authToken = 'test'; const module = Container.get(TaskRunnerModule); afterEach(async () => { @@ -24,6 +26,17 @@ describe('TaskRunnerModule in external mode', () => { await expect(module.start()).rejects.toThrow('Task runner is disabled'); }); + it('should throw if auth token is missing', async () => { + const runnerConfig = new TaskRunnersConfig(); + runnerConfig.mode = 'external'; + runnerConfig.enabled = true; + runnerConfig.authToken = ''; + + const module = new TaskRunnerModule(runnerConfig); + + await expect(module.start()).rejects.toThrowError(MissingAuthTokenError); + }); + it('should start the task runner', async () => { runnerConfig.enabled = true;