こんにちは。
先日に投稿した 「Inspector V2 の ECR 拡張スキャン結果を Slack に通知する」 ですが、Lambda のみ手動で作成していたので、こちらも Terraform でデプロイできるように実装します。
Terraform
Terraform で Lambda をデプロイするには、予めソースコードを zip 化しておく必要があるようです。
ついては archive_file という Data Source で対応します。
ソースコードの更新も、zip ファイルの hash 値で判断してくれるようで便利ですね。
/**
# NOTE: Lambda
*/
// Python スクリプト ZIP 化
data "archive_file" "ecr_enhanced_scanning_finding" {
type = "zip"
source_file = "${path.module}/lambda/source/ecr_enhanced_scanning_finding_notice.py"
output_path = "${path.module}/lambda/bin/ecr_enhanced_scanning_finding_notice.zip"
}
// Lambda
resource "aws_lambda_function" "ecr_enhanced_scanning_finding" {
filename = data.archive_file.ecr_enhanced_scanning_finding.output_path
function_name = "${var.common.project}-${var.common.environment}-ecr-enhanced-scan-finding-notice-function"
description = "${var.common.project}-${var.common.environment}-ecr-enhanced-scan-finding-notice-function"
role = aws_iam_role.lambda_role.arn
handler = "ecr_enhanced_scanning_finding_notice.lambda_handler"
source_code_hash = data.archive_file.ecr_enhanced_scanning_finding.output_base64sha256
reserved_concurrent_executions = -1
runtime = "python3.7"
environment {
variables = {
channelName = var.channel_name
kmsEncryptedHookUrl = var.kms_encrypted_hookurl
}
}
lifecycle {
ignore_changes = [
environment
]
}
}
// SNS
resource "aws_lambda_permission" "ecr_enhanced_scanning_finding" {
statement_id = "AllowExecutionFromSNS"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.ecr_enhanced_scanning_finding.function_name
principal = "sns.amazonaws.com"
source_arn = aws_sns_topic.ecr_enhanced_scanning_finding.arn
}
// SNS Subscription
resource "aws_sns_topic_subscription" "ecr_enhanced_scanning_finding" {
topic_arn = aws_sns_topic.ecr_enhanced_scanning_finding.arn
protocol = "lambda"
endpoint = aws_lambda_function.ecr_enhanced_scanning_finding.arn
}
Lambda
Lambda のソースコード ( Python ) は、以下のようなディレクトリ構成で配置します。
. ├── eventbridge.tf ├── lambda │ ├── bin │ │ └── ecr_enhanced_scanning_finding_notice.zip │ └── source │ └── ecr_enhanced_scanning_finding_notice.py ├── lambda.tf └── variables.tf
現状、Terraform には環境変数を KMS キーで暗号化する機能が見当たりませんでした。
そのため、手動で暗号化するか、暗号化しないかのどちらでも対応できるように実装しておきます。
import boto3
import json
import logging
import os
from base64 import b64decode
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
from urllib.parse import quote
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
CHANNEL_NAME = os.environ['channelName']
if "hooks.slack.com" in os.environ['kmsEncryptedHookUrl']:
logger.info("kmsEncryptedHookUrl: " + str(os.environ['kmsEncryptedHookUrl']))
logger.info("kmsEncryptedHookUrl is not Encrypted")
UNENCRYPTED_HOOK_URL = os.environ['kmsEncryptedHookUrl']
HOOK_URL = "https://" + quote(UNENCRYPTED_HOOK_URL)
else:
logger.info("kmsEncryptedHookUrl is Encrypted")
logger.info("kmsEncryptedHookUrl: " + str(os.environ['kmsEncryptedHookUrl']))
ENCRYPTED_HOOK_URL = os.environ['kmsEncryptedHookUrl']
HOOK_URL = "https://" + boto3.client('kms').decrypt(
CiphertextBlob=b64decode(ENCRYPTED_HOOK_URL),
EncryptionContext={'LambdaFunctionName': os.environ['AWS_LAMBDA_FUNCTION_NAME']}
)['Plaintext'].decode('utf-8')
logger.info("Event: " + str(event))
message = json.loads(event['Records'][0]['Sns']['Message'])
logger.info("Message: " + str(message))
resources = message['resources'][0]
title = message['title']
severity = message['severity']
inspector_score = message['inspectorScore']
source_url = message['sourceUrl']
# MEMO: <!channel>, <@user_id>
slack_message = {
"channel": CHANNEL_NAME,
"icon_emoji": ":rotating_light:",
"attachments": [
{
"color": "#FF0000",
"title": "ECR イメージスキャン結果に脆弱性が存在します",
"title_link": "https://ap-northeast-1.console.aws.amazon.com/ecr/repositories?region=ap-northeast-1",
"text": "<!here> \n *Resources* : %s \n" % (resources),
"fields": [
{
"title": "Title",
"value": title,
"short": True
},
{
"title": "Severity",
"value": severity,
"short": True
},
{
"title": "Inspector_score",
"value": inspector_score,
"short": True
},
{
"title": "Source_url",
"value": source_url,
"short": True
}
]
}
]
}
logger.info("HOOK_URL: " + str(HOOK_URL))
logger.info("CHANNEL_NAME: " + str(CHANNEL_NAME))
req = Request(HOOK_URL, json.dumps(slack_message).encode('utf-8'))
try:
response = urlopen(req)
response.read()
logger.info("Message posted to %s", slack_message['channel'])
except HTTPError as e:
logger.error("Request failed: %d %s", e.code, e.reason)
except URLError as e:
logger.error("Server connection failed: %s", e.reason)Terraform ですが思っていたよりも Lambda 周りのデプロイツールとして優秀なんじゃないかと思いました。
わあい。