AWS Transfer Family for FTPを利用し、AWS PrivateLink 経由でファイル転送してみた

2024.02.08

はじめに

AWS Transfer Family for FTPをを利用し、AWS PrivateLink経由でファイル転送する構成を構築したので、手順をまとめました。

Transfer Family for FTPは、VPC内部からしか利用できません。

そのため、Transfer Family for FTPとは別のVPC上にFTP接続するEC2クライアントが存在する場合、VPC同士をVPCピアリングなどで接続するとファイル転送できます。VPCのCIDRが重複している場合は接続できないですが、AWS PrivateLinkを利用することで重複したVPCからでもファイル転送ができます。

Transfer Familyの概要は、下記をご参考ください。

AWS PrivateLinkの概要は、下記をご参考ください。

構成

構成図は下記の通りです。

構成の補足です。詳細は、構築手順で説明します

  • 2つのVPCは、同一アカウント(別アカウントでも可能)
  • サブネット上に作成されるリソースはすべて同じ(ap-northeast-1a)
  • Transfer Family for FTPの仕様
    • 認証方法は、カスタムIDプロバイダー(AWS Lambda)
    • ファイル転送先はS3バケット
  • S3バケットやサブネットなどは作成済み

構築の流れ

構築の流れは、下記の通りです

  1. IAMロール作成
  2. Lambda作成
  3. セキュリティグループ作成
  4. Transfer Family作成
  5. Lambdaのアクセス権限設定
  6. NLBとターゲットグループ作成
  7. Transfer FamilyのPassiveIpを変更
  8. エンドポイントサービス作成
  9. VPCエンドポイント作成
  10. エンドポイント接続承諾
  11. EC2からFTP接続

IAMロール作成

Transfer Family経由でS3バケットを利用するためのIAMロールを作成します。

AmazonS3FullAccessポリシーをアタッチします。

名前はTransfer-Family-Roleとしました。

Lambda作成

Transfer Familyのユーザー管理は、今回カスタムIDプロバイダー を利用するため、認証用のLambdaを作成します。

本来であれば、SecretManagerを利用する方がよいですが、今回は検証のため、Lambdaのコード内にユーザー情報を入れます。

Pythonコードは下記の通りです。最小コードにしています。あくまでもサンプルコードとご認識ください。IAMロールのARNは、各々変えます。

def lambda_handler(event, context):
    if event["username"] == "class" and event["password"] == "method":
        return { 'Role': 'arn:aws:iam::アカウントID:role/Transfer-Family-Role' }
    return {}

処理としては、ユーザー情報を判定し、Transfer Familyにロール情報を渡しています。

後ほど、Transfer FamilyがLambdaを呼ぶための権限をLambdaに設定します。Transfer FamilyのARNが必要なため、Transfer Family作成後に行います。

セキュリティグループ作成

下記リソース用のセキュリティグループをそれぞれ作成します。インバウンドルールは3つとも同じです。

  • Transfer Family VPCエンドポイント
  • NLB
  • PrivateLinkの利用側のVPCエンドポイント

FTP通信のため、ポート21(コントロールチャネル)とポート範囲8192〜8200(データチャネル)が利用できるように、インバウンドルールを下記の通り設定します。

Transfer Family作成

Transfer Family サーバーを作成します。

プロトコルはFTPを選択します。

IDプロバイダーは、カスタムIDプロバイダーを選択します。

先程作成したLambdaを選択します。

エンドポイントタイプは、VPCの内部を選択します。2AZのサブネットを指定していますが、1つでも構いません。先程作成したセキュリティグループを指定します。

ドメインは、S3を選択します。

そのほかはデフォルト設定で作成します。

作成後、Transfer FamilyのVPCエンドポイントのプライベートIPは、10.0.3.22510.0.4.82であることが確認できました。

NLBの作成時に改めて解説しますが、NLBのターゲットグループは、Transfer FamilyのVPCエンドポイントを1AZしか指定できないため、今回は10.0.3.225を指定します。

ここまでの構築リソースは下記の通りです。

Lambdaのアクセス権限設定

Transfer FamilyがLambdaを呼ぶための権限をLambdaに設定します。

Lambdaのリソースベースポリシーステートメントからアクセス権限を追加をクリックします。

下記の通りに入力します

  • ステートメントID(一意であればよい)
    • transferfamily-GetUserConfigLambdaPermission
  • プリンシパル
    • transfer.amazonaws.com
  • ソースのARN(作成したTransfer FamilyのARN)
    • arn:aws:transfer:ap-northeast-1:アカウントID:server/Transfer FamilyのサーバーID
  • アクション
    • lambda:InvokeFunction

