

# 在 Lambda 函数中使用 Secrets Manager 密钥
<a name="with-secrets-manager"></a>

AWS Secrets Manager 可帮助您管理 Lambda 函数所需的凭证、API 密钥和其他密钥。您有两种主要方法可以在 Lambda 函数中检索密钥，与直接使用 AWS SDK 检索密钥相比，这两种方法的性能更佳且成本更低：
+ **AWS 参数和密钥 Lambda 扩展** - 一种与运行时无关的解决方案，提供一个简单的 HTTP 接口来检索密钥
+ **Powertools for AWS Lambda 参数实用程序** - 支持多个提供商（Secrets Manager、Parameter Store、AppConfig）的代码集成解决方案，具有内置转换功能

这两种方法都维护密钥的本地缓存，从而无需您的函数在每次调用时都调用 Secrets Manager。当您的函数请求密钥时，将首先检查缓存。如果密钥可用且尚未过期，则会立即返回该密钥。否则，将从 Secrets Manager 中检索、缓存并返回密钥。这种缓存机制通过最大限度地减少 API 调用，可以缩短响应时间并降低成本。

## 选择方法
<a name="lambda-secrets-manager-choosing-approach"></a>

在扩展和 PowerTools 之间进行选择时，请考虑以下因素：

在以下情况下使用 AWS 参数和密钥 Lambda 扩展：  
+ 您需要一个与任何 Lambda 运行时兼容的、与运行时无关的解决方案
+ 您不想在函数中添加代码依赖项
+ 您只需从 Secrets Manager 或 Parameter Store 检索密钥

在以下情况下使用 Powertools for AWS Lambda 参数实用程序：  
+ 您想要获得与应用程序代码集成的开发体验
+ 您需要支持多个提供商（Secrets Manager、Parameter Store、AppConfig）
+ 您需要内置数据转换（JSON 解析、base64 解码）
+ 您正在使用 Python、TypeScript、Java 或 .NET 运行时

## 何时将 Secrets Manager 与 Lambda 结合使用
<a name="lambda-secrets-manager-when-to-use"></a>

将 Secrets Manager 与 Lambda 结合使用的常见场景包括：
+ 存储您的函数用于连接到 Amazon RDS 或其他数据库的数据库凭证
+ 管理您的函数调用的外部服务的 API 密钥
+ 存储加密密钥或其他敏感配置数据
+ 自动轮换凭证，无需更新函数代码

## 使用 AWS 参数和密钥 Lambda 扩展
<a name="lambda-secrets-manager-extension-approach"></a>

