0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【検証】Secrets ManagerのローテーションLambdaが「Unable to import module」「pg module not found」で失敗する現象を再現・実証する

0
Posted at

検証の狙い

検証する主張は次の4点です。

  1. ランタイムとデプロイパッケージ(.so)は一体であり、ランタイム単独変更(または自動アップグレード)はimport失敗を起こす
  2. ローテーション失敗専用の CloudWatch メトリクスは存在せず、CloudTrail RotationFailed + Lambda Errors でのみ追える
  3. Found credentials in environment variables 以降で無音になるのはランタイムではなくネットワーク起因である(切り分けの分岐)
  4. 正規の更新パス(コンソールで新しいローテーション関数を作成)でローテーションが復旧する

設計判断・思想(「更新責任はユーザー側にある」「Runtimeを常に指定すべきではない」等)は観測できないため検証対象から除外します。

本検証は払い出しに SAR (Serverless Application Repository) の SecretsManagerRDSPostgreSQLRotationSingleUser テンプレートを用いる構成で実施します。
AWS::SecretsManager transform 固有の挙動検証(旧版のデフォルトランタイムが Python 3.10 か、CFN Runtime
追加 / 2024-09-16 移行での復旧)は、Terraform 管理のローテーション紐付けと競合しないSAR採用の都合上スコープ外とします。

主張→実験の対応づけ表

# 検証する主張(詳細版由来) 再現方法(何を起こすか) 証拠(観測すべきログ行 / メトリクス)
1 ランタイム単独変更/自動アップグレードで同梱.soと非互換になり壊れる SAR(SecretsManagerRDSPostgreSQLRotationSingleUser)で関数を払い出し、LambdaのRuntime設定だけを別バージョンに手動変更してローテーション実行 CloudWatch Logsに Unable to import module 'lambda_function' または pg module not found
2 ローテーション失敗専用CloudWatchメトリクスは無い/CloudTrail+Errorsで追う 主張1で失敗させた状態でメトリクスとイベントを確認 Lambda Errors > 0、CloudTrailに RotationFailed イベント(Secrets Manager名前空間にローテーション成否のネイティブメトリクスが無いこと)
3 Found credentials...後の無音はネットワーク起因でランタイムとは別軸 VPC内のLambdaからSecrets Managerエンドポイントに到達できない状態(エンドポイント未設定/SG遮断)でローテーション実行 Logsが Found credentials in environment variables 以降で無音(importエラーは出ない)/関数タイムアウト
4 正規パスでローテーションが復旧する 失敗状態のシークレットに対し、コンソールで新しいローテーション関数を作成して適用 CloudTrailに RotationSucceeded、Logsで4ステップ(createSecret/setSecret/testSecret/finishSecret)完走、AWSCURRENTが更新される

検証環境

hosted rotation Lambda は Terraform 単体では生成できない ため、本検証では SAR(Serverless Application Repository)の公式アプリ で払い出します。Terraform はシークレットとローテーションのスケジュール紐付けまでを担い、ローテーション Lambda 本体は SAR で払い出した関数 ARN を参照させます。SAR アプリは Lambda 本体・実行ロール・invoke permission のみを作成し RotationSchedule を作らないため、Terraform 側のローテーション紐付けと競合しません。

使用したリソースは以下のGitHubにて公開しています。
aws-secrets-rotation-lambda-failure-lab

構成要素

  • 検証用シークレット(RDS資格情報を模したJSON)
  • ローテーション用 Lambda(SAR アプリ SecretsManagerRDSPostgreSQLRotationSingleUser で払い出し、またはコンソールで新規作成)
  • 対象DB(最小構成の RDS。主張1〜2・4の再現には接続テスト=testSecret を通すために必要。主張1の import 失敗の観測だけなら createSecret 到達前後で十分な場合もある)
  • IAM(ローテーションLambda実行ロール。SAR テンプレートが自動付与)
  • 主張3用に VPC / サブネット / セキュリティグループ(エンドポイント到達不可を意図的に作る)

Terraform 概要(抜粋)

