AWS Lambda(Node.js)からAmazon RDSへIAM認証で接続する

# 概要
現在開発している[burari](https://appsto.re/i6dX8tB)というおでかけアプリでは、APIGatewayからLambdaを実行し、RDSのデータを取得している。
以前まで、LambdaからRDSへの安全な接続にはVPCが必須となっており、[AWS Lambdaの定期実行をSAMを使って実装する](http://qiita.com/xen/items/e07736382229fed50727)にも書いたとおり、コールドスタート問題が懸念になっていたが、[IAM認証でLambdaからRDSに接続できるようになった](http://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html)ため、ついにVPCから解放される日がきた。VPCから解放されることにより、LambdaからSNSやKMSへの接続にNATが不要になるため、お財布にも優しいサービス運営が待ち受けている。
今回は接続まわりを実装してみる 💪💪

# 環境
* Amazon RDS (MySQL v5.7.17, t2.micro)
* AWS Lambda (Node.js v8.10)
* [Sequelize](http://sequelizejs.com) (ORM for Node.js)

# 制限
以下のような同時接続数への制限が発生するため、t2.microの場合は接続数を10以下に制限する必要がある。
> IAM データベース認証を使用する場合は、db.t2.micro インスタンスクラスを使用している場合を除き、1 秒あたりの接続数を 20 以下に制限する必要があります。この場合、1 秒あたりの接続数を 10 以下に制限する必要があります。

# RDSの「IAM DB Authentication Enabled」をオンにする
今回は既存のDBの設定をAWS Console上から変更する。

<img width="544" alt="Screen Shot 2017-08-23 at 10.15.24 PM.png" src="https://qiita-image-store.s3.amazonaws.com/0/118028/5ed88eb5-b092-d826-886f-a0dc8af40b3c.png">

変更するInstanceを選択し、Instance ActionsのModifyから、上記の「IAM DB Authentication Enabled」をYESにすればOK。

# DB Userの作成
Masterユーザーでログインし、IAM認証用のユーザーを作成する。このときHostには「%」を指定。

```sql
CREATE USER 'iam_user'@'%' IDENTIFIED WITH AWSAuthenticationPlugin as 'RDS';
```

そしてSSL経由での使用を許可するよう変更。

```sql
GRANT SELECT,INSERT,UPDATE,DELETE ON dbname.* to 'iam_user'@'%' REQUIRE SSL;
```

# IAM Policyの作成と、IAM Roleへの付与
以下のようなPolicyを作成する。dbi-resource-idはConsole上の「Resource ID」に記載されている。

```json:AmazonRDSIAMAccess
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "rds-db:connect"
            ],
            "Resource": [
                "arn:aws:rds-db:ap-northeast-1:1234567890:dbuser:<dbi-resource-id>/iam_user"
            ]
        }
    ]
}
```

Policy作成後、Lambdaを実行するRoleに上記PolicyをAttachする。

# コードの実装
Tokenを取得した後、Sequelizeを利用してRDSからデータを取得する。

```js:index.js
import AWS from 'aws-sdk';
import Sequelize from 'sequelize';

export const handler = async (event, context, callback) => {
    const connect = async () => {
        const signer = new AWS.RDS.Signer({
            'region': 'ap-northeast-1',
            'username': 'iam_user',
            'hostname': 'dbname.xxxxxxxx.ap-northeast-1.rds.amazonaws.com',
            'port': XXXX
        });

        let token;
        await signer.getAuthToken((error, result) => {
            if(error) {
                throw error;
            }

            token = result;
        });

        return token;
    };

    const fetch = async (token) => {
        const options = {
            'host': 'dbname.xxxxxxxx.ap-northeast-1.rds.amazonaws.com',
            'port': XXXX,
            'ssl': true,
            'dialect': 'mysql',
            'dialectOptions': {
            'ssl': 'Amazon RDS',
            'authSwitchHandler': (data, callback) => {
                if (data.pluginName === 'mysql_clear_password') {
                    const password = token + '\0';
                    const buffer = Buffer.from(password);
                    callback(null, buffer);
                }
            }
        };

        const sequelize = new Sequelize('dbname', 'iam_user', token, options);
        const model = sequelize.define('table_name', {
            'tableId': { 'type': Sequelize.STRING, 'primaryKey': true },
            'name': Sequelize.STRING
        });

        return model.findAll();
    };

    try {
        const token = await connect();
        const data = await fetch(token);
        context.succeed(data);

    } catch(error) {
        context.fail(error);
    }
}
```

# 実行結果
RDSからデータを取得することに成功 🙆🙆

<img width="986" alt="Screen Shot 2017-08-24 at 1.28.28 AM.png" src="https://qiita-image-store.s3.amazonaws.com/0/118028/3d5cc8df-3dc8-bbaf-67c4-1b7f313a5cd2.png">

# 参考
* [MySQL および Amazon Aurora に対する IAM データベース認証](http://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html)
* [【全世界待望】Public AccessのRDSへIAM認証(+ SSL)で安全にLambda Pythonから接続する](http://blog.serverworks.co.jp/tech/2017/04/27/rds-iam-auth-lambda-python/)
* [It fails connection when server uses PAM](https://github.com/sidorares/node-mysql2/issues/438#issuecomment-255343793)