Edited at

AWS LambdaでAmazon RDS for MySQLへ接続する(Node.js 4.3 + KMSで暗号化したMySQL接続パスワードをkms.decryptで復号化してMySQLヘ接続 + バッチ実行をSNS通知する)

More than 3 years have passed since last update.


はじめに

Node.js 4.3でAWS Lambdaファンクションを作成し、MySQL(Amazon RDS for MySQL)へ接続するサンプルです。

Lambdaファンクション内のMySQL接続パスワードは平文ではなく、AWS Key Management Service(KMS)により暗号化したMysQL接続パスワード文字列を記載し、Lambdaファンクション実行時にkms.decryptでパスワードを復号化してMySQLデータベースへ接続します。

また、Lambdaファンクションの実行をAWS SNSでメール通知します。


参考資料

以下のページを参考にさせて頂きました。ありがとうございました。

http://qiita.com/Keisuke69/items/cba4b501e91da95188f8

http://qiita.com/na0AaooQ/items/cfd9f2babd2ce604049b


作業用マシン

Lambdaファンクションのデプロイパッケージ作成はAmazon LinuxのEC2インスタンスで行いました。

Lambdaファンクションのデプロイパッケージ作業用マシンは以下のAMIで作成しました。

・Amazon Linux AMI 2016.03.0 (HVM), SSD Volume Type - ami-f80e0596


テスト用のAmazon RDS for MySQL

まず、Amazon RDS for MySQLインスタンスを作成し、テストデータを追加します。

[ec2-user@example-server ~]$ hostname

example-server
[ec2-user@example-server ~]$ mysql -u root -p -h example-rds-mysql-server.carvmoii2uds.ap-northeast-1.rds.amazonaws.com
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 12
Server version: 5.6.27-log MySQL Community Server (GPL)

Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP ON exampledb.* TO example_user@'192.168.0.0/255.255.0.0' IDENTIFIED BY 'exam
ple_password';
Query OK, 0 rows affected (0.00 sec)

mysql> DELETE FROM exampledb.example_table;
Query OK, 6 rows affected (0.00 sec)

mysql> SELECT * FROM exampledb.example_table;
Empty set (0.00 sec)

mysql> INSERT INTO exampledb.example_table( id, insert_date) VALUES( 1, '2016-03-10 12:30:00' );
Query OK, 1 row affected (0.01 sec)

mysql> INSERT INTO exampledb.example_table( id, insert_date) VALUES( 2, '2016-03-20 12:30:00' );
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO exampledb.example_table( id, insert_date) VALUES( 3, '2016-03-30 13:40:00' );
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO exampledb.example_table( id, insert_date) VALUES( 4, '2016-04-05 14:10:00' );
Query OK, 1 row affected (0.01 sec)

mysql> INSERT INTO exampledb.example_table( id, insert_date) VALUES( 5, '2016-04-10 22:00:00' );
Query OK, 1 row affected (0.01 sec)

mysql> SELECT * FROM exampledb.example_table;
+------+---------------------+
| id | insert_date |
+------+---------------------+
| 1 | 2016-03-10 12:30:00 |
| 2 | 2016-03-20 12:30:00 |
| 3 | 2016-03-30 13:40:00 |
| 4 | 2016-04-05 14:10:00 |
| 5 | 2016-04-10 22:00:00 |
+------+---------------------+
5 rows in set (0.00 sec)

mysql> SELECT * FROM exampledb.example_table WHERE insert_date <= date_sub(curdate(), interval 2 week);
+------+---------------------+
| id | insert_date |
+------+---------------------+
| 1 | 2016-03-10 12:30:00 |
| 2 | 2016-03-20 12:30:00 |
+------+---------------------+
2 rows in set (0.00 sec)

mysql>


Lambda用のIAMロール設定

IAMロール[lambda_basic_vpc_execution]に以下のIAMポリシーを設定する。

・KMSによる暗号化したパスワード復号化用のIAMポリシー

{

"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1448696327000",
"Effect": "Allow",
"Action": [
"kms:*"
],
"Resource": [
"arn:aws:kms:ap-northeast-1:**************:key/KMSの暗号化キーのARNを指定する"
]
}
]
}

・SNS通知用のIAMポリシー

{

"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1460306102000",
"Effect": "Allow",
"Action": [
"sns:Publish"
],
"Resource": [
"arn:aws:sns:ap-northeast-1:***********:example-lambda-mysql-sns"
]
}
]
}

・Lambda VPC用のポリシー

{

"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
},
{
"Effect": "Allow",
"Action": [
"ec2:CreateNetworkInterface",
"ec2:DescribeNetworkInterfaces",
"ec2:DetachNetworkInterface",
"ec2:DeleteNetworkInterface"
],
"Resource": "*"
}
]
}


