概要
現在開発している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上から変更する。
変更する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」に記載されている。
{
"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からデータを取得する。
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からデータを取得することに成功 🙆🙆