iOS&Androidスマートフォン開発(Swift/Kotlin/React-Native) および Next.js/LaravelをはじめとしたWeb開発ならば、ぜひ創業13年の弊社にお任せください。ワンストップでお届けします。お問合せはこちらから

RDSのマスターパスワードを変えると何が起きるのか。使い捨てのDBで試した手順とリスク

  2026年11月06日 12:00 / - PV

yuku_tasのアイコン

こんにちは、Webサービス開発者のyuku_tasです。

簡単なプロフィール: MoldSpoon Inc.代表。開業から15年、業界に携わって20年。Webサイト/iOS/Androidアプリ開発から、データ分析業務・コンサル業務、ベンチャーから保険会社まで様々な案件を個人会社の形で請け負ってまいりました。詳しくはこちら

こんにちは!朝晩がすっかり冷え込んできましたね。yuku_tasです。

ハッカーの皆様は、RDSのマスターパスワードを本番で変えたことはありますか?

退職者が知っていた、どこかに貼ってしまった、そもそも誰も覚えていない。変えなければいけない日は突然やってきます。 でも「変えた瞬間、動いているアプリはどうなるのか」を実際に見たことがある人は、案外少ないのではないでしょうか。

公式ドキュメントには「ダウンタイムは発生しない」と書いてあります。そこで、使い捨てのRDSを作って実際に変更し、0.5秒〜1秒ごとにログインを試して記録しました。

先に結論

  • パスワードを変えても、既存の接続・実行中のクエリ・開いているトランザクションは無事でした
  • 新旧の入れ替わりは一瞬で、API呼び出しの約3.6秒後でした。「どちらでも入れない時間」は計測間隔の中では見えませんでした
  • ただし新しい接続は、古いパスワードだと即座に失敗します。 ここがアプリの止まる場所です
  • Secrets Managerの即時ローテーションでは、シークレットから取得した値でのログインが約60秒間失敗しました(6回すべて)
  • ログインできない原因がパスワード間違い・アカウントロック・権限喪失なら再設定で直りました。Host Blockedは直りません

試した環境

  • RDS for MySQL 8.4.11 / db.t4g.micro / Single-AZ / gp3 20GB / 東京リージョン
  • ほぼ無負荷の、計測後に削除するDB
  • max_connections 60 / max_connect_errors 100 / skip_name_resolve OFF
  • 計測はCloudShellからboto3とPyMySQLで行いました
  • t は変更APIを呼んだ時点からの秒数です

ローテーション以外の項目は1回ずつの計測です。インスタンスサイズや負荷が変われば数字は動くはずなので、「このくらいの桁で起きる」という目安として読んでください。

Multi-AZ、リードレプリカ、PostgreSQL、Aurora、負荷がかかった状態は試していません。

手順

自分でパスワードを決める場合

手動で変更する
# 履歴に平文を残さないよう、read で受ける
read -rs NEW_PW

aws rds modify-db-instance --no-cli-pager \
  --db-instance-identifier mydb \
  --master-user-password "$NEW_PW" \
  --query 'DBInstance.[DBInstanceStatus,PendingModifiedValues]'

--apply-immediately は要りません。AWS CLIのリファレンスどおり、付けなくても変更は非同期ですぐに適用されます。メンテナンスウィンドウまで待ってはくれないので、実行した時点で本番に効くと思ってください。

なお、/、@、"、空白はパスワードに使えません。試すと次のエラーで即拒否されました。

Only printable ASCII characters besides '/', '@', '"', ' ' may be used.

実行時の時系列

t(秒)起きたこと
0.31APIが応答
0.8状態は available のまま(変更は保留中)
3.6旧パスワードが失敗し、新パスワードが通るようになる
4.0状態が resetting-master-credentials になる
64.5状態が available に戻る

ここから分かることが2つあります。

1つ目は、状態が available でも完了とは限らないことです。呼び出しの直後は available のままなので、状態だけを見る確認だと「もう終わった」と誤認します。PendingModifiedValues も一緒に見るか、resetting-master-credentials になるのを確かめてから待ちましょう。

