Error: Connecting to a SFTP server with ssh2-sftp-client in a AWS lambda function throws a time out
Error: Connecting to a SFTP server with ssh2-sftp-client in a AWS lambda function throws a time out
我正在尝试连接到 SFTP 服务器并列出 /ARCHIVE
文件夹中的文档。凭据存储在 .env 文件中。当我 运行 在我的本地计算机上运行它时,它会工作并列出文档。
async function main (event){
let Client = require('ssh2-sftp-client');
let sftp = new Client();
const dotenv = require('dotenv');
dotenv.config();
const ftpOptions = {
host: process.env.FTP_HOST,
port: process.env.FTP_PORT,
username: process.env.FTP_USER,
password: process.env.FTP_PASSWORD,
debug: console.log
}
await sftp.connect(ftpOptions);
let documentList = await sftp.list('/ARCHIVE');
console.log(documentList);
sftp.end();
}
但是如果我在我的 AWS lambda 函数中尝试它,它会尝试连接并超时。在执行 sftp.connect(ftpOptions)
之前加载环境变量并可用。日志显示该函数正在尝试连接到服务器,但甚至无法使用凭据登录。
Function Logs:
START RequestId: 20d3c3b7-19d8-49cf-8997-1f2331eb0b6e Version: $LATEST
2020-06-04T08:27:12.837Z 20d3c3b7-19d8-49cf-8997-1f2331eb0b6e INFO Debugging turned on
2020-06-04T08:27:12.860Z 20d3c3b7-19d8-49cf-8997-1f2331eb0b6e INFO DEBUG: Local ident: 'SSH-2.0-ssh2js0.4.10'
2020-06-04T08:27:12.898Z 20d3c3b7-19d8-49cf-8997-1f2331eb0b6e INFO DEBUG: Client: Trying ftp_foo.com on port 22 ...
2020-06-04T08:27:32.929Z 20d3c3b7-19d8-49cf-8997-1f2331eb0b6e INFO CLIENT[sftp]: Connection attempt 1 failed. Trying again.
2020-06-04T08:27:33.931Z 20d3c3b7-19d8-49cf-8997-1f2331eb0b6e INFO DEBUG: Local ident: 'SSH-2.0-ssh2js0.4.10'
2020-06-04T08:27:33.931Z 20d3c3b7-19d8-49cf-8997-1f2331eb0b6e INFO DEBUG: Client: Trying ftp_foo.com on port 22 ...
2020-06-04T08:27:53.951Z 20d3c3b7-19d8-49cf-8997-1f2331eb0b6e INFO CLIENT[sftp]: Exhausted all connection attempts. Giving up
2020-06-04T08:27:53.957Z 20d3c3b7-19d8-49cf-8997-1f2331eb0b6e ERROR Invoke Error {"errorType":"Error","errorMessage":"connect: Timed out while waiting for handshake after 2 attempts","code":"ERR_GENERIC_CLIENT","custom":true,"stack":["Error: connect: Timed out while waiting for handshake after 2 attempts"," at Object.formatError (/var/task/node_modules/ssh2-sftp-client/src/utils.js:62:18)"," at Client.connectErrorListener (/var/task/node_modules/ssh2-sftp-client/src/index.js:98:21)"," at Object.onceWrapper (events.js:417:26)"," at Client.emit (events.js:310:20)"," at Timeout._onTimeout (/var/task/node_modules/ssh2/lib/client.js:697:14)"," at listOnTimeout (internal/timers.js:549:17)"," at processTimers (internal/timers.js:492:7)"]}
2020-06-04T08:27:53.959Z 20d3c3b7-19d8-49cf-8997-1f2331eb0b6e ERROR Unhandled Promise Rejection {"errorType":"Runtime.UnhandledPromiseRejection","errorMessage":"Error: end: No SFTP connection available","reason":{"errorType":"Error","errorMessage":"end: No SFTP connection available","code":"ERR_NOT_CONNECTED","custom":true,"stack":["Error: end: No SFTP connection available"," at formatError (/var/task/node_modules/ssh2-sftp-client/src/utils.js:62:18)"," at Object.haveConnection (/var/task/node_modules/ssh2-sftp-client/src/utils.js:612:20)"," at /var/task/node_modules/ssh2-sftp-client/src/index.js:1248:19"," at new Promise (<anonymous>)"," at SftpClient.end (/var/task/node_modules/ssh2-sftp-client/src/index.js:1236:12)"," at downloadPDFs (/var/task/index.js:40:14)"," at processTicksAndRejections (internal/process/task_queues.js:97:5)"]},"promise":{},"stack":["Runtime.UnhandledPromiseRejection: Error: end: No SFTP connection available"," at process.<anonymous> (/var/runtime/index.js:35:15)"," at process.emit (events.js:310:20)"," at processPromiseRejections (internal/process/promises.js:209:33)"," at processTicksAndRejections (internal/process/task_queues.js:98:32)"]}
[ERROR] [1591259273978] LAMBDA_RUNTIME Failed to post handler success response. Http response code: 403.
我的本地计算机上是否发生了一些事情,以提供我没有在 lambda 函数中实现的连接?
问题可能是您的 Lambda 函数无法访问 SFTP 主机。
此 SFTP 服务器是托管在 AWS 之外还是在您的账户内?
如果它托管在互联网上而不是您在 AWS 上,您可以执行以下操作:
- 创建一个 public VPC [如果您已经有一个 public VPC,请跳过此步骤]
- 在那个 public VPC 中添加一个 NAT 网关 [如果你已经有一个 NAT 网关也可以跳过这个]
- 在您的 Lambda 函数所在的 VPC 中,添加一条路由 table,通过将目的地设置为
0.0.0.0/0
并将目标设置为我们在中创建的 NAT 网关,将流量重定向到 NAT 网关步骤 2.
您可以通过访问这篇 AWS 文章找到有关如何将 Internet 连接添加到您的 Lambda 函数的详细答案:How do I give internet access to my Lambda function in a VPC?
如果 SFTP 服务器已由您托管在 AWS 中,您只需使用 AWS 安全组添加对其的访问即可。
查看这篇文章,了解如何将 Lambda 与另一个 AWS 服务连接(它使用 RDS,但您可以在 SFTP 服务器上遵循相同的逻辑):Configuring a Lambda function to access Amazon RDS in an Amazon VPC
此外,请确保将 Lambda 超时设置得足够长,以允许与外部 SFTP 服务器连接和交换数据。
AWS 外部 SFTP 服务器的解决方案
正如@Atef 之前提到的,问题是 SFTP 服务器托管在 AWS 之外,并且只授予对白名单 IP 的访问权限。所以我使用了以下解决方法:
使用 VPC 为 Lambda 函数配置静态 IP
- 打开VPC Management Console
- 创建 Elastic IP
- 使用 Wizard ("with private and public subnets")
创建一个 VPC
- 将弹性 IP 附加到 VPC ("Elastic IP Allocation ID")
- 使用路由 table
将您的私有和 public 子网从 0.0.0.0/0 路由到弹性 IP
- 创建一个 Internet Gateway 并将其附加到 VPC
- 将 Lambda 函数附加到 VPC
- 为您的 lambda 服务角色授予权限
AWSLambdaVPCAccessExecutionRole
和 AmazonEC2FullAccess
您可以通过从您的函数调用 ipify API 来测试它,并检查 HTTP 响应中提供的 ip。它应该与您的弹性 IP 相同。现在您可以将此 IP 列入白名单以访问 SFTP 服务器。
更多信息还 see this article。
我正在尝试连接到 SFTP 服务器并列出 /ARCHIVE
文件夹中的文档。凭据存储在 .env 文件中。当我 运行 在我的本地计算机上运行它时,它会工作并列出文档。
async function main (event){
let Client = require('ssh2-sftp-client');
let sftp = new Client();
const dotenv = require('dotenv');
dotenv.config();
const ftpOptions = {
host: process.env.FTP_HOST,
port: process.env.FTP_PORT,
username: process.env.FTP_USER,
password: process.env.FTP_PASSWORD,
debug: console.log
}
await sftp.connect(ftpOptions);
let documentList = await sftp.list('/ARCHIVE');
console.log(documentList);
sftp.end();
}
但是如果我在我的 AWS lambda 函数中尝试它,它会尝试连接并超时。在执行 sftp.connect(ftpOptions)
之前加载环境变量并可用。日志显示该函数正在尝试连接到服务器,但甚至无法使用凭据登录。
Function Logs:
START RequestId: 20d3c3b7-19d8-49cf-8997-1f2331eb0b6e Version: $LATEST
2020-06-04T08:27:12.837Z 20d3c3b7-19d8-49cf-8997-1f2331eb0b6e INFO Debugging turned on
2020-06-04T08:27:12.860Z 20d3c3b7-19d8-49cf-8997-1f2331eb0b6e INFO DEBUG: Local ident: 'SSH-2.0-ssh2js0.4.10'
2020-06-04T08:27:12.898Z 20d3c3b7-19d8-49cf-8997-1f2331eb0b6e INFO DEBUG: Client: Trying ftp_foo.com on port 22 ...
2020-06-04T08:27:32.929Z 20d3c3b7-19d8-49cf-8997-1f2331eb0b6e INFO CLIENT[sftp]: Connection attempt 1 failed. Trying again.
2020-06-04T08:27:33.931Z 20d3c3b7-19d8-49cf-8997-1f2331eb0b6e INFO DEBUG: Local ident: 'SSH-2.0-ssh2js0.4.10'
2020-06-04T08:27:33.931Z 20d3c3b7-19d8-49cf-8997-1f2331eb0b6e INFO DEBUG: Client: Trying ftp_foo.com on port 22 ...
2020-06-04T08:27:53.951Z 20d3c3b7-19d8-49cf-8997-1f2331eb0b6e INFO CLIENT[sftp]: Exhausted all connection attempts. Giving up
2020-06-04T08:27:53.957Z 20d3c3b7-19d8-49cf-8997-1f2331eb0b6e ERROR Invoke Error {"errorType":"Error","errorMessage":"connect: Timed out while waiting for handshake after 2 attempts","code":"ERR_GENERIC_CLIENT","custom":true,"stack":["Error: connect: Timed out while waiting for handshake after 2 attempts"," at Object.formatError (/var/task/node_modules/ssh2-sftp-client/src/utils.js:62:18)"," at Client.connectErrorListener (/var/task/node_modules/ssh2-sftp-client/src/index.js:98:21)"," at Object.onceWrapper (events.js:417:26)"," at Client.emit (events.js:310:20)"," at Timeout._onTimeout (/var/task/node_modules/ssh2/lib/client.js:697:14)"," at listOnTimeout (internal/timers.js:549:17)"," at processTimers (internal/timers.js:492:7)"]}
2020-06-04T08:27:53.959Z 20d3c3b7-19d8-49cf-8997-1f2331eb0b6e ERROR Unhandled Promise Rejection {"errorType":"Runtime.UnhandledPromiseRejection","errorMessage":"Error: end: No SFTP connection available","reason":{"errorType":"Error","errorMessage":"end: No SFTP connection available","code":"ERR_NOT_CONNECTED","custom":true,"stack":["Error: end: No SFTP connection available"," at formatError (/var/task/node_modules/ssh2-sftp-client/src/utils.js:62:18)"," at Object.haveConnection (/var/task/node_modules/ssh2-sftp-client/src/utils.js:612:20)"," at /var/task/node_modules/ssh2-sftp-client/src/index.js:1248:19"," at new Promise (<anonymous>)"," at SftpClient.end (/var/task/node_modules/ssh2-sftp-client/src/index.js:1236:12)"," at downloadPDFs (/var/task/index.js:40:14)"," at processTicksAndRejections (internal/process/task_queues.js:97:5)"]},"promise":{},"stack":["Runtime.UnhandledPromiseRejection: Error: end: No SFTP connection available"," at process.<anonymous> (/var/runtime/index.js:35:15)"," at process.emit (events.js:310:20)"," at processPromiseRejections (internal/process/promises.js:209:33)"," at processTicksAndRejections (internal/process/task_queues.js:98:32)"]}
[ERROR] [1591259273978] LAMBDA_RUNTIME Failed to post handler success response. Http response code: 403.
我的本地计算机上是否发生了一些事情,以提供我没有在 lambda 函数中实现的连接?
问题可能是您的 Lambda 函数无法访问 SFTP 主机。
此 SFTP 服务器是托管在 AWS 之外还是在您的账户内?
如果它托管在互联网上而不是您在 AWS 上,您可以执行以下操作:
- 创建一个 public VPC [如果您已经有一个 public VPC,请跳过此步骤]
- 在那个 public VPC 中添加一个 NAT 网关 [如果你已经有一个 NAT 网关也可以跳过这个]
- 在您的 Lambda 函数所在的 VPC 中,添加一条路由 table,通过将目的地设置为
0.0.0.0/0
并将目标设置为我们在中创建的 NAT 网关,将流量重定向到 NAT 网关步骤 2.
您可以通过访问这篇 AWS 文章找到有关如何将 Internet 连接添加到您的 Lambda 函数的详细答案:How do I give internet access to my Lambda function in a VPC?
如果 SFTP 服务器已由您托管在 AWS 中,您只需使用 AWS 安全组添加对其的访问即可。
查看这篇文章,了解如何将 Lambda 与另一个 AWS 服务连接(它使用 RDS,但您可以在 SFTP 服务器上遵循相同的逻辑):Configuring a Lambda function to access Amazon RDS in an Amazon VPC
此外,请确保将 Lambda 超时设置得足够长,以允许与外部 SFTP 服务器连接和交换数据。
AWS 外部 SFTP 服务器的解决方案
正如@Atef 之前提到的,问题是 SFTP 服务器托管在 AWS 之外,并且只授予对白名单 IP 的访问权限。所以我使用了以下解决方法:
使用 VPC 为 Lambda 函数配置静态 IP
- 打开VPC Management Console
- 创建 Elastic IP
- 使用 Wizard ("with private and public subnets") 创建一个 VPC
- 将弹性 IP 附加到 VPC ("Elastic IP Allocation ID")
- 使用路由 table 将您的私有和 public 子网从 0.0.0.0/0 路由到弹性 IP
- 创建一个 Internet Gateway 并将其附加到 VPC
- 将 Lambda 函数附加到 VPC
- 为您的 lambda 服务角色授予权限
AWSLambdaVPCAccessExecutionRole
和AmazonEC2FullAccess
- 为您的 lambda 服务角色授予权限
您可以通过从您的函数调用 ipify API 来测试它,并检查 HTTP 响应中提供的 ip。它应该与您的弹性 IP 相同。现在您可以将此 IP 列入白名单以访问 SFTP 服务器。
更多信息还 see this article。