
Secret Manager のローテーション関数が無限に呼び出される原因と対処【Google Cloud】
はじめに
こんにちは。すらぼです。皆さんは Secret Manager でパスワードローテーションしてますか?
先日、Google Cloud の Secret Manager を使ってデータベースのパスワードを管理・ローテーションする仕組みを構築しようとしたところ、意図せずローテーションが無限ループする問題が発生しました。
この記事では、Secret Manager のローテーション機能を使う際に注意すべきポイントと、対応策についてまとめます。
やりたかったこと
Secret Manager にデータベースのパスワードを保存して、Cloud Run functions で定期的にローテーションする仕組みを作ろうとしていました。
具体的には以下のような構成です。
- Secret Manager にパスワードを保存
- ローテーション期間を設定して、期限が来たら自動でローテーション
- Pub/Sub トピックを通じてローテーション関数をトリガー
- Cloud Run functions でパスワードを更新
やったこと
以下の設定を行いました
- Secret Manager のシークレットに Pub/Sub トピックを設定
- ローテーション関数を実装
- ローテーション関数に Pub/Sub を監視する Eventarc トリガーを設定
- ローテーション期間と次回ローテーション日時を設定
ただ、このとき設定に誤りがあり、問題が発生しました。
起きた問題
期待としては、期限が来たら自動で1回だけローテーションが実行されることを想定していました。
ただ、実際には 1分間に20回以上ローテーションが連続して発生する 問題が発生しました。
ログを確認すると、パスワードローテーション関数が短時間に何度も呼び出されており、データベースのパスワードが次々と更新されていました。
原因: SECRET_VERSION_ADD イベントによる無限ループ
原因は、Secret Manager に設定した Pub/Sub トピックの設定にありました。
Secret Manager では、以下のすべてのイベントで Pub/Sub に通知が送信されます。
SECRET_ROTATE
- ローテーション期限到達時(処理したいイベント)SECRET_VERSION_ADD
- 新しいバージョン追加時- その他のイベント(SECRET_UPDATE, SECRET_DELETE など)
つまり、ローテーション関数が実行されて新しいシークレットバージョンを追加すると、SECRET_VERSION_ADD
イベントが発火し、再び関数が呼ばれてしまい、無限ループが発生します。
解決策
解決策は、大きく2通りあります。
- 関数内でイベントタイプをフィルタリングする
- Pub/Sub サブスクリプションでサブスクリプションフィルターを使う
それぞれ紹介します。
1. 関数内でイベントタイプをフィルタリングする
関数内でイベントタイプをチェックし、SECRET_ROTATE
イベントのみを処理するように実装します。
const functions = require('@google-cloud/functions-framework');
functions.http('rotatePassword', async (req, res) => {
try {
// Pub/Sub メッセージからイベントタイプを取得
const pubsubMessage = req.body.message;
const attributes = pubsubMessage?.attributes || {};
// SECRET_ROTATE イベントのみ処理(SECRET_VERSION_ADD は無視)
if (attributes.eventType !== 'SECRET_ROTATE') {
console.log(`Ignoring event type: ${attributes.eventType}`);
res.status(200).json({
success: true,
message: `Event type ${attributes.eventType} ignored`
});
return;
}
console.log('Processing SECRET_ROTATE event');
// ここにローテーション処理を記述
// ...
これにより
SECRET_ROTATE
イベント → パスワードローテーションを実行- それ以外(
SECRET_VERSION_ADD
など) のイベント → 何もしない
という動作になり、無限ループを回避できます。
後述の Pub/Sub からの直接呼び出しに比べると
- メリット
- Eventarc トリガーであるため、Cloud Run functions の管理画面から確認でき、後から設定を追いやすい
- デメリット
- 関数が2回呼ばれてしまう
- 関数の修正を誤ると、無限ループが発生するリスクがある(→根本的な解決ではない)
このデメリットを気にする場合は、以下の方法があります。
2. Pub/Sub サブスクリプションでサブスクリプションフィルタを使う
もう1つの解決策として、Pub/Sub サブスクリプション側でフィルタリングする方法があります。
この方法では、関数側のコード変更が不要で、Google Cloud 側での設定のみで対応できます。
手順
まず、ローテーション関数の Cloud Run functions の URL を確認します。
gcloud functions describe $CLOUD_RUN_FUNCTIONS_NAME --region=asia-northeast1 --gen2 --format="value(serviceConfig.uri)"
次に、Push サブスクリプションを手動で作成し、SECRET_ROTATE
イベントのみをフィルタリングします。
gcloud pubsub subscriptions create rotation-subscription \
--topic=$PUB_SUB_TOPIC \
--push-endpoint=$CLOUD_RUN_FUNCTIONS_URL \
--push-auth-service-account=$SERVICE_ACCOUNT \
--push-auth-token-audience=$CLOUD_RUN_FUNCTIONS_URL \
--message-filter='attributes.eventType="SECRET_ROTATE"'
※ $PUB_SUB_TOPIC
, $CLOUD_RUN_FUNCTIONS_URL
, $SERVICE_ACCOUNT
は、適切な値に置き換えてください。
作成したサブスクリプションの設定を確認します。
gcloud pubsub subscriptions describe rotation-subscription
filter: attributes.eventType="SECRET_ROTATE"
と表示されていれば OK です。
この方法のメリット・デメリット
- メリット
- 関数のコード変更が不要
SECRET_VERSION_ADD
イベントはサブスクリプション側でフィルタリングされるため、関数が呼ばれない。- インフラ設定のみで対応可能
- デメリット
- Eventarc トリガーではないため、Cloud Run functions の管理画面から「トリガー」として表示されない
注意点
この方法を使う場合、Eventarc トリガーは作成しません。Eventarc トリガーを作成すると、別のサブスクリプションが自動作成されてしまい、フィルタなしのサブスクリプションからも関数が呼ばれてしまいます。
手動で作成した Push サブスクリプションのみを使用することで、イベントタイプを適切にフィルタリングできます。
補足: Eventarc トリガーだけでは対応できない
Eventarc にもフィルター機能が存在します。当初はこれを使えばいいのではと思いましたが、以下の制約から実現できないことがわかりました。
- Secret Manager からはカスタム属性で
SECRET_ROTATE
やSECRET_VERSION_ADD
などが呼ばれている - Eventarc トリガーは、カスタム属性ではフィルターできない。
- 自動作成されるサブスクリプションに、フィルターを設定する方法がない。
- サブスクリプションフィルターは作成時しか設定できず、作成後の変更はできない。
これらの制約から、上記2つのいずれかの対応が必要でした。
まとめ
紹介した2つの方法のどちらかを使うことで、無限ループを避けることができます。
Secret Manager のローテーションを自動で行う際は、イベントタイプのフィルタリングを忘れないようにしましょう。