Amazon Lambda 用于整合您的身份提供商 - Amazon Transfer Family
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅 中国的 Amazon Web Services 服务入门 (PDF)

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

Amazon Lambda 用于整合您的身份提供商

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

注意

在创建使用 Lambda 作为身份提供程序的 Transfer Family 服务器之前,必须创建该函数。有关示例 Lambda 函数,请参阅 Lambda 函数示例。或者,您可以部署使用其中一个的 CloudFormation 堆栈Lambda 函数模板。此外,请确保您的 Lambda 函数使用信任 Transfer Family 的基于资源的策略。有关策略示例,请参阅Lambda 资源策略

  1. 打开Amazon Transfer Family 控制台

  2. 选择“创建服务器”以打开“创建服务器”页面。在“选择身份提供程序”中,选择“自定义身份提供程序”,如以下屏幕截图所示。

    
                选择身份提供程序控制台部分,其中已选中自定义身份提供程序。还选择了默认值,即用户可以使用其密码或密钥进行身份验证。
    注意

    只有启用 SFTP 作为 Transfer Family 服务器的协议之一时,才能选择身份验证方法。

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

  4. 对于Amazon Lambda 函数,选择 Lambda 函数名称。

  5. 填写其余的方框,然后选择“创建服务器”。有关创建服务器的其余步骤的详细信息,请参阅 配置 SFTP、FTPS 或 FTP 服务器端点

Lambda 资源策略

您必须有一个引用 Transfer Family 服务器和 Lambda ARN 的策略。例如,您可以将以下策略与连接到您的身份提供程序的 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:transfer:region:account-id:function:my-lambda-auth-function", "Condition": { "ArnLike": { "AWS:SourceArn": "arn:aws:transfer:region:account-id:server/server-id" } } } ] }"
注意

在该示例中,将每个用户输入占位符 替换为您自己的信息。

事件消息结构

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

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

其中 usernamepassword 是发送到服务器的登录凭证的值。

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

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 函数

要实现不同的身份验证策略,请编辑 Lambda 函数。为了帮助您满足应用程序的需求,您可以部署堆 CloudFormation 栈。有关更多信息, Amazon Lambda 开发人员指南 通过 Node.js 构建 Lambda 函数

Lambda 函数模板

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

注意

您可以通过在模板中指定启用 FIPS 的安全策略 Amazon CloudFormation 来创建启用 FIPS 的 Amazon Transfer Family 服务器。有关可用安全策略的描述,请参见 Amazon Transfer Family 服务器的安全策略

创建用于身份验证的 Amazon CloudFormation 堆栈
  1. 打开 Amazon CloudFormation 控制台,网址为 https://console.aws.amazon.com/cloudformation

  2. 按照Amazon CloudFormation 用户指南中的选择 Amazon CloudFormation 堆栈模板中的使用现有模板部署堆栈的说明进行操作。

  3. 使用以下模板之一来创建在 Transfer Family 中进行身份验证的 Lambda 函数。

    • 经典 (Amazon Cognito) 堆栈模板

      用于在中创建用作 Amazon Lambda 自定义身份提供者的基本模板 Amazon Transfer Family。它会针对 Amazon Cognito 进行身份验证以进行基于密码的身份验证,如果使用基于公钥的身份验证,则会从 Amazon S3 存储桶返回公钥。部署后,您可以修改 Lambda 函数代码以执行不同的操作。

    • Amazon Secrets Manager 堆栈模板

      与 Amazon Transfer Family 服务器 Amazon Lambda 一起使用的基本模板,用于将 Secrets Manager 作为身份提供者进行集成。它根据格式aws/transfer/server-id/username的条目 Amazon Secrets Manager 进行身份验证。此外,该密钥必须包含返回给 Transfer Family 的所有用户属性的键值对。部署后,您可以修改 Lambda 函数代码以执行不同的操作。

    • Okta 堆栈模板:与 Amazon Transfer Family 服务器 Amazon Lambda 一起使用,将 Okta 作为自定义身份提供程序集成的基本模板。

    • Okta-MFA 堆栈模板:一种基本模板,用于 Amazon Lambda 与 Amazon Transfer Family 服务器一起使用,将 Okta 与 MultiFactor 身份验证集成,作为自定义身份提供商。

    • Azure Active Directory 模板:此堆栈的详细信息在博客文章中描述了使用 Azure 活动目录进行身份验证和 Amazon Lambda。 Amazon Transfer Family

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

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

有效的 Lambda 值

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

描述 必填

Role

指定控制用户对 Amazon S3 存储桶或 Amazon EFS 文件系统访问权限的 IAM 角色的 Amazon Resource Name (ARN) 。附加到此角色的策略确定在将文件传入和传出 Amazon S3 存储桶或 Amazon EFS 文件系统时要为用户提供的访问权限级别。IAM 角色还应包含一个信任关系,从而允许服务器在为用户的传输请求提供服务时访问您的资源。

有关建立信任关系的详细信息,请参阅 建立信任关系

必需

PosixProfile

完整的 POSIX 身份,包括用户 ID (Uid)、组 ID (Gid) 和任何辅助组 ID (SecondaryGids),用于控制用户对 Amazon EFS 文件系统的访问。POSIX 权限针对文件系统中的文件和目录设置,用于确定用户在将文件传入和传出 Amazon EFS 文件系统时获得的访问权限级别。

Amazon EFS 后备存储为必填项

PublicKeys

对此用户有效的 SSH 公钥值列表。空列表表示这不是有效的登录名。密码认证期间不得返回。

可选

Policy

适用于您的用户的会话策略,可让您跨多个用户使用相同的 IAM 角色。此策略将用户的访问范围缩小至 Amazon S3 存储桶的一部分。

可选

HomeDirectoryType

您希望用户在登录服务器时,用户主目录的登录目录(文件夹)的类型。

  • 如果您将其设置为 PATH,则用户将在其文件传输协议客户端中原样看到 Amazon S3 存储桶或 Amazon EFS 路径。

  • 如果您将其设置为 LOGICAL,则必须在HomeDirectoryDetails参数中提供映射,以使 Amazon S3 或 Amazon EFS 路径对用户可见。

可选

HomeDirectoryDetails

逻辑目录映射指定哪些 Amazon S3 或 Amazon EFS 路径和密钥应对您的用户可见,以及使其对用户可见的方式。您需要指定EntryTarget对,其中 Entry 显示如何使路径可见,Target 是实际的 Amazon S3 或 Amazon EFS 路径。

如果 HomeDirectoryType 值为 LOGICAL,则为必填项

HomeDirectory

用户使用客户端登录服务器时的登录目录。

可选

注意

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

Lambda 函数示例

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

注意

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

Logical 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. 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: '/fs-faa1a123' // Not required, defaults to '/' }; // 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 示例函数为拥有逻辑主目录的用户提供了详细信息。

# 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': '/fs-fs-faa1a123', '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

测试您的配置

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

Console
使用 Amazon Transfer Family 控制台测试您的配置
  1. 打开Amazon Transfer Family 控制台

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

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

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

CLI
使用 Amazon CLI 测试您的配置
  1. 运行 test-identity-provider 命令。如后续步骤所述,将 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
  2. 输入服务器 ID。

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

  4. 如果在部署 Amazon 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": "" }