Sử Dụng Environment Variables Trong AWS Lambda: Cấu Hình và Mã Hóa Với KMS

Bạn vừa deploy Lambda function lên production và nhận ra endpoint database đang được hardcode thẳng trong source code — điều này không chỉ là security risk mà còn khiến mỗi lần đổi môi trường phải rebuild và redeploy toàn bộ function. Bài viết này hướng dẫn cách sử dụng Lambda Environment Variables để tách cấu hình khỏi code, và cách mã hóa chúng bằng AWS KMS để bảo vệ thông tin nhạy cảm như database endpoint, credentials, hay API keys.

TL;DR — Lambda Environment Variables và KMS Encryption

Vấn đềGiải pháp
Hardcode config trong codeDùng Environment Variables, đọc qua os.environ
Giá trị nhạy cảm lộ trong consoleBật encryption at rest bằng KMS CMK
Cần decrypt trong runtimeDùng KMS Decrypt API trong function code
Phân quyền truy cập KMSGắn kms:Decrypt vào execution role

Cơ Chế Hoạt Động Của Lambda Environment Variables

Lambda lưu environment variables dưới dạng key-value pairs gắn với function configuration — không phải với deployment package. Khi function được invoke, Lambda runtime inject các biến này vào process environment trước khi handler code chạy. Điều này có nghĩa là code chỉ cần đọc os.environ['DB_ENDPOINT'] mà không cần biết giá trị được lưu ở đâu.

Về mặt lưu trữ, Lambda luôn mã hóa environment variables at rest bằng một AWS-managed key theo mặc định. Tuy nhiên, key mặc định này là shared key — tất cả Lambda functions trong cùng account và region dùng chung. Nếu bạn cần kiểm soát chặt hơn, ví dụ audit log riêng hoặc rotation policy riêng, bạn cần chỉ định một Customer Managed Key (CMK) từ AWS KMS.

Ngoài encryption at rest, Lambda còn hỗ trợ encryption in transit — giá trị được mã hóa bằng KMS trước khi gửi đến Lambda service. Với tùy chọn này, giá trị thực sự không bao giờ xuất hiện dưới dạng plaintext trong Lambda console, và bạn phải tự gọi KMS Decrypt API trong function code để lấy giá trị gốc.

graph LR Config["Function Configuration
Env Vars (encrypted at rest)"] --> Invoke["Lambda Invoke Request"] Invoke --> Runtime["Lambda Runtime
inject env vars vào process"] Runtime --> Handler["Handler Code
os.environ['DB_ENDPOINT']"] Handler -->|"nếu dùng encryption in transit"| KMS["KMS Decrypt API"] KMS -->|"plaintext value"| Handler ExecRole["Execution Role"] -->|"kms:Decrypt"| KMS
  1. Function Configuration: Environment variables được lưu cùng function config, mã hóa at rest bằng KMS.
  2. Invoke: Khi có invoke request, Lambda runtime nhận config và inject variables vào process environment.
  3. Handler Execution: Code đọc biến qua os.environ — nếu dùng encryption in transit, phải gọi thêm KMS Decrypt.
  4. KMS Decrypt (tùy chọn): Execution role cần có quyền kms:Decrypt trên CMK tương ứng.

Thiết Lập Lambda Environment Variables Qua AWS CLI

Cách nhanh nhất để kiểm tra cơ chế này là dùng CLI. Ví dụ dưới đây tạo hoặc cập nhật environment variables cho một function đã tồn tại. Lưu ý rằng --environment sẽ ghi đè toàn bộ environment variables hiện tại — không phải merge. Nếu bạn chỉ muốn thêm một biến mà giữ nguyên các biến khác, hãy lấy giá trị hiện tại trước rồi merge lại.

# Cập nhật environment variables cho Lambda function
aws lambda update-function-configuration \
  --function-name my-db-function \
  --environment 'Variables={DB_ENDPOINT=mydb.cluster-abc123.us-east-1.rds.amazonaws.com,DB_PORT=5432,ENVIRONMENT=production}'
# Kiểm tra environment variables hiện tại của function
aws lambda get-function-configuration \
  --function-name my-db-function \
  --query 'Environment'

Trong Python runtime, đọc biến như sau:

import os

def handler(event, context):
    db_endpoint = os.environ['DB_ENDPOINT']
    db_port = os.environ.get('DB_PORT', '5432')  # fallback nếu không có
    # ... kết nối database
    return {'statusCode': 200}

Mã Hóa Environment Variables Bằng KMS CMK

Đây là phần mà nhiều engineer bỏ qua vì nghĩ AWS-managed key là đủ. Thực tế, nếu bạn cần audit trail riêng cho từng function, hoặc cần revoke access cho một function cụ thể mà không ảnh hưởng function khác, bạn phải dùng CMK.

Bước 1: Tạo KMS CMK

aws kms create-key \
  --description 'CMK for Lambda environment variables - my-db-function' \
  --key-usage ENCRYPT_DECRYPT \
  --query 'KeyMetadata.KeyId' \
  --output text
# Tạo alias dễ nhớ cho key
aws kms create-alias \
  --alias-name alias/lambda-my-db-function \
  --target-key-id <KEY_ID_từ_bước_trên>

Bước 2: Gắn KMS Key Vào Lambda Function

aws lambda update-function-configuration \
  --function-name my-db-function \
  --kms-key-arn arn:aws:kms:us-east-1:123456789012:key/<KEY_ID> \
  --environment 'Variables={DB_ENDPOINT=mydb.cluster-abc123.us-east-1.rds.amazonaws.com,DB_PORT=5432}'

Bước 3: Cấp Quyền Cho Execution Role

Execution role của Lambda cần quyền kms:Decrypt trên CMK. Nếu thiếu quyền này, function sẽ fail ngay khi khởi động với lỗi AccessDeniedException từ KMS — không phải lỗi trong handler code.

🔽 IAM Policy cho Execution Role (click để mở rộng)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowKMSDecryptForEnvVars",
      "Effect": "Allow",
      "Action": [
        "kms:Decrypt"
      ],
      "Resource": "arn:aws:kms:us-east-1:123456789012:key/<KEY_ID>"
    }
  ]
}
# Gắn inline policy vào execution role
aws iam put-role-policy \
  --role-name my-db-function-execution-role \
  --policy-name KMSDecryptForEnvVars \
  --policy-document file://kms-decrypt-policy.json