2つ目は、状態が戻るのを待たなくても新パスワードは効いていることです。約60秒の resetting-master-credentials は、新パスワードが使えない時間ではありませんでした。

変更前のパスワードで接続するとどうなるか

新しく接続しようとすると、t=3.6以降は失敗します。 一方で、変更前のパスワードですでに張ってある接続は切れません。変更の前後で次の4つを動かし続けましたが、いずれも無傷でした。

  • 保持した接続での SELECT 1(90回、エラーなし)
  • 変更をまたいで実行した SELECT SLEEP(45)(完走)
  • 変更前に開いたトランザクションの COMMIT(成功)
  • 別ユーザー app_user の新規ログイン(175回すべて成功)

ということは、接続プールが接続を持ち続けている間は問題が表に出ません。 新しい接続を張るタイミング、つまりプールの寿命切れ、再起動、デプロイ、スケールアウトのときに初めて失敗します。「変更した直後は平気だったのに、夜のデプロイで落ちた」という形になりやすいはずです(この部分は計測からの推測です)。

変更するリスク

リスク1:打ち間違えると約1分直せない

変更の約1秒後にもう一度変更を呼ぶと、拒否されました。

InvalidDBInstanceState

有効なのは1回目のパスワードで、状態が available に戻ったのは2回目の呼び出しから約61秒後でした。つまり、打ち間違えたパスワードで確定すると、新しい接続が通らないまま約1分は直せません。

変更するパスワードは、シェルに入力する前にシークレットの保管先へ入れておき、そこからコピーして使うのが安全です。

リスク2:再接続の嵐は、意外と大丈夫だった

パスワードを変えた直後に、古いパスワードのアプリが再接続を繰り返す場面を想定しました。誤ったパスワードでの20並列のログインを60秒間続け、その間に毎秒、正しいパスワードでのログインも試しています。

  • 失敗は 17,579回(すべて 1045 Access denied)
  • 正しいパスワードでの新規ログインの失敗は 0回
  • 新規接続の所要時間の中央値は約45ms(平常時は単発で8〜35ms)
  • Threads_connected のピークは24(max_connections は60)
  • ブロックは発生せず

毎秒約290回も失敗しているのに max_connect_errors の100に届きません。performance_schema.host_cache を見ると、SUM_CONNECT_ERRORS は0のままで、COUNT_AUTHENTICATION_ERRORS だけが増えていました。認証失敗はブロックの計数に入りません。

ただし、これはパスワードの間違いでブロックされないという意味であって、次のリスク3とは別の話です。また、今回は無負荷のDBです。負荷がかかったDBで同じことが起きても平気だとは言えません。

リスク3:Host Blockedはパスワードでは直らない

ここが今回いちばん知っておきたいところです。 認証失敗は数えられませんが、認証の前に切断された接続は数えられます。

TCPでつないで、認証に進まず切断する。これを正常なログインを挟まずに繰り返すと、101回目でブロックされました。 解除後にもう一度試しても101回目でした。

1129: Host '...' is blocked because of many connection errors; unblock with 'mysqladmin flush-hosts'

ブロック中の様子と、解除を試した結果は次のとおりです。

試したこと結果
同じIPから admin でログイン1129で拒否
同じIPから app_user でログイン1129で拒否(ユーザーではなく接続元単位)
ブロック前から保持していた接続問題なく使える
マスターパスワードの再設定(64秒)解除されない
FLUSH HOSTS8.4では 1064 の構文エラー。解除されない
TRUNCATE TABLE performance_schema.host_cache解除された
インスタンスの再起動(約49秒)解除された

「ログインできない!」と慌ててパスワードを再設定しても、1分待たされたうえに状況は何も変わりません。エラーコードが1045か1129かを見てから動くのが大事です。

また、FLUSH HOSTS は長年の定番ですが、今回の8.4では構文エラーでした。古い手順書をそのまま実行すると、ここで詰まります。エラー文とマニュアルにある mysqladmin flush-hosts は今回試していません。

