From 90f4d916fec59ea8fda629d1d320581e4d945318 Mon Sep 17 00:00:00 2001 From: badra001 Date: Tue, 12 Aug 2025 11:37:36 -0400 Subject: [PATCH] update tag passing, variables --- README.eicar.md | 10 + README.md | 105 +++++++++++ TODO.md | 6 + base.tf | 4 + data.tf | 10 +- eventbridge.guardduty.tf | 162 +++++++++++++++++ eventbridge.s3.tf | 97 ++++++++++ lambda.layer.tf | 34 ++++ lambda.move.tf | 174 ++++++++++++++++++ lambda.notify.tf | 170 +++++++++++++++++ lambda.s3-tag.tf | 167 +++++++++++++++++ lambda.s3.tf | 159 ++++++++++++++++ locals.tf | 7 + main.tf | 112 ++++++++++++ outputs.tf | 39 ++++ region.tf | 3 + role.tf | 383 +++++++++++++++++++++++++++++++++++++++ secret.tf | 96 ++++++++++ tags.tf | 15 ++ variables.api.tf | 11 ++ variables.lambda.tf | 33 ++++ variables.tf | 79 ++++++++ version.tf | 3 +- versions.tf | 28 ++- 24 files changed, 1899 insertions(+), 8 deletions(-) create mode 100644 README.eicar.md create mode 100644 TODO.md create mode 100644 base.tf create mode 100644 eventbridge.guardduty.tf create mode 100644 eventbridge.s3.tf create mode 100644 lambda.layer.tf create mode 100644 lambda.move.tf create mode 100644 lambda.notify.tf create mode 100644 lambda.s3-tag.tf create mode 100644 lambda.s3.tf create mode 100644 locals.tf create mode 100644 main.tf create mode 100644 outputs.tf create mode 100644 region.tf create mode 100644 role.tf create mode 100644 secret.tf create mode 100644 tags.tf create mode 100644 variables.api.tf create mode 100644 variables.lambda.tf create mode 100644 variables.tf diff --git a/README.eicar.md b/README.eicar.md new file mode 100644 index 0000000..3710067 --- /dev/null +++ b/README.eicar.md @@ -0,0 +1,10 @@ +EICAR test file + +https://www.eicar.org/download-anti-malware-testfile/ + + + +https://www.eicar.org/download/eicar-com/?wpdmdl=8840&refresh=687011f18864d1752175089 +https://www.eicar.org/download/eicar-com-2/?wpdmdl=8842&refresh=687011f37fd6d1752175091 +https://www.eicar.org/download/eicar_com-zip/?wpdmdl=8847&refresh=687011f5747b81752175093 +https://www.eicar.org/download/eicar-com-2-2/?wpdmdl=8848&refresh=687011f75ca9b1752175095 diff --git a/README.md b/README.md index e69de29..452b42c 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,105 @@ +# DARHTS/DAPPS S3 Workflow + +## Environments + +| Environment | Account | +|-------------|---------| +| dev | ma41 | +| ite | adsd-dapps-ite | +| uat | adsd-dapps-test | +| stage | adsd-dapps-stage | +| prod | adsd-dapps-prod | +| train | adsd-dapps-prod | + +## DARHTS buckets + +Three buckets per environment, for the following purposes: + +* in: uploaded files from DARHTS Salesforce +* clean: after s3 scan, files which do not have threats are moved here via EventBridge and Lambda +* quarantine: after s3 scan, files which may have threats or are unknown types (not no_threats) are moved here via EventBridge and Lambda + +v-s3-ditd-darhts-{env}-in-{account}-{region-short} +v-s3-ditd-darhts-{env}-clean-{account}-{region-short} +v-s3-ditd-darhts-{env}-quarantine-{account}-{region-short} + +The `clean` bucket will need versioning turned on for replication to the DAPPS bucket (below) +All 3 buckets will use a bucket-specific KMS key. +All 3 buckets will use bucket keys +All 3 buckets will have finops tags for the DARHTS project accountable by DITD. + +The `in` bucket will have GuardDuty S3 Malware scanning enabled, which requires an IAM role and IAM Policy to allow the service +to scan and tag. These will have the following names: + +p-{in-bucketname}-gd +r-{in-bucketname}-gd + +where {in-bucketname} is the bucket id of the `in` bucket id, without the `v-s3-` prefix. +These IAM objects will have finops tags for the DARHTS project accountable by DITD. +Need to determine where the scan events go, and how OIS will be notified. + +An EventBridge will be setup for the GuardDuty scan with three targets. It will be named ditd-darhts-{env}-in-guardduty. + +1. Cloudwatch Log (format /aws/eventbridge/gd-{in-bucketname}) +1. DARHTS API + * needs URL per environment + * needs credentials per environment + * credentials into AWS Secret under /apps/darhts/{env}/api-credentials +1. Lambda (for the move, format guardduty-move-files-{in-bucketname}) + * BUCKET_IN = arn of in bucket + * BUCKET_CLEAN = arn of clean bucket + * BUCKET_QUARANTINE = arn of quarantine bucket + * with a log /aws/lambda/{lambda-name} + * Logic: + * triggered by event bridge after scan + * if scanned object tag is NO_THREATS_FOUND, copy to `clean` bucket, delete from `in` bucket + * if scanned object tag is anything else, copy to `quarantine` bucket, delete from `in` bucket + +On creation in the `clean` bucket, S3 notification even will trigger another EventBridge named ditd-darhts-env-in-guardduty to send indication +to DARHTS API the object has arrived in the clean bucket. + +### Access to Buckets + +An IAM service use will be created and it will be allowed to assume a role. + +IAM service: + +* Name: s-ditd-darhts-{env}-s3 +* finops tags for DARHTS project accountable by DITD +* need to determine how to pass and rotate credentials every 90 days +* will neeed contact (Census) name and email address (group desired) +* permission allow it to assume the role for the file activity (below) + +IAM role: + +* Name: r-ditd-darhts-{env}-s3 +* finops tags for DARHTS project accountable by DITD +* permissions to PUT into `in` bucket +* permissions to GET and TAG for `clean` bucket + +## DAPPS buckets + +Same environments as above. + +Nne bucket per environment, for the following purposes: + +* clean: replicated files from DARHTS in bucket if no_threats AND sync-to-dapps tag set + +The format is: + +v-s3-adsd-dapps-{env}-clean-{account}-{region-short} + +Bucket replication from the v-s3-ditd-darhts-{env}-clean bucket to the v-s3-adsd-dapps-{env}-clean buckets +will be handled by an IAM role with the name + +r-ditd-darhts-{env}-clean-{account}-{region-short}-replication + +Which has permission for GETs on the source bucket and PUTs on the target bucket. +It has a rule which replicates all prefixes where TWO tag values match. + +# CHANGELOG + +* 1.0.0 -- 2025-07-11 + - initial + + diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..cc65de7 --- /dev/null +++ b/TODO.md @@ -0,0 +1,6 @@ +# To Do + +* make replication work on tags +* package layer code into zip file in repo +* allow individual lambdas to make own zip files + diff --git a/base.tf b/base.tf new file mode 100644 index 0000000..703bef2 --- /dev/null +++ b/base.tf @@ -0,0 +1,4 @@ +module "base" { + source = "git@github.e.it.census.gov:terraform-modules/boc-nts//base-label" +# filename = format("%v/%v", path.module, "base.yml") +} diff --git a/data.tf b/data.tf index 16506e6..2fc0f99 100644 --- a/data.tf +++ b/data.tf @@ -1,7 +1,7 @@ -data "aws_caller_identity" "current" {} - -data "aws_arn" "current" { - arn = data.aws_caller_identity.current.arn +data "aws_kms_key" "s3_key" { + key_id = "alias/aws/s3" } -data "aws_region" "current" {} +data "aws_s3_bucket" "log_bucket" { + bucket = format("inf-logs-%v-%v", var.account_id, local.region) +} diff --git a/eventbridge.guardduty.tf b/eventbridge.guardduty.tf new file mode 100644 index 0000000..4998545 --- /dev/null +++ b/eventbridge.guardduty.tf @@ -0,0 +1,162 @@ +locals { + short_files_in = "files_in" +} + +# https://repost.aws/knowledge-center/cloudwatch-log-group-eventbridge +# must start with /aws/events + +resource "aws_cloudwatch_log_group" "guardduty_event_log" { + # name = format("/aws/events/%v-%v-%v/%v/%v", var.app_info.organization, var.app_info.name, var.app_info.environment, "gd", local.short_files_in) + name = format("/aws/events/%v/%v-%v", var.input_resource_label, "in", "guardduty") + retention_in_days = var.log_retention_in_days + + tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["log"], + ) +} + +data "aws_iam_policy_document" "guardduty_event_log" { + statement { + sid = "TrustEventsToStoreLogEvent" + effect = "Allow" + actions = [ + "logs:CreateLogStream", + "logs:PutLogEvents", + ] + principals { + type = "Service" + identifiers = [ + "events.amazonaws.com", + "delivery.logs.amazonaws.com", + ] + } + condition { + test = "StringEquals" + variable = "aws:SourceAccount" + values = [data.aws_caller_identity.current.account_id] + } + resources = [format("%v:log-stream:*", aws_cloudwatch_log_group.guardduty_event_log.arn)] + } +} + +resource "aws_cloudwatch_log_resource_policy" "guardduty_event_log" { + policy_document = data.aws_iam_policy_document.guardduty_event_log.json + policy_name = format("%v-%v", var.input_resource_label, "guardduty") +} + +module "eventbridge_guardduty" { + source = "terraform-aws-modules/eventbridge/aws" + # role_name = format("r-%v-%v-%v-%v-%v", var.app_info.organization, var.app_info.name, var.app_info.environment, "gd", local.short_files_in) + role_name = format("%v%v-%v-%v", try(module.base.prefixes.role, ""), var.input_resource_label, "in", "guardduty") + + append_rule_postfix = false + create_bus = false + create_role = true + create_permissions = true + + ## create_connections = true + ## create_api_destinations = true + ## attach_api_destination_policy = true + + attach_lambda_policy = true + lambda_target_arns = [ + module.lambda_move.lambda_function_arn, + module.lambda_notify.lambda_function_arn, + ] + + attach_cloudwatch_policy = true + cloudwatch_target_arns = [ + aws_cloudwatch_log_group.guardduty_event_log.arn, + # aws_cloudwatch_log_group.log.arn, + ] + + rules = { + format("%v-guardduty", var.input_resource_label) = { + description = "GuardDuty Scan Events" + state = "ENABLED" + # role_arn = true + event_pattern = jsonencode({ + "source" = ["aws.guardduty"] + "detail-type" = ["GuardDuty Malware Protection Object Scan Result"] + }) + } + } + targets = { + format("%v-guardduty", var.input_resource_label) = [ + { + name = "log-to-cloudwatch" + arn = aws_cloudwatch_log_group.guardduty_event_log.arn + }, + { + name = "lambda-status-move" + arn = module.lambda_move.lambda_function_arn + attach_role_arn = true + dead_letter_arn = module.lambda_move_failure.queue_arn + retry_policy = { + maximum_event_age_in_seconds = var.dlq_event_age + maximum_retry_attempts = var.dlq_retry_attempts + } + }, + { + name = "lambda-status-notify" + arn = module.lambda_notify.lambda_function_arn + attach_role_arn = true + dead_letter_arn = module.lambda_notify_failure.queue_arn + retry_policy = { + maximum_event_age_in_seconds = var.dlq_event_age + maximum_retry_attempts = var.dlq_retry_attempts + } + }, + ] + } + + ## not supported in govcloud + ## https://docs.aws.amazon.com/govcloud-us/latest/UserGuide/govcloud-eventbridge.html + ## + ## api_destinations = { + ## darhts = { + ## description = "DARHTS Salesforce API" + ## invocation_endpoint = "https://census-darhts--ppandya2.sandbox.my.salesforce.com//services/data/v63.0/sobjects/GuardDutyObjectScan__e/" + ## http_method = "POST" + ## invocation_rate_limit_per_second = 300 + ## } + ## } + ## + ## connections = { + ## darhts = { + ## authorization_type = "OAUTH_CLIENT_CREDENTIALS" + ## auth_parameters = { + ## oauth = { + ## authorization_endpoint = "https://census-darhts--ppandya2.sandbox.my.salesforce.com/services/oauth2/token" + ## http_method = "POST" + ## + ## client_parameters = { + ## client_id = "3MVG9T8tY5rhBtS0ULMDTdhcFlvMrWqhtPHqfb.zlDpiigxGqdJoT3PGVmWzA99lxW2e4LYxYkrKpTU0V4s7d" + ## client_secret = "Pass1234!" + ## } + ## oauth_http_parameters = { + ## body = [{ + ## key = "grant_type" + ## value = "client_credentials" + ## is_value_secret = false + ## }] + ## } + ## } + ## } + ## } + ## } + + role_tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["role"], + ) + + tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["guardduty"], + ) +} diff --git a/eventbridge.s3.tf b/eventbridge.s3.tf new file mode 100644 index 0000000..ebc3e63 --- /dev/null +++ b/eventbridge.s3.tf @@ -0,0 +1,97 @@ +locals { + short_files_clean = "files_clean" +} + +module "eventbridge_s3" { + source = "terraform-aws-modules/eventbridge/aws" + # role_name = format("r-%v-%v-%v-%v-%v", var.app_info.organization, var.app_info.name, var.app_info.environment, "s3", local.short_files_clean) + role_name = format("%v%v-%v-%v", try(module.base.prefixes.role, ""), var.input_resource_label, "clean", "s3") + + append_rule_postfix = false + create_bus = false + create_role = true + create_permissions = true + + ## create_connections = true + ## create_api_destinations = true + ## attach_api_destination_policy = true + + attach_lambda_policy = true + lambda_target_arns = [ + module.lambda_s3_tag.lambda_function_arn, + module.lambda_s3.lambda_function_arn, + ] + + attach_cloudwatch_policy = false + + rules = { + format("%v-s3", var.input_resource_label) = { + description = "S3 Notification Event" + state = "ENABLED" + event_pattern = jsonencode({ + source = ["aws.s3"] + detail-type = ["Object Created"] + detail = { + "bucket" = { + "name" = [module.files_clean.s3_bucket_id] + } + } + }) + } + format("%v-s3-tag", var.input_resource_label) = { + description = "S3 Tag Notification Event" + state = "ENABLED" + event_pattern = jsonencode({ + source = ["aws.s3"] + detail-type = ["Object Tags Added"] + detail = { + "bucket" = { + "name" = [module.files_clean.s3_bucket_id] + } + } + }) + } + } + targets = { + format("%v-s3", var.input_resource_label) = [ + { + name = "lambda-s3-notify" + arn = module.lambda_s3.lambda_function_arn + attach_role_arn = true + dead_letter_arn = module.lambda_s3_failure.queue_arn + retry_policy = { + maximum_event_age_in_seconds = var.dlq_event_age + maximum_retry_attempts = var.dlq_retry_attempts + } + } + ] + format("%v-s3-tag", var.input_resource_label) = [ + # { + # name = "log-to-cloudwatch" + # arn = aws_cloudwatch_log_group.guardduty_event_log.arn + # }, + { + name = "lambda-s3-tag-notify" + arn = module.lambda_s3_tag.lambda_function_arn + attach_role_arn = true + dead_letter_arn = module.lambda_s3_tag_failure.queue_arn + retry_policy = { + maximum_event_age_in_seconds = var.dlq_event_age + maximum_retry_attempts = var.dlq_retry_attempts + } + } + ] + } + + role_tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["role"], + ) + + tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["event"], + ) +} diff --git a/lambda.layer.tf b/lambda.layer.tf new file mode 100644 index 0000000..f4014f2 --- /dev/null +++ b/lambda.layer.tf @@ -0,0 +1,34 @@ +module "lambda_layer" { + source = "terraform-aws-modules/lambda/aws" + + create_layer = true +# create_package = true + create_package = false + + layer_name = format("%v-common", var.input_resource_label) + description = "DAHRTS DAPPS common code" + compatible_runtimes = [format("python%v", var.python_runtime)] + + local_existing_package = format("%v/%v/%v",path.module,"code","darhts-guardduty-move.package.zip") + source_path = [ + { + path = "${path.root}/code/packages", + patterns = [ + "!history.*", + "!\\.venv/?.*", + "!README.*", + "!pip.*", + "!pyproject.*", + "!requirements.*", + "!setup.env", + "!uv.*", + ] + } + ] + + tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["lambda"], + ) +} diff --git a/lambda.move.tf b/lambda.move.tf new file mode 100644 index 0000000..8c39b26 --- /dev/null +++ b/lambda.move.tf @@ -0,0 +1,174 @@ +module "lambda_move" { + source = "terraform-aws-modules/lambda/aws" + + create_function = true +# create_package = true + create_package = false + create_role = true + create_async_event_config = true + + attach_cloudwatch_logs_policy = true + # attach_dead_letter_policy = true + attach_async_event_policy = true + + cloudwatch_logs_retention_in_days = var.log_retention_in_days + cloudwatch_logs_tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["log"], + ) + + function_name = format("%v-guardduty-move", var.input_resource_label) + role_name = format("%v%v-guardduty-move", try(module.base.prefixes.role, ""), var.input_resource_label) + description = "DARHTS DAPPS GuardDuty Move Files" + handler = "darhts-guardduty-move.lambda_handler" + runtime = format("python%v", var.python_runtime) + publish = true + timeout = var.lambda_timeout + memory_size = var.lambda_memory_size + ephemeral_storage_size = var.lambda_ephemeral_storage_size + timeouts = {} + tracing_mode = "PassThrough" + reserved_concurrent_executions = -1 + + local_existing_package = format("%v/%v/%v",path.module,"code","darhts-guardduty-move.zip") + source_path = "${path.root}/code/darhts-guardduty-move.py" + + layers = [ + module.lambda_layer.lambda_layer_arn, + ] + + environment_variables = merge( + { + Enabled = true + GUARDDUTY_MOVE_ORG = "default" + GUARDDUTY_MOVE_VERBOSE = false + GUARDDUTY_MOVE_BUCKET_IN = module.files_in.s3_bucket_id + GUARDDUTY_MOVE_BUCKET_CLEAN = module.files_clean.s3_bucket_id + GUARDDUTY_MOVE_BUCKET_QUARANTINE = module.files_quarantine.s3_bucket_id + + #POWERTOOLS_LOG_LEVEL = "INFO" + POWERTOOLS_LOG_LEVEL = "DEBUG" + }, + var.lambda_environment_variables_override, + ) + + attach_policy_jsons = true + number_of_policy_jsons = 1 + policy_jsons = [ + data.aws_iam_policy_document.lambda_move.json + ] + destination_on_failure = module.lambda_move_failure.queue_arn + + role_tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["role"], + ) + + tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["lambda"], + ) +} + +module "lambda_move_alias" { + source = "terraform-aws-modules/lambda/aws//modules/alias" + + name = module.lambda_move.lambda_function_name + function_name = module.lambda_move.lambda_function_name + description = "DARHTS DAPPS GuardDuty Move Files" + function_version = "$LATEST" +} + +data "aws_iam_policy_document" "lambda_move" { + statement { + sid = "UseSSMSecret" + effect = "Allow" + actions = [ + "secretsmanager:DescribeSecret", + "secretsmanager:GetSecretValue", + "secretsmanager:GetResourcePolicy", + ] + resources = [ + aws_secretsmanager_secret.app_secret.arn, + aws_secretsmanager_secret_version.app_secret.arn, + ] + } + statement { + sid = "KMSAccessPolicy" + effect = "Allow" + actions = [ + "kms:Decrypt", + "kms:DescribeKey" + ] + resources = [aws_kms_key.app_secret.arn] + } + statement { + sid = "S3" + effect = "Allow" + actions = [ + "s3:ListBucket", + "s3:DeleteObject*", + "s3:GetObject*", + "s3:PutObject*", + ] + resources = [ + module.files_in.s3_bucket_arn, + format("%v/*", module.files_in.s3_bucket_arn), + module.files_clean.s3_bucket_arn, + format("%v/*", module.files_clean.s3_bucket_arn), + module.files_quarantine.s3_bucket_arn, + format("%v/*", module.files_quarantine.s3_bucket_arn), + ] + } + statement { + sid = "S3AccessEncryptionKey" + effect = "Allow" + actions = [ + "kms:ReEncrypt*", + "kms:GenerateDataKey", + "kms:Encrypt", + "kms:Decrypt" + ] + resources = [ + module.files_in.kms_key_arn, + module.files_clean.kms_key_arn, + module.files_quarantine.kms_key_arn, + ] + } + + ## statement { + ## sid = "WriteToSQS" + ## effect = "Allow" + ## actions = ["sqs:SendMessage"] + ## resources = [module.lambda_move_failure.queue_arn] + ## } +} + +module "lambda_move_failure" { + source = "terraform-aws-modules/sqs/aws" + name = format("%v-move-failure", var.input_resource_label) + + sqs_managed_sse_enabled = true + create_queue_policy = true + queue_policy_statements = { + eventbridge = { + sid = "SNSPublish" + actions = ["sqs:SendMessage"] + principals = [ + { + type = "Service" + identifiers = ["events.amazonaws.com"] + } + ] + } + } + + tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["sqs"], + ) +} diff --git a/lambda.notify.tf b/lambda.notify.tf new file mode 100644 index 0000000..191ac03 --- /dev/null +++ b/lambda.notify.tf @@ -0,0 +1,170 @@ +# resource "aws_cloudwatch_log_group" "lambda_notify" { +# name = "/aws/lambda/darhts-guardduty-notify" +# retention_in_days = var.log_retention_in_days +# +# tags = merge( +# local.base_tags, +# var.tags, +# local.input_finops_roles["log"], +# ) +# } + +module "lambda_notify" { + source = "terraform-aws-modules/lambda/aws" + + create_function = true +# create_package = true + create_package = false + create_role = true + create_async_event_config = true + + attach_cloudwatch_logs_policy = true + # attach_dead_letter_policy = true + attach_async_event_policy = true + + cloudwatch_logs_retention_in_days = var.log_retention_in_days + cloudwatch_logs_tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["log"], + ) + + function_name = format("%v-guardduty-notify", var.input_resource_label) + role_name = format("%v%v-guardduty-notify", try(module.base.prefixes.role, ""), var.input_resource_label) + description = "DARHTS DAPPS GuardDuty Notify to Salesforce API" + handler = "darhts-guardduty-notify.lambda_handler" + runtime = format("python%v", var.python_runtime) + publish = true + timeout = var.lambda_timeout + memory_size = var.lambda_memory_size + ephemeral_storage_size = var.lambda_ephemeral_storage_size + timeouts = {} + tracing_mode = "PassThrough" + reserved_concurrent_executions = -1 + dead_letter_target_arn = module.lambda_notify_failure.queue_arn + + + local_existing_package = format("%v/%v/%v",path.module,"code","darhts-guardduty-notify.zip") + source_path = "${path.root}/code/darhts-guardduty-notify.py" + + layers = [ + module.lambda_layer.lambda_layer_arn, + ] + + environment_variables = merge( + { + Enabled = true + GUARDDUTY_NOTIFY_ORG = "default" + GUARDDUTY_NOTIFY_VERBOSE = false + GUARDDUTY_NOTIFY_SECRET_NAME = var.secret_name + GUARDDUTY_NOTIFY_ENVIRONMENT = var.app_info.environment +# GUARDDUTY_NOTIFY_AUTH_URL = "${var.app_info.token_url}/fail" + GUARDDUTY_NOTIFY_AUTH_URL = var.app_info.token_url + GUARDDUTY_NOTIFY_SALESFORCE_API_VERSION = var.app_info.salesforce_api_version +# GUARDDUTY_NOTIFY_PLATFORM_EVENT_NAME = "FailGuardDutyObjectScan__e" + GUARDDUTY_NOTIFY_PLATFORM_EVENT_NAME = "GuardDutyObjectScan__e" + # use DEBUG for debbuing, along with GUARDDUTY_MOVE_VERBOSE + #POWERTOOLS_LOG_LEVEL = "INFO" + POWERTOOLS_LOG_LEVEL = "DEBUG" + }, + var.lambda_environment_variables_override, + ) + + attach_policy_jsons = true + number_of_policy_jsons = 1 + policy_jsons = [ + data.aws_iam_policy_document.lambda_notify.json + ] + destination_on_failure = module.lambda_notify_failure.queue_arn + + # attach_policies = true + # policies = [ + # "arn:aws:iam::aws:policy/service-role/AWSLambdaSQSQueueExecutionRole", + # ] + # number_of_policies = 1 + + # allowed_triggers = { + # APIGatewayAny = { + # service = "apigateway" + # source_arn = "arn:aws:execute-api:eu-west-1:135367859851:aqnku8akd0/*/*/*" + # } + # } + + role_tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["role"], + ) + + tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["lambda"], + ) +} + +module "lambda_notify_alias" { + source = "terraform-aws-modules/lambda/aws//modules/alias" + + name = module.lambda_notify.lambda_function_name + function_name = module.lambda_notify.lambda_function_name + description = "DARHTS DAPPS GuardDuty Notify to Salesforce API" + function_version = "$LATEST" +} + +data "aws_iam_policy_document" "lambda_notify" { + statement { + sid = "UseSSMSecret" + effect = "Allow" + actions = [ + "secretsmanager:DescribeSecret", + "secretsmanager:GetSecretValue", + "secretsmanager:GetResourcePolicy", + ] + resources = [ + aws_secretsmanager_secret.app_secret.arn, + aws_secretsmanager_secret_version.app_secret.arn, + ] + } + statement { + sid = "KMSAccessPolicy" + effect = "Allow" + actions = [ + "kms:Decrypt", + "kms:DescribeKey" + ] + resources = [aws_kms_key.app_secret.arn] + } + statement { + sid = "WriteToSQS" + effect = "Allow" + actions = ["sqs:SendMessage"] + resources = [module.lambda_notify_failure.queue_arn] + } +} + +module "lambda_notify_failure" { + source = "terraform-aws-modules/sqs/aws" + name = format("%v-guardduty-notify-failure", var.input_resource_label) + + sqs_managed_sse_enabled = true + create_queue_policy = true + queue_policy_statements = { + eventbridge = { + sid = "SNSPublish" + actions = ["sqs:SendMessage"] + principals = [ + { + type = "Service" + identifiers = ["events.amazonaws.com"] + } + ] + } + } + + tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["sqs"], + ) +} diff --git a/lambda.s3-tag.tf b/lambda.s3-tag.tf new file mode 100644 index 0000000..c8e525a --- /dev/null +++ b/lambda.s3-tag.tf @@ -0,0 +1,167 @@ +module "lambda_s3_tag" { + source = "terraform-aws-modules/lambda/aws" + + create_function = true +# create_package = true + create_package = false + create_role = true + create_async_event_config = true + + attach_cloudwatch_logs_policy = true + # attach_dead_letter_policy = true + attach_async_event_policy = true + + cloudwatch_logs_retention_in_days = var.log_retention_in_days + cloudwatch_logs_tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["log"], + ) + + function_name = format("%v-s3-tag", var.input_resource_label) + role_name = format("%v%v-s3-tag", try(module.base.prefixes.role, ""), var.input_resource_label) + description = "DARHTS DAPPS S3 Tag Copy to DAPPS Clean" + handler = "darhts-s3-tag.lambda_handler" + runtime = format("python%v", var.python_runtime) + publish = true + timeout = var.lambda_timeout + memory_size = var.lambda_memory_size + ephemeral_storage_size = var.lambda_ephemeral_storage_size + timeouts = {} + tracing_mode = "PassThrough" + reserved_concurrent_executions = -1 + dead_letter_target_arn = module.lambda_s3_tag_failure.queue_arn + + + local_existing_package = format("%v/%v/%v",path.module,"code","darhts-s3-tag.zip") + source_path = "${path.root}/code/darhts-s3-tag.py" + + layers = [ + module.lambda_layer.lambda_layer_arn, + ] + + environment_variables = merge( + { + Enabled = true + S3_TAG_ORG = "default" + S3_TAG_VERBOSE = false + S3_TAG_ENVIRONMENT = var.app_info.environment + S3_TAG_BUCKET_CLEAN_IN = module.files_clean.s3_bucket_id + S3_TAG_BUCKET_CLEAN_OUT = module.files_out_clean.s3_bucket_id + S3_TAG_TRIGGER_TAGS = jsonencode({ + GuardDutyMalwareScanStatus = "NO_THREATS_FOUND" + darhts_certified = "true" + }) + # use DEBUG for debbuing, along with S3_MOVE_VERBOSE + #POWERTOOLS_LOG_LEVEL = "INFO" + POWERTOOLS_LOG_LEVEL = "DEBUG" + }, + var.lambda_environment_variables_override, + ) + + attach_policy_jsons = true + number_of_policy_jsons = 1 + policy_jsons = [ + data.aws_iam_policy_document.lambda_s3_tag.json + ] + destination_on_failure = module.lambda_s3_tag_failure.queue_arn + + # attach_policies = true + # policies = [ + # "arn:aws:iam::aws:policy/service-role/AWSLambdaSQSQueueExecutionRole", + # ] + # number_of_policies = 1 + + # allowed_triggers = { + # APIGatewayAny = { + # service = "apigateway" + # source_arn = "arn:aws:execute-api:eu-west-1:135367859851:aqnku8akd0/*/*/*" + # } + # } + + role_tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["role"], + ) + + tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["lambda"], + ) +} + +module "lambda_s3_tag_alias" { + source = "terraform-aws-modules/lambda/aws//modules/alias" + + name = module.lambda_s3_tag.lambda_function_name + function_name = module.lambda_s3_tag.lambda_function_name + description = "DARHTS DAPPS S3 Notify to Salesforce API" + function_version = "$LATEST" +} + +data "aws_iam_policy_document" "lambda_s3_tag" { + statement { + sid = "S3" + effect = "Allow" + actions = [ + "s3:ListBucket", + "s3:DeleteObject*", + "s3:GetObject*", + "s3:PutObject*", + ] + resources = [ + module.files_clean.s3_bucket_arn, + format("%v/*", module.files_clean.s3_bucket_arn), + module.files_out_clean.s3_bucket_arn, + format("%v/*", module.files_out_clean.s3_bucket_arn), + ] + } + statement { + sid = "S3AccessEncryptionKey" + effect = "Allow" + actions = [ + "kms:ReEncrypt*", + "kms:GenerateDataKey", + "kms:Encrypt", + "kms:Decrypt" + ] + resources = [ + module.files_clean.kms_key_arn, + module.files_out_clean.kms_key_arn, + ] + } + statement { + sid = "WriteToSQS" + effect = "Allow" + actions = ["sqs:SendMessage"] + resources = [module.lambda_s3_tag_failure.queue_arn] + } +} + +module "lambda_s3_tag_failure" { + source = "terraform-aws-modules/sqs/aws" + name = format("%v-s3-tag-failure", var.input_resource_label) + + sqs_managed_sse_enabled = true + create_queue_policy = true + queue_policy_statements = { + eventbridge = { + sid = "SNSPublish" + actions = ["sqs:SendMessage"] + principals = [ + { + type = "Service" + identifiers = ["events.amazonaws.com"] + } + ] + } + } + + tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["sqs"], + ) +} diff --git a/lambda.s3.tf b/lambda.s3.tf new file mode 100644 index 0000000..11a0686 --- /dev/null +++ b/lambda.s3.tf @@ -0,0 +1,159 @@ +module "lambda_s3" { + source = "terraform-aws-modules/lambda/aws" + + create_function = true +# create_package = true + create_package = false + create_role = true + create_async_event_config = true + + attach_cloudwatch_logs_policy = true + # attach_dead_letter_policy = true + attach_async_event_policy = true + + cloudwatch_logs_retention_in_days = var.log_retention_in_days + cloudwatch_logs_tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["log"], + ) + + function_name = format("%v-s3-notify", var.input_resource_label) + role_name = format("%v%v-s3-notify", try(module.base.prefixes.role, ""), var.input_resource_label) + description = "DARHTS DAPPS S3 Notify to Salesforce API" + handler = "darhts-s3-notify.lambda_handler" + runtime = format("python%v", var.python_runtime) + publish = true + timeout = var.lambda_timeout + memory_size = var.lambda_memory_size + ephemeral_storage_size = var.lambda_ephemeral_storage_size + timeouts = {} + tracing_mode = "PassThrough" + reserved_concurrent_executions = -1 + dead_letter_target_arn = module.lambda_s3_failure.queue_arn + + + local_existing_package = format("%v/%v/%v",path.module,"code","darhts-s3-notify.zip") + source_path = "${path.root}/code/darhts-s3-notify.py" + + layers = [ + module.lambda_layer.lambda_layer_arn, + ] + + environment_variables = merge( + { + Enabled = true + S3_NOTIFY_ORG = "default" + S3_NOTIFY_VERBOSE = false + S3_NOTIFY_SECRET_NAME = var.secret_name + S3_NOTIFY_ENVIRONMENT = var.app_info.environment +# S3_NOTIFY_AUTH_URL = "${var.app_info.token_url}/fail" + S3_NOTIFY_AUTH_URL = var.app_info.token_url + S3_NOTIFY_SALESFORCE_API_VERSION = var.app_info.salesforce_api_version +# S3_NOTIFY_PLATFORM_EVENT_NAME = "FailDARHTSCleanCreateObjectEvent__e" + S3_NOTIFY_PLATFORM_EVENT_NAME = "DARHTSCleanCreateObjectEvent__e" + # use DEBUG for debbuing, along with S3_MOVE_VERBOSE + #POWERTOOLS_LOG_LEVEL = "INFO" + POWERTOOLS_LOG_LEVEL = "DEBUG" + }, + var.lambda_environment_variables_override, + ) + + attach_policy_jsons = true + number_of_policy_jsons = 1 + policy_jsons = [ + data.aws_iam_policy_document.lambda_s3.json + ] + destination_on_failure = module.lambda_s3_failure.queue_arn + + # attach_policies = true + # policies = [ + # "arn:aws:iam::aws:policy/service-role/AWSLambdaSQSQueueExecutionRole", + # ] + # number_of_policies = 1 + + # allowed_triggers = { + # APIGatewayAny = { + # service = "apigateway" + # source_arn = "arn:aws:execute-api:eu-west-1:135367859851:aqnku8akd0/*/*/*" + # } + # } + + role_tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["role"], + ) + + tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["lambda"], + ) +} + +module "lambda_s3_alias" { + source = "terraform-aws-modules/lambda/aws//modules/alias" + + name = module.lambda_s3.lambda_function_name + function_name = module.lambda_s3.lambda_function_name + description = "DARHTS DAPPS S3 Notify to Salesforce API" + function_version = "$LATEST" +} + +data "aws_iam_policy_document" "lambda_s3" { + statement { + sid = "UseSSMSecret" + effect = "Allow" + actions = [ + "secretsmanager:DescribeSecret", + "secretsmanager:GetSecretValue", + "secretsmanager:GetResourcePolicy", + ] + resources = [ + aws_secretsmanager_secret.app_secret.arn, + aws_secretsmanager_secret_version.app_secret.arn, + ] + } + statement { + sid = "KMSAccessPolicy" + effect = "Allow" + actions = [ + "kms:Decrypt", + "kms:DescribeKey" + ] + resources = [aws_kms_key.app_secret.arn] + } + statement { + sid = "WriteToSQS" + effect = "Allow" + actions = ["sqs:SendMessage"] + resources = [module.lambda_s3_failure.queue_arn] + } +} + +module "lambda_s3_failure" { + source = "terraform-aws-modules/sqs/aws" + name = format("%v-s3-notify-failure", var.input_resource_label) + + sqs_managed_sse_enabled = true + create_queue_policy = true + queue_policy_statements = { + eventbridge = { + sid = "SNSPublish" + actions = ["sqs:SendMessage"] + principals = [ + { + type = "Service" + identifiers = ["events.amazonaws.com"] + } + ] + } + } + + tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["sqs"], + ) +} diff --git a/locals.tf b/locals.tf new file mode 100644 index 0000000..8560648 --- /dev/null +++ b/locals.tf @@ -0,0 +1,7 @@ +locals { + base_tags = { + "boc:created_by" = "terraform" + } + region_short = join("", [for c in split("-", local.region) : substr(c, 0, 1)]) + iam_arn = format("arn:%v:iam::%v:%%v", data.aws_arn.current.partition, data.aws_caller_identity.current.account_id) +} diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..a510f12 --- /dev/null +++ b/main.tf @@ -0,0 +1,112 @@ +module "files_in" { + source = "git@github.e.it.census.gov:terraform-modules/aws-s3.git//standard?ref=tf-upgrade" + + bucket_name = format("%v-in", var.input_resource_label) + access_log_bucket = data.aws_s3_bucket.log_bucket.id + bucket_key_enabled = true + use_kms_encryption = true + name_include_region = true + name_include_account = true + name_include_region_compact = true + name_enforce_region_compact = true + versioning = false + + tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["s3"], + ) +} + +module "files_clean" { + source = "git@github.e.it.census.gov:terraform-modules/aws-s3.git//standard?ref=tf-upgrade" + + bucket_name = format("%v-clean", var.input_resource_label) + access_log_bucket = data.aws_s3_bucket.log_bucket.id + bucket_key_enabled = true + use_kms_encryption = true + name_include_region = true + name_include_account = true + name_include_region_compact = true + name_enforce_region_compact = true + versioning = true + + tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["s3"], + ) +} + +resource "aws_s3_bucket_notification" "files_clean" { + bucket = module.files_clean.s3_bucket_id + eventbridge = true +} + +module "files_quarantine" { + source = "git@github.e.it.census.gov:terraform-modules/aws-s3.git//standard?ref=tf-upgrade" + + bucket_name = format("%v-quarantine", var.input_resource_label) + access_log_bucket = data.aws_s3_bucket.log_bucket.id + bucket_key_enabled = true + use_kms_encryption = true + name_include_region = true + name_include_account = true + name_include_region_compact = true + name_enforce_region_compact = true + versioning = false + + tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["s3"], + ) +} + +module "files_out_clean" { + source = "git@github.e.it.census.gov:terraform-modules/aws-s3.git//standard?ref=tf-upgrade" + + bucket_name = format("%v-clean", var.output_resource_label) + access_log_bucket = data.aws_s3_bucket.log_bucket.id + bucket_key_enabled = true + use_kms_encryption = true + name_include_region = true + name_include_account = true + name_include_region_compact = true + name_enforce_region_compact = true + versioning = true + + tags = merge( + local.base_tags, + var.tags, + local.output_finops_roles["s3"], + ) +} + +resource "aws_s3_bucket_notification" "files_out_clean" { + bucket = module.files_out_clean.s3_bucket_id + eventbridge = true +} + +data "aws_guardduty_detector" "main" {} + +resource "aws_guardduty_malware_protection_plan" "s3_malware" { + role = aws_iam_role.guardduty_malware_role.arn + + protected_resource { + s3_bucket { + bucket_name = module.files_in.s3_bucket_id + } + } + actions { + tagging { + status = "ENABLED" + } + } + + tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["guardduty"], + ) +} diff --git a/outputs.tf b/outputs.tf new file mode 100644 index 0000000..67169fc --- /dev/null +++ b/outputs.tf @@ -0,0 +1,39 @@ +# +# output "lambda_function_name" { +# value = aws_lambda_function.router.function_name +# } +# +# output "event_rule_arn" { +# value = aws_cloudwatch_event_rule.guardduty_event.arn +# } + +output "s3_bucket_files_in" { + description = "files-in bucket ARN and id" + value = { + id = module.files_in.s3_bucket_id + arn = module.files_in.s3_bucket_arn + } +} + +output "s3_bucket_files_quarantine" { + description = "files-quarantine bucket ARN and id" + value = { + id = module.files_quarantine.s3_bucket_id + arn = module.files_quarantine.s3_bucket_arn + } +} + +output "s3_bucket_files_clean" { + description = "files-clean bucket ARN and id" + value = { + id = module.files_clean.s3_bucket_id + arn = module.files_clean.s3_bucket_arn + } +} +output "s3_bucket_files_out_clean" { + description = "files-out-clean (from replication) bucket ARN and id" + value = { + id = module.files_out_clean.s3_bucket_id + arn = module.files_out_clean.s3_bucket_arn + } +} diff --git a/region.tf b/region.tf new file mode 100644 index 0000000..f617506 --- /dev/null +++ b/region.tf @@ -0,0 +1,3 @@ +locals { + region = var.region +} diff --git a/role.tf b/role.tf new file mode 100644 index 0000000..50a7e37 --- /dev/null +++ b/role.tf @@ -0,0 +1,383 @@ +resource "aws_iam_role" "guardduty_malware_role" { + name = format("%v%v-%v", try(module.base.prefixes.role, ""), "gd", trimprefix(module.files_in.s3_bucket_id, try(module.base.prefixes.s3, ""))) + assume_role_policy = data.aws_iam_policy_document.guardduty_malware_assume_role.json + path = "/service-role/" + + tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["guardduty"], + ) +} + +resource "aws_iam_role_policy" "guardduty_malware_policy" { + name = "guardduty_malware_policy" + role = aws_iam_role.guardduty_malware_role.id + policy = data.aws_iam_policy_document.guardduty_malware_access_policy.json +} + +#data "aws_iam_policy" "guardduty_malware_policy" { +# name = format("GuardDutyS3MalwareProtectionPolicy-%v-88686", "v-s3-malwarescanning-ri-files-in") +#} + +#data "aws_iam_role" "role" { +# name = "GuardDutyS3MalwareScanRole-53c66456-54d3-426f-ac04-1ce1eb60caac" +#} + +#resource "aws_iam_role_policy_attachment" "guardduty_malware_policy" { +# role = aws_iam_role.guardduty_malware_role.name +# policy_arn = data.aws_iam_policy.guardduty_malware_policy.arn +#} + +data "aws_iam_policy_document" "guardduty_malware_assume_role" { + statement { + sid = "GuardDutyMalwareProtectionForS3" + effect = "Allow" + principals { + type = "Service" + identifiers = ["malware-protection-plan.guardduty.amazonaws.com"] + } + actions = ["sts:AssumeRole"] + condition { + test = "StringEquals" + variable = "aws:SourceAccount" + values = [data.aws_caller_identity.current.account_id] + } + condition { + test = "ArnLike" + variable = "aws:SourceArn" + values = [format("arn:%v:guardduty:%v:%v:malware-protection-plan/*", data.aws_arn.current.partition, var.region, data.aws_caller_identity.current.account_id)] + } + } +} + + +## data "aws_iam_policy_document" "guardduty_malware_access_policy" { +## statement { +## sid = "AllowGuarddutyMalwareProtectionListBucket" +## effect = "Allow" +## actions = [ +## "s3:ListBucket", +## ] +## resources = [ +## module.files_in.s3_bucket_arn, +## ] +## } +## statement { +## sid = "AllowGuarddutyMalwareProtection" +## effect = "Allow" +## actions = [ +## "s3:GetObject", +## "s3:GetObjectVersion", +## "s3:GetBucketLocation", +## "s3:GetEncryptionConfiguration", +## "s3:GetObjectTagging", +## "s3:PutObjectTagging", +## "s3:TagResource", +## ] +## resources = [ +## module.files_in.s3_bucket_arn, +## format("%v/*", module.files_in.s3_bucket_arn), +## ] +## } +## statement { +## sid = "AllowGuarddutyMalwareProtectionEvents" +## effect = "Allow" +## actions = [ +## "events:PutEvents", +## ] +## resources = ["*"] +## } +## # statement { +## # effect = "Allow" +## # actions = [ +## # "kms:Decrypt" +## # ] +## # resources = ["*"] +## # } +## } + +# from: https://docs.aws.amazon.com/guardduty/latest/ug/malware-protection-s3-iam-policy-prerequisite.html + +data "aws_iam_policy_document" "guardduty_malware_access_policy_old" { + statement { + sid = "AllowManagedRuleToSendS3EventsToGuardDuty" + effect = "Allow" + actions = [ + "events:PutRule", + "events:DeleteRule", + "events:PutTargets", + "events:RemoveTargets", + ] + resources = [ + format("arn:%v:events:%v:%v:rule/DO-NOT-DELETE-AmazonGuardDutyMalwareProtectionS3*", data.aws_arn.current.partition, var.region, data.aws_caller_identity.current.account_id) + ] + condition { + test = "StringLike" + variable = "events:ManagedBy" + values = ["malware-protection-plan.guardduty.amazonaws.com"] + } + } + statement { + sid = "AllowGuardDutyToMonitorEventBridgeManagedRule" + effect = "Allow" + actions = [ + "events:DescribeRule", + "events:ListTargetsByRule", + ] + resources = [ + format("arn:%v:events:%v:%v:rule/DO-NOT-DELETE-AmazonGuardDutyMalwareProtectionS3*", data.aws_arn.current.partition, var.region, data.aws_caller_identity.current.account_id) + ] + } + statement { + sid = "AllowPostScanTag" + effect = "Allow" + actions = [ + "s3:PutObjectTagging", + "s3:GetObjectTagging", + "s3:PutObjectVersionTagging", + "s3:GetObjectVersionTagging", + ] + resources = [module.files_in.s3_bucket_arn] + } + statement { + sid = "AllowEnableS3EventBridgeEvents" + effect = "Allow" + actions = [ + "s3:PutBucketNotification", + "s3:GetBucketNotification", + ] + resources = [module.files_in.s3_bucket_arn] + } + statement { + sid = "AllowPutValidationObject" + effect = "Allow" + actions = [ + "s3:PutObject" + ] + resources = [ + format("%v/%v", module.files_in.s3_bucket_arn, "malware-protection-resource-validation-object") + ] + } + statement { + sid = "AllowCheckBucketOwnership" + effect = "Allow" + actions = [ + "s3:ListBucket" + ] + resources = [module.files_in.s3_bucket_arn] + } + statement { + sid = "AllowMalwareScan" + effect = "Allow" + actions = [ + "s3:GetObject", + "s3:GetObjectVersion", + ] + resources = [format("%v/*", module.files_in.s3_bucket_arn)] + } + # { + # sid = "AllowDecryptForMalwareScan", + # effect = "Allow", + # actions = [ + # "kms:GenerateDataKey", + # "kms:Decrypt" + # ] + # resources = "arn:aws:kms:us-east-1:111122223333:key/APKAEIBAERJR2EXAMPLE", + # "Condition": { + # "StringLike": { + # "kms:ViaService": "s3.us-east-1.amazonaws.com" + # } + # } + # } + # ] + # } + # +} + +data "aws_iam_policy_document" "guardduty_malware_access_policy" { + statement { + sid = "AllowManagedRuleToSendS3EventsToGuardDuty" + effect = "Allow" + resources = [ + format("arn:%v:events:%v:%v:rule/DO-NOT-DELETE-AmazonGuardDutyMalwareProtectionS3*", data.aws_arn.current.partition, var.region, data.aws_caller_identity.current.account_id) + ] + actions = ["events:PutRule"] + + condition { + test = "Null" + variable = "events:detail-type" + values = ["false"] + } + + condition { + test = "Null" + variable = "events:source" + values = ["false"] + } + + condition { + test = "StringEquals" + variable = "events:ManagedBy" + values = ["malware-protection-plan.guardduty.amazonaws.com"] + } + + condition { + test = "ForAllValues:StringEquals" + variable = "events:detail-type" + + values = [ + "Object Created", + "AWS API Call via CloudTrail", + ] + } + + condition { + test = "ForAllValues:StringEquals" + variable = "events:source" + values = ["aws.s3"] + } + } + + statement { + sid = "AllowUpdateTargetAndDeleteManagedRule" + effect = "Allow" + resources = [ + format("arn:%v:events:%v:%v:rule/DO-NOT-DELETE-AmazonGuardDutyMalwareProtectionS3*", data.aws_arn.current.partition, var.region, data.aws_caller_identity.current.account_id) + ] + actions = [ + "events:DeleteRule", + "events:PutTargets", + "events:RemoveTargets", + ] + + condition { + test = "StringEquals" + variable = "events:ManagedBy" + values = ["malware-protection-plan.guardduty.amazonaws.com"] + } + } + + statement { + sid = "AllowGuardDutyToMonitorEventBridgeManagedRule" + effect = "Allow" + resources = [ + format("arn:%v:events:%v:%v:rule/DO-NOT-DELETE-AmazonGuardDutyMalwareProtectionS3*", data.aws_arn.current.partition, var.region, data.aws_caller_identity.current.account_id) + ] + actions = [ + "events:DescribeRule", + "events:ListTargetsByRule", + ] + } + + statement { + sid = "AllowEnableS3EventBridgeEvents" + effect = "Allow" + resources = [module.files_in.s3_bucket_arn] + + actions = [ + "s3:PutBucketNotification", + "s3:GetBucketNotification", + ] + + condition { + test = "StringEquals" + variable = "aws:ResourceAccount" + values = [data.aws_caller_identity.current.account_id] + } + } + + statement { + sid = "AllowPostScanTag" + effect = "Allow" + resources = [format("%v/*", module.files_in.s3_bucket_arn)] + + actions = [ + "s3:GetObjectTagging", + "s3:GetObjectVersionTagging", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging", + ] + + condition { + test = "StringEquals" + variable = "aws:ResourceAccount" + values = [data.aws_caller_identity.current.account_id] + } + } + + statement { + sid = "AllowPutValidationObject" + effect = "Allow" + resources = [format("%v/%v", module.files_in.s3_bucket_arn, "malware-protection-resource-validation-object")] + actions = ["s3:PutObject"] + + condition { + test = "StringEquals" + variable = "aws:ResourceAccount" + values = [data.aws_caller_identity.current.account_id] + } + } + + statement { + sid = "AllowCheckBucketOwnership" + effect = "Allow" + resources = [module.files_in.s3_bucket_arn] + actions = ["s3:ListBucket"] + + condition { + test = "StringEquals" + variable = "aws:ResourceAccount" + values = [data.aws_caller_identity.current.account_id] + } + } + + statement { + sid = "AllowMalwareScan" + effect = "Allow" + resources = [format("%v/*", module.files_in.s3_bucket_arn)] + + actions = [ + "s3:GetObject", + "s3:GetObjectVersion", + ] + + condition { + test = "StringEquals" + variable = "aws:ResourceAccount" + values = [data.aws_caller_identity.current.account_id] + } + } + + statement { + sid = "AllowDecryptForMalwareScan" + effect = "Allow" + resources = [ + data.aws_kms_key.s3_key.arn, + module.files_in.kms_key_arn, + ] + + actions = [ + "kms:GenerateDataKey", + "kms:Decrypt", + ] + + condition { + test = "StringEquals" + variable = "kms:CallerAccount" + values = [data.aws_caller_identity.current.account_id] + } + + condition { + test = "StringEquals" + variable = "kms:ViaService" + values = [format("s3.%v.amazonaws.com", var.region)] + } + + condition { + test = "StringLike" + variable = "kms:EncryptionContext:aws:s3:arn" + values = [module.files_in.s3_bucket_arn] + } + } +} diff --git a/secret.tf b/secret.tf new file mode 100644 index 0000000..18c7ee9 --- /dev/null +++ b/secret.tf @@ -0,0 +1,96 @@ +data "aws_iam_role" "inf_cloud_admin" { + name = "r-inf-cloud-admin" +} + +resource "aws_secretsmanager_secret" "app_secret" { + name = var.secret_name + description = try(var.app_info.description, var.input_resource_label) + kms_key_id = aws_kms_key.app_secret.arn + policy = data.aws_iam_policy_document.app_secret.json + recovery_window_in_days = 0 + + tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["secret"], + { Name = var.app_info.secret_name }, + ) +} + +resource "aws_secretsmanager_secret_version" "app_secret" { + secret_id = aws_secretsmanager_secret.app_secret.id + secret_string = jsonencode({ + darhts_client_id = var.api_client_id + darhts_client_secret = var.api_client_secret + }) +} + +data "aws_iam_policy_document" "app_secret" { + statement { + sid = "UseSSMSecret" + effect = "Allow" + actions = [ + "secretsmanager:DescribeSecret", + "secretsmanager:GetSecretValue", + "secretsmanager:GetResourcePolicy", + ] + resources = ["*"] + principals { + type = "AWS" + # identifiers = ["*"] + identifiers = [ + data.aws_caller_identity.current.account_id, + ] + } + } +} + +data "aws_iam_policy_document" "app_secret_key" { + statement { + sid = "BuiltinKMSAdminRoles" + effect = "Allow" + actions = ["kms:*"] + resources = ["*"] + principals { + type = "AWS" + identifiers = flatten(concat([ + data.aws_iam_role.inf_cloud_admin.arn, + format(local.iam_arn, "root"), + ], + # [for p in var.sso_permissionset_names : [for f in local.sso_role_arn_formats : format(f, p)]], + )) + } + } + statement { + sid = "KMSAccessPolicy" + effect = "Allow" + actions = [ + "kms:Decrypt", + "kms:DescribeKey" + ] + resources = ["*"] + principals { + type = "AWS" + identifiers = [data.aws_caller_identity.current.account_id] + } + } +} + +resource "aws_kms_key" "app_secret" { + description = format("KMS CMK %v in %v", var.app_info.key_name, var.region) + enable_key_rotation = true + policy = data.aws_iam_policy_document.app_secret_key.json + multi_region = false + + tags = merge( + local.base_tags, + var.tags, + local.input_finops_roles["kms"], + { Name = format("v-kms-%v", var.app_info.key_name) }, + ) +} + +resource "aws_kms_alias" "app_secret" { + name = format("alias/v-kms-%v", var.app_info.key_name) + target_key_id = aws_kms_key.app_secret.key_id +} diff --git a/tags.tf b/tags.tf new file mode 100644 index 0000000..732f60b --- /dev/null +++ b/tags.tf @@ -0,0 +1,15 @@ +locals { + tag_roles = [ + "event", + "guardduty", + "kms ", + "lambda", + "log", + "role", + "s3", + "sqs", + "secret", + ] + input_finops_roles = {for r in local.tag_roles: r => { "finops_project_role" = trimprefix(format("%v_%v",lookup(var.input_resource_tags,"finops_project_name",""),"_")) } } + output_finops_roles = {for r in local.tag_roles: r => { "finops_project_role" = trimprefix(format("%v_%v",lookup(var.output_resource_tags,"finops_project_name",""),"_")) } } +} diff --git a/variables.api.tf b/variables.api.tf new file mode 100644 index 0000000..4a4203c --- /dev/null +++ b/variables.api.tf @@ -0,0 +1,11 @@ +variable "api_client_id" { + description = "Salesforce API Client Id" + sensitive = true + type = string +} + +variable "api_client_secret" { + description = "Salesforce API Client Secreet" + sensitive = true + type = string +} diff --git a/variables.lambda.tf b/variables.lambda.tf new file mode 100644 index 0000000..4e17670 --- /dev/null +++ b/variables.lambda.tf @@ -0,0 +1,33 @@ +variable "lambda_timeout" { + description = "Lambda Timeout in seconds" + type = number + default = 300 +} + +variable "lambda_memory_size" { + description = "Memory size (minimum 128M)" + type = number + default = 128 + + validation { + condition = var.lambda_memory_size >= 128 && var.lambda_memory_size <= 10240 + error_message = "Lambda memory size must be between 128 MB and 10240 MB (10G), inclusive." + } +} + +variable "lambda_ephemeral_storage_size" { + description = "Lambda emphemral storage size (minimum 512M)" + type = number + default = 512 + + validation { + condition = var.lambda_ephemeral_storage_size >= 512 && var.lambda_ephemeral_storage_size <= 10240 + error_message = "Lambda ephemeral storage size must be between 512 MB and 10240 MB (10G), inclusive. " + } +} + +variable "python_runtime" { + description = "Python runtime version" + type = string + default = "3.12" +} diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..a8b8388 --- /dev/null +++ b/variables.tf @@ -0,0 +1,79 @@ +variable "log_retention_in_days" { + description = "Number of days to keep cloudwatch logs (default is 30). See the documentation for available values." + type = number + default = 30 + + # valid and allowed here: 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180 + # valid but excluded here: 365, 400, 545, 731, 1827, 2192, 2557, 2922, 3288, 3653 + validation { + condition = var.log_retention_in_days > 0 && var.log_retention_in_days <= 180 && contains([1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180], var.log_retention_in_days) + error_message = "Cloudwatch logs retention must not be 0 (infinite), and be between 1 and 180 days (30 is default)." + } +} + +# variable "app_name" { +# description = "NTS Base Label format {org}-{app}-{env} for application" +# type = string +# } + +variable "app_info" { + description = "Structure with organization, name, environment" + type = map(string) +} + +# settings = { +# secret_name = "infoblox" +# secret_name_format = "/enterprise/terraform/providers/%v" +# description = "Infoblox provider settings" +# username = "apiadmin" +# hostname = "bcc-inf-gm.console.tco.census.gov" +# port = 443 +# api_version = "2.9" +# ssl_mode = true +# } +# +# + +variable "environment_label" { + description = "Label to be used as the environment" + type = string +} + +variable "input_resource_label" { + description = "Label to be used on input components (in, clean, quarantine and related resources)" + type = string +} + +variable "output_resource_label" { + description = "Label to be used on output components (replication clean and related resources)" + type = string +} + +variable "secret_name" { + description = "Label to be used for the AWS Secret for the Salesforce API" + type = string +} + +variable "dlq_retry_attempts" { + description = "Dead Letter Queue maxium_retry_attempts (default: 3)" + type = number + default = 3 +} + +variable "dlq_event_age" { + description = "Dead Letter Queue maximum_event_age_in_seconds (default: 3600)" + type = number + default = 3600 +} + +variable "input_resource_tags" { + description = "AWS Tags to apply to input resources (should include finops_ tags)" + type = map(string) + default = {} +} + +variable "output_resource_tags" { + description = "AWS Tags to apply to output resources (should include finops_ tags)" + type = map(string) + default = {} +} diff --git a/version.tf b/version.tf index a0cd862..a7eaa37 100644 --- a/version.tf +++ b/version.tf @@ -1,3 +1,4 @@ locals { - _module_version = "0.0.0" + _module_name = "aws-app-ditd-darhts-s3-transfer" + _module_version = "0.9.0" } diff --git a/versions.tf b/versions.tf index 4ba10ce..27445c5 100644 --- a/versions.tf +++ b/versions.tf @@ -2,8 +2,32 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 3.66.0" + version = ">= 5.0" } + # ldap = { + # source = "trevex/ldap" + # version = ">= 0.5.4" + # } + # external = { + # source = "hashicorp/external" + # version = ">= 1.0" + # } + # null = { + # source = "hashicorp/null" + # version = ">= 1.0" + # } + # random = { + # source = "hashicorp/random" + # version = ">= 1.0" + # } + # template = { + # source = "hashicorp/template" + # version = ">= 1.0" + # } + # infoblox = { + # source = "infobloxopen/infoblox" + # version = ">= 2.1.0" + # } } -# required_version = ">= 0.13" + required_version = ">= 1.0.0" }