TransferFamilyへの認証認可をLambdaとSecretsManagerでお手軽に実装してみた

# はじめに
S3にFTPでファイルの送受信を行いたいと考えたとき、マネージドなFTP
のサービスであるTransfer Familyが第一候補にあがります。

Transfer Familyにファイルアップロードを行うにあたって必要になるのがユーザー管理です。
ユーザー管理には以下の3つのタイプがあります。

1. AWS Transfer Familyのサービス管理
2. AWS Directory Service for Microsoft Active Directory (AD管理)
3. カスタム ID プロバイダー

ADを使えばユーザー管理やアクセス権限の制御はできるものの、例えばユーザー数が少ない場合には少し過剰な気もします。また、FTP（SFTPではない）を利用する場合には カスタムIDプロバイダー しか利用することができません。そのため、今回は以下のような想定で3.のカスタムIDプロバイダーによる認証を実装してみます。

1. FTPを利用
2. 利用者が数ユーザーのみと小規模

# やること
今回はLambdaとSecretsManagerを利用した認証の仕組みを作成します。

# IAMポリシーの作成
まずは、FTPを利用するユーザーの権限を管理するために、IAMポリシーを作成します。
IAMポリシーのダッシュボードから、ポリシーを作成 を押します。
![2022-11-13-14-43-54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964981/75da476c-d00c-7710-2667-ff8ca49c2423.png)

ポリシーをJSON形式で記載するので、JSON タブを選択します。
![2022-11-13-14-44-21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964981/a3755853-e90d-4c5a-0c49-b6895af9516d.png)

FTPユーザーに「どのS3バケットの読み書き権限を与えるか」をポリシーで定義します。
以下のJSONの「bucketname」部分をユーザーにアクセスさせたいバケット名に置換して、マネジメントコンソールに貼り付けます。
```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid":"ReadWriteS3",
      "Action": [
            "s3:ListBucket"
                ],
      "Effect": "Allow",
      "Resource": ["arn:aws:s3:::bucketname"]
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObject",              
        "s3:DeleteObjectVersion",
        "s3:GetObjectVersion",
        "s3:GetObjectACL",
        "s3:PutObjectACL"
      ],
      "Resource": ["arn:aws:s3:::bucketname/*"]
    }
  ]
}    
```


貼り付けたら、次のステップ をを押します。
ここで記載したS3バケットに対して、FTPユーザーはアクセス権限が付与されます。
![2022-11-13-14-47-32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964981/0749f4dc-f7e0-1073-7eca-5c5bc07e118a.png)

タグの追加画面はデフォルトのままで、もう一度 次のステップ:確認 を押します。
![2022-11-13-14-48-21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964981/c0a171c4-9384-9d51-91f5-94b9ac7e364e.png)

ポリシーに名前をつけます。
ここでは、「UserA」というFTPユーザーに対してのポリシーを想定しています。
ポリシーの作成をクリックして作成完了です。
![2022-11-13-14-49-08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964981/f67f7085-a9b6-3d98-8ecf-0b6653966eef.png)

ユーザーごとに権限を変える場合は、ここでユーザーの数だけポリシーを作成すればOKです。

# IAMロールの作成
次に、IAMロールを作成します。
LambdaでユーザーIDとパスワードの認証を行った後、IAMロールをReturnすることで、ユーザーがどのAWSリソースを操作できるのか、認可を行うために利用します。

IAMからロールを作成していきましょう
![2022-11-13-14-41-09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964981/b2dae704-eb90-bbf1-2ad0-330c9c382127.png)

信頼されたエンティティタイプで、AWSのサービス にチェックを入れます。
![2022-11-13-14-41-43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964981/3dc64dec-0982-4729-7f5d-8ddc2855c5f5.png)

ユースケースでは、他のAWSサービスのユースケースで「Transfer」を選択します。
そして 次へ を押します。
![2022-11-13-14-42-11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964981/1815ccb2-26fa-4cc8-8a0f-b33618265a70.png)


次に、このIAMロールに許可されるポリシーを追加します。
AWSTransferFullAccess を検索して、チェックを入れましょう。
![2022-11-13-14-50-41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964981/b990428b-8a32-4477-dd0e-fb263dc3461e.png)

これだけだと、TransferFamilyに対する許可しか付与されていません。
S3のアクセス権限を付与するため、先程作成したFTPTransfer-UserA-Policyも追加しましょう。
追加ができたら 次へ を押します。
![2022-11-13-14-51-21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964981/f0007218-a288-3c5a-f747-1c55defbc860.png)

そして、このIAMロールに名前をつけます。
ここでは FTPTransder-UserA-Role としています。
![2022-11-13-14-52-04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964981/ab0aad17-6994-3cbe-b2e6-ab0dbdaa17b6.png)


ステップ2: 許可を追加する　で、2つのポリシーが付与されていることを確認します。
2つのポリシーが確認できたら ロールの作成 を押します。
![2022-11-13-14-52-15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964981/1fd54906-32e2-570a-7a5d-4368596c5804.png)

ロールの一覧画面に戻るので、作成したロールを検索します。
![2022-11-13-14-53-09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964981/8c4207ef-2c31-df68-d6a2-392487267089.png)

ロール名をクリックすると詳細が表示されるので、ARN欄の値をコピーしておきましょう。
これは、Lambda関数でFTPユーザーに権限を払い出す際に利用します。
![2022-11-13-14-53-35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964981/f9a94561-9fec-2741-21c9-1462ad2dc7db.png)

