如何使用缓存的 boto3 客户端和 Lambda 预配置并发刷新凭据?

How are credentials refreshed with cached boto3 client and Lambda Provisioned Concurrency?

遵循最佳实践 Take advantage of execution environment reuse to improve the performance of your function,我正在调查在使用 Lambda 预配置并发时缓存 boto3 客户端是否有任何负面影响。 boto3 客户端通过 @lru_cache 装饰器缓存,并且是惰性初始化的。现在,问题是 boto3 客户端的基础凭据不会刷新,因为预配置并发将使执行环境保持活动状态的时间未知。此生命周期可能长于 Lambda 环境注入的临时凭证的持续时间。

我找不到任何文档来说明如何处理此案例。有谁知道 Lambda 环境如何处理上述情况下的凭据刷新?

如果您使用的是硬编码凭据:

您的安全问题比“重复使用”凭据更严重,应该立即

删除它们

来自documentation

Do NOT put literal access keys in your application files. If you do, you create a risk of accidentally exposing your credentials if, for example, you upload the project to a public repository.

Do NOT include files that contain credentials in your project area.

将它们替换为 execution role


如果您使用的是执行角色:

您没有为任何 AWS SDK 调用手动提供任何凭证。 SDK 的凭据自动 来自 Lambda 函数的执行角色

即使 Boto3 角色凭据在预配并发的幕后跨调用共享(没人确定),问题会是什么?

让 Amazon 处理角色凭据 - 您根本没有责任管理它。


我更担心应用程序代码存在安全漏洞,而不是亚马逊使用执行角色凭据自动验证 SDK 请求。

他们不是。

documentation for Boto3 doesn't do a very good job of describing the credential chain, but the CLI documentation 显示了凭证的各种来源(并且由于 CLI 是用 Python 编写的,它提供了权威文档)。

与从实例元数据中检索基于角色的凭据的 EC2 和 ECS 不同,Lambda 在环境变量中提供了凭据。 Lambda 运行时在启动时设置这些环境变量,并且 Lambda 运行时的每次调用都使用相同的值。

并发 Lambda 接收单独的凭据集,就像您对 STS 进行并发显式调用一样 AssumeRole

预配并发有点棘手。您可能认为同一个 Lambda 运行时“永远”存在,但事实上并非如此:如果您重复调用具有预置并发的 Lambda,您会看到它在某个时刻创建了一个新的 CloudWatch 日志流。这表明 Lambda 已启动新的运行时。 Lambda 将在停止向旧运行时发送请求之前完成新运行时的初始化,因此您不会遇到冷启动延迟。


更新:

这是一个 Python Lambda,它演示了我上面所说的内容。作为其初始化代码的一部分(在处理程序之外),它会记录首次初始化的时间,然后在每次调用时报告该时间戳。它还会记录“A​​WS”环境变量的当前内容,以便您可以查看其中是否有任何更改。

import json
import os
from datetime import datetime

print("initializing environment")
init_timestamp = datetime.utcnow()

def lambda_handler(event, context):
    print(f"environment was initialized at {init_timestamp.isoformat()}")
    print("")
    print("**** env ****")
    keys = list(os.environ.keys())
    keys.sort()
    for k in keys:
        if k.startswith("AWS_"):
            print(f"{k}: {os.environ[k]}")

将其配置为预置并发,然后使用此 shell 命令每 45 秒调用一次:

while true ; do date ; aws lambda invoke --function-name InvocationExplorer:2 --invocation-type Event --payload '{"foo": "irrelevant"}' /tmp/$$ ; sleep 45 ; done

保持 运行 一个小时或更长时间,您将获得两个日志流。第一个流如下所示(显示开始和结束,省略了数百条消息):

2021-10-19T16:19:32.699-04:00   initializing environment
2021-10-19T16:30:57.240-04:00   START RequestId: a27f6802-c7e6-4f70-b890-2e0172d46780 Version: 2
2021-10-19T16:30:57.243-04:00   environment was initialized at 2021-10-19T16:19:32.699455 
...
2021-10-19T17:07:24.853-04:00   END RequestId: dd9a356f-7928-4bf9-be56-86f4c5e1bb64
2021-10-19T17:07:24.853-04:00   REPORT RequestId: dd9a356f-7928-4bf9-be56-86f4c5e1bb64 Duration: 1.00 ms Billed Duration: 2 ms Memory Size: 128 MB Max Memory Used: 39 MB 

如您所见,Lambda 是在 16:19:32 初始化的,那是我启用预置并发的时间。第一个请求在 16:30:57.

处理

但我想指出的是此日志流中的 last 请求,在 17:07:24 或 Lambda 初始化后大约 48 分钟。

第二个日志流是这样开始的:

2021-10-19T17:04:08.739-04:00   initializing environment
2021-10-19T17:08:10.276-04:00   START RequestId: 6b15ba7c-91e2-4f91-bb6c-99b9877f1ebf Version: 2
2021-10-19T17:08:10.279-04:00   environment was initialized at 2021-10-19T17:04:08.739398 

如您所见,它在第一个流中的最终请求之前几分钟初始化,但在第一个流之后开始处理调用。

当然,这不是保证的行为。这就是 Lambda 今天 的工作方式, 并且将来可能会改变。但改变的可能性不大:当前植入的行为与记录的一样,任何改变都有破坏客户代码的风险。