完全な一式ではなく、Terraform が担う範囲の抜粋です。

# シークレット本体
resource "aws_secretsmanager_secret" "rds" {
  name = "verify/rds-rotation-runtime"
}

resource "aws_secretsmanager_secret_version" "rds" {
  secret_id = aws_secretsmanager_secret.rds.id
  secret_string = jsonencode({
    username = "appuser"
    password = "INITIAL_PLACEHOLDER"
    engine   = "postgres"
    host     = aws_db_instance.verify.address
    dbname   = "appdb"
    port     = 5432
  })
}

# ローテーションのスケジュール紐付け。
# rotation_lambda_arn には SAR で払い出した hosted rotation Lambda の ARN を渡す
# (Terraform 単体では Lambda 本体は vend できない)。
resource "aws_secretsmanager_secret_rotation" "rds" {
  secret_id           = aws_secretsmanager_secret.rds.id
  rotation_lambda_arn = var.rotation_lambda_arn

  rotation_rules {
    automatically_after_days = 30
  }
}

hosted rotation Lambda 本体は、検証では SAR アプリ SecretsManagerRDSPostgreSQLRotationSingleUser で vend し、生成された関数 ARN を上記 rotation_lambda_arn に渡します。

コスト注意と Teardown 手順

  • RDS インスタンスは課金されます。 検証完了後は速やかに削除してください。検証中も最小インスタンスクラス・シングルAZ・ストレージ最小で構成します。
  • CloudWatch Logs / CloudTrail のログ保存も少額ながら課金対象です。検証後はロググループも削除します。
  • Teardown は次の順で確実に実施します。
    1. ローテーションスケジュールを無効化(aws_secretsmanager_secret_rotation の削除)
    2. hosted rotation Lambda を vend した CloudFormation スタックを削除(aws cloudformation delete-stack / マネジメントコンソール)
    3. Terraform 管理分を破棄: terraform destroy
    4. 残存リソース確認: Secrets Manager のシークレット(強制削除する場合は復元待機期間に注意)、Lambda 関数、RDS インスタンス・サブネットグループ、VPC / サブネット / セキュリティグループ、ロググループ
  • 検証後、対象リソースが残っていないことを必ず確認すること。

セキュリティ注意

  • 検証用シークレットの初期パスワードはプレースホルダ(INITIAL_PLACEHOLDER)とし、実運用の資格情報は使わない。
  • 検証環境は本番から分離したアカウント/VPCで実施する。
  • ローテーションLambdaの実行ロールは検証対象シークレット・DBに限定し、過剰な権限を付与しない。
  • 主張3でエンドポイント到達不可を意図的に作る際も、検証用VPCに閉じて行い、本番経路に影響を与えない。

シナリオ別検証

以下の実測結果は、検証用 AWS 環境で実際に観測したログ・メトリクス・スクリーンショットです。ログの抜粋はコンソール表示をそのまま転記しています。

検証1: ランタイム単独変更/自動アップグレードで.so非互換により壊れる

構成図

architecture-1.png

仮説

ランタイムとデプロイパッケージ(依存・.so)は一体であり、LambdaのRuntime設定だけを書き換える(または廃止に伴う自動アップグレードでランタイムだけが上がる)と、同梱.soのABIと不一致になり import に失敗する。

再現手順

  1. AWS Serverless Application Repository (SAR) の SecretsManagerRDSPostgreSQLRotationSingleUser で hosted rotation Lambda を払い出す(同梱依存は PyGreSQL)。起点ランタイムは aws lambda get-function-configuration で確認しておく。
  2. Lambda コンソールで対象関数の Runtime 設定だけを別バージョンに手動変更する(同梱.soは変えない)。
  3. Secrets Manager で「Rotate secret immediately」を実行する。
  4. 対象関数の CloudWatch Logs を確認する。

観測すべき証拠

CloudWatch Logs に Unable to import module 'lambda_function' または pg module not found(あるいは .so ロード失敗)が出力されれば、ランタイムと同梱依存の不一致が実証される。

