Skip to content

Commit

Permalink
fix(core): Use hostname from URL instead of Host header for SNI (n8n-…
Browse files Browse the repository at this point in the history
  • Loading branch information
elsmr authored Feb 6, 2024
1 parent 76f3170 commit 7531f34
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 12 deletions.
51 changes: 39 additions & 12 deletions packages/core/src/NodeExecuteFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import FormData from 'form-data';
import { createReadStream } from 'fs';
import { access as fsAccess, writeFile as fsWriteFile } from 'fs/promises';
import { IncomingMessage, type IncomingHttpHeaders } from 'http';
import { Agent } from 'https';
import { Agent, type AgentOptions } from 'https';
import get from 'lodash/get';
import pick from 'lodash/pick';
import { extension, lookup } from 'mime-types';
Expand Down Expand Up @@ -229,7 +229,22 @@ async function generateContentLengthHeader(config: AxiosRequestConfig) {
}
}

async function parseRequestObject(requestObject: IDataObject) {
const getHostFromRequestObject = (
requestObject: Partial<{
url: string;
uri: string;
baseURL: string;
}>,
): string | null => {
try {
const url = (requestObject.url ?? requestObject.uri) as string;
return new URL(url, requestObject.baseURL).hostname;
} catch (error) {
return null;
}
};

export async function parseRequestObject(requestObject: IDataObject) {
// This function is a temporary implementation
// That translates all http requests done via
// the request library to axios directly
Expand Down Expand Up @@ -468,6 +483,17 @@ async function parseRequestObject(requestObject: IDataObject) {
});
}

const host = getHostFromRequestObject(requestObject);
const agentOptions: AgentOptions = {};
if (host) {
agentOptions.servername = host;
}
if (requestObject.rejectUnauthorized === false) {
agentOptions.rejectUnauthorized = false;
agentOptions.secureOptions = crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT;
}
axiosConfig.httpsAgent = new Agent(agentOptions);

if (requestObject.timeout !== undefined) {
axiosConfig.timeout = requestObject.timeout as number;
}
Expand Down Expand Up @@ -732,14 +758,11 @@ export async function proxyRequestToAxios(
maxBodyLength: Infinity,
maxContentLength: Infinity,
};
let configObject: ConfigObject;
if (uriOrObject !== undefined && typeof uriOrObject === 'string') {
axiosConfig.url = uriOrObject;
}
if (uriOrObject !== undefined && typeof uriOrObject === 'object') {
configObject = uriOrObject;
let configObject: ConfigObject & { uri?: string };
if (typeof uriOrObject === 'string') {
configObject = { uri: uriOrObject, ...options };
} else {
configObject = options || {};
configObject = uriOrObject ?? {};
}

axiosConfig = Object.assign(axiosConfig, await parseRequestObject(configObject));
Expand Down Expand Up @@ -859,11 +882,15 @@ function convertN8nRequestToAxios(n8nRequest: IHttpRequestOptions): AxiosRequest
axiosRequest.responseType = n8nRequest.encoding;
}

const host = getHostFromRequestObject(n8nRequest);
const agentOptions: AgentOptions = {};
if (host) {
agentOptions.servername = host;
}
if (n8nRequest.skipSslCertificateValidation === true) {
axiosRequest.httpsAgent = new Agent({
rejectUnauthorized: false,
});
agentOptions.rejectUnauthorized = false;
}
axiosRequest.httpsAgent = new Agent(agentOptions);

if (n8nRequest.arrayFormat !== undefined) {
axiosRequest.paramsSerializer = (params) => {
Expand Down
12 changes: 12 additions & 0 deletions packages/core/test/NodeExecuteFunctions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
copyInputItems,
getBinaryDataBuffer,
parseIncomingMessage,
parseRequestObject,
proxyRequestToAxios,
setBinaryDataBuffer,
} from '@/NodeExecuteFunctions';
Expand All @@ -21,6 +22,7 @@ import nock from 'nock';
import { tmpdir } from 'os';
import { join } from 'path';
import Container from 'typedi';
import type { Agent } from 'https';

const temporaryDir = mkdtempSync(join(tmpdir(), 'n8n'));

Expand Down Expand Up @@ -358,6 +360,16 @@ describe('NodeExecuteFunctions', () => {
});
});

describe('parseRequestObject', () => {
test('should not use Host header for SNI', async () => {
const axiosOptions = await parseRequestObject({
url: 'https://example.de/foo/bar',
headers: { Host: 'other.host.com' },
});
expect((axiosOptions.httpsAgent as Agent).options.servername).toEqual('example.de');
});
});

describe('copyInputItems', () => {
it('should pick only selected properties', () => {
const output = copyInputItems(
Expand Down

0 comments on commit 7531f34

Please sign in to comment.