概要

現在開発しているburariというおでかけアプリでは、APIGatewayからLambdaを実行し、RDSのデータを取得している。
以前まで、LambdaからRDSへの安全な接続にはVPCが必須となっており、AWS Lambdaの定期実行をSAMを使って実装するにも書いたとおり、コールドスタート問題が懸念になっていたが、IAM認証でLambdaからRDSに接続できるようになったため、ついにVPCから解放される日がきた。VPCから解放されることにより、LambdaからSNSやKMSへの接続にNATが不要になるため、お財布にも優しいサービス運営が待ち受けている。
今回は接続まわりを実装してみる 💪💪

環境

  • Amazon RDS (MySQL v5.7.17, t2.micro)
  • AWS Lambda (Node.js v6.10)
  • Sequelize (ORM for Node.js)

制限

以下のような同時接続数への制限が発生するため、t2.microの場合は接続数を10以下に制限する必要がある。

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

RDSの「IAM DB Authentication Enabled」をオンにする

今回は既存のDBの設定をAWS Console上から変更する。

Screen Shot 2017-08-23 at 10.15.24 PM.png

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

DB Userの作成

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

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

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

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

IAM Policyの作成と、IAM Roleへの付与

以下のようなPolicyを作成する。dbi-resource-idはConsole上の「Resource ID」に記載されている。

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からデータを取得する。

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

export const handler = (event, context, callback) => {
    const connect = () => {
        return new Promise((resolve, reject) => {
            new AWS.RDS.Signer({
                'region': 'ap-northeast-1',
                'username': 'iam_user',
                'hostname': 'dbname.xxxxxxxx.ap-northeast-1.rds.amazonaws.com',
                'port': XXXX
            }).getAuthToken((error, token) => {
                if(error) {
                    reject(error);
                    return;
                }

                resolve(token);
            });
        });
    };

    const fetch = (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();
    };

    connect().then(fetch).then((data) => {
        context.succeed(data);

    }).catch((error) => {
        context.fail(error);
    });
}

実行結果

RDSからデータを取得することに成功 🙆🙆

Screen Shot 2017-08-24 at 1.28.28 AM.png

参考

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.