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