PostgreSQLなRDSでIAM Database Authenticationを使ってみた

PostgreSQLなRDSでIAM Database Authenticationを使ってみた

最近、とあるPostgreSQLなRDS(Auroraでない)を運用しているのですが、普通にRailsから使っているのとは別に、Lambdaからも接続する要件があり、表題のIAM Database Authenticationを試してみたのでここに記します。
ちなみに、LambdaからRDSへの接続は不特定多数からのアクセス用途では推奨されていない話は有名ですが、今回は定期実行(1日に数回)のLambdaなので問題無しです。

IAM Database Authentication

RDS固有の機能で、IAMクレデンシャルによるRDBユーザーへのログイン認証を提供する機能です。
接続権限を保有するクレデンシャルから、15分間有効なAWS Signature Version 4による電子署名を発行し、これをログインパスワードに用いる事で認証を行おうと言うコンセプトです。

これにより、DBのパスワードを直接アプリに持たせる事なく運用が可能で、他にもメリットがいくつかあるのですが詳しくは公式ドキュメントへ

使えるバージョン

ただし、この機能の利用には制限があり、次の条件下でのみ利用可能となっています (PostgreSQLのみ記載):

  • PostgreSQL(非Aurora): 9.6.11、および10.6以降が対象
  • PostgreSQL互換のAurora: 9.6.9、および10.4以降が対象

いずれにしてもPostgreSQLの場合はインスタンスタイプの制限が無いようです。(MySQLにはある)

利用制限

使っているインスタンスイプにより、秒間の最大接続数が制限される事がある、と公式ドキュメントには書かれていますが具体的な事は現時点では書かれていません。
ただ、特に今回のLambdaの様にたまにしかDBにアクセスしない様なワークロードでは問題なく使えそうです。

設定編

RDSは既に準備している物とします。今回は非AuroraのRDSを使っていますが、作業内容はほぼ一緒です。

IAM DB authenticationを有効にする

作成時に有効にしていない場合は、インスタンス(Auroraの場合はクラスタ)詳細の「Modify」から変更が可能です。

RDSインスタンス設定の変更画面

専用のPostgreSQLユーザーを作成する

ユーザー作成及び権限付与の権限を持っているユーザーで以下を実行:

CREATE USER iam_user WITH LOGIN;
GRANT rds_iam TO iam_user;

今回はユーザー名を iam_user としました。
また、 rds_iam を付与されたユーザーは通常のパスワードログインができなくなるので専用のユーザーを作る必要があります。

署名作成用のポリシーを作成

実際に接続を行うリソースが持つIAM Roleないしはユーザーに rds-db:connect 権限を付与します。今回はLambdaの実行Roleに対して以下のポリシーを付与します:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "rds-db:connect"
            ],
            "Resource": [
                "arn:aws:rds-db:リージョン:AWSアカウントID:dbuser:DbiリソースID/iam_user"
            ]
        }
    ]
}

リージョンAWSアカウントID は適宜読み替えて下さい。
DbiリソースID とは、リージョン毎に固有なDBのIDで、 db-ABCDEFGHIJKL01234 の様な形式を持ち、これもインスタンス(Auroraの場合はクラスタ)の “Configuration” タブで確認ができます:

RDSインスタンスの詳細画面

最後は先程追加したPostgreSQLのユーザー名です。
因みに、 arn:aws:rds-db:リージョン:AWSアカウントID:dbuser:DbiリソースID/* や arn:aws:rds-db:リージョン:AWSアカウントID:dbuser:*/* の様にワイルドカードを使って一括許可も可能です。

また、Management Consoleで rds-db:connect の付与を行うと以下の通り、存在しない権限として警告が表示されますが無視して良いそうです:

尚、AdministratorAccess等のIAMの管理権限を持っているユーザーはポリシーの付与が無くても大丈夫なようです。

実際に認証してみる

CLIから接続

まずは psql を使ってCLIから認証を試してみます。パスワードとしてのトークンを得るため、先程設定したRoleまたはユーザーを使って以下のコマンドを実行してみます:

