カスタムIDプロバイダー(Lambda)を使ったAWS Transfer Family (FTP) をCloudFormationで作成する

2022.05.24

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

いわさです。

CloudFormationでAWS Transfer Familyを作成する機会があったのですが、FTPかつLambdaカスタムIDプロバイダーという変わった構成で構築する機会があり、あまりCloudFormationのサンプルがなかったので記事にしてみました。

カスタムIDプロバイダーでLambdaを直接統合する機能は以前紹介しています。

テンプレート

全体像のリポジトリはこちらになります。

VPC一式とTransfer Familyで構成されています。
Transfer FamilyはFTPを使っていて、ドメインはS3です。
カスタムIDプロバイダー用にAPI Gatewayを使わずに直接Lambdaを使っています。 Lambdaから返却するポリシーのベースは前述の記事を参考に作成しましたが、ポリシーの内容はFTPでアクセスするS3バケットの情報やIAMロールを含めています。

この記事ではポイントとなる部分を抜粋して紹介します。
なお、先程のリポジトリにデプロイしたらそのまま動くテンプレートをアップしています。

Transfer::Server

前提として、Transfer FamilyをFTPでアクセスする際は、パブリックインターネットアクセスが出来ません。VPCを使ったプライベートアクセスが前提となっています。
EndpointTypeEndpointDetailsにはVPC関連の情報を設定します。
そして、カスタムIDプロバイダー部分ですが、IdentityProviderTypeAWS_LAMBDAを、IdentityProviderDetails.FunctionにLambda関数のARNを指定します。

細かい制御はLambda関数側で行います。

  FTP:
    Type: AWS::Transfer::Server
    Properties: 
      Domain: S3
      Protocols: 
        - FTP
      EndpointType: VPC
      EndpointDetails: 
        SecurityGroupIds: 
          - !Ref SecurityGroup
        SubnetIds: 
          - !Ref PublicSubnet
        VpcId: !Ref VPC
      IdentityProviderType: AWS_LAMBDA
      IdentityProviderDetails: 
        Function: !GetAtt lambdaFunction.Arn

Lambda

ランタイムはPython3.8を使っています。
冒頭の記事でもLambda関数で返却すべき内容を紹介していますが、ユーザー名とパスワードをここでは固定にしています。
そして、関数の返却値として、IAMロールのARNとホームディレクトリーとなるS3バケットの情報を展開しています。

また、リソースベースポリシーでTransferからのInvokeFunctionを許可することを忘れないようにしましょう。

  lambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub ${AWS::StackName}-auth-lambda
      Role: !GetAtt lambdaExecutionRole.Arn
      Handler: index.lambda_handler
      Runtime: python3.8
      Code:
        ZipFile: !Sub
        - |
          def lambda_handler(event, context):
            if event["username"] == "hoge" and event["password"] == "fuga":
                return { 
                  'Role': '${lambdaReturnRole}',
                  'Policy': '',
                  'HomeDirectory': '/${bucketName}/'
                }
            else:
                return {}
        - { lambdaReturnRole: !GetAtt lambdaReturnRole.Arn, bucketName: !Ref S3Bucket }
  lambdaFunctionInvokePermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !GetAtt lambdaFunction.Arn
      Principal: transfer.amazonaws.com
      SourceArn: !GetAtt FTP.Arn

動作確認

Transfer Familyを構築したら、まずはコンソール上でテストを行ってみると良いです。
カスタムIDプロバイダーの権限忘れなどはここでデバッグすることが出来ます。

カスタムIDプロバイダーは期待どおり動作しました。
しかし、実際にFTPクライアントからの接続も行いましょう。

先程のテストコンソールでは実際のインフラ上で接続を行うわけではないので、セキュリティグループの設定ミスなどは発見出来ません。
ここでは、スタックで作成されたVPC上にEC2インスタンスを構築し、FTPクライアントで接続してファイルアップロードをしてみます。

[ec2-user@ip-10-0-0-241 ~]$ sudo yum -y install ftp
読み込んだプラグイン:extras_suggestions, langpacks, priorities, update-motd
amzn2-core                                                                                                                                                         | 3.7 kB  00:00:00     
依存性の解決をしています
--> トランザクションの確認を実行しています。
---> パッケージ ftp.x86_64 0:0.17-67.amzn2.0.2 を インストール
--> 依存性解決を終了しました。

依存性を解決しました

==========================================================================================================================================================================================
 Package                               アーキテクチャー                         バージョン                                             リポジトリー                                  容量
==========================================================================================================================================================================================
インストール中:
 ftp                                   x86_64                                   0.17-67.amzn2.0.2                                      amzn2-core                                    61 k

トランザクションの要約
==========================================================================================================================================================================================
インストール  1 パッケージ

総ダウンロード容量: 61 k
インストール容量: 96 k
Downloading packages:
ftp-0.17-67.amzn2.0.2.x86_64.rpm                                                                                                                                   |  61 kB  00:00:00     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  インストール中          : ftp-0.17-67.amzn2.0.2.x86_64                                                                                                                              1/1 
  検証中                  : ftp-0.17-67.amzn2.0.2.x86_64                                                                                                                              1/1 

インストール:
  ftp.x86_64 0:0.17-67.amzn2.0.2                                                                                                                                                          

完了しました!

FTPクライアントをインストールしたので、実際に接続してみましょう。
FTPのエンドポイントと、先程デプロイされたLambda関数内でハードコーディングされているユーザー名とパスワードを使用します。

[ec2-user@ip-10-0-0-241 ~]$ ftp vpce-019625ad18283af2c-xe213jcp.vpce-svc-0cee93c75e789f90b.ap-northeast-1.vpce.amazonaws.com
Connected to vpce-019625ad18283af2c-xe213jcp.vpce-svc-0cee93c75e789f90b.ap-northeast-1.vpce.amazonaws.com (10.0.0.173).
220 Service ready for new user.
Name (vpce-019625ad18283af2c-xe213jcp.vpce-svc-0cee93c75e789f90b.ap-northeast-1.vpce.amazonaws.com:ec2-user): hoge
331 User name okay, need password for hoge.
Password:
230 User logged in, proceed.
Remote system type is UNIX.

ftp> bi
200 Command TYPE okay.
ftp> put hoge.txt
local: hoge.txt remote: hoge.txt
227 Entering Passive Mode (10,0,0,173,32,3)
150 File status okay; about to open data connection.
226 Transfer complete.

S3コンソール上でも確認してみます。

期待どおりアップロードされていました。

さいごに

本日はCloudFormationを使って、FTP+LambdaカスタムIDプロバイダーなTransfer Familyを構築してみました。
SFTPやAPI Gateway、Service Managedな構成がウェブ上では非常に多く、ちょっとトライ&エラーで苦労しました。次回以降はこのテンプレートを活用して、FTPクライアント側の検証なども紹介したいと思います。