Lambda環境変数の使い方とKMS暗号化:DBエンドポイントをコードから切り離す実践ガイド
Lambdaのコードにデータベースエンドポイントをハードコードしたまま本番デプロイしてしまった経験は、エンジニアなら一度はある。設定値が変わるたびにデプロイが必要になり、最悪の場合は認証情報がリポジトリに残る。Lambda環境変数とKMS暗号化を組み合わせることで、この問題を構造的に解決できる。
TL;DR:Lambda環境変数とKMS暗号化の要点
| 項目 | 内容 |
|---|---|
| 環境変数の設定方法 | コンソール・CLI・CloudFormation/CDKで設定可能 |
| デフォルト暗号化 | 保存時はAWS管理キー(aws/lambda)で自動暗号化 |
| カスタムKMS暗号化 | 顧客管理キー(CMK)を指定して追加保護が可能 |
| コード側の取得方法 | process.env / os.environ など言語標準の環境変数APIで取得 |
| 注意点 | 環境変数の値はLambdaコンソールで平文表示される(CMK指定時はコンソール上で暗号化テキストとして表示可能) |
Lambda環境変数の仕組みを理解する
Lambda環境変数は、関数コードから分離された設定値をランタイム起動時に注入する仕組みだ。コンテナイメージで言えば、docker run -e KEY=VALUE に相当する。関数が実行されると、設定した環境変数はOSレベルの環境変数としてランタイムプロセスに渡され、コードからは通常の環境変数として読み取れる。
暗号化の層は2段階ある。まず、すべての環境変数はAWSのインフラ側でAWS管理キー(aws/lambda)を使って保存時に暗号化される。これはデフォルト動作で、追加設定は不要だ。次に、顧客管理キー(CMK)を指定すると、そのキーで追加の暗号化レイヤーが適用される。CMKを使う場合、コンソール上での値の表示が暗号化テキストになり、復号にはKMSへのアクセス権限が必要になる。
CMK"] KMS -->|暗号化テキスト返却| LambdaService LambdaService -->|暗号化済み値を保存| Storage["Lambda設定ストレージ"] Storage -->|コールドスタート時| LambdaService LambdaService -->|KMS Decrypt呼び出し
実行ロール権限使用| KMS KMS -->|平文返却| LambdaService LambdaService -->|OS環境変数として展開| Runtime["Lambdaランタイム
process.env / os.environ"] Runtime -->|コードから読み取り| Code["関数コード"]
- 設定フェーズ:環境変数をLambda関数に登録する。CMKを指定した場合、KMSで暗号化された状態でAWS側に保存される。
- コールドスタート時:Lambdaサービスが実行環境を初期化する際、CMK暗号化が有効な場合はKMSを呼び出して復号し、プロセスの環境変数として展開する。
- 関数実行時:コードはOSの環境変数として平文の値を読み取る。KMSの呼び出しはLambdaサービスが代行しており、コード側では意識不要。
- IAM制御:CMKを使う場合、Lambda実行ロールにKMSの復号権限が必要。これがないとコールドスタートが失敗する。
環境変数の設定方法:CLIを使った実践手順
まず、暗号化なし(デフォルトのAWS管理キー)で環境変数を設定する基本パターンから始める。データベースエンドポイントのような接続情報を渡す典型的なユースケースだ。
ステップ1:既存の環境変数設定を確認する
変更前に現在の設定を確認しておく。既存の環境変数を上書きしてしまうミスを防ぐためだ。update-function-configurationは差分更新ではなく、--environmentに指定したオブジェクト全体で置き換わる点に注意が必要。
aws lambda get-function-configuration \
--function-name my-function \
--region us-east-1 \
--query 'Environment'
ステップ2:環境変数を設定する(AWS管理キー)
既存の変数を含めてすべての環境変数を一括で指定する。部分更新はできないため、get-function-configurationで取得した既存の値もマージして渡す。
aws lambda update-function-configuration \
--function-name my-function \
--region us-east-1 \
--environment 'Variables={DB_ENDPOINT=mydb.cluster-xxxx.us-east-1.rds.amazonaws.com,DB_PORT=5432,ENV=production}'
ステップ3:コードから環境変数を読み取る
ランタイムに関係なく、OSの環境変数として取得できる。以下はPythonとNode.jsの例だ。
# Python
import os
def handler(event, context):
db_endpoint = os.environ['DB_ENDPOINT']
db_port = os.environ.get('DB_PORT', '5432') # デフォルト値付き
return {'endpoint': db_endpoint}
// Node.js
exports.handler = async (event) => {
const dbEndpoint = process.env.DB_ENDPOINT;
const dbPort = process.env.DB_PORT || '5432';
return { endpoint: dbEndpoint };
};
KMSカスタムキーによる暗号化:Lambda環境変数のセキュリティ強化
AWS管理キーによるデフォルト暗号化で多くのケースは十分だが、CMKを使う理由が明確にある場合がある。キーポリシーによるアクセス制御、CloudTrailでのKMS呼び出し監査、キーローテーションの自律管理などだ。セキュリティ要件やコンプライアンス要件がそれを求めるなら、CMKを使う。
ステップ4:KMSキーを作成する
Lambda専用のCMKを作成する。キーポリシーで許可するプリンシパルを最小化しておくことが重要だ。
aws kms create-key \
--description 'Lambda environment variable encryption key' \
--region us-east-1
出力のKeyMetadata.KeyIdを控えておく。次のコマンドでエイリアスを付けると管理しやすい。
aws kms create-alias \
--alias-name alias/lambda-env-key \
--target-key-id <KeyId> \
--region us-east-1
ステップ5:Lambda実行ロールにKMS権限を付与する
CMKを使う場合、Lambda実行ロールがKMSの復号を呼び出せないとコールドスタートが失敗する。これを見落とすと、関数がAccessDeniedExceptionで起動できなくなる。IAMポリシーに以下を追加する。
🔽 IAMポリシードキュメントを展開
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowLambdaKMSDecrypt",
"Effect": "Allow",
"Action": [
"kms:Decrypt"
],
"Resource": "arn:aws:kms:us-east-1:123456789012:key/<KeyId>"
}
]
}
ポリシーをロールにアタッチする。
aws iam put-role-policy \
--role-name my-lambda-execution-role \
--policy-name LambdaKMSDecryptPolicy \
--policy-document file://kms-decrypt-policy.json
ステップ6:CMKを指定して環境変数を設定する
KMSキーのARNを--kms-key-arnで指定する。これ以降、環境変数はCMKで暗号化されて保存される。
aws lambda update-function-configuration \
--function-name my-function \
--region us-east-1 \
--kms-key-arn arn:aws:kms:us-east-1:123456789012:key/<KeyId> \
--environment 'Variables={DB_ENDPOINT=mydb.cluster-xxxx.us-east-1.rds.amazonaws.com,DB_PORT=5432,ENV=production}'
ステップ7:設定を確認する
KMSキーが正しく紐付いているか確認する。
aws lambda get-function-configuration \
--function-name my-function \
--region us-east-1 \
--query '{KMSKeyArn:KMSKeyArn, Environment:Environment}'
(kms-key-arn指定) Lambda->>KMS: Encrypt(環境変数値) KMS-->>Lambda: 暗号化テキスト Lambda-->>Dev: 設定完了 Note over Lambda,Code: コールドスタート発生時 Lambda->>Role: AssumeRole Role-->>Lambda: 一時認証情報 Lambda->>KMS: Decrypt(暗号化テキスト) KMS-->>Lambda: 平文の環境変数値 Lambda->>Code: OS環境変数として注入 Code->>Code: os.environ['DB_ENDPOINT']で取得
- CMK設定後の保存フロー:
update-function-configuration呼び出し時にKMSで暗号化され、暗号化テキストとして保存される。 - コールドスタート時の復号:Lambdaサービスが実行ロールの権限でKMS Decryptを呼び出し、平文に戻してプロセスに渡す。
- ウォームスタート時:実行環境が再利用されるため、KMS呼び出しは発生しない。コールドスタートのみKMSコストが発生する。
よくある失敗パターン:誤診断から正しい原因特定へ
CMKを設定した直後に関数が起動しなくなり、CloudWatchログに何も出ない。最初はコードのバグを疑ってデプロイをロールバックしようとするが、実際にはKMS権限の問題でコールドスタート自体が失敗している。ログが出ないのはハンドラーに到達する前に落ちているからだ。
確認すべき場所はCloudWatchのLambdaログではなく、CloudTrailのKMSイベントだ。
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=Decrypt \
--region us-east-1 \
--query 'Events[?contains(CloudTrailEvent, `my-function`)].{Time:EventTime,Error:CloudTrailEvent}'
ここでAccessDeniedExceptionが見つかれば、実行ロールへのKMS権限付与が漏れている。ステップ5のIAMポリシーを確認する。
コールドスタート失敗はLambdaのログに出ない。KMSやVPCなどの外部依存が起動前に失敗する場合、CloudTrailかLambdaサービスのエラーメトリクスを見るしかない。
もう一つの落とし穴は、update-function-configurationの環境変数が全置換であることを忘れることだ。既存の変数を取得せずに一部だけ更新しようとすると、他の変数が消える。CIパイプラインで自動化する場合は、必ず現在の設定を取得してマージする処理を入れる。
CloudFormationでの宣言的な管理
手動のCLI操作はアドホックな確認には使えるが、本番環境ではInfrastructure as Codeで管理する。CloudFormationでの記述例を示す。
🔽 CloudFormationテンプレートを展開
AWSTemplateFormatVersion: '2010-09-09'
Resources:
MyLambdaFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: my-function
Runtime: python3.12
Handler: index.handler
Role: !GetAtt LambdaExecutionRole.Arn
KmsKeyArn: !GetAtt LambdaEnvKey.Arn
Environment:
Variables:
DB_ENDPOINT: !Ref DBEndpoint
DB_PORT: '5432'
ENV: production
Code:
ZipFile: |
import os
def handler(event, context):
return {'endpoint': os.environ['DB_ENDPOINT']}
LambdaEnvKey:
Type: AWS::KMS::Key
Properties:
Description: Lambda environment variable encryption key
EnableKeyRotation: true
KeyPolicy:
Version: '2012-10-17'
Statement:
- Sid: AllowRootAccount
Effect: Allow
Principal:
AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
Action: 'kms:*'
Resource: '*'
- Sid: AllowLambdaDecrypt
Effect: Allow
Principal:
AWS: !GetAtt LambdaExecutionRole.Arn
Action: 'kms:Decrypt'
Resource: '*'
Parameters:
DBEndpoint:
Type: String
NoEcho: true
Description: Database endpoint URL
NoEcho: trueを指定することで、CloudFormationコンソールやCLI出力でパラメータ値がマスクされる。DBエンドポイントのような接続情報には必ず設定する。
Lambda環境変数のセキュリティ上の限界を理解する
環境変数は設定の分離には有効だが、シークレット管理の完全な代替ではない。いくつかの制約を把握しておく必要がある。
まず、CMKを使っても、Lambda実行ロールを持つコードからは平文で読み取れる。環境変数はプロセスレベルで平文展開されるため、コード内でログ出力してしまうと値が漏れる。print(os.environ)のような全環境変数ダンプは本番コードに残してはいけない。
パスワードや APIキーのような高機密情報には、AWS Secrets ManagerまたはSSM Parameter Store(SecureString)の使用を検討する。これらはコード実行時に動的に取得でき、ローテーションの仕組みも持つ。DBエンドポイントのような接続情報は環境変数で十分なケースが多いが、認証情報はSecrets Managerで管理するという使い分けが現実的だ。
まとめとNext Steps:Lambda環境変数の実運用
Lambda環境変数を使ったDBエンドポイントの外部化は、コードと設定を分離する最初のステップだ。デフォルトのAWS管理キーで十分なケースも多いが、監査要件やキー管理の自律性が必要な場合はCMKを追加する。CMKを使う場合は、実行ロールへのKMS復号権限の付与を忘れずに。
- 高機密情報の管理には AWS Secrets Manager を検討する
- 設定値の階層管理には SSM Parameter Store が有効
- 環境変数のサイズ制限や個数制限は Lambda公式ドキュメント で最新値を確認すること
用語集
| 用語 | 説明 |
|---|---|
| CMK(Customer Managed Key) | ユーザーが作成・管理するKMS暗号化キー。キーポリシーで細かいアクセス制御が可能。 |
| AWS管理キー | AWSが自動作成・管理するKMSキー。aws/lambdaのようにサービス名がプレフィックスになる。 |
| コールドスタート | Lambdaが新しい実行環境を初期化するフェーズ。CMK使用時はここでKMS復号が発生する。 |
| 実行ロール | Lambda関数がAWSサービスを呼び出す際に使用するIAMロール。CMK使用時はKMS Decrypt権限が必要。 |
| NoEcho | CloudFormationパラメータの属性。trueにするとコンソールやAPI出力で値がマスクされる。 |
コメント
コメントを投稿