0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

VPC InternalなAWS Transfer for FTPをTerraformで構築する

Posted at

はじめに

AWS Transfer Family第2弾。
以前の記事はこちら。

前回はSFTPのプロトコルで実装したが、要件上InternalなのでFTPでも充分な場合の実装をしてみる。

IAM

IAMは前回の記事と同じ内容で、サービスロールとユーザ用の両方を用意する。Lambda用のパーミッションも必要になるが、それはLambdaの項でまとめて書く。

resource "aws_iam_role" "ftp_server" {
  name               = local.ftp_server_role_name
  assume_role_policy = data.aws_iam_policy_document.ftp_server_assume.json
}

data "aws_iam_policy_document" "ftp_server_assume" {
  statement {
    effect = "Allow"

    actions = [
      "sts:AssumeRole",
    ]

    principals {
      type = "Service"
      identifiers = [
        "transfer.amazonaws.com",
      ]
    }
  }
}

resource "aws_iam_role_policy" "ftp_server" {
  role   = aws_iam_role.ftp_server.name
  name   = local.ftp_server_policy_name
  policy = data.aws_iam_policy_document.ftp_server_custom.json
}

data "aws_iam_policy_document" "ftp_server_custom" {
  statement {
    effect = "Allow"

    actions = [
      "logs:CreateLogGroup",
      "logs:CreateLogStream",
      "logs:PutLogEvents",
    ]

    resources = [
      "*",
    ]
  }
}

resource "aws_iam_role" "ftp_user" {
  name               = local.ftp_user_role_name
  assume_role_policy = data.aws_iam_policy_document.ftp_user_assume.json
}

data "aws_iam_policy_document" "ftp_user_assume" {
  statement {
    effect = "Allow"

    actions = [
      "sts:AssumeRole",
    ]

    principals {
      type = "Service"
      identifiers = [
        "transfer.amazonaws.com",
      ]
    }
  }
}

resource "aws_iam_role_policy" "ftp_user" {
  role   = aws_iam_role.ftp_user.name
  name   = local.ftp_user_policy_name
  policy = data.aws_iam_policy_document.ftp_user_custom.json
}

data "aws_iam_policy_document" "ftp_user_custom" {
  statement {
    effect = "Allow"

    actions = [
      "s3:*",
    ]

    resources = [
      "${aws_s3_bucket.ftp.arn}",
      "${aws_s3_bucket.ftp.arn}/*",
    ]
  }
}

セキュリティグループ

VPCエンドポイントにアタッチするセキュリティグループは、最低限、以下のように用意しておこう。
アクセス元のVPCが明確なのであれば、そこで絞り込みをするとよりセキュアになる。

なお、ポートは公式のユーザーガイドに記載されているとおり、制御ポートとして21、データ転送ポートとして8192~8200を使用するので、その範囲を開けておく。

