resource "aws_cloudwatch_log_group" "sh_firehose_logs" { name = local.sh_firehose_log_group retention_in_days = var.firehose_logs_retention_days tags = var.custom_tags } resource "aws_cloudwatch_log_stream" "sh_firehose_logs_stream" { name = "findings-stream" log_group_name = aws_cloudwatch_log_group.sh_firehose_logs.name } # define who can assume the role data "aws_iam_policy_document" "sh_firehose_role_trust_policy" { statement { effect = "Allow" actions = ["sts:AssumeRole"] principals { type = "Service" identifiers = [ "firehose.amazonaws.com", ] } } } # define what the role can do # allow to write in S3 # allow to write firehose errors in Cloudwtach logs data "aws_iam_policy_document" "sh_firehose_role_S3_policy" { statement { effect = "Allow" actions = [ "s3:AbortMultipartUpload", "s3:GetBucketLocation", "s3:ListBucket", "s3:ListBucketMultipartUploads", ] resources = [ module.reporting_bucket.s3_arn, "${module.reporting_bucket.s3_arn}/*", ] } statement { effect = "Allow" actions = [ "s3:GetObject", "s3:PutObject", ] resources = [ "${module.reporting_bucket.s3_arn}/${local.s3_raw_ingestion_path_sh}*" ] } statement { effect = "Allow" actions = [ "logs:PutLogEvents", ] resources = [ aws_cloudwatch_log_group.sh_firehose_logs.arn ] } } # define what the role can do # allow to read in AWS Glue the datamodel maping (JSON/Parquet => Athena syntetic DB) data "aws_iam_policy_document" "sh_firehose_role_glue_policy" { statement { effect = "Allow" actions = [ "glue:GetTableVersions", ] resources = [ "*", ] } } # role for firehose to be able to write in S3 resource "aws_iam_role" "sh_firehose_role" { depends_on = [ aws_cloudwatch_log_group.sh_firehose_logs ] name = "${var.name_prefix}reporting-firehose-role" tags = var.custom_tags assume_role_policy = data.aws_iam_policy_document.sh_firehose_role_trust_policy.json inline_policy { name = "AllowWriteS3PCRReporting" policy = data.aws_iam_policy_document.sh_firehose_role_S3_policy.json } inline_policy { name = "AllowReadGluePCRReporting" policy = data.aws_iam_policy_document.sh_firehose_role_glue_policy.json } } resource "aws_kinesis_firehose_delivery_stream" "sh_reporting_to_s3" { depends_on = [ aws_glue_catalog_table.securityhub_findings_events_table ] name = "${var.name_prefix}reporting-s3-events-ingestion" tags = var.custom_tags destination = "extended_s3" extended_s3_configuration { role_arn = aws_iam_role.sh_firehose_role.arn bucket_arn = module.reporting_bucket.s3_arn buffer_size = "128" # MB buffer_interval = "300" # Seconds (minimum 60) prefix = "${local.s3_raw_ingestion_path_sh}year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/" error_output_prefix = "${local.s3_raw_ingestion_path_sh}firehose-error/!{firehose:error-output-type}/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/" cloudwatch_logging_options { enabled = true log_group_name = local.sh_firehose_log_group log_stream_name = aws_cloudwatch_log_stream.sh_firehose_logs_stream.name } data_format_conversion_configuration { # this part convert the JSON in parquet format, put it in comment if you want only JSON input_format_configuration { deserializer { open_x_json_ser_de {} } } output_format_configuration { serializer { parquet_ser_de { compression = "SNAPPY" } } } schema_configuration { role_arn = aws_iam_role.sh_firehose_role.arn database_name = aws_glue_catalog_table.securityhub_findings_events_table.database_name table_name = aws_glue_catalog_table.securityhub_findings_events_table.name } enabled = true } } } # define who can assume the role data "aws_iam_policy_document" "sh_event_rule_role_trust_policy" { statement { effect = "Allow" actions = ["sts:AssumeRole"] principals { type = "Service" identifiers = [ "events.amazonaws.com", ] } } } # define what the role can do data "aws_iam_policy_document" "sh_event_rule_role_policy" { statement { effect = "Allow" actions = [ "firehose:PutRecord", "firehose:PutRecordBatch", ] resources = [ aws_kinesis_firehose_delivery_stream.sh_reporting_to_s3.arn, ] } } # Role for Event-bridge to be able to write to firehose resource "aws_iam_role" "sh_event_rule_role" { name = "${var.name_prefix}reporting-event-rule-role" tags = var.custom_tags assume_role_policy = data.aws_iam_policy_document.sh_event_rule_role_trust_policy.json inline_policy { name = "AllowWriteFirehosePCRReporting" policy = data.aws_iam_policy_document.sh_event_rule_role_policy.json } } # lets not use the default event-bus, to avoid mixing local event with aggregated events from all member accounts and other regions data "aws_iam_policy_document" "sh_event_bus_policy" { statement { sid = "DevAccountAccess" effect = "Allow" actions = [ "events:PutEvents", ] resources = [ aws_cloudwatch_event_bus.sh_event_bus.arn ] principals { type = "AWS" identifiers = var.allowed_member_accounts } } } resource "aws_cloudwatch_event_bus_policy" "sh_event_bus_policy" { policy = data.aws_iam_policy_document.sh_event_bus_policy.json event_bus_name = aws_cloudwatch_event_bus.sh_event_bus.name } resource "aws_cloudwatch_event_bus" "sh_event_bus" { name = "${var.name_prefix}reporting-events-from-member-accounts" tags = var.custom_tags } resource "aws_cloudwatch_event_rule" "sh_events_rule" { tags = var.custom_tags name = "${var.name_prefix}reporting-security-hub-findings-events" description = "Capture Security-Hub finding imported (= new or update) events" event_bus_name = aws_cloudwatch_event_bus.sh_event_bus.name event_pattern = <<-EOF { "source": ["aws.securityhub"], "detail-type": ["Security Hub Findings - Imported"] } EOF } resource "aws_cloudwatch_event_target" "sh_event_to_firehose" { event_bus_name = aws_cloudwatch_event_rule.sh_events_rule.event_bus_name rule = aws_cloudwatch_event_rule.sh_events_rule.name arn = aws_kinesis_firehose_delivery_stream.sh_reporting_to_s3.arn role_arn = aws_iam_role.sh_event_rule_role.arn }