RDS ProxyはSecrets ManagerのSecretsをどのように利用しているのか

RDS Proxyの裏側に関する考察など
2022.06.11

MAD事業部@大阪の岩田です。

先日社内のチャットで以下のような質問がありました。

この質問に対する回答をせっかくなのでブログにまとめてみました。

環境

今回検証に使用した環境です。

  • エディション:Amazon RDS for PostgreSQL
  • エンジンバージョン: Postgresql 13.6
  • インスタンスクラス:db.m6g.large
  • IAM認証は無効

DBユーザーはマスターユーザーとは別にuser01というユーザーを作成し、クライアントからRDS Proxyに接続する際はuser01というユーザーを利用します。またuser01がアクセス可能なdb1というデータベースも事前に作成しています。

各種設定値については分析しやすいようにログ関連の設定だけ以下のように設定しています。

  • log_statement = 'all' に設定
  • log_connections = 1 に設定
  • RDS Proxyの「拡張されたログ記録」を有効化

RDS ProxyがSecrets Managerのシークレットをどのように利用しているのか?

以下の内容は特に公式ドキュメント等には記載されておらず、RDS Proxyの振る舞いから推測したものです 実際の内部仕様とは異なる可能性がある点をご了承下さい

RDS ProxyはSecrets Managerのシークレットをどのように利用しているのか?という質問の答えですが、私は以下2つの用途でシークレットを利用していると考えています。

  1. RDS Proxyがクライアントの接続要求を認証する際に参照する
  2. RDS ProxyがRDSとの接続をOPENする際に参照する

まず1つ目の「RDS Proxyがクライアントの接続要求を認証する際に参照する」ですが、RDS Proxyはクライアントの接続要求を認証する段階ではいちいちRDSまで問い合わせていません。あくまでRDS Proxy単体でSecrets Managerに保存されたシークレットとクライアントが送信したパスワードが一致することを確認して認証を行っています。

続いて2つ目の「RDS ProxyがRDSとの接続をOPENする際に参照する」についてです。RDS ProxyがRDSとの接続をOPENするタイミングはクライアントからRDS Proxyに対する接続要求とは独立したタイミングで行われます。クライアントの要求を処理するためにチェックアウト可能な接続がコネクションプーリング内に存在しない場合に、新たにRDS Proxy <-> RDS間の接続をOPENしてコネクションプール内に追加します。そのため、場合によってはクライアントがRDS Proxyに接続したタイミングではなく、クライアントがSQLを発行したタイミングで初めてRDS Proxy<->RDS間の接続がOPENされるといったケースがありえます。

このあたりの振る舞いを整理するため、様々なパターンでRDS Proxyへのアクセスを試みて挙動を観察してみました。

DBユーザーのパスワードとシークレットが不一致の場合どうなるのか?

まずはDBユーザーのパスワードとSecrets Managerに登録されたシークレットが不一致となる状況を作り出してRDS Proxyの動作を確認してみます。

RDS Proxy <-> RDS間の接続が未確立かつ、クライアントが送信したパスワードとシークレットが不一致の場合

EC2環境からpsqlでRDS Proxyに接続を試みます

psql -h <RDS Proxyのエンドポイント> -U user01

パスワードの入力を要求されるので、Secrets Managerに設定したパスワードと不一致となる適当な文字列を入力します

Password for user user01:
psql: error: FATAL:  The password that was provided for the role user01 is wrong.
FATAL:  The password that was provided for the role user01 is wrong.

パスワードが間違っているということで接続エラーとなりました。

この時RDS側のログを確認すると、log_connections = 1を設定しているにも関わらず特にRDS ProxyからRDSに対する接続のログは確認できませんでした。この振る舞いからクライアントの認証はあくまでRDS Proxyだけで完結していると考えて良さそうです。

RDS Proxy <-> RDS間の接続が未確立かつ、クライアントが送信したパスワードとシークレットが一致するものの、シークレットとDBユーザーのパスワードが一致しない場合

先程の検証結果からクライアントからの接続要求に対する認証処理はRDS Proxy単体で処理していると推測できました。ではRDS Proxyが認識しているシークレットとクライアントが送信したパスワードが一致すれば実際のDBユーザーのパスワードとは不一致でもRDS Proxyには接続できるのでしょうか?DBユーザーのパスワードを適当な文字列に更新して再度トライしてみます。

postgres=> alter role user01 password 'hogehogehoge';

これでSecrets Managerに登録されたシークレットとDBユーザーのパスワードが不一致となりました。

EC2インスタンスからpsqlでRDS Proxyに接続してみます

psql -h pg13proxy.proxy-czo4ezbcwzku.ap-northeast-1.rds.amazonaws.com -U user01
Password for user user01:

パスワードを要求されるので、DBユーザーのパスワードではなくSecrets Managerに登録した文字列を指定します。

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

user01=>

接続できました!実際にはDBユーザーのパスワードはすでにhogehogehogeに更新されていますが、問題なく接続できています。このままSQLを発行するとどうなるでしょうか?

