Skip to content

Commit

Permalink
fix: Fix issue with key based credentials not being read correctly (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Joffcom authored and netroy committed Aug 17, 2023
1 parent ff2d4ac commit 0a219af
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 69 deletions.
49 changes: 33 additions & 16 deletions packages/nodes-base/nodes/Ftp/Ftp.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
JsonObject,
} from 'n8n-workflow';
import { BINARY_ENCODING, NodeApiError } from 'n8n-workflow';
import { formatPrivateKey } from '@utils/utilities';
import { createWriteStream } from 'fs';
import { basename, dirname } from 'path';
import type { Readable } from 'stream';
Expand Down Expand Up @@ -463,14 +464,22 @@ export class Ftp implements INodeType {
const credentials = credential.data as ICredentialDataDecryptedObject;
try {
const sftp = new sftpClient();
await sftp.connect({
host: credentials.host as string,
port: credentials.port as number,
username: credentials.username as string,
password: credentials.password as string,
privateKey: credentials.privateKey as string | undefined,
passphrase: credentials.passphrase as string | undefined,
});
if (credentials.privateKey) {
await sftp.connect({
host: credentials.host as string,
port: credentials.port as number,
username: credentials.username as string,
privateKey: formatPrivateKey(credentials.privateKey as string),
passphrase: credentials.passphrase as string | undefined,
});
} else {
await sftp.connect({
host: credentials.host as string,
port: credentials.port as number,
username: credentials.username as string,
password: credentials.password as string,
});
}
} catch (error) {
return {
status: 'Error',
Expand Down Expand Up @@ -506,14 +515,22 @@ export class Ftp implements INodeType {

if (protocol === 'sftp') {
sftp = new sftpClient();
await sftp.connect({
host: credentials.host as string,
port: credentials.port as number,
username: credentials.username as string,
password: credentials.password as string,
privateKey: credentials.privateKey as string | undefined,
passphrase: credentials.passphrase as string | undefined,
});
if (credentials.privateKey) {
await sftp.connect({
host: credentials.host as string,
port: credentials.port as number,
username: credentials.username as string,
privateKey: formatPrivateKey(credentials.privateKey as string),
passphrase: credentials.passphrase as string | undefined,
});
} else {
await sftp.connect({
host: credentials.host as string,
port: credentials.port as number,
username: credentials.username as string,
password: credentials.password as string,
});
}
} else {
ftp = new ftpClient();
await ftp.connect({
Expand Down
4 changes: 3 additions & 1 deletion packages/nodes-base/nodes/Google/GenericFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import type { OptionsWithUri } from 'request';
import moment from 'moment-timezone';
import * as jwt from 'jsonwebtoken';

import { formatPrivateKey } from '@utils/utilities';

const googleServiceAccountScopes = {
bigquery: ['https://www.googleapis.com/auth/bigquery'],
books: ['https://www.googleapis.com/auth/books'],
Expand Down Expand Up @@ -63,7 +65,7 @@ export async function getGoogleAccessToken(

const scopes = googleServiceAccountScopes[service];

const privateKey = (credentials.privateKey as string).replace(/\\n/g, '\n').trim();
const privateKey = formatPrivateKey(credentials.privateKey as string);
credentials.email = ((credentials.email as string) || '').trim();

const now = moment().unix();
Expand Down
7 changes: 4 additions & 3 deletions packages/nodes-base/nodes/MQTT/Mqtt.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
} from 'n8n-workflow';

import * as mqtt from 'mqtt';
import { formatPrivateKey } from '@utils/utilities';

export class Mqtt implements INodeType {
description: INodeTypeDescription = {
Expand Down Expand Up @@ -118,9 +119,9 @@ export class Mqtt implements INodeType {
(credentials.clientId as string) || `mqttjs_${Math.random().toString(16).substr(2, 8)}`;
const clean = credentials.clean as boolean;
const ssl = credentials.ssl as boolean;
const ca = credentials.ca as string;
const cert = credentials.cert as string;
const key = credentials.key as string;
const ca = formatPrivateKey(credentials.ca as string);
const cert = formatPrivateKey(credentials.cert as string);
const key = formatPrivateKey(credentials.key as string);
const rejectUnauthorized = credentials.rejectUnauthorized as boolean;

let client: mqtt.MqttClient;
Expand Down
7 changes: 4 additions & 3 deletions packages/nodes-base/nodes/MQTT/MqttTrigger.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
import { NodeOperationError } from 'n8n-workflow';

import * as mqtt from 'mqtt';
import { formatPrivateKey } from '@utils/utilities';

export class MqttTrigger implements INodeType {
description: INodeTypeDescription = {
Expand Down Expand Up @@ -101,9 +102,9 @@ export class MqttTrigger implements INodeType {
(credentials.clientId as string) || `mqttjs_${Math.random().toString(16).substr(2, 8)}`;
const clean = credentials.clean as boolean;
const ssl = credentials.ssl as boolean;
const ca = credentials.ca as string;
const cert = credentials.cert as string;
const key = credentials.key as string;
const ca = formatPrivateKey(credentials.ca as string);
const cert = formatPrivateKey(credentials.cert as string);
const key = formatPrivateKey(credentials.key as string);
const rejectUnauthorized = credentials.rejectUnauthorized as boolean;

let client: mqtt.MqttClient;
Expand Down
21 changes: 9 additions & 12 deletions packages/nodes-base/nodes/MySql/v2/transport/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { ICredentialDataDecryptedObject, IDataObject } from 'n8n-workflow';
import { formatPrivateKey } from '@utils/utilities';

import mysql2 from 'mysql2/promise';
import type { Client, ConnectConfig } from 'ssh2';
import { rm, writeFile } from 'fs/promises';
import { rm } from 'fs/promises';

import { file } from 'tmp-promise';
import type { Mysql2Pool } from '../helpers/interfaces';

async function createSshConnectConfig(credentials: IDataObject) {
Expand All @@ -16,14 +16,11 @@ async function createSshConnectConfig(credentials: IDataObject) {
password: credentials.sshPassword as string,
} as ConnectConfig;
} else {
const { path } = await file({ prefix: 'n8n-ssh-' });
await writeFile(path, credentials.privateKey as string);

const options: ConnectConfig = {
host: credentials.host as string,
username: credentials.username as string,
port: credentials.port as number,
privateKey: path,
host: credentials.sshHost as string,
username: credentials.sshUser as string,
port: credentials.sshPort as number,
privateKey: formatPrivateKey(credentials.privateKey as string),
};

if (credentials.passphrase) {
Expand Down Expand Up @@ -63,12 +60,12 @@ export async function createPool(
baseCredentials.ssl = {};

if (caCertificate) {
baseCredentials.ssl.ca = caCertificate;
baseCredentials.ssl.ca = formatPrivateKey(caCertificate as string);
}

if (clientCertificate || clientPrivateKey) {
baseCredentials.ssl.cert = clientCertificate;
baseCredentials.ssl.key = clientPrivateKey;
baseCredentials.ssl.cert = formatPrivateKey(clientCertificate as string);
baseCredentials.ssl.key = formatPrivateKey(clientPrivateKey as string);
}
}

Expand Down
15 changes: 6 additions & 9 deletions packages/nodes-base/nodes/Postgres/v2/transport/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { IDataObject } from 'n8n-workflow';
import { formatPrivateKey } from '@utils/utilities';

import { Client } from 'ssh2';
import type { ConnectConfig } from 'ssh2';
Expand All @@ -8,8 +9,7 @@ import { createServer } from 'net';

import pgPromise from 'pg-promise';

import { rm, writeFile } from 'fs/promises';
import { file } from 'tmp-promise';
import { rm } from 'fs/promises';

import type { PgpDatabase } from '../helpers/interfaces';

Expand All @@ -22,14 +22,11 @@ async function createSshConnectConfig(credentials: IDataObject) {
password: credentials.sshPassword as string,
} as ConnectConfig;
} else {
const { path } = await file({ prefix: 'n8n-ssh-' });
await writeFile(path, credentials.privateKey as string);

const options: ConnectConfig = {
host: credentials.host as string,
username: credentials.username as string,
port: credentials.port as number,
privateKey: path,
host: credentials.sshHost as string,
username: credentials.sshUser as string,
port: credentials.sshPort as number,
privateKey: formatPrivateKey(credentials.privateKey as string),
};

if (credentials.passphrase) {
Expand Down
14 changes: 11 additions & 3 deletions packages/nodes-base/nodes/RabbitMQ/GenericFunctions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { IDataObject, IExecuteFunctions, ITriggerFunctions } from 'n8n-workflow';
import { sleep } from 'n8n-workflow';
import { formatPrivateKey } from '@utils/utilities';

import * as amqplib from 'amqplib';

Expand All @@ -20,10 +21,17 @@ export async function rabbitmqConnect(
if (credentials.ssl === true) {
credentialData.protocol = 'amqps';

optsData.ca = credentials.ca === '' ? undefined : [Buffer.from(credentials.ca as string)];
optsData.ca =
credentials.ca === '' ? undefined : [Buffer.from(formatPrivateKey(credentials.ca as string))];
if (credentials.passwordless === true) {
optsData.cert = credentials.cert === '' ? undefined : Buffer.from(credentials.cert as string);
optsData.key = credentials.key === '' ? undefined : Buffer.from(credentials.key as string);
optsData.cert =
credentials.cert === ''
? undefined
: Buffer.from(formatPrivateKey(credentials.cert as string));
optsData.key =
credentials.key === ''
? undefined
: Buffer.from(formatPrivateKey(credentials.key as string));
optsData.passphrase = credentials.passphrase === '' ? undefined : credentials.passphrase;
optsData.credentials = amqplib.credentials.external();
}
Expand Down
13 changes: 10 additions & 3 deletions packages/nodes-base/nodes/RabbitMQ/RabbitMQ.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {
import { NodeApiError, NodeOperationError } from 'n8n-workflow';

import { rabbitmqConnectExchange, rabbitmqConnectQueue } from './GenericFunctions';
import { formatPrivateKey } from '@utils/utilities';

export class RabbitMQ implements INodeType {
description: INodeTypeDescription = {
Expand Down Expand Up @@ -376,12 +377,18 @@ export class RabbitMQ implements INodeType {
credentialData.protocol = 'amqps';

optsData.ca =
credentials.ca === '' ? undefined : [Buffer.from(credentials.ca as string)];
credentials.ca === ''
? undefined
: [Buffer.from(formatPrivateKey(credentials.ca as string))];
if (credentials.passwordless === true) {
optsData.cert =
credentials.cert === '' ? undefined : Buffer.from(credentials.cert as string);
credentials.cert === ''
? undefined
: Buffer.from(formatPrivateKey(credentials.cert as string));
optsData.key =
credentials.key === '' ? undefined : Buffer.from(credentials.key as string);
credentials.key === ''
? undefined
: Buffer.from(formatPrivateKey(credentials.key as string));
optsData.passphrase =
credentials.passphrase === '' ? undefined : credentials.passphrase;
optsData.credentials = amqplib.credentials.external();
Expand Down
23 changes: 4 additions & 19 deletions packages/nodes-base/nodes/Ssh/Ssh.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import type {
} from 'n8n-workflow';
import { BINARY_ENCODING, NodeOperationError } from 'n8n-workflow';

import { formatPrivateKey } from '@utils/utilities';

import { rm, writeFile } from 'fs/promises';

import { file as tmpFile } from 'tmp-promise';
Expand Down Expand Up @@ -47,14 +49,6 @@ async function resolveHomeDir(
return path;
}

function sanitizePrivateKey(privateKey: string) {
const [openSshKey, bodySshKey, endSshKey] = privateKey
.split('-----')
.filter((item) => item !== '');

return `-----${openSshKey}-----\n${bodySshKey.replace(/ /g, '\n')}\n-----${endSshKey}-----`;
}

export class Ssh implements INodeType {
description: INodeTypeDescription = {
displayName: 'SSH',
Expand Down Expand Up @@ -304,15 +298,11 @@ export class Ssh implements INodeType {
password: credentials.password as string,
});
} else {
const { path } = await tmpFile({ prefix: 'n8n-ssh-' });
temporaryFiles.push(path);
await writeFile(path, sanitizePrivateKey(credentials.privateKey as string));

const options: Config = {
host: credentials.host as string,
username: credentials.username as string,
port: credentials.port as number,
privateKey: path,
privateKey: formatPrivateKey(credentials.privateKey as string),
};

if (credentials.passphrase) {
Expand Down Expand Up @@ -364,16 +354,11 @@ export class Ssh implements INodeType {
});
} else if (authentication === 'privateKey') {
const credentials = await this.getCredentials('sshPrivateKey');

const { path } = await tmpFile({ prefix: 'n8n-ssh-' });
temporaryFiles.push(path);
await writeFile(path, sanitizePrivateKey(credentials.privateKey as string));

const options: Config = {
host: credentials.host as string,
username: credentials.username as string,
port: credentials.port as number,
privateKey: path,
privateKey: formatPrivateKey(credentials.privateKey as string),
};

if (credentials.passphrase) {
Expand Down
30 changes: 30 additions & 0 deletions packages/nodes-base/utils/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,36 @@ export const keysToLowercase = <T>(headers: T) => {
}, {} as IDataObject);
};

/**
* Formats a private key by removing unnecessary whitespace and adding line breaks.
* @param privateKey - The private key to format.
* @returns The formatted private key.
*/
export function formatPrivateKey(privateKey: string): string {
if (/\n/.test(privateKey)) {
return privateKey;
}
let formattedPrivateKey = '';
const parts = privateKey.split('-----').filter((item) => item !== '');
parts.forEach((part) => {
const regex = /(PRIVATE KEY|CERTIFICATE)/;
if (regex.test(part)) {
formattedPrivateKey += `-----${part}-----`;
} else {
const passRegex = /Proc-Type|DEK-Info/;
if (passRegex.test(part)) {
part = part.replace(/:\s+/g, ':');
formattedPrivateKey += part.replace(/\\n/g, '\n');
formattedPrivateKey += part.replace(/\s+/g, '\n');
} else {
formattedPrivateKey += part.replace(/\\n/g, '\n');
formattedPrivateKey += part.replace(/\s+/g, '\n');
}
}
});
return formattedPrivateKey;
}

/**
* @TECH_DEBT Explore replacing with handlebars
*/
Expand Down

0 comments on commit 0a219af

Please sign in to comment.