diff --git a/README.md b/README.md index 846c50e..8311388 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,36 @@ module "cis_alarms" { AWS CloudTrail normally publishes logs into AWS CloudWatch Logs. This module creates log metric filters together with metric alarms according to [CIS AWS Foundations Benchmark v1.4.0 (05-28-2021)](https://www.cisecurity.org/benchmark/amazon_web_services/). Read more about [CIS AWS Foundations Controls](https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-cis-controls.html). +### Log Group Data Protection Policy + +```hcl +module "log_group_data_protection" { + source = "terraform-aws-modules/cloudwatch/aws//modules/log-data-protection-policy" + version = "~> 4.0" + + log_group_name = "my-log-group" + create_log_data_protection_policy = true + log_data_protection_policy_name = "RedactAddress" + + data_identifiers = ["arn:aws:dataprotection::aws:data-identifier/Address"] + findings_destination_cloudwatch_log_group = "audit-log-group" +} +``` + +### Log Subscription Filter + +```hcl +module "log_subscription_filter" { + source = "terraform-aws-modules/cloudwatch/aws//modules/log-subscription-filter" + + name = "my-filter" + destination_arn = "arn:aws:firehose:eu-west-1:835367859852:deliverystream/cw-logs" + filter_pattern = "%test%" + log_group_name = "my-log-group" + role_arn = "arn:aws:iam::835367859852:role/cw-logs-to-firehose" +} +``` + ### Metric Stream ```hcl @@ -234,6 +264,8 @@ module "log_account_policy" { - [Cloudwatch query definition](https://github.com/terraform-aws-modules/terraform-aws-cloudwatch/tree/master/examples/query-definition) - [Cloudwatch Metric Stream](https://github.com/terraform-aws-modules/terraform-aws-cloudwatch/tree/master/examples/metric-stream) - [Cloudwatch Composite Alarm](https://github.com/terraform-aws-modules/terraform-aws-cloudwatch/tree/master/examples/composite-alarm) +- [Cloudwatch log subscription filter](https://github.com/terraform-aws-modules/terraform-aws-cloudwatch/tree/master/examples/log-subscription-filter) +- [Cloudwatch log data protection policy](https://github.com/terraform-aws-modules/terraform-aws-cloudwatch/tree/master/examples/log-group-with-data-protection-policy) - [Cloudwatch Log Account Policy](https://github.com/terraform-aws-modules/terraform-aws-cloudwatch/tree/master/examples/log-account-policy) diff --git a/examples/log-group-with-data-protection-policy/README.md b/examples/log-group-with-data-protection-policy/README.md new file mode 100644 index 0000000..6417fbe --- /dev/null +++ b/examples/log-group-with-data-protection-policy/README.md @@ -0,0 +1,39 @@ +# Complete Cloudwatch log group and data protection policy + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 5.0 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [audit\_destination\_group](#module\_audit\_destination\_group) | ../../modules/log-group | n/a | +| [custom\_data\_protection\_policy\_log\_group](#module\_custom\_data\_protection\_policy\_log\_group) | ../../modules/log-group | n/a | +| [custom\_log\_data\_protection\_policy](#module\_custom\_log\_data\_protection\_policy) | ../../modules/log-data-protection-policy | n/a | +| [log\_data\_protection\_policy](#module\_log\_data\_protection\_policy) | ../../modules/log-data-protection-policy | n/a | +| [log\_group](#module\_log\_group) | ../../modules/log-group | n/a | + +## Resources + +No resources. + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [cloudwatch\_log\_group\_arn](#output\_cloudwatch\_log\_group\_arn) | ARN of Cloudwatch log group | +| [cloudwatch\_log\_group\_name](#output\_cloudwatch\_log\_group\_name) | Name of Cloudwatch log group | + diff --git a/examples/log-group-with-data-protection-policy/main.tf b/examples/log-group-with-data-protection-policy/main.tf new file mode 100644 index 0000000..dd43255 --- /dev/null +++ b/examples/log-group-with-data-protection-policy/main.tf @@ -0,0 +1,84 @@ +provider "aws" { + region = "eu-west-1" +} + +module "log_group" { + source = "../../modules/log-group" + + name_prefix = "my-log-group-" + retention_in_days = 7 +} + +module "custom_data_protection_policy_log_group" { + source = "../../modules/log-group" + + name_prefix = "my-custom-policy-log-group-" + retention_in_days = 7 +} + + +module "audit_destination_group" { + source = "../../modules/log-group" + + name_prefix = "audit-destination-log-group-" + retention_in_days = 7 +} + +module "log_data_protection_policy" { + source = "../../modules/log-data-protection-policy" + + log_group_name = module.log_group.cloudwatch_log_group_name + create_log_data_protection_policy = true + log_data_protection_policy_name = "RedactAddress" + + data_identifiers = ["arn:aws:dataprotection::aws:data-identifier/Address"] + findings_destination_cloudwatch_log_group = module.audit_destination_group.cloudwatch_log_group_name +} + +module "custom_log_data_protection_policy" { + source = "../../modules/log-data-protection-policy" + + log_group_name = module.custom_data_protection_policy_log_group.cloudwatch_log_group_name + + # custom data identifier not yet supported by the data source for aws_cloudwatch_log_data_protection_policy within the module + # specify your own json policy document if this is needed + # https://github.com/hashicorp/terraform-provider-aws/issues/35682 + policy_document = jsonencode({ + Name = "RedactCustomerId" + Version = "2021-06-01" + + Configuration = { + CustomDataIdentifier = [ + { + Name = "CustomerId", + Regex = "CustomerId-\\d{5}" + } + ] + } + + Statement = [ + { + Sid = "Audit" + DataIdentifier = ["CustomerId"] + Operation = { + Audit = { + FindingsDestination = { + CloudWatchLogs = { + LogGroup = module.audit_destination_group.cloudwatch_log_group_name + } + } + } + } + }, + { + Sid = "Redact" + DataIdentifier = ["CustomerId"] + Operation = { + Deidentify = { + MaskConfig = {} + } + } + } + ] + }) +} diff --git a/examples/log-group-with-data-protection-policy/outputs.tf b/examples/log-group-with-data-protection-policy/outputs.tf new file mode 100644 index 0000000..e11eab4 --- /dev/null +++ b/examples/log-group-with-data-protection-policy/outputs.tf @@ -0,0 +1,9 @@ +output "cloudwatch_log_group_name" { + description = "Name of Cloudwatch log group" + value = module.log_group.cloudwatch_log_group_name +} + +output "cloudwatch_log_group_arn" { + description = "ARN of Cloudwatch log group" + value = module.log_group.cloudwatch_log_group_arn +} diff --git a/examples/log-group-with-data-protection-policy/variables.tf b/examples/log-group-with-data-protection-policy/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/examples/log-group-with-data-protection-policy/versions.tf b/examples/log-group-with-data-protection-policy/versions.tf new file mode 100644 index 0000000..ddfcb0e --- /dev/null +++ b/examples/log-group-with-data-protection-policy/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.0" + } + } +} diff --git a/modules/log-data-protection-policy/README.md b/modules/log-data-protection-policy/README.md new file mode 100644 index 0000000..65d8994 --- /dev/null +++ b/modules/log-data-protection-policy/README.md @@ -0,0 +1,50 @@ +# log-data-protection-policy + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 5.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 5.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_log_data_protection_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_data_protection_policy) | resource | +| [aws_cloudwatch_log_data_protection_policy_document.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/cloudwatch_log_data_protection_policy_document) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [audit\_statement\_sid](#input\_audit\_statement\_sid) | Name of the audit statement. | `string` | `"audit-policy"` | no | +| [create](#input\_create) | Whether to create the cloudwatch log data protection policy. | `bool` | `true` | no | +| [create\_log\_data\_protection\_policy](#input\_create\_log\_data\_protection\_policy) | Whether to create the cloudwatch log data protection policy. | `bool` | `false` | no | +| [data\_identifiers](#input\_data\_identifiers) | Set of at least 1 sensitive data identifiers that you want to mask. | `list(string)` | `null` | no | +| [deidentify\_statement\_sid](#input\_deidentify\_statement\_sid) | Name of the deidentify statement. | `string` | `"redact-policy"` | no | +| [findings\_destination\_cloudwatch\_log\_group](#input\_findings\_destination\_cloudwatch\_log\_group) | Configures CloudWatch Logs as a findings destination. | `string` | `null` | no | +| [findings\_destination\_firehose\_delivery\_stream](#input\_findings\_destination\_firehose\_delivery\_stream) | Configures Kinesis Firehose as a findings destination. | `string` | `null` | no | +| [findings\_destination\_s3\_bucket](#input\_findings\_destination\_s3\_bucket) | Configures S3 as a findings destination. | `string` | `null` | no | +| [log\_data\_protection\_description](#input\_log\_data\_protection\_description) | The description of the data protection policy document. | `string` | `null` | no | +| [log\_data\_protection\_policy\_name](#input\_log\_data\_protection\_policy\_name) | The name of the data protection policy document. | `string` | `null` | no | +| [log\_group\_name](#input\_log\_group\_name) | The name of the log group under which the log stream is to be created. | `string` | `null` | no | +| [policy\_document](#input\_policy\_document) | Specifies the data protection policy in JSON. | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [log\_group\_name](#output\_log\_group\_name) | Name of Cloudwatch log group | + diff --git a/modules/log-data-protection-policy/main.tf b/modules/log-data-protection-policy/main.tf new file mode 100644 index 0000000..8360a10 --- /dev/null +++ b/modules/log-data-protection-policy/main.tf @@ -0,0 +1,60 @@ +resource "aws_cloudwatch_log_data_protection_policy" "this" { + count = var.create ? 1 : 0 + + log_group_name = var.log_group_name + policy_document = var.create_log_data_protection_policy ? data.aws_cloudwatch_log_data_protection_policy_document.this[0].json : var.policy_document +} + +data "aws_cloudwatch_log_data_protection_policy_document" "this" { + count = var.create && var.create_log_data_protection_policy ? 1 : 0 + + name = var.log_data_protection_policy_name + description = var.log_data_protection_description + + statement { + sid = var.audit_statement_sid + data_identifiers = var.data_identifiers + + operation { + audit { + findings_destination { + + dynamic "cloudwatch_logs" { + for_each = var.findings_destination_cloudwatch_log_group != null ? [true] : [] + + content { + log_group = var.findings_destination_cloudwatch_log_group + } + } + + dynamic "firehose" { + for_each = var.findings_destination_firehose_delivery_stream != null ? [true] : [] + + content { + delivery_stream = var.findings_destination_firehose_delivery_stream + } + } + + dynamic "s3" { + for_each = var.findings_destination_s3_bucket != null ? [true] : [] + + content { + bucket = var.findings_destination_s3_bucket + } + } + } + } + } + } + + statement { + sid = var.deidentify_statement_sid + data_identifiers = var.data_identifiers + + operation { + deidentify { + mask_config {} + } + } + } +} diff --git a/modules/log-data-protection-policy/outputs.tf b/modules/log-data-protection-policy/outputs.tf new file mode 100644 index 0000000..dda9933 --- /dev/null +++ b/modules/log-data-protection-policy/outputs.tf @@ -0,0 +1,4 @@ +output "log_group_name" { + description = "Name of Cloudwatch log group" + value = var.log_group_name +} diff --git a/modules/log-data-protection-policy/variables.tf b/modules/log-data-protection-policy/variables.tf new file mode 100644 index 0000000..0e1a65c --- /dev/null +++ b/modules/log-data-protection-policy/variables.tf @@ -0,0 +1,71 @@ +variable "create" { + description = "Whether to create the cloudwatch log data protection policy." + type = bool + default = true +} + +variable "create_log_data_protection_policy" { + description = "Whether to create the cloudwatch log data protection policy." + type = bool + default = false +} + +variable "log_group_name" { + description = "The name of the log group under which the log stream is to be created." + type = string + default = null +} + +variable "policy_document" { + description = "Specifies the data protection policy in JSON." + type = string + default = null +} + +variable "log_data_protection_policy_name" { + description = "The name of the data protection policy document." + type = string + default = null +} + +variable "log_data_protection_description" { + description = "The description of the data protection policy document." + type = string + default = null +} + +variable "audit_statement_sid" { + description = "Name of the audit statement." + type = string + default = "audit-policy" +} + +variable "deidentify_statement_sid" { + description = "Name of the deidentify statement." + type = string + default = "redact-policy" +} + +variable "data_identifiers" { + description = "Set of at least 1 sensitive data identifiers that you want to mask." + type = list(string) + default = null +} + +variable "findings_destination_cloudwatch_log_group" { + description = "Configures CloudWatch Logs as a findings destination." + type = string + default = null +} + +variable "findings_destination_firehose_delivery_stream" { + description = "Configures Kinesis Firehose as a findings destination." + type = string + default = null +} + +variable "findings_destination_s3_bucket" { + description = "Configures S3 as a findings destination." + type = string + default = null +} diff --git a/modules/log-data-protection-policy/versions.tf b/modules/log-data-protection-policy/versions.tf new file mode 100644 index 0000000..ddfcb0e --- /dev/null +++ b/modules/log-data-protection-policy/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.0" + } + } +} diff --git a/wrappers/log-data-protection-policy/README.md b/wrappers/log-data-protection-policy/README.md new file mode 100644 index 0000000..0d9a0d0 --- /dev/null +++ b/wrappers/log-data-protection-policy/README.md @@ -0,0 +1,100 @@ +# Wrapper for module: `modules/log-data-protection-policy` + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/cloudwatch/aws//wrappers/log-data-protection-policy" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-cloudwatch.git//wrappers/log-data-protection-policy?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/cloudwatch/aws//wrappers/log-data-protection-policy" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/wrappers/log-data-protection-policy/main.tf b/wrappers/log-data-protection-policy/main.tf new file mode 100644 index 0000000..61bdb98 --- /dev/null +++ b/wrappers/log-data-protection-policy/main.tf @@ -0,0 +1,18 @@ +module "wrapper" { + source = "../../modules/log-data-protection-policy" + + for_each = var.items + + audit_statement_sid = try(each.value.audit_statement_sid, var.defaults.audit_statement_sid, "audit-policy") + create = try(each.value.create, var.defaults.create, true) + create_log_data_protection_policy = try(each.value.create_log_data_protection_policy, var.defaults.create_log_data_protection_policy, false) + data_identifiers = try(each.value.data_identifiers, var.defaults.data_identifiers, null) + deidentify_statement_sid = try(each.value.deidentify_statement_sid, var.defaults.deidentify_statement_sid, "redact-policy") + findings_destination_cloudwatch_log_group = try(each.value.findings_destination_cloudwatch_log_group, var.defaults.findings_destination_cloudwatch_log_group, null) + findings_destination_firehose_delivery_stream = try(each.value.findings_destination_firehose_delivery_stream, var.defaults.findings_destination_firehose_delivery_stream, null) + findings_destination_s3_bucket = try(each.value.findings_destination_s3_bucket, var.defaults.findings_destination_s3_bucket, null) + log_data_protection_description = try(each.value.log_data_protection_description, var.defaults.log_data_protection_description, null) + log_data_protection_policy_name = try(each.value.log_data_protection_policy_name, var.defaults.log_data_protection_policy_name, null) + log_group_name = try(each.value.log_group_name, var.defaults.log_group_name, null) + policy_document = try(each.value.policy_document, var.defaults.policy_document, null) +} diff --git a/wrappers/log-data-protection-policy/outputs.tf b/wrappers/log-data-protection-policy/outputs.tf new file mode 100644 index 0000000..ec6da5f --- /dev/null +++ b/wrappers/log-data-protection-policy/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/wrappers/log-data-protection-policy/variables.tf b/wrappers/log-data-protection-policy/variables.tf new file mode 100644 index 0000000..a6ea096 --- /dev/null +++ b/wrappers/log-data-protection-policy/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/wrappers/log-data-protection-policy/versions.tf b/wrappers/log-data-protection-policy/versions.tf new file mode 100644 index 0000000..51cad10 --- /dev/null +++ b/wrappers/log-data-protection-policy/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 0.13.1" +}