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" }