diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f4873d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* + +# .tfvars files +*.tfvars + +.terraform/* +logs +common/README.md diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..85f5ae4 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,17 @@ +repos: +- repo: https://github.com/antonbabenko/pre-commit-terraform + rev: v1.31.0 + hooks: +# - id: terraform_validate + - id: terraform_fmt + - id: terraform_docs_replace + args: ['table'] + exclude: common/*.tf + exclude: version.tf + +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.1.0 + hooks: + - id: check-symlinks + - id: detect-aws-credentials + - id: detect-private-key diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..72de437 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ +# Versions + +* v1.0 -- 20210218 + - initial creation + - module: terraform-state + diff --git a/README.md b/README.md index 97f16ee..dc81bdb 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,27 @@ # aws-inf-setup -AWS Infrastructure Setup + +This contains a bunch of submodules used for setting up an AWS account, to the standard configurations we use +at Census. + +## Submodules + +### [terraform-state](terraform-state) + +This creates an S3 bucket, KMS key, and DynamoDB table for use with an AWS account. The bucket region is important +for connection to the remote state. Key ARN and ID, S3 bucket ID, and DDB tables are exportet, but they follow a standard +structure so they are not really needed. The bucket is `inf-tfstate-{account_id}`. + +This has no other dependencies, since it has to be created first. Only one is needed per account. + +### splunk-user + +### access-logging-bucket + +This sets up the S3 bucket used for access logs. One is needed per region, and the region and account are included +in the bucket names: `inf-log-{account_id}-{region}`. + +### object-logging +### cloudtrail +### config +### gpg-key + diff --git a/access-logging-bucket/version.tf b/access-logging-bucket/version.tf new file mode 120000 index 0000000..b83c5b7 --- /dev/null +++ b/access-logging-bucket/version.tf @@ -0,0 +1 @@ +../common/version.tf \ No newline at end of file diff --git a/cloudtrail/version.tf b/cloudtrail/version.tf new file mode 120000 index 0000000..b83c5b7 --- /dev/null +++ b/cloudtrail/version.tf @@ -0,0 +1 @@ +../common/version.tf \ No newline at end of file diff --git a/common/data.tf b/common/data.tf new file mode 100644 index 0000000..30905b7 --- /dev/null +++ b/common/data.tf @@ -0,0 +1,23 @@ +data "aws_caller_identity" "current" {} + +data "aws_arn" "current" { + arn = data.aws_caller_identity.current.arn +} + +data "aws_region" "current" {} + +# output "caller_account_id" { +# value = data.aws_caller_identity.current.account_id +# } +# +# output "account_caller_arn" { +# value = data.aws_caller_identity.current.arn +# } +# +# output "account_caller_arn_partition" { +# value = data.aws_arn.current.partition +# } +# +# output "account_region"name" { +# value = data.aws_region.current.name +# } diff --git a/common/defaults.tf b/common/defaults.tf new file mode 100644 index 0000000..c67478b --- /dev/null +++ b/common/defaults.tf @@ -0,0 +1,5 @@ + +locals { + _defaults = { + } +} diff --git a/common/prefixes.tf b/common/prefixes.tf new file mode 100644 index 0000000..fafcbde --- /dev/null +++ b/common/prefixes.tf @@ -0,0 +1,12 @@ +locals { + _prefixes = { + "efs" = "v-efs-" + "s3" = "v-s3-" + "ebs" = "v-ebs-" + "kms" = "k-kms-" + "role" = "r-" + "policy" = "p-" + "security-group" = "" + # "security-group" = "sg-" + } +} diff --git a/common/variables.common.tf b/common/variables.common.tf new file mode 100644 index 0000000..2bc450a --- /dev/null +++ b/common/variables.common.tf @@ -0,0 +1,86 @@ +#--- +# account info +#--- +variable "account_id" { + description = "AWS Account ID (default will pull from current user)" + type = string + default = "" +} + +variable "account_alias" { + description = "AWS Account Alias (required)" + type = string +} + +variable "override_prefixes" { + description = "Override built-in prefixes by component (efs, s3, ebs, kms, role, policy, security-group). This should be used primarily for common infrastructure things" + type = map(string) + default = {} +} + +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 = {} +} + +## # s3 +## 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 "kms_key_id" { +## description = "AWS KMS Key ID (one per bucket). This is currently ignored." +## 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 "lifecycle_rules" { +## # description = "Setup lifecycle rules (in-progress, not working)" +## # type = map() +## # default = {} +## # } +## diff --git a/common/version.tf b/common/version.tf new file mode 100644 index 0000000..107272c --- /dev/null +++ b/common/version.tf @@ -0,0 +1,3 @@ +locals { + _module_version = "1.0" +} diff --git a/config/version.tf b/config/version.tf new file mode 120000 index 0000000..b83c5b7 --- /dev/null +++ b/config/version.tf @@ -0,0 +1 @@ +../common/version.tf \ No newline at end of file diff --git a/gpg-key/version.tf b/gpg-key/version.tf new file mode 120000 index 0000000..b83c5b7 --- /dev/null +++ b/gpg-key/version.tf @@ -0,0 +1 @@ +../common/version.tf \ No newline at end of file diff --git a/object-logging/version.tf b/object-logging/version.tf new file mode 120000 index 0000000..b83c5b7 --- /dev/null +++ b/object-logging/version.tf @@ -0,0 +1 @@ +../common/version.tf \ No newline at end of file diff --git a/terraform-state/README.md b/terraform-state/README.md new file mode 100644 index 0000000..e850cf5 --- /dev/null +++ b/terraform-state/README.md @@ -0,0 +1,101 @@ +# aws-inf-setup :: terraform-state + +This set up the needed components for the Terraform remote state: + +* S3 bucket +* KMS key for the bucket +* DynamoDB table for locking + +# Usage +Here is a simple example, the one most commonly expected to be used. + +```hcl +module "tfstate" { + source = "git@github.e.it.census.gov:terraform-modules/aws-inf-setup.git//terraform-state" + + account_alias = "do2-govcloud" +} +``` + +This one can be used if you need to customize stuff, though really, the defaults are all built +for a reason, and deployment code (i.e., Ansible) will expect these defaults to be used in +variable file generation. + +```hcl +module "tfstate_full" { + source = "git@github.e.it.census.gov:terraform-modules/aws-inf-setup.git//terraform-state" + + # required + account_alias = "do2-govcloud" + + # optional, defaults + tfstate_key_prefix = "do2-govcloud" + kms_tfstate_key = "k-kms-inf-tfstate" + tfstate_table = "tf_remote_state" + tfstate_bucket = "inf-tfstate-123456789012" + tfstate_bucket_prefix = "inf-tfstate" + tfstate_key_suffix = "terraform.tfstate" + + # this is generally not needed and not recommended + component_tags = { + "s3" = { + "SpecialTag1" = "something" + "SpecialTag2" = "somethingElse" + } + } +} +``` + +## Requirements + +No requirements. + +## Providers + +| Name | Version | +|------|---------| +| aws | n/a | + +## Modules + +No Modules. + +## Resources + +| Name | +|------| +| [aws_arn](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/arn) | +| [aws_caller_identity](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | +| [aws_dynamodb_table](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_table) | +| [aws_iam_policy_document](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | +| [aws_kms_alias](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_alias) | +| [aws_kms_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | +| [aws_region](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | +| [aws_s3_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | +| [aws_s3_bucket_public_access_block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| account\_alias | AWS Account Alias (required) | `string` | n/a | yes | +| account\_id | AWS Account ID (default will pull from current user) | `string` | `""` | no | +| component\_tags | Additional tags for Components (s3, kms, ddb) | `map(map(string))` |
{
"ddb": {},
"kms": {},
"s3": {}
} | no |
+| kms\_tfstate\_key | Terraform remote state KMS key alias | `string` | `"k-kms-inf-tfstate"` | no |
+| 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 | 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 |
+| tfstate\_bucket | Terraform remote state S3 bucket | `string` | `""` | no |
+| tfstate\_bucket\_prefix | Terraform remote state S3 bucket prefix, prepended to the AWS account ID to make the bucket name. | `string` | `"inf-tfstate"` | no |
+| tfstate\_key\_prefix | Terraform remote state S3 bucket prefix (account alias) | `string` | `""` | no |
+| tfstate\_key\_suffix | Terraform remote state S3 bucket suffix | `string` | `"terraform.tfstate"` | no |
+| tfstate\_region | Terraform remote state S3 bucket region | `string` | `""` | no |
+| tfstate\_table | Terraform remote state table | `string` | `"tf_remote_state"` | no |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| tfstate\_bucket\_arn | Terraform state S3 bucket ARN |
+| tfstate\_bucket\_id | Terraform state S3 bucket ID |
+| tfstate\_dynamodb\_arn | Terraform state DynamoDB table ARN |
+| tfstate\_key\_arn | inf-tfstate KMS key ARN |
diff --git a/terraform-state/data.tf b/terraform-state/data.tf
new file mode 120000
index 0000000..995624d
--- /dev/null
+++ b/terraform-state/data.tf
@@ -0,0 +1 @@
+../common/data.tf
\ No newline at end of file
diff --git a/terraform-state/defaults.tf b/terraform-state/defaults.tf
new file mode 120000
index 0000000..a5556ac
--- /dev/null
+++ b/terraform-state/defaults.tf
@@ -0,0 +1 @@
+../common/defaults.tf
\ No newline at end of file
diff --git a/terraform-state/main.tf b/terraform-state/main.tf
new file mode 100644
index 0000000..886b96d
--- /dev/null
+++ b/terraform-state/main.tf
@@ -0,0 +1,175 @@
+/*
+* # aws-inf-setup :: terraform-state
+*
+* This set up the needed components for the Terraform remote state:
+*
+* * S3 bucket
+* * KMS key for the bucket
+* * DynamoDB table for locking
+*
+* # Usage
+* Here is a simple example, the one most commonly expected to be used.
+*
+* ```hcl
+* module "tfstate" {
+* source = "git@github.e.it.census.gov:terraform-modules/aws-inf-setup.git//terraform-state"
+*
+* account_alias = "do2-govcloud"
+* }
+* ```
+*
+* This one can be used if you need to customize stuff, though really, the defaults are all built
+* for a reason, and deployment code (i.e., Ansible) will expect these defaults to be used in
+* variable file generation.
+*
+* ```hcl
+* module "tfstate_full" {
+* source = "git@github.e.it.census.gov:terraform-modules/aws-inf-setup.git//terraform-state"
+*
+* # required
+* account_alias = "do2-govcloud"
+*
+* # optional, defaults
+* tfstate_key_prefix = "do2-govcloud"
+* kms_tfstate_key = "k-kms-inf-tfstate"
+* tfstate_table = "tf_remote_state"
+* tfstate_bucket = "inf-tfstate-123456789012"
+* tfstate_bucket_prefix = "inf-tfstate"
+* tfstate_key_suffix = "terraform.tfstate"
+*
+* # this is generally not needed and not recommended
+* component_tags = {
+* "s3" = {
+* "SpecialTag1" = "something"
+* "SpecialTag2" = "somethingElse"
+* }
+* }
+* }
+* ```
+*/
+
+locals {
+ account_id = var.account_id != "" ? var.account_id : data.aws_caller_identity.current.account_id
+ tfstate_region = data.aws_region.current.name
+
+ tfstate_key_arn = aws_kms_key.tfstate_key.arn
+ tfstate_bucket = var.tfstate_bucket != "" ? var.tfstate_bucket : format("%v-%v", var.tfstate_bucket_prefix, local.account_id)
+
+ base_tags = {
+ "boc:tf_module_version" = local._module_version
+ "boc:created_by" = "terraform"
+ }
+}
+
+
+#---
+# dynamodb table
+#---
+resource "aws_dynamodb_table" "tfstate" {
+ name = var.tfstate_table
+ hash_key = "LockID"
+ billing_mode = "PROVISIONED"
+ read_capacity = 1
+ write_capacity = 1
+
+ attribute {
+ name = "LockID"
+ type = "S"
+ }
+
+ server_side_encryption {
+ enabled = true
+ }
+
+ tags = merge(
+ var.tags,
+ local.base_tags,
+ lookup(var.component_tags, "ddb", {}),
+ map("Name", var.tfstate_table),
+ )
+}
+
+# create iam policy for it, to apply to roles/groups as needed
+
+data "aws_iam_policy_document" "tfstate" {
+ statement {
+ sid = "TFRemoteStateList"
+ effect = "Allow"
+ resources = [aws_s3_bucket.tfstate.arn]
+ actions = ["s3:ListBucket"]
+ }
+
+ statement {
+ sid = "TFRemoteState"
+ effect = "Allow"
+ resources = ["${aws_s3_bucket.tfstate.arn}/*"]
+ actions = ["s3:GetObject", "s3:PutObject"]
+ }
+
+ statement {
+ sid = "TFRemoteStateDDB"
+ effect = "Allow"
+ resources = [aws_dynamodb_table.tfstate.arn]
+ actions = ["dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:DeleteItem"]
+ }
+}
+
+#---
+# s3
+#---
+resource "aws_s3_bucket" "tfstate" {
+ bucket = local.tfstate_bucket
+ acl = "private"
+
+ server_side_encryption_configuration {
+ rule {
+ apply_server_side_encryption_by_default {
+ kms_master_key_id = aws_kms_key.tfstate_key.arn
+ sse_algorithm = "aws:kms"
+ }
+ }
+ }
+
+ versioning {
+ enabled = true
+ }
+
+ lifecycle {
+ prevent_destroy = true
+ }
+
+ tags = merge(
+ var.tags,
+ local.base_tags,
+ lookup(var.component_tags, "s3", {}),
+ map("Name", local.tfstate_bucket),
+ )
+}
+
+resource "aws_s3_bucket_public_access_block" "tfstate" {
+ bucket = aws_s3_bucket.tfstate.id
+ block_public_acls = true
+ block_public_policy = true
+ ignore_public_acls = true
+ restrict_public_buckets = true
+}
+
+#---
+# kms
+#---
+resource "aws_kms_key" "tfstate_key" {
+ description = "inf-tfstate encryption key"
+ enable_key_rotation = true
+
+ tags = merge(
+ var.tags,
+ local.base_tags,
+ lookup(var.component_tags, "kms", {}),
+ map("Name", var.kms_tfstate_key)
+ )
+}
+
+resource "aws_kms_alias" "tfstate_key" {
+ name = "alias/${var.kms_tfstate_key}"
+ target_key_id = aws_kms_key.tfstate_key.key_id
+}
diff --git a/terraform-state/outputs.tf b/terraform-state/outputs.tf
new file mode 100644
index 0000000..e5a5fcf
--- /dev/null
+++ b/terraform-state/outputs.tf
@@ -0,0 +1,25 @@
+output "tfstate_key_arn" {
+ description = "inf-tfstate KMS key ARN"
+ value = aws_kms_key.tfstate_key.arn
+}
+
+output "tfstate_bucket_id" {
+ description = "Terraform state S3 bucket ID"
+ value = aws_s3_bucket.tfstate.id
+}
+
+output "tfstate_bucket_arn" {
+ description = "Terraform state S3 bucket ARN"
+ value = aws_s3_bucket.tfstate.arn
+}
+
+output "tfstate_dynamodb_arn" {
+ description = "Terraform state DynamoDB table ARN"
+ value = aws_dynamodb_table.tfstate.arn
+}
+
+output "tfstate_region" {
+ description = "Terraform state region"
+ value = local.tfstate_region
+}
+
diff --git a/terraform-state/prefixes.tf b/terraform-state/prefixes.tf
new file mode 120000
index 0000000..7e265d5
--- /dev/null
+++ b/terraform-state/prefixes.tf
@@ -0,0 +1 @@
+../common/prefixes.tf
\ No newline at end of file
diff --git a/terraform-state/variables.common.tf b/terraform-state/variables.common.tf
new file mode 120000
index 0000000..7439ed8
--- /dev/null
+++ b/terraform-state/variables.common.tf
@@ -0,0 +1 @@
+../common/variables.common.tf
\ No newline at end of file
diff --git a/terraform-state/variables.tf b/terraform-state/variables.tf
new file mode 100644
index 0000000..5430c4d
--- /dev/null
+++ b/terraform-state/variables.tf
@@ -0,0 +1,50 @@
+variable "kms_tfstate_key" {
+ description = "Terraform remote state KMS key alias"
+ type = string
+ default = "k-kms-inf-tfstate"
+}
+
+variable "tfstate_table" {
+ description = "Terraform remote state table"
+ type = string
+ default = "tf_remote_state"
+}
+
+variable "tfstate_bucket" {
+ description = "Terraform remote state S3 bucket"
+ type = string
+ # default = "inf-tfstate-{{ tf_account }}"
+ default = ""
+}
+
+variable "tfstate_bucket_prefix" {
+ description = "Terraform remote state S3 bucket prefix, prepended to the AWS account ID to make the bucket name."
+ type = string
+ default = "inf-tfstate"
+}
+
+variable "tfstate_key_prefix" {
+ description = "Terraform remote state S3 bucket prefix (account alias)"
+ type = string
+ default = ""
+ # default = "{{ tf_account_name | quote }}"
+}
+
+variable "tfstate_key_suffix" {
+ description = "Terraform remote state S3 bucket suffix"
+ type = string
+ default = "terraform.tfstate"
+}
+
+variable "tfstate_region" {
+ description = "Terraform remote state S3 bucket region"
+ type = string
+ # default = "{{ these_regions[0] | quote }}"
+ default = ""
+}
+
+variable "component_tags" {
+ description = "Additional tags for Components (s3, kms, ddb)"
+ type = map(map(string))
+ default = { "s3" = {}, "kms" = {}, "ddb" = {} }
+}
diff --git a/terraform-state/version.tf b/terraform-state/version.tf
new file mode 120000
index 0000000..b83c5b7
--- /dev/null
+++ b/terraform-state/version.tf
@@ -0,0 +1 @@
+../common/version.tf
\ No newline at end of file