diff --git a/org-logging/README.md b/org-logging/README.md new file mode 100644 index 0000000..1d09f36 --- /dev/null +++ b/org-logging/README.md @@ -0,0 +1,212 @@ +# aws-inf-setup :: logging + +This set up the needed components for logging in a region: S3, KMS key, SNS, SQS, logging, +cloudwatch log groups, and associated permissions. It also generates a splunk configuration to be used +for pulling logging events. + +* S3 bucket +* SNS Topic +* SQS Queue (and Deadletter queue) +* Cloudwatch Log +* setup/*.conf files for Splunk + * inputs.{name}.{account}.{region}.conf + +Once setup, the gnerated Splunk configuration files can be provided to the Splunk team for ingesting +as Logging. + +## Usage: Simple + +This siomple configuration is how it will typically be deployed. + +```hcl +module "logging_key" { + source = "git@github.e.it.census.gov:terraform-modules/aws-inf-setup.git//logging-key" + + tags = local.common_tags +} + +module "logging" { + source = "git@github.e.it.census.gov:terraform-modules/aws-inf-setup.git//logging" + + account_alias = var.account_alias + access_log_bucket = module.logs.bucket_id + kms_key_arn = module.logging_key.kms_key_arn + + enable_sns = true + enable_sqs = true + + tags = local.common_tags +} +``` + +## Usage: Extended + +This shows the creation of a key with additional variables, along with a policy for key access (currently +just a placholder), and the logging with more variables offered. + +```hcl +module "logging_key" { + source = "git@github.e.it.census.gov:terraform-modules/aws-inf-setup.git//logging-key" + + name = "mylogging" + kms_admin_roles = ["arn:aws:iam::079788916859:role/r-inf-cloud-admin"] + kms_policy_document = data.aws_iam_policy_document.myct_policy.json + + tags = { + Environment = "csvd:infrastructure" + } + + component_tags = { + "kms" = { + "SpecialTag1" = "something" + "SpecialTag2" = "somethingElse" + } + } +} + +data "aws_iam_policy_document" "myct_policy" {} + +module "logging" { + source = "git@github.e.it.census.gov:terraform-modules/aws-inf-setup.git//logging" + + name = "mylogging" + account_alias = var.account_alias + access_log_bucket = module.logs.bucket_id + kms_key_arn = module.logging_key.kms_key_arn + + enable_organization = false + enable_sns = true + enable_sqs = true + + tags = merge( + local.common_tags, + tomap({ Environment = "csvd:infrastructure" }), + ) +} +```hcl + +## Usage: Organization Cloudtrail + +This can be used for creation of an organization cloud trail. It is only applicable to the master +account of the organization, so you won't see this one used very often. + +```hcl +data "aws_organizations_organization" "org" {} + +module "org_logging_key" { + source = "git@github.e.it.census.gov:terraform-modules/aws-inf-setup.git//logging-key" + + name = "org-logging" + tags = local.common_tags +} + +module "org_logging" { + source = "git@github.e.it.census.gov:terraform-modules/aws-inf-setup.git//logging" + + account_alias = var.account_alias + enable_organization = true + access_log_bucket = module.logs.bucket_id + kms_key_arn = module.org_logging_key.kms_key_arn + organization_id = data.aws_organizations_organization.org.id + + enable_sns = true + enable_sqs = true + + tags = local.common_tags +} +``` + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.12 | +| [aws](#requirement\_aws) | >= 3.66.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.66.0 | +| [local](#provider\_local) | n/a | +| [null](#provider\_null) | n/a | +| [template](#provider\_template) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_policy.logging_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_role.logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_s3_bucket.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | +| [aws_s3_bucket_acl.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource | +| [aws_s3_bucket_logging.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource | +| [aws_s3_bucket_ownership_controls.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_ownership_controls) | resource | +| [aws_s3_bucket_policy.policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | +| [aws_s3_bucket_public_access_block.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | +| [aws_s3_bucket_server_side_encryption_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | +| [aws_sns_topic.logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic) | resource | +| [aws_sns_topic_policy.logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic_policy) | resource | +| [aws_sns_topic_subscription.additional_logging_sqs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic_subscription) | resource | +| [aws_sns_topic_subscription.logging_sqs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic_subscription) | resource | +| [aws_sqs_queue.additional_logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) | resource | +| [aws_sqs_queue.additional_logging_deadletter](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) | resource | +| [aws_sqs_queue.logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) | resource | +| [aws_sqs_queue.logging_deadletter](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) | resource | +| [aws_sqs_queue_policy.additional_logging_deadletter](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue_policy) | resource | +| [aws_sqs_queue_policy.additional_logging_sqs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue_policy) | resource | +| [aws_sqs_queue_policy.logging_deadletter](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue_policy) | resource | +| [aws_sqs_queue_policy.logging_sqs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue_policy) | resource | +| [local_file.splunk_logging](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource | +| [null_resource.policy_delay](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | +| [null_resource.splunk_logging](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | +| [aws_arn.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/arn) | data source | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_iam_policy_document.additional_logging_deadletter](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.additional_logging_sqs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.logging_assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.logging_cloudwatch](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.logging_deadletter](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.logging_s3](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.logging_sqs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.logging_topic](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_kms_key.incoming_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/kms_key) | data source | +| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | +| [aws_regions.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/regions) | data source | +| [template_file.splunk_logging](https://registry.terraform.io/providers/hashicorp/template/latest/docs/data-sources/file) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [access\_log\_bucket](#input\_access\_log\_bucket) | Server Access Logging Bucket ID | `string` | n/a | yes | +| [access\_log\_bucket\_prefix](#input\_access\_log\_bucket\_prefix) | Server Access Log bucket prefix, to which the Object Logging bucket name will be appended to make the target\_prefix | `string` | `"s3"` | no | +| [account\_alias](#input\_account\_alias) | AWS Account Alias | `string` | `""` | no | +| [account\_id](#input\_account\_id) | AWS Account ID (default will pull from current user) | `string` | `""` | no | +| [additional\_sqs\_names](#input\_additional\_sqs\_names) | List of additional SQS queues to create and subscribe to the SNS topic (if enabled) | `list(string)` | `[]` | no | +| [cloudtrail\_bucket\_prefix](#input\_cloudtrail\_bucket\_prefix) | Access log bucket prefix, to which the bucket name will be appended to make the target\_prefix | `string` | `"cloudtrail"` | no | +| [component\_tags](#input\_component\_tags) | Additional tags for Components (s3, kms, ddb) | `map(map(string))` |
{
"ddb": {},
"kms": {},
"s3": {}
}
| no | +| [enable\_organization](#input\_enable\_organization) | Enable Logging as an organization trail. This will only work in the organization master account | `bool` | `false` | no | +| [enable\_sns](#input\_enable\_sns) | Flag to enable or disable the creation of SNS for Cloudtrail (TBD) | `bool` | `false` | no | +| [enable\_sqs](#input\_enable\_sqs) | Flag to enable or disable the creation of SQS attached to SNS for Cloudtrail, used for Splunk ingestion (TBD) | `bool` | `false` | no | +| [kms\_key\_arn](#input\_kms\_key\_arn) | AWS Logging KMS ARN to be used for encrypting the ClouldTrail, S3 Bucket, and SQS | `string` | n/a | yes | +| [kms\_key\_management\_identifiers](#input\_kms\_key\_management\_identifiers) | AWS IAM ARNs (roles, groups, users) for full access to the created KMS Key for this bucket | `list(string)` | `[]` | no | +| [name](#input\_name) | Name to apply to Cloudtrail, S3, SNS and SQS | `string` | `null` | no | +| [organization\_id](#input\_organization\_id) | AWS Organization ID | `string` | `""` | no | +| [override\_prefixes](#input\_override\_prefixes) | Override built-in prefixes by component (efs, s3, ebs, kms, role, policy, security-group). This should be used primarily for common infrastructure things | `map(string)` | `{}` | no | +| [tags](#input\_tags) | AWS Tags to apply to appropriate resources (S3, KMS). Do not include safeguard tags here, use the data\_safeguard field for such things. | `map(string)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [additional\_sqs\_info](#output\_additional\_sqs\_info) | Additional SQS ARNs and IDs (main, deadletter) | +| [s3\_bucket\_arn](#output\_s3\_bucket\_arn) | Created S3 Bucket ARN | +| [s3\_bucket\_id](#output\_s3\_bucket\_id) | Created S3 Bucket ID | +| [sns\_arn](#output\_sns\_arn) | SNS ARN | +| [sqs\_info](#output\_sqs\_info) | Main SQS ARNs and IDs (main, deadletter) | diff --git a/org-logging/additional-sqs.tf b/org-logging/additional-sqs.tf new file mode 100644 index 0000000..af47251 --- /dev/null +++ b/org-logging/additional-sqs.tf @@ -0,0 +1,103 @@ +locals { + additional_sqs_names = var.enable_sqs ? toset(var.additional_sqs_names) : toset([]) +} + +resource "aws_sqs_queue" "additional_logging_deadletter" { + for_each = local.additional_sqs_names + name = format("%v-deadletter", each.key) + delay_seconds = 0 + max_message_size = 262144 + message_retention_seconds = lookup(local._defaults["sqs_deadletter"], "message_retention_seconds", 1 * 86400) + receive_wait_time_seconds = 15 + visibility_timeout_seconds = 3600 + + kms_master_key_id = data.aws_kms_key.incoming_key.id + kms_data_key_reuse_period_seconds = 300 + + tags = merge( + local.base_tags, + var.tags, + tomap({ Name = format("%v-deadletter", each.key) }), + ) +} + +resource "aws_sqs_queue_policy" "additional_logging_deadletter" { + for_each = local.additional_sqs_names + queue_url = var.enable_sqs ? aws_sqs_queue.additional_logging_deadletter[each.key].id : null + policy = data.aws_iam_policy_document.additional_logging_deadletter[each.key].json +} + +data "aws_iam_policy_document" "additional_logging_deadletter" { + for_each = local.additional_sqs_names + statement { + sid = "AllowSNSSendMessage" + effect = "Allow" + actions = ["sqs:SendMessage"] + resources = [var.enable_sqs ? aws_sqs_queue.additional_logging_deadletter[each.key].arn : ""] + principals { + type = "AWS" + identifiers = ["*"] + } + condition { + test = "ArnEquals" + variable = "aws:SourceArn" + values = [var.enable_sns ? aws_sns_topic.logging[0].arn : ""] + } + } +} + +resource "aws_sqs_queue" "additional_logging" { + for_each = local.additional_sqs_names + name = each.key + delay_seconds = 0 + max_message_size = 262144 + message_retention_seconds = lookup(local._defaults["sqs_deadletter"], "message_retention_seconds", 7 * 86400) + receive_wait_time_seconds = 15 + visibility_timeout_seconds = 7200 + + redrive_policy = jsonencode({ + deadLetterTargetArn = var.enable_sqs ? aws_sqs_queue.additional_logging_deadletter[each.key].arn : null + maxReceiveCount = 100 + }) + + kms_master_key_id = data.aws_kms_key.incoming_key.id + kms_data_key_reuse_period_seconds = 300 + + tags = merge( + local.base_tags, + var.tags, + tomap({ Name = each.key }), + ) +} + +resource "aws_sqs_queue_policy" "additional_logging_sqs" { + for_each = local.additional_sqs_names + queue_url = var.enable_sqs ? aws_sqs_queue.additional_logging[each.key].id : null + policy = data.aws_iam_policy_document.additional_logging_sqs[each.key].json +} + +data "aws_iam_policy_document" "additional_logging_sqs" { + for_each = local.additional_sqs_names + statement { + sid = "AllowSNSSendMessage" + effect = "Allow" + actions = ["sqs:SendMessage"] + resources = [var.enable_sqs ? aws_sqs_queue.additional_logging[each.key].arn : ""] + principals { + type = "AWS" + identifiers = ["*"] + } + condition { + test = "ArnEquals" + variable = "aws:SourceArn" + values = [var.enable_sns ? aws_sns_topic.logging[0].arn : ""] + } + } +} + +resource "aws_sns_topic_subscription" "additional_logging_sqs" { + for_each = var.enable_sns ? local.additional_sqs_names : toset([]) + protocol = "sqs" + topic_arn = var.enable_sns ? aws_sns_topic.logging[0].arn : null + endpoint = var.enable_sqs ? aws_sqs_queue.additional_logging[each.key].arn : null +} diff --git a/org-logging/base_tags.tf b/org-logging/base_tags.tf new file mode 100644 index 0000000..602b87a --- /dev/null +++ b/org-logging/base_tags.tf @@ -0,0 +1,6 @@ +locals { + base_tags = { + "boc:tf_module_version" = local._module_version + "boc:created_by" = "terraform" + } +} diff --git a/org-logging/cloudtrail.tf b/org-logging/cloudtrail.tf new file mode 100644 index 0000000..6899337 --- /dev/null +++ b/org-logging/cloudtrail.tf @@ -0,0 +1,49 @@ +resource "aws_iam_role" "logging" { + name = local.role_name + assume_role_policy = data.aws_iam_policy_document.logging_assume.json + description = "AWS Logging Role for ${local.name}" + force_detach_policies = false + max_session_duration = 3600 + # add deny billing + managed_policy_arns = [aws_iam_policy.logging_policy.arn] + path = "/" + + tags = merge( + local.base_tags, + var.tags, + tomap({ Name = local.role_name }), + ) +} + +data "aws_iam_policy_document" "logging_assume" { + statement { + sid = "AWSLoggingServiceAssumeRole" + effect = "Allow" + actions = ["sts:AssumeRole"] + principals { + type = "Service" + identifiers = ["logging.amazonaws.com"] + } + } +} + +resource "aws_iam_policy" "logging_policy" { + name = local.policy_name + policy = data.aws_iam_policy_document.logging_cloudwatch.json +} + + +data "aws_iam_policy_document" "logging_cloudwatch" { + statement { + sid = "AWSLoggingCreateLogStream" + effect = "Allow" + actions = ["logs:CreateLogStream"] + resources = local.resources + } + statement { + sid = "AWSLoggingPutLogEvents" + effect = "Allow" + actions = ["logs:PutLogEvents"] + resources = local.resources + } +} diff --git a/org-logging/cloudwatch.tf.off b/org-logging/cloudwatch.tf.off new file mode 100644 index 0000000..355bc17 --- /dev/null +++ b/org-logging/cloudwatch.tf.off @@ -0,0 +1,37 @@ +locals { + cloudwatch_prefix = replace(aws_cloudwatch_log_group.this.arn, "/:\\*$/", "") + cloudwatch_suffix = format("%v_CloudTrail_%v", local.account_id, local.region) + org_cloudwatch_suffix = format("%v_*", var.organization_id) + cloudwatch_resources = join(":", [local.cloudwatch_prefix, "log-stream", local.cloudwatch_suffix]) + org_cloudwatch_resources = var.enable_organization ? join(":", [local.cloudwatch_prefix, "log-stream", local.org_cloudwatch_suffix]) : null + resources = compact([local.cloudwatch_resources, local.org_cloudwatch_resources]) +} + +resource "aws_cloudwatch_log_group" "this" { + name = local.name + kms_key_id = var.kms_key_arn + retention_in_days = lookup(local._defaults["cloudwatch"], "retention_in_days", 7) + + tags = merge( + local.base_tags, + var.tags, + tomap({ Name = local.name }), + ) +} + +## data "aws_iam_policy_document" "cloudwatch_policy" { +## statement { +## sid = "AWSCloudTrailCreateLogStream" +## effect = "Allow" +## actions = ["logs:CreateLogStream"] +## resources = local.resources +## } +## +## statement { +## sid = "AWSCloudTrailPutLogEvents" +## effect = "Allow" +## actions = ["logs:PutLogEvents"] +## resources = local.resources +## } +## } +## diff --git a/org-logging/data.policies.tf b/org-logging/data.policies.tf new file mode 100644 index 0000000..f35b38c --- /dev/null +++ b/org-logging/data.policies.tf @@ -0,0 +1,31 @@ +data "aws_iam_policy_document" "logging_s3" { + statement { + sid = "AWSLoggingWrite" + effect = "Allow" + resources = ["${aws_s3_bucket.this.arn}/*"] + actions = ["s3:PutObject"] + + principals { + type = "Service" + identifiers = ["logging.amazonaws.com"] + } + + condition { + test = "StringLike" + variable = "s3:x-amz-acl" + values = ["bucket-owner-full-control"] + } + } + + statement { + sid = "AWSLoggingAclCheck" + effect = "Allow" + resources = [aws_s3_bucket.this.arn] + actions = ["s3:GetBucketAcl"] + + principals { + type = "Service" + identifiers = ["logging.amazonaws.com"] + } + } +} diff --git a/org-logging/data.tf b/org-logging/data.tf new file mode 120000 index 0000000..995624d --- /dev/null +++ b/org-logging/data.tf @@ -0,0 +1 @@ +../common/data.tf \ No newline at end of file diff --git a/org-logging/defaults.tf b/org-logging/defaults.tf new file mode 100644 index 0000000..7d127a1 --- /dev/null +++ b/org-logging/defaults.tf @@ -0,0 +1,66 @@ +# for the accesss logs for load balancers +# https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html#access-logging-bucket-permissions + +locals { + _defaults = { + "account_settings" = { + allow_users_to_change_password = true + hard_expiry = false + max_password_age = 89 + minimum_password_length = 14 + password_reuse_prevention = 24 + require_lowercase_characters = true + require_numbers = true + require_symbols = true + require_uppercase_characters = true + } + "load-balancer" = { + "gov" = ["190560391635", "048591011584"] + "us-gov-east-1" = "190560391635" + "us-gov-west-1" = "048591011584" + + "ew" = ["127311923021", "033677994240", "027434742980", "797873946194"] + "us-east-1" = "127311923021" + "us-east-2" = "033677994240" + "us-west-1" = "027434742980" + "us-west-2" = "797873946194" + } + "ses" = { + "event_types" = ["bounce", "delivery", "complaint"] + } + "org_logging" = { + "name" = "inf-org-logging" + } + "logging" = { + "name" = "inf-logging" + } + "config" = { + "name" = "inf-config" + } + "cloudwatch" = { + "retention_in_days" = 14 + } + "sqs" = { + "message_retention_seconds" = 14 * 86400 + } + "sqs_deadletter" = { + "message_retention_seconds" = 7 * 86400 + } + "splunk_description" = { + "api_list" = [ + "ec2_volumes", "ec2_instances", "ec2_reserved_instances", "ec2_key_pairs", "ec2_security_groups", "ec2_images", "ec2_addresses", + "ebs_snapshots", "classic_load_balancers", "application_load_balancers", + "vpcs", "vpc_network_acls", "vpc_subnets", + "rds_instances", + "lambda_functions", + "s3_buckets", + "iam_users" + ] + "api_interval" = 3600 + } + "force_detach_policies" = false + "max_session_duration" = 3600 + } +} + + diff --git a/org-logging/generate_splunk.cloudtrail.tf b/org-logging/generate_splunk.cloudtrail.tf new file mode 100644 index 0000000..d626d62 --- /dev/null +++ b/org-logging/generate_splunk.cloudtrail.tf @@ -0,0 +1,45 @@ +#--- +# generate splunk inputs file +#--- +data "template_file" "splunk_logging" { + template = file("${path.module}/templates/inputs.logging.conf.tpl") + vars = { + account_id = local.account_id + account_alias = local.account_alias + # entry_uuid = random_uuid.splunk_logging.result + region = local.region + logging_name = local.splunk_name + queue_url = var.enable_sqs ? aws_sqs_queue.logging[0].id : null + } +} + +# resource "random_uuid" "splunk_logging" { +# keepers = { +# queue_url = var.enable_sqs ? aws_sqs_queue.logging[0].id : null +# } +# } + +resource "null_resource" "splunk_logging" { + count = var.enable_sqs ? 1 : 0 + triggers = { + filename = format("inputs.%v.%v-%v.%v.conf", local.splunk_name, local.account_id, local.account_alias, local.region) + directory = format("%v/setup", path.root) + } + + provisioner "local-exec" { + command = "test -d ${self.triggers.directory} || mkdir ${self.triggers.directory}" + } + + # provisioner "local-exec" { + # working_dir = "setup" + # command = "echo '${data.template_file.splunk_logging.rendered}' > inputs.${local.splunk_name}.${local.account_id}.${local.region}.conf" + # } +} + +resource "local_file" "splunk_logging" { + count = var.enable_sqs ? 1 : 0 + + content = data.template_file.splunk_logging.rendered + file_permission = "0644" + filename = var.enable_sqs ? format("%v/%v", null_resource.splunk_logging[0].triggers.directory, null_resource.splunk_logging[0].triggers.filename) : null +} diff --git a/org-logging/main.tf b/org-logging/main.tf new file mode 100644 index 0000000..be2c776 --- /dev/null +++ b/org-logging/main.tf @@ -0,0 +1,160 @@ +/* +* # aws-inf-setup :: logging +* +* This set up the needed components for logging in a region: S3, KMS key, SNS, SQS, logging, +* cloudwatch log groups, and associated permissions. It also generates a splunk configuration to be used +* for pulling logging events. +* +* * S3 bucket +* * SNS Topic +* * SQS Queue (and Deadletter queue) +* * Cloudwatch Log +* * setup/*.conf files for Splunk +* * inputs.{name}.{account}.{region}.conf +* +* Once setup, the gnerated Splunk configuration files can be provided to the Splunk team for ingesting +* as Logging. +* +* ## Usage: Simple +* +* This siomple configuration is how it will typically be deployed. +* +* ```hcl +* module "logging_key" { +* source = "git@github.e.it.census.gov:terraform-modules/aws-inf-setup.git//logging-key" +* +* tags = local.common_tags +* } +* +* module "logging" { +* source = "git@github.e.it.census.gov:terraform-modules/aws-inf-setup.git//logging" +* +* account_alias = var.account_alias +* access_log_bucket = module.logs.bucket_id +* kms_key_arn = module.logging_key.kms_key_arn +* +* enable_sns = true +* enable_sqs = true +* +* tags = local.common_tags +* } +* ``` +* +* ## Usage: Extended +* +* This shows the creation of a key with additional variables, along with a policy for key access (currently +* just a placholder), and the logging with more variables offered. +* +* ```hcl +* module "logging_key" { +* source = "git@github.e.it.census.gov:terraform-modules/aws-inf-setup.git//logging-key" +* +* name = "mylogging" +* kms_admin_roles = ["arn:aws:iam::079788916859:role/r-inf-cloud-admin"] +* kms_policy_document = data.aws_iam_policy_document.myct_policy.json +* +* tags = { +* Environment = "csvd:infrastructure" +* } +* +* component_tags = { +* "kms" = { +* "SpecialTag1" = "something" +* "SpecialTag2" = "somethingElse" +* } +* } +* } +* +* data "aws_iam_policy_document" "myct_policy" {} +* +* module "logging" { +* source = "git@github.e.it.census.gov:terraform-modules/aws-inf-setup.git//logging" +* +* name = "mylogging" +* account_alias = var.account_alias +* access_log_bucket = module.logs.bucket_id +* kms_key_arn = module.logging_key.kms_key_arn +* +* enable_organization = false +* enable_sns = true +* enable_sqs = true +* +* tags = merge( +* local.common_tags, +* tomap({ Environment = "csvd:infrastructure" }), +* ) +* } +* ```hcl +* +* +* ## Usage: Organization Cloudtrail +* +* This can be used for creation of an organization cloud trail. It is only applicable to the master +* account of the organization, so you won't see this one used very often. +* +* ```hcl +* data "aws_organizations_organization" "org" {} +* +* module "org_logging_key" { +* source = "git@github.e.it.census.gov:terraform-modules/aws-inf-setup.git//logging-key" +* +* name = "org-logging" +* tags = local.common_tags +* } +* +* module "org_logging" { +* source = "git@github.e.it.census.gov:terraform-modules/aws-inf-setup.git//logging" +* +* account_alias = var.account_alias +* enable_organization = true +* access_log_bucket = module.logs.bucket_id +* kms_key_arn = module.org_logging_key.kms_key_arn +* organization_id = data.aws_organizations_organization.org.id +* +* enable_sns = true +* enable_sqs = true +* +* tags = local.common_tags +* } +* ``` +*/ + +# key_name = (var.name != "" && var.name != null) ? var.name : local.name +# sns_name = local.name +# sqs_name = local.name +# role_name = local.name +# policy_name = local.name + + +locals { + # basic details about the env + account_id = var.account_id != "" ? var.account_id : data.aws_caller_identity.current.account_id + region = data.aws_region.current.name + account_environment = data.aws_arn.current.partition == "aws-us-gov" ? "gov" : "ew" + partition = data.aws_arn.current.partition + account_alias = var.account_alias == "" ? "MISSING" : var.account_alias + + _name = var.name == null ? format("%v-%v", lookup(local._defaults["logging"], "name"), local.region) : var.name + name = var.enable_organization ? lookup(local._defaults["org_logging"], "name") : local._name + kms_key_name = format("k-%v", local.name) + kms_admin_root = format("arn:%v:iam::%v:root", local.partition, local.account_id) + # kms_admin_roles = compact(concat([var.kms_admin_root], var.kms_admin_roles)) + # kms_policy_document = var.kms_policy_document != null ? var.kms_policy_document : data.aws_iam_policy_document.empty.json + + _bucket_name = var.name == null ? format("%v-%v-%v", lookup(local._defaults["logging"], "name"), local.account_id, local.region) : var.name + bucket_name = var.enable_organization ? format("%v-%v-%v", lookup(local._defaults["org_logging"], "name"), local.account_id, local.region) : local._bucket_name + + role_name = format("%v%v", local._prefixes["role"], local.name) + policy_name = format("%v%v", local._prefixes["policy"], local.name) + + splunk_name = var.enable_organization ? "org-logging" : "logging" +} + +data "aws_kms_key" "incoming_key" { + key_id = var.kms_key_arn +} + +# data "aws_organizations_organization" "org" {} + + + diff --git a/org-logging/outputs.tf b/org-logging/outputs.tf new file mode 100644 index 0000000..3e1b408 --- /dev/null +++ b/org-logging/outputs.tf @@ -0,0 +1,43 @@ +output "s3_bucket_arn" { + description = "Created S3 Bucket ARN" + value = aws_s3_bucket.this.arn +} + +output "s3_bucket_id" { + description = "Created S3 Bucket ID" + value = aws_s3_bucket.this.id +} + +output "sqs_info" { + description = "Main SQS ARNs and IDs (main, deadletter)" + value = { + "main" = { + arn = var.enable_sqs ? aws_sqs_queue.logging[0].arn : null + id = var.enable_sqs ? aws_sqs_queue.logging[0].id : null + } + "deadletter" = { + arn = var.enable_sqs ? aws_sqs_queue.logging_deadletter[0].arn : null + id = var.enable_sqs ? aws_sqs_queue.logging_deadletter[0].id : null + } + } +} + +output "additional_sqs_info" { + description = "Additional SQS ARNs and IDs (main, deadletter)" + value = { for k in local.additional_sqs_names : k => { + "name" = k + "main" = { + arn = lookup(aws_sqs_queue.additional_logging, k, { arn : null }).arn + id = lookup(aws_sqs_queue.additional_logging, k, { id : null }).id + } + "deadletter" = { + arn = lookup(aws_sqs_queue.additional_logging_deadletter, k, { arn : null }).arn + id = lookup(aws_sqs_queue.additional_logging_deadletter, k, { id : null }).id + } + } } +} + +output "sns_arn" { + description = "SNS ARN" + value = var.enable_sns ? aws_sns_topic.logging[0].arn : null +} diff --git a/org-logging/prefixes.tf b/org-logging/prefixes.tf new file mode 100644 index 0000000..361746b --- /dev/null +++ b/org-logging/prefixes.tf @@ -0,0 +1,33 @@ +locals { + _prefixes = { + "efs" = "v-efs-" + "s3" = "v-s3-" + "ebs" = "v-ebs-" + "kms" = "k-kms-" + "role" = "r-" + "policy" = "p-" + "group" = "g-" + "security-group" = "" # "sg-" + # VPC + "vpc" = "" + "dhcp-options" = "" + "vpc-peer" = "vpcp-" + "route-table" = "route-" + "subnet" = "" + "vpc-endpoint" = "vpce-" + "elastic-ip" = "eip-" + "nat-gateway" = "nat-" + "internet-gateway" = "igw-" + "network-acl" = "nacl-" + "customer-gateway" = "cgw-" + "vpn-gateway" = "vpcg-" + "vpn-connection" = "vpn_" + "log-group" = "lg-" + "log-stream" = "lgs-" + "transit-gateway" = "tgw-" + "transit-gateway-peer" = "tgwp-" + "transit-gateway-route-table" = "tgwr-" + "transit-gateway-attachment" = "tgwa-" + "transit-gateway-vpn" = "tgwv-" + } +} diff --git a/org-logging/s3.tf b/org-logging/s3.tf new file mode 100644 index 0000000..efb42dc --- /dev/null +++ b/org-logging/s3.tf @@ -0,0 +1,105 @@ +resource "aws_s3_bucket" "this" { + bucket = local.bucket_name + # acl = "private" + force_destroy = false + + tags = merge( + local.base_tags, + var.tags, + { "Name" = local.name }, + ) +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "this" { + bucket = aws_s3_bucket.this.id + rule { + apply_server_side_encryption_by_default { + kms_master_key_id = var.kms_key_arn + sse_algorithm = "aws:kms" + } + } +} + +resource "aws_s3_bucket_logging" "this" { + bucket = aws_s3_bucket.this.id + target_bucket = var.access_log_bucket + target_prefix = format("%s/%s/", var.access_log_bucket_prefix, local.bucket_name) +} + +resource "aws_s3_bucket_acl" "this" { + count = 0 + bucket = aws_s3_bucket.this.id + acl = "private" +} + +resource "aws_s3_bucket_ownership_controls" "this" { + bucket = aws_s3_bucket.this.id + + rule { + object_ownership = "BucketOwnerEnforced" + } +} + +#--- +# bucket policy (apply also encryption key usage here?) +# deny unencrypted uploads policy statement removed for default encryption +#--- +data "aws_iam_policy_document" "bucket_policy" { + statement { + sid = "AWSLoggingAclCheck" + effect = "Allow" + actions = ["s3:GetBucketAcl"] + principals { + type = "Service" + identifiers = ["logging.amazonaws.com"] + } + resources = [aws_s3_bucket.this.arn] + } + statement { + sid = "AWSLoggingWrite" + effect = "Allow" + actions = ["s3:PutObject"] + principals { + type = "Service" + identifiers = ["logging.amazonaws.com"] + } + resources = [format("%v/%v/*", aws_s3_bucket.this.arn, var.logging_bucket_prefix)] + condition { + test = "StringEquals" + variable = "s3:x-amz-acl" + values = ["bucket-owner-full-control"] + } + } +} + +#--- +# apply policy to bucket and public access block policy to bucket +#--- +resource "aws_s3_bucket_policy" "policy" { + bucket = aws_s3_bucket.this.bucket + policy = data.aws_iam_policy_document.bucket_policy.json + depends_on = [null_resource.policy_delay] +} + +resource "aws_s3_bucket_public_access_block" "this" { + bucket = aws_s3_bucket.this.id + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + +#--- +# 180s delay needed for bucket to create and policy to apply, before +# creating a logging to point to it +#--- +resource "null_resource" "policy_delay" { + triggers = { + bucket = aws_s3_bucket.this.id + } + provisioner "local-exec" { + when = create + command = "sleep 180" + } +} + diff --git a/org-logging/s3.tf2 b/org-logging/s3.tf2 new file mode 100644 index 0000000..b537e45 --- /dev/null +++ b/org-logging/s3.tf2 @@ -0,0 +1,57 @@ +#--- +# s3 +#--- +resource "aws_s3_bucket" "logging" { + bucket = local.bucket_name + acl = "private" + + server_side_encryption_logginguration { + rule { + apply_server_side_encryption_by_default { + kms_master_key_id = aws_kms_key.logging_key.arn + sse_algorithm = "aws:kms" + } + } + } + + versioning { + enabled = false + } + + logging { + target_bucket = aws_s3_bucket.logs.id + target_prefix = "s3/{local.logging_bucket}/" + } + + lifecycle { + prevent_destroy = true + ignore_changes = [tags["boc:tf_module_version"]] + } + + # probably want some migration of old data to some other location + # like glacier + + tags = merge( + var.tags, + local.base_tags, + lookup(var.component_tags, "s3", {}), + map("Name", local.bucket_name), + ) + + provisioner "local-exec" { + command = "sleep 30" + } +} + +resource "aws_s3_bucket_public_access_block" "logging" { + bucket = aws_s3_bucket.logging.id + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + +resource "aws_s3_bucket_policy" "logging" { + bucket = aws_s3_bucket.logging.id + policy = data.aws_iam_policy_document.logging_s3.json +} diff --git a/org-logging/sns.tf b/org-logging/sns.tf new file mode 100644 index 0000000..e7dc064 --- /dev/null +++ b/org-logging/sns.tf @@ -0,0 +1,56 @@ +resource "aws_sns_topic" "logging" { + count = var.enable_sns ? 1 : 0 + name = local.name + kms_master_key_id = data.aws_kms_key.incoming_key.id + + tags = merge( + local.base_tags, + var.tags, + tomap({ Name = local.name }), + ) +} + +resource "aws_sns_topic_policy" "logging" { + count = var.enable_sns ? 1 : 0 + arn = var.enable_sns ? aws_sns_topic.logging[0].arn : null + policy = data.aws_iam_policy_document.logging_topic.json +} + +data "aws_iam_policy_document" "logging_topic" { + policy_id = format("%v_topic", local.name) + statement { + sid = "CloudtrailSNSPermissions" + effect = "Allow" + principals { + type = "AWS" + identifiers = ["*"] + } + actions = [ + "sns:Subscribe", + "sns:SetTopicAttributes", + "sns:RemovePermission", + "sns:Receive", + "sns:Publish", + "sns:ListSubscriptionsByTopic", + "sns:GetTopicAttributes", + "sns:DeleteTopic", + "sns:AddPermission", + ] + condition { + test = "StringEquals" + variable = "AWS:SourceOwner" + values = [local.account_id] + } + resources = [var.enable_sns ? aws_sns_topic.logging[0].arn : ""] + } + statement { + sid = "LoggingSNSPolicy" + effect = "Allow" + principals { + type = "Service" + identifiers = ["logging.amazonaws.com"] + } + actions = ["sns:Publish"] + resources = [var.enable_sns ? aws_sns_topic.logging[0].arn : ""] + } +} diff --git a/org-logging/sqs.tf b/org-logging/sqs.tf new file mode 100644 index 0000000..32dc07c --- /dev/null +++ b/org-logging/sqs.tf @@ -0,0 +1,103 @@ +resource "aws_sqs_queue" "logging_deadletter" { + count = var.enable_sqs ? 1 : 0 + # delay=0 retention=4d max=256k visibility=1h + name = format("%v-deadletter", local.name) + delay_seconds = 0 + max_message_size = 262144 + message_retention_seconds = lookup(local._defaults["sqs_deadletter"], "message_retention_seconds", 1 * 86400) + # message_retention_seconds = 345600 + receive_wait_time_seconds = 15 + visibility_timeout_seconds = 3600 + + kms_master_key_id = data.aws_kms_key.incoming_key.id + kms_data_key_reuse_period_seconds = 300 + + tags = merge( + local.base_tags, + var.tags, + tomap({ Name = format("%v-deadletter", local.name) }), + ) +} + +resource "aws_sqs_queue_policy" "logging_deadletter" { + count = var.enable_sqs ? 1 : 0 + queue_url = var.enable_sqs ? aws_sqs_queue.logging_deadletter[0].id : null + policy = data.aws_iam_policy_document.logging_deadletter.json +} + +data "aws_iam_policy_document" "logging_deadletter" { + # policy_id = "SQSDefaultPolicy" + statement { + sid = "AllowSNSSendMessage" + effect = "Allow" + actions = ["sqs:SendMessage"] + resources = [var.enable_sqs ? aws_sqs_queue.logging_deadletter[0].arn : ""] + principals { + type = "AWS" + identifiers = ["*"] + } + condition { + test = "ArnEquals" + variable = "aws:SourceArn" + values = [var.enable_sns ? aws_sns_topic.logging[0].arn : ""] + } + } +} + +resource "aws_sqs_queue" "logging" { + count = var.enable_sqs ? 1 : 0 + # delay=0 retention=7d max=256k visibity=2h + name = local.name + delay_seconds = 0 + max_message_size = 262144 + message_retention_seconds = lookup(local._defaults["sqs_deadletter"], "message_retention_seconds", 7 * 86400) + # message_retention_seconds = 604800 + receive_wait_time_seconds = 15 + visibility_timeout_seconds = 7200 + + redrive_policy = jsonencode({ + deadLetterTargetArn = var.enable_sqs ? aws_sqs_queue.logging_deadletter[0].arn : null + maxReceiveCount = 100 + }) + + kms_master_key_id = data.aws_kms_key.incoming_key.id + kms_data_key_reuse_period_seconds = 300 + + tags = merge( + local.base_tags, + var.tags, + tomap({ Name = local.name }), + ) +} + +resource "aws_sqs_queue_policy" "logging_sqs" { + count = var.enable_sqs ? 1 : 0 + queue_url = var.enable_sqs ? aws_sqs_queue.logging[0].id : null + policy = data.aws_iam_policy_document.logging_sqs.json +} + +data "aws_iam_policy_document" "logging_sqs" { + # policy_id = "SQSDefaultPolicy" + statement { + sid = "AllowSNSSendMessage" + effect = "Allow" + actions = ["sqs:SendMessage"] + resources = [var.enable_sqs ? aws_sqs_queue.logging[0].arn : ""] + principals { + type = "AWS" + identifiers = ["*"] + } + condition { + test = "ArnEquals" + variable = "aws:SourceArn" + values = [var.enable_sns ? aws_sns_topic.logging[0].arn : ""] + } + } +} + +resource "aws_sns_topic_subscription" "logging_sqs" { + count = var.enable_sqs && var.enable_sns ? 1 : 0 + protocol = "sqs" + topic_arn = var.enable_sns ? aws_sns_topic.logging[0].arn : null + endpoint = var.enable_sqs ? aws_sqs_queue.logging[0].arn : null +} diff --git a/org-logging/variables.common.tf b/org-logging/variables.common.tf new file mode 120000 index 0000000..7439ed8 --- /dev/null +++ b/org-logging/variables.common.tf @@ -0,0 +1 @@ +../common/variables.common.tf \ No newline at end of file diff --git a/org-logging/variables.tf b/org-logging/variables.tf new file mode 100644 index 0000000..92fc932 --- /dev/null +++ b/org-logging/variables.tf @@ -0,0 +1,91 @@ +## variable "bucket_name" { +## description = "Cloudtrail S3 bucket name" +## type = string +## default = "" +## } +## +## variable "bucket_name_prefix" { +## description = "Cloudtrail S3 bucket prefix, prepended to the AWS account ID and region to make the bucket name." +## type = string +## default = "" +## } + +# enable_sns +# enable_sqs +# name (default = inf-cloudtrail-{account}-{region}) +# cloudtrail_name (name) +# log_bucket (existing bucket; if empty, will create one) +# sns_name (name) +# sqs_name (name) + +variable "name" { + description = "Name to apply to Cloudtrail, S3, SNS and SQS" + type = string + default = null +} + +variable "cloudtrail_bucket_prefix" { + description = "Access log bucket prefix, to which the bucket name will be appended to make the target_prefix" + type = string + default = "cloudtrail" +} + +# required +variable "access_log_bucket" { + description = "Server Access Logging Bucket ID" + type = string + # default = null +} + +variable "access_log_bucket_prefix" { + description = "Server Access Log bucket prefix, to which the Object Logging bucket name will be appended to make the target_prefix" + type = string + default = "s3" +} + +variable "kms_key_management_identifiers" { + description = "AWS IAM ARNs (roles, groups, users) for full access to the created KMS Key for this bucket" + type = list(string) + default = [] +} + +variable "kms_key_arn" { + description = "AWS Logging KMS ARN to be used for encrypting the ClouldTrail, S3 Bucket, and SQS" + type = string +} + +variable "enable_sns" { + description = "Flag to enable or disable the creation of SNS for Cloudtrail (TBD)" + type = bool + default = false +} + +variable "enable_sqs" { + description = "Flag to enable or disable the creation of SQS attached to SNS for Cloudtrail, used for Splunk ingestion (TBD)" + type = bool + default = false +} + +variable "component_tags" { + description = "Additional tags for Components (s3, kms, ddb)" + type = map(map(string)) + default = { "s3" = {}, "kms" = {}, "ddb" = {} } +} + +variable "enable_organization" { + description = "Enable Logging as an organization trail. This will only work in the organization master account" + type = bool + default = false +} + +variable "organization_id" { + description = "AWS Organization ID" + type = string + default = "" +} + +variable "additional_sqs_names" { + description = "List of additional SQS queues to create and subscribe to the SNS topic (if enabled)" + type = list(string) + default = [] +} diff --git a/org-logging/version.tf b/org-logging/version.tf new file mode 120000 index 0000000..b83c5b7 --- /dev/null +++ b/org-logging/version.tf @@ -0,0 +1 @@ +../common/version.tf \ No newline at end of file diff --git a/org-logging/versions.tf b/org-logging/versions.tf new file mode 100644 index 0000000..3d116e6 --- /dev/null +++ b/org-logging/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.66.0" + } + # ldap = { + # source = "trevex/ldap" + # version = ">= 0.5.4" + # } + } + required_version = ">= 0.12" + # required_version = ">= 0.13" +}