feat: External Secrets storage for credentials (#6477)

Github issue / Community forum post (link here to close automatically):

---------

Co-authored-by: Romain Minaud <romain.minaud@gmail.com>
Co-authored-by: Valya Bullions <valya@n8n.io>
Co-authored-by: Csaba Tuncsik <csaba@n8n.io>
Co-authored-by: Giulio Andreini <g.andreini@gmail.com>
Co-authored-by: Omar Ajoue <krynble@gmail.com>
This commit is contained in:
Alex Grozav
2023-08-25 11:33:46 +03:00
committed by GitHub
parent c833078c87
commit ed927d34b2
89 changed files with 4164 additions and 57 deletions

View File

@@ -136,6 +136,7 @@ import {
setAllWorkflowExecutionMetadata,
setWorkflowExecutionMetadata,
} from './WorkflowExecutionMetadata';
import { getSecretsProxy } from './Secrets';
import { getUserN8nFolderPath } from './UserSettings';
axios.defaults.timeout = 300000;
@@ -1683,6 +1684,7 @@ export function getAdditionalKeys(
additionalData: IWorkflowExecuteAdditionalData,
mode: WorkflowExecuteMode,
runExecutionData: IRunExecutionData | null,
options?: { secretsEnabled?: boolean },
): IWorkflowDataProxyAdditionalKeys {
const executionId = additionalData.executionId || PLACEHOLDER_EMPTY_EXECUTION_ID;
const resumeUrl = `${additionalData.webhookWaitingBaseUrl}/${executionId}`;
@@ -1723,6 +1725,7 @@ export function getAdditionalKeys(
: undefined,
},
$vars: additionalData.variables,
$secrets: options?.secretsEnabled ? getSecretsProxy(additionalData) : undefined,
// deprecated
$executionId: executionId,
@@ -1858,6 +1861,7 @@ export async function getCredentials(
// }
const decryptedDataObject = await additionalData.credentialsHelper.getDecrypted(
additionalData,
nodeCredentials,
type,
mode,

View File

@@ -0,0 +1,76 @@
import type { IDataObject, IWorkflowExecuteAdditionalData } from 'n8n-workflow';
import { ExpressionError } from 'n8n-workflow';
function buildSecretsValueProxy(value: IDataObject): unknown {
return new Proxy(value, {
get(target, valueName) {
if (typeof valueName !== 'string') {
return;
}
if (!(valueName in value)) {
throw new ExpressionError('Could not load secrets', {
description:
'The credential in use tries to use secret from an external store that could not be found',
});
}
const retValue = value[valueName];
if (typeof retValue === 'object' && retValue !== null) {
return buildSecretsValueProxy(retValue as IDataObject);
}
return retValue;
},
});
}
export function getSecretsProxy(additionalData: IWorkflowExecuteAdditionalData): IDataObject {
const secretsHelpers = additionalData.secretsHelpers;
return new Proxy(
{},
{
get(target, providerName) {
if (typeof providerName !== 'string') {
return {};
}
if (secretsHelpers.hasProvider(providerName)) {
return new Proxy(
{},
{
get(target2, secretName): IDataObject | undefined {
if (typeof secretName !== 'string') {
return;
}
if (!secretsHelpers.hasSecret(providerName, secretName)) {
throw new ExpressionError('Could not load secrets', {
description:
'The credential in use tries to use secret from an external store that could not be found',
});
}
const retValue = secretsHelpers.getSecret(providerName, secretName);
if (typeof retValue === 'object' && retValue !== null) {
return buildSecretsValueProxy(retValue) as IDataObject;
}
return retValue;
},
set() {
return false;
},
ownKeys() {
return secretsHelpers.listSecrets(providerName);
},
},
);
}
throw new ExpressionError('Could not load secrets', {
description:
'The credential in use pulls secrets from an external store that is not reachable',
});
},
set() {
return false;
},
ownKeys() {
return secretsHelpers.listProviders();
},
},
);
}