実測結果

  • ランタイム変更

    • 変更前
      varificate-1-1.png
    • 変更後
      varificate-1-2.png
  • Secrets の手動ローテーション
    varificate-1-3.png

  • エラーログチェック(CloudWatch Logs のエラー文抜粋)

    [ERROR] Runtime.ImportModuleError: Unable to import module 'lambda_function': Cannot import shared library for PyGreSQL,
    probably because no libpq.so is installed.
    No module named 'pg._pg'
    Traceback (most recent call last):
    

考察

ランタイムだけを別バージョンへ手動変更し、同梱の .so(PyGreSQL)はそのままで手動ローテーションを実行したところ、CloudWatch Logs に Runtime.ImportModuleError: Unable to import module 'lambda_function' が出力された。エラー本文も Cannot import shared library for PyGreSQL / No module named 'pg._pg' と、同梱ネイティブ拡張(libpq.so / pg._pg)のロード失敗を直接示している。仮説どおり、ランタイムと同梱依存(.so)は一体であり、ランタイム単独変更で import 失敗が起きることが実証された。

検証2: ローテーション失敗専用CloudWatchメトリクスは無い/CloudTrail+Errorsで追う

構成図

architecture-2.png

仮説

ローテーション失敗専用の CloudWatch メトリクスは存在しない。失敗は CloudTrail の RotationFailed イベントと Lambda の Errors の併用でしか追えない。

再現手順

  1. 検証1で失敗状態を作る。
  2. CloudWatch メトリクス側で Secrets Manager 名前空間にローテーション成否のネイティブメトリクスが無いことを確認する。
  3. Lambda メトリクスで Errors を確認する。
  4. CloudTrail のイベント履歴で RotationFailed を確認する。

観測すべき証拠

Lambda Errors > 0 かつ CloudTrail に RotationFailed イベントが記録され、一方で Secrets Manager 側にローテーション成否専用メトリクスが見当たらなければ、主張が実証される。

実測結果

  • 検証1のエラー時のメトリクスを確認(Secrets Manager 名前空間には SecretCount のみで、ローテーション成否のメトリクスは存在しない)
    varificate-2-1.png
    varificate-2-2.png

  • Lambda メトリクスで Errors を確認
    varificate-2-3.png

  • CloudTrail のイベント履歴で RotationFailed を確認
    varificate-2-4.png

考察

Secrets Manager 名前空間のメトリクスは SecretCount のみで、ローテーション成否を示す専用メトリクスは存在しなかった。一方、失敗そのものは Lambda の Errors(>0)と CloudTrail の RotationFailed イベントの両方で確認できた。したがって、ローテーション失敗の監視は Secrets Manager のネイティブメトリクスではなく、Lambda Errors と CloudTrail RotationFailed の併用で行う必要がある、という主張が実証された。

検証3: Found credentials...後の無音はネットワーク起因(ランタイムとは別軸)

構成図

architecture-3.png

仮説

CloudWatch Logs が Found credentials in environment variables 以降で無音になるのは、Secrets Manager エンドポイント到達不可(ネットワーク)の典型であり、ランタイム更新では直らない。import エラーとは別の切り分けに進むべきである。

再現手順

  1. 正常に動くローテーション Lambda を VPC 内に配置する。
  2. Secrets Manager への到達経路を意図的に塞ぐ(本検証では Secrets Manager の VPC エンドポイントを一時的に削除)。
  3. ローテーションを実行し CloudWatch Logs を確認する。

観測すべき証拠

Logs が Found credentials in environment variables の後で無音になり(import エラーや pg module not found は出ない)、関数がタイムアウトすれば、ネットワーク起因でありランタイムとは別軸であることが実証される。

実測結果

  • VPC エンドポイントを一時的に削除
    varificate-3-1.png

  • ローテーションの手動実行
    varificate-1-3.png

  • CloudWatch Logs を確認(Lambda はタイムアウト 30 秒で設定しており、ログ上で Duration: 30000.00 ms となっていることからタイムアウトが発生したことが確認できる)
    varificate-3-2.png

考察