作業用マシンにNode.jsとnpmインストール

作業用マシン(Amazon Linux)にNode.jsとnpmをインストールします。

[ec2-user@example-server ~]$ hostname

example-server
[ec2-user@example-server ~]$ sudo yum -y update

[ec2-user@example-server ~]$ uname -a
Linux example-server 4.4.5-15.26.amzn1.x86_64 #1 SMP Wed Mar 16 17:15:34 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
[ec2-user@example-server ~]$

[ec2-user@example-server ~]$ sudo yum install nodejs npm --enablerepo=epel

[ec2-user@example-server ~]$ node -v
v0.10.42
[ec2-user@example-server ~]$ npm -v
1.3.6
[ec2-user@example-server ~]$


AWS Lambdaファンクションのデプロイパッケージを作成する

[ec2-user@example-server ~]$ mkdir /home/ec2-user/example-lambda-mysql-select

[ec2-user@example-server ~]$
[ec2-user@example-server ~]$ cd /home/ec2-user/example-lambda-mysql-select
[ec2-user@example-server example-lambda-mysql-select]$ pwd
/home/ec2-user/example-lambda-mysql-select
[ec2-user@example-server example-lambda-mysql-select]$ npm install mysql

Lambdaファンクションのコードを[example.js]ファイルとして保存します。

[example.js]ファイルには後述の[Node.jsのAWS Lambdaファンクションサンプルコード(example.js)]の内容を記述します。

[ec2-user@example-server example-lambda-mysql-select]$ vi /home/ec2-user/example-lambda-mysql-select/example.js

Lambdaファンクションのデプロイパッケージを作成します。zipでexample-lambda-mysql-select配下を固めます。

[ec2-user@example-server example-lambda-mysql-select]$ pwd

/home/ec2-user/example-lambda-mysql-select
[ec2-user@example-server example-lambda-mysql-select]$ zip -r example-lambda-mysql-select.zip .

[ec2-user@example-server example-lambda-mysql-select]$ ls -lrta /home/ec2-user/example-lambda-mysql-select/
total 272
drwxrwxr-x 3 ec2-user ec2-user 4096 Apr 11 00:55 node_modules
-rw-rw-r-- 1 ec2-user ec2-user 2649 Apr 11 00:55 example.js
drwx------ 21 ec2-user ec2-user 4096 Apr 11 00:55 ..
-rw-rw-r-- 1 ec2-user ec2-user 258434 Apr 11 00:56 example-lambda-mysql-select.zip
drwxrwxr-x 3 ec2-user ec2-user 4096 Apr 11 00:56 .
[ec2-user@example-server example-lambda-mysql-select]$

zipファイル化したデプロイパッケージをS3へアップロードします。このzipファイルは後でLambdaファンクション作成時に利用します。

[ec2-user@example-server example-lambda-mysql-select]$ aws s3 cp /home/ec2-user/example-lambda-mysql-select/example-lambda-mysql-select.zip s3://適当なS3バケット/

upload: ./example-lambda-mysql-select.zip to s3://適当なS3バケット/example-lambda-mysql-select.zip
[ec2-user@example-server example-lambda-mysql-select]$


Node.jsのAWS Lambdaファンクションサンプルコード(example.js)

以下がLambdaファンクションのコードです。


example.js

var AWS = require('aws-sdk');

var mysql = require('mysql');

// KMSにより暗号化したMySQL接続用のパスワード文字列
var kmsEncyptedToken = "CiC*********(KMSで暗号化したMySQL接続用のパスワード文字列)***********UI=";

// 接続先のMySQLサーバ情報
var mysql_host = "example-rds-mysql-server.carvmoii2uds.ap-northeast-1.rds.amazonaws.com";
var mysql_user = "example_user";
var mysql_dbname = "exampledb";
var mysql_password = "";

var sns_topic_arn = "arn:aws:sns:ap-northeast-1:*******************:example-lambda-mysql-sns";

