Lambda にシークレットを渡すとき、環境変数にそのまま入れるか、ランタイムで Secrets Manager から取るか、悩んだんですけど——正直「KMS で暗号化されるんだし環境変数でいいのでは?」と思っていました。
調べてみると、これがけっこう違う問題でした。
「保存時の暗号化」と「取り扱い中の露出」は別の話
Lambda 環境変数は静止時に KMS で暗号化されます。そこは本当です。ただしデフォルト(AWS managed key)の場合、lambda:GetFunctionConfiguration 権限を持つ人やツールからは平文で読めます。CMK(Customer Managed Key)を使えば kms:Decrypt 権限でさらに制御できますが、ツールチェーン側の露出は残ります。
具体的な露出経路はこんな感じです。
環境変数方式の露出経路(Lambda 外)
構成管理ツール側
├─ Terraform state → JSON 内に平文で記録
├─ terraform plan → diff 出力に平文で表示
├─ lambroll diff → 同上
└─ CloudFormation → パラメータで直値を渡すと平文
(dynamic reference 利用時は露出経路が異なる)
CI/CD 側
├─ デプロイパイプラインの履歴 → plan 出力が保存される
└─ PR 上の plan 結果コメント → GitHub に平文が残る
lambroll の diff コマンドで Secrets Manager の値が平文表示される、というのが自分でハマったきっかけです。でもこれは lambroll だけの問題ではなくて、「Lambda を取り巻くツールチェーン全体」の問題です。
Lambda 自体のセキュリティとツールチェーンのセキュリティは別レイヤー。環境変数の KMS 暗号化は前者しかカバーしません。
本質は「いつシークレットを解決するか」
「環境変数は危険だからやめよう」というシンプルな話ではありません。正しい問いは「シークレットの解決をデプロイ時にやるか、ランタイム時にやるか」です。
デプロイ時解決(環境変数方式)
Secrets Manager → Terraform/lambroll
→ 環境変数として埋め込み → Lambda 実行
メリット: コードがシンプル / レイテンシゼロ
コスト : パイプライン全体に平文が流れる
ローテーション時に再デプロイが必要
---
ランタイム解決(SDK / Extension 方式)
Lambda 実行 → SDK/Extension
→ Secrets Manager から取得 → 利用
メリット: パイプラインに平文が流れない
ローテーション時に再デプロイ不要
コスト : 取得時のレイテンシ / API 呼び出しコスト
コードの複雑性が増す
どちらを選ぶかはセキュリティ要件だけでなく、「そのシークレットがローテーションされるか」「パイプラインのログが永続化されるか」で決まります。
レイテンシの問題は Parameters and Secrets Extension でかなり解消された
「SDK で取るとレイテンシが増える」はかつては事実でした。決め手になったのは AWS Parameters and Secrets Lambda Extension(2022年リリース)の存在です。
Extension は Lambda 実行環境に常駐するプロセスで、ローカル HTTP サーバー(port 2773)からシークレットをキャッシュして返してくれます。ただしハンドラ内(INVOKE フェーズ)からのみ呼べます。グローバル初期化コードからの取得は SDK 直接呼び出しに切り替える必要があります。
取得方式ごとのレイテンシ比較(目安) 方式 コールドスタート ウォームスタート ────────────────────────────────────────────────────────────────── 環境変数 0 ms 0 ms SDK 直接(キャッシュなし) 50-200 ms 50-200 ms SDK + アプリ内キャッシュ 50-200 ms ~0 ms Parameters and Secrets Extension ~50 ms (初回) ~12 ms
(レイテンシは計測値の一例です。公式仕様値ではなく、環境やリージョンによって異なります)
ウォームスタート時で約 12ms。ほとんどのユースケースでは無視できる水準です。メモリのオーバーヘッドは約 50MB なので、レイテンシもメモリも致命的というほどではありません。
判断フロー
「このシークレット、どう渡す?」と自分に問うとき、こういう順で考えています。
そのシークレットはローテーションされるか?
│
├─ Yes → ランタイム解決(SDK / Extension)一択
│ 再デプロイなしでローテーションに追従できる
│
└─ No → デプロイパイプラインに plan 出力や state が残るか?
│
├─ Yes(Terraform / lambroll / CloudFormation)
│ │
│ ├─ plan 出力が PR コメントや CI ログに残る?
│ │ │
│ │ ├─ Yes → ランタイム解決を推奨
│ │ │ 平文が GitHub/CI 履歴に永続化される
│ │ │
│ │ └─ No → sensitive + state 暗号化を前提に
│ │ 環境変数方式も許容可能
│ │
│ └─ state の保管先にアクセス制御があるか?
│ ├─ S3 + SSE + バケットポリシー → 許容範囲
│ └─ ローカル state → ランタイム解決を推奨
│
└─ No(手動設定のみ)
→ 環境変数方式でも実害は小さい
ただし GetFunctionConfiguration の
権限管理は必要
ローテーションがあるなら迷わずランタイム解決です。ないケースでも、Terraform や lambroll を使っていて plan 出力が CI ログに残るなら、ランタイム解決の方が後悔が少ないです。
まとめ
Lambda 環境変数の KMS 暗号化は「Lambda に保存されているときだけ」の話で、パイプラインや state を流れる間は平文です。この差を意識しておくと、「環境変数で十分?」の問いに自分なりの答えを出せます。
- ローテーションあり → ランタイム解決一択
- plan/diff/state が CI/PR に残る構成 → ランタイム解決を推奨
- 手動設定のみ・ローテーションなし → 環境変数方式も許容範囲(GetFunctionConfiguration の権限管理は必要)
Parameters and Secrets Lambda Extension のおかげで「SDKで取るとレイテンシが」という懸念はかなり薄れました。どちらを使うかは、レイテンシよりも「シークレットがいつ・どこで平文になるか」で判断するのが実態に合っています。