resource "aws_security_group" "ftp" {
  vpc_id = data.aws_vpc.my.id
  name   = local.security_group_name

  ingress {
    from_port   = 21
    to_port     = 21
    protocol    = "TCP"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 8192
    to_port     = 8200
    protocol    = "TCP"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

S3バケット

S3バケットも前回の記事と同じ内容で用意する。同じ内容と言いながらも中身が微妙に違うのは、前回記事を書いた後にTerraformとAWS両方のアップデートが間に入っているためだ。

resource "aws_s3_bucket" "ftp" {
  bucket = local.s3_bucket_name

  force_destroy = true
}

resource "aws_s3_bucket_ownership_controls" "ftp" {
  bucket = aws_s3_bucket.ftp.id

  rule {
    object_ownership = "BucketOwnerEnforced"
  }
}

resource "aws_s3_bucket_public_access_block" "ftp" {
  bucket = aws_s3_bucket.ftp.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

Lambda

今回の構成の場合、認証をLambdaで行うことになる。
スクリプトとTerraformを以下のように作成する。

スクリプト

スクリプトは、公式のユーザーガイドに従い、以下のように書いておく。公式でベタ書きしている部分は、環境変数で外部から注入できるようにしておこう。

auth.py
import os
import json

def lambda_handler(event, context):
  if event['serverId'] != '' and event['username'] == os.environ['FTP_USER_NAME'] and event['password'] == os.environ['FTP_PASSWORD']:
    response = {
      'Role': os.environ['IAM_ROLE_ARN'],
      'HomeDirectoryType': "LOGICAL",
      'HomeDirectoryDetails': json.dumps([
        {
          'Entry': '/',
          'Target': '/' + os.environ['S3_BUCKET_ID']
        }
      ])
    }
  else:
    response = {}

  return response

Terraform

こちらも基本は公式のユーザーガイドに従い作っていく。

Lambda関数

data "archive_file" "ftp_auth" {
  type        = "zip"
  source_dir  = "../scripts/auth"
  output_path = "../outputs/auth.zip"
}

resource "aws_lambda_function" "ftp_auth" {
  depends_on = [
    aws_cloudwatch_log_group.lambda_ftp_auth,
  ]

  function_name    = local.lambda_function_name
  filename         = data.archive_file.ftp_auth.output_path
  role             = aws_iam_role.lambda_ftp_auth.arn
  handler          = "auth.lambda_handler"
  source_code_hash = data.archive_file.ftp_auth.output_base64sha256
  runtime          = "python3.10"

  memory_size = 128
  timeout     = 30

  environment {
    variables = {
      FTP_USER_NAME = local.ftp_user_name
      FTP_PASSWORD  = local.ftp_password
      S3_BUCKET_ID  = aws_s3_bucket.ftp.id
      IAM_ROLE_ARN  = aws_iam_role.ftp_user.arn
    }
  }
}

リソースベースポリシー

リソースベースポリシーは、Terraformの場合はaws_lambda_permissionのリソースを使用する。

resource "aws_lambda_permission" "ftp_auth" {
  statement_id  = "AllowExecutionFromTransfer"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.ftp_auth.function_name
  principal     = "transfer.amazonaws.com"
}

ログ取得

resource "aws_cloudwatch_log_group" "lambda_ftp_auth" {
  name              = "/aws/lambda/${local.lambda_function_name}"
  retention_in_days = 3
}

IAM

resource "aws_iam_role" "lambda_ftp_auth" {
  name               = local.lambda_ftp_auth_role_name
  assume_role_policy = data.aws_iam_policy_document.lambda_ftp_auth_assume.json
}

data "aws_iam_policy_document" "lambda_ftp_auth_assume" {
  statement {
    effect = "Allow"

    actions = [
      "sts:AssumeRole",
    ]

    principals {
      type = "Service"
      identifiers = [
        "lambda.amazonaws.com",
      ]
    }
  }
}

resource "aws_iam_role_policy" "lambda_ftp_auth_custom" {
  name   = local.lambda_ftp_auth_policy_name
  role   = aws_iam_role.lambda_ftp_auth.id
  policy = data.aws_iam_policy_document.lambda_ftp_auth_custom.json
}

data "aws_iam_policy_document" "lambda_ftp_auth_custom" {
  statement {
    effect = "Allow"

    actions = [
      "logs:CreateLogGroup",
      "logs:CreateLogStream",
      "logs:PutLogEvents",
    ]

    resources = [
      "*",
    ]
  }
}

AWS Transfer for FTP

さて、ここまで作れれば後は簡単だ。
endpoint_typeVPCで設定しておく。

上で作成したLambdaのARNを、functionで指定すれば勝手に呼び出してくれる。

VPCエンドポイントを永続で保持するのであれば、自動払い出しをやめることはできないので、前回記事を参考にしながらterraform importをしよう。

resource "aws_transfer_server" "ftp" {
  protocols     = ["FTP"]
  endpoint_type = "VPC"

  identity_provider_type = "AWS_LAMBDA"
  function               = aws_lambda_function.ftp_auth.arn

  endpoint_details {
    vpc_id             = data.aws_vpc.my.id
    subnet_ids         = data.aws_subnets.my_vpc.ids
    security_group_ids = [aws_security_group.ftp.id]
  }

  logging_role = aws_iam_role.ftp_server.arn
}

ここまでやれば、FTPサーバ作成時に払い出されたIPアドレスに標準のFTPクライアントで接続できるようになっているはずだ(TerraformのApplyが完了しても、VPCエンドポイントがアクティベーションされるのに2~3分程度追加でかかるので、少し待つようにしy)。Lambdaを作る手間を考えると、鍵の管理とあまり変わらない気がするが、鍵ファイルの管理が不要な分、運用が軽いはずだ。オンプレからのデータ移行等で一時的な作成であればこの方式は悪くは無いと思う。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?