Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/quality-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,17 @@ jobs:
poetry run coverage run --source=src -m unittest discover || echo "mesh_processor tests failed" >> ../../failed_tests.txt
poetry run coverage xml -o ../../mesh_processor-coverage.xml
- name: Run unittest with mns_publisher
working-directory: lambdas/mns_publisher
id: mnspublisher
env:
PYTHONPATH: ${{ env.LAMBDA_PATH }}/mns_publisher/src:${{ env.LAMBDA_PATH }}/mns_publisher/tests:${{ env.SHARED_PATH }}/src
continue-on-error: true
run: |
poetry install
poetry run coverage run --source=src -m unittest discover || echo "mns_publisher tests failed" >> ../../failed_tests.txt
poetry run coverage xml -o ../../mns_publisher-coverage.xml
- name: Run unittest with coverage-mns-subscription
working-directory: lambdas/mns_subscription
id: mns_subscription
Expand Down
2 changes: 2 additions & 0 deletions infrastructure/instance/dynamodb.tf
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ resource "aws_dynamodb_table" "delta-dynamodb-table" {
name = "imms-${local.resource_scope}-delta"
billing_mode = "PAY_PER_REQUEST"
hash_key = "PK"
stream_enabled = true
stream_view_type = "NEW_IMAGE"
deletion_protection_enabled = !local.is_temp

attribute {
Expand Down
118 changes: 118 additions & 0 deletions infrastructure/instance/mns_outbound_events_eb_pipe.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# IAM Role for EventBridge Pipe
resource "aws_iam_role" "mns_outbound_events_eb_pipe" {
name = "${local.resource_scope}-mns-outbound-eventbridge-pipe-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "pipes.amazonaws.com"
}
Condition = {
StringEquals = {
"aws:SourceAccount" = var.immunisation_account_id
}
}
}
]
})
}

resource "aws_iam_role_policy" "mns_outbound_events_eb_pipe_source_policy" {
role = aws_iam_role.mns_outbound_events_eb_pipe.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
"Effect" : "Allow",
"Action" : [
"dynamodb:DescribeStream",
"dynamodb:GetRecords",
"dynamodb:GetShardIterator",
"dynamodb:ListStreams"
],
"Resource" : aws_dynamodb_table.delta-dynamodb-table.stream_arn
},
{
"Effect" : "Allow",
"Action" : [
"kms:Decrypt",
"kms:GenerateDataKey"
],
"Resource" : data.aws_kms_key.existing_dynamo_encryption_key.arn
},
]
})
}

resource "aws_iam_role_policy" "mns_outbound_events_eb_pipe_target_policy" {
role = aws_iam_role.mns_outbound_events_eb_pipe.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"sqs:GetQueueAttributes",
"sqs:SendMessage",
],
Resource = [
aws_sqs_queue.mns_outbound_events.arn,
]
},
]
})
}

resource "aws_iam_role_policy" "mns_outbound_events_eb_pipe_cw_log_policy" {
role = aws_iam_role.mns_outbound_events_eb_pipe.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
Resource = [
"arn:aws:logs:${var.aws_region}:${var.immunisation_account_id}:log-group:/aws/vendedlogs/pipes/${local.resource_scope}-mns-outbound-event-pipe-logs:*",
]
},
]
})
}

resource "aws_cloudwatch_log_group" "mns_outbound_events_eb_pipe" {
name = "/aws/vendedlogs/pipes/${local.resource_scope}-mns-outbound-event-pipe-logs"
retention_in_days = 30
}