$ aws --region ap-northeast-1 rds generate-db-auth-token --hostname postgres.xxxxxxxxxx.ap-northeast-1.rds.amazonaws.com --port 5432 --username iam_user

postgres.xxxxxxxxxx.ap-northeast-1.rds.amazonaws.com:5432/?Action=connect&DBUser=iam_user&X-Amz-Algorithm=AWS4-HMAC-SHA256...

するとご覧のような署名が返されます。これが15分間だけ有効な iam_user のパスワードとなります。
ちなみに、このコマンドはクレデンシャル情報に基づいたAWS Signature Version 4による事前署名なので、ネットワークを経由する事なく計算が可能で、権限さえあれば生成可能です。したがって必ずしもPostgreSQLにログインを行うマシンから実行をする必要はありません。

また、 generate-db-auth-token は --port を含めて全optionが必須となっています。

この結果を、 PGPASSWORD 環境変数に入れて、 psql でログインしてみましょう:

$ export PGPASSWORD=`aws --region ap-northeast-1 rds generate-db-auth-token --hostname postgres.xxxxxxxxxx.ap-northeast-1.rds.amazonaws.com --port 5432 --username iam_user`
$ psql "host=postgres.xxxxxxxxxx.ap-northeast-1.rds.amazonaws.com port=5432 user=iam_user dbname=postgres"

psql (11.4, server 11.2)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.

postgres=>

無事にログインができました。
15分が経過したあと再認証してみましたが、無事却下されました:

$ psql "host=postgres.xxxxxxxxxx.ap-northeast-1.rds.amazonaws.com port=5432 user=iam_user dbname=postgres"

psql: FATAL:  PAM authentication failed for user "iam_user"

Lambdaから認証

今回はLambdaで使っているので、以下はNode.jsでの接続例です:

const AWS = require("aws-sdk");
const fs = require("fs");
const pg = require("pg");

const signer = new AWS.RDS.Signer({
    region: "ap-northeast-1",
    hostname: "postgres.xxxxxxxxxx.ap-northeast-1.rds.amazonaws.com",
    port: 5432,
    username: "iam_user"
});

const pgClient = new pg.Client({
    host: "postgres.xxxxxxxxxx.ap-northeast-1.rds.amazonaws.com",
    user: "iam_user",
    database: "postgres",
    password: signer.getAuthToken(),
    ssl: {
        ca: fs.readFileSync(__dirname + "/rds-ca-2015-root.pem").toString(),
    }
});

今回、PostgreSQLのクライアントとして pg と言うnpmを使っています。
また、その際SSLを明示的に有効化する必要があったので、AWSが配布しているRoot証明書 (https://s3.amazonaws.com/rds-downloads/rds-ca-2015-root.pem) を使いました。

おまけ (Scalaから接続編)

実はScalaで書かれているあるバッチ(これも1日数回定期実行)でも使っているので、ついでに書いておきます:

import com.amazonaws.services.rds.auth.{GetIamAuthTokenRequest, RdsIamAuthTokenGenerator}

val tokenGenerator = RdsIamAuthTokenGenerator.builder
  .credentials(new DefaultAWSCredentialsProviderChain)
  .region("ap-northeast-1")
  .build

val password = tokenGenerator.getAuthToken(
  GetIamAuthTokenRequest.builder
    .hostname("postgres.xxxxxxxxxx.ap-northeast-1.rds.amazonaws.com")
    .port(5432)
    .userName("iam_user")
    .build
)

ConnectionPool.singleton(
  "jdbc:postgresql://postgres.xxxxxxxxxx.ap-northeast-1.rds.amazonaws.com:5432/postgres",
  "iam_user",
  password
)

PostgreSQLのクライアントはScalikeJDBCです。また、 aws-java-sdk-rds のインストールも必要です。

we are hiring

優秀な技術者と一緒に、好きな場所で働きませんか

株式会社もばらぶでは、優秀で意欲に溢れる方を常に求めています。働く場所は自由、働く時間も柔軟に選択可能です。

現在、以下の職種を募集中です。ご興味のある方は、リンク先をご参照下さい。