主に以下2例に対して情報を少し追加するだけの記事です。
API GatewayのバックをVPC Lambdaにしている場合、APIのくせに返答に時間がかかる場合がある、という問題があります。なので、
- 非VPC Lambdaから安全にPublic AccessibleなRDSに接続したい
- かつ、API Gatewayに接続元IP制限をつけたい
というのがやりたいことです。
RDS接続認証でIAMが使えるようになった
- AWS IAM で RDS for MySQL と Amazon Aurora データベースへのアクセスを管理
- IAM Database Authentication for MySQL and Amazon Aurora
執筆時点で公式日本語ドキュメントは無し。
つまりどういうこと?
MySQLの認証プラグインを作ったからRDSイメージにいれておいたよー、という話らしいので見てみる。
select * from plugin;
+-------------------------+-------------+
| name | dl |
+-------------------------+-------------+
| AWSAuthenticationPlugin | aws_auth.so |
+-------------------------+-------------+
1 row in set (0.01 sec)
同じものをPostgreSQLでも作ってくれればPostgreSQLでもIAM認証が使える用になるんですよね? (よく知らないまま希望)
それのなにがうれしいの?
MySQLのuser/password認証がAWS_ACCESS_KEY_IDとAWS_ACCESS_SECRET_KEYに置き換わったような感じです。
……って言っちゃうと「それはそう」案件になるんですが、特定のIAMロールを持つ非VPC Lambdaからのみのアクセスを許可するようなRDS MySQLを作ることができる、というのが特にうれしい点です。
MySQL組み込みの認証機構よりは比較的安全な方法で、RDSをPublic Accessibleにできるようになる、とも言います。RDSがPublic Accessibleなら、Lambdaも非VPCでよくなります。しかもそのLambdaも特定のIAMロールを持つものだけに制限できます。安心だ。
やりたいこと
RDS (IAM & SSL) | AWS Lambda (IAM) | API Gateway (https) | CloudFront (https) | Web Application Firewall
の構成を作りたい。社内APIとかですな。なんか登場人物が多いですが、以下の事情があります
- ソースIP制限をつけたいが、API Gateway自体にソースIP制限機能がない
- WAFにはあるが、WAFはALBかCloudFrontにしか使えない
よって、
- CloudFrontをAPI Gatewayの前段に置く
- WAFをCloudFrontの前段に置く
ことでアクセスをコントロールします。
API Gateway自体に関しては、API Tokenを必須にすることでアクセスを制限します。もちろんCloudFrontからはアクセスできないといけないので、Origin設定でAPI Tokenをヘッダに追加する必要があります。ややこしいなおい。
ちなみに、API Gatewayの認証をIAMにすることもできるんですが、これだとAPIユーザーにAWS v4 Signature Authを実装させることになるので心苦しいです。
うっわめんどくさっ。
やってみよう
RDSをつくる
IAM Database Authentication for MySQL and Amazon Aurora
IAM認証はdb.m1.smallより大きいインスタンスのみでしか使えないので注意。ユーザーの作り方もドキュメントのままですが、
CREATE USER jane_doe IDENTIFIED WITH AWSAuthenticationPlugin as 'RDS' REQUIRE SSL;
DBとLambda間はSSLにしないといけないので、REQUIRE SSL
オプションを付けてSSLを強制します。
Lambdaのレンジ:3306
のInをAllowしたSecGroupを作る、つってもLambdaのレンジってめっちゃ広そう?
https://docs.aws.amazon.com/ja_jp/general/latest/gr/aws-ip-ranges.html
Lambdaをつくる
試しに作ったLambdaは
from __future__ import print_function
import os
import boto3
import mysql.connector
def lambda_handler(event, context):
print(event, context)
token = boto3.client('rds').generate_db_auth_token(
DBHostname=os.environ['RDS_HOST'],
Port=3306,
DBUsername=os.environ['RDS_USER']
)
conn = mysql.connector.connect(
host=os.environ['RDS_HOST'],
user=os.environ['RDS_USER'],
password=token,
database=os.environ['RDS_DATABASE'],
ssl_verify_cert=True,
ssl_ca='rds-combined-ca-bundle.pem'
)
cursor = conn.cursor()
cursor.execute('SELECT user FROM mysql.user')
rows = cursor.fetchall()
result = [str(x[0]) for x in rows]
return {'users': result}
DBのユーザーをSELECT
して返すだけのものです。
Lambda実行時のIAMロールにIAM Database Authポリシーが必要になります。
Attaching an IAM Policy Account to an IAM User or Role
arn:aws:rds-db:region:account-id:dbuser:dbi-resource-id/database-user-name
ここでMySQLのGRANT
のON
とTO
を指定しているみたいなものと考える。
別のIAMロールを付けてLambdaを実行してみると、みごとにDB接続エラーが発生する。
Lambdaをzipでまとめる
Dockerfile
を作ってzipを生成するナウでヤングな最先端のイカしたアレだぜ。デプロイ時に非VPCを選択。
FROM amazonlinux
RUN yum install -y python27-devel python27-pip zip
RUN pip install --upgrade pip
RUN mkdir /opt/rds-iam-auth /opt/build
COPY ./ /opt/rds-iam-auth/
WORKDIR /opt/rds-iam-auth
RUN pip install wheel
RUN pip install -r requirements.txt -t .
RUN zip -r rds-iam-auth.zip *
CMD cp rds-iam-auth.zip /opt/build
悲しいかなLambdaのamazonlinuxに入っているboto3がIAM RDS authに未対応 (執筆時) だったのでrequirement.txt
に追記。近々AMIもアップデートされるだろう。
その他をつくる
コンソールからぽちぽちやる作業がたくさんあります。特にここで追記することはないので、先人の記事を参照したいところだ。
- API GatewayにCloud Frontを紐づけ、WAFでIP制限をかけてみる
- AWS WAFでCloudFront経由でのアクセスにIPアドレス制限を設定する
- Amazon CloudFrontでAPI Gatewayの痒いところに手を届ける
注意点まとめ
- APIエンドポイントを直接使用されないように、API Tokenを必須にしておくこと。
- WAFはGlobalリージョンで作成しないとCloudFrontのコンソールから選べないので注意。
- CloudFrontのOrigin設定時に、
x-api-token
ヘッダ転送設定を追加すること。 - CloudFrontの設定反映には時間がかかるの辛いコーヒー飲むしかない。
ためす
WAFで社内のGlobal IPのみアクセスを許可。
$ curl https://hoge.cloudfront.net/
{"users": ["helloworld", "iamuser1", "mysql.sys", "rdsadmin"]}
WiFi変えたりとかして、Global IPを変えてみると
$ curl https://hoge.cloudfront.net/
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>ERROR: The request could not be satisfied</TITLE>
</HEAD><BODY>
<H1>ERROR</H1>
<H2>The request could not be satisfied.</H2>
<HR noshade size="1px">
Request blocked.
<BR clear="all">
<HR noshade size="1px">
<PRE>
Generated by cloudfront (CloudFront)
Request ID: jIG5TSJUn9rPZYI0JwUr9wZHFHFa_LRyVmwGC302sHjBgv0sLoPubA==
</PRE>
<ADDRESS>
</ADDRESS>
</BODY></HTML>
Forbiddenされる。
## まとめ
AWSよくできてんなー。