EIP＋セキュリティグループでセキュアにしたAWS Transfer for SFTPをTerraformで構築する

# はじめに
AWS Transfer for SFTPはマネージドなSFTPサーバをサクッと立てることができる素晴らしいサービス。
素のままではVPCのリソースではないが、2020年の1月にVPC経由でEIPをアタッチしてセキュリティグループによりセキュアにアクセスできるようになった。

構成図については[クラスメソッド先生のこの記事](https://dev.classmethod.jp/articles/aws-transfer-for-sftp-supports-vpc-security-groups-and-eip/)が分かりやすい。

この構成は、マネージメントコンソールでは簡単に作れるが、Terraformで実現しようとするとちょっとクセがある部分があるため、そこも含めてどうやって実装するかを検討する(2020年11月時点の話。将来的にはもっと簡単に作れるようになると思う)。

# 全体構成
全体構成としては以下のようになる。
ただし、VPC とパブリックなサブネット2つ、SSH のインバウンド通信を許容するセキュリティグループは既に作ってあって、data source で参照する前提とする。

```
terraform/
├── 01_main.tf
├── 02_variable.tf
├── 03_datasource.tf
├── 11_eip.tf
├── 12_iam.tf
├── 13_s3.tf
├── 14_transfer.tf
└── 15_vpcendpoint.tf
```

# Terraformリソース
## EIP
これは特に難しいことはない。パブリックなサブネット2つにアタッチするEIPを用意しておけばよい。
※たぶん1つでも問題ない

```HCL:11_eip.tf
###############################################################################
# EIP                                                                         #
###############################################################################
resource "aws_eip" "sftp1" {
  vpc = true
}

resource "aws_eip" "sftp2" {
  vpc = true
}
```

## IAM
IAM については、AWS Transfer for SFTP のサーバが CloudWatch Logs にログを出力するための権限と、接続してくるユーザが S3 にアクセスするための権限を用意する必要がある。
いずれも principals は ```transfer.amazonaws.com``` で良い。

```HCL:12_iam.tf
################################################################################
# IAM Role for Transfer for SFTP Server                                        #
################################################################################
resource "aws_iam_role" "sftp_server" {
  name               = local.sftp_server_role_name
  assume_role_policy = data.aws_iam_policy_document.sftp_server_assume.json
}

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

    actions = [
      "sts:AssumeRole",
    ]

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

resource "aws_iam_role_policy_attachment" "sftp_server" {
  role       = aws_iam_role.sftp_server.name
  policy_arn = aws_iam_policy.sftp_server.arn
}

resource "aws_iam_policy" "sftp_server" {
  name   = local.sftp_server_policy_name
  policy = data.aws_iam_policy_document.sftp_server_custom.json
}

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

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

    resources = [
      "*",
    ]
  }
}

################################################################################
# IAM Role for Transfer for SFTP User                                          #
################################################################################
resource "aws_iam_role" "sftp_user" {
  name               = local.sftp_user_role_name
  assume_role_policy = data.aws_iam_policy_document.sftp_user_assume.json
}

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

    actions = [
      "sts:AssumeRole",
    ]

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

resource "aws_iam_role_policy_attachment" "sftp_user" {
  role       = aws_iam_role.sftp_user.name
  policy_arn = aws_iam_policy.sftp_user.arn
}

resource "aws_iam_policy" "sftp_user" {
  name   = local.sftp_user_policy_name
  policy = data.aws_iam_policy_document.sftp_user_custom.json
}

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

    actions = [
      "s3:*",
    ]

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

```

## S3バケット
S3 のバケットは、最小権限を考えると個別に用意し、IAMで↑のようにアクセス範囲を絞った方が良い。
なお、外部ユーザのアクセスではあるが、IAMで制御しているため、ACL は ```private``` で問題ない。

```HCL:13_s3.tf
################################################################################
# S3                                                                           #
################################################################################
resource "aws_s3_bucket" "sftp" {
  bucket = local.bucket_name
  acl    = "private"
}
```

## AWS Transfer for SFTP
いよいよ本題。
サーバ、ユーザ、SSHキーの設定をすることになる。

### サーバ
今回の、EIP＋セキュリティグループを使う場合は、```endpoint_type``` に ```VPC``` を設定しよう。
その上で、```endpoint_details``` で VPC、サブネット、EIPのIPアドレス情報を設定する。

```HCL:14_transfer.tf(抜粋)
###############################################################################
# Transfer for SFTP                                                           #
###############################################################################
resource "aws_transfer_server" "sftp" {
  identity_provider_type = "SERVICE_MANAGED"
  endpoint_type          = "VPC"

  endpoint_details {
    vpc_id     = data.aws_vpc.my.id
    subnet_ids = data.aws_subnet_ids.my_vpc.ids
    address_allocation_ids = [
      aws_eip.sftp1.id,
      aws_eip.sftp2.id,
    ]
  }

  logging_role = aws_iam_role.sftp_server.arn
}
```

### ユーザ
ユーザの設定では、ホームディレクトリのマッピングをする。
```entry``` が、sftp 接続をした際のデフォルトパスで、```target``` が実際のパスになる。
下記の設定をすることで、トップディレクトリが今回のために用意したバケットになり、../ にアクセスしても上位のバケット一覧は取れなくなっていてセキュアだ。

```HCL:14_transfer.tf(抜粋)
resource "aws_transfer_user" "sftp_user" {
  server_id = aws_transfer_server.sftp.id
  user_name = local.sftp_user_name
  role      = aws_iam_role.sftp_user.arn

  home_directory_type = "LOGICAL"
  home_directory_mappings {
    entry  = "/"
    target = "/${local.bucket_name}"
  }
}
```

### SSHキー
SSHキーは、自分で

```shell-session
$ ssh-keygen -P "" -f [鍵ファイル名]
```

しても良いが、せっかくだから全部Terraformで書いてみよう。
```tls_private_key``` のプロバイダを使い、公開鍵をSFTPのユーザに設定し、秘密鍵でアクセスすることが可能だ。

```local_file``` で ```provisioner``` を使っているのは、SSHでは鍵ファイルのパーミッションで自分以外に権限がついているとエラーになるためだ。

```HCL:14_transfer.tf(抜粋)
resource "aws_transfer_ssh_key" "sftp" {
  server_id = aws_transfer_server.sftp.id
  user_name = aws_transfer_user.sftp_user.user_name
  body      = tls_private_key.sftp.public_key_openssh
}

resource "tls_private_key" "sftp" {
  algorithm = "RSA"
  rsa_bits  = 2048
}

resource "local_file" "private_key" {
  filename = local.private_key_file
  content  = tls_private_key.sftp.private_key_pem

  provisioner "local-exec" {
    command = "chmod 600 ${local.private_key_file}"
  }
}
```

さて、ここで準備が完了したので ```terraform apply``` してアクセスしようとしても到達しない。
ここまで読んで既に気付いている人もいるだろうが、セキュリティグループの設定がここまで登場しない。

この構成のミソは、セキュリティグループはSFTPサーバではなく、あくまでもVPCエンドポイントにアタッチされているということだ。マネージメントコンソールでは、TransferをVPCタイプで起動した際に良い感じにこのアタッチをしてくれているだけだ。

そして、2020年11月時点では、1回の apply ではVPCタイプのVPCエンドポイントの情報を取得することはできないし、自分で定義したものを紐付けることもできない。

## VPCエンドポイント

仕方がないので、一旦ここで区切って、

```shell-session
$ terraform import aws_vpc_endpoint.sftp [払い出されたVPCエンドポイントID]
```

でインポートしてあげよう。
インポートの際には、以下のリソースを準備しておく。

```HCL:15_vpcendpoint.tf
###############################################################################
# VPC Endpoint                                                                #
###############################################################################
resource "aws_vpc_endpoint" "sftp" {
  # count             = 0
  vpc_endpoint_type = "Interface"
  service_name      = "com.amazonaws.ap-northeast-1.transfer.server.c-0002"

  vpc_id     = data.aws_vpc.my.id
  subnet_ids = data.aws_subnet_ids.my_vpc.ids

  security_group_ids = [
    data.aws_security_group.ssh.id,
  ]

  tags = {
    Name = local.sftp_vpce_name
  }
}
```

初回の apply 時にはこのリソースは邪魔になるので ```count = 0``` を入れておき、import が完了したらこの行をコメントアウトして再度 apply することで、エンドポイントにアタッチされたデフォルトのセキュリティグループを消しつつ、SSHのセキュリティグループを設定することが可能だ。ついでに Name で識別子を設定しておこう。

# いざ、動かす！
さて、ここまで完了したは後はアクセスするだけだ。

```shell-session
$ sftp -i [HCL:14_transfer.tfで作成した秘密鍵] [ユーザ名]@s-xxxxxxxxxxxxxxxxx.server.transfer.[リージョン].amazonaws.com
Connected to s-xxxxxxxxxxxxxxxxx.server.transfer.ap-northeast-1.amazonaws.com.
sftp> 
```

よし！接続することができた！もちろん、ちゃんと ```ls``` も ```put``` もできるので、色々試してみよう！