使用KMS在Lambda函数中尝试解密密文会导致超时。

29

使用AWS CLI从命令行解密密文时,密文可以顺利解密:

$ aws kms decrypt --ciphertext-blob fileb://encrypted-secrets --output text --query Plaintext --region us-east-1 | base64 --decode > decryped-secrets

当尝试从js脚本执行此操作时,此解密操作也可以在本地工作:

#!/usr/local/bin/node

const fs = require('fs');
const AWS = require('aws-sdk');
const kms = new AWS.KMS({region:'us-east-1'});

const secretPath = './encrypted-secrets';
const encryptedSecret = fs.readFileSync(secretPath);

const params = {
      CiphertextBlob: encryptedSecret
};

kms.decrypt(params, function(err, data) {
  if (err) {
    console.log(err, err.stack);
  } else {
    const decryptedScret = data['Plaintext'].toString();
    console.log('decrypted secret', decryptedScret);
  }
});

然而,当尝试使用几乎完全相同的代码在 AWS Lambda 函数的上下文中运行时,函数调用会导致超时:

'use strict';

const zlib = require('zlib');
const mysql = require('mysql');
const fs = require('fs');
const AWS = require('aws-sdk');
const kms = new AWS.KMS({region:'us-east-1'});

const secretPath = './encrypted-secrets';
const encryptedSecret = fs.readFileSync(secretPath);

const params = {
    CiphertextBlob: encryptedSecret
};

exports.handler = (event, context, callback) => {
    kms.decrypt(params, (err, data) => {
       if (err) {
            console.log(err, err.stack);
            return callback(err);
        } else {
            const decryptedScret = data['Plaintext'].toString();
            console.log('decrypted secret', decryptedScret);
            return callback(null, `Successfully processed ${parsed.logEvents.length} log events.`);
        }
    });
};

超时日志:

START RequestId: start-request-id-redacted Version: $LATEST
END RequestId: end-request-id-redacted
REPORT RequestId: report-requested-id-redacted  Duration: 10002.43 ms   Billed Duration: 10000 ms   Memory Size: 128 MB Max Memory Used: 18 MB  
2016-11-13T19:22:28.774Z task-id-redacted Task timed out after 10.00 seconds

注:

  • 如果我注释掉对 kms.decrypt 的调用并尝试使用 console.log 输出 params 或其他任何东西,这些值将被输出而没有问题。似乎在 kms.decrypt 调用中存在某种问题,并且没有超时之外的实际错误返回。
  • 附加到调用 lambda 函数的角色上的策略包括附加的策略 AWSLambdaVPCAccessExecutionRole,以及以下附加的内联策略:

policygen-lambda_basic_execution_and_kms_decrypt-201611131221:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "sid-redacted",
            "Effect": "Allow",
            "Action": [
                "kms:Decrypt"
            ],
            "Resource": [
                "arn:aws:kms:us-east-1:account-redacted:key/key-id-redacted"
            ]
        }
    ]
}
  • 我已经从代码中删除了任何识别信息。
5个回答

46
经过与AWS支持人员的深入交谈,他们非常乐于提供帮助,我们得到了答案: 超时的主要原因是Lambda函数内部与KMS服务之间缺乏连接,因为KMS服务在Lambda函数配置的VPC中没有端点。 为了使VPC中的Lambda函数能够连接到除Amazon S3之外的任何服务(Amazon S3在VPC中有端点),Lambda函数必须位于/关联至少一个私有子网,并且最好是两个私有子网,其路由表包括0.0.0.0/16的目标路由到NAT网关。 Lambda函数不可能位于具有Internet Gateway的公共子网中。 让VPC绑定的Lambda函数访问没有VPC端点的KMS和所有其他服务的步骤:
  1. 创建或记下一个已有的私有子网,该子网具有针对0.0.0.0/0到NAT网关的路由表条目。
    • 如果您还没有NAT网关、路由表和上述子网,则需要首先创建它们并适当地将它们相互关联。
  2. 在创建Lambda函数时,将Lambda函数附加到上述私有子网,或编辑Lambda函数以具有该配置。

如果您遵循这两个步骤,您应该能够从Lambda函数内调用kms.encrypt和其他请求,这些请求需要出站/出口互联网连接,因为这些服务在您的VPC内没有端点。

Visual overview of how Lambda works within a VPC


1
我们遇到了相同的问题,这篇文章真的很有帮助!AWS支持团队是否解释了为什么公共VPC子网中的EC2实例可以访问KMS/其他服务,但Lambda函数不能呢? - Sanketh Katta
2
他们没有具体解释,但根据我对Lambda函数实例化方式的理解,这是由于Lambda函数位于其自己的VPC中的短暂容器中。 - zealoushacker
在这张图中,互联网网关被描绘为位于VPC子网中,这是不准确的。互联网网关与整个VPC相关联,但并不驻留在任何特定的子网中。 - Kevin Audleman
6
值得注意的是,AWS现在为KMS提供了一个接口终端,您可以在VPC中使用它与lambda一起使用:https://aws.amazon.com/blogs/security/how-to-connect-directly-to-aws-key-management-service-from-amazon-vpc-by-using-an-aws-privatelink-endpoint/ - killthrush
1
这太棒了。节省了我挠头思考的时间。 - simplytunde

7

EC2实例默认拥有自己的公共IP,所以它们可以轻松访问需要连接互联网的任何服务(例如KMS)。

附加到您的VPC的Lambda函数没有公共IP,因此要通过互联网访问服务(例如KMS),您需要设置NAT,就像zealoushacker所描述的那样。


这取决于 - 对于默认VPC中的EC2实例或“EC2 Classic”帐户,您是正确的。但是,如果您创建了自己的VPC,则实例可能默认不会获得公共IP,并且需要NAT网关 - 这是在创建VPC时的设置。 - RichVel

4

除了zealoushacker的优秀回答,您还应该检查lambda的安全组设置是否有指向0.0.0.0和任何端口的出站规则。

在我们的情况下,我们已经在私有子网中运行,但是将安全组限制为我们的RDS数据库。


1

简单更新一下,我遇到了同样的问题,但现在我们可以直接向连接到您的Lambda的VPC添加指向KMS的终端点。

您可以在此处找到详细信息:https://docs.aws.amazon.com/kms/latest/developerguide/kms-vpc-endpoint.html

我进行此更新是因为我本来打算按照其他答案操作(这些答案仍然有效),但这种方式似乎更加自然。


2
请添加更多细节以扩展您的答案,例如工作代码或文档引用。 - Community

0

就像 @killthrush 提到的那样,aws endpoint 是实现这一点非常好的方法。

你只需要添加端点,什么都不需要做,它就可以正常工作。

我的 Terraform 实现看起来像这样:

resource "aws_vpc_endpoint" "kms_endpoint" {
  service_name        = "com.amazonaws.${data.aws_region.current.name}.kms"
  vpc_id              = data.aws_vpc.default.id
  subnet_ids          = data.aws_subnets.default.ids
  vpc_endpoint_type   = "Interface"
  private_dns_enabled = true

  tags = {
    Name  = "${var.env}-kms-endpoint"
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接