AWS Transfer FamilyでSFTPサーバーを立ててみた
AWS Transfer Familyを利用して、S3と連携できるマネージドSFTPサーバーをTerraformで簡単に構築する手順を紹介する。料金やWinSCP接続などの注意点も含め、一連の設計・運用のポイントを解説する。
目次
はじめに
- SAP勉強してたら結構出てきた、AWS Transfer Familyというサービス
- SFTP、FTPS、FTP対応で
- SFTPサーバーのエンドポイントだと、ほぼSFTPサーバーが立ってるイメージで、WinSCPみたいなSFTPアプリケーションから接続できる。
- ただ、エンドポイントにアップロードすると、S3やEFSに勝手に同期してくれるというのが大きな売りで、AWSのワークロードにつなげやすいという特徴を持つ
価格
Transfer Familyの値段はちょっとお高め。東京リージョンの場合
- サーバーエンドポイントで各プロトコルが有効になっている時間 プロトコルあたり USD 0.30/時間
- データアップロード 転送されたギガバイト (GB) あたり USD 0.04
- データダウンロード USD 0.04/転送されたギガバイト (GB)
エンドポイントが24時間30日立ってるとすると、エンドポイントだけで216ドル。SFTPへのファイル送受信がオンプレで結構大事なワークロードになっていて、それをAWSにシームレスに移行したいという場合ぐらいしか刺さらなそう。
注意点
- EC2のSFTPサーバー
- SFTPサーバー立てるだけなら、SSH対応のAmazon LinuxのEC2でもできる
- デフォルトのAmazon Linux 2023でキーペアを設定すれば、
ec2-user
でWinSCPから接続すればそのままSFTP接続できる - ただアンマネージド(EC2)のSFTPだとS3やEFSの同期はつかない
- 特にユーザーデータで初期化する場合だと、ユーザー管理や権限管理が結構面倒くさい。
ec2-user
以外でやろうとすると、非常にハマって挫折した
- Transfer Familyについて
- Transfer Family側には、オンプレのSFTPの取り込み機能はない。あくまでマネージドSFTPサーバー
- オンプレからTransfer Familyでデータ連携する場合は、オンプレ側からSFTPの送信がいる
作るもの
- Transfer Familyでもデフォルトのエンドポイント(ドメイン)は作成されるが、IPを固定したいため、エンドポイントにElastic IPを紐づける
SSHキーの準備
カレントディレクトリのssh_keys
以下にSSHキーを準備しておく。これはSFTPに接続する際に必要。
mkdir -p ssh_keys
cd ssh_keys
ssh-keygen -t rsa -b 4096 -C "<input your text>" -f ./id_rsa
Terraformのコード
Transfer Familyをデプロイするためのコード。
Transfer Familyのセキュリティグループのインバウンドを絞ることでIP制限を実現し、Transfer FamilyのIPはElastic IPで固定する。
# S3バケットの作成
resource "aws_s3_bucket" "transfer_bucket" {
bucket = var.s3_bucket_name
force_destroy = true
}
#----------------------------------------------------------------
# Transfer Family ログ出力用 IAM ロール
# (CloudWatch Logs 等を利用したい場合)
#----------------------------------------------------------------
data "aws_iam_policy_document" "transfer_trust_policy" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["transfer.amazonaws.com"]
}
}
}
resource "aws_iam_role" "transfer_logging_role" {
name = "transfer-logging-role"
assume_role_policy = data.aws_iam_policy_document.transfer_trust_policy.json
}
# CloudWatch Logs 等への書き込みポリシーをアタッチ
resource "aws_iam_role_policy_attachment" "transfer_attach_logging_policy" {
role = aws_iam_role.transfer_logging_role.name
policy_arn = "arn:aws:iam::aws:policy/CloudWatchLogsFullAccess"
}
#----------------------------------------------------------------
# S3 へのアクセス権限用 IAM ロール
# (Server Managed IDP を利用する場合、ユーザーごとにこのロールを指定)
#----------------------------------------------------------------
data "aws_iam_policy_document" "transfer_role_policy_doc" {
statement {
actions = [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
]
resources = [
aws_s3_bucket.transfer_bucket.arn,
"${aws_s3_bucket.transfer_bucket.arn}/*"
]
}
}
resource "aws_iam_role" "transfer_access_role" {
name = "transfer-s3-access-role"
assume_role_policy = data.aws_iam_policy_document.transfer_trust_policy.json
}
resource "aws_iam_role_policy" "transfer_access_policy" {
name = "transfer-s3-access-policy"
role = aws_iam_role.transfer_access_role.id
policy = data.aws_iam_policy_document.transfer_role_policy_doc.json
}
#----------------------------------------------------------------
# Transfer Family の SFTP サーバーを PUBLIC エンドポイントで作成
# Elastic IP をアタッチ
#----------------------------------------------------------------
resource "aws_eip" "transfer_eip" {
domain = "vpc"
tags = {
Name = "transfer-eip"
}
}
# セキュリティグループ: SSH (SFTP) 用
resource "aws_security_group" "sftp_sg" {
name = "sftp_sg"
description = "Allow SSH access for SFTP"
vpc_id = var.vpc_id
ingress {
description = "SSH"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [var.my_api_cidr] # 必要に応じて制限
}
egress {
description = "Allow all outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
}
resource "aws_transfer_server" "public_sftp_server" {
endpoint_type = "VPC"
identity_provider_type = "SERVICE_MANAGED"
protocols = ["SFTP"]
logging_role = aws_iam_role.transfer_logging_role.arn
endpoint_details {
# Public endpoint では address_allocation_ids を設定できます
# Elastic IP の id を割り当てることで固定 IP 化
address_allocation_ids = [aws_eip.transfer_eip.id]
subnet_ids = [var.sftp_public_subnet_id]
vpc_id = var.vpc_id
security_group_ids = [aws_security_group.sftp_sg.id]
}
# タグ付けは任意
tags = {
Name = "my-public-transfer-sftp"
}
}
#----------------------------------------------------------------
# 6. Transfer Family ユーザーの作成
# (Server Managed IDP + デフォルトホームディレクトリを指定)
#----------------------------------------------------------------
resource "aws_transfer_user" "sftp_user" {
server_id = aws_transfer_server.public_sftp_server.id
user_name = "sftp_user"
role = aws_iam_role.transfer_access_role.arn
home_directory = "/${aws_s3_bucket.transfer_bucket.bucket}"
# 必要に応じてポリシー (AWS Management Console 上でユーザーポリシーを設定する代わりに、ここで指定)
policy = <<POLICY
{
"Version":"2012-10-17",
"Statement":[{
"Effect":"Allow",
"Action":[ "s3:*" ],
"Resource":[
"${aws_s3_bucket.transfer_bucket.arn}",
"${aws_s3_bucket.transfer_bucket.arn}/*"
]
}]
}
POLICY
tags = {
Name = "sftp_user"
}
}
resource "aws_transfer_ssh_key" "ssh_key" {
server_id = aws_transfer_server.public_sftp_server.id
user_name = aws_transfer_user.sftp_user.user_name
body = file("${path.module}/ssh_keys/id_rsa.pub")
}
output "sftp_server_host" {
value = aws_transfer_server.public_sftp_server.endpoint
}
output "sftp_public_ip" {
value = aws_eip.transfer_eip.public_ip
}
WinSCPで接続する
terraform applyすると、以下のように表示される。
sftp_public_ip = "52.198.xx.xx"
sftp_server_host = "s-xxxxxx.server.transfer.ap-northeast-1.amazonaws.com"
今回はパブリックIPでWinSCPから接続してみる。Elastic IPなので、サーバーのホスト名が変わっても安定的に接続できるはず。接続はSSHキー経由で接続する。PuTTY形式に変換したSSHキーでも普通に接続できる。
必要ならSSHキーのパスフレーズを入れて普通につながる。
S3との連携
ドラッグアンドドロップでWinSCPでアップロード
S3に即時に同期されているのはちょっとかっこいい。ここからS3のイベント通知などを使ってLambdaなどと連携してフローを入れていくのがおそらく想定された使い方かと思われる。
所感
- Transfer Family便利! ただ値段がちょっと高い
- ユーザーが増えたり権限を管理したいだとTransfer Familyの強み出てきそう
- 取り込む側の使い勝手(DataSyncだとSFTP対応してないし)がちょっと悪いのがうーんという感じ。SFTPはAWS側で持っておくのがベストプラクティスなのかもしれない
Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内
技術書コーナー
北海道の駅巡りコーナー