resource "aws_pipes_pipe" "mns_outbound_events" {
depends_on = [
aws_iam_role_policy.mns_outbound_events_eb_pipe_source_policy,
aws_iam_role_policy.mns_outbound_events_eb_pipe_target_policy,
aws_iam_role_policy.mns_outbound_events_eb_pipe_cw_log_policy,
]
name = "${local.resource_scope}-mns-outbound-events"
role_arn = aws_iam_role.mns_outbound_events_eb_pipe.arn
source = aws_dynamodb_table.delta-dynamodb-table.stream_arn
target = aws_sqs_queue.mns_outbound_events.arn

source_parameters {
dynamodb_stream_parameters {
starting_position = "TRIM_HORIZON"
}
}

log_configuration {
include_execution_data = ["ALL"]
level = "ERROR"
cloudwatch_logs_log_destination {
log_group_arn = aws_cloudwatch_log_group.pipe_log_group.arn
}
}
}
238 changes: 238 additions & 0 deletions infrastructure/instance/mns_publisher_lambda.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
locals {
mns_publisher_lambda_dir = abspath("${path.root}/../../lambdas/mns_publisher")
mns_publisher_lambda_files = fileset(local.mns_publisher_lambda_dir, "**")
mns_publisher_lambda_dir_sha = sha1(join("", [for f in local.mns_publisher_lambda_files : filesha1("${local.mns_publisher_lambda_dir}/${f}")]))
mns_publisher_lambda_name = "${local.short_prefix}-mns-publisher-lambda"
}

resource "aws_ecr_repository" "mns_publisher_lambda_repository" {
image_scanning_configuration {
scan_on_push = true
}
name = "${local.short_prefix}-mns-publisher-repo"
force_delete = local.is_temp
}

# Module for building and pushing Docker image to ECR
module "mns_publisher_docker_image" {
source = "terraform-aws-modules/lambda/aws//modules/docker-build"
version = "8.5.0"
docker_file_path = "./mns_publisher/Dockerfile"

create_ecr_repo = false
ecr_repo = aws_ecr_repository.mns_publisher_lambda_repository.name
ecr_repo_lifecycle_policy = jsonencode({
"rules" : [
{
"rulePriority" : 1,
"description" : "Keep only the last 2 images",
"selection" : {
"tagStatus" : "any",
"countType" : "imageCountMoreThan",
"countNumber" : 2
},
"action" : {
"type" : "expire"
}
}
]
})

platform = "linux/amd64"
use_image_tag = false
source_path = abspath("${path.root}/../../lambdas")
triggers = {
dir_sha = local.mns_publisher_lambda_dir_sha
shared_dir_sha = local.shared_dir_sha
}
}

resource "aws_ecr_repository_policy" "mns_publisher_lambda_ecr_image_retrieval_policy" {
repository = aws_ecr_repository.mns_publisher_lambda_repository.name

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
"Sid" : "LambdaECRImageRetrievalPolicy",
"Effect" : "Allow",
"Principal" : {
"Service" : "lambda.amazonaws.com"
},
"Action" : [
"ecr:BatchGetImage",
"ecr:DeleteRepositoryPolicy",
"ecr:GetDownloadUrlForLayer",
"ecr:GetRepositoryPolicy",
"ecr:SetRepositoryPolicy"
],
"Condition" : {
"StringLike" : {
"aws:sourceArn" : "arn:aws:lambda:${var.aws_region}:${var.immunisation_account_id}:function:${local.mns_publisher_lambda_name}"
}
}
}
]
})
}

# IAM Role for Lambda
resource "aws_iam_role" "mns_publisher_lambda_exec_role" {
name = "${local.mns_publisher_lambda_name}-exec-role"
assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [{
Effect = "Allow",
Sid = "",
Principal = {
Service = "lambda.amazonaws.com"
},
Action = "sts:AssumeRole"
}]
})
}

# Policy for Lambda execution role
resource "aws_iam_policy" "mns_publisher_lambda_exec_policy" {
name = "${local.mns_publisher_lambda_name}-exec-policy"
policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow"
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = "arn:aws:logs:${var.aws_region}:${var.immunisation_account_id}:log-group:/aws/lambda/${local.mns_publisher_lambda_name}:*"
},
{
Effect = "Allow",
Action = [
"ec2:CreateNetworkInterface",
"ec2:DescribeNetworkInterfaces",
"ec2:DeleteNetworkInterface"
],
Resource = "*"
},
{
"Effect" : "Allow",
"Action" : [
"firehose:PutRecord",
"firehose:PutRecordBatch"
],
"Resource" : "arn:aws:firehose:*:*:deliverystream/${module.splunk.firehose_stream_name}"
},
{
Effect = "Allow",
Action = [
"sqs:ReceiveMessage",
"sqs:DeleteMessage",
"sqs:GetQueueAttributes"
],
Resource = aws_sqs_queue.mns_outbound_events.arn
}
]
})
}

