From ce79e6b74f6d94694f16988c8601f7c0639a04b3 Mon Sep 17 00:00:00 2001 From: Basit Ali Date: Fri, 22 Apr 2022 19:33:09 +0500 Subject: [PATCH] feat(All AWS Nodes): Enable support for AWS temporary credentials (#2587) * Enable support for AWS temporary credentials * :hammer: removed toggle from ui added sessionToken to other aws services that using sign function from aws4 module * Update sign method for other AWS nodes * Remove the unneeded additional `temporaryCredentials` checkbox * Update description for session token * :zap: added missing session token to credentials test * Update sign method for DynamoDB * :hammer: added back toggle for hiding session token, fixed linter errors * :zap: wording fix Co-authored-by: Michael Kret --- .../nodes-base/credentials/Aws.credentials.ts | 23 +++++++++++++++++++ .../nodes/Aws/Comprehend/GenericFunctions.ts | 7 +++++- .../nodes/Aws/DynamoDB/GenericFunctions.ts | 11 +++++---- .../nodes-base/nodes/Aws/GenericFunctions.ts | 7 +++++- .../nodes/Aws/Rekognition/GenericFunctions.ts | 7 +++++- .../nodes-base/nodes/Aws/S3/AwsS3.node.ts | 3 ++- .../nodes/Aws/S3/GenericFunctions.ts | 12 ++++++---- .../nodes/Aws/SES/GenericFunctions.ts | 8 ++++++- .../nodes/Aws/Textract/GenericFunctions.ts | 15 ++++++++++-- .../nodes/Aws/Transcribe/GenericFunctions.ts | 7 +++++- .../nodes-base/nodes/S3/GenericFunctions.ts | 8 ++++++- 11 files changed, 90 insertions(+), 18 deletions(-) diff --git a/packages/nodes-base/credentials/Aws.credentials.ts b/packages/nodes-base/credentials/Aws.credentials.ts index efb5fe59aa..26649c29b3 100644 --- a/packages/nodes-base/credentials/Aws.credentials.ts +++ b/packages/nodes-base/credentials/Aws.credentials.ts @@ -31,6 +31,29 @@ export class Aws implements ICredentialType { password: true, }, }, + { + displayName: 'Temporary Security Credentials', + name: 'temporaryCredentials', + description: 'Support for temporary credentials from AWS STS', + type: 'boolean', + default: false, + }, + { + displayName: 'Session Token', + name: 'sessionToken', + type: 'string', + displayOptions: { + show: { + temporaryCredentials: [ + true, + ], + }, + }, + default: '', + typeOptions: { + password: true, + }, + }, { displayName: 'Custom Endpoints', name: 'customEndpoints', diff --git a/packages/nodes-base/nodes/Aws/Comprehend/GenericFunctions.ts b/packages/nodes-base/nodes/Aws/Comprehend/GenericFunctions.ts index 40d4c73b90..7c32669470 100644 --- a/packages/nodes-base/nodes/Aws/Comprehend/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Aws/Comprehend/GenericFunctions.ts @@ -46,8 +46,13 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I // Sign AWS API request with the user credentials const signOpts = { headers: headers || {}, host: endpoint.host, method, path, body } as Request; - sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`.trim(), secretAccessKey: `${credentials.secretAccessKey}`.trim() }); + const securityHeaders = { + accessKeyId: `${credentials.accessKeyId}`.trim(), + secretAccessKey: `${credentials.secretAccessKey}`.trim(), + sessionToken: credentials.temporaryCredentials ? `${credentials.sessionToken}`.trim() : undefined, + }; + sign(signOpts, securityHeaders); const options: OptionsWithUri = { headers: signOpts.headers, diff --git a/packages/nodes-base/nodes/Aws/DynamoDB/GenericFunctions.ts b/packages/nodes-base/nodes/Aws/DynamoDB/GenericFunctions.ts index 8b25dd39b0..cb25ca7030 100644 --- a/packages/nodes-base/nodes/Aws/DynamoDB/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Aws/DynamoDB/GenericFunctions.ts @@ -40,7 +40,11 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I // Concatenate path and instantiate URL object so it parses correctly query strings const endpoint = new URL(getEndpointForService(service, credentials) + path); - + const securityHeaders = { + accessKeyId: `${credentials.accessKeyId}`.trim(), + secretAccessKey: `${credentials.secretAccessKey}`.trim(), + sessionToken: credentials.temporaryCredentials ? `${credentials.sessionToken}`.trim() : undefined, + }; const options = sign({ // @ts-ignore uri: endpoint, @@ -50,10 +54,7 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I path: '/', headers: { ...headers }, body: JSON.stringify(body), - }, { - accessKeyId: credentials.accessKeyId, - secretAccessKey: credentials.secretAccessKey, - }); + }, securityHeaders); try { return JSON.parse(await this.helpers.request!(options)); diff --git a/packages/nodes-base/nodes/Aws/GenericFunctions.ts b/packages/nodes-base/nodes/Aws/GenericFunctions.ts index 1c89db14f5..f1d1d13508 100644 --- a/packages/nodes-base/nodes/Aws/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Aws/GenericFunctions.ts @@ -36,8 +36,13 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I // Sign AWS API request with the user credentials const signOpts = { headers: headers || {}, host: endpoint.host, method, path, body } as Request; - sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`.trim(), secretAccessKey: `${credentials.secretAccessKey}`.trim() }); + const securityHeaders = { + accessKeyId: `${credentials.accessKeyId}`.trim(), + secretAccessKey: `${credentials.secretAccessKey}`.trim(), + sessionToken: credentials.temporaryCredentials ? `${credentials.sessionToken}`.trim() : undefined, + }; + sign(signOpts, securityHeaders); const options: OptionsWithUri = { headers: signOpts.headers, diff --git a/packages/nodes-base/nodes/Aws/Rekognition/GenericFunctions.ts b/packages/nodes-base/nodes/Aws/Rekognition/GenericFunctions.ts index 27ba23e4e3..3a8b98c360 100644 --- a/packages/nodes-base/nodes/Aws/Rekognition/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Aws/Rekognition/GenericFunctions.ts @@ -43,8 +43,13 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I // Sign AWS API request with the user credentials const signOpts = {headers: headers || {}, host: endpoint.host, method, path, body} as Request; + const securityHeaders = { + accessKeyId: `${credentials.accessKeyId}`.trim(), + secretAccessKey: `${credentials.secretAccessKey}`.trim(), + sessionToken: credentials.temporaryCredentials ? `${credentials.sessionToken}`.trim() : undefined, + }; - sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`.trim(), secretAccessKey: `${credentials.secretAccessKey}`.trim()}); + sign(signOpts, securityHeaders); const options: OptionsWithUri = { headers: signOpts.headers, diff --git a/packages/nodes-base/nodes/Aws/S3/AwsS3.node.ts b/packages/nodes-base/nodes/Aws/S3/AwsS3.node.ts index c77fa6d3c6..5709841f9a 100644 --- a/packages/nodes-base/nodes/Aws/S3/AwsS3.node.ts +++ b/packages/nodes-base/nodes/Aws/S3/AwsS3.node.ts @@ -22,6 +22,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + JsonObject, NodeOperationError, } from 'n8n-workflow'; @@ -632,7 +633,7 @@ export class AwsS3 implements INodeType { } } catch (error) { if (this.continueOnFail()) { - returnData.push({ error: error.message }); + returnData.push({ error: (error as JsonObject).message }); continue; } throw error; diff --git a/packages/nodes-base/nodes/Aws/S3/GenericFunctions.ts b/packages/nodes-base/nodes/Aws/S3/GenericFunctions.ts index 0838622d01..d2d7d4d19c 100644 --- a/packages/nodes-base/nodes/Aws/S3/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Aws/S3/GenericFunctions.ts @@ -27,7 +27,7 @@ import { } from 'n8n-core'; import { - IDataObject, NodeApiError, NodeOperationError, + IDataObject, JsonObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string | Buffer, query: IDataObject = {}, headers?: object, option: IDataObject = {}, region?: string): Promise { // tslint:disable-line:no-any @@ -37,9 +37,13 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I // Sign AWS API request with the user credentials const signOpts = {headers: headers || {}, host: endpoint.host, method, path: `${endpoint.pathname}?${queryToString(query).replace(/\+/g, '%2B')}`, body} as Request; + const securityHeaders = { + accessKeyId: `${credentials.accessKeyId}`.trim(), + secretAccessKey: `${credentials.secretAccessKey}`.trim(), + sessionToken: credentials.temporaryCredentials ? `${credentials.sessionToken}`.trim() : undefined, + }; - - sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`.trim(), secretAccessKey: `${credentials.secretAccessKey}`.trim()}); + sign(signOpts, securityHeaders); const options: OptionsWithUri = { headers: signOpts.headers, @@ -55,7 +59,7 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I try { return await this.helpers.request!(options); } catch (error) { - throw new NodeApiError(this.getNode(), error); + throw new NodeApiError(this.getNode(), (error as JsonObject)); } } diff --git a/packages/nodes-base/nodes/Aws/SES/GenericFunctions.ts b/packages/nodes-base/nodes/Aws/SES/GenericFunctions.ts index 201b03ce51..09adde5ead 100644 --- a/packages/nodes-base/nodes/Aws/SES/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Aws/SES/GenericFunctions.ts @@ -38,7 +38,13 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I // Sign AWS API request with the user credentials const signOpts = { headers: headers || {}, host: endpoint.host, method, path, body } as Request; - sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`.trim(), secretAccessKey: `${credentials.secretAccessKey}`.trim() }); + const securityHeaders = { + accessKeyId: `${credentials.accessKeyId}`.trim(), + secretAccessKey: `${credentials.secretAccessKey}`.trim(), + sessionToken: credentials.temporaryCredentials ? `${credentials.sessionToken}`.trim() : undefined, + }; + + sign(signOpts, securityHeaders); const options: OptionsWithUri = { headers: signOpts.headers, diff --git a/packages/nodes-base/nodes/Aws/Textract/GenericFunctions.ts b/packages/nodes-base/nodes/Aws/Textract/GenericFunctions.ts index f0ba1e8dc6..b263612463 100644 --- a/packages/nodes-base/nodes/Aws/Textract/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Aws/Textract/GenericFunctions.ts @@ -49,8 +49,13 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I // Sign AWS API request with the user credentials const signOpts = { headers: headers || {}, host: endpoint.host, method, path, body } as Request; - sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`.trim(), secretAccessKey: `${credentials.secretAccessKey}`.trim() }); + const securityHeaders = { + accessKeyId: `${credentials.accessKeyId}`.trim(), + secretAccessKey: `${credentials.secretAccessKey}`.trim(), + sessionToken: credentials.temporaryCredentials ? `${credentials.sessionToken}`.trim() : undefined, + }; + sign(signOpts, securityHeaders); const options: OptionsWithUri = { headers: signOpts.headers, @@ -131,7 +136,13 @@ export async function validateCrendetials(this: ICredentialTestFunctions, decryp // Sign AWS API request with the user credentials const signOpts = { host: endpoint.host, method: 'POST', path: '?Action=GetCallerIdentity&Version=2011-06-15' } as Request; - sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`.trim(), secretAccessKey: `${credentials.secretAccessKey}`.trim() }); + const securityHeaders = { + accessKeyId: `${credentials.accessKeyId}`.trim(), + secretAccessKey: `${credentials.secretAccessKey}`.trim(), + sessionToken: credentials.temporaryCredentials ? `${credentials.sessionToken}`.trim() : undefined, + }; + + sign(signOpts, securityHeaders); const options: OptionsWithUri = { headers: signOpts.headers, diff --git a/packages/nodes-base/nodes/Aws/Transcribe/GenericFunctions.ts b/packages/nodes-base/nodes/Aws/Transcribe/GenericFunctions.ts index 4e7b8886cb..7de8078242 100644 --- a/packages/nodes-base/nodes/Aws/Transcribe/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Aws/Transcribe/GenericFunctions.ts @@ -49,8 +49,13 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I // Sign AWS API request with the user credentials const signOpts = { headers: headers || {}, host: endpoint.host, method, path, body } as Request; - sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`.trim(), secretAccessKey: `${credentials.secretAccessKey}`.trim() }); + const securityHeaders = { + accessKeyId: `${credentials.accessKeyId}`.trim(), + secretAccessKey: `${credentials.secretAccessKey}`.trim(), + sessionToken: credentials.temporaryCredentials ? `${credentials.sessionToken}`.trim() : undefined, + }; + sign(signOpts, securityHeaders); const options: OptionsWithUri = { headers: signOpts.headers, diff --git a/packages/nodes-base/nodes/S3/GenericFunctions.ts b/packages/nodes-base/nodes/S3/GenericFunctions.ts index e59fdea25b..8b87646bdc 100644 --- a/packages/nodes-base/nodes/S3/GenericFunctions.ts +++ b/packages/nodes-base/nodes/S3/GenericFunctions.ts @@ -61,7 +61,13 @@ export async function s3ApiRequest(this: IHookFunctions | IExecuteFunctions | IL body, } as Request; - sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`.trim(), secretAccessKey: `${credentials.secretAccessKey}`.trim() }); + const securityHeaders = { + accessKeyId: `${credentials.accessKeyId}`.trim(), + secretAccessKey: `${credentials.secretAccessKey}`.trim(), + sessionToken: credentials.temporaryCredentials ? `${credentials.sessionToken}`.trim() : undefined, + }; + + sign(signOpts, securityHeaders); const options: OptionsWithUri = { headers: signOpts.headers,