Encryption In Transit: Khi Nào Cần Và Cách Dùng

Encryption at rest bằng CMK bảo vệ giá trị khi lưu trữ, nhưng giá trị vẫn xuất hiện dưới dạng plaintext trong Lambda console và trong response của get-function-configuration. Nếu bạn cần bảo vệ giá trị khỏi bị nhìn thấy qua console — ví dụ với database password — bạn cần dùng encryption helpers (encrypt in transit).

Với cơ chế này, bạn tự mã hóa giá trị bằng KMS trước khi lưu vào environment variable, và decrypt trong runtime. Lambda console chỉ thấy ciphertext.

sequenceDiagram participant Dev as Developer participant KMS as AWS KMS participant Lambda as Lambda Config participant Runtime as Lambda Runtime Dev->>KMS: kms:Encrypt(plaintext_password) KMS-->>Dev: ciphertext_blob (base64) Dev->>Lambda: update-function-configuration (ciphertext as env var) Note over Lambda: Console chỉ thấy ciphertext Runtime->>Runtime: Cold start — load env vars Runtime->>KMS: kms:Decrypt(ciphertext_blob) KMS-->>Runtime: plaintext_password Runtime->>Runtime: Cache trong module-level variable
  1. Encrypt trước khi lưu: Dùng kms:Encrypt để mã hóa plaintext value, lưu ciphertext vào environment variable.
  2. Runtime decrypt: Trong function code, gọi kms:Decrypt để lấy plaintext value khi cần dùng.
  3. Cache kết quả: Decrypt một lần khi cold start, cache trong biến module-level để tránh gọi KMS mỗi invocation.

Encrypt Giá Trị Trước Khi Lưu

# Mã hóa database password bằng KMS
aws kms encrypt \
  --key-id alias/lambda-my-db-function \
  --plaintext fileb://<(echo -n 'my-secret-password') \
  --query 'CiphertextBlob' \
  --output text
# Output là base64-encoded ciphertext — lưu giá trị này vào environment variable
# Lưu ciphertext vào environment variable
aws lambda update-function-configuration \
  --function-name my-db-function \
  --environment 'Variables={DB_ENDPOINT=mydb.cluster-abc123.us-east-1.rds.amazonaws.com,DB_PASSWORD_ENCRYPTED=<CIPHERTEXT_BASE64>}'

Decrypt Trong Function Code (Python)

🔽 Python handler với KMS decrypt và caching (click để mở rộng)
import os
import boto3
import base64

# Cache ở module level — chỉ decrypt một lần per cold start
_db_password = None

def get_db_password():
    global _db_password
    if _db_password is not None:
        return _db_password

    kms_client = boto3.client('kms')
    encrypted_value = os.environ['DB_PASSWORD_ENCRYPTED']

    response = kms_client.decrypt(
        CiphertextBlob=base64.b64decode(encrypted_value)
    )
    _db_password = response['Plaintext'].decode('utf-8')
    return _db_password

