こんにちは。
先日に投稿した 「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 周りのデプロイツールとして優秀なんじゃないかと思いました。
わあい。