TRUNCATE を実行するには、ブロックされていない経路からの接続が必要です。今回はブロック前の接続を保持していたので実行できました。別のIPの踏み台があれば、そちらからでも実行できるはずです。どちらもなければ再起動になり、その間は全接続が切れます。

正常なログインが1回でもあるとブロックされない

試しに、20回ごとに正常なログインを挟んで同じ切断を300回繰り返すと、ブロックされませんでした。MySQLのマニュアルが「成功した接続なしに連続して」と書いているとおりの動きです。

ここからは推測ですが、普段からログインが成功し続けているアプリサーバーより、たまにしか正常ログインしない接続元のほうが危ないと考えられます。DBのポートにTCPで疎通確認だけをする監視やヘルスチェックは、今回の実験と同じ形です。また、NATゲートウェイの裏の全サーバーはDBから同じIPに見えるので、ブロックされれば全台が巻き添えになるはずです。どちらも今回は試していません。

Secrets Managerは「約60秒、取得した値で入れない」

RDSにパスワードの管理を任せる --manage-master-user-password は、人がパスワードを扱わずに済む良い機能です。ところが、即時ローテーションを計測すると意外な結果になりました。

即時ローテーション
aws rds modify-db-instance --no-cli-pager 

ローテーション中に約1秒ごとに GetSecretValue で値を取り直し、その値でログインを試しました。

回起動方法失敗の開始(t)失敗の終了(t)長さ
1RDS経由8.669.460.8秒
2RDS経由8.968.759.8秒
3RDS経由8.468.259.8秒
4RDS経由7.767.659.9秒
5Secrets Manager経由9.169.260.1秒
6Secrets Manager経由7.568.160.6秒

6回とも、ほぼ60秒でした。偶然ではなさそうです。

なぜ失敗するのか

失敗している間、シークレットの次の版(ステージ AWSPENDING)の値でも試すと、そちらではログインできました(2〜6回目で確認)。

つまり、この約60秒間はこういう状態です。

  • DBのパスワードは、もう新しい値になっている
  • GetSecretValue が既定で返す AWSCURRENT は、まだ古い値のまま

切り替わった先の値が取得できるようになるのは、状態が available に戻るころです。新旧の入れ替わりそのものは手動変更と同じく、計測間隔(約1秒)の中ではどちらも通らない時間は見えませんでした。

何が困るのか

よくある「認証に失敗したらシークレットを取り直して再接続」が、1回きりだと効きません。 取り直しても約60秒は古い値が返ってくるからです。

今回試した範囲から言える対策はこちらです。

  • 再接続のリトライは少なくとも70秒以上続くようにする(最長で t=69.4 まで失敗したので、余裕を見て90秒程度)
  • その間の認証失敗はブロックの計数に入らないので(リスク2)、この目的のリトライでブロックされる心配は小さい
  • どうしても待てないなら AWSPENDING も試す手はありますが、おすすめはしません。 今回のローテーション開始直後は、版が存在しなかったり、存在してもまだログインできなかったりしました。AWSが利用者向けに保証している動きでもなさそうです

監視についても1つ注意があります。describe-db-instances の SecretStatus は、RDS経由だと rotating になりましたが、RotateSecret(Secrets Manager経由)では計測中ずっと active のままに見えました。ローテーション中かどうかの判定には使えません。

自動ローテーションでも同じなのか

RDS管理のシークレットは、既定で7日ごとにローテーションされます。今回の5・6回目は、Secrets Managerの RotateSecret で起動しました。ただし、スケジュールで起動した本物の自動ローテーションは計測していません。 待つと1回につき7日かかるためです。

同じ経路だとすれば、アプリが新しい接続を張った瞬間が、毎週の約60秒に当たると失敗することになります。任せる前に、接続処理のリトライが60秒を超えて続くかを確かめておくことをおすすめします。