AWS 参数和密钥 Lambda 扩展使用与任何 Lambda 运行时兼容的简单 HTTP 接口。默认情况下，它会缓存密钥 300 秒（5 分钟），最多可容纳 1,000 个密钥。您可以[使用环境变量自定义这些设置](#lambda-secrets-manager-env-vars)。

### 在 Lambda 函数中使用 Secrets Manager
<a name="lambda-secrets-manager-setup"></a>

本节假设您已经拥有 Secrets Manager 密钥。要创建密钥，请参阅 [Create an AWS Secrets Manager secret](https://docs.aws.amazon.com/secretsmanager/latest/userguide/create_secret.html)。

#### 创建部署程序包
<a name="lambda-secrets-manager-function-code"></a>

选择您的首选运行时，并按照步骤创建用于从 Secrets Manager 检索密钥的函数。该示例函数从 Secrets Manager 中检索密钥，并可用于访问数据库凭证、API 密钥或应用程序中的其他敏感配置数据。

------
#### [ Python ]

**创建 Python 函数**

1. 创建并导航到新的项目目录。示例：

   ```
   mkdir my_function
   cd my_function
   ```

1. 使用以下代码创建名为 `lambda_function.py` 的文件。对于 `secret_name`，使用您的密钥的名称或 Amazon 资源名称 (ARN)。

   ```
   import json
   import os
   import requests
   
   def lambda_handler(event, context):
       try:
           # Replace with the name or ARN of your secret
           secret_name = "arn:aws:secretsmanager:us-east-1:111122223333:secret:SECRET_NAME"
           
           secrets_extension_endpoint = f"http://localhost:2773/secretsmanager/get?secretId={secret_name}"
           headers = {"X-Aws-Parameters-Secrets-Token": os.environ.get('AWS_SESSION_TOKEN')}
           
           response = requests.get(secrets_extension_endpoint, headers=headers)
           print(f"Response status code: {response.status_code}")
           
           secret = json.loads(response.text)["SecretString"]
           print(f"Retrieved secret: {secret}")
           
           return {
               'statusCode': response.status_code,
               'body': json.dumps({
                   'message': 'Successfully retrieved secret',
                   'secretRetrieved': True
               })
           }
       
       except Exception as e:
           print(f"Error: {str(e)}")
           return {
               'statusCode': 500,
               'body': json.dumps({
                   'message': 'Error retrieving secret',
                   'error': str(e)
               })
           }
   ```

1. 使用此内容创建名为 `requirements.txt` 的文件：

   ```
   requests
   ```

1. 安装依赖项：

   ```
   pip install -r requirements.txt -t .
   ```

1. 创建包含所有文件的 .zip 文件：

   ```
   zip -r function.zip .
   ```

------
#### [ Node.js ]

**创建 Node.js 函数**

1. 创建并导航到新的项目目录。示例：

   ```
   mkdir my_function
   cd my_function
   ```

1. 使用以下代码创建名为 `index.mjs` 的文件。对于 `secret_name`，使用您的密钥的名称或 Amazon 资源名称 (ARN)。

   ```
   import http from 'http';
   
   export const handler = async (event) => {
       try {
           // Replace with the name or ARN of your secret
           const secretName = "arn:aws:secretsmanager:us-east-1:111122223333:secret:SECRET_NAME";
           const options = {
               hostname: 'localhost',
               port: 2773,
               path: `/secretsmanager/get?secretId=${secretName}`,
               headers: {
                   'X-Aws-Parameters-Secrets-Token': process.env.AWS_SESSION_TOKEN
               }
           };
   
           const response = await new Promise((resolve, reject) => {
               http.get(options, (res) => {
                   let data = '';
                   res.on('data', (chunk) => { data += chunk; });
                   res.on('end', () => {
                       resolve({ 
                           statusCode: res.statusCode, 
                           body: data 
                       });
                   });
               }).on('error', reject);
           });
   
           const secret = JSON.parse(response.body).SecretString;
           console.log('Retrieved secret:', secret);
   
           return {
               statusCode: response.statusCode,
               body: JSON.stringify({
                   message: 'Successfully retrieved secret',
                   secretRetrieved: true
               })
           };
       } catch (error) {
           console.error('Error:', error);
           return {
               statusCode: 500,
               body: JSON.stringify({
                   message: 'Error retrieving secret',
                   error: error.message
               })
           };
       }
   };
   ```

1. 创建包含 `index.mjs` 文件的 .zip 文件：

   ```
   zip -r function.zip index.mjs
   ```

------
#### [ Java ]

**创建 Java 函数**

1. 创建 Maven 项目：

   ```
   mvn archetype:generate \
       -DgroupId=example \
       -DartifactId=lambda-secrets-demo \
       -DarchetypeArtifactId=maven-archetype-quickstart \
       -DarchetypeVersion=1.4 \
       -DinteractiveMode=false
   ```

1. 导航到项目目录：

   ```
   cd lambda-secrets-demo
   ```

1. 打开 `pom.xml` 并将内容替换为以下内容：

   ```
   <project xmlns="http://maven.apache.org/POM/4.0.0"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
       <modelVersion>4.0.0</modelVersion>
   
       <groupId>example</groupId>
       <artifactId>lambda-secrets-demo</artifactId>
       <version>1.0-SNAPSHOT</version>
   
       <properties>
           <maven.compiler.source>11</maven.compiler.source>
           <maven.compiler.target>11</maven.compiler.target>
       </properties>
   
       <dependencies>
           <dependency>
               <groupId>com.amazonaws</groupId>
               <artifactId>aws-lambda-java-core</artifactId>
               <version>1.2.1</version>
           </dependency>
       </dependencies>
   
       <build>
           <plugins>
               <plugin>
                   <groupId>org.apache.maven.plugins</groupId>
                   <artifactId>maven-shade-plugin</artifactId>
                   <version>3.2.4</version>
                   <executions>
                       <execution>
                           <phase>package</phase>
                           <goals>
                               <goal>shade</goal>
                           </goals>
                           <configuration>
                               <createDependencyReducedPom>false</createDependencyReducedPom>
                               <finalName>function</finalName>
                           </configuration>
                       </execution>
                   </executions>
               </plugin>
           </plugins>
       </build>
   </project>
   ```

1. 将 `/lambda-secrets-demo/src/main/java/example/App.java` 重命名为 `Hello.java`，以匹配 Lambda 的默认 Java 处理程序名称 (`example.Hello::handleRequest`)：

   ```
   mv src/main/java/example/App.java src/main/java/example/Hello.java
   ```

1. 打开 `Hello.java` 文件并将其内容替换为以下内容。对于 `secretName`，使用您的密钥的名称或 Amazon 资源名称 (ARN)。

   ```
   package example;
   
   import com.amazonaws.services.lambda.runtime.Context;
   import com.amazonaws.services.lambda.runtime.RequestHandler;
   import java.net.URI;
   import java.net.http.HttpClient;
   import java.net.http.HttpRequest;
   import java.net.http.HttpResponse;
   
   public class Hello implements RequestHandler<Object, String> {
       private final HttpClient client = HttpClient.newHttpClient();
   
       @Override
       public String handleRequest(Object input, Context context) {
           try {
               // Replace with the name or ARN of your secret
               String secretName = "arn:aws:secretsmanager:us-east-1:111122223333:secret:SECRET_NAME";
               String endpoint = "http://localhost:2773/secretsmanager/get?secretId=" + secretName;
   
               HttpRequest request = HttpRequest.newBuilder()
                   .uri(URI.create(endpoint))
                   .header("X-Aws-Parameters-Secrets-Token", System.getenv("AWS_SESSION_TOKEN"))
                   .GET()
                   .build();
   
               HttpResponse<String> response = client.send(request, 
                   HttpResponse.BodyHandlers.ofString());
   
               String secret = response.body();
               secret = secret.substring(secret.indexOf("SecretString") + 15);
               secret = secret.substring(0, secret.indexOf("\""));
   
               System.out.println("Retrieved secret: " + secret);
               return String.format(
                   "{\"statusCode\": %d, \"body\": \"%s\"}",
                   response.statusCode(), "Successfully retrieved secret"
               );
   
           } catch (Exception e) {
               e.printStackTrace();
               return String.format(
                   "{\"body\": \"Error retrieving secret: %s\"}", 
                   e.getMessage()
               );
           }
       }
   }
   ```

1. 删除测试目录。Maven 默认创建此项，但在本例中我们不需要它。

   ```
   rm -rf src/test
   ```

1. 构建项目：

   ```
   mvn package
   ```

1. 下载 JAR 文件 (`target/function.jar`) 以供稍后使用。

------

#### 创建函数
<a name="lambda-secrets-manager-create"></a>

1. 打开 Lamba 控制台的 [Functions page](https://console.aws.amazon.com/lambda/home#/functions)（函数页面）。

1. 选择**创建函数**。

1. 选择**从头开始编写**。

1. 对于**函数名称**，请输入 **secret-retrieval-demo**。

1. 选择您的首选**运行时**。

1. 选择**创建函数**。

**上传部署包**

1. 在函数的**代码**选项卡中，选择**上传自**，然后选择 **.zip 文件**（对于 Python 和 Node.js）或 **.jar 文件**（对于 Java）。

1. 上传您之前创建的部署包。

1. 选择**保存**。

#### 添加扩展
<a name="lambda-secrets-manager-extension"></a>

**将 AWS 参数和密钥 Lambda 扩展添加为层**

1. 在函数的**代码**选项卡中，向下滚动到**层**。

1. 选择 **Add a layer**。

1. 选择 **AWS 层**。

1. 选择 **AWS-Parameters-and-Secrets-Lambda-Extension**。

1. 选择最新版本。

1. 选择**添加**。

#### 添加权限
<a name="lambda-secrets-manager-permissions"></a>

**向您的执行角色添加 Secrets Manager 权限**

1. 选择 **Configuration**（配置）选项卡，然后选择 **Permissions**（权限）。

1. 在**角色名称**下，选择至执行角色的链接。此角色将在 IAM 控制台中打开角色。  
![\[\]](http://docs.aws.amazon.com/zh_cn/lambda/latest/dg/images/execution-role-console.png)

1. 选择**添加权限**，然后选择**创建内联策略**。  
![\[\]](http://docs.aws.amazon.com/zh_cn/lambda/latest/dg/images/create-inline-policy.png)

1. 选择 **JSON** 选项卡，然后添加以下策略。对于 `Resource`，输入您的密钥的 ARN。

------
#### [ JSON ]

****  

   ```
   {
       "Version":"2012-10-17",		 	 	 
       "Statement": [
           {
               "Effect": "Allow",
               "Action": "secretsmanager:GetSecretValue",
               "Resource": "arn:aws:secretsmanager:us-east-1:111122223333:secret:SECRET_NAME"
           }
       ]
   }
   ```

------

1. 选择**下一步**。

1. 输入策略的名称。

1. 选择**创建策略**。

#### 测试此函数
<a name="lambda-secrets-manager-test"></a>

**测试此函数**

1. 返回 Lambda 控制台。

1. 选择**测试**选项卡。

1. 选择**测试**。您应看到以下响应：  
![\[\]](http://docs.aws.amazon.com/zh_cn/lambda/latest/dg/images/execution-results-secret.png)

### 环境变量
<a name="lambda-secrets-manager-env-vars"></a>

AWS 参数和密钥 Lambda 扩展使用下面的默认设置。您可以通过创建相应的[环境变量](configuration-envvars.md#create-environment-variables)来覆盖这些设置。要查看函数的当前设置，请将 `PARAMETERS_SECRETS_EXTENSION_LOG_LEVEL` 设置为 `DEBUG`。该扩展将在每次函数调用开始时将其配置信息记录到 CloudWatch Logs 中。


| 设置 | 默认 值 | 有效值 | 环境变量 | Details | 
| --- | --- | --- | --- | --- | 
| HTTP 端口 | 2773 | 1 - 65535 | PARAMETERS\$1SECRETS\$1EXTENSION\$1HTTP\$1PORT | 本地 HTTP 服务器的端口 | 
| 已启用缓存 | TRUE | TRUE \$1 FALSE | PARAMETERS\$1SECRETS\$1EXTENSION\$1CACHE\$1ENABLED | 启用或禁用缓存 | 
| 缓存大小 | 1000 | 0 - 1000 | PARAMETERS\$1SECRETS\$1EXTENSION\$1CACHE\$1SIZE | 设置为 0 以禁用缓存 | 
| Secrets Manager TTL | 300 秒 | 0 - 300 秒 | SECRETS\$1MANAGER\$1TTL | 缓存密钥的生存时间。设置为 0 以禁用缓存。如果 PARAMETERS\$1SECRETS\$1EXTENSION\$1CACHE\$1SIZE 的值为 0，则忽略此变量。 | 
| Parameter Store TTL | 300 秒 | 0 - 300 秒 | SSM\$1PARAMETER\$1STORE\$1TTL | 缓存参数的生存时间。设置为 0 以禁用缓存。如果 PARAMETERS\$1SECRETS\$1EXTENSION\$1CACHE\$1SIZE 的值为 0，则忽略此变量。 | 
| 日志级别 | INFO | 调试 \$1 信息 \$1 警告 \$1 错误 \$1 无 | PARAMETERS\$1SECRETS\$1EXTENSION\$1LOG\$1LEVEL | 扩展日志中报告的详细信息级别 | 
| 最大连接数 | 3 | 1 或更多 | PARAMETERS\$1SECRETS\$1EXTENSION\$1MAX\$1CONNECTIONS | 对 Parameter Store 或 Secrets Manager 的请求的最大 HTTP 连接数 | 
| Secrets Manager 超时 | 0（无超时） | 所有整数 | SECRETS\$1MANAGER\$1TIMEOUT\$1MILLIS | Secrets Manager 的请求超时（以毫秒为单位） | 
| Parameter Store 超时 | 0（无超时） | 所有整数 | SSM\$1PARAMETER\$1STORE\$1TIMEOUT\$1MILLIS | Parameter Store 的请求超时（以毫秒为单位） | 

### 使用密钥轮换
<a name="lambda-secrets-manager-rotation"></a>

如果频繁轮换密钥，则默认 300 秒缓存持续时间可能会导致您的函数使用过时的密钥。您有两种方法可以确保函数使用最新的密钥值：
+ 通过将 `SECRETS_MANAGER_TTL` 环境变量设置为较低的值（以秒为单位）来缩短缓存 TTL。例如，将其设置为 `60` 可确保函数永远不会使用超过一分钟的密钥。
+ 在密钥请求中使用 `AWSCURRENT` 或 `AWSPREVIOUS` 暂存标签来确保获得所需的特定版本：

  ```
  secretsmanager/get?secretId=YOUR_SECRET_NAME&versionStage=AWSCURRENT
  ```

选择最能平衡您对性能和新鲜度需求的方法。较低 TTL 意味着对 Secrets Manager 的调用更频繁，但可确保您使用最新的密钥值。

## 使用 Powertools for AWS Lambda 中的参数实用程序
<a name="lambda-secrets-manager-powertools-approach"></a>

Powertools for AWS Lambda 中的参数实用程序提供了一个统一的界面，用于从多个提供商（包括 Secrets Manager、Parameter Store 和 AppConfig）检索密钥。它处理缓存、转换，并提供与扩展方法相比更加集成的开发体验。

### 参数实用程序的好处
<a name="lambda-secrets-manager-powertools-benefits"></a>
+ **多个提供商** - 使用相同的接口从 Secrets Manager、Parameter Store 和 AppConfig 检索参数
+ **内置转换** - 自动 JSON 解析、base64 解码和其他数据转换
+ **集成缓存** - 支持 TTL 的可配置缓存，可减少 API 调用
+ **键入安全** - 在 TypeScript 和其他支持的运行时中提供强大的键入支持
+ **错误处理** - 内置重试逻辑和错误处理

### 代码示例
<a name="lambda-secrets-manager-powertools-examples"></a>

以下示例展示了如何在不同的运行时使用参数实用程序检索密钥：

**Python**  
有关完整示例和设置说明，请参阅[参数实用程序文档](https://docs.powertools.aws.dev/lambda/python/latest/utilities/parameters/)。
使用 Powertools for AWS Lambda 参数实用程序从 Secrets Manager 中检索密钥。  

```
from aws_lambda_powertools import Logger
from aws_lambda_powertools.utilities import parameters

logger = Logger()

def lambda_handler(event, context):
    try:
        # Get secret with caching (default TTL: 5 seconds)
        secret_value = parameters.get_secret("my-secret-name")
        
        # Get secret with custom TTL
        secret_with_ttl = parameters.get_secret("my-secret-name", max_age=300)
        
        # Get secret and transform JSON
        secret_json = parameters.get_secret("my-json-secret", transform="json")
        
        logger.info("Successfully retrieved secrets")
        
        return {
            'statusCode': 200,
            'body': 'Successfully retrieved secrets'
        }
        
    except Exception as e:
        logger.error(f"Error retrieving secret: {str(e)}")
        return {
            'statusCode': 500,
            'body': f'Error: {str(e)}'
        }
```

**TypeScript**  
有关完整示例和设置说明，请参阅[参数实用程序文档](https://docs.aws.amazon.com/powertools/typescript/2.1.1/utilities/parameters/)。
使用 Powertools for AWS Lambda 参数实用程序从 Secrets Manager 中检索密钥。  

```
import { Logger } from '@aws-lambda-powertools/logger';
import { getSecret } from '@aws-lambda-powertools/parameters/secrets';
import type { Context } from 'aws-lambda';

const logger = new Logger();

export const handler = async (event: any, context: Context) => {
    try {
        // Get secret with caching (default TTL: 5 seconds)
        const secretValue = await getSecret('my-secret-name');
        
        // Get secret with custom TTL
        const secretWithTtl = await getSecret('my-secret-name', { maxAge: 300 });
        
        // Get secret and transform JSON
        const secretJson = await getSecret('my-json-secret', { transform: 'json' });
        
        logger.info('Successfully retrieved secrets');
        
        return {
            statusCode: 200,
            body: 'Successfully retrieved secrets'
        };
        
    } catch (error) {
        logger.error('Error retrieving secret', { error });
        return {
            statusCode: 500,
            body: `Error: ${error}`
        };
    }
};
```

**Java**  
有关完整示例和设置说明，请参阅[参数实用程序文档](https://docs.powertools.aws.dev/lambda/java/latest/utilities/parameters/)。
使用 Powertools for AWS Lambda 参数实用程序从 Secrets Manager 中检索密钥。  

```
import software.amazon.lambda.powertools.logging.Logging;
import software.amazon.lambda.powertools.parameters.SecretsProvider;
import software.amazon.lambda.powertools.parameters.ParamManager;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;

public class SecretHandler implements RequestHandler<Object, String> {
    
    private final SecretsProvider secretsProvider = ParamManager.getSecretsProvider();
    
    @Logging
    @Override
    public String handleRequest(Object input, Context context) {
        try {
            // Get secret with caching (default TTL: 5 seconds)
            String secretValue = secretsProvider.get("my-secret-name");
            
            // Get secret with custom TTL (300 seconds)
            String secretWithTtl = secretsProvider.withMaxAge(300).get("my-secret-name");
            
            // Get secret and transform JSON
            MySecret secretJson = secretsProvider.get("my-json-secret", MySecret.class);
            
            return "Successfully retrieved secrets";
            
        } catch (Exception e) {
            return "Error retrieving secret: " + e.getMessage();
        }
    }
    
    public static class MySecret {
        // Define your secret structure here
    }
}
```

**.NET**  
有关完整示例和设置说明，请参阅[参数实用程序文档](https://docs.aws.amazon.com/powertools/typescript/latest/features/parameters/)。
使用 Powertools for AWS Lambda 参数实用程序从 Secrets Manager 中检索密钥。  

```
using AWS.Lambda.Powertools.Logging;
using AWS.Lambda.Powertools.Parameters;
using Amazon.Lambda.Core;

[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]

public class Function
{
    private readonly ISecretsProvider _secretsProvider;
    
    public Function()
    {
        _secretsProvider = ParametersManager.SecretsProvider;
    }
    
    [Logging]
    public async Task<string> FunctionHandler(object input, ILambdaContext context)
    {
        try
        {
            // Get secret with caching (default TTL: 5 seconds)
            var secretValue = await _secretsProvider.GetAsync("my-secret-name");
            
            // Get secret with custom TTL
            var secretWithTtl = await _secretsProvider.WithMaxAge(TimeSpan.FromMinutes(5))
                .GetAsync("my-secret-name");
            
            // Get secret and transform JSON
            var secretJson = await _secretsProvider.GetAsync<MySecret>("my-json-secret");
            
            return "Successfully retrieved secrets";
        }
        catch (Exception e)
        {
            return $"Error retrieving secret: {e.Message}";
        }
    }
    
    public class MySecret
    {
        // Define your secret structure here
    }
}
```

### 设置和权限
<a name="lambda-secrets-manager-powertools-setup"></a>

要使用参数实用程序，您需要：

1. 为您的运行时安装 Powertools for AWS Lambda。有关更多信息，请参阅 [Powertools for AWS Lambda](powertools-for-lambda.md)。

1. 向您的函数的执行角色添加必要的 IAM 权限。有关详细信息，请参阅[在 AWS Lambda 中管理权限](lambda-permissions.md)。

1. 通过[环境变量](configuration-envvars.md)配置任何可选设置。

所需的 IAM 权限与扩展方法相同。该实用程序将根据您的配置自动处理缓存和对 Secrets Manager 的 API 调用。