diff --git a/environments/prod/main.tf b/environments/prod/main.tf index 6825655..f1d6c7d 100644 --- a/environments/prod/main.tf +++ b/environments/prod/main.tf @@ -51,12 +51,14 @@ module "billing_report" { # too much work to enable SES on AWS just for this notification #ses_sender_email = "root@kiraum.it" #ses_domain = "kiraum.it" - recipient_emails = ["tfgoncalves@xpto.it"] - notification_service = "SNS" - daily_cost_threshold = "0.01" - weekly_cost_threshold = "1.00" - monthly_cost_threshold = "5.00" - yearly_cost_threshold = "60.00" + recipient_emails = ["tfgoncalves@xpto.it"] + notification_service = "SNS" + enable_slack_notification = true + slack_webhook_url = var.slack_webhook_url + daily_cost_threshold = "0.01" + weekly_cost_threshold = "1.00" + monthly_cost_threshold = "5.00" + yearly_cost_threshold = "60.00" } # Route53 module for DNS management diff --git a/environments/prod/variables.tf b/environments/prod/variables.tf index 39e183e..2ce8f34 100644 --- a/environments/prod/variables.tf +++ b/environments/prod/variables.tf @@ -21,3 +21,8 @@ variable "cost_center" { # description = "Data classification for tagging" # type = string #} + +variable "slack_webhook_url" { + description = "Slack webhook URL for notifications" + type = string +} diff --git a/modules/billing_report/lambda_function.py b/modules/billing_report/lambda_function.py index a0acc3f..e7bf5ab 100644 --- a/modules/billing_report/lambda_function.py +++ b/modules/billing_report/lambda_function.py @@ -3,6 +3,7 @@ import datetime import json import os +import urllib.request import boto3 from botocore.exceptions import ClientError @@ -16,6 +17,40 @@ NOTIFICATION_SERVICE = os.environ.get("NOTIFICATION_SERVICE", "SNS").upper() +def get_ssm_parameter(parameter_name): + """ + Retrieve a parameter value from AWS Systems Manager Parameter Store. + + Args: + parameter_name (str): The name of the parameter to retrieve. + + Returns: + str: The decrypted value of the parameter. + """ + ssm = boto3.client("ssm") + response = ssm.get_parameter(Name=parameter_name, WithDecryption=True) + return response["Parameter"]["Value"] + + +def send_slack_notification(message): + """ + Send a notification to a Slack channel using a webhook URL stored in SSM. + + Args: + message (str): The message to send to the Slack channel. + """ + webhook_url = get_ssm_parameter("/billing_report/slack_webhook_url") + payload = json.dumps({"text": message}).encode("utf-8") + req = urllib.request.Request( + webhook_url, data=payload, headers={"Content-Type": "application/json"} + ) + with urllib.request.urlopen(req) as response: + if response.getcode() != 200: + print( + f"Failed to send Slack notification. Status code: {response.getcode()}" + ) + + def calculate_time_periods(time_period, current_date): """ Calculate start and end dates for the given time period. @@ -426,6 +461,8 @@ def lambda_handler(event, context): if current_costs > cost_threshold: print("Cost threshold exceeded. Sending notification.") send_notification(report, f"AWS Cost Report - {time_period.capitalize()}") + if os.environ.get("ENABLE_SLACK") == "true": + send_slack_notification(report) else: print( f"Total cost ({current_costs:.7f} {unit}) did not exceed the threshold ({cost_threshold:.7f} {unit}). No notification sent." diff --git a/modules/billing_report/lambda_mock_test.py b/modules/billing_report/lambda_mock_test.py index fdb32d1..8df7b37 100644 --- a/modules/billing_report/lambda_mock_test.py +++ b/modules/billing_report/lambda_mock_test.py @@ -6,7 +6,6 @@ from unittest.mock import MagicMock import boto3 - from lambda_function import lambda_handler diff --git a/modules/billing_report/main.tf b/modules/billing_report/main.tf index d5c08c7..347d795 100644 --- a/modules/billing_report/main.tf +++ b/modules/billing_report/main.tf @@ -16,6 +16,9 @@ terraform { # Get current AWS account information data "aws_caller_identity" "current" {} +# Retrieve information about the current AWS region +data "aws_region" "current" {} + # Define Lambda function for billing report resource "aws_lambda_function" "billing_report" { filename = data.archive_file.lambda_zip.output_path @@ -32,6 +35,7 @@ resource "aws_lambda_function" "billing_report" { RECIPIENT_EMAILS = jsonencode(var.recipient_emails) SNS_TOPIC_ARN = aws_sns_topic.billing_report.arn NOTIFICATION_SERVICE = var.notification_service + ENABLE_SLACK = var.enable_slack_notification DAILY_COST_THRESHOLD = var.daily_cost_threshold WEEKLY_COST_THRESHOLD = var.weekly_cost_threshold MONTHLY_COST_THRESHOLD = var.monthly_cost_threshold @@ -107,6 +111,13 @@ resource "aws_iam_role_policy" "lambda_policy" { "sns:Publish" ] Resource = aws_sns_topic.billing_report.arn + }, + { + Effect = "Allow" + Action = [ + "ssm:GetParameter" + ] + Resource = "arn:aws:ssm:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:parameter/billing_report/slack_webhook_url" } ] }) @@ -279,3 +290,10 @@ resource "aws_sns_topic_subscription" "billing_report_email" { protocol = "email" endpoint = var.recipient_emails[count.index] } + +# Store Slack webhook URL securely in SSM Parameter Store +resource "aws_ssm_parameter" "slack_webhook_url" { + name = "/billing_report/slack_webhook_url" + type = "SecureString" + value = var.slack_webhook_url +} diff --git a/modules/billing_report/requirements.txt b/modules/billing_report/requirements.txt index 17328f4..296ed28 100644 --- a/modules/billing_report/requirements.txt +++ b/modules/billing_report/requirements.txt @@ -1,12 +1,12 @@ # This file was autogenerated by uv via the following command: # uv pip compile pyproject.toml -astroid==3.3.4 +astroid==3.3.5 # via pylint black==24.8.0 # via aws-cost-explorer-lambda (pyproject.toml) -boto3==1.35.32 +boto3==1.35.34 # via aws-cost-explorer-lambda (pyproject.toml) -botocore==1.35.32 +botocore==1.35.34 # via # boto3 # s3transfer @@ -38,7 +38,7 @@ pylint==3.3.1 # via aws-cost-explorer-lambda (pyproject.toml) python-dateutil==2.9.0.post0 # via botocore -ruff==0.6.8 +ruff==0.6.9 # via aws-cost-explorer-lambda (pyproject.toml) s3transfer==0.10.2 # via boto3 diff --git a/modules/billing_report/variables.tf b/modules/billing_report/variables.tf index dcec894..c03ea87 100644 --- a/modules/billing_report/variables.tf +++ b/modules/billing_report/variables.tf @@ -47,3 +47,14 @@ variable "yearly_cost_threshold" { type = string default = "0.01" } + +variable "enable_slack_notification" { + description = "Enable Slack webhook notification" + type = bool + default = false +} + +variable "slack_webhook_url" { + description = "Slack webhook URL for notifications" + type = string +} diff --git a/modules/static_website/content/.well-known/security.txt b/modules/static_website/content/.well-known/security.txt index ddba9dd..1dc2146 100644 --- a/modules/static_website/content/.well-known/security.txt +++ b/modules/static_website/content/.well-known/security.txt @@ -1,4 +1,3 @@ Contact: mailto:root@kiraum.it Expires: 2027-12-31T23:59:59.000Z Preferred-Languages: en - diff --git a/modules/static_website/content/styles.css b/modules/static_website/content/styles.css index 27c241f..e188ca1 100644 --- a/modules/static_website/content/styles.css +++ b/modules/static_website/content/styles.css @@ -86,4 +86,3 @@ a:hover { height: 35px; margin: 0 5px; } -