|
| 1 | +# Prototype of id_sync_lambda.tf |
| 2 | + |
| 3 | +# This is a WIP. |
| 4 | +# This is an attempt to define the terraform for the new NHS MNS id sync lambda. |
| 5 | +# Some resources may be unnecessary. |
| 6 | +# The ones we do require for SQS queue and its KMS key are at the bottom of the lambda execution policy. |
| 7 | + |
| 8 | +# Define the directory containing the Docker image and calculate its SHA-256 hash for triggering redeployments |
| 9 | +locals { |
| 10 | + id_sync_lambda_dir = abspath("${path.root}/../id_sync") |
| 11 | + id_sync_lambda_files = fileset(local.id_sync_lambda_dir, "**") |
| 12 | + id_sync_lambda_dir_sha = sha1(join("", [for f in local.id_sync_lambda_files : filesha1("${local.id_sync_lambda_dir}/${f}")])) |
| 13 | +} |
| 14 | + |
| 15 | +resource "aws_ecr_repository" "id_sync_lambda_repository" { |
| 16 | + image_scanning_configuration { |
| 17 | + scan_on_push = true |
| 18 | + } |
| 19 | + name = "${local.short_prefix}-id-sync-repo" |
| 20 | + force_delete = local.is_temp |
| 21 | +} |
| 22 | + |
| 23 | +# Module for building and pushing Docker image to ECR |
| 24 | +module "id_sync_docker_image" { |
| 25 | + source = "terraform-aws-modules/lambda/aws//modules/docker-build" |
| 26 | + version = "8.0.1" |
| 27 | + |
| 28 | + create_ecr_repo = false |
| 29 | + ecr_repo = aws_ecr_repository.id_sync_lambda_repository.name |
| 30 | + ecr_repo_lifecycle_policy = jsonencode({ |
| 31 | + "rules" : [ |
| 32 | + { |
| 33 | + "rulePriority" : 1, |
| 34 | + "description" : "Keep only the last 2 images", |
| 35 | + "selection" : { |
| 36 | + "tagStatus" : "any", |
| 37 | + "countType" : "imageCountMoreThan", |
| 38 | + "countNumber" : 2 |
| 39 | + }, |
| 40 | + "action" : { |
| 41 | + "type" : "expire" |
| 42 | + } |
| 43 | + } |
| 44 | + ] |
| 45 | + }) |
| 46 | + |
| 47 | + platform = "linux/amd64" |
| 48 | + use_image_tag = false |
| 49 | + source_path = local.id_sync_lambda_dir |
| 50 | + triggers = { |
| 51 | + dir_sha = local.id_sync_lambda_dir_sha |
| 52 | + } |
| 53 | +} |
| 54 | + |
| 55 | +# Define the lambdaECRImageRetreival policy |
| 56 | +resource "aws_ecr_repository_policy" "id_sync_lambda_ECRImageRetreival_policy" { |
| 57 | + repository = aws_ecr_repository.id_sync_lambda_repository.name |
| 58 | + |
| 59 | + policy = jsonencode({ |
| 60 | + Version = "2012-10-17" |
| 61 | + Statement = [ |
| 62 | + { |
| 63 | + Sid : "LambdaECRImageRetrievalPolicy", |
| 64 | + Effect : "Allow", |
| 65 | + Principal : { |
| 66 | + Service : "lambda.amazonaws.com" |
| 67 | + }, |
| 68 | + Action : [ |
| 69 | + "ecr:BatchGetImage", |
| 70 | + "ecr:DeleteRepositoryPolicy", |
| 71 | + "ecr:GetDownloadUrlForLayer", |
| 72 | + "ecr:GetRepositoryPolicy", |
| 73 | + "ecr:SetRepositoryPolicy" |
| 74 | + ], |
| 75 | + Condition : { |
| 76 | + StringLike : { |
| 77 | + # "aws:sourceArn" : "arn:aws:lambda:eu-west-2:${local.immunisation_account_id}:function:${local.short_prefix}-id_sync_lambda" |
| 78 | + "aws:sourceArn" : aws_lambda_function.id_sync_lambda.arn |
| 79 | + } |
| 80 | + } |
| 81 | + } |
| 82 | + ] |
| 83 | + }) |
| 84 | +} |
| 85 | + |
| 86 | +# IAM Role for Lambda |
| 87 | +resource "aws_iam_role" "id_sync_lambda_exec_role" { |
| 88 | + name = "${local.short_prefix}-id-sync-lambda-exec-role" |
| 89 | + assume_role_policy = jsonencode({ |
| 90 | + Version = "2012-10-17", |
| 91 | + Statement = [{ |
| 92 | + Effect = "Allow", |
| 93 | + Sid = "", |
| 94 | + Principal = { |
| 95 | + Service = "lambda.amazonaws.com" |
| 96 | + }, |
| 97 | + Action = "sts:AssumeRole" |
| 98 | + }] |
| 99 | + }) |
| 100 | +} |
| 101 | + |
| 102 | +# Policy for Lambda execution role |
| 103 | +resource "aws_iam_policy" "id_sync_lambda_exec_policy" { |
| 104 | + name = "${local.short_prefix}-id-sync-lambda-exec-policy" |
| 105 | + policy = jsonencode({ |
| 106 | + Version = "2012-10-17", |
| 107 | + Statement = [ |
| 108 | + { |
| 109 | + Effect = "Allow" |
| 110 | + Action = [ |
| 111 | + "logs:CreateLogGroup", |
| 112 | + "logs:CreateLogStream", |
| 113 | + "logs:PutLogEvents" |
| 114 | + ] |
| 115 | + Resource = "arn:aws:logs:${var.aws_region}:${local.immunisation_account_id}:log-group:/aws/lambda/${local.short_prefix}-id_sync_lambda:*" |
| 116 | + }, |
| 117 | + # ** TODO need to ascertain whether we need these S3 policies. possibly not. we WILL need an SQS policy though. |
| 118 | + { |
| 119 | + Effect = "Allow" |
| 120 | + Action = [ |
| 121 | + "s3:GetObject", |
| 122 | + "s3:ListBucket", |
| 123 | + "s3:PutObject", |
| 124 | + "s3:CopyObject", |
| 125 | + "s3:DeleteObject" |
| 126 | + ] |
| 127 | + Resource = [ |
| 128 | + aws_s3_bucket.batch_data_source_bucket.arn, |
| 129 | + "${aws_s3_bucket.batch_data_source_bucket.arn}/*" |
| 130 | + ] |
| 131 | + }, |
| 132 | + { |
| 133 | + Effect = "Allow" |
| 134 | + Action = [ |
| 135 | + "s3:GetObject", |
| 136 | + "s3:PutObject", |
| 137 | + "s3:ListBucket" |
| 138 | + ] |
| 139 | + Resource = [ |
| 140 | + aws_s3_bucket.batch_data_destination_bucket.arn, |
| 141 | + "${aws_s3_bucket.batch_data_destination_bucket.arn}/*" |
| 142 | + ] |
| 143 | + }, |
| 144 | + # ** TODO: do we need these ec2 policies? I think they're to do with VPCs |
| 145 | + { |
| 146 | + Effect = "Allow", |
| 147 | + Action = [ |
| 148 | + "ec2:CreateNetworkInterface", |
| 149 | + "ec2:DescribeNetworkInterfaces", |
| 150 | + "ec2:DeleteNetworkInterface" |
| 151 | + ], |
| 152 | + Resource = "*" |
| 153 | + }, |
| 154 | + # ** TODO: ditto. The bucket is imms-${local.environment}-fhir-config |
| 155 | + # Examine it. |
| 156 | + { |
| 157 | + Effect = "Allow" |
| 158 | + Action = [ |
| 159 | + "s3:GetObject", |
| 160 | + "s3:PutObject", |
| 161 | + "s3:ListBucket" |
| 162 | + ] |
| 163 | + Resource = [ |
| 164 | + local.config_bucket_arn, |
| 165 | + "${local.config_bucket_arn}/*" |
| 166 | + ] |
| 167 | + }, |
| 168 | + { |
| 169 | + Effect : "Allow", |
| 170 | + Action : [ |
| 171 | + "firehose:PutRecord", |
| 172 | + "firehose:PutRecordBatch" |
| 173 | + ], |
| 174 | + Resource : "arn:aws:firehose:*:*:deliverystream/${module.splunk.firehose_stream_name}" |
| 175 | + }, |
| 176 | + { |
| 177 | + Effect = "Allow" |
| 178 | + Action = "lambda:InvokeFunction" |
| 179 | + Resource = [ |
| 180 | + "arn:aws:lambda:${var.aws_region}:${local.immunisation_account_id}:function:imms-${local.env}-id_sync_lambda", |
| 181 | + ] |
| 182 | + }, |
| 183 | + # New: required for SQS queue and its KMS key |
| 184 | + |
| 185 | + # Notes: |
| 186 | + # - the SQS queue is defined in terraform/sqs_id_sync.tf in branch VED-80-id-sync-sqs |
| 187 | + # - the KMS key in terraform/temp_id_sync_sqs_kms.tf in the same branch; this will eventually be replaced by |
| 188 | + # the version of infra/kms.tf in branch VED-80-id-sync-sqs-infra |
| 189 | + |
| 190 | + { |
| 191 | + Effect = "Allow", |
| 192 | + Action = [ |
| 193 | + "sqs:ReceiveMessage", |
| 194 | + "sqs:DeleteMessage", |
| 195 | + "sqs:GetQueueAttributes" |
| 196 | + ], |
| 197 | + Resource = "arn:aws:sqs:eu-west-2:${local.immunisation_account_id}:${local.short_prefix}-id-sync-queue" |
| 198 | + } |
| 199 | + { |
| 200 | + Effect = "Allow", |
| 201 | + Action = [ |
| 202 | + "kms:Decrypt", |
| 203 | + "kms:GenerateDataKey" |
| 204 | + ], |
| 205 | + Resource = data.aws_kms_key.existing_id_sync_encryption_key.arn |
| 206 | + } |
| 207 | + ] |
| 208 | + }) |
| 209 | +} |
| 210 | + |
| 211 | +resource "aws_iam_policy" "id_sync_lambda_kms_access_policy" { |
| 212 | + name = "${local.short_prefix}-id-sync-lambda-kms-policy" |
| 213 | + description = "Allow Lambda to decrypt environment variables" |
| 214 | + |
| 215 | + policy = jsonencode({ |
| 216 | + Version = "2012-10-17" |
| 217 | + Statement = [ |
| 218 | + { |
| 219 | + Effect = "Allow" |
| 220 | + Action = [ |
| 221 | + "kms:Decrypt" |
| 222 | + ] |
| 223 | + Resource = data.aws_kms_key.existing_lambda_encryption_key.arn |
| 224 | + }, |
| 225 | + { |
| 226 | + Effect = "Allow" |
| 227 | + Action = [ |
| 228 | + "kms:Encrypt", |
| 229 | + "kms:Decrypt", |
| 230 | + "kms:GenerateDataKey*" |
| 231 | + ] |
| 232 | + Resource = [ |
| 233 | + data.aws_kms_key.existing_s3_encryption_key.arn, |
| 234 | + ] |
| 235 | + } |
| 236 | + ] |
| 237 | + }) |
| 238 | +} |
| 239 | + |
| 240 | +# Attach the execution policy to the Lambda role |
| 241 | +resource "aws_iam_role_policy_attachment" "id_sync_lambda_exec_policy_attachment" { |
| 242 | + role = aws_iam_role.id_sync_lambda_exec_role.name |
| 243 | + policy_arn = aws_iam_policy.id_sync_lambda_exec_policy.arn |
| 244 | +} |
| 245 | + |
| 246 | +# Attach the kms policy to the Lambda role |
| 247 | +resource "aws_iam_role_policy_attachment" "id_sync_lambda_kms_policy_attachment" { |
| 248 | + role = aws_iam_role.id_sync_lambda_exec_role.name |
| 249 | + policy_arn = aws_iam_policy.id_sync_lambda_kms_access_policy.arn |
| 250 | +} |
| 251 | + |
| 252 | +# Lambda Function with Security Group and VPC. |
| 253 | +resource "aws_lambda_function" "id_sync_lambda" { |
| 254 | + function_name = "${local.short_prefix}-id_sync_lambda" |
| 255 | + role = aws_iam_role.id_sync_lambda_exec_role.arn |
| 256 | + package_type = "Image" |
| 257 | + image_uri = module.id_sync_docker_image.image_uri |
| 258 | + architectures = ["x86_64"] |
| 259 | + timeout = 360 |
| 260 | + |
| 261 | + vpc_config { |
| 262 | + subnet_ids = local.private_subnet_ids |
| 263 | + security_group_ids = [data.aws_security_group.existing_securitygroup.id] |
| 264 | + } |
| 265 | + |
| 266 | + # ** TODO: we're likely to not need any of the REDIS_ variables |
| 267 | + environment { |
| 268 | + variables = { |
| 269 | + CONFIG_BUCKET_NAME = local.config_bucket_name |
| 270 | + REDIS_HOST = data.aws_elasticache_cluster.existing_redis.cache_nodes[0].address |
| 271 | + REDIS_PORT = data.aws_elasticache_cluster.existing_redis.cache_nodes[0].port |
| 272 | + ID_SYNC_PROC_LAMBDA_NAME = "imms-${local.env}-id_sync_lambda" |
| 273 | + SPLUNK_FIREHOSE_NAME = module.splunk.firehose_stream_name |
| 274 | + } |
| 275 | + } |
| 276 | + kms_key_arn = data.aws_kms_key.existing_lambda_encryption_key.arn |
| 277 | + |
| 278 | + depends_on = [ |
| 279 | + aws_cloudwatch_log_group.id_sync_log_group, |
| 280 | + aws_iam_policy.id_sync_lambda_exec_policy |
| 281 | + ] |
| 282 | +} |
| 283 | + |
| 284 | +resource "aws_cloudwatch_log_group" "id_sync_log_group" { |
| 285 | + name = "/aws/lambda/${local.short_prefix}-id_sync_lambda" |
| 286 | + retention_in_days = 30 |
| 287 | +} |
| 288 | + |
| 289 | + |
| 290 | +# S3 Bucket notification to trigger Lambda function for config bucket |
| 291 | +resource "aws_s3_bucket_notification" "config_lambda_notification" { |
| 292 | + |
| 293 | + bucket = aws_s3_bucket.batch_config_bucket.bucket |
| 294 | + |
| 295 | + lambda_function { |
| 296 | + lambda_function_arn = aws_lambda_function.id_sync_lambda.arn |
| 297 | + events = ["s3:ObjectCreated:*"] |
| 298 | + } |
| 299 | +} |
| 300 | + |
| 301 | +# Permission for the new S3 bucket to invoke the Lambda function |
| 302 | +resource "aws_lambda_permission" "new_s3_invoke_permission" { |
| 303 | + |
| 304 | + statement_id = "AllowExecutionFromNewS3" |
| 305 | + action = "lambda:InvokeFunction" |
| 306 | + function_name = aws_lambda_function.id_sync_lambda.function_name |
| 307 | + principal = "s3.amazonaws.com" |
| 308 | + source_arn = local.config_bucket_arn |
| 309 | +} |
0 commit comments