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

More than 1 year has passed since last update.


概要

現在開発している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 v8.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 = 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からデータを取得することに成功 🙆🙆

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


参考