LambdaでKMSを使用して環境変数の暗号化・復号化を行う
はじめに
LambdaでKMSを使用して環境変数の暗号化・復号化を行うことで、セキュリティが向上します。
LambdaでKMSを使用するメリット
データの保護
環境変数に機密情報(APIキー、データベースのパスワードなど)を保存する場合、その情報を暗号化することで、不正アクセスによる情報漏洩のリスクを軽減できます。KMSを使用して環境変数を暗号化すると、その情報はAWSのセキュリティ基盤によって保護されます。
アクセス制御
KMSでは、暗号化キーへのアクセスを細かく制御できます。IAMポリシーを使用して、特定のユーザーやサービスだけがキーを使用できるように設定することで、権限のないアクセスからキーを保護します。
LambdaでKMSを使用するデメリット
料金
KMSは使用に応じて料金が発生します。キーの作成、保管料、および暗号化・復号化操作の実行によって料金がかかります。特に、大量の暗号化・復号化操作を行う場合、コストが顕著になる可能性があります。
パフォーマンス
KMSを使用して暗号化・復号化を実行すると、これらの操作に時間がかかるため、アプリケーションのレスポンス時間が影響を受ける可能性があります。特に、Lambda関数のようなサーバーレス環境では、関数の起動時間(コールドスタート)に暗号化・復号化の時間が加わるため、レスポンスの遅延が発生することがあります。
KMSキーの作成
キーの作成
を選択
下記内容を選択し、次へ
を選択
エイリアス
を入力して次へ
を選択
キー管理者は指定せず、次へ
を選択
内容を確認して完了
を選択してください。
環境変数の設定
Lambda > 関数 > 関数を選択 > 設定> 環境変数を選択
編集
を選択
キー
と値
を入力し、暗号化の設定
を選択し、
転送時の暗号化に使用するヘルパーの有効化
をチェックします。チェックを入れると、
環境変数キー/値の右側に暗号化
と表示されるので選択してください。
転送時の暗号化を行うための AWS KMS キーで先ほど作成したキーを選択し、暗号化
を選択します。
値が暗号化されたことを確認し、保存
を選択してください。
KMSキーに対するデータの復号化を許可するポリシーをアタッチする
IAM > ロール Lambda関数のIAMロール(設定 > アクセス権限 > 実行ロールからロール名は確認できます)を選択、
許可 > 許可ポリシー > 表示されている許可ポリシーを選択、
JSON
を選択し、編集
を選択してください
下記を追記して次へ
を選択、変更を保存
を選択してください。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"kms:Decrypt"
],
"Resource": "<先ほど作成したKMSキーのARN>"
}
]
}
kms:Decrypt
操作は、AWS KMSに保存されている特定のキーを使用して、暗号化されたデータ(Ciphertext)を元の平文データ(Plaintext)に復号化するプロセスを指します。
LambdaでKMS復号化を行う
下記を参考にさせていただきました🙇
環境変数を指定して取得する場合
Python
SECRET
という環境変数を取得しています。
import boto3
import os
def lambda_handler(event, context):
kms_client = boto3.client("kms")
ENCRYPTED = os.environ['SECRET']
DECRYPTED = boto3.client('kms').decrypt(
CiphertextBlob=b64decode(ENCRYPTED),
EncryptionContext={'LambdaFunctionName': os.environ['AWS_LAMBDA_FUNCTION_NAME']}
)['Plaintext'].decode('utf-8')
return {
'statusCode': 200,
'body': DECRYPTED
}
JavaScript
同じくSECRET
という環境変数を取得しています。
import { DecryptCommand, KMSClient } from '@aws-sdk/client-kms';
export const handler = async (event) => {
const kmsClient = new KMSClient({ region: '<リージョン名>' });
try {
// 'SECRET'環境変数の値を取得
const encryptedSecret = process.env.SECRET;
if (!encryptedSecret) {
console.error('SECRET environment variable is not defined');
return;
}
// Base64デコードされた値をKMSで復号化
const { Plaintext } = await kmsClient.send(new DecryptCommand({
CiphertextBlob: Buffer.from(encryptedSecret, 'base64'),
EncryptionContext: { LambdaFunctionName: process.env.AWS_LAMBDA_FUNCTION_NAME },
}));
// 復号化された値をUTF-8文字列として取得
const decryptedSecret = new TextDecoder().decode(Plaintext);
// 復号化された値の使用
console.log(`Decrypted SECRET: ${decryptedSecret}`);
} catch (error) {
console.error(`Error decrypting SECRET: ${error}`);
}
};
すべての環境変数を取得する場合
Python
import boto3
import os
def lambda_handler(event, context):
environment_variables = os.environ
decrypted_environment = {}
kms_client = boto3.client("kms")
for key, value in environment_variables.items():
# 取得した環境変数のキーが 'aws:' または 'AWS:' で始まる場合、そのまま decrypted_environmentに追加
if key.startswith("aws:") or key.startswith("AWS:"):
decrypted_environment[key] = value
else:
try:
# KMSで暗号化されたデータの復号化
# kms_client.decrypt()を使用して、KMSによって暗号化されたデータを復号化
# b64decode(value)を使用して、Base64でエンコードされた暗号化されたデータをバイナリ形式にデコード
# CiphertextBlobには復号化するデータが含まれている
# EncryptionContextには、復号化のためのコンテキスト情報が含まれています。ここではLambda関数の名前が含まれています。
# Plaintextからは、復号化されたテキストが得られる
decrypted_value = kms_client.decrypt(
CiphertextBlob=b64decode(value),
EncryptionContext={
"LambdaFunctionName": os.environ["AWS_LAMBDA_FUNCTION_NAME"]
},
)["Plaintext"].decode("utf-8")
decrypted_environment[key] = decrypted_value
except Exception as e:
print(f"Error decrypting value for key {key}: {e}")
decrypted_environment[key] = value
# 含まれているか確認するコード
if secret in decrypted_environment.values():
print('環境変数にsecretという値が存在します。')
JavaScript
import { DecryptCommand, KMSClient } from '@aws-sdk/client-kms';
import CryptoJS from 'crypto-js';
export const handler = async (event) => {
const kmsClient = new KMSClient({ region: '<リージョン名>' });
try {
const environmentVariables = process.env;
let decryptedEnvironment = {};
for (const [key, value] of Object.entries(environmentVariables)) {
if (!key.startsWith('aws:') && !key.startsWith('AWS:')) {
try {
const { Plaintext } = await kmsClient.send(
new DecryptCommand({
CiphertextBlob: Buffer.from(value, 'base64'),
EncryptionContext: {
LambdaFunctionName: process.env.AWS_LAMBDA_FUNCTION_NAME,
},
})
);
const decodedValue = Plaintext.reduce(
(acc, byte) => acc + String.fromCharCode(byte),
''
);
decryptedEnvironment[key] = decodedValue;
} catch (error) {
console.error(`Error decrypting value for key ${key}: ${error}`);
decryptedEnvironment[key] = value;
}
} else {
decryptedEnvironment[key] = value;
}
}
// 含まれているか確認するコード
if (Object.values(decryptedEnvironment).includes(secret)) {
console.log('環境変数にsecretという値が存在します。');
}
参考にさせていただきました
終わりに
何かありましたらお気軽にコメント等いただけると助かります。
ここまでお読みいただきありがとうございます🎉
Discussion