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 上,您可以执行以下操作:

  1. 创建一个 public VPC [如果您已经有一个 public VPC,请跳过此步骤]
  2. 在那个 public VPC 中添加一个 NAT 网关 [如果你已经有一个 NAT 网关也可以跳过这个]
  3. 在您的 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

  1. 打开VPC Management Console
  2. 创建 Elastic IP
  3. 使用 Wizard ("with private and public subnets")
  4. 创建一个 VPC
  5. 将弹性 IP 附加到 VPC ("Elastic IP Allocation ID")
  6. 使用路由 table
  7. 将您的私有和 public 子网从 0.0.0.0/0 路由到弹性 IP
  8. 创建一个 Internet Gateway 并将其附加到 VPC
  9. 将 Lambda 函数附加到 VPC
    1. 为您的 lambda 服务角色授予权限 AWSLambdaVPCAccessExecutionRoleAmazonEC2FullAccess

您可以通过从您的函数调用 ipify API 来测试它,并检查 HTTP 响应中提供的 ip。它应该与您的弹性 IP 相同。现在您可以将此 IP 列入白名单以访问 SFTP 服务器。

更多信息还 see this article