この記事は、terraform Advent Calendar 2024 の7日目のエントリです。
はじめに
terraformで、AWS S3とTransferFamilyを組み合わせて、さっとsftpサーバを作ります。
sftpユーザや鍵ファイルも併せて作るので、apply後にsftp接続できます。
なお、tfstateに公開鍵も秘密鍵も記載されているため気を付けてください。
(Transfer Familyを触ったのは初めてでした。知らないサービス多すぎ!)
AWS Transfer Familyとは
完全マネージドなファイル転送サービスで、SFTP、FTPS、FTPを使用して、安全にデータをAWSに移動できます。
各接続方式や接続先サービス(s3、EFS)を選択したサーバを用意し、それに対して、アクセスするユーザやロール、接続先(S3バケット名など)を指定します。 Transferのサーバーはあくまで接続に関する情報のまとまりであり、例えばS3バケット名を紐づけるわけではないです。そのため、1つのサーバーに対し、異なるロールや異なるアクセス先をもつユーザを設定できます。
環境
環境と設定
- terraform : v1.9.4
- バケット名 : advent-server
- sftpユーザ名 : sftp-advent
ファイル構成
$ tree ./ ./ ├── main.tf ├── output.tf ├── terraform.tfvars └── variables.tf
各ファイルについて
terraform.tfvars
バケット名、sftpユーザ名、アクセス記録のためのロググループの変数を用意します。
bucket_name = "advent-server" sftp_user = "sftp-advent" log_group = "/transfer/advent-server"
variables.tf
各変数を定義します。
variable "bucket_name" { type = string } variable "sftp_user" { type = string } variable "log_group" { type = string }
main.tf
作成するリソースとポイントを記載します。
- s3バケット
- force_destroy = true
- バケットが空でなくても削除します
- force_destroy = true
- transferに設定するロールとポリシー
- ロールにポリシーをアタッチします
- transferに設定するロググループ
- transferサーバー
- sftp接続で作成します
- sftpユーザと鍵ファイル
- ローカルに秘密鍵を保存します
provider "aws" { region = "ap-northeast-1" access_key = "AKIA**********" secret_key = "**************" } resource "aws_s3_bucket" "bucket" { bucket = var.bucket_name force_destroy = true # バケット削除時にオブジェクトも削除する } resource "aws_iam_role" "transfer_role" { name = "TransferFamilyS3AccessRole" assume_role_policy = jsonencode({ Version = "2012-10-17", Statement = [ { "Sid" : "", "Effect" : "Allow", "Principal" : { "Service" : "transfer.amazonaws.com" }, "Action" : "sts:AssumeRole" } ] }) } resource "aws_iam_policy" "s3_access_policy" { name = "S3AccessPolicyForTransferFamily" policy = jsonencode({ Version = "2012-10-17", Statement = [ { "Sid" : "Statement1", "Effect" : "Allow", "Action" : [ "s3:ListBucket", "s3:GetBucketLocation" ], "Resource" : "${aws_s3_bucket.bucket.arn}" }, { "Sid" : "Statement2", "Effect" : "Allow", "Action" : [ "s3:PutObject", "s3:GetObject", "s3:DeleteObject" ], "Resource" : "${aws_s3_bucket.bucket.arn}/*" } ] }) } resource "aws_iam_role_policy_attachment" "attach_s3_policy" { role = aws_iam_role.transfer_role.name policy_arn = aws_iam_policy.s3_access_policy.arn } resource "aws_cloudwatch_log_group" "transfer" { name_prefix = var.log_group } resource "aws_transfer_server" "sftp_server" { domain = "S3" protocols = ["SFTP"] # SFTPは鍵認証のみ使えます identity_provider_type = "SERVICE_MANAGED" endpoint_type = "PUBLIC" # 外部から接続できるようにする structured_log_destinations = [ "${aws_cloudwatch_log_group.transfer.arn}:*" ] tags = { Name = "transfer-${var.bucket_name}" } } resource "aws_transfer_user" "sftp_user" { server_id = aws_transfer_server.sftp_server.id user_name = var.sftp_user role = aws_iam_role.transfer_role.arn home_directory = "/${var.bucket_name}" } resource "tls_private_key" "sftp_tls" { algorithm = "RSA" rsa_bits = 4096 } resource "aws_transfer_ssh_key" "sftp_ssh_key" { # SFTP接続は鍵認証のみ。 server_id = aws_transfer_server.sftp_server.id user_name = aws_transfer_user.sftp_user.user_name body = trimspace(tls_private_key.sftp_tls.public_key_openssh) } resource "local_file" "private_key_file" { content = tls_private_key.sftp_tls.private_key_pem file_permission = "0600" filename = pathexpand("~/.ssh/${var.sftp_user}.pem") }
output.tf
接続するためのsftpエンドポイントを表示します。
output "sftp_endpoint" { value = aws_transfer_server.sftp_server.endpoint }
terraformの実行
terraform apply
transferの作成に3分ほどかかりました。
$ terraform apply Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # aws_cloudwatch_log_group.transfer will be created + resource "aws_cloudwatch_log_group" "transfer" { (略) Plan: 10 to add, 0 to change, 0 to destroy. Changes to Outputs: + sftp_endpoint = (known after apply) Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes tls_private_key.sftp_tls: Creating... aws_cloudwatch_log_group.transfer: Creating... aws_iam_role.transfer_role: Creating... aws_s3_bucket.bucket: Creating... (略) Apply complete! Resources: 10 added, 0 changed, 0 destroyed. Outputs: sftp_endpoint = "******.server.transfer.ap-northeast-1.amazonaws.com" $
sftp接続
sftp接続し、ファイルのアップロードやダウンロードが確認できました。
$ sftp -i ~/.ssh/sftp-advent.pem sftp-advent@******.server.transfer.ap-northeast-1.amazonaws.com Connected to ******.server.transfer.ap-northeast-1.amazonaws.com. sftp> pwd Remote working directory: /advent-server sftp> ls sftp> put main.tf Uploading main.tf to /advent-server/main.tf maintf 100% 2565 59.0KB/s 00:00 sftp> ls main.tf sftp> get main.tf Fetching /advent-server/main.tf to main.tf main.tf sftp> exit $
その他
terraform destroyで、バケットにファイルが残っていても全て削除できます。ローカルの秘密鍵も削除されます。
また、カスタムホスト名を付与するならRoute53でホストゾーンとレコードを作成します。
マネジメントコンソールでカスタムホスト名を付けてPlanしたところ、タグのみの差異だったため、ちょっとめんどくさがってコードにそのタグを付けたらホストゾーンとレコードも作成されました。この場合、destroyしてもホストゾーンは残っていたので、このやり方はあまりよくないんだろうなと思いました。