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へのアクセス権限が必要になる。

graph TD Dev["開発者 / CI"] -->|update-function-configuration| LambdaService["Lambdaサービス"] LambdaService -->|CMK指定時: KMS Encrypt| KMS["AWS 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["関数コード"]
  1. 設定フェーズ:環境変数をLambda関数に登録する。CMKを指定した場合、KMSで暗号化された状態でAWS側に保存される。
  2. コールドスタート時:Lambdaサービスが実行環境を初期化する際、CMK暗号化が有効な場合はKMSを呼び出して復号し、プロセスの環境変数として展開する。
  3. 関数実行時:コードはOSの環境変数として平文の値を読み取る。KMSの呼び出しはLambdaサービスが代行しており、コード側では意識不要。
  4. 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}'
sequenceDiagram participant Dev as 開発者 participant Lambda as Lambdaサービス participant KMS as AWS KMS participant Role as 実行ロール participant Code as 関数コード Dev->>Lambda: update-function-configuration
(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']で取得
  1. CMK設定後の保存フローupdate-function-configuration呼び出し時にKMSで暗号化され、暗号化テキストとして保存される。
  2. コールドスタート時の復号:Lambdaサービスが実行ロールの権限でKMS Decryptを呼び出し、平文に戻してプロセスに渡す。
  3. ウォームスタート時:実行環境が再利用されるため、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復号権限の付与を忘れずに。

用語集

用語説明
CMK(Customer Managed Key)ユーザーが作成・管理するKMS暗号化キー。キーポリシーで細かいアクセス制御が可能。
AWS管理キーAWSが自動作成・管理するKMSキー。aws/lambdaのようにサービス名がプレフィックスになる。
コールドスタートLambdaが新しい実行環境を初期化するフェーズ。CMK使用時はここでKMS復号が発生する。
実行ロールLambda関数がAWSサービスを呼び出す際に使用するIAMロール。CMK使用時はKMS Decrypt権限が必要。
NoEchoCloudFormationパラメータの属性。trueにするとコンソールやAPI出力で値がマスクされる。

コメント

このブログの人気の投稿

EC2 SSH接続タイムアウトの原因と修正方法 — セキュリティグループのインバウンドルール完全ガイド

S3パブリックアクセス拒否の原因と解決策:バケットレベルの「Block Public Access」が優先される仕組み

EC2インスタンスIDをメタデータから取得する方法 — IMDSv2が安全な理由