From 2f0f549e456cb89ee6f8f22c546ea84ffa80b582 Mon Sep 17 00:00:00 2001 From: Benjamin Edwards Date: Fri, 3 Feb 2023 13:00:31 -0500 Subject: [PATCH] cross-account firehose destinations via module (#9528) initial support for cross-account firehose destinations --- .../.header.md | 6 + .../firehose/.terraform-docs.yml | 1 + .../firehose/README.md | 51 +++++ .../firehose/main.tf | 104 ++++++++++ .../firehose/outputs.tf | 19 ++ .../firehose/variables.tf | 42 ++++ .../firehose/version.tf | 10 + .../target-account/README.md | 46 +++++ .../target-account/main.tf | 179 ++++++++++++++++++ .../target-account/outputs.tf | 11 ++ .../target-account/variables.tf | 14 ++ .../target-account/version.tf | 10 + 12 files changed, 493 insertions(+) create mode 100644 terraform/addons/byo-s3-logging-destination-firehose/.header.md create mode 100644 terraform/addons/byo-s3-logging-destination-firehose/firehose/.terraform-docs.yml create mode 100644 terraform/addons/byo-s3-logging-destination-firehose/firehose/README.md create mode 100644 terraform/addons/byo-s3-logging-destination-firehose/firehose/main.tf create mode 100644 terraform/addons/byo-s3-logging-destination-firehose/firehose/outputs.tf create mode 100644 terraform/addons/byo-s3-logging-destination-firehose/firehose/variables.tf create mode 100644 terraform/addons/byo-s3-logging-destination-firehose/firehose/version.tf create mode 100644 terraform/addons/byo-s3-logging-destination-firehose/target-account/README.md create mode 100644 terraform/addons/byo-s3-logging-destination-firehose/target-account/main.tf create mode 100644 terraform/addons/byo-s3-logging-destination-firehose/target-account/outputs.tf create mode 100644 terraform/addons/byo-s3-logging-destination-firehose/target-account/variables.tf create mode 100644 terraform/addons/byo-s3-logging-destination-firehose/target-account/version.tf diff --git a/terraform/addons/byo-s3-logging-destination-firehose/.header.md b/terraform/addons/byo-s3-logging-destination-firehose/.header.md new file mode 100644 index 0000000000..cd409454e3 --- /dev/null +++ b/terraform/addons/byo-s3-logging-destination-firehose/.header.md @@ -0,0 +1,6 @@ +# Logging Destination: Firehose +This addon provides a Kinesis Firehose logging destination for Fleet. + +First apply the `target-account` module which will provision the necessary bucket, KMS key, and policies. + +Then apply the `firehose` module with the required variables. diff --git a/terraform/addons/byo-s3-logging-destination-firehose/firehose/.terraform-docs.yml b/terraform/addons/byo-s3-logging-destination-firehose/firehose/.terraform-docs.yml new file mode 100644 index 0000000000..1d139ddb40 --- /dev/null +++ b/terraform/addons/byo-s3-logging-destination-firehose/firehose/.terraform-docs.yml @@ -0,0 +1 @@ +header-from: .header.md diff --git a/terraform/addons/byo-s3-logging-destination-firehose/firehose/README.md b/terraform/addons/byo-s3-logging-destination-firehose/firehose/README.md new file mode 100644 index 0000000000..8270139f8a --- /dev/null +++ b/terraform/addons/byo-s3-logging-destination-firehose/firehose/README.md @@ -0,0 +1,51 @@ +# Logging Destination: Firehose +This addon provides a Kinesis Firehose logging destination for Fleet with support for cross account S3 delivery. + +## Requirements + +Apply module `target-account` to provision destination bucket, kms key, and IAM policies. + +## Providers + +| Name | Version | +|---------------------------------------------------|---------| +| [aws](#provider\_aws) | 4.49.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------| +| [aws_iam_policy.firehose-results](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.firehose-status](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_role.firehose-results](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role.firehose-status](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.firehose-results](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.firehose-status](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_kinesis_firehose_delivery_stream.osquery_results](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kinesis_firehose_delivery_stream) | resource | +| [aws_kinesis_firehose_delivery_stream.osquery_status](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kinesis_firehose_delivery_stream) | resource | +| [aws_iam_policy_document.osquery_firehose_assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.osquery_results_policy_doc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.osquery_status_policy_doc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|-------------------------------|----------------------------------------|----------|---------------------|:--------:| +| firehose_results_name | n/a | `string` | no default provided | yes | +| firehose_status_name | n/a | `string` | no default provided | yes | +| customer_prefix | used for resource tagging | `string` | no default provided | yes | +| kms_key_arn | key arn used to encrypt target buckets | `string` | no default provided | yes | +| results_destination_s3_bucket | bucket name to send osquery results | `string` | no default provided | yes | +| status_destination_s3_bucket | bucket name to send osquery status | `string` | no default provided | yes | + + +## Outputs + +| Name | Description | +|-----------------------------------------------------------------------------------------------------------------|-------------| +| [fleet-extra-env-variables](#output\_fleet-extra-env-variables) | n/a | diff --git a/terraform/addons/byo-s3-logging-destination-firehose/firehose/main.tf b/terraform/addons/byo-s3-logging-destination-firehose/firehose/main.tf new file mode 100644 index 0000000000..59986d18ac --- /dev/null +++ b/terraform/addons/byo-s3-logging-destination-firehose/firehose/main.tf @@ -0,0 +1,104 @@ +data "aws_region" "current" {} +data "aws_caller_identity" "current" {} + +data "aws_iam_policy_document" "osquery_firehose_assume_role" { + statement { + effect = "Allow" + actions = ["sts:AssumeRole"] + principals { + identifiers = ["firehose.amazonaws.com"] + type = "Service" + } + } +} + +data "aws_iam_policy_document" "firehose_policy" { + statement { + effect = "Allow" + actions = [ + "s3:AbortMultipartUpload", + "s3:GetBucketLocation", + "s3:GetObject", + "s3:ListBucket", + "s3:ListBucketMultipartUploads", + "s3:PutObject", + "s3:PutObjectAcl" // required according to https://docs.aws.amazon.com/firehose/latest/dev/controlling-access.html#using-iam-s3 + ] + resources = [ + "arn:aws:s3:::${var.results_destination_s3_bucket}", + "arn:aws:s3:::${var.results_destination_s3_bucket}/*", + "arn:aws:s3:::${var.status_destination_s3_bucket}", + "arn:aws:s3:::${var.status_destination_s3_bucket}/*" + ] + } + + statement { + effect = "Allow" + actions = ["kms:GenerateDataKey*"] + resources = [var.kms_key_arn] + } + + statement { + effect = "Allow" + actions = ["logs:PutLogEvents"] + resources = [ + "arn:aws:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:log-group:/aws/kinesisfirehose/${var.firehose_results_name}:*", + "arn:aws:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:log-group:/aws/kinesisfirehose/${var.firehose_status_name}:*" + ] + } +} + +resource "aws_iam_role" "firehose" { + name = "${var.customer_prefix}-firehose-cross-account-role" + assume_role_policy = data.aws_iam_policy_document.osquery_firehose_assume_role.json +} + +resource "aws_iam_policy" "firehose" { + policy = data.aws_iam_policy_document.firehose_policy.json +} + +resource "aws_iam_role_policy_attachment" "firehose" { + policy_arn = aws_iam_policy.firehose.arn + role = aws_iam_role.firehose.name +} + +resource "aws_kinesis_firehose_delivery_stream" "osquery_results" { + name = var.firehose_results_name + destination = "s3" + + s3_configuration { + prefix = var.results_object_prefix + role_arn = aws_iam_role.firehose.arn + bucket_arn = "arn:aws:s3:::${var.results_destination_s3_bucket}" + kms_key_arn = var.kms_key_arn + } +} + +resource "aws_kinesis_firehose_delivery_stream" "osquery_status" { + name = var.firehose_status_name + destination = "s3" + + s3_configuration { + prefix = var.status_object_prefix + role_arn = aws_iam_role.firehose + bucket_arn = "arn:aws:s3:::${var.status_destination_s3_bucket}" + kms_key_arn = var.kms_key_arn + } +} + +data "aws_iam_policy_document" "firehose-logging" { + statement { + actions = [ + "firehose:DescribeDeliveryStream", + "firehose:PutRecord", + "firehose:PutRecordBatch", + ] + resources = [aws_kinesis_firehose_delivery_stream.osquery_results.arn, aws_kinesis_firehose_delivery_stream.osquery_status.arn] + } +} + +resource "aws_iam_policy" "firehose-logging" { + name = "fleet-firehose-logging" + description = "An IAM policy for fleet to log to Firehose destinations" + policy = data.aws_iam_policy_document.firehose-logging.json +} diff --git a/terraform/addons/byo-s3-logging-destination-firehose/firehose/outputs.tf b/terraform/addons/byo-s3-logging-destination-firehose/firehose/outputs.tf new file mode 100644 index 0000000000..0aea5c0816 --- /dev/null +++ b/terraform/addons/byo-s3-logging-destination-firehose/firehose/outputs.tf @@ -0,0 +1,19 @@ +output "fleet_extra_environment_variables" { + value = { + FLEET_FIREHOSE_STATUS_STREAM = aws_kinesis_firehose_delivery_stream.osquery_status.name + FLEET_FIREHOSE_RESULT_STREAM = aws_kinesis_firehose_delivery_stream.osquery_results.name + FLEET_FIREHOSE_REGION = data.aws_region.current.name + FLEET_OSQUERY_STATUS_LOG_PLUGIN = "firehose" + FLEET_OSQUERY_RESULT_LOG_PLUGIN = "firehose" + } +} + +output "fleet_extra_iam_policies" { + value = [ + aws_iam_policy.firehose-logging.arn + ] +} + +output "firehose_role_arn" { + value = aws_iam_role.firehose.arn +} diff --git a/terraform/addons/byo-s3-logging-destination-firehose/firehose/variables.tf b/terraform/addons/byo-s3-logging-destination-firehose/firehose/variables.tf new file mode 100644 index 0000000000..eafe6daff0 --- /dev/null +++ b/terraform/addons/byo-s3-logging-destination-firehose/firehose/variables.tf @@ -0,0 +1,42 @@ +variable "results_destination_s3_bucket" { + type = string + description = "s3 bucket name for osquery results" +} + +variable "status_destination_s3_bucket" { + type = string + description = "s3 bucket name for osquery status" +} + +variable "kms_key_arn" { + type = string + description = "kms key arn used to encrypt destination buckets" + default = "arn:aws:kms:us-east-2:123456789123:key/fix-me" +} + +variable "firehose_results_name" { + type = string + description = "name of the firehose delivery stream for osquery results logs" +} + +variable "firehose_status_name" { + type = string + description = "name of the firehose delivery stream for osquery status logs" +} + +variable "customer_prefix" { + type = string + description = "customer prefix to use to namespace all resources" +} + +variable "results_object_prefix" { + type = string + description = "object prefix for results logs e.g. 'results/'" + default = "results/" +} + +variable "status_object_prefix" { + type = string + description = "object prefix for results logs e.g. 'status/'" + default = "status/" +} \ No newline at end of file diff --git a/terraform/addons/byo-s3-logging-destination-firehose/firehose/version.tf b/terraform/addons/byo-s3-logging-destination-firehose/firehose/version.tf new file mode 100644 index 0000000000..00143c571d --- /dev/null +++ b/terraform/addons/byo-s3-logging-destination-firehose/firehose/version.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.3.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.52.0" + } + } +} \ No newline at end of file diff --git a/terraform/addons/byo-s3-logging-destination-firehose/target-account/README.md b/terraform/addons/byo-s3-logging-destination-firehose/target-account/README.md new file mode 100644 index 0000000000..2ff22be984 --- /dev/null +++ b/terraform/addons/byo-s3-logging-destination-firehose/target-account/README.md @@ -0,0 +1,46 @@ +# Logging Destination: S3 +This module will provision necessary resources to feed osquery results/status logs into S3. + +## Requirements + +None + +## Providers + +| Name | Version | +|---------------------------------------------------|---------| +| [aws](#provider\_aws) | 4.52.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------| +| [aws_s3_bucket.osquery-results](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | +| [aws_s3_bucket.osquery-status](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | +| [aws_s3_bucket_acl.osquery-results](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource | +| [aws_s3_bucket_acl.osquery-status](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource | +| [aws_s3_bucket_public_access_block.osquery-results](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | +| [aws_s3_bucket_public_access_block.osquery-status](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | +| [aws_s3_bucket_server_side_encryption_configuration.osquery-results](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | +| [aws_s3_bucket_server_side_encryption_configuration.osquery-status](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | +| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------------------------|----------------------------------------|----------|---------------------|:--------:| +| osquery_results_bucket | name of the bucket for results logging | `string` | no default provided | yes | +| osquery_status_bucket | name of the bucket for status logging | `string` | no default provided | yes | +| fleet_iam_role_arn | the role ARN from Fleet Cloud | `string` | no default provided | yes | + +## Outputs + +| Name | Description | +|---------------------|-------------| +| kms_key_arn | n/a | +| results_bucket_name | n/a | +| status_bucket_name | n/a | diff --git a/terraform/addons/byo-s3-logging-destination-firehose/target-account/main.tf b/terraform/addons/byo-s3-logging-destination-firehose/target-account/main.tf new file mode 100644 index 0000000000..4ee98db0bf --- /dev/null +++ b/terraform/addons/byo-s3-logging-destination-firehose/target-account/main.tf @@ -0,0 +1,179 @@ +data "aws_caller_identity" "current" {} + +data "aws_iam_policy_document" "results" { + statement { + principals { + identifiers = [var.fleet_iam_role_arn] + type = "AWS" + } + effect = "Allow" + actions = [ + "s3:AbortMultipartUpload", + "s3:GetBucketLocation", + "s3:GetObject", + "s3:ListBucket", + "s3:ListBucketMultipartUploads", + "s3:PutObject", + "s3:PutObjectAcl" // required according to https://docs.aws.amazon.com/firehose/latest/dev/controlling-access.html#using-iam-s3 + ] + resources = [ + aws_s3_bucket.osquery-results.arn, + "${aws_s3_bucket.osquery-results.arn}/*" + ] + } + + statement { + principals { + identifiers = [var.fleet_iam_role_arn] + type = "AWS" + } + effect = "Allow" + actions = ["s3:PutObject"] + resources = ["${aws_s3_bucket.osquery-results.arn}/*"] + condition { + test = "StringEquals" + values = ["bucket-owner-full-control"] + variable = "s3:x-amz-acl" + } + } +} + +data "aws_iam_policy_document" "status" { + statement { + principals { + identifiers = [var.fleet_iam_role_arn] + type = "AWS" + } + effect = "Allow" + actions = [ + "s3:AbortMultipartUpload", + "s3:GetBucketLocation", + "s3:GetObject", + "s3:ListBucket", + "s3:ListBucketMultipartUploads", + "s3:PutObject", + "s3:PutObjectAcl" // required according to https://docs.aws.amazon.com/firehose/latest/dev/controlling-access.html#using-iam-s3 + ] + resources = [ + aws_s3_bucket.osquery-status.arn, + "${aws_s3_bucket.osquery-status.arn}/*" + ] + } + + statement { + principals { + identifiers = [var.fleet_iam_role_arn] + type = "AWS" + } + effect = "Allow" + actions = ["s3:PutObject"] + resources = ["${aws_s3_bucket.osquery-status.arn}/*"] + condition { + test = "StringEquals" + values = ["bucket-owner-full-control"] + variable = "s3:x-amz-acl" + } + } +} + +resource "aws_s3_bucket" "osquery-results" { + bucket = var.osquery_results_bucket +} + +resource "aws_s3_bucket" "osquery-status" { + bucket = var.osquery_status_bucket +} + +resource "aws_s3_bucket_policy" "results" { + bucket = aws_s3_bucket.osquery-results.id + policy = data.aws_iam_policy_document.results.json +} + +resource "aws_s3_bucket_policy" "status" { + bucket = aws_s3_bucket.osquery-status.id + policy = data.aws_iam_policy_document.status.json +} + +resource "aws_s3_bucket_public_access_block" "results" { + bucket = aws_s3_bucket.osquery-results.id + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + +resource "aws_s3_bucket_public_access_block" "status" { + bucket = aws_s3_bucket.osquery-status.id + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + + +resource "aws_s3_bucket_acl" "results" { + bucket = aws_s3_bucket.osquery-results.id + acl = "private" +} + +resource "aws_s3_bucket_acl" "status" { + bucket = aws_s3_bucket.osquery-status.id + acl = "private" +} + +data "aws_iam_policy_document" "key_policy" { + // self account has access to key + statement { + principals { + identifiers = [ + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" + ] + type = "AWS" + } + effect = "Allow" + actions = ["*"] + resources = ["*"] + } + + // only allow the IAM role from fleet aws account + statement { + principals { + identifiers = [var.fleet_iam_role_arn] + type = "AWS" + } + effect = "Allow" + actions = ["kms:GenerateDataKey*"] + resources = ["*"] // this is basically "self" aka this particular key + } +} + +// customer managed key to allow other aws account access +resource "aws_kms_key" "key" { + enable_key_rotation = true + policy = data.aws_iam_policy_document.key_policy.json + description = "key used for osquery results and status bucket encryption" +} + +// enable server side encryption with KMS key +resource "aws_s3_bucket_server_side_encryption_configuration" "results" { + bucket = aws_s3_bucket.osquery-results.id + rule { + bucket_key_enabled = true + apply_server_side_encryption_by_default { + kms_master_key_id = aws_kms_key.key.id + sse_algorithm = "aws:kms" + } + } +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "status" { + bucket = aws_s3_bucket.osquery-status.id + rule { + bucket_key_enabled = true + apply_server_side_encryption_by_default { + kms_master_key_id = aws_kms_key.key.id + sse_algorithm = "aws:kms" + } + } +} + diff --git a/terraform/addons/byo-s3-logging-destination-firehose/target-account/outputs.tf b/terraform/addons/byo-s3-logging-destination-firehose/target-account/outputs.tf new file mode 100644 index 0000000000..69f21d29fd --- /dev/null +++ b/terraform/addons/byo-s3-logging-destination-firehose/target-account/outputs.tf @@ -0,0 +1,11 @@ +output "kms_key_arn" { + value = aws_kms_key.key.arn +} + +output "results_bucket_name" { + value = aws_s3_bucket.osquery-results.id +} + +output "status_bucket_name" { + value = aws_s3_bucket.osquery-status.id +} \ No newline at end of file diff --git a/terraform/addons/byo-s3-logging-destination-firehose/target-account/variables.tf b/terraform/addons/byo-s3-logging-destination-firehose/target-account/variables.tf new file mode 100644 index 0000000000..ac68078ad1 --- /dev/null +++ b/terraform/addons/byo-s3-logging-destination-firehose/target-account/variables.tf @@ -0,0 +1,14 @@ +variable "osquery_results_bucket" { + type = string + description = "name of the bucket to store osquery results logs" +} + +variable "osquery_status_bucket" { + type = string + description = "name of the bucket to store osquery status logs" +} + +variable "fleet_iam_role_arn" { + type = string + description = "the arn of the fleet role that firehose will assume to write data to your bucket" +} \ No newline at end of file diff --git a/terraform/addons/byo-s3-logging-destination-firehose/target-account/version.tf b/terraform/addons/byo-s3-logging-destination-firehose/target-account/version.tf new file mode 100644 index 0000000000..00143c571d --- /dev/null +++ b/terraform/addons/byo-s3-logging-destination-firehose/target-account/version.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.3.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.52.0" + } + } +} \ No newline at end of file