mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 04:04:06 -08:00
fix: Fix issue with key based credentials not being read correctly (#6824)
This commit is contained in:
parent
6553d92c7c
commit
db21a8db75
|
@ -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';
|
||||
|
@ -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',
|
||||
|
@ -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({
|
||||
|
|
|
@ -11,6 +11,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'],
|
||||
|
@ -69,7 +71,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();
|
||||
|
|
|
@ -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 = {
|
||||
|
@ -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;
|
||||
|
|
|
@ -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 = {
|
||||
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { IDataObject } from 'n8n-workflow';
|
||||
import { formatPrivateKey } from '@utils/utilities';
|
||||
|
||||
import { Client } from 'ssh2';
|
||||
import type { ConnectConfig } from 'ssh2';
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 = {
|
||||
|
@ -375,12 +376,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();
|
||||
|
|
|
@ -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';
|
||||
|
@ -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',
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue