diff --git a/CHANGELOG.md b/CHANGELOG.md index 0228841..366fb5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -325,3 +325,7 @@ * 2.4.14 -- 2023-11-09 - iam-general-policies - add kms:ListGrants to KMS keys + +* 2.5.0 -- 2024-01-02 + - s3-config-org + - create for org-based s3 bucket and kms key for centralized config locations (within aws organization) diff --git a/common/version.tf b/common/version.tf index 046ed4f..fca0743 100644 --- a/common/version.tf +++ b/common/version.tf @@ -1,3 +1,3 @@ locals { - _module_version = "2.4.14" + _module_version = "2.5.0" } diff --git a/s3-config-org/README.md b/s3-config-org/README.md new file mode 100644 index 0000000..5a754e6 --- /dev/null +++ b/s3-config-org/README.md @@ -0,0 +1,144 @@ +# aws-inf-setup :: s3-config + +This set up the needed components for S3 config bucket. This needs to be in just one region. + +* S3 bucket + +# Usage +Here is a simple example, the one most commonly expected to be used. + +```hcl +module "config" { + source = "git@github.e.it.census.gov:terraform-modules/aws-inf-setup.git//s3-config" +} +``` + +## Requirements + +No requirements. + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_s3_bucket.config](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | +| [aws_s3_bucket_public_access_block.config](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | 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_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [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 | +| [bucket\_name](#input\_bucket\_name) | Logging S3 bucket name | `string` | `""` | no | +| [bucket\_name\_prefix](#input\_bucket\_name\_prefix) | Logging S3 bucket prefix, prepended to the AWS account ID and region to make the bucket name. | `string` | `"inf-config"` | no | +| [component\_tags](#input\_component\_tags) | Additional tags for Components (s3, kms) | `map(map(string))` |
{
"kms": {},
"s3": {}
} | 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 |
+|------|-------------|
+| [bucket\_arn](#output\_bucket\_arn) | Config S3 bucket ARN |
+| [bucket\_id](#output\_bucket\_id) | Config S3 bucket ID |
+
+
+# aws-inf-setup :: s3-config-org
+
+This set up the needed components for an organization-use S3 config bucket. THis will exist in each available
+region.
+
+* S3 bucket
+
+# Usage
+Here is a simple example, the one most commonly expected to be used.
+
+```hcl
+module "config" {
+ source = "git@github.e.it.census.gov:terraform-modules/aws-inf-setup.git//s3-config-org"
+}
+```
+
+# Links
+* https://cloudyadvice.com/2022/04/14/automated-enterprise-deployment-of-aws-config/
+
+## Requirements
+
+No requirements.
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | n/a |
+| [time](#provider\_time) | n/a |
+
+## Modules
+
+No modules.
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_kms_alias.key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_alias) | resource |
+| [aws_kms_key.key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource |
+| [aws_s3_bucket.config_org](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
+| [aws_s3_bucket_acl.config_org](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource |
+| [aws_s3_bucket_logging.config_org](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource |
+| [aws_s3_bucket_ownership_controls.config_org](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.config_org](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource |
+| [aws_s3_bucket_server_side_encryption_configuration.config_org](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource |
+| [aws_s3_bucket_versioning.config_org](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource |
+| [time_sleep.policy_delay](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | 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.bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.empty](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.key_admin](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.key_policy_combined](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_organizations_organization.org](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/organizations_organization) | 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 |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [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 |
+| [bucket\_name](#input\_bucket\_name) | Organization Config S3 bucket name | `string` | `null` | no |
+| [bucket\_name\_prefix](#input\_bucket\_name\_prefix) | Organization Config S3 bucket prefix, prepended to the AWS account ID and region to make the bucket name. | `string` | `"inf-org-config"` | no |
+| [component\_tags](#input\_component\_tags) | Additional tags for Components (s3, kms) | `map(map(string))` | {
"kms": {},
"s3": {}
} | no |
+| [key\_name](#input\_key\_name) | Name to apply to Org Logging KMS Key (default: k-inf-org-logging) | `string` | `null` | no |
+| [kms\_admin\_roles](#input\_kms\_admin\_roles) | AWS KMS Key administrative role(s) which have full access to the key. The root user is included by default. | `list(string)` | `[]` | no |
+| [kms\_policy\_document](#input\_kms\_policy\_document) | AWS KMS Key Policy Document JSON, merged with admin policy document | `string` | `null` | 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 |
+|------|-------------|
+| [bucket\_arn](#output\_bucket\_arn) | Organization Config S3 bucket ARN |
+| [bucket\_id](#output\_bucket\_id) | Organization Config S3 bucket ID |
+| [kms\_alias\_name](#output\_kms\_alias\_name) | Organization Config S3 Key Alias name |
+| [kms\_key\_arn](#output\_kms\_key\_arn) | Organization Config S3 Key ARN |
+| [kms\_key\_id](#output\_kms\_key\_id) | Organization Config S3 Key ID |
+
\ No newline at end of file
diff --git a/s3-config-org/data.org.tf b/s3-config-org/data.org.tf
new file mode 100644
index 0000000..e2e1318
--- /dev/null
+++ b/s3-config-org/data.org.tf
@@ -0,0 +1 @@
+data "aws_organizations_organization" "org" {}
diff --git a/s3-config-org/data.tf b/s3-config-org/data.tf
new file mode 120000
index 0000000..995624d
--- /dev/null
+++ b/s3-config-org/data.tf
@@ -0,0 +1 @@
+../common/data.tf
\ No newline at end of file
diff --git a/s3-config-org/defaults.tf b/s3-config-org/defaults.tf
new file mode 120000
index 0000000..a5556ac
--- /dev/null
+++ b/s3-config-org/defaults.tf
@@ -0,0 +1 @@
+../common/defaults.tf
\ No newline at end of file
diff --git a/s3-config-org/kms.tf b/s3-config-org/kms.tf
new file mode 100644
index 0000000..3ebbbbb
--- /dev/null
+++ b/s3-config-org/kms.tf
@@ -0,0 +1,139 @@
+locals {
+ kms_key_name = format("%v%v", local._prefixes["kms"], local.key_name)
+ kms_admin_root = format("arn:%v:iam::%v:root", local.partition, local.account_id)
+ kms_admin_roles = var.kms_admin_roles
+ kms_policy_document = var.kms_policy_document != null ? var.kms_policy_document : data.aws_iam_policy_document.empty.json
+}
+
+resource "aws_kms_key" "key" {
+ description = "KMS CMK for Organization Config S3"
+ enable_key_rotation = true
+ policy = data.aws_iam_policy_document.key_policy_combined.json
+
+ tags = merge(
+ local.base_tags,
+ var.tags,
+ {
+ "boc:aws:region" = local.region
+ Name = local.kms_key_name
+ },
+ )
+}
+
+resource "aws_kms_alias" "key" {
+ name = "alias/${local.kms_key_name}"
+ target_key_id = aws_kms_key.key.key_id
+}
+
+data "aws_iam_policy_document" "key_policy_combined" {
+ source_policy_documents = [
+ data.aws_iam_policy_document.key.json,
+ data.aws_iam_policy_document.key_admin.json,
+ local.kms_policy_document
+ ]
+}
+
+data "aws_iam_policy_document" "key_admin" {
+ dynamic "statement" {
+ for_each = length(local.kms_admin_roles) > 0 ? [1] : []
+ content {
+ sid = "BuiltinKMSAdminRoles"
+ effect = "Allow"
+ actions = ["kms:*"]
+ resources = ["*"]
+ principals {
+ type = "AWS"
+ identifiers = local.kms_admin_roles
+ }
+ }
+ }
+}
+
+data "aws_iam_policy_document" "empty" {}
+
+data "aws_iam_policy_document" "key" {}
+
+## data "aws_iam_policy_document" "key" {
+## policy_id = "object-logging-cloud-trail"
+## # manage key by root and other principals
+## statement {
+## sid = "IAMPermissionsAccessKMSManagement"
+## effect = "Allow"
+## actions = ["kms:*"]
+## resources = ["*"]
+## principals {
+## type = "AWS"
+## identifiers = [local.kms_admin_root]
+## }
+## }
+## # let cloudtrial, logs, sns, and sqs find key
+## statement {
+## sid = "KMSDescribeKeyFromServices"
+## effect = "Allow"
+## actions = ["kms:DescribeKey"]
+## resources = ["*"]
+## principals {
+## type = "Service"
+## identifiers = ["cloudtrail.amazonaws.com", "sns.amazonaws.com", "sqs.amazonaws.com"]
+## }
+## }
+## statement {
+## sid = "OrgLoggingKMSEncryptAccess"
+## effect = "Allow"
+## actions = [
+## "kms:Decrypt*",
+## "kms:Encrypt*",
+## "kms:ReEncrypt*",
+## "kms:GenerateDataKey",
+## ]
+## resources = ["*"]
+## principals {
+## type = "Service"
+## identifiers = ["cloudtrail.amazonaws.com"]
+## }
+## # condition {
+## # test = "StringLike"
+## # variable = "kms:EncryptionContext:aws:cloudtrail:arn"
+## # values = [format("arn:%v:cloudtrail:*:%v:trail/*", local.partition, local.account_id)]
+## # }
+## }
+## # https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html
+## statement {
+## sid = "Cloudwatch"
+## effect = "Allow"
+## actions = [
+## "kms:Decrypt*",
+## "kms:Encrypt*",
+## "kms:ReEncrypt*",
+## "kms:GenerateDataKey*",
+## "kms:Describe*"
+## ]
+## resources = ["*"]
+## principals {
+## type = "Service"
+## identifiers = ["logs.amazonaws.com", "logs.${local.region}.amazonaws.com"]
+## }
+## condition {
+## test = "StringLike"
+## variable = "kms:EncryptionContext:aws:logs:arn"
+## values = [format("arn:%v:logs:%v:%v:log-group:*", local.partition, local.region, local.account_id)]
+## }
+## }
+## # https://aws.amazon.com/blogs/compute/encrypting-messages-published-to-amazon-sns-with-aws-kms/
+## # https://docs.aws.amazon.com/sns/latest/dg/sns-key-management.html#sns-what-permissions-for-sse
+## # https://docs.aws.amazon.com/sns/latest/dg/sns-enable-encryption-for-topic-sqs-queue-subscriptions.html
+## statement {
+## sid = "ServiceMSAccess"
+## effect = "Allow"
+## actions = [
+## "kms:Decrypt*",
+## "kms:GenerateDataKey*",
+## "kms:Describe*"
+## ]
+## resources = ["*"]
+## principals {
+## type = "Service"
+## identifiers = ["sns.amazonaws.com", "sqs.amazonaws.com"]
+## }
+## }
+## }
diff --git a/s3-config-org/main.tf b/s3-config-org/main.tf
new file mode 100644
index 0000000..d872372
--- /dev/null
+++ b/s3-config-org/main.tf
@@ -0,0 +1,172 @@
+/*
+* # aws-inf-setup :: s3-config-org
+*
+* This set up the needed components for an organization-use S3 config bucket. THis will exist in each available
+* region.
+*
+* * S3 bucket
+*
+* # Usage
+* Here is a simple example, the one most commonly expected to be used.
+*
+* ```hcl
+* module "config" {
+* source = "git@github.e.it.census.gov:terraform-modules/aws-inf-setup.git//s3-config-org"
+* }
+* ```
+*
+* # Links
+* * https://cloudyadvice.com/2022/04/14/automated-enterprise-deployment-of-aws-config/
+*/
+
+locals {
+ account_id = var.account_id != "" ? var.account_id : data.aws_caller_identity.current.account_id
+ logs_region = data.aws_region.current.name
+ account_environment = data.aws_arn.current.partition == "aws-us-gov" ? "gov" : "ew"
+
+ bucket_name = var.bucket_name != "" ? var.bucket_name : format("%v-%v-%v", var.bucket_name_prefix, local.account_id, local.region)
+ key_name = compact(var.key_name, var.bucket_name, var.bucket_name_prefix)[0]
+
+ base_tags = {
+ "Organization" = "census:aditcio:csvd"
+ "boc:tf_module_version" = local._module_version
+ "boc:created_by" = "terraform"
+ }
+}
+
+resource "aws_s3_bucket" "config_org" {
+ bucket = local.bucket_name
+ force_destroy = var.force_destroy
+
+ lifecycle {
+ prevent_destroy = false
+ }
+
+ tags = merge(
+ local.base_tags,
+ var.tags,
+ { "Name" = local.name },
+ )
+}
+
+resource "aws_s3_bucket_server_side_encryption_configuration" "config_org" {
+ bucket = aws_s3_bucket.config_org.id
+ rule {
+ apply_server_side_encryption_by_default {
+ kms_master_key_id = var.kms_key_arn
+ sse_algorithm = "aws:kms"
+ }
+ bucket_key_enabled = true
+ }
+}
+
+
+resource "aws_s3_bucket_logging" "config_org" {
+ bucket = aws_s3_bucket.config_org.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" "config_org" {
+ count = 0
+ bucket = aws_s3_bucket.config_org.id
+ # acl = "private"
+ acl = "log-delivery-write"
+}
+
+resource "aws_s3_bucket_ownership_controls" "config_org" {
+ bucket = aws_s3_bucket.config_org.id
+
+ rule {
+ object_ownership = "BucketOwnerEnforced"
+ }
+}
+
+# no versioning on logs
+resource "aws_s3_bucket_versioning" "config_org" {
+ bucket = aws_s3_bucket.config_org.id
+ versioning_configuration {
+ status = "Suspended"
+ }
+}
+
+#---
+# 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", "s3:ListBucket"]
+ principals {
+ type = "Service"
+ identifiers = ["logging.amazonaws.com"]
+ }
+ resources = [aws_s3_bucket.config_org.arn]
+ condition {
+ test = "StringEquals"
+ variable = "aws:PrincipalOrgId"
+ values = [local.organization_id]
+ }
+ }
+ statement {
+ sid = "AWSLoggingWrite"
+ effect = "Allow"
+ actions = ["s3:PutObject"]
+ principals {
+ type = "Service"
+ identifiers = ["logging.amazonaws.com"]
+ }
+ resources = [format("%v/*", aws_s3_bucket.config_org.arn)]
+ condition {
+ test = "StringEquals"
+ variable = "s3:x-amz-acl"
+ values = ["bucket-owner-full-control"]
+ }
+ condition {
+ test = "StringEquals"
+ variable = "aws:PrincipalOrgId"
+ values = [data.organization_id]
+ }
+ }
+ # key access
+}
+
+#---
+# apply policy to bucket and public access block policy to bucket
+#---
+resource "aws_s3_bucket_policy" "policy" {
+ bucket = aws_s3_bucket.config_org.bucket
+ policy = data.aws_iam_policy_document.bucket_policy.json
+ depends_on = [time_sleep.policy_delay]
+}
+
+resource "aws_s3_bucket_public_access_block" "config_org" {
+ bucket = aws_s3_bucket.config_org.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.config_org.id
+## }
+## provisioner "local-exec" {
+## when = create
+## command = "sleep 180"
+## }
+## }
+
+resource "time_sleep" "policy_delay" {
+ triggers = {
+ bucket = aws_s3_bucket.config_org.id
+ }
+ create_duration = "180s"
+}
diff --git a/s3-config-org/outputs.tf b/s3-config-org/outputs.tf
new file mode 100644
index 0000000..14ec1e7
--- /dev/null
+++ b/s3-config-org/outputs.tf
@@ -0,0 +1,24 @@
+output "bucket_id" {
+ description = "Organization Config S3 bucket ID"
+ value = aws_s3_bucket.config_org.id
+}
+
+output "bucket_arn" {
+ description = "Organization Config S3 bucket ARN"
+ value = aws_s3_bucket.config_org.arn
+}
+
+output "kms_key_id" {
+ description = "Organization Config S3 Key ID"
+ value = aws_kms_key.key.id
+}
+
+output "kms_key_arn" {
+ description = "Organization Config S3 Key ARN"
+ value = aws_kms_key.key.arn
+}
+
+output "kms_alias_name" {
+ description = "Organization Config S3 Key Alias name"
+ value = aws_kms_alias.key.arn
+}
diff --git a/s3-config-org/prefixes.tf b/s3-config-org/prefixes.tf
new file mode 120000
index 0000000..7e265d5
--- /dev/null
+++ b/s3-config-org/prefixes.tf
@@ -0,0 +1 @@
+../common/prefixes.tf
\ No newline at end of file
diff --git a/s3-config-org/variables.common.tf b/s3-config-org/variables.common.tf
new file mode 120000
index 0000000..7439ed8
--- /dev/null
+++ b/s3-config-org/variables.common.tf
@@ -0,0 +1 @@
+../common/variables.common.tf
\ No newline at end of file
diff --git a/s3-config-org/variables.kms.tf b/s3-config-org/variables.kms.tf
new file mode 100644
index 0000000..9e647c1
--- /dev/null
+++ b/s3-config-org/variables.kms.tf
@@ -0,0 +1,17 @@
+variable "key_name" {
+ description = "Name to apply to Org Logging KMS Key (default: k-inf-org-logging)"
+ type = string
+ default = null
+}
+
+variable "kms_policy_document" {
+ description = "AWS KMS Key Policy Document JSON, merged with admin policy document"
+ type = string
+ default = null
+}
+
+variable "kms_admin_roles" {
+ description = "AWS KMS Key administrative role(s) which have full access to the key. The root user is included by default."
+ type = list(string)
+ default = []
+}
diff --git a/s3-config-org/variables.tf b/s3-config-org/variables.tf
new file mode 100644
index 0000000..2e5c126
--- /dev/null
+++ b/s3-config-org/variables.tf
@@ -0,0 +1,17 @@
+variable "bucket_name" {
+ description = "Organization Config S3 bucket name"
+ type = string
+ default = null
+}
+
+variable "bucket_name_prefix" {
+ description = "Organization Config S3 bucket prefix, prepended to the AWS account ID and region to make the bucket name."
+ type = string
+ default = "inf-org-config"
+}
+
+variable "component_tags" {
+ description = "Additional tags for Components (s3, kms)"
+ type = map(map(string))
+ default = { "s3" = {}, "kms" = {}, }
+}
diff --git a/s3-config-org/version.tf b/s3-config-org/version.tf
new file mode 120000
index 0000000..b83c5b7
--- /dev/null
+++ b/s3-config-org/version.tf
@@ -0,0 +1 @@
+../common/version.tf
\ No newline at end of file