VPC エンドポイントを削除して Secrets Manager への到達経路を断った状態でローテーションを実行すると、CloudWatch Logs は Found credentials in environment variables 以降で無音となり、検証1で見られた import エラー(Unable to import module / pg module not found)は出力されなかった。最終的に Duration: 30000.00 ms でタイムアウト(設定値30秒)しており、無音はランタイム不整合ではなくネットワーク到達不可が原因であることが実証された。同じ「ローテーション失敗」でも、検証1(import エラーが出る)とは切り分けの軸が異なる点が対比できる。

検証4: 正規パスでローテーションが復旧する

構成図

architecture-4.png

仮説

失敗状態のローテーションに対し、正規パス(壊した構成を同梱依存と互換のある正常な状態へ戻す/コンソールで新しいローテーション関数を作成する)を取れば、ローテーションが復旧する。

再現手順

  1. 検証1・検証3で作った失敗要因を正常な状態へ戻す。
  2. Lambda のランタイムを Python 3.12(同梱依存と互換のあるバージョン)に戻す。
  3. 検証3で削除した Secrets Manager の VPC エンドポイントを再作成する。
  4. 「Rotate secret immediately」を実行し、CloudWatch Logs と CloudTrail、シークレットのステージングラベル(パスワード)を確認する。

観測すべき証拠

4ステップ(createSecret / setSecret / testSecret / finishSecret)が完走し、AWSCURRENT が新パスワードに更新されれば復旧が実証される。

実測結果

  • 検証3で削除した VPC エンドポイントを再作成
    varificate-4-1.png

  • 手動ローテーション実施
    varificate-1-3.png

  • パスワードが更新されていることを確認

    • 更新前
      varificate-4-2.png

    • 更新後
      varificate-4-3.png

  • CloudWatch Logs に4ステップのログが出力されている
    varificate-4-4.png

考察

ランタイムを同梱依存と互換のある Python 3.12 に戻し、検証3で削除した VPC エンドポイントを再作成したうえで手動ローテーションを実行すると、CloudWatch Logs に4ステップ(createSecret / setSecret / testSecret / finishSecret)が出力され、AWSCURRENT のパスワードが新しい値に更新された。これにより、壊した構成を正常な状態へ戻す正規パスでローテーションが復旧することが実証された。なお本実測では「新しいローテーション関数の作成」ではなく、検証1・検証3で意図的に壊した要因(ランタイム・ネットワーク)の復元によって復旧を確認している。いずれも「正常な構成へ戻す」という同じ正規パスの一形態である。

まとめ

検証用 AWS 環境での実測により、4つの主張はいずれも観測された証拠で裏付けられました。

# 主張 結果 主な証拠
1 ランタイム単独変更で同梱.soと非互換になり壊れる CloudWatch Logs の Runtime.ImportModuleError: Unable to import module 'lambda_function' / Cannot import shared library for PyGreSQL / No module named 'pg._pg'
2 ローテーション失敗専用 CloudWatch メトリクスは無い Secrets Manager 名前空間は SecretCount のみ/Lambda Errors > 0/CloudTrail RotationFailed
3 Found credentials... 後の無音はネットワーク起因 VPC エンドポイント削除後、import エラーは出ず Duration: 30000.00 ms でタイムアウト
4 正規パスでローテーションが復旧する ランタイム復元・VPC エンドポイント再作成後、4ステップ完走+AWSCURRENT のパスワード更新
  • 検証1と検証3は、どちらも「ローテーション失敗」でありながら切り分けの軸が異なります(import エラーが出る=ランタイム/同梱依存の不整合、無音でタイムアウト=ネットワーク到達不可)。ログに import エラーが出るか否かが分岐の決め手になります。
  • 検証2のとおり、ローテーション失敗を監視する専用メトリクスは存在しないため、Lambda Errors と CloudTrail RotationFailed を併用した監視設計が必要です。
  • 検証4は「新しいローテーション関数の作成」そのものではなく、検証1・検証3で意図的に壊した構成(ランタイム・ネットワーク)を正常状態へ戻すことで復旧を確認しました。いずれも「正常な構成へ戻す」正規パスの一形態です。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?