AWS LambdaでKMSを使ってセキュアにRDSへ接続する(インフラ編)

この記事は[WHITEPLUS Advent Calendar](http://qiita.com/advent-calendar/2016/whiteplus "")  2016 10日目になります。

こんにちは。株式会社ホワイトプラス、エンジニアの @hachihiro224です。
一応マネージャーをさせてもらっているので、フロントエンド、バックエンド、インフラといろいろな領域を見ています。

今日は弊社で利用しているAWS Lambdaの環境構築について紹介します。

## RDSに接続するAWS Lambdaの環境構築

利用するサービスは次の3つです。

- AWS Lambda(処理本体)
- AWS RDS(データベース)
- AWS KMS(接続情報の暗号化)

次の順で構築していきます。

1. Lambdaを配置するVPC Subnetの作成
1. KMSを利用した接続情報の暗号化
1. RDSに接続するLambda Functionの生成

## Lambdaを配置するVPC Subnetの作成
VPCにLamba Functionを配置する際に気をつけるポイントです。

- KMSを利用するためにはインターネットにアクセス出来る必要がある
- パブリックサブネットに配置してもインターネットにアクセスできない

ということで、プライベートサブネットにNATゲートウェイを配置して、インターネットアクセス可能にします。

こちらの記事を参考に構築しました。
http://yoshidashingo.hatenablog.com/entry/2015/12/18/041217

サブネットは異なるアベイラビリティゾーンのものを一つずつ用意します。

## KMSを利用した接続情報の暗号化
RDSに接続する際、考慮しなければならないのが接続情報の管理です。
KMSを利用すると接続情報を暗号化し、安心してコードに載せることができます。

KMSの利用はこちらの記事を参考にしました。
http://dev.classmethod.jp/cloud/decrypt-sensitive-data-with-kms-on-lambda-invocation/

環境を構築したら次のコマンドで接続情報を暗号化します。

```
# db_host
aws kms encrypt --key-id $KEYID --plaintext 'db_host'
# db_user
aws kms encrypt --key-id $KEYID --plaintext 'db_user'
# db_password
aws kms encrypt --key-id $KEYID --plaintext 'db_password'
```

## RDSに接続するLambda Functionの生成
いよいよLambdaでRDSに接続します。

次のような作業ディレクトリを作ります。

```
sample_function
├── create-function.sh
└── src
    └── sample_function.py
```

必要なパッケージをインストールします。

```
pip install boto3 PyMySQL
```

RDSに接続するlambda functionを作成します。

``` sample_function.py
# -*- coding: utf-8 -*-

import sys
import logging
import pymysql
import base64
import boto3

logger = logging.getLogger()
logger.setLevel(logging.INFO)
kms = boto3.client('kms')


def decrypt(encrypted):
    return kms.decrypt(CiphertextBlob=base64.b64decode(encrypted))['Plaintext']

# rds settings
rds_host = decrypt('暗号化されたdb_host')
name = decrypt('暗号化されたdb_user')
password = decrypt('暗号化されたdb_password')
db_name = 'データベース名'

try:
    conn = pymysql.connect(rds_host, user=name, passwd=password, db=db_name, connect_timeout=5, charset='utf8')
except:
    logger.error("ERROR: Unexpected error: Could not connect to MySql instance.")
    sys.exit()


def lambda_handler(event, context):
    with conn.cursor() as cur:
        num = cur.execute('SELECT NOW()')
        for row in cur:
            logger.info('接続確認 %s' % row[0])

        return "Got %d items from RDS MySQL table" % num
```

Lambda Functionの実行に必要なIAM ROLEを作成します。
VPC権限: https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/vpc.html
KMS権限: インラインポリシーで以下を追加

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1448696327000",
            "Effect": "Allow",
            "Action": [
                "kms:Decrypt"
            ],
            "Resource": [
                "arn:aws:kms:ap-northeast-1:123456789012:key/xxx-yyy-zzz"
            ]
        }
    ]
}
```
lambda functionをAWSに登録します。
sample-function直下で以下のシェルを実行します。

``` create-function.sh
#!/bin/bash

# TODO: update function_name
FUNCTION_NAME=sample_function

cd "$(dirname "$0")"/src
mkdir bundle
cp *.py bundle/
cd bundle

pip freeze > require.txt
[ -s require.txt ] && pip install -r require.txt -t .

zip -r upload.zip .

aws lambda create-function \
    --function-name ${FUNCTION_NAME} \
    --runtime python2.7 \
    --role #lambdaとKMSが使えるIAM ROLEのarn \
    --handler ${FUNCTION_NAME}.lambda_handler \
    --vpc-config SubnetIds=#1.で作成したvpc subnet,SecurityGroupIds=#RDSに接続可能なsecurity group \
    --zip-file fileb://upload.zip \
    --timeout 60

cd .. && rm -rf bundle
```

以下のコマンドでLambda Functionを実行します。

```
aws lambda invoke --function-name sample_function --payload '' response && cat response
```

無事にRDSへ接続するLambda Functionの作成ができました。

## まとめ
LambdaでのVPC接続はVPCの設定、KMSでの接続情報の暗号化など関連するタスクがあり、始めるまでのハードルが高めです。
今回紹介した内容が少しでも参考になればと思います。

明日は弊社デザイナー @hayaoo  の「再考、デザイン・スプリントの誤解と３つのメリット」です。

#ホワイトプラスではエンジニアを募集しています
ホワイトプラスでは、[新しい技術にどんどん挑戦したい！という技術で事業に貢献したいエンジニアを募集](https://www.wantedly.com/projects/69257)しております。