user01=> select now();
FATAL:  The password that was provided for the role user01 is wrong.
SSL connection has been closed unexpectedly
The connection to the server was lost. Attempting reset: Succeeded.
psql (13.3, server 13.6)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
user01=>

接続エラーになりました。

この時の各種ログを確認してみます。

RDS Proxyのログは以下の通りです。

2022-06-10T07:27:13.303Z [INFO] [dbConnection=582686011] The new database connection failed. Error code: [28P01]. Message: password authentication failed for user "user01".

続いてRDSのログです。

2022-06-10 07:27:13 UTC:172.31.55.6(10811):[unknown]@[unknown]:[26900]:LOG:  connection received: host=172.31.55.6 port=10811
2022-06-10 07:27:13 UTC:172.31.55.6(10811):user01@user01:[26900]:FATAL:  password authentication failed for user "user01"
2022-06-10 07:27:13 UTC:172.31.55.6(10811):user01@user01:[26900]:DETAIL:  Password does not match for user "user01".
        Connection matched pg_hba.conf line 13: "host   all                             all                     all                     md5"

SQLを発行したのは16:27だったので、RDS ProxyがRDSとの接続を確立するタイミングはクライアントがRDS Proxyに接続したタイミングとは独立していると考えて間違い無さそうです。

RDS Proxy <-> RDS間の接続が確立済み、かつクライアントが送信したパスワードとシークレットが一致するものの、シークレットとDBユーザーのパスワードが一致しない場合

先程のパターンはRDS Proxy <-> RDS間の接続が存在しない状態からスタートしましたが、今度はRDS Proxy <-> RDS間の接続が既にOPENされており、RDS Proxyのコネクションプール内に利用可能なDB接続が存在する状態で試してみます。

まずDBユーザーパスワードをSecrets Managerに登録済みのパスワードに戻します。

postgres=> alter role user01 password '<Secrets Managerに登録済みのパスワード>';
ALTER ROLE

この状態でEC2からRDS Proxyに接続してSQLを発行します

$ psql -h <RDSプロキシのエンドポイント> -U user01 db1
Password for user user01:
psql (13.3, server 13.6)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.

db1=> select now();
              now
-------------------------------
 2022-06-10 07:39:29.227461+00
(1 row)

db1=>

SQLの発行に成功したらpsqlのセッションを終了し、クライアント<->RDS Proxy間の接続をCLOSEします。この状態で直接RDSに接続し、接続状況を確認するとuser01のセッションが残っていることが分かります。

postgres=> select usename,count(*) from pg_stat_activity group by usename order by usename;
    usename    | count
---------------+-------
 postgres      |     1
 rdsadmin      |     3
 rdsproxyadmin |     2
 user01        |     1
               |     4
(5 rows)

再度DBユーザーのパスワードをhogehogehogeに変更してみます

postgres=> alter role user01 password 'hogehogehoge';
ALTER ROLE

再度EC2からRDS Proxyにpsqlで接続します。

$ psql -h <RDSプロキシのエンドポイント>  -U user01 db1
Password for user user01:
psql (13.3, server 13.6)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.

続いてSQLの発行を試みます。RDS Proxyが認識しているSecrets Managerに登録されたパスワードはすでにDBユーザーの実際のパスワードとは乖離している状態ですが、、、

db1=> select now();
              now
-------------------------------
 2022-06-10 07:43:00.481073+00
(1 row)

問題なくSQLが発行できました。これはDBユーザーのパスワード変更前にRDS Proxy<->RDS間の接続が確立していたためですね。

ターミナルの別タブを使ってもう1つEC2からRDS Proxyに接続してSQLを発行してみます。

$ psql -h <RDSプロキシのエンドポイント> -U user01 db1
Password for user user01:
psql (13.3, server 13.6)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.

db1=> select now();
              now
-------------------------------
 2022-06-10 07:46:43.824934+00
(1 row)

SQL発行まで成功しました。これは発行したSQLがselect now()だけなので、1つ目のpsqlセッションと2つめのpsqlセッションともに同じRDS Proxy<->RDS間の接続をチェックイン/チェックアウトして利用できるためです。

続いて1つ目のpsqlセッションからトランザクションを開始してみます。

db1=> begin;
BEGIN

これで1つ目のpsqlセッションがコネクションプール内の接続をチェックアウトした状態になります。

続いて2つ目のpsqlセッションからSQLを発行してみます。今度はコネクションプール内の接続がチェックアウトできないため、RDS Proxyが新たにRDSとの接続OPENを試行するはずです。

db1=> select now();
FATAL:  The password that was provided for the role user01 is wrong.
SSL connection has been closed unexpectedly
The connection to the server was lost. Attempting reset: Succeeded.
psql (13.3, server 13.6)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)

エラーになりました。RDS Proxyが認識しているSecrets Managerに登録されたパスワードとDBユーザーの実際のパスワードが乖離しているためですね。

シークレットの削除が予定されている場合

続いてシークレットの削除が予定されており、RDS Proxyからシークレットが利用できないケースについて考えてみます

クライアント<->RDS Proxyの接続が未確立の場合