NLBとターゲットグループ作成

FTP通信を行うので、ターゲットグループには、ポート21、8192~8200を指定し、ターゲットIPは10.0.3.225にします。

ターゲットグループは、1つずつポートを指定するため、合計10つのターゲットグループを作成する必要があります。

ターゲットIPは1つしか指定できないため、Transfer Family VPCエンドポイントの1AZしか指定できません。

AWS CloudShellから10個のターゲットグループをCLIを実行し作成します。

// 以下2つは個々に設定を変えて下さい。
$ TARGET_IP="10.0.3.225"
$ VPC_ID="vpc-xxxx"

$ for port in 21 8192 8193 8194 8195 8196 8197 8198 8199 8200
do
  # ターゲットグループを作成
  TARGET_GROUP_ARN=$(aws elbv2 create-target-group \
    --name tf-ftp-$port \
    --protocol TCP \
    --port $port \
    --health-check-protocol TCP \
    --target-type ip \
    --vpc-id $VPC_ID \
    --query "TargetGroups[0].TargetGroupArn" \
    --output text)

  # ターゲットグループにIPアドレスを登録
  aws elbv2 register-targets \
    --target-group-arn $TARGET_GROUP_ARN \
    --targets Id=$TARGET_IP,Port=$port
done

ターゲットグループが10個作成できました。

続いて、内部用のNLBを作成し、リスナー10個追加します。先程作成したターゲットグループに転送するようにします。

1サブネット上でNLBを作成します。

// 以下3つは個々に設定を変えて下さい。

$ NLB_NAME="transfer-family-nlb"
$ SUBNET_ID="subnet-xxxxx"
$ NLB_SG_ID="sg-xxxxx"


$ NLB_ARN=$(aws elbv2 create-load-balancer \
  --name $NLB_NAME \
  --type network \
  --scheme internal \
  --subnets $SUBNET_ID \
  --security-groups $NLB_SG_ID \
  --query 'LoadBalancers[0].LoadBalancerArn' \
  --output text)

$ echo "NLB ARN: $NLB_ARN"
NLB ARN: arn:aws:elasticloadbalancing:ap-northeast-1:xxx:loadbalancer/net/my-nlb/xxxxx

$ for port in 21 8192 8193 8194 8195 8196 8197 8198 8199 8200
do
  # ターゲットグループのARNを取得
  TARGET_GROUP_ARN=$(aws elbv2 describe-target-groups \
    --names tf-ftp-$port \
    --query "TargetGroups[0].TargetGroupArn" \
    --output text)

  # リスナーをNLBに追加
  aws elbv2 create-listener \
    --load-balancer-arn $NLB_ARN \
    --protocol TCP \
    --port $port \
    --default-actions Type=forward,TargetGroupArn=$TARGET_GROUP_ARN
done

NLBが作成できました。

各ターゲットグループのターゲットIPは、Transfer Family VPCエンドポイントのプライベートIP(10.0.3.225)が指定されていることも確認できます。

ここまでの構築リソースは下記の通りです。

Transfer FamilyのPassiveIpを変更

利用するFTPクライアントによっては、このPassiveIp変更は不要です。PassiveIpのデフォルト値は、AUTOです。 今回の検証では、lftpコマンドを利用するため、変更は不要です。 変更な必要な場合は、以下の通りに変更下さい。

変更が必要かどうかの判断は下記の記事を参考下さい。

NLB経由でFTP接続するためには、Transfer FamilyのPassiveIpは、NLBのプライベートIPを指定するケースがあります。AWSドキュメントでは、一部のFTPクライアントでPassiveIpを0.0.0.0と指定も可能と記載がありますが、今回はNLBのプライベートIPを指定します。

まず、NLBのプライベートIPをマネジメントコンソールのネットワークインターフェイスから確認します。

10.0.3.119であることがわかりました。

Transfer Familyの[追加設定]から、PassiveIpを10.0.3.119に変更します。

設定変更後は、再起動する必要があります。停止した後、開始することで再起動したことになります。

PassiveIp 値を変更した場合、変更を反映するには、転送ファミリーサーバーを停止してから再起動する必要があります。 引用

エンドポイントサービス作成

サービス提供側(NLBが存在するVPC側)でエンドポイントサービスを作成します。

作成したNLBを指定します。ほかはデフォルトのままです。

