AWS Transfer Familyで簡単FTPサーバ

FTP???と言われてしまいそうですが、とある案件でシステム間連携をする際にどうしてもFTPで接続したいんです。という要望を頂きまして構築した時のメモです。
DirectConnectで接続した先の環境からのFTP接続なので、通信経路のセキュリティ的な意味ではまあいっか?と。。。こちらで用意したFTPサーバに、システム間連携先のお客様サーバから接続してくるという構成です。

SFTP(もしくはFTPS)に変更してもそんなに工数かからないと思うのですが。SFTPにしちゃえばいいのに。。。というお話はしつつAWS Transfer Familyを使って構築していきます。

サーバを作成を押してインスタンスを作成します。

要件でFTPとなっているので、FTPにチェックを入れて次へ

IDプロバイダー(FTPする際のユーザ管理)で、管理は楽にしたいのでサービスマネージドを選ぼうとすると、FTPSとFTPでは選べないと言われます。
早速イライラしながら、DirectoryServiceなんて使ってないので、カスタムを選択します。

カスタムプロバイダー(API GatewayのURL)を聞かれるので、ここで一旦終了。
API Gatewayを先に作ります。

公式の「カスタムIDプロバイダーの操作」手順を参考に進めていきます

カスタム ID プロバイダーの操作 - AWS Transfer Family
カスタム ID プロバイダーの使用について説明します。カスタム ID プロバイダーは API Gateway を使用します。
https://docs.aws.amazon.com/ja_jp/transfer/latest/userguide/custom-identity-provider-users.html

Transfer Family カスタム認証に API Gateway メソッドを使用するには
のところにテンプレートがあるので、基本的なスタックテンプレートを使ってみます。

AWS CloudFormation コンソール(https://console.aws.amazon.com/cloudformation) を開いて、スタックの作成をポチっとします。

テンプレートのURL(「カスタム ID プロバイダーを使用した認証」に書いてあります)を設定して、次へ

CreateServerをfalseにして、その他の設定値は変更せず進めていきます。

作成完了したらOKです。

作成されたリソースを確認します。

Lambdaの認証サンプルで以下のコードが設定されています。

'use strict';

// 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 serverId, only that it is provided.
  if (event.password == '' && ( event.protocol == 'FTP' || event.protocol == 'FTPS')) {
    // Return HTTP status 200 but with no role in the response to indicate authentication failure
    response = {};
  } else if (event.serverId !== "" && event.username == 'myuser') {
    response = {
      Role: 'arn:aws:iam::000000000000:role/MyUserS3AccessRole', // The user will be authenticated if and only if the Role field is not blank
      Policy: '', // Optional JSON blob to further restrict this user's permissions
      HomeDirectory: '/' // 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 myrsapubkey" ];
    // Check if password is correct
    } else if (event.password !== 'MySuperSecretPassword') {
      // 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);
};

これでログインできそうですね。
 ユーザ名:myuser
 パスワード:MySuperSecretPassword
S3や専用のロール作ってから認証部分を更新したいと思います。

API Gatewayの準備ができたので、再度Transfer Familyを作成していきます。
カスタムプロバイダーにAPI Gateway URLを設定する必要があるので、確認しておきます。
URLの呼び出し:
https://a7mbwowjjd.execute-api.ap-northeast-1.amazonaws.com/prod
の部分をメモしておきます。

TransferFamilyの作成は、カスタムプロバイダーは先程メモしたもの、ロールはCloudFormationで作成されたTransferIdentityProviderRoleを設定します。以降の設定項目のVPC、AZ、セキュリティグループは環境にあったものを設定していきます。
途中でドメイン(TransferFamilyのデータアクセス先)はS3を選択します。

作成できたら接続テストを実施します。作成したTransfer Familyを開いて、画面右上のアクション - テスト を選択します。

接続情報を入力しテストを実施します。
 ユーザ名:myuser
 パスワード:MySuperSecretPassword
 送信元IP:8.8.8.8(どこのIPでもOKです)
StatusCodeが200で返ってくればOKです。

次は任意の名前でS3バケットを作成し、テスト用のファイルをアップロードしておきます。

次にFTPログイン時に使用するロールを作成していきます。
権限はS3FullAccessにしていますが、本番運用では対象バケットを絞るなど適切な権限にしてください。

Lambdaの認証を変更します。

    response = {
      Role: 'arn:aws:iam::925722219028:role/transferfamily', // The user will be authenticated if and only if the Role field is not blank
      Policy: '', // Optional JSON blob to further restrict this user's permissions
      HomeDirectory: '/tf-test-20210822/' // Not required, defaults to '/'
    };

これで準備出来たので動作確認していきます。

[root@ip-10-0-0-29 ~]# ftp
ftp> open 10.0.2.105
Connected to 10.0.2.105 (10.0.2.105).
220 Service ready for new user.
Name (10.0.2.105:root): myuser
331 User name okay, need password for myuser.
Password:
230 User logged in, proceed.
Remote system type is UNIX.
ftp> ls
227 Entering Passive Mode (10,0,2,105,32,5)
150 File status okay; about to open data connection.
-rwxr--r--   1 - -       206292 Aug 22 02:28 image.png
226 Closing data connection.
ftp> bin
200 Command TYPE okay.
ftp> get image.png
local: image.png remote: image.png
227 Entering Passive Mode (10,0,2,105,32,3)
150 File status okay; about to open data connection.
226 Transfer complete.
206292 bytes received in 0.769 secs (268.14 Kbytes/sec)

無事接続とファイル一覧表示、ファイルの取得が出来ました。今後サーバの保守がなくなるのはとても良いと思います。
あと、SFTPで構成すれば認証部分の実装も必要ないので、もっと簡単に構築できますね。