クライアントとRDS Proxyの接続が未確立の場合から確認していきます。まずシークレットの削除を予定します。

$aws secretsmanager delete-secret --secret-id <シークレット名>

この状態でEC2からpsqlで RDS Proxyに接続を試みます。

$ psql -h <RDSプロキシのエンドポイント> -U user01 db1

するとパスワードを聞かれるまでもなく以下のエラーが返却されました

psql: error: FATAL:  This RDS proxy has no credentials for the role user01. Check the credentials for this role and try again.
FATAL:  This RDS proxy has no credentials for the role user01. Check the credentials for this role and try again.

クライアント<->RDS Proxy間の接続が確立済かつRDS Proxy<->RDS間の接続が未確立の場合

続いてクライアントとRDS Proxyの接続が確立した後にシークレットの削除を予定した場合の振る舞いを確認してみましょう。まずは先程削除を予定したシークレットをリストア

$ aws secretsmanager restore-secret --secret-id <シークレット名>

EC2からpsqlでRDS Proxyに接続します

$ psql -h <RDSプロキシのエンドポイント> -U user01 db1
psql: error: FATAL:  This RDS proxy has no credentials for the role user01. Check the credentials for this role and try again.
FATAL:  This RDS proxy has no credentials for the role user01. Check the credentials for this role and try again.

シークレットリストア済にも関わらずエラーが返却されてきました。この振る舞いから推測するとRDS Proxy側でシークレットのネガティブキャッシュを保持しているようですね。少し時間をおいてから再度接続を試みます。

$ psql -h <RDSプロキシのエンドポイント> -U user01 db1
Password for user user01:
psql (13.3, server 13.6)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.

db1=>

今度はRDS Proxyに接続できました。再度シークレットの削除を予定します。

$ aws secretsmanager delete-secret --secret-id <シークレット名>

RDSに直接接続したpsqlのセッションからRDS Proxy<->RDS間の接続が存在しないことを確認

postgres=> select usename,count(*) from pg_stat_activity group by usename order by usename;
    usename    | count
---------------+-------
 postgres      |     1
 rdsadmin      |     3
 rdsproxyadmin |     2
               |     4
(4 rows)

psqlからSQLを発行します

db1=> select now();
              now
-------------------------------
 2022-06-11 07:59:39.757722+00
(1 row)

普通にSQLが発行できてしまいました。やはりRDS Proxy側でシークレットをキャッシュしているように見えます。

RDSの接続状況は以下のような状況でした

postgres=> select usename,count(*) from pg_stat_activity group by usename order by usename;
    usename    | count
---------------+-------
 postgres      |     1
 rdsadmin      |     3
 rdsproxyadmin |     2
 user01        |     1
               |     4
(5 rows)

改めてRDS Proxy<->RDSの接続をすべてCLOSEした上でシークレットをリストアし、クライアントからRDS Proxyへの接続からやり直してみます。

$ psql -h <RDSプロキシのエンドポイント> -U user01 db1
Password for user user01:
psql (13.3, server 13.6)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.

db1=>

再度シークレットの削除を予定

$ aws secretsmanager delete-secret --secret-id <シークレット名>

先程はこの後SQLが普通に発行できたので、RDS Proxyがシークレットをキャシュしていそうだと推測しました。キャッシュの有効期限は不明ですが、とりあえずコーヒーでも飲んで休憩します。

10分ほど経過したでしょうか?再度SQLの発行を試みます。

db1=> select now();
FATAL:  Unknown error.
SSL connection has been closed unexpectedly
The connection to the server was lost. Attempting reset: Failed.
!?>

今度はエラーになりました。RDS Proxy<->RDS間の接続OPENに失敗したことが原因と考えられます。

また Attempting reset: Failed.の出力からEC2<->RDS Proxyの接続再OPENを試みたものの、すでにシークレットの削除が予定されているため、RDS Proxyへの接続時点でエラーになったと推測できます。

CW LogsからRDS Proxyのログを確認すると以下のようなログが確認できました。

@timestamp @message
2022-06-11 08:19:47.152 Credentials couldn't be retrieved. The AWS Secrets Manager secret's ARN <シークレットのARN> wasn't found or isn't accessible. Make sure that the ARN is correct.
2022-06-11 08:19:50.541 Credentials couldn't be retrieved. The AWS Secrets Manager secret's ARN <シークレットのARN> wasn't found or isn't accessible. Make sure that the ARN is correct.
2022-06-11 08:20:02.722 Credentials couldn't be retrieved. The AWS Secrets Manager secret's ARN <シークレットのARN> wasn't found or isn't accessible. Make sure that the ARN is correct.
2022-06-11 08:20:47.155 Credentials couldn't be retrieved. The AWS Secrets Manager secret's ARN <シークレットのARN> wasn't found or isn't accessible. Make sure that the ARN is correct.

エラーログが複数回出力されていることとタイムスタンプから考察すると、エクスポネンシャルバックオフが実装されていそうですね。

まとめ

RDS ProxyとSecrets Managerの関連性を整理してみました。皆さんの理解の助けになれば幸いです。