TypeORMからIAMデータベース認証でRDSへ接続する

はじめに

はじめまして。サーバサイドエンジニアのkazushiです。
Node.jsでサーバサイドアプリケーションを開発しています。
今回、TypeORMからIAMデータベース認証でRDSへ接続する機会があり、その過程でいろいろハマることが多かったので備忘録も兼ねて手順を紹介したいと思います。

IAMデータベース認証とは

通常のパスワードを使ったデータベースの認証の代わりに、権限を付与したIAMロールを用いる事で認証を行う方式です。
主なメリットとして、アプリケーションの設定ファイルなどにパスワードを書かなくて済むという点があります。

IAMデータベース認証の有効化

IAMデータベース認証を行うにはまず、DBインスタンスまたはクラスタ毎にIAM認証を有効にする必要があります。
変更は一瞬で完了するのでダウンタイムは発生しません。

iam1

IAMポリシーの作成

次にアプリケーションで利用するためのIAMポリシーを作成します。
ポリシーには1つだけリソースを含み、arnのフォーマットは以下となります。

arn:aws:rds-db:[リージョン]:[AWSアカウントID]:dbuser:[DBリソースID]/[DBユーザー名]

設定例

{
   "Version": "2012-10-17",
   "Statement": [
      {
         "Effect": "Allow",
         "Action": [
             "rds-db:connect"
         ],
         "Resource": [
             "arn:aws:rds-db:ap-northeast-1:123456789012:dbuser:cluster-AAAAAAAAAAAAAAAAAAAAAAAAAA/iamdba"
         ]
      }
   ]
}

ポリシーを作成したらIAMユーザーまたはロールへポリシーのアタッチを行います。
今回はECSで動かすため、ECS実行用のロールへアタッチしました。

iam2

IAM認証専用のDBユーザーの作成

IAM認証を行うためには専用のDBユーザーが必要となるので作成します。
通常のユーザー作成と異なる点は、AWSAuthenticationPluginの指定とREQUIRE SSLが必須となります。

-- ユーザー作成
CREATE USER iamdba IDENTIFIED WITH AWSAuthenticationPlugin AS 'RDS';

-- 権限付与
GRANT SELECT,INSERT,UPDATE,DELETE on *.* TO 'iamdba'@'%' REQUIRE SSL;
FLUSH PRIVILEGES;

IAM認証を使用したDBへの接続

ここまでセットアップできたらいよいよDBに接続していきます。
まずはsdkを利用して認証トークンを取得します。

import * as AWS from 'aws-sdk';

const token = new AWS.RDS.Signer().getAuthToken({
  region: 'ap-northeast-1',
  username: master.username,
  hostname: master.host,
  port: 3306,
});

続いてTypeORMの接続オプションのパスワードに先ほど取得した認証トークンを設定します。
認証トークンの有効期限は15分間ですが、一度接続してしまえば途中で接続が切れることはありません。

import { ConnectionOptions } from 'typeorm';

const options: ConnectionOptions = {
  type: 'mysql',
  host: master.host,
  port: master.port,
  username: master.username,
  password: token,
  database: master.database,
  entities: [User, Photo],
};

これだけではまだ接続できません。実はTypeORMではextraという接続オプションでMySQLクライアントに直接渡すオプションを指定できるので、これを利用して証明書の指定と、トークン文字列を平文で送るためのauthSwitchHandlerを定義します。
なおauthSwitchHandlerを使うためにはMySQLクライアントに node-mysql2 を使用する必要があります。

options.extra = {
  ssl: 'Amazon RDS',
  authSwitchHandler: (data: any, cb: any) => {
    if (data.pluginName === 'mysql_clear_password') {
      const buffer = Buffer.from(master.password + '\0');
      cb(null, buffer);
    }
  }
};

最終的には以下のようになります。

import * as AWS from 'aws-sdk';
import { ConnectionOptions, createConnection } from 'typeorm';

const token = new AWS.RDS.Signer().getAuthToken({
  region: 'ap-northeast-1',
  username: master.username,
  hostname: master.host,
  port: 3306,
});

const options: ConnectionOptions = {
  type: 'mysql',
  host: master.host,
  port: master.port,
  username: master.username,
  password: token,
  database: master.database,
  entities: [User, Photo],
  extra: {
    ssl: 'Amazon RDS',
    authSwitchHandler: (data: any, cb: any) => {
      if (data.pluginName === 'mysql_clear_password') {
        const buffer = Buffer.from(master.password + '\0');
        cb(null, buffer);
      }
    },
  },
};

async function main() {
  const connection = await createConnection(options);
  const userRepository = connection.getRepository(User);
  const allUsers = await userRepository.find();
  console.log(allUsers);
}

main().catch(console.error);

終わりに

ここまでお読みいただきありがとうございました。
IAMデータベース認証は同時接続数などの制限も多いですが、導入する事で秘匿情報の管理に悩まされずに済むというメリットがあります。
本記事がIAMデータベース認証導入の一助となれば幸いです。

Author image
About kazushi
expand_less