resource "aws_iam_policy" "mns_publisher_lambda_kms_access_policy" {
name = "${local.mns_publisher_lambda_name}-kms-policy"
description = "Allow Lambda to decrypt environment variables"

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"kms:Decrypt"
]
Resource = data.aws_kms_key.existing_lambda_encryption_key.arn
}
]
})
}

# Attach the execution policy to the Lambda role
resource "aws_iam_role_policy_attachment" "mns_publisher_lambda_exec_policy_attachment" {
role = aws_iam_role.mns_publisher_lambda_exec_role.name
policy_arn = aws_iam_policy.mns_publisher_lambda_exec_policy.arn
}

# Attach the kms policy to the Lambda role
resource "aws_iam_role_policy_attachment" "mns_publisher_lambda_kms_policy_attachment" {
role = aws_iam_role.mns_publisher_lambda_exec_role.name
policy_arn = aws_iam_policy.mns_publisher_lambda_kms_access_policy.arn
}

# Lambda Function with Security Group and VPC.
resource "aws_lambda_function" "mns_publisher_lambda" {
function_name = local.mns_publisher_lambda_name
role = aws_iam_role.mns_publisher_lambda_exec_role.arn
package_type = "Image"
image_uri = module.mns_publisher_docker_image.image_uri
architectures = ["x86_64"]
timeout = 120

vpc_config {
subnet_ids = local.private_subnet_ids
security_group_ids = [data.aws_security_group.existing_securitygroup.id]
}

environment {
variables = {
SPLUNK_FIREHOSE_NAME = module.splunk.firehose_stream_name
}
}

kms_key_arn = data.aws_kms_key.existing_lambda_encryption_key.arn
reserved_concurrent_executions = local.is_temp ? -1 : 20
depends_on = [
aws_cloudwatch_log_group.mns_publisher_lambda_log_group,
aws_iam_policy.mns_publisher_lambda_exec_policy
]
}

resource "aws_cloudwatch_log_group" "mns_publisher_lambda_log_group" {
name = "/aws/lambda/${local.mns_publisher_lambda_name}"
retention_in_days = 30
}

resource "aws_lambda_event_source_mapping" "mns_outbound_event_sqs_to_lambda" {
event_source_arn = aws_sqs_queue.mns_outbound_events.arn
function_name = aws_lambda_function.mns_publisher_lambda.arn
batch_size = 10
enabled = true
}

resource "aws_cloudwatch_log_metric_filter" "mns_publisher_error_logs" {
count = var.error_alarm_notifications_enabled ? 1 : 0

name = "${local.short_prefix}-MnsPublisherErrorLogsFilter"
pattern = "%\\[ERROR\\]%"
log_group_name = aws_cloudwatch_log_group.mns_publisher_lambda_log_group.name

metric_transformation {
name = "${local.short_prefix}-MnsPublisherErrorLogs"
namespace = "${local.short_prefix}-MnsPublisherLambda"
value = "1"
}
}

resource "aws_cloudwatch_metric_alarm" "mns_publisher_error_alarm" {
count = var.error_alarm_notifications_enabled ? 1 : 0

alarm_name = "${local.mns_publisher_lambda_name}-error"
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = 1
metric_name = "${local.short_prefix}-MnsPublisherErrorLogs"
namespace = "${local.short_prefix}-MnsPublisherLambda"
period = 120
statistic = "Sum"
threshold = 1
alarm_description = "This sets off an alarm for any error logs found in the MNS Publisher Lambda function"
alarm_actions = [data.aws_sns_topic.imms_system_alert_errors.arn]
treat_missing_data = "notBreaching"
}
Loading