# Secret Managerの設定
いよいよSecretsManagerの設定です。
新しいシークレットを保存する を押します。
![2022-11-13-14-39-54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964981/8e8a28e9-3d88-eedc-8d3b-11a6b9e4d440.png)

そして、利用するシークレットの値を保存していきます。
ここでは、UserAというFTPユーザーのデータを保存します。
以下の通り設定しましょう。

- シークレットのタイプ
    - その他のシークレットのタイプ
- キー/値
  - Password / パスワードの値
  - Role / 先程コピーしたIAMロールのARN

設定できたら 次へ を押します。

![2022-11-13_14-54-27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964981/f1673844-4415-efb1-c7a9-8b5d7ced8c86.png)


シークレットの名前をつけて、次へ
![2022-11-13-14-57-20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964981/bfdc0c4a-c2a2-0a67-e539-d359585c6774.png)


ローテーションの設定は特に行わないので、次へ
![2022-11-13-14-58-10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964981/de2285f8-e8a2-a60d-9540-e778e4d4bb56.png)


レビュー画面で設定を確認し、保存
![2022-11-13-14-58-31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964981/e660e00c-4510-983c-b5fa-f192f957b0fd.png)

## Lambda関数の作成
ここまでで、FTPユーザーであるUserAの各種設定ができました。
UserAがどのS3バケットを見ることができるのか、どんなパスワードでログインできるのか、といった情報などの設定ができているので、ここからはLambda関数で認証認可の部分を作っていきます。

### 実行権限の設定
Lambdaは既に空のLambdaが作成済みである想定です。
デフォルトのままだと、LambdaはSecretsManagerにアクセスする権限がないため、ここではLambdaのIAMロールにSecretsManagerへの実行権限を付与していきます。

Lambda関数を作成した後、設定 -> アクセス権限 から 実行ロール 欄を確認します。
ロール名を押して、Lambdaに付与されているIAMロールを編集していきましょう。
![2022-11-13-15-13-19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964981/b0d745ec-ba66-ab53-2edc-0440acfe911f.png)

許可を追加 -> ポリシーをアタッチ を押します。
![2022-11-13-15-13-45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964981/b6e0774f-f799-689f-a850-e18c2bce7690.png)

SecretsManagerReadWriteにチェックを入れて、ポリシーをアタッチ を押します。
これで、LambdaがSecretsManagerから値を取得することができるようになりました。
![2022-11-13-15-14-08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2964981/7e0e290c-7759-f146-0958-892f9316bb3a.png)

それでは、Lambdaの実装を行います。
以下のコードをコピペしましょう。

```python
import json
import boto3
from botocore.exceptions import ClientError

REGION_NAME = "ap-northeast-1"

def lambda_handler(event, context):
    
    secret_name = "FTP/{}".format(event["username"])
    
    # Screts Managerのクライアントを作成
    session = boto3.session.Session()
    client = session.client(
        service_name='secretsmanager',
        region_name=REGION_NAME
    )
    
    try:
        get_secret_value_response = client.get_secret_value(
            SecretId=secret_name
        )
    except ClientError as e:
        # For a list of exceptions thrown, see
        # https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html
        raise e

    # 取得したシークレットの値を使いやすいように加工
    secret = json.loads(get_secret_value_response['SecretString'])
    PASSWORD = secret['Password']
    ROLE = secret['Role']
    
    # パスワードチェック
    if event["password"] == PASSWORD:
        return { 'Role': ROLE }
    else:
        return {}
```

実行していることは単純です。
ユーザーがFTPクライアントから接続する際に、ユーザーIDとパスワードを入力します。
それぞれの値がLambdaのeventに`username` `password`として渡されます。

このLambda関数では、渡された`username`の値をキーとして、SecretsManagerから`password`とS3へのアクセス権限を定義した`Role`の値を取得します。

そして、ユーザーから入力されたパスワードの値と、SecretsManagerから取得した`Password`の値が等しいかを確認し、正しい場合はRoleをReturnしています。
```python
    # 取得したシークレットの値を使いやすいように加工
    secret = json.loads(get_secret_value_response['SecretString'])
    PASSWORD = secret['Password']
    ROLE = secret['Role']
    
    # パスワードチェック
    if event["password"] == PASSWORD:
        return { 'Role': ROLE }
    else:
        return {}
```
# 動作確認
FTPクライアントからTransferFamilyに対して接続を行い、ユーザーIDに`UserA`、パスワードはSecretsManagerに設定したパスワードを入力し、ログイン確認を行いましょう。

事前にポリシーで定義したバケットにしかアクセス権限はないため、FTPクライアント側にバケットのパスを設定しておくことを忘れずに。

# 検証を通じてわかったこと
今回の記事では、Secret Managerを利用してセキュアにFTPのユーザー認証を行いました。
ですが、検証目的だったら以下に記載のような超シンプルなコードでも動きます。

```python
def lambda_handler(event, context):
    if event["username"] == "hoge" and event["password"] == "hogehoge":
        return { 'Role': 'arn:aws:iam::{ARN}:role/{RoleName}' }
    else:
        return {}
```

まずはお手軽に検証を行った上で、最終的にはSecret Managerを使ってセキュアにするのが良いかもしれませんね。

# 参考URL
[AWS Transfer Family のカスタムIDプロバイダーで AWS Secrets Manager を使用して パスワード認証を有効にしてみる | DevelopersIO](https://dev.classmethod.jp/articles/aws-transfer-family-with-apigateway-custom-id-provider-aws-secrets-manager/)