作成後、サービス名を控えておきます。

もし、サービス利用者側(クライアント側)が別のアカウントの場合、プリンシパルを許可します。

今回プリンシパルは、アカウント内のすべてのプリンシパルを設定しました。VPCエンドポイントサービスの利用をIAMユーザーやIAMロールに制限することも可能です。

  • AWS アカウント (およびアカウント内のすべてのプリンシパル)
    • arn:aws:iam::aws-account-id:root
  • 特定の IAM ユーザー
    • arn:aws:iam::aws-account-id:user/user-name
  • 特定の IAM ロール
    • arn:aws:iam::aws-account-id:role/role-name

VPCエンドポイント作成

サービス利用者側(クライアント側)でVPCエンドポイントを作成します。

先程のエンドポイントサービスのサービス名を入力し、[サービスの検証]をします。

作成したセキュリティグループを指定します。

作成後、VPCエンドポイントのDNS名を控えておきます。

エンドポイント接続承諾

サービス提供側のエンドポイントサービスで、エンドポイントの承諾をします。

これでPrivateLinkの設定は完了です。

ここまでの構築リソースは下記の通りです。

EC2からFTP接続

サービス提供側で、EC2(Amazon Linux 2)を起動します。

EC2でlftpパッケージをインストールし、アップロード用のtest.txtファイルを作成しておきます。

$ sudo yum install -y lftp
~中略~
Installed:
  lftp-4.4.8-12.amzn2.1.x86_6                                                                            

Complete!

$ echo "test" > test.txt

VPCエンドポイントのDNS名を指定してFTP接続を実行し、Lambdaに設定しているユーザー名とパスワードを入力しログインします。

$ lftp vpce-0f95aea68fb6d5ba8-hpr7z8e8.vpce-svc-04ec99342c0977f6c.ap-northeast-1.vpce.amazonaws.com

// login ユーザー名
lftp vpce-0f95aea68fb6d5ba8-hpr7z8e8.vpce-svc-04ec99342c0977f6c.ap-northeast-1.vpce.amazonaws.com:~> login class

// パスワード
Password:

Transfer Familyでは、PASV レスポンスの際、Transfer FamilyのVPC エンドポイントのプライベートIPアドレスとポート番号をクライアントに通知します。 クライアントがこの IP アドレスに対してデータ転送用コネクションの確立を試みるため、NLB 経由でのデータ転送用コネクションが確立できません。

クライアントが拡張パッシブモード(EPSV)を利用できる場合、レスポンスがポート番号のみとなり、上記のプライベート IP アドレスが返却され利用する問題が発生しないため、NLB 経由での接続が可能となります。

現状 lftpコマンドは、拡張パッシブモードをサポートしていますが、ftp コマンドではサポートされていません。

パッシブモードの応答については下記の記事が分かりやすいです。

// 拡張パッシブモード有効化 set ftp:prefer-epsv true
lftp class@vpce-0f95aea68fb6d5ba8-hpr7z8e8.vpce-svc-04ec99342c0977f6c.ap-northeast-1.vpce.amazonaws.com:~> set ftp:prefer-epsv true

デフォルトで拡張パッシブモード有効化されていましたので、上記のコマンド実行は不要でした。

それではファイルをアップロードしてみます。

lftp class@vpce-0f95aea68fb6d5ba8-hpr7z8e8.vpce-svc-04ec99342c0977f6c.ap-northeast-1.vpce.amazonaws.com:/> cd s3バケット名

lftp class@vpce-0f95aea68fb6d5ba8-hpr7z8e8.vpce-svc-04ec99342c0977f6c.ap-northeast-1.vpce.amazonaws.com:/s3バケット名> put test.txt
9 bytes transferred   

lftp class@vpce-0f95aea68fb6d5ba8-hpr7z8e8.vpce-svc-04ec99342c0977f6c.ap-northeast-1.vpce.amazonaws.com:/s3バケット名> ls
-rwxr--r--   1 - -            0 Sep  7 11:57 test.txt

アップロードできることが確認できました。

最後に

AWS Transfer Family for FTPをAWS PrivateLink経由でFTP接続する構成を構築手順をご紹介しました。

今回は、単一AZでの構成のため、マルチAZに比べ、可用性は低い点に注意です。

また、Transfer Familyで利用するプロトコルは、FTPではなくSFTPでのファイル転送がよいです。どうしてもFTPを利用しなければならないときに、参考になれば幸いです。

参考