def handler(event, context):
    db_endpoint = os.environ['DB_ENDPOINT']
    db_password = get_db_password()
    # ... kết nối database với db_endpoint và db_password
    return {'statusCode': 200}

Caching plaintext trong module-level variable là đánh đổi có chủ ý: bạn giảm số lần gọi KMS (và latency tương ứng) nhưng giá trị tồn tại trong memory suốt vòng đời của execution environment. Với Lambda, đây thường là lựa chọn hợp lý hơn so với gọi KMS mỗi invocation.

Một Lỗi Hay Gặp: AccessDeniedException Khi Cold Start

Triệu chứng: function hoạt động bình thường trong môi trường dev, nhưng sau khi deploy lên production với CMK mới, CloudWatch Logs chỉ thấy AccessDeniedException ngay từ đầu — không có log nào từ handler code.

Chẩn đoán ban đầu thường sai: engineer kiểm tra VPC configuration, security group, hay network connectivity vì nghĩ function không kết nối được database. Thực ra function chưa kịp chạy đến dòng code nào — nó fail ngay khi Lambda runtime cố gắng decrypt environment variables lúc khởi tạo execution environment.

Nguyên nhân thực sự: execution role thiếu kms:Decrypt trên CMK, hoặc KMS key policy không cho phép execution role sử dụng key. Hai lớp phân quyền này — IAM policy trên role và KMS key policy — đều phải cho phép thì decrypt mới thành công.

# Kiểm tra key policy hiện tại
aws kms get-key-policy \
  --key-id alias/lambda-my-db-function \
  --policy-name default \
  --query 'Policy' \
  --output text
# Kiểm tra execution role có quyền kms:Decrypt không
aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::123456789012:role/my-db-function-execution-role \
  --action-names kms:Decrypt \
  --resource-arns arn:aws:kms:us-east-1:123456789012:key/<KEY_ID>

KMS key policy là lớp kiểm soát độc lập với IAM — ngay cả khi IAM policy cho phép, key policy có thể deny. Cả hai phải align.

So Sánh: Environment Variables vs. AWS Secrets Manager vs. SSM Parameter Store

Environment variables phù hợp cho configuration không quá nhạy cảm và không cần rotation. Với database credentials thực sự, bạn nên cân nhắc Secrets Manager hoặc SSM Parameter Store — cả hai đều hỗ trợ rotation tự động và audit trail chi tiết hơn.

Tiêu chíEnv Variables + KMSSSM Parameter StoreSecrets Manager
Độ phức tạp setupThấpTrung bìnhTrung bình
Automatic rotationKhôngKhông (tự implement)Có (native)
Audit trailCloudTrail KMS eventsCloudTrail SSM eventsCloudTrail Secrets Manager events
Latency khi đọcKhông có (inject lúc start)Network callNetwork call
Phù hợp choNon-sensitive config, endpointsConfig có versioningCredentials cần rotation

Wrap-Up: Sử Dụng Lambda Environment Variables Đúng Cách

Với Lambda Environment Variables, nguyên tắc cơ bản là: dùng AWS-managed key cho config thông thường, chuyển sang CMK khi cần audit trail riêng hoặc kiểm soát access theo function, và dùng encryption in transit khi giá trị không được phép xuất hiện dưới dạng plaintext trong console. Với database credentials thực sự nhạy cảm cần rotation, Secrets Manager là lựa chọn phù hợp hơn.

Tài liệu tham khảo chính thức:

Glossary — Thuật Ngữ Chính

Thuật ngữGiải thích
CMK (Customer Managed Key)KMS key do bạn tạo và quản lý, có key policy riêng, rotation policy riêng
Execution RoleIAM role gắn với Lambda function, xác định quyền mà function code có thể sử dụng
Encryption at restMã hóa dữ liệu khi lưu trữ — environment variables luôn được mã hóa at rest bởi Lambda
Encryption in transit (context Lambda)Mã hóa giá trị bằng KMS trước khi lưu vào env var, decrypt thủ công trong runtime
Cold startLần đầu tiên Lambda khởi tạo execution environment — đây là thời điểm runtime inject environment variables

Nhận xét

Bài đăng phổ biến từ blog này

EC2 Không Có Internet Trong Custom VPC: Cách Gắn Internet Gateway và Cập Nhật Route Table

Route 53 Alias vs CNAME: Khi Nào Dùng Alias Record Cho Zone Apex?