[AWS][AWS Transfer Family][SFTP] AWS Transfer FamilyでSFTPサーバーを立てる(パスワード認証)

## 概要

外部ユーザー向けに、SFTPサーバーを立てる要件があり、AWS Transfer Familyで立てられるようだったので、構築をしてみました。
情報はいろいろありますが、テンプレートを一部修正しないと動かなかったので修正箇所についても記載しています。

S3バケットを使用したものになります。

こちらはユーザー認証にユーザーIDとパスワードを使用します。
SFTPでユーザーIDとSSHのパブリックキーを使用する場合は、[こちらの記事](https://zenn.dev/tsonwk/articles/27dd2bc502fb2c)をご確認ください。

SSHのパブリックキーを使用するパターンの方が構築は簡単だと思いますし、セキュリティ的にも良いと思います。
ただ、ユーザーにSSHキーの作成を依頼する点のハードルが高い場合等はこちらの選択肢もあるかと思います。

構築に当たり参考にさせて頂いたサイト：
https://dev.classmethod.jp/articles/awssftp-password-auth-logical-dir/

公式：
https://docs.aws.amazon.com/ja_jp/transfer/latest/userguide/what-is-aws-transfer-family.html

それにしても、料金が高いんですね（後で知りました）
サーバーが停止含めて立ってるだけで月 `$216`
ちょっとした使用程度だと、ちょっと通らない金額だなーと思いました。

https://dev.classmethod.jp/articles/202210-aws-transfer-family-sftp/
https://aws.amazon.com/jp/aws-transfer-family/pricing/

## 前提
- AWSコンソールに接続出来る
- 各サービスの作成、編集が出来る権限を持っている
- ID・パスワードで接続出来る様にする
- ユーザーの指定ディレクト以外は操作できないようにする

## 事前準備
サーバー構築前に必要な設定を行っておきます。

### S3バケットの作成
---
S3にバケットを作成します。
今回は `sftp-server-bucket-atf` を作成しました。
バケット名以外はすべてデフォルト設定のままです。
![](https://storage.googleapis.com/zenn-user-upload/c3278ea5ce65-20230708.png)

### ロールの作成
---
SFTPの接続ユーザーに操作を許可するためのロールが必要になるため事前に作成しておきます。

#### ポリシーを作成
ロールに紐つけるポリシーを作成します。
ユーザー自身のディレクトリ以外は操作できないようなポリシーの作成方法もありますが、この条件は今回はSecretManagerのシークレット値で設定するため(論理ディレクトリ)、指定バケット以下は操作可能な汎用ポリシーを作成します。

- IAMのポリシーから「ポリシーの作成」を選択します
![](https://storage.googleapis.com/zenn-user-upload/56789c5e29df-20230708.png)
- 「アクセス許可を指定」：「JSON」での編集モードにします
![](https://storage.googleapis.com/zenn-user-upload/0c1eb1ad992f-20230708.png)
- 以下の内容を記載します
	- Resourceの`バケット名`は各自が作成したバケット名に変更してください。
```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject",
                "s3:GetObjectVersion"
            ],
            "Resource": [
                "arn:aws:s3:::sftp-server-bucket-atf",
                "arn:aws:s3:::sftp-server-bucket-atf/*"
            ]
        }
    ]
}
```
- すべての操作を許可したい場合
```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:*"
            ],
            "Resource": [
                "arn:aws:s3:::sftp-server-bucket-atf",
                "arn:aws:s3:::sftp-server-bucket-atf/*"
            ]
        }
    ]
}
```
- 入力したら「次へ」
![](https://storage.googleapis.com/zenn-user-upload/fe0f034d39f3-20230708.png)
- 「確認して作成」：ポリシー名や説明を入力します
	- ポリシー名：`SftpS3UserAccessPolicy`
	- 説明：任意（未入力でも可）
	- タグの追加：任意（未入力でも可）
![](https://storage.googleapis.com/zenn-user-upload/581a00198153-20230708.png)
- 「ポリシーの作成」を行います
![](https://storage.googleapis.com/zenn-user-upload/53b6e1933235-20230708.png)

#### ロールを作成
- IAMのロールから「ロールの作成」を選択します
![](https://storage.googleapis.com/zenn-user-upload/adacee20c17b-20230708.png)
- 「信頼されたエンティティを選択」：
	- 信頼されたエンティティタイプ：「AWSのサービス」
	- ユースケース：「Transfer」を検索・選択
	- 「次へ」
![](https://storage.googleapis.com/zenn-user-upload/0d01f59b2dcf-20230708.png)
- 「許可を追加」：先程作成したポリシー `SftpS3UserAccessPolicy` を選択し「次へ」
![](https://storage.googleapis.com/zenn-user-upload/bebb8a94dae4-20230708.png)
- 「名前、確認、および作成」：ロール名などを入力
	- ロール名：`SftpS3UserAccessRole`
	- 説明：任意（未入力でも可）
	- タグの追加：任意（未入力でも可）
![](https://storage.googleapis.com/zenn-user-upload/8873fdb5cc29-20230708.png)
- 「ロールを作成」する
![](https://storage.googleapis.com/zenn-user-upload/47974e2ba22d-20230708.png)
- 作成したロールの`arn`を後で使用します
![](https://storage.googleapis.com/zenn-user-upload/9c0f7307b479-20230708.png)

### CloudFormationのテンプレート準備
---
SFTPでパスワード認証を実現する場合、IDプロバイダーのサービスマネージドは使用できず、カスタムIDプロバイダーを使用する必要があります。
その際に使用するLambdaや必要サービスの構築にCloudFormationを使用します。
基本的にテンプレートベースになりますが、テンプレートの内容を少しだけ変更する必要があるので、先に準備をしておきます。

#### 公式テンプレートのダウンロード
まずは公式テンプレートをダウンロードします。
カスタムIDプロバイダーはLambdaを使用したものとAPI-Gateway(+Lambda)の2パターンを選べます。
今回はLambdaを使用したものを使うため、使用するテンプレートは異なりますが、修正箇所は同じになります。
ユーザー管理はSecretsManagerを使用します。
- AWS Lambda を使用してアイデンティティプロバイダーを接続する場合
	- [AWS Secrets Manager スタックテンプレート](https://s3.amazonaws.com/aws-transfer-resources/custom-idp-templates/aws-transfer-custom-idp-secrets-manager-lambda.template.yml)
- Amazon API Gateway を使用してアイデンティティプロバイダーを接続する場合
	- [AWS Secrets Manager スタックテンプレート(API Gateway)](https://s3.amazonaws.com/aws-transfer-resources/custom-idp-templates/aws-transfer-custom-idp-secrets-manager-apig.template.yml)
- その他、公式の情報やテンプレート等は[こちらをご確認ください](https://docs.aws.amazon.com/ja_jp/transfer/latest/userguide/custom-identity-provider-users.html)

今回は「AWS Lambda を使用してアイデンティティプロバイダーを接続する場合」で進めて行きます。

- ダウンロードしたファイルをテキストエディタなどで開く
- `LamdaSecretsPolicy`の`Resource`、`Fn::sub`のarnを修正
```yaml
# 107行目
- arn:${AWS::Partition}:secretsmanager:${SecretsRegion}:${AWS::AccountId}:secret:aws/transfer/*
 ↓
 arn:${AWS::Partition}:secretsmanager:${SecretsRegion}:${AWS::AccountId}:secret:*
 
# 修正後のLambdaSecretsPolicyの内容
- PolicyName: LambdaSecretsPolicy
  PolicyDocument:
    Version: '2012-10-17'
    Statement:
    - Effect: Allow
      Action:
      - secretsmanager:GetSecretValue
      Resource:
        Fn::Sub:
          - arn:${AWS::Partition}:secretsmanager:${SecretsRegion}:${AWS::AccountId}:secret:*
          - SecretsRegion:
              Fn::If:
                - SecretsManagerRegionProvided
                - Ref: SecretsManagerRegion
                - Ref: AWS::Region
                
```
- `GetUserConfigLambda`の`ZipFile`、`Fn::Sub`の内容を修正
```yaml
# 147行目
resp = get_secret("aws/transfer/" + input_serverId + "/" + input_username)
↓
resp = get_secret("SFTP/" + input_username)
```

## 構築
SFTPサーバーの構築、必要なLambda関数などをCloudFormationで作成します。
- CloudFormationに接続し、スタックページから「スタックの作成」→「新しいリソースを使用（標準）」を選択します
![](https://storage.googleapis.com/zenn-user-upload/5defc6c1cb46-20230708.png)
- 「スタックの作成」：
	- 「テンプレートの準備完了」
	- 「テンプレートファイルのアップロード」
	- 「ファイルの選択」：最初に修正したファイルを選択
	- 「次へ」
![](https://storage.googleapis.com/zenn-user-upload/ac39e462045d-20230708.png)
- 「スタックの詳細を指定」：
	- 「スタックの名前」：`sft-server-lambda-id-provider`（なんでも大丈夫です）
	- 「パラメーター」：
		- 「CreateServer」：true（AWS Transfer FamilyのSFTPサーバーも一緒に作成します）
			- falseにして別途AWS Transfer Familyの画面でサーバー作成も可能です
		- 「SecretsManagerRegion」：ap-northeast-1
			- 未入力の場合は今スタックを作っているリージョンになるっぽい？
		- 「次へ」
![](https://storage.googleapis.com/zenn-user-upload/187c6231d983-20230708.png)
- 「スタックオプションの設定」：基本的に何も変更しないで「次へ」
![](https://storage.googleapis.com/zenn-user-upload/ebc961468167-20230708.png)
- 「レビュー」：最後のチェックボックスにチェックして「送信」
![](https://storage.googleapis.com/zenn-user-upload/4642946c0e36-20230708.png)
- スタックの完了(`CREATE_COMPLETE`)を待機する
	- サーバー作成する場合は5分ほど時間がかかります
![](https://storage.googleapis.com/zenn-user-upload/05d5973e50d3-20230708.png)
- `AWS Transfer Family`サービスページでサーバーを確認する
![](https://storage.googleapis.com/zenn-user-upload/8b10ed5fe102-20230708.png)

## ユーザー作成
ユーザーの管理は`AWS Secrets Manager`を使用します。
- AWS Secrets Managerに接続し、「シークレット」→「新しいシークレットを保存する」を選択
![](https://storage.googleapis.com/zenn-user-upload/ade0926e0354-20230708.png)
- 「シークレットのタイプを選択」：
	- 「シークレットのタイプ」：その他のシークレットのタイプ
	- 「キー/値のペア」：3行を追加します
		- 「Password」：パスワードを指定
		- 「Role」：最初に作成したロールのarnを指定
		- 「HomeDirectoryDetails」：`[{"Entry": "/", "Target": "/sftp-server-bucket-atf/${Transfer:UserName}"}]`
			- 「Target」部分に「バケット名」が入っています。自身の作成したバケット名に変更してください。
			- `${Transfer:UserName}`の部分が接続ユーザー名になり、自身のユーザー名のディレクトリを指定しています。
			- このキー値を「HomeDirectory」とし、値を「/sftp-server-bucket-atf/sftp-user01/」としても接続などは可能ですが、上位のディレクトリなども見れるようになります。
			- 「HomeDirectory」指定でユーザー自身のディレクトリのみを制限する場合はIAMのポリシーなどで制御が必要になります
		- 「暗号化キー」：デフォルトのまま
		- 「次」
![](https://storage.googleapis.com/zenn-user-upload/ce853ca5226c-20230708.png)
- 「シークレットを設定」：シークレットの名前、説明を記入する
	- 「シークレットの名前」：`SFTP/sftp-user01`
		- `sftp-user01`部分が**接続するユーザー名**になるので、任意で変更してください。
		- `SFTP/`部分はLambda関数内の認証で使用するものになります
		- 「オプションの説明」：任意
		- 「タグ」：任意
		- 「その他の項目はなにも選択せずデフォルトのまま
		- 「次」
![](https://storage.googleapis.com/zenn-user-upload/df6cf4aae398-20230708.png)
- 「ローテーションを設定 - オプション」：デフォルトのまま何も設定せずに「次」
![](https://storage.googleapis.com/zenn-user-upload/520e7a860c13-20230708.png)
- 「レビュー」：内容を確認して「保存
![](https://storage.googleapis.com/zenn-user-upload/f7a82b6581b9-20230708.png)」
- リフレッシュすると追加したユーザーが表示されます
![](https://storage.googleapis.com/zenn-user-upload/631fbe7e433a-20230708.png)

## 接続・確認
接続はSFTP接続が出来るクライアント等を使用して確認してください。
例としてCyberduckでの設定を記載します
- サーバー（エンドポイント）：AWS Transfer Familyのサーバー詳細ページのエンドポイント
- ユーザー名：作成したユーザー `sftp-user01`
- パスワード：作成したパスワード
![](https://storage.googleapis.com/zenn-user-upload/2b66fe2a055f-20230708.png)
![](https://storage.googleapis.com/zenn-user-upload/4e2fe8c61a49-20230708.png)
- 接続時に初回接続メッセージが表示された場合は「許可する」
![](https://storage.googleapis.com/zenn-user-upload/60feb2841841-20230708.png)

- 接続しファイルをアップロード
![](https://storage.googleapis.com/zenn-user-upload/919ce1b34e1f-20230708.png)
- S3バケットを確認：S3バケットでユーザーディレクトリにファイルが作成されていることを確認します。
![](https://storage.googleapis.com/zenn-user-upload/b251432862dc-20230708.png)

## その他：SFTP管理者用のユーザー作成
全ユーザーの内容を確認・修正出来る管理者的なユーザーの作成になります。
基本的に「ユーザー作成」と同じで、「HomeDirectoryDetails」のTarget部分が少し違うだけになります。
- 「キー/値のペア」：3行を追加します
	- 「Password」：パスワードを指定
	- 「Role」：最初に作成したロールのarnを指定
	- 「HomeDirectoryDetails」：`[{"Entry": "/", "Target": "/sftp-server-bucket-atf"}]`
		- Targetの部分の`/${Transfer:UserName}`を削除し、`/バケット名` のみにする

あとは`sftp-admin`みたいなユーザー名とかで同じ様に作成していけば大丈夫です。


以上です。