切り替えのときの動き

  • 管理へ切り替える: 約2秒後に旧パスワードが失敗。シークレットの値でのログインはその直後に成功。切り替え直後の一瞬、シークレットの取得が ResourceNotFound になりました
  • 手動へ戻す: 約2秒後に新旧が入れ替わり。元のシークレットには DeletedDate が付きます。戻したあとは古いシークレットを読みに行かないよう、アプリ側の参照先を変えておく必要があります

どちらも既存の接続は無傷でした。

失敗した時の修正手段

マスターパスワードの再設定は、ログインできなくなったときの最後の手段として語られます。公式ドキュメントにも、権限を誤って消したら再設定で戻せると書いてあります。8.4の admin は SHOW GRANTS で見ると権限を rds_superuser_role というロール経由で持っているので、実際に壊して試しました。

壊し方症状再設定で直ったか
ALTER USER ... ACCOUNT LOCK3118 でログイン不可直った(完了まで約62秒)
SET DEFAULT ROLE NONEログインはできるが SELECT が 1142、CREATE USER が 1227直った(約63秒)
REVOKE rds_superuser_role同上。SHOW GRANTS は GRANT USAGE のみ直った(約62秒)
認証前の切断を繰り返す1129 でログイン不可直らない

ロールを剥がした場合も、再設定だけで GRANT と既定ロールが元どおりになりました。

小さな落とし穴として、8.4の admin に REVOKE CREATE USER, SELECT を実行すると、成功扱いなのに SHOW GRANTS は変わりません。 権限が直接ではなくロールに付いているので、直接の権限を剥がす操作は何も変えません。以前の感覚で権限を確認すると、思っていた状態と違うかもしれません。

ログに平文のパスワードは出ないか?確認してみた

CloudTrailの ModifyDBInstance を確認しました。パスワードを渡した7件すべてで、値は次のように伏せられていました。

"masterUserPassword": "HIDDEN_DUE_TO_SECURITY_REASONS"

拒否された変更(二重変更の2回目、使えない文字)も記録に残っていました。誰がいつ変えようとしたかは後から追えます。

一方で、シェルの履歴には自分の入力がそのまま残りえます。先ほどの read -rs はそのための一手間です(こちらは一般的な注意で、今回検証したものではありません)。

失敗を避けるためのチェックリスト

  1. マスターユーザーをアプリから直接使っていないか確認する(AWSも非推奨としています。分けてあれば、今回の影響はほぼ運用者だけで済みます)
  2. 新しいパスワードを先に保管先へ入れる。使えない文字が含まれていないか確認する
  3. 変更前に、ブロックされていない経路で接続を1本確保しておく
  4. 変更を実行し、新しいパスワードでのログインを確認する。available かどうかだけで判断しない
  5. 古いパスワードを使っている接続先(アプリ、バッチ、監視、BIツール)を更新する。既存の接続は切れないので、更新漏れは次の再接続まで見つかりません
  6. 更新後にアプリを再起動して、新しい接続が張れることを確認する

まとめ

実行していろいろ試してみた結果の知見を以下にまとめてみます。

  • 変更しても既存接続は切れない。失敗は次の新規接続で初めて表に出る
  • 新旧の入れ替わりは約3.6秒後。約60秒の resetting-master-credentials はパスワードが使えない時間ではない
  • 打ち間違えると、約1分は再変更できない
  • 認証失敗ではブロックされない(60秒17,579回で0件)。認証前の切断は101回目でブロックされる
  • Host Blockedはパスワードの再設定では直らない。 TRUNCATE TABLE performance_schema.host_cache か再起動で解除する。8.4の FLUSH HOSTS は構文エラー
  • ロックも権限喪失も、再設定で直った
  • Secrets Managerの即時ローテーションでは、取得した値でのログインが約60秒失敗する(6回すべて)。リトライは70秒以上続ける

いざという時に一番効くのは、慌てて再設定する前に、エラーコードを見ることです。1045なら再設定、1129なら再設定しても無駄です。この1行だけでも、手順書の先頭に書いておくと1分を節約できます。

参考リンク


この記事が何かのお役に立てれば幸いです。
最後までお読みいただきありがとうございました!

Share Me!