exports.handler = function(event, context){

// 実行するSQL文
var sql = "SELECT * FROM exampledb.example_table WHERE insert_date <= date_sub(curdate(), interval 2 week)";

if (kmsEncyptedToken && kmsEncyptedToken !== "<kmsEncryptedToken>") {

var encryptedBuf = new Buffer(kmsEncyptedToken, 'base64');
var cipherText = {CiphertextBlob: encryptedBuf};

var kms = new AWS.KMS({ region: 'ap-northeast-1' });
var sns = new AWS.SNS({ region: 'ap-northeast-1' });

// KMSにより暗号化したパスワード文字列の復号化
kms.decrypt(cipherText, function (err, data) {
if (err) {

console.log("CipherText Decrypt error: " + err);
context.fail(err);

} else {

mysql_password = data.Plaintext.toString('ascii');

var connection = mysql.createConnection({
host : mysql_host,
user : mysql_user,
password : mysql_password,
database : mysql_dbname
});

console.log("MySQL Server Name: " + mysql_host);
console.log("MySQL User Name: " + mysql_user);
console.log("MySQL Database Name: " + mysql_dbname);
console.log("MySQL Exec SQL: " + sql);

// MySQLデータベースへの接続
connection.connect();

// MySQLデータベースでSQL実行
connection.query(sql, function(err, rows, fields) {
if (err) {

console.log("MySQL Select Error");
context.fail(err);
sns.publish({
Message: 'Lambda Function Error',
Subject: 'Lambda Function Error',
TopicArn: sns_topic_arn
}, function(err, data){
if(err) throw err;
else context.fail('SNS Publish Error');
});

throw err;

} else {

console.log("MySQL Select Success");
console.log(rows);
console.log(fields);
}
});

connection.end(function(err) {

sns.publish({
Message: 'Lambda Function Success',
Subject: 'Lambda Function Success',
TopicArn: sns_topic_arn
}, function(err, data){
if(err) throw err;
else context.fail('SNS Publish Error');
});

context.done();
});
}
});

} else {

context.fail("kmsEncyptedToken has not been set.");

}

console.log('end');

};



Lambdaファンクション作成

先ほど作成したLambdaのデプロイパッケージ[example-lambda-mysql-select.zip]をLambdaにアップロードして、Lambdaファンクションを作成します。

前述の手順でS3にパッケージアップロードしていると思いますので、それを選択します。

 s3://適当なS3バケット/example-lambda-mysql-select.zip

LambdaファンクションのIAMポリシーは[lambda_basic_vpc_execution]を選択、セキュリティグループは、Amazon RDS for MySQLインスタンスへMySQL接続可能な設定のセキュリティグループを選択して、Lambdaファンクションを作成します。


Lambdaファンクション実行

Lambdaファンクションを実行し、正常にMySQLデータベースへ接続出来れば、Lambdaの[Log outPut]に以下のようにMySQLデータベースのSELECT結果が表示されます。

また、ANS SNSで指定したメールアドレス宛に[Lambda Fanction Success]メールが届きます。

START RequestId: 442b53cf-ff43-11e5-9dc8-2123a6cf68bb Version: $LATEST

2016-04-10T17:40:08.926Z 442b53cf-ff43-11e5-9dc8-2123a6cf68bb end
2016-04-10T17:40:10.127Z 442b53cf-ff43-11e5-9dc8-2123a6cf68bb MySQL Server Name: example-rds-mysql-server.carvmoii2uds.ap-northeast-1.rds.amazonaws.com
2016-04-10T17:40:10.183Z 442b53cf-ff43-11e5-9dc8-2123a6cf68bb MySQL User Name: example_user
2016-04-10T17:40:10.183Z 442b53cf-ff43-11e5-9dc8-2123a6cf68bb MySQL Database Name: exampledb
2016-04-10T17:40:10.183Z 442b53cf-ff43-11e5-9dc8-2123a6cf68bb MySQL Exec SQL: SELECT * FROM exampledb.example_table WHERE insert_date <= date_sub(curdate(), interval 2 week)
2016-04-10T17:40:10.346Z 442b53cf-ff43-11e5-9dc8-2123a6cf68bb MySQL Select Success
2016-04-10T17:40:10.346Z 442b53cf-ff43-11e5-9dc8-2123a6cf68bb [ RowDataPacket { id: 1, insert_date: Thu Mar 10 2016 12:30:00 GMT+0000 (UTC) },
RowDataPacket { id: 2, insert_date: Sun Mar 20 2016 12:30:00 GMT+0000 (UTC) } ]
2016-04-10T17:40:10.567Z 442b53cf-ff43-11e5-9dc8-2123a6cf68bb [ FieldPacket {
catalog: 'def',
db: 'exampledb',
table: 'example_table',
orgTable: 'example_table',
name: 'id',
orgName: 'id',
charsetNr: 63,
length: 12,
type: 3,
flags: 0,
decimals: 0,
default: undefined,
zeroFill: false,
protocol41: true },
FieldPacket {
catalog: 'def',
db: 'exampledb',
table: 'example_table',
orgTable: 'example_table',
name: 'insert_date',
orgName: 'insert_date',
charsetNr: 63,
length: 19,
type: 12,
flags: 128,
decimals: 0,
default: undefined,
zeroFill: false,
protocol41: true } ]
END RequestId: 442b53cf-ff43-11e5-9dc8-2123a6cf68bb
REPORT RequestId: 442b53cf-ff43-11e5-9dc8-2123a6cf68bb Duration: 2670.49 ms Billed Duration: 2700 ms Memory Size: 128 MB Max Memory Used: 30 MB


以上になります。