IAM認証RDS・非VPC Lambda・API Gateway・CloudFront・WAFで接続元IP制限付きAPIを作成する

  • 9
    いいね
  • 0
    コメント

主に以下2例に対して情報を少し追加するだけの記事です。

API GatewayのバックをVPC Lambdaにしている場合、APIのくせに返答に時間がかかる場合がある、という問題があります。なので、

  • 非VPC Lambdaから安全にPublic AccessibleなRDSに接続したい
  • かつ、API Gatewayに接続元IP制限をつけたい

というのがやりたいことです。

RDS接続認証でIAMが使えるようになった

執筆時点で公式日本語ドキュメントは無し。

つまりどういうこと?

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

rds-iam.png

の構成を作りたい。社内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のGRANTONTOを指定しているみたいなものと考える。

別の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エンドポイントを直接使用されないように、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よくできてんなー。

  • この記事は以下の記事からリンクされています
  • aws周りのメモ2からリンク