diff --git a/common/README.md b/common/README.md
deleted file mode 100644
index 41623d7..0000000
--- a/common/README.md
+++ /dev/null
@@ -1,64 +0,0 @@
-## Requirements
-
-No requirements.
-
-## Providers
-
-| Name | Version |
-|------|---------|
-| [aws](#provider\_aws) | n/a |
-| [null](#provider\_null) | 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.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
-| [aws_s3_bucket_object.this_objects](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_object) | 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 |
-| [null_resource.policy_delay](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.bucket_policy_combined](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_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_iam_policy_document.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
-| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |
-
-## Inputs
-
-| Name | Description | Type | Default | Required |
-|------|-------------|------|---------|:--------:|
-| [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) | Access log bucket prefix, to which the bucket name will be appended to make the target\_prefix | `string` | `"s3"` | no |
-| [allowed\_cidr](#input\_allowed\_cidr) | List of allowed source IPs (NOT from within the VPC). If empty, there will be no restrictions on source IP. If provided, you must also use allowed\_endpoints for access within a VPC. | `list(string)` | `[]` | no |
-| [allowed\_endpoints](#input\_allowed\_endpoints) | List of allowed VPC endpoint IDs. If used, it will enable access to the bucket from the specific VPC endpoints. | `list(string)` | `[]` | no |
-| [bucket\_folders](#input\_bucket\_folders) | List of folders (keys) to create after creation of bucket. They will have object metadata provided based on metadata\_tags and data\_safeguard labels. | `list(string)` | `[]` | no |
-| [bucket\_name](#input\_bucket\_name) | AWS Bucket Name. Standard prefix will be applied here, do not include here. | `string` | n/a | yes |
-| [bucket\_policy\_document](#input\_bucket\_policy\_document) | IAM Policy document describing additiona policy to be attached to the bucket beyond the default | `string` | `""` | no |
-| [force\_destroy](#input\_force\_destroy) | Sets force\_destroy to allow the bucket and contents to be deleted. The deletion may take a very long time based on the number of objects. You normally want to update this to true, apply, and then destroy the resource. | `bool` | `false` | 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\_key\_arn](#input\_kms\_key\_arn) | AWS KMS Key ARN, a key created external to this module call. | `string` | `null` | no |
-| [kms\_key\_id](#input\_kms\_key\_id) | AWS KMS Key ID (one per bucket). This is currently ignored (and deprecated). | `string` | `null` | no |
-| [kms\_policy\_document](#input\_kms\_policy\_document) | AWS KMS Key Policy Document JSON, merged with admin policy document | `string` | `""` | no |
-| [metadata\_tags](#input\_metadata\_tags) | AWS S3 Custom metadata (prefix x-amzn-meta- automatically included, not needed here). If data\_safeguard labels are applied, they will be incorporated on any bucket objects created. | `map(string)` | `{}` | no |
-| [require\_explicit\_encryption](#input\_require\_explicit\_encryption) | When enabled, adds bucket policy to Deny unencrypted uploads and incorrect encryption header. Should not normally be needed. | `bool` | `false` | 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 |
-|------|-------------|
-| [kms\_key\_alias](#output\_kms\_key\_alias) | Created KMS Key Alias name |
-| [kms\_key\_arn](#output\_kms\_key\_arn) | Created KMS Key ARN |
-| [kms\_key\_id](#output\_kms\_key\_id) | Created KMS Key ID |
-| [s3\_bucket\_arn](#output\_s3\_bucket\_arn) | Created S3 Bucket ARN |
-| [s3\_bucket\_id](#output\_s3\_bucket\_id) | Created S3 Bucket ID |
diff --git a/common/kms.tf b/common/kms.tf
new file mode 100644
index 0000000..b42ca3e
--- /dev/null
+++ b/common/kms.tf
@@ -0,0 +1,66 @@
+# data "aws_kms_key" "incoming_key" {
+# count = var.kms_key_arn != null ? 1 : 0
+# key_id = var.kms_key_arn
+# }
+#
+locals {
+ kms_key_arn = var.kms_key_arn != null ? var.kms_key_arn : aws_kms_key.key.arn
+ kms_key_name = format("%s%s", local._prefixes["kms"], local.name)
+
+ kms_admin_root = [format("arn:%v:iam::%v:root", local.partition, local.account_id)]
+ kms_admin_roles = compact(concat(local.kms_admin_root, var.kms_admin_roles))
+ kms_policy_document = length(var.kms_policy_document) > 0 ? var.kms_policy_document : data.aws_iam_policy_document.empty.json
+}
+
+#---
+# create a key and alias if not specified
+#---
+resource "aws_kms_key" "key" {
+ count = var.kms_key_arn == null ? 1 : 0
+ description = "KMS CMK for S3 bucket ${local.name}"
+ enable_key_rotation = true
+ policy = data.aws_iam_policy_document.key_policy_combined.json
+
+ tags = merge(
+ local.base_tags,
+ var.tags,
+ local.enforced_tags,
+ map("Name", local.kms_key_name)
+ )
+}
+
+resource "aws_kms_alias" "key" {
+ count = var.kms_key_arn == null ? 1 : 0
+ name = "alias/${local.kms_key_name}"
+ target_key_id = var.kms_key_arn == null ? aws_kms_key.key[0].key_id : null
+}
+
+# auto includes root
+data "aws_iam_policy_document" "key_admin" {
+ statement {
+ sid = "BuiltinKMSAdminRoles"
+ effect = "Allow"
+ actions = ["kms:*"]
+ resources = ["*"]
+ principals {
+ type = "AWS"
+ identifiers = local.kms_admin_roles
+ }
+ }
+}
+
+data "aws_iam_policy_document" "key_policy_combined" {
+ source_policy_documents = [
+ data.aws_iam_policy_document.key_admin.json,
+ local.kms_policy_document
+ ]
+}
+
+data "aws_iam_policy_document" "bucket_policy_combined" {
+ source_policy_documents = [
+ data.aws_iam_policy_document.this.json,
+ local.bucket_policy_document
+ ]
+}
+
+data "aws_iam_policy_document" "empty" {}
diff --git a/common/outputs.kms.tf b/common/outputs.kms.tf
new file mode 100644
index 0000000..4ad613a
--- /dev/null
+++ b/common/outputs.kms.tf
@@ -0,0 +1,18 @@
+#---
+# key
+#---
+output "kms_key_id" {
+ description = "Created KMS Key ID"
+ value = aws_kms_key.key.id
+}
+
+output "kms_key_arn" {
+ description = "Created KMS Key ARN"
+ value = aws_kms_key.key.arn
+}
+
+output "kms_key_alias" {
+ description = "Created KMS Key Alias name"
+ value = aws_kms_alias.key.name
+}
+
diff --git a/common/outputs.s3.tf b/common/outputs.s3.tf
new file mode 100644
index 0000000..a29a743
--- /dev/null
+++ b/common/outputs.s3.tf
@@ -0,0 +1,12 @@
+#---
+# bucket
+#---
+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
+}
diff --git a/common/variables.common.tf b/common/variables.common.tf
new file mode 100644
index 0000000..010acce
--- /dev/null
+++ b/common/variables.common.tf
@@ -0,0 +1,6 @@
+variable "tags" {
+ description = "AWS Tags to apply to appropriate resources (S3, KMS). Do not include safeguard tags here, use the data_safeguard field for such things."
+ type = map(string)
+ default = {}
+}
+
diff --git a/common/variables.kms.tf b/common/variables.kms.tf
new file mode 100644
index 0000000..3fe6f33
--- /dev/null
+++ b/common/variables.kms.tf
@@ -0,0 +1,23 @@
+variable "kms_key_id" {
+ description = "AWS KMS Key ID (one per bucket). This is currently ignored (and deprecated)."
+ type = string
+ default = null
+}
+
+variable "kms_key_arn" {
+ description = "AWS KMS Key ARN, a key created external to this module call."
+ type = string
+ default = null
+}
+
+variable "kms_policy_document" {
+ description = "AWS KMS Key Policy Document JSON, merged with admin policy document"
+ type = string
+ default = ""
+}
+
+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/common/variables.s3.tf b/common/variables.s3.tf
new file mode 100644
index 0000000..6f15cf3
--- /dev/null
+++ b/common/variables.s3.tf
@@ -0,0 +1,62 @@
+variable "bucket_name" {
+ description = "AWS Bucket Name. Standard prefix will be applied here, do not include here."
+ type = string
+}
+
+variable "bucket_folders" {
+ description = "List of folders (keys) to create after creation of bucket. They will have object metadata provided based on metadata_tags and data_safeguard labels."
+ type = list(string)
+ default = []
+}
+
+variable "bucket_policy_document" {
+ description = "IAM Policy document describing additiona policy to be attached to the bucket beyond the default"
+ type = string
+ default = ""
+}
+
+variable "metadata_tags" {
+ description = "AWS S3 Custom metadata (prefix x-amzn-meta- automatically included, not needed here). If data_safeguard labels are applied, they will be incorporated on any bucket objects created."
+ type = map(string)
+ default = {}
+}
+
+variable "access_log_bucket_prefix" {
+ description = "Access log bucket prefix, to which the bucket name will be appended to make the target_prefix"
+ type = string
+ default = "s3"
+}
+
+variable "access_log_bucket" {
+ description = "Server Access Logging Bucket ID"
+ type = string
+ # default = null
+}
+
+variable "allowed_cidr" {
+ description = "List of allowed source IPs (NOT from within the VPC). If empty, there will be no restrictions on source IP. If provided, you must also use allowed_endpoints for access within a VPC."
+ type = list(string)
+ default = []
+}
+
+variable "allowed_endpoints" {
+ description = "List of allowed VPC endpoint IDs. If used, it will enable access to the bucket from the specific VPC endpoints."
+ type = list(string)
+ default = []
+}
+
+variable "force_destroy" {
+ description = "Sets force_destroy to allow the bucket and contents to be deleted. The deletion may take a very long time based on the number of objects. You normally want to update this to true, apply, and then destroy the resource."
+ type = bool
+ default = false
+}
+
+variable "require_explicit_encryption" {
+ description = "When enabled, adds bucket policy to Deny unencrypted uploads and incorrect encryption header. Should not normally be needed."
+ type = bool
+ default = false
+}
+
+# TBD
+# variable "kms_policy_read_arns" { }
+# variable "kms_policy_write_arns" { }
diff --git a/examples/policy/README.md b/examples/policy/README.md
new file mode 100644
index 0000000..0acfbad
--- /dev/null
+++ b/examples/policy/README.md
@@ -0,0 +1,28 @@
+## Requirements
+
+No requirements.
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | n/a |
+
+## Modules
+
+No modules.
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_iam_policy.policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
+| [aws_iam_policy_document.policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+
+## Inputs
+
+No inputs.
+
+## Outputs
+
+No outputs.
diff --git a/standard/kms.tf b/standard/kms.tf
new file mode 120000
index 0000000..b0b3f29
--- /dev/null
+++ b/standard/kms.tf
@@ -0,0 +1 @@
+../common/kms.tf
\ No newline at end of file
diff --git a/standard/outputs.kms.tf b/standard/outputs.kms.tf
new file mode 120000
index 0000000..74a8d7b
--- /dev/null
+++ b/standard/outputs.kms.tf
@@ -0,0 +1 @@
+../common/outputs.kms.tf
\ No newline at end of file
diff --git a/standard/outputs.s3.tf b/standard/outputs.s3.tf
new file mode 120000
index 0000000..594685a
--- /dev/null
+++ b/standard/outputs.s3.tf
@@ -0,0 +1 @@
+../common/outputs.s3.tf
\ No newline at end of file
diff --git a/standard/variables.common.tf b/standard/variables.common.tf
new file mode 120000
index 0000000..7439ed8
--- /dev/null
+++ b/standard/variables.common.tf
@@ -0,0 +1 @@
+../common/variables.common.tf
\ No newline at end of file
diff --git a/standard/variables.kms.tf b/standard/variables.kms.tf
new file mode 120000
index 0000000..08cab47
--- /dev/null
+++ b/standard/variables.kms.tf
@@ -0,0 +1 @@
+../common/variables.kms.tf
\ No newline at end of file
diff --git a/standard/variables.s3.tf b/standard/variables.s3.tf
new file mode 120000
index 0000000..49213df
--- /dev/null
+++ b/standard/variables.s3.tf
@@ -0,0 +1 @@
+../common/variables.s3.tf
\ No newline at end of file
diff --git a/title26/kms.tf b/title26/kms.tf
new file mode 120000
index 0000000..b0b3f29
--- /dev/null
+++ b/title26/kms.tf
@@ -0,0 +1 @@
+../common/kms.tf
\ No newline at end of file
diff --git a/title26/outputs.kms.tf b/title26/outputs.kms.tf
new file mode 120000
index 0000000..74a8d7b
--- /dev/null
+++ b/title26/outputs.kms.tf
@@ -0,0 +1 @@
+../common/outputs.kms.tf
\ No newline at end of file
diff --git a/title26/outputs.s3.tf b/title26/outputs.s3.tf
new file mode 120000
index 0000000..594685a
--- /dev/null
+++ b/title26/outputs.s3.tf
@@ -0,0 +1 @@
+../common/outputs.s3.tf
\ No newline at end of file
diff --git a/title26/variables.common.tf b/title26/variables.common.tf
new file mode 120000
index 0000000..7439ed8
--- /dev/null
+++ b/title26/variables.common.tf
@@ -0,0 +1 @@
+../common/variables.common.tf
\ No newline at end of file
diff --git a/title26/variables.kms.tf b/title26/variables.kms.tf
new file mode 120000
index 0000000..08cab47
--- /dev/null
+++ b/title26/variables.kms.tf
@@ -0,0 +1 @@
+../common/variables.kms.tf
\ No newline at end of file
diff --git a/title26/variables.s3.tf b/title26/variables.s3.tf
new file mode 120000
index 0000000..49213df
--- /dev/null
+++ b/title26/variables.s3.tf
@@ -0,0 +1 @@
+../common/variables.s3.tf
\ No newline at end of file