diff --git a/terraform/environments/ppud/eventbridge.tf b/terraform/environments/ppud/eventbridge.tf index 067f3748561..c20d4843d31 100644 --- a/terraform/environments/ppud/eventbridge.tf +++ b/terraform/environments/ppud/eventbridge.tf @@ -52,6 +52,31 @@ resource "aws_cloudwatch_event_target" "trigger_lambda_target_ppud_elb_report_pr arn = aws_lambda_function.terraform_lambda_func_ppud_elb_report_prod[0].arn } +# Eventbridge rule to invoke the WAM ELB report lambda function every weekday at 20:15 + +resource "aws_lambda_permission" "allow_eventbridge_invoke_wam_elb_report_prod" { + count = local.is-production == true ? 1 : 0 + statement_id = "AllowEventBridgeInvoke" + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.terraform_lambda_func_wam_elb_report_prod[0].function_name + principal = "events.amazonaws.com" + source_arn = aws_cloudwatch_event_rule.daily_schedule_wam_elb_report_prod[0].arn +} + +resource "aws_cloudwatch_event_rule" "daily_schedule_wam_elb_report_prod" { + count = local.is-production == true ? 1 : 0 + name = "wam-elb-report-daily-weekday-schedule" + description = "Trigger Lambda at 20:15 UTC on weekdays" + schedule_expression = "cron(15 20 ? * MON-FRI *)" +} + +resource "aws_cloudwatch_event_target" "trigger_lambda_target_wam_elb_report_prod" { + count = local.is-production == true ? 1 : 0 + rule = aws_cloudwatch_event_rule.daily_schedule_wam_elb_report_prod[0].name + target_id = "wam_elb_report" + arn = aws_lambda_function.terraform_lambda_func_wam_elb_report_prod[0].arn +} + # Eventbridge rule to invoke the PPUD Email Report lambda function every Monday at 07:00 resource "aws_lambda_permission" "allow_eventbridge_invoke_ppud_email_report_prod" { @@ -66,8 +91,8 @@ resource "aws_lambda_permission" "allow_eventbridge_invoke_ppud_email_report_pro resource "aws_cloudwatch_event_rule" "weekly_schedule_ppud_email_report_prod" { count = local.is-production == true ? 1 : 0 name = "ppud-email-report-weekly-schedule" - description = "Trigger Lambda at 07:00 UTC each Monday" - schedule_expression = "cron(0 7 ? * MON *)" + description = "Trigger Lambda at 07:15 UTC each Monday" + schedule_expression = "cron(15 7 ? * MON *)" } resource "aws_cloudwatch_event_target" "trigger_lambda_target_ppud_email_report_prod" { diff --git a/terraform/environments/ppud/lambda.tf b/terraform/environments/ppud/lambda.tf index 160c11294d3..0786b37bd28 100644 --- a/terraform/environments/ppud/lambda.tf +++ b/terraform/environments/ppud/lambda.tf @@ -398,6 +398,7 @@ resource "aws_lambda_permission" "allow_lambda_to_query_cloudwatch_send_cpu_grap resource "aws_lambda_function" "terraform_lambda_func_send_cpu_graph_dev" { # checkov:skip=CKV_AWS_117: "PPUD Lambda functions do not require VPC access and can run in no-VPC mode" + # checkov:skip=CKV_AWS_272: "PPUD Lambda code signing temporarily disabled for maintenance purposes" count = local.is-development == true ? 1 : 0 filename = "${path.module}/lambda_scripts/send_cpu_graph_dev.zip" function_name = "send_cpu_graph" @@ -455,6 +456,7 @@ resource "aws_lambda_permission" "allow_lambda_to_query_cloudwatch_send_cpu_grap } resource "aws_lambda_function" "terraform_lambda_func_send_cpu_graph_prod" { + # checkov:skip=CKV_AWS_272: "PPUD Lambda code signing temporarily disabled for maintenance purposes" count = local.is-production == true ? 1 : 0 filename = "${path.module}/lambda_scripts/send_cpu_graph_prod.zip" function_name = "send_cpu_graph" @@ -517,7 +519,7 @@ resource "aws_lambda_permission" "allow_lambda_to_query_cloudwatch_ppud_email_re } resource "aws_lambda_function" "terraform_lambda_func_ppud_email_report_prod" { - # checkov:skip=CKV_AWS_117: "PPUD Lambda functions do not require VPC access and can run in no-VPC mode" + # checkov:skip=CKV_AWS_272: "PPUD Lambda code signing temporarily disabled for maintenance purposes" count = local.is-production == true ? 1 : 0 filename = "${path.module}/lambda_scripts/ppud_email_report_prod.zip" function_name = "ppud_email_report" @@ -569,7 +571,7 @@ resource "aws_lambda_permission" "allow_lambda_to_query_cloudwatch_ppud_elb_repo } resource "aws_lambda_function" "terraform_lambda_func_ppud_elb_report_prod" { - # checkov:skip=CKV_AWS_117: "PPUD Lambda functions do not require VPC access and can run in no-VPC mode" + # checkov:skip=CKV_AWS_272: "PPUD Lambda code signing temporarily disabled for maintenance purposes" count = local.is-production == true ? 1 : 0 filename = "${path.module}/lambda_scripts/ppud_elb_report_prod.zip" function_name = "ppud_elb_report" @@ -607,6 +609,58 @@ data "archive_file" "zip_the_ppud_elb_report_prod" { output_path = "${path.module}/lambda_scripts/ppud_elb_report_prod.zip" } +################################################### +# Lambda Function to graph WAM ELB Requests - PROD +################################################### + +resource "aws_lambda_permission" "allow_lambda_to_query_cloudwatch_wam_elb_report_prod" { + count = local.is-production == true ? 1 : 0 + statement_id = "AllowAccesstoCloudWatch" + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.terraform_lambda_func_wam_elb_report_prod[0].function_name + principal = "cloudwatch.amazonaws.com" + source_arn = "arn:aws:cloudwatch:eu-west-2:${local.environment_management.account_ids["ppud-production"]}:*" +} + +resource "aws_lambda_function" "terraform_lambda_func_wam_elb_report_prod" { + # checkov:skip=CKV_AWS_272: "PPUD Lambda code signing temporarily disabled for maintenance purposes" + count = local.is-production == true ? 1 : 0 + filename = "${path.module}/lambda_scripts/wam_elb_report_prod.zip" + function_name = "wam_elb_report" + role = aws_iam_role.lambda_role_cloudwatch_get_metric_data_prod[0].arn + handler = "wam_elb_report_prod.lambda_handler" + runtime = "python3.12" + timeout = 300 + depends_on = [aws_iam_role_policy_attachment.attach_lambda_policy_cloudwatch_get_metric_data_to_lambda_role_cloudwatch_get_metric_data_prod] + reserved_concurrent_executions = 5 + # code_signing_config_arn = "arn:aws:lambda:eu-west-2:${local.environment_management.account_ids["ppud-production"]}:code-signing-config:csc-0bafee04a642a41c1" + dead_letter_config { + target_arn = aws_sqs_queue.lambda_queue_prod[0].arn + } + tracing_config { + mode = "Active" + } + layers = [ + "arn:aws:lambda:eu-west-2:${data.aws_ssm_parameter.klayers_account_prod[0].value}:layer:Klayers-p312-numpy:8", + "arn:aws:lambda:eu-west-2:${data.aws_ssm_parameter.klayers_account_prod[0].value}:layer:Klayers-p312-pillow:1", + aws_lambda_layer_version.lambda_layer_matplotlib_prod[0].arn + ] + # VPC configuration + vpc_config { + subnet_ids = [data.aws_subnet.private_subnets_b.id] + security_group_ids = [aws_security_group.PPUD-Mail-Server[0].id] + } +} + +# Archive the zip file + +data "archive_file" "zip_the_wam_elb_report_prod" { + count = local.is-production == true ? 1 : 0 + type = "zip" + source_dir = "${path.module}/lambda_scripts/" + output_path = "${path.module}/lambda_scripts/wam_elb_report_prod.zip" +} + #################################################### # Lambda Function to graph Memory Utilization - PROD #################################################### @@ -621,6 +675,7 @@ resource "aws_lambda_permission" "allow_lambda_to_query_cloudwatch_send_memory_g } resource "aws_lambda_function" "terraform_lambda_func_send_memory_graph_prod" { + # checkov:skip=CKV_AWS_272: "PPUD Lambda code signing temporarily disabled for maintenance purposes" count = local.is-production == true ? 1 : 0 filename = "${path.module}/lambda_scripts/send_memory_graph_prod.zip" function_name = "send_memory_graph" diff --git a/terraform/environments/ppud/lambda_scripts/ppud_elb_report_prod.py b/terraform/environments/ppud/lambda_scripts/ppud_elb_report_prod.py index a0de1784e03..bcb74890111 100644 --- a/terraform/environments/ppud/lambda_scripts/ppud_elb_report_prod.py +++ b/terraform/environments/ppud/lambda_scripts/ppud_elb_report_prod.py @@ -18,7 +18,7 @@ # Configuration CURRENT_DATE = datetime.now().strftime('%a %d %b %Y') SENDER = 'noreply@internaltest.ppud.justice.gov.uk' -RECIPIENTS = ['nick.buckingham@colt.net'] +RECIPIENTS = ['nick.buckingham@colt.net', 'kofi-nimoh@colt.net'] SUBJECT = f'AWS PPUD Load Balancer Report - {CURRENT_DATE}' AWS_REGION = 'eu-west-2' ELB_NAME = "app/PPUD-ALB/9d129853721723f4" # Replace with your ELB name diff --git a/terraform/environments/ppud/lambda_scripts/ppud_email_report_prod.py b/terraform/environments/ppud/lambda_scripts/ppud_email_report_prod.py index f04c8243f9c..b90abb9e902 100644 --- a/terraform/environments/ppud/lambda_scripts/ppud_email_report_prod.py +++ b/terraform/environments/ppud/lambda_scripts/ppud_email_report_prod.py @@ -1,8 +1,3 @@ -# Python script to retrieve emails send data from S3, count the number of outgoing emails -# graph it and email it to end users via the internal mail relay. -# Nick Buckingham -# 9 December 2024 - import boto3 import os os.environ['MPLCONFIGDIR'] = "/tmp/graph" @@ -24,8 +19,10 @@ TODAY = datetime.now() YESTERDAY = TODAY - timedelta(days=1) YESTERDAY_DATE = YESTERDAY.strftime('%a %d %b %Y') -bucket_name = 'moj-lambda-layers-prod' -file_names = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] +WEEK_AGO = TODAY - timedelta(days=7) +WEEK_AGO_DATE = WEEK_AGO.strftime('%a %d %b %Y') +S3_BUCKET = 'moj-lambda-layers-prod' +FILE_NAMES = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] SENDER = 'donotreply@cjsm.secure-email.ppud.justice.gov.uk' RECIPIENTS = ['nick.buckingham@colt.net'] SUBJECT = f'AWS PPUD Email Report - {CURRENT_DATE}' @@ -34,8 +31,6 @@ # SMTP Configuration SMTP_SERVER = "10.27.9.39" SMTP_PORT = 25 -MAIL_FROM = "donotreply@cjsm.secure-email.ppud.justice.gov.uk" -EMAIL_TO = ["nick.buckingham@colt.net"] def retrieve_file_from_s3(bucket, key): response = s3.get_object(Bucket=bucket, Key=key) @@ -81,7 +76,7 @@ def send_email_with_graph(graph_base64):
Hi Team,
-Please find below the PPUD email report for the week ending {YESTERDAY_DATE}.
+Please find below the PPUD email report for the week of {WEEK_AGO_DATE} to {YESTERDAY_DATE}.
This is an automated email.
@@ -123,8 +118,8 @@ def lambda_handler(event, context): pattern = r'to=<' data = {} - for file_name in file_names: - content = retrieve_file_from_s3(bucket_name, file_name) + for file_name in FILE_NAMES: + content = retrieve_file_from_s3(S3_BUCKET, file_name) count = count_occurrences(content, pattern) data[file_name] = count diff --git a/terraform/environments/ppud/lambda_scripts/wam_elb_report_prod.py b/terraform/environments/ppud/lambda_scripts/wam_elb_report_prod.py new file mode 100644 index 00000000000..09b4612db8c --- /dev/null +++ b/terraform/environments/ppud/lambda_scripts/wam_elb_report_prod.py @@ -0,0 +1,159 @@ +# Python script to retrieve elastic load balancer data from cloudwatch, count the connections per 15 minutes +# graph it and email it to end users via the internal mail relay. +# Nick Buckingham +# 9 December 2024 + +import boto3 +import os +os.environ['MPLCONFIGDIR'] = "/tmp/graph" +import matplotlib.pyplot as plt +from datetime import datetime, timedelta +import io +import base64 +import smtplib +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from botocore.exceptions import NoCredentialsError, PartialCredentialsError + +# Configuration +CURRENT_DATE = datetime.now().strftime('%a %d %b %Y') +SENDER = 'noreply@internaltest.ppud.justice.gov.uk' +RECIPIENTS = ['nick.buckingham@colt.net'] +SUBJECT = f'AWS WAM Load Balancer Report - {CURRENT_DATE}' +AWS_REGION = 'eu-west-2' +ELB_NAME = "app/WAM-ALB-PROD/bfc963544454bdde" # Replace with your ELB name + +# SMTP Configuration +SMTP_SERVER = "10.27.9.39" +SMTP_PORT = 25 + +# Initialize AWS clients +cloudwatch = boto3.client("cloudwatch", region_name=AWS_REGION) +#ses = boto3.client("ses", region_name=AWS_REGION) + +def get_elb_request_counts(ELB_NAME): + """Fetches daily connection counts for the ELB from CloudWatch.""" + # Calculate the start and end time for the day + #start_time = datetime(2024, 12, 8, 6, 0, 0) # 08:00 UTC, 28 Nov 2024 + #end_time = datetime(2024, 12, 8, 20, 10, 0) # 17:00 UTC, 28 Nov 2024 + current_time = datetime.utcnow() + end_time = datetime.utcnow() + start_time = end_time - timedelta(hours=14) + + response = cloudwatch.get_metric_statistics( + Namespace="AWS/ApplicationELB", + MetricName="RequestCount", + Dimensions=[ + {"Name": "LoadBalancer", "Value": ELB_NAME}, +# {'Name': 'TargetGroup', 'Value': 'PPUD'}, +# {'Name': 'AvailabilityZone', 'Value': 'eu-west-2c'} + ], + StartTime=start_time, + EndTime=end_time, + Period=900, # 15 minute intervals + Statistics=["Sum"] + ) + + data_points = sorted(response["Datapoints"], key=lambda x: x["Timestamp"]) + return [(dp["Timestamp"].strftime('%H:%M'), dp["Sum"]) for dp in data_points] + +def create_graph(request_data): + """Plots the graph of requests and returns it as an in-memory file.""" + times, requests = zip(*request_data) + plt.figure(figsize=(20, 6)) + plt.plot(times, requests, color="blue") + plt.title(f"Requests to the WAM Load Balancer on {CURRENT_DATE} (Every 15 Minutes)") + plt.xlabel("Time (UTC)") + plt.ylabel("Number of Requests") + plt.xticks(rotation=45) + plt.grid(axis="y", linestyle="--", linewidth=0.7, alpha=0.7) + plt.tight_layout() + + # graph_path = "elb_daily_connections.png" + # plt.savefig(graph_path) + # plt.close() + # return graph_path + + # Save the graph to a temporary buffer + temp_file = "/tmp/elb_daily_connections.png" + plt.savefig(temp_file) + plt.close() + + # Read the image and encode it to base64 + with open(temp_file, "rb") as image_file: + encoded_string = base64.b64encode(image_file.read()).decode("utf-8") + + # Cleanup temporary file + os.remove(temp_file) + return encoded_string + +def email_image_to_users(graph_base64): + """ + Send an email with the graph embedded in the email body using AWS SES. + """ + ses_client = boto3.client("ses", region_name=AWS_REGION) + + # Email body with the embedded image + email_body = f""" + + +Hi Team,
+Please find below the WAM Elastic Load Balancer report for {CURRENT_DATE}.
+ +This is an automated email.
+ + + """ + + # Create the email message + msg = MIMEMultipart("alternative") + msg["From"] = SENDER + msg["To"] = ", ".join(RECIPIENTS) + msg["Subject"] = SUBJECT + + # Attach the HTML body + msg.attach(MIMEText(email_body, "html")) + + # Send the email with AWS SES + # try: + # response = ses_client.send_raw_email( + # Source=SENDER, + # Destinations=RECIPIENTS, + # RawMessage={"Data": msg.as_string()}, + # ) + # print("Email sent! Message ID:", response["MessageId"]) + # except Exception as e: + # print("Error sending email:", e) + # raise + + # Send the email with an EC2 Instance Mail Relay + try: + with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server: + # server.starttls() + # server.login(SENDER, EMAIL_PASSWORD) + server.sendmail(SENDER, RECIPIENTS, msg.as_string()) + print("Email sent successfully.") + except Exception as e: + print(f"Error sending email: {e}") + + +def lambda_handler(event, context): + try: + # Get hourly request counts + request_data = get_elb_request_counts(ELB_NAME) + if not request_data: + print("No data found for the specified ELB.") + return + + # Create graph + #temp_file = plot_graph(request_data) + graph_base64 = create_graph(request_data) + + # Send email + email_image_to_users(graph_base64) + print("Process completed successfully.") + + except (NoCredentialsError, PartialCredentialsError) as cred_error: + print(f"Credential issue: {cred_error}") + except Exception as e: + print(f"An error occurred: {e}")