fix(core): Fix data decryption on credentials import (#7560)

Due to a change, during the credentials import command, the core's
Credential object is being called through its prototype. This caused the
Credential's cipher variable to not be set, thus no cipher service being
available during import. This fix catches this edge case and provides a
fix.
This commit is contained in:
Michael Auerswald 2023-10-31 13:15:09 +01:00 committed by GitHub
parent 774d521dbd
commit b350568505
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 74 additions and 6 deletions

View file

@ -1,5 +1,5 @@
import { flags } from '@oclif/command'; import { flags } from '@oclif/command';
import { Credentials } from 'n8n-core'; import { Cipher } from 'n8n-core';
import fs from 'fs'; import fs from 'fs';
import glob from 'fast-glob'; import glob from 'fast-glob';
import { Container } from 'typedi'; import { Container } from 'typedi';
@ -69,6 +69,7 @@ export class ImportCredentialsCommand extends BaseCommand {
let totalImported = 0; let totalImported = 0;
const cipher = Container.get(Cipher);
await this.initOwnerCredentialRole(); await this.initOwnerCredentialRole();
const user = flags.userId ? await this.getAssignee(flags.userId) : await this.getOwner(); const user = flags.userId ? await this.getAssignee(flags.userId) : await this.getOwner();
@ -92,12 +93,10 @@ export class ImportCredentialsCommand extends BaseCommand {
const credential = jsonParse<ICredentialsEncrypted>( const credential = jsonParse<ICredentialsEncrypted>(
fs.readFileSync(file, { encoding: 'utf8' }), fs.readFileSync(file, { encoding: 'utf8' }),
); );
if (typeof credential.data === 'object') { if (typeof credential.data === 'object') {
// plain data / decrypted input. Should be encrypted first. // plain data / decrypted input. Should be encrypted first.
Credentials.prototype.setData.call(credential, credential.data); credential.data = cipher.encrypt(credential.data);
} }
await this.storeCredential(credential, user); await this.storeCredential(credential, user);
} }
}); });
@ -123,7 +122,7 @@ export class ImportCredentialsCommand extends BaseCommand {
for (const credential of credentials) { for (const credential of credentials) {
if (typeof credential.data === 'object') { if (typeof credential.data === 'object') {
// plain data / decrypted input. Should be encrypted first. // plain data / decrypted input. Should be encrypted first.
Credentials.prototype.setData.call(credential, credential.data); credential.data = cipher.encrypt(credential.data);
} }
await this.storeCredential(credential, user); await this.storeCredential(credential, user);
} }
@ -155,7 +154,10 @@ export class ImportCredentialsCommand extends BaseCommand {
this.ownerCredentialRole = ownerCredentialRole; this.ownerCredentialRole = ownerCredentialRole;
} }
private async storeCredential(credential: object, user: User) { private async storeCredential(credential: Partial<CredentialsEntity>, user: User) {
if (!credential.nodesAccess) {
credential.nodesAccess = [];
}
const result = await this.transactionManager.upsert(CredentialsEntity, credential, ['id']); const result = await this.transactionManager.upsert(CredentialsEntity, credential, ['id']);
await this.transactionManager.upsert( await this.transactionManager.upsert(
SharedCredentials, SharedCredentials,

View file

@ -0,0 +1,47 @@
import * as Config from '@oclif/config';
import { InternalHooks } from '@/InternalHooks';
import { ImportCredentialsCommand } from '@/commands/import/credentials';
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
import * as testDb from '../shared/testDb';
import { mockInstance } from '../shared/utils';
beforeAll(async () => {
mockInstance(InternalHooks);
mockInstance(LoadNodesAndCredentials);
await testDb.init();
});
beforeEach(async () => {
await testDb.truncate(['Credentials']);
});
afterAll(async () => {
await testDb.terminate();
});
test('import:credentials should import a credential', async () => {
const config: Config.IConfig = new Config.Config({ root: __dirname });
const before = await testDb.getAllCredentials();
expect(before.length).toBe(0);
const importer = new ImportCredentialsCommand(
['--input=./test/integration/commands/importCredentials/credentials.json'],
config,
);
const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {
throw new Error('process.exit');
});
await importer.init();
try {
await importer.run();
} catch (error) {
expect(error.message).toBe('process.exit');
}
const after = await testDb.getAllCredentials();
expect(after.length).toBe(1);
expect(after[0].name).toBe('cred-aws-test');
expect(after[0].id).toBe('123');
expect(after[0].nodesAccess).toStrictEqual([]);
mockExit.mockRestore();
});

View file

@ -0,0 +1,15 @@
[
{
"createdAt": "2023-07-10T14:50:49.193Z",
"updatedAt": "2023-10-27T13:34:42.917Z",
"id": "123",
"name": "cred-aws-test",
"data": {
"region": "eu-west-1",
"accessKeyId": "999999999999",
"secretAccessKey": "aaaaaaaaaaaaa"
},
"type": "aws",
"nodesAccess": ""
}
]

View file

@ -181,6 +181,10 @@ export function affixRoleToSaveCredential(role: Role) {
saveCredential(credentialPayload, { user, role }); saveCredential(credentialPayload, { user, role });
} }
export async function getAllCredentials() {
return Db.collections.Credentials.find();
}
// ---------------------------------- // ----------------------------------
// user creation // user creation
// ---------------------------------- // ----------------------------------