

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# AWS Lambda 用于整合您的身份提供商
<a name="custom-lambda-idp"></a>

本主题介绍如何创建连接到您的自定义身份提供商的 AWS Lambda 函数。您可以使用任何自定义身份提供商，例如 Okta、Secrets Manager 或包含授权和身份验证逻辑的自定义数据存储。 OneLogin

对于大多数用例，配置自定义身份提供商的推荐方法是使用[自定义身份提供商解决方案](custom-idp-toolkit.md)。

**注意**  
在创建使用 Lambda 作为身份提供程序的 Transfer Family 服务器之前，必须创建该函数。有关示例 Lambda 函数，请参阅 [Lambda 函数示例](#lambda-auth-examples)。或者，您可以部署使用其中一个的 CloudFormation 堆栈[Lambda 函数模板](#lambda-idp-templates)。此外，请确保您的 Lambda 函数使用信任 Transfer Family 的基于资源的策略。有关策略示例，请参阅 [Lambda 资源策略](#lambda-resource-policy)。

1. 打开 [AWS Transfer Family 控制台](https://console.aws.amazon.com/transfer/)。

1. 选择“**创建服务器**”以打开“**创建服务器**”页面。**在“选择身份提供程序**”中，选择“**自定义身份提供程序**”，如以下屏幕截图所示。  
![\[选择身份提供程序控制台部分，其中已选中自定义身份提供程序。还选择了默认值，即用户可以使用其密码或密钥进行身份验证。\]](http://docs.aws.amazon.com/zh_cn/transfer/latest/userguide/images/custom-lambda-console.png)
**注意**  
只有启用 SFTP 作为 Transfer Family 服务器的协议之一时，才能选择身份验证方法。

1. 确保选择了默认值 “** AWS Lambda 用于连接您的身份提供商**”。

1. 对于 **AWS Lambda 函数**，选择 Lambda 函数名称。

1. 填写其余的方框，然后选择“**创建服务器**”。有关创建服务器的其余步骤的详细信息，请参阅 [配置 SFTP、FTPS 或 FTP 服务器端点](tf-server-endpoint.md)。

## Lambda 资源策略
<a name="lambda-resource-policy"></a>

您必须有一个引用 Transfer Family 服务器和 Lambda ARNs 的策略。例如，您可以将以下策略与连接到您的身份提供程序的 Lambda 函数一起使用。策略会以 JSON 格式转义为字符串。

****  

```
"Policy":
"{\"Version\":\"2012-10-17\",
\"Id\":\"default\",
\"Statement\":[
  {\"Sid\":\"AllowTransferInvocation\",
  \"Effect\":\"Allow\",
  \"Principal\":{\"Service\":\"transfer.amazonaws.com\"},
  \"Action\":\"lambda:InvokeFunction\",
  \"Resource\":\"arn:aws:lambda:region:123456789012:function:my-lambda-auth-function\",
  \"Condition\":{\"ArnLike\":{\"AWS:SourceArn\":\"arn:aws:transfer:region:123456789012:server/server-id\"}}}
]}"
```

**注意**  
在上面的示例政策中，用您自己的信息替换每项*user input placeholder*政策。

## 事件消息结构
<a name="event-message-structure"></a>

来自自定义 IDP 的 SFTP 服务器发送给授权程序 Lambda 函数的事件消息结构如下所示。

```
{
    "username": "value",
    "password": "value",
    "protocol": "SFTP",
    "serverId": "s-abcd123456",
    "sourceIp": "192.168.0.100"
}
```

其中 `username` 和 `password` 是发送到服务器的登录凭证的值。

例如，您可输入以下连接命令。

```
sftp bobusa@server_hostname
```

系统会提示您输入密码：

```
Enter password:
    mysecretpassword
```

您可以在 Lambda 函数中进行检查，方法是在 Lambda 函数中打印传递的事件。此部分与以下文本块类似。

```
{
    "username": "bobusa",
    "password": "mysecretpassword",
    "protocol": "SFTP",
    "serverId": "s-abcd123456",
    "sourceIp": "192.168.0.100"
}
```

FTP 和 FTPS 的事件结构类似：唯一的区别是 `protocol` 参数会使用这些值，而不是 SFTP。

## 用于身份验证的 Lambda 函数
<a name="authentication-lambda-examples"></a>

要实现不同的身份验证策略，请编辑 Lambda 函数。为了帮助您满足应用程序的需求，您可以部署堆 CloudFormation 栈。有关更多信息，请参见 [AWS Lambda 开发人员指南](https://docs.aws.amazon.com/lambda/latest/dg/welcome.html) 或 [ 通过 Node.js 构建 Lambda 函数](https://docs.aws.amazon.com/lambda/latest/dg/lambda-nodejs.html)。

**Topics**
+ [有效的 Lambda 值](#lambda-valid-values)
+ [Lambda 函数示例](#lambda-auth-examples)
+ [测试您的配置](#authentication-test-configuration)
+ [Lambda 函数模板](#lambda-idp-templates)

### 有效的 Lambda 值
<a name="lambda-valid-values"></a>

下表详细介绍了 Transfer Family 接受的用于自定义身份提供程序的 Lambda 函数的值。


|  值  |  说明  |  必填  | 
| --- | --- | --- | 
|  `Role`  |  指定控制用户对 Amazon S3 存储桶或 Amazon EFS 文件系统访问权限的 IAM 角色的 Amazon Resource Name (ARN) 。附加到此角色的策略确定在将文件传入和传出 Amazon S3 存储桶或 Amazon EFS 文件系统时要为用户提供的访问权限级别。IAM 角色还应包含一个信任关系，从而允许服务器在为用户的传输请求提供服务时访问您的资源。 有关建立信任关系的详细信息，请参阅 [建立信任关系](requirements-roles.md#establish-trust-transfer)。  |  必需  | 
|  `PosixProfile`  |  控制用户访问您的 Amazon EFS 文件系统的完整 POSIX 身份，包括用户 ID IDs (`Uid``Gid``SecondaryGids`)、群组 ID () 和任何辅助群组 ()。POSIX 权限针对文件系统中的文件和目录设置，用于确定用户在将文件传入和传出 Amazon EFS 文件系统时获得的访问权限级别。  |  Amazon EFS 后备存储为必填项  | 
|  `PublicKeys`  |  对此用户有效的 SSH 公钥值列表。空列表表示这不是有效的登录名。密码认证期间不得返回。  |  可选  | 
|  `Policy`  |  适用于您的用户的会话策略，可让您跨多个用户使用相同的 IAM 角色。此策略将用户的访问范围缩小至 Amazon S3 存储桶的一部分。有关使用自定义身份提供商的会话策略的更多信息，请参阅本主题中的会话策略示例。  |  可选  | 
|  `HomeDirectoryType`  |  您希望用户在登录服务器时，用户主目录的登录目录（文件夹）的类型。 [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/transfer/latest/userguide/custom-lambda-idp.html)  |  可选  | 
|  `HomeDirectoryDetails`  |  逻辑目录映射指定哪些 Amazon S3 或 Amazon EFS 路径和密钥应对您的用户可见，以及使其对用户可见的方式。您需要指定 `Entry` 和 `Target` 对，其中 `Entry` 显示如何使路径可见，`Target` 是实际的 Amazon S3 或 Amazon EFS 路径。  |  如果 `HomeDirectoryType` 值为 `LOGICAL`，则为必填项  | 
|  `HomeDirectory`  |  用户使用客户端登录服务器时的登录目录。格式取决于您的存储后端： [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/transfer/latest/userguide/custom-lambda-idp.html)  路径中必须包含存储桶名称或 Amazon EFS 文件系统 ID。省略此信息将导致文件传输过程中出现 “找不到文件” 错误。   |  可选  | 

**注意**  
`HomeDirectoryDetails` 是 JSON 映射的字符串表示形式。这与 `PosixProfile` 形成鲜明对比，后者是一个实际的 JSON 映射对象，`PublicKeys` 是一个字符串的 JSON 数组。有关特定语言的详细信息，请参阅代码示例。

**HomeDirectory 格式要求**  
使用`HomeDirectory`参数时，请确保包含完整的路径格式：  
**对于 Amazon S3 存储：**请务必使用以下格式包含存储桶名称 `/bucket-name/path`
**对于 Amazon EFS 存储：**请务必使用以下格式包含文件系统 ID `/fs-12345/path`
“找不到文件” 错误的一个常见原因是`HomeDirectory`路径中省略了存储桶名称或 EFS 文件系统 ID。如果设置`HomeDirectory`为`/`不带存储标识符，则会导致身份验证成功，但文件操作失败。

### Lambda 函数示例
<a name="lambda-auth-examples"></a>

本节介绍了一些 NodeJS 和 Python 中的 Lambda 函数示例。

**注意**  
在这些示例中，用户、角色、POSIX 配置文件、密码和主目录详细信息均为示例，必须将其替换为实际值。

------
#### [ Logical home directory, NodeJS ]

[以下 NodeJS 示例函数为拥有逻辑主目录](https://docs.aws.amazon.com/transfer/latest/userguide/logical-dir-mappings.html)的用户提供了详细信息。

```
// GetUserConfig Lambda

exports.handler = (event, context, callback) => {
  console.log("Username:", event.username, "ServerId: ", event.serverId);

  var response;
  // Check if the username presented for authentication is correct. This doesn't check the value of the server ID, only that it is provided.
  if (event.serverId !== "" && event.username == 'example-user') {
    var homeDirectoryDetails = [
      {
        Entry: "/",
        Target: "/fs-faa1a123"
      }
    ];
    response = {
      Role: 'arn:aws:iam::123456789012:role/transfer-access-role', // The user is authenticated if and only if the Role field is not blank
      PosixProfile: {"Gid": 65534, "Uid": 65534}, // Required for EFS access, but not needed for S3
      HomeDirectoryDetails: JSON.stringify(homeDirectoryDetails),
      HomeDirectoryType: "LOGICAL",
    };

    // Check if password is provided
    if (!event.password) {
      // If no password provided, return the user's SSH public key
      response['PublicKeys'] = [ "ssh-rsa abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" ];
    // Check if password is correct
    } else if (event.password !== 'Password1234') {
      // Return HTTP status 200 but with no role in the response to indicate authentication failure
      response = {};
    }
  } else {
    // Return HTTP status 200 but with no role in the response to indicate authentication failure
    response = {};
  }
  callback(null, response);
};
```

------
#### [ Path-based home directory, NodeJS ]

以下 NodeJS 示例函数为拥有基于路径的主目录的用户提供了详细信息。

```
// GetUserConfig Lambda

exports.handler = (event, context, callback) => {
  console.log("Username:", event.username, "ServerId: ", event.serverId);

  var response;
  // Check if the username presented for authentication is correct. This doesn't check the value of the server ID, only that it is provided.
  // There is also event.protocol (one of "FTP", "FTPS", "SFTP") and event.sourceIp (e.g., "127.0.0.1") to further restrict logins.
  if (event.serverId !== "" && event.username == 'example-user') {
    response = {
      Role: 'arn:aws:iam::123456789012:role/transfer-access-role', // The user is authenticated if and only if the Role field is not blank
      Policy: '', // Optional, JSON stringified blob to further restrict this user's permissions
      // HomeDirectory format depends on your storage backend:
      // For S3: '/bucket-name/user-home-directory' (e.g., '/my-transfer-bucket/users/john')
      // For EFS: '/fs-12345/user-home-directory' (e.g., '/fs-faa1a123/users/john')
      HomeDirectory: '/my-transfer-bucket/users/example-user' // S3 example - replace with your bucket name
      // HomeDirectory: '/fs-faa1a123/users/example-user' // EFS example - uncomment for EFS
    };
    
    // Check if password is provided
    if (!event.password) {
      // If no password provided, return the user's SSH public key
     response['PublicKeys'] = [ "ssh-rsa abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" ];
    // Check if password is correct
    } else if (event.password !== 'Password1234') {
      // Return HTTP status 200 but with no role in the response to indicate authentication failure
      response = {};
    } 
  } else {
    // Return HTTP status 200 but with no role in the response to indicate authentication failure
    response = {};
  }
  callback(null, response);
};
```

------
#### [ Logical home directory, Python ]

以下 Python 示例函数为拥有[逻辑主目录](https://docs.aws.amazon.com/transfer/latest/userguide/logical-dir-mappings.html)的用户提供了详细信息。

```
# GetUserConfig Python Lambda with LOGICAL HomeDirectoryDetails
import json

def lambda_handler(event, context):
  print("Username: {}, ServerId: {}".format(event['username'], event['serverId']))

  response = {}

  # Check if the username presented for authentication is correct. This doesn't check the value of the server ID, only that it is provided.
  if event['serverId'] != '' and event['username'] == 'example-user':
    homeDirectoryDetails = [
      {
        'Entry': '/',
        'Target': '/fs-faa1a123'
      }
    ]
    response = {
      'Role': 'arn:aws:iam::123456789012:role/transfer-access-role', # The user will be authenticated if and only if the Role field is not blank
      'PosixProfile': {"Gid": 65534, "Uid": 65534}, # Required for EFS access, but not needed for S3
      'HomeDirectoryDetails': json.dumps(homeDirectoryDetails),
      'HomeDirectoryType': "LOGICAL"
    }

    # Check if password is provided
    if event.get('password', '') == '':
      # If no password provided, return the user's SSH public key
     response['PublicKeys'] = [ "ssh-rsa abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" ]
    # Check if password is correct
    elif event['password'] != 'Password1234':
      # Return HTTP status 200 but with no role in the response to indicate authentication failure
      response = {}
  else:
    # Return HTTP status 200 but with no role in the response to indicate authentication failure
    response = {}

  return response
```

------
#### [ Path-based home directory, Python ]

以下 Python 示例函数为拥有基于路径的主目录的用户提供了详细信息。

```
# GetUserConfig Python Lambda with PATH HomeDirectory

def lambda_handler(event, context):
  print("Username: {}, ServerId: {}".format(event['username'], event['serverId']))

  response = {}

  # Check if the username presented for authentication is correct. This doesn't check the value of the server ID, only that it is provided.
  # There is also event.protocol (one of "FTP", "FTPS", "SFTP") and event.sourceIp (e.g., "127.0.0.1") to further restrict logins.
  if event['serverId'] != '' and event['username'] == 'example-user':
    response = {
      'Role': 'arn:aws:iam::123456789012:role/transfer-access-role', # The user will be authenticated if and only if the Role field is not blank
      'Policy': '', #  Optional, JSON stringified blob to further restrict this user's permissions
      # HomeDirectory format depends on your storage backend:
      # For S3: '/bucket-name/user-home-directory' (e.g., '/my-transfer-bucket/users/john')
      # For EFS: '/fs-12345/user-home-directory' (e.g., '/fs-faa1a123/users/john')
      'HomeDirectory': '/my-transfer-bucket/users/example-user', # S3 example - replace with your bucket name
      # 'HomeDirectory': '/fs-faa1a123/users/example-user', # EFS example - uncomment for EFS
      'HomeDirectoryType': "PATH" # Not strictly required, defaults to PATH
    }
    
    # Check if password is provided
    if event.get('password', '') == '':
      # If no password provided, return the user's SSH public key
     response['PublicKeys'] = [ "ssh-rsa abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" ]
    # Check if password is correct
    elif event['password'] != 'Password1234':
      # Return HTTP status 200 but with no role in the response to indicate authentication failure
      response = {}
  else:
    # Return HTTP status 200 but with no role in the response to indicate authentication failure
    response = {}

  return response
```

------

### 测试您的配置
<a name="authentication-test-configuration"></a>

创建自定义身份提供程序后，应测试您的配置。

------
#### [ Console ]

**使用 AWS Transfer Family 控制台测试您的配置**

1. 打开 [AWS Transfer Family 控制台](https://console.aws.amazon.com/transfer/)。

1. 在“**服务器**”页面上，选择您的新服务器，选择“**操作**”，然后选择“**测试**”。

1. 输入您在部署 CloudFormation 堆栈时设置的**用户名**和**密码**文本。如果您保留默认选项，则用户名为 `myuser`，密码为 `MySuperSecretPassword`。

1. 如果在部署 CloudFormation 堆栈时设置了**源 IP** 地址，请选择**服务器协议**并输入源 IP 地址。

------
#### [ CLI ]

**使用 AWS CLI 测试您的配置**

1. 运行 [test-identity-provider](https://docs.aws.amazon.com/cli/latest/reference/transfer/test-identity-provider.html) 命令。如后续步骤所述，将 `user input placeholder` 用您自己的信息进行替换。

   ```
   aws transfer test-identity-provider --server-id s-1234abcd5678efgh --user-name myuser --user-password MySuperSecretPassword --server-protocol FTP --source-ip 127.0.0.1
   ```

1. 输入服务器 ID。

1. 输入您在部署 CloudFormation 堆栈时设置的用户名和密码。如果您保留默认选项，则用户名为 `myuser`，密码为 `MySuperSecretPassword`。

1. 如果在部署 CloudFormation 堆栈时设置了服务器协议和源 IP 地址，请输入它们。

------

如果用户身份验证成功，则测试将返回 `StatusCode: 200` HTTP 响应、一个空字符串 `Message: ""`（否则将包含失败原因）和一个 `Response` 字段。

**注意**  
 在下面的响应示例中，`Response` 字段是一个已经 “字符串化” 的 JSON 对象（转换为可在程序中使用的扁平 JSON 字符串），其中包含用户角色和权限的详细信息。

```
{
    "Response":"{\"Policy\":\"{\\\"Version\\\":\\\"2012-10-17\\\",\\\"Statement\\\":[{\\\"Sid\\\":\\\"ReadAndListAllBuckets\\\",\\\"Effect\\\":\\\"Allow\\\",\\\"Action\\\":[\\\"s3:ListAllMybuckets\\\",\\\"s3:GetBucketLocation\\\",\\\"s3:ListBucket\\\",\\\"s3:GetObjectVersion\\\",\\\"s3:GetObjectVersion\\\"],\\\"Resource\\\":\\\"*\\\"}]}\",\"Role\":\"arn:aws:iam::000000000000:role/MyUserS3AccessRole\",\"HomeDirectory\":\"/\"}",
    "StatusCode": 200,
    "Message": ""
}
```

### Lambda 函数模板
<a name="lambda-idp-templates"></a>

您可以部署使用 Lambda 函数进行身份验证的 CloudFormation 堆栈。我们提供了多个模板，可使用登录凭证对您的用户进行身份验证和授权。您可以修改这些模板或 AWS Lambda 代码以进一步自定义用户访问权限。

**注意**  
您可以通过在模板中指定启用 FIPS 的安全策略 CloudFormation 来创建启用 FIPS 的 AWS Transfer Family 服务器。有关可用安全策略的描述，请参见 [AWS Transfer Family 服务器的安全策略](security-policies.md) 

**创建用于身份验证的 CloudFormation 堆栈**

1. 在 [https://console.aws.amazon.com/cloudformat](https://console.aws.amazon.com/cloudformation/) ion 上打开 CloudFormation 控制台。

1. 按照*AWS CloudFormation 用户指南*中的[选择 CloudFormation 堆栈模板中的使用现有模板部署堆栈](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-using-console-create-stack-template.html)的说明进行操作。

1. 使用以下模板之一来创建在 Transfer Family 中进行身份验证的 Lambda 函数。
   + [经典 (Amazon Cognito) 堆栈模板](https://s3.amazonaws.com/aws-transfer-resources/custom-idp-templates/aws-transfer-custom-idp-basic-lambda-cognito-s3.template.yml)

     用于在中创建用作 AWS Lambda 自定义身份提供者的基本模板 AWS Transfer Family。它会针对 Amazon Cognito 进行身份验证以进行基于密码的身份验证，如果使用基于公钥的身份验证，则会从 Amazon S3 存储桶返回公钥。部署后，您可以修改 Lambda 函数代码以执行不同的操作。
   + [AWS Secrets Manager 堆栈模板](https://s3.amazonaws.com/aws-transfer-resources/custom-idp-templates/aws-transfer-custom-idp-secrets-manager-lambda.template.yml)

     与 AWS Transfer Family 服务器 AWS Lambda 一起使用的基本模板，用于将 Secrets Manager 作为身份提供者进行集成。它根据格式`aws/transfer/server-id/username`的条目 AWS Secrets Manager 进行身份验证。此外，该密钥必须包含返回给 Transfer Family 的所有用户属性的键值对。部署后，您可以修改 Lambda 函数代码以执行不同的操作。
   + [Okta 堆栈模板](https://s3.amazonaws.com/aws-transfer-resources/custom-idp-templates/aws-transfer-custom-idp-okta-lambda.template.yml)：与 AWS Transfer Family 服务器 AWS Lambda 一起使用，将 Okta 作为自定义身份提供程序集成的基本模板。
   + [Okta-MFA 堆栈模板](https://s3.amazonaws.com/aws-transfer-resources/custom-idp-templates/aws-transfer-custom-idp-okta-mfa-lambda.template.yml)：一种基本模板，可与 AWS Transfer Family 服务器 AWS Lambda 一起使用，将 Okta 与多因素身份验证集成，作为自定义身份提供商。
   + [Azure Active Directory 模板](https://s3.amazonaws.com/aws-transfer-resources/custom-idp-templates/aws-transfer-custom-idp-basic-lambda-azure-ad.template.yml)：博客文章[使用 Azure 活动目录进行 AWS Transfer Family 身份验证](https://aws.amazon.com/blogs/storage/authenticating-to-aws-transfer-family-with-azure-active-directory-and-aws-lambda/)中描述了此堆栈的详细信息。 AWS Lambda

   部署堆栈后，您可以在 CloudFormation 控制台的**输出**选项卡上查看有关堆栈的详细信息。

   部署其中一个堆栈是将自定义身份提供程序集成到 Transfer Family 工作流程的最简单方法。