Răsfoiți Sursa

Initial Commit

Copied modules.
Removed '0.1' from the directory structure.
Updated references to submodules with 0.1
Fred Damstra 5 ani în urmă
comite
5c4a96a001
99 a modificat fișierele cu 2723 adăugiri și 0 ștergeri
  1. 6 0
      .gitattributes
  2. 99 0
      .gitignore
  3. 5 0
      README.md
  4. 3 0
      base/README.md
  5. 4 0
      base/account_standards/README.md
  6. 7 0
      base/account_standards/main.tf
  7. 3 0
      base/account_standards/outputs.tf
  8. 10 0
      base/account_standards/vars.tf
  9. 3 0
      base/account_standards/version.tf
  10. 4 0
      base/globally_accessible_bucket/README.md
  11. 105 0
      base/globally_accessible_bucket/kms.tf
  12. 78 0
      base/globally_accessible_bucket/main.tf
  13. 3 0
      base/globally_accessible_bucket/outputs.tf
  14. 47 0
      base/globally_accessible_bucket/vars.tf
  15. 3 0
      base/globally_accessible_bucket/version.tf
  16. 6 0
      base/iam/bootstrap_mdradmin_policies/datasources.tf
  17. 5 0
      base/iam/bootstrap_mdradmin_policies/locals.tf
  18. 7 0
      base/iam/bootstrap_mdradmin_policies/main.tf
  19. 120 0
      base/iam/bootstrap_mdradmin_policies/policy-mdradmin_tfstate_setup.tf
  20. 12 0
      base/iam/bootstrap_mdradmin_policies/variables.tf
  21. 46 0
      base/iam/child_account_roles/README.md
  22. 4 0
      base/iam/child_account_roles/account_alias.tf
  23. 14 0
      base/iam/child_account_roles/assume_role_policy-non_saml.tf
  24. 4 0
      base/iam/child_account_roles/datasources.tf
  25. 12 0
      base/iam/child_account_roles/locals.tf
  26. 3 0
      base/iam/child_account_roles/module-policies.tf
  27. 25 0
      base/iam/child_account_roles/role-mdr_engineer_readonly.tf
  28. 10 0
      base/iam/child_account_roles/role-mdr_terraformer.tf
  29. 9 0
      base/iam/child_account_roles/variables.tf
  30. 4 0
      base/iam/child_account_roles/versions.tf
  31. 53 0
      base/iam/common_services_roles/README.md
  32. 3 0
      base/iam/common_services_roles/account_alias.tf
  33. 16 0
      base/iam/common_services_roles/assume_role_policy-non_saml.tf
  34. 22 0
      base/iam/common_services_roles/assume_role_policy-okta_saml.tf
  35. 16 0
      base/iam/common_services_roles/datasources.tf
  36. 12 0
      base/iam/common_services_roles/locals.tf
  37. 3 0
      base/iam/common_services_roles/module-policies.tf
  38. 28 0
      base/iam/common_services_roles/modules/saml_linked_role/main.tf
  39. 7 0
      base/iam/common_services_roles/modules/saml_linked_role/outputs.tf
  40. 20 0
      base/iam/common_services_roles/modules/saml_linked_role/variables.tf
  41. 29 0
      base/iam/common_services_roles/role-mdr_developer_readonly.tf
  42. 29 0
      base/iam/common_services_roles/role-mdr_engineer_readonly.tf
  43. 10 0
      base/iam/common_services_roles/role-mdr_terraformer.tf
  44. 5 0
      base/iam/common_services_roles/saml_provider.tf
  45. 8 0
      base/iam/common_services_roles/variables.tf
  46. 4 0
      base/iam/common_services_roles/versions.tf
  47. 41 0
      base/iam/okta_saml_roles/README.md
  48. 3 0
      base/iam/okta_saml_roles/account_alias.tf
  49. 73 0
      base/iam/okta_saml_roles/assume_role_policy-okta_saml.tf
  50. 16 0
      base/iam/okta_saml_roles/datasources.tf
  51. 14 0
      base/iam/okta_saml_roles/locals.tf
  52. 28 0
      base/iam/okta_saml_roles/modules/saml_linked_role/main.tf
  53. 7 0
      base/iam/okta_saml_roles/modules/saml_linked_role/outputs.tf
  54. 20 0
      base/iam/okta_saml_roles/modules/saml_linked_role/variables.tf
  55. 71 0
      base/iam/okta_saml_roles/policy-mdr_engineer.tf
  56. 32 0
      base/iam/okta_saml_roles/policy-mdr_iam_admin.tf
  57. 54 0
      base/iam/okta_saml_roles/policy-mdr_readonly_assumerole.tf
  58. 55 0
      base/iam/okta_saml_roles/policy-mdr_terraformer.tf
  59. 14 0
      base/iam/okta_saml_roles/role-mdr_engineer.tf
  60. 29 0
      base/iam/okta_saml_roles/role-mdr_engineer_readonly.tf
  61. 19 0
      base/iam/okta_saml_roles/role-mdr_iam_admin.tf
  62. 14 0
      base/iam/okta_saml_roles/role-mdr_terraformer.tf
  63. 5 0
      base/iam/okta_saml_roles/saml_provider.tf
  64. 12 0
      base/iam/okta_saml_roles/variables.tf
  65. 4 0
      base/iam/okta_saml_roles/versions.tf
  66. 23 0
      base/iam/standard_iam_policies/README.md
  67. 4 0
      base/iam/standard_iam_policies/datasources.tf
  68. 7 0
      base/iam/standard_iam_policies/locals.tf
  69. 10 0
      base/iam/standard_iam_policies/outputs.tf
  70. 71 0
      base/iam/standard_iam_policies/policy-mdr_engineer.tf
  71. 32 0
      base/iam/standard_iam_policies/policy-mdr_iam_admin.tf
  72. 55 0
      base/iam/standard_iam_policies/policy-mdr_readonly_assumerole.tf
  73. 55 0
      base/iam/standard_iam_policies/policy-mdr_terraformer.tf
  74. 3 0
      base/iam/standard_iam_policies/versions.tf
  75. 3 0
      base/standard_vpc/README.md
  76. 144 0
      base/standard_vpc/main.tf
  77. 21 0
      base/standard_vpc/outputs.tf
  78. 89 0
      base/standard_vpc/security-groups.tf
  79. 202 0
      base/standard_vpc/typicalhost.tf.disabled
  80. 51 0
      base/standard_vpc/vars.tf
  81. 3 0
      base/standard_vpc/version.tf
  82. 4 0
      base/tfstate/tfstate-s3/0.1/datasources.tf
  83. 22 0
      base/tfstate/tfstate-s3/0.1/dynamodb.tf
  84. 109 0
      base/tfstate/tfstate-s3/0.1/kms.tf
  85. 4 0
      base/tfstate/tfstate-s3/0.1/locals.tf
  86. 3 0
      base/tfstate/tfstate-s3/0.1/outputs.tf
  87. 48 0
      base/tfstate/tfstate-s3/0.1/s3.tf
  88. 12 0
      base/tfstate/tfstate-s3/0.1/variables.tf
  89. 3 0
      base/vmray_instances/README.md
  90. 21 0
      base/vmray_instances/cloud-init/cloud-init.tpl
  91. 25 0
      base/vmray_instances/fipsnotes.md
  92. 103 0
      base/vmray_instances/main.tf
  93. 23 0
      base/vmray_instances/outputs.tf
  94. 11 0
      base/vmray_instances/remote_state.tf
  95. 36 0
      base/vmray_instances/security-groups.tf
  96. 75 0
      base/vmray_instances/vars.tf
  97. 3 0
      base/vmray_instances/version.tf
  98. 3 0
      submodules/README.md
  99. 8 0
      thirdparty/README.md

+ 6 - 0
.gitattributes

@@ -0,0 +1,6 @@
+**/ks.cfg text eol=lf
+packer/scripts/** text eol=lf
+packer/configurator-tool/** text eol=lf
+packer/hyperv-provision/**  eol=crlf
+packer/hyperv-setup/**  eol=crlf
+**/*.tfstate binary -diff merge=binary

+ 99 - 0
.gitignore

@@ -0,0 +1,99 @@
+**/*.swp
+**/*.swo
+**/*.iso
+*.rpm
+salt/splunk/deployment_server/files/*
+salt/splunk/master/files/*
+
+# Created by https://www.gitignore.io/api/macos,splunk,terraform,visualstudiocode
+# Edit at https://www.gitignore.io/?templates=macos,splunk,terraform,visualstudiocode
+
+### macOS ###
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+### Splunk ###
+# gitignore template for Splunk apps
+# documentation: http://docs.splunk.com/Documentation/Splunk/6.2.3/admin/Defaultmetaconf
+
+# Splunk local meta file
+local.meta
+
+
+### Terraform ###
+# Local .terraform directories
+**/.terraform/*
+
+# .tfstate files
+*.tfstate
+*.tfstate.*
+
+!**/000-mdradmin-bootstrap/terraform.tfstate 
+!**/001-tfstate/terraform.tfstate
+
+### Terragrunt ###
+# Local .terragrunt directories
+.terragrunt-cache
+
+# Crash log files
+crash.log
+
+# Ignore any .tfvars files that are generated automatically for each Terraform run. Most
+# .tfvars files are managed as part of configuration and so should be included in
+# version control.
+#
+# example.tfvars
+
+# Ignore override files as they are usually used to override resources locally and so
+# are not checked in
+override.tf
+override.tf.json
+*_override.tf
+*_override.tf.json
+
+# Include override files you do wish to add to version control using negated pattern
+#
+# !example_override.tf
+
+### VisualStudioCode ###
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+### VisualStudioCode Patch ###
+# Ignore all local history of files
+.history
+
+# End of https://www.gitignore.io/api/macos,splunk,terraform,visualstudiocode
+
+\.vscode/
+
+# python
+__pycache__/*
+*.pyc

+ 5 - 0
README.md

@@ -0,0 +1,5 @@
+# modules
+
+This directory contains modules that are included from the environment directories.
+
+Modules called from other modules should be placed under the `z_Shared_Modules` subdirectory.

+ 3 - 0
base/README.md

@@ -0,0 +1,3 @@
+# base modules
+
+This directory contains modules that are intended to be called directly from the `xdr-terraform-live` repository.

+ 4 - 0
base/account_standards/README.md

@@ -0,0 +1,4 @@
+# Account Standards
+
+Creates elements that are standard in all accounts, such as access keys, kms keys, etc.
+

+ 7 - 0
base/account_standards/main.tf

@@ -0,0 +1,7 @@
+# Generates everybody's ssh key pairs
+resource "aws_key_pair" "key_pair" {
+  for_each = var.key_pairs
+
+  key_name   = each.key
+  public_key = each.value
+}

+ 3 - 0
base/account_standards/outputs.tf

@@ -0,0 +1,3 @@
+output "key_pair_names" {
+  value = [ for k, v in aws_key_pair.key_pair : v.key_name ] # Should just be the keys, but that might change
+}

+ 10 - 0
base/account_standards/vars.tf

@@ -0,0 +1,10 @@
+# No local module inputs (yet)
+
+
+# ----------------------------------
+# Below this line are variables inherited from higher levels, so they
+# do not need to be explicitly passed to this module.
+variable "key_pairs" {
+  description = "Public SSH keys"
+  type = map
+}

+ 3 - 0
base/account_standards/version.tf

@@ -0,0 +1,3 @@
+terraform {
+  required_version = "~> 0.12"
+}

+ 4 - 0
base/globally_accessible_bucket/README.md

@@ -0,0 +1,4 @@
+# globally accessible bucket
+
+Creates an encrypted S3 bucket that is globally accessible.
+

+ 105 - 0
base/globally_accessible_bucket/kms.tf

@@ -0,0 +1,105 @@
+resource "aws_kms_key" "bucketkey" {
+  description             = "S3 KMS for ${var.name}."
+  deletion_window_in_days = 30
+  enable_key_rotation     = true
+  policy                  = data.aws_iam_policy_document.kms_key_policy.json
+  tags                    = merge(var.standard_tags, var.tags)
+}
+
+resource "aws_kms_alias" "bucketkey" {
+  name          = "alias/${var.name}"
+  target_key_id = aws_kms_key.bucketkey.key_id
+}
+
+data "aws_iam_policy_document" "kms_key_policy" {
+  policy_id = var.name
+  statement {
+    sid    = "Enable IAM User Permissions"
+    effect = "Allow"
+    principals {
+      type        = "AWS"
+      identifiers = [
+        "arn:${var.aws_partition}:iam::${var.aws_account_id}:root",
+        "arn:${var.aws_partition}:iam::${var.aws_account_id}:user/MDRAdmin",
+      ]
+    }
+    actions   = ["kms:*"]
+    resources = ["*"]
+  }
+
+  statement {
+    sid    = "Allow access for Engineers"
+    effect = "Allow"
+    principals {
+      type = "AWS"
+      identifiers = [
+        "arn:${var.aws_partition}:iam::${var.aws_account_id}:user/MDRAdmin",
+        "arn:${var.aws_partition}:iam::${var.aws_account_id}:role/user/mdr_terraformer",
+      ]
+    }
+
+    actions = [
+      "kms:Create*",
+      "kms:Describe*",
+      "kms:Enable*",
+      "kms:List*",
+      "kms:Put*",
+      "kms:Update*",
+      "kms:Revoke*",
+      "kms:Disable*",
+      "kms:Get*",
+      "kms:Delete*",
+      "kms:TagResource",
+      "kms:UntagResource",
+      "kms:ScheduleKeyDeletion",
+      "kms:CancelKeyDeletion"
+    ]
+    resources = ["*"]
+  }
+
+  statement {
+    sid    = "Allow use of the key to encrypt and decrypt"
+    effect = "Allow"
+    principals {
+      type = "AWS"
+      identifiers = [
+        "arn:${var.aws_partition}:iam::${var.aws_account_id}:user/MDRAdmin",
+        "arn:${var.aws_partition}:iam::${var.aws_account_id}:role/user/mdr_terraformer",
+      ]
+    }
+    actions = [
+      "kms:Encrypt",
+      "kms:Decrypt",
+      "kms:ReEncrypt*",
+      "kms:GenerateDataKey*",
+      "kms:DescribeKey"
+    ]
+    resources = ["*"]
+  }
+
+  statement {
+    sid    = "Allow attachment of persistent resources"
+    effect = "Allow"
+    principals {
+      type = "AWS"
+      identifiers = [
+        "arn:${var.aws_partition}:iam::${var.aws_account_id}:user/MDRAdmin",
+        "arn:${var.aws_partition}:iam::${var.aws_account_id}:role/user/mdr_terraformer",
+      ]
+    }
+    actions = [
+      "kms:CreateGrant",
+      "kms:ListGrants",
+      "kms:RevokeGrant"
+    ]
+    resources = ["*"]
+    condition {
+      test     = "Bool"
+      variable = "kms:GrantIsForAWSResource"
+      values   = ["true"]
+    }
+  }
+
+  # TODO: Do we need to grant read access to other accounts?
+}
+    

+ 78 - 0
base/globally_accessible_bucket/main.tf

@@ -0,0 +1,78 @@
+locals {
+  # Technically, we don't need these in ARN format, but it makes updates slightly clearer
+  accounts = [ for a in var.account_list: "arn:${var.aws_partition}:iam::${a}:root" ]
+}
+
+resource "aws_s3_bucket" "bucket" {
+  bucket = var.name
+  acl    = "private"
+
+  versioning {
+    enabled = false
+  }
+
+  tags = merge(var.standard_tags, var.tags)
+
+  # FIXME: Does this keep a cross-account dependency?
+  #logging {
+  #  target_bucket = "dps-s3-logs"
+  #  target_prefix = "aws_terraform_s3_state_access_logs/"
+  #}
+
+  lifecycle_rule {
+    enabled                                = true
+    prefix                                 = ""
+    abort_incomplete_multipart_upload_days = 7
+
+    expiration {
+      days                         = 0
+      expired_object_delete_marker = false
+    }
+  }
+
+  server_side_encryption_configuration {
+    rule {
+      apply_server_side_encryption_by_default {
+        kms_master_key_id = aws_kms_key.bucketkey.arn
+        sse_algorithm     = "aws:kms"
+      }
+    }
+  }
+
+}
+
+resource "aws_s3_bucket_public_access_block" "public_access_block" {
+  bucket                  = aws_s3_bucket.bucket.id
+  block_public_acls       = true
+  block_public_policy     = true
+  ignore_public_acls      = true
+  restrict_public_buckets = true
+}
+
+resource "aws_s3_bucket_policy" "policy" {
+  bucket = aws_s3_bucket.bucket.id
+
+  policy = <<POLICY
+{
+  "Version": "2012-10-17",
+  "Id": "AllowAllAccounts",
+  "Statement": [
+    {
+      "Sid": "AccountAllow",
+      "Effect": "Allow",
+      "Principal": {
+        "AWS": ${jsonencode(local.accounts)}
+      },
+      "Action": [
+        "s3:GetObject",
+        "s3:ListBucket"
+      ],
+      "Resource": [
+        "${aws_s3_bucket.bucket.arn}",
+        "${aws_s3_bucket.bucket.arn}/*"
+      ]
+    }
+  ]
+}
+POLICY
+}

+ 3 - 0
base/globally_accessible_bucket/outputs.tf

@@ -0,0 +1,3 @@
+#output "TODO" {
+#  value = TODO
+#}

+ 47 - 0
base/globally_accessible_bucket/vars.tf

@@ -0,0 +1,47 @@
+# No local module inputs (yet)
+variable "name" {
+  description = "Name of the S3 bucket."
+  type = string
+}
+
+variable "tags" {
+  description = "Tags for the bucket and kms key."
+  type = map
+}
+
+# ----------------------------------
+# Below this line are variables inherited from higher levels, so they
+# do not need to be explicitly passed to this module.
+variable "standard_tags" {
+  type = map
+}
+
+variable "aws_account_id" {
+  type = string
+}
+
+variable "account_list" {
+  type = list
+}
+
+# ----------------------------------
+# Required for remote state, though they can be used elsewhere
+variable "remote_state_bucket" {
+  type = string
+}
+
+variable "aws_region" {
+  type = string
+}
+
+variable "aws_partition" {
+  type = string
+}
+
+variable "common_services_account" {
+  type = string
+}
+
+variable "common_profile" {
+  type = string
+}

+ 3 - 0
base/globally_accessible_bucket/version.tf

@@ -0,0 +1,3 @@
+terraform {
+  required_version = "~> 0.12"
+}

+ 6 - 0
base/iam/bootstrap_mdradmin_policies/datasources.tf

@@ -0,0 +1,6 @@
+data "aws_caller_identity" "current" {}
+
+data "aws_partition" "current" {}
+
+data "aws_region" "current" {}
+

+ 5 - 0
base/iam/bootstrap_mdradmin_policies/locals.tf

@@ -0,0 +1,5 @@
+locals {
+  aws_partition = data.aws_partition.current.partition
+  aws_account   = data.aws_caller_identity.current.account_id
+  aws_region    = data.aws_region.current.name
+}

+ 7 - 0
base/iam/bootstrap_mdradmin_policies/main.tf

@@ -0,0 +1,7 @@
+
+resource "aws_iam_user_policy_attachment" "this" {
+  for_each = toset(var.users)
+
+  user       = each.key
+  policy_arn = aws_iam_policy.mdradmin_tfstate_setup.arn
+}

+ 120 - 0
base/iam/bootstrap_mdradmin_policies/policy-mdradmin_tfstate_setup.tf

@@ -0,0 +1,120 @@
+resource "aws_iam_policy" "mdradmin_tfstate_setup" {
+  name        = "mdradmmin_tfstate_setup"
+  path        = "/bootstrap/"
+  description = "Gives MDRAdmin account rights needed to set up tfstate management"
+  policy      = data.aws_iam_policy_document.mdradmin_tfstate_setup.json
+}
+
+data "aws_iam_policy_document" "mdradmin_tfstate_setup" {
+  statement {
+    sid = "DynamoDBTablesAndLocking"
+    actions = [
+      "dynamodb:*"
+    ]
+    resources = [
+      "arn:${local.aws_partition}:dynamodb:${local.aws_region}:${local.aws_account}:table/${var.lock_table_name}"
+    ]
+    condition {
+      test     = "BoolIfExists"
+      variable = "aws:MultiFactorAuthPresent"
+      values = [
+        true
+      ]
+    }
+  }
+
+  statement {
+    sid = "DynamoDBTablesAndLocking2"
+    actions = [
+      "dynamodb:ListTables"
+    ]
+    resources = [
+      "arn:${local.aws_partition}:dynamodb:${local.aws_region}:${local.aws_account}:table/*"
+    ]
+    condition {
+      test     = "BoolIfExists"
+      variable = "aws:MultiFactorAuthPresent"
+      values = [
+        true
+      ]
+    }
+  }
+
+  statement {
+    sid = "KMSKeyCreate"
+    actions = [
+      "kms:CreateAlias",
+      "kms:CreateKey",
+      "kms:List*",
+      "kms:DeleteAlias",
+      "kms:DeleteKey"
+    ]
+
+    # I wish I could scope this down to just specific keys
+    # But I don't think it's possible
+    resources = [
+      "*"
+    ]
+    condition {
+      test     = "BoolIfExists"
+      variable = "aws:MultiFactorAuthPresent"
+      values = [
+        true
+      ]
+    }
+  }
+  statement {
+    sid = "S3AllResources"
+    actions = [ 
+      "s3:HeadBucket" 
+    ]
+    resources = [
+      "*"
+    ]
+    condition {
+      test     = "BoolIfExists"
+      variable = "aws:MultiFactorAuthPresent"
+      values = [
+        true
+      ]
+    }
+  }
+  statement {
+    sid = "S3ManageStateBucket"
+    actions = [
+      "s3:CreateBucket",
+      "s3:DeleteBucket",
+      "s3:ListBucket",
+      "s3:Get*",
+      "s3:Put*"
+    ]
+    resources = [
+      "arn:${local.aws_partition}:s3:::${var.bucket_name}"
+    ]
+    condition {
+      test     = "BoolIfExists"
+      variable = "aws:MultiFactorAuthPresent"
+      values = [
+        true
+      ]
+    }
+  }
+  statement {
+    sid = "S3ObjectOperations"
+    actions = [
+      "s3:PutObject*",
+      "s3:GetObject*",
+      "s3:DeleteObject*"
+    ]
+    resources = [
+      "arn:${local.aws_partition}:s3:::${var.bucket_name}/*"
+    ]
+    condition {
+      test     = "BoolIfExists"
+      variable = "aws:MultiFactorAuthPresent"
+      values = [
+        true
+      ]
+    }
+  }
+}

+ 12 - 0
base/iam/bootstrap_mdradmin_policies/variables.tf

@@ -0,0 +1,12 @@
+variable users {
+  type    = list(string)
+  default = []
+}
+
+variable bucket_name {
+  type = string
+}
+
+variable lock_table_name {
+  type = string
+}

+ 46 - 0
base/iam/child_account_roles/README.md

@@ -0,0 +1,46 @@
+# child_account_roles module
+
+Creates "standard" IAM policies and roles in an account being treated like an
+AWS organizations child account.
+
+Picture our collection of AWS accounts with the "common-services"
+account being the root of an Organizations hierarchy, where all of the users
+exist there and AssumeRole to the correct role in the child account.
+
+```java
+common-services
+    prod-c2
+    test-c2
+    prod-customer-1
+    prod-customer-2
+    ...
+```
+
+This module makes roles that are NOT SAML linked. It is expected you will
+AssumeRole into these roles cross-account.
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| aws  | ~2.0?   |
+
+## Inputs
+
+| Name | Description | Type | Required |
+|------|-------------|------|----------|
+| okta_app | The (friendly) name of the Okta app.  In our environment either "AWS - Commercial" or "AWS - GovCloud" | `string` | Yes |
+| account_alias | The account alias that should be set for the AWS account.  This is an AWS global value | `string` | yes |
+
+## Roles created
+
+| Role Name         | Attached Policies | Description |
+|--------------------|-------------------|-------------|
+| /user/mdr\_engineer\_readonly | ReadOnlyAccess <br> mdr\_engineer\_readonly\_assumerole | Read only access to AWS console with ability to escalate to Terraformer role
+| /user/mdr\_terraformer| mdr\_terraformer | Full read/write access to (almost) everything.  Has some limitations around PassRole and AssumeRole
+
+## Modules referenced
+
+| Module name            | purpose |
+|------------------------|---------|
+| standard_iam_policies  | defines the policies used by the roles  |

+ 4 - 0
base/iam/child_account_roles/account_alias.tf

@@ -0,0 +1,4 @@
+resource "aws_iam_account_alias" "alias" {
+  count         = length(var.account_alias)==0 ? 0 : 1
+  account_alias = var.account_alias
+}

+ 14 - 0
base/iam/child_account_roles/assume_role_policy-non_saml.tf

@@ -0,0 +1,14 @@
+data "aws_iam_policy_document" "non_saml_assume_role_policy" {
+    statement {
+    sid    = "AllowAssumeRoleFromTrustedAccounts"
+    effect = "Allow"
+    principals {
+      type        = "AWS"
+      identifiers = var.assume_role_trusted_arns
+    }
+
+    actions = [
+      "sts:AssumeRole",
+    ]
+  }
+}

+ 4 - 0
base/iam/child_account_roles/datasources.tf

@@ -0,0 +1,4 @@
+data "aws_caller_identity" "current" {}
+
+data "aws_partition" "current" {}
+

+ 12 - 0
base/iam/child_account_roles/locals.tf

@@ -0,0 +1,12 @@
+locals {
+
+  # Just to shorten up some references
+  aws_partition = data.aws_partition.current.partition
+  aws_account   = data.aws_caller_identity.current.account_id
+
+  # Used in assume-role policies coming from SAML
+  saml_signin_page = {
+    "aws"        = "https://signin.aws.amazon.com/saml"
+    "aws-us-gov" = "https://signin.amazonaws-us-gov.com/saml"
+  }
+}

+ 3 - 0
base/iam/child_account_roles/module-policies.tf

@@ -0,0 +1,3 @@
+module "standard_iam_policies" {
+  source = "../standard_iam_policies"
+}

+ 25 - 0
base/iam/child_account_roles/role-mdr_engineer_readonly.tf

@@ -0,0 +1,25 @@
+#------------------------------------------------------------------------------------------
+# A Read Only Engineer.  Assumption is this is everyone's normal working
+# role day-to-day in the AWS console.  When you need it, you then elevate
+# to mdr_terraformer.
+#
+# Note this is NOT JUST READ ONLY ACCESS.  This should only be
+# assigned to ENGINEERS who you expect will able to make changes
+# as needed.  
+#------------------------------------------------------------------------------------------
+
+resource aws_iam_role "role-mdr_engineer_readonly" {
+  name                  = "mdr_engineer_readonly"
+  path                  = "/user/"
+  assume_role_policy    = data.aws_iam_policy_document.non_saml_assume_role_policy.json
+}
+
+resource "aws_iam_role_policy_attachment" "mdr_engineer_readonly_ViewOnlyAccess" {
+  role       = aws_iam_role.role-mdr_engineer_readonly.name
+  policy_arn = "arn:${local.aws_partition}:iam::aws:policy/job-function/ViewOnlyAccess"
+}
+
+resource "aws_iam_role_policy_attachment" "mdr_engineer_readonly_assumerole" {
+  role       = aws_iam_role.role-mdr_engineer_readonly.name
+  policy_arn = module.standard_iam_policies.arns["mdr_readonly_assumerole"]
+}

+ 10 - 0
base/iam/child_account_roles/role-mdr_terraformer.tf

@@ -0,0 +1,10 @@
+resource aws_iam_role "mdr_terraformer" {
+  name                  = "mdr_terraformer"
+  path                  = "/user/"
+  assume_role_policy    = data.aws_iam_policy_document.non_saml_assume_role_policy.json
+}
+
+resource aws_iam_role_policy_attachment "mdr_terraformer-mdr_terraformer" {
+  role       = aws_iam_role.mdr_terraformer.name 
+  policy_arn = module.standard_iam_policies.arns["mdr_terraformer"]
+}

+ 9 - 0
base/iam/child_account_roles/variables.tf

@@ -0,0 +1,9 @@
+variable account_alias {
+  type    = string
+  default = ""
+}
+
+variable assume_role_trusted_arns {
+  type = list(string)
+}
+

+ 4 - 0
base/iam/child_account_roles/versions.tf

@@ -0,0 +1,4 @@
+
+terraform {
+  required_version = ">= 0.12, < 0.13"
+}

+ 53 - 0
base/iam/common_services_roles/README.md

@@ -0,0 +1,53 @@
+# common_services_roles module
+
+Creates "standard" IAM policies and roles in an account being treated like an
+AWS organizations hierarchy root.
+
+Picture our collection of AWS accounts with the "common-services"
+account being the root of an Organizations hierarchy, where all of the users
+exist there and AssumeRole to the correct role in the child account
+
+```java
+common-services
+    prod-c2
+    test-c2
+    prod-customer-1
+    prod-customer-2
+    ...
+```
+
+This module makes one SAML-linked role `mdr_engineer_readonly` that we
+access via OKTA.  From there you AssumeRole into `mdr_terraformer` to make
+changes - either in this account or in others.  Or, you AssumeRole into 
+`mdr_engineer_readonly` in the other accounts for "just browsing".
+
+Make sure you have an `OKTA_API_TOKEN` enviornment variable set with
+an Okta API token.
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| aws  | ~2.0?   |
+| okta | ?       |
+
+## Inputs
+
+| Name | Description | Type | Required |
+|------|-------------|------|----------|
+| okta_app | The (friendly) name of the Okta app.  In our environment either "AWS - Commercial" or "AWS - GovCloud" | `string` | Yes |
+| account_alias | The account alias that should be set for the AWS account.  This is an AWS global value | `string` | yes |
+
+## Roles created
+
+| Role Name         | Attached Policies | Description |
+|--------------------|-------------------|-------------|
+| /user/mdr\_engineer\_readonly | ReadOnlyAccess <br> mdr\_engineer\_readonly\_assumerole | Read only access to AWS console with ability to escalate to Terraformer role
+| /user/mdr\_terraformer| mdr\_terraformer | Full read/write access to (almost) everything.  Has some limitations around PassRole and AssumeRole
+
+## Modules referenced
+
+| Module name            | purpose |
+|------------------------|---------|
+| standard_iam_policies  | defines the policies used by the roles  |
+| saml_linked_role       | submodule that defines the okta group and role        |

+ 3 - 0
base/iam/common_services_roles/account_alias.tf

@@ -0,0 +1,3 @@
+resource "aws_iam_account_alias" "alias" {
+  account_alias = var.account_alias
+}

+ 16 - 0
base/iam/common_services_roles/assume_role_policy-non_saml.tf

@@ -0,0 +1,16 @@
+data "aws_iam_policy_document" "non_saml_assume_role_policy" {
+    statement {
+    sid    = "AllowAssumeRoleFromReadOnly"
+    effect = "Allow"
+    principals {
+      type        = "AWS"
+      identifiers = [
+        "arn:${local.aws_partition}:iam::${local.aws_account}:role/user/mdr_engineer_readonly"
+      ]
+    }
+
+    actions = [
+      "sts:AssumeRole",
+    ]
+  }
+}

+ 22 - 0
base/iam/common_services_roles/assume_role_policy-okta_saml.tf

@@ -0,0 +1,22 @@
+data "aws_iam_policy_document" "okta_saml_assume_role_policy" {
+  statement {
+    sid    = "AllowAssumeRoleViaOkta"
+    effect = "Allow"
+    principals {
+      type        = "Federated"
+      identifiers = [aws_iam_saml_provider.okta.arn]
+    }
+
+    actions = [
+      "sts:AssumeRoleWithSAML",
+    ]
+
+    condition {
+      test     = "StringEquals"
+      variable = "SAML:aud"
+      values = [
+        local.saml_signin_page[local.aws_partition]
+      ]
+    }
+  }
+}

+ 16 - 0
base/iam/common_services_roles/datasources.tf

@@ -0,0 +1,16 @@
+data "aws_caller_identity" "current" {}
+
+data "aws_partition" "current" {}
+
+data "okta_app" "awsapp" {
+  label = var.okta_app
+}
+
+data "okta_app_saml" "awsapp" {
+  id = data.okta_app.awsapp.id
+}
+
+data "okta_app_metadata_saml" "awsapp" {
+  app_id = data.okta_app.awsapp.id
+  key_id = data.okta_app_saml.awsapp.key_id
+}

+ 12 - 0
base/iam/common_services_roles/locals.tf

@@ -0,0 +1,12 @@
+locals {
+
+  # Just to shorten up some references
+  aws_partition = data.aws_partition.current.partition
+  aws_account   = data.aws_caller_identity.current.account_id
+
+  # Used in assume-role policies coming from SAML
+  saml_signin_page = {
+    "aws"        = "https://signin.aws.amazon.com/saml"
+    "aws-us-gov" = "https://signin.amazonaws-us-gov.com/saml"
+  }
+}

+ 3 - 0
base/iam/common_services_roles/module-policies.tf

@@ -0,0 +1,3 @@
+module "standard_iam_policies" {
+  source = "../standard_iam_policies"
+}

+ 28 - 0
base/iam/common_services_roles/modules/saml_linked_role/main.tf

@@ -0,0 +1,28 @@
+data "aws_caller_identity" "current" { }
+
+data "aws_partition" "current" { }
+
+resource "aws_iam_role" "this" {
+  path               = var.path
+  assume_role_policy = var.assume_role_policy
+  name               = var.name
+}
+
+resource "okta_group" "this" {
+
+  # aws#ModelClient-Test#IAMFullAccess#449047653882
+  name = format("%s#%s#%s#%s",
+                data.aws_partition.current.partition,
+                var.account_friendly_name,
+                var.name,
+                data.aws_caller_identity.current.account_id)
+}
+
+resource "okta_app_group_assignment" "this" {
+
+  group_id = okta_group.this.id
+  app_id   = var.okta_app_id
+  lifecycle {
+    ignore_changes = [ priority ]
+  }
+}

+ 7 - 0
base/iam/common_services_roles/modules/saml_linked_role/outputs.tf

@@ -0,0 +1,7 @@
+output arn {
+    value = aws_iam_role.this.arn
+}
+
+output name {
+    value = aws_iam_role.this.name
+}

+ 20 - 0
base/iam/common_services_roles/modules/saml_linked_role/variables.tf

@@ -0,0 +1,20 @@
+variable name {
+    type = string
+}
+
+variable path {
+    type    = string
+    default = "/"
+}
+
+variable assume_role_policy {
+    type = string
+}
+
+variable okta_app_id {
+    type = string
+}
+
+variable account_friendly_name {
+    type = string
+}

+ 29 - 0
base/iam/common_services_roles/role-mdr_developer_readonly.tf

@@ -0,0 +1,29 @@
+#------------------------------------------------------------------------------------------
+# A Read Only Developer.  Assumption is this is everyone's normal working
+# role day-to-day in the AWS console.  When you need it, you then elevate
+# to mdr_terraformer.
+#
+# This has the exact same permissions in the common services accounts as
+# mdr_engineer_readonly, except the cross-domain trusts will be different
+# so that someone with "developer" cannot assumerole into production accounts
+#------------------------------------------------------------------------------------------
+
+module "role-mdr_developer_readonly" {
+  source = "./modules/saml_linked_role"
+
+  name                  = "mdr_developer_readonly"
+  account_friendly_name = aws_iam_account_alias.alias.account_alias
+  path                  = "/user/"
+  assume_role_policy    = data.aws_iam_policy_document.okta_saml_assume_role_policy.json
+  okta_app_id           = data.okta_app.awsapp.id
+}
+
+resource "aws_iam_role_policy_attachment" "mdr_devloper_readonly_ViewOnlyAccess" {
+  role       = module.role-mdr_developer_readonly.name
+  policy_arn = "arn:${local.aws_partition}:iam::aws:policy/job-function/ViewOnlyAccess"
+}
+
+resource "aws_iam_role_policy_attachment" "mdr_developer_readonly_assumerole" {
+  role       = module.role-mdr_developer_readonly.name
+  policy_arn = module.standard_iam_policies.arns["mdr_readonly_assumerole"]
+}

+ 29 - 0
base/iam/common_services_roles/role-mdr_engineer_readonly.tf

@@ -0,0 +1,29 @@
+#------------------------------------------------------------------------------------------
+# A Read Only Engineer.  Assumption is this is everyone's normal working
+# role day-to-day in the AWS console.  When you need it, you then elevate
+# to mdr_terraformer.
+#
+# Note this is NOT JUST READ ONLY ACCESS.  This should only be
+# assigned to ENGINEERS who you expect will able to make changes
+# as needed.  
+#------------------------------------------------------------------------------------------
+
+module "role-mdr_engineer_readonly" {
+  source = "./modules/saml_linked_role"
+
+  name                  = "mdr_engineer_readonly"
+  account_friendly_name = aws_iam_account_alias.alias.account_alias
+  path                  = "/user/"
+  assume_role_policy    = data.aws_iam_policy_document.okta_saml_assume_role_policy.json
+  okta_app_id           = data.okta_app.awsapp.id
+}
+
+resource "aws_iam_role_policy_attachment" "mdr_engineer_readonly_ViewOnlyAccess" {
+  role       = module.role-mdr_engineer_readonly.name
+  policy_arn = "arn:${local.aws_partition}:iam::aws:policy/job-function/ViewOnlyAccess"
+}
+
+resource "aws_iam_role_policy_attachment" "mdr_engineer_readonly_assumerole" {
+  role       = module.role-mdr_engineer_readonly.name
+  policy_arn = module.standard_iam_policies.arns["mdr_readonly_assumerole"]
+}

+ 10 - 0
base/iam/common_services_roles/role-mdr_terraformer.tf

@@ -0,0 +1,10 @@
+resource aws_iam_role "mdr_terraformer" {
+  name                  = "mdr_terraformer"
+  path                  = "/user/"
+  assume_role_policy    = data.aws_iam_policy_document.non_saml_assume_role_policy.json
+}
+
+resource aws_iam_role_policy_attachment "mdr_terraformer-mdr_terraformer" {
+  role       = aws_iam_role.mdr_terraformer.name 
+  policy_arn = module.standard_iam_policies.arns["mdr_terraformer"]
+}

+ 5 - 0
base/iam/common_services_roles/saml_provider.tf

@@ -0,0 +1,5 @@
+resource "aws_iam_saml_provider" "okta" {
+  name                   = "OKTA"
+  saml_metadata_document = data.okta_app_metadata_saml.awsapp.metadata
+}
+

+ 8 - 0
base/iam/common_services_roles/variables.tf

@@ -0,0 +1,8 @@
+variable okta_app {
+  type = string
+}
+
+variable account_alias {
+  type = string
+}
+

+ 4 - 0
base/iam/common_services_roles/versions.tf

@@ -0,0 +1,4 @@
+
+terraform {
+  required_version = ">= 0.12, < 0.13"
+}

+ 41 - 0
base/iam/okta_saml_roles/README.md

@@ -0,0 +1,41 @@
+# okta saml roles module
+
+Defines several well-known IAM roles and ties them to matching
+OKTA groups that are passed over as part of a SAML assertion.
+
+Make sure you have an `OKTA_API_TOKEN` enviornment variable set with
+an Okta API token.
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| aws  | ~2.0?   |
+| okta | ?       |
+
+## Inputs
+
+| Name | Description | Type | Required |
+|------|-------------|------|----------|
+| okta_app | The (friendly) name of the Okta app.  In our environment either "AWS - Commercial" or "AWS - GovCloud" | `string` | Yes |
+| account_alias | The account alias that should be set for the AWS account.  This is an AWS global value | `string` | yes |
+| trusted arns | Any ARNS that should be able to AssumeRole. This is mostly intended for use in "child" AWS accounts. | `list(string)` | no |
+
+
+## Roles created
+
+| Role Name         | Attached Policies | Description |
+|--------------------|-------------------|-------------|
+| /user/mdr\_engineer | mdr\_engineer      | "legacy" role. 
+| /user/mdr\_engineer\_readonly | ReadOnlyAccess <br> mdr\_engineer\_readonly\_assumerole | Read only access to AWS console with ability to escalate to Terraformer role
+| /user/mdr\_iam\_admin | IAMFullAccess <br> iam\_admin\_kms | "legacy" role.  
+| /user/mdr\_terraformer| mdr\_terraformer | Full read/write access to (almost) everything.  Has some limitations around PassRole and AssumeRole
+
+## Policies created
+
+| Policy Name        | Description |
+|--------------------|-------------|
+| mdr\_engineer      | "legacy" policy.  Gives effectively PowerUserAccess but with limitations on iam:PassRole and sts:AssumeRole.
+| iam\_admin\_kms    | "legacy" policy.  Gives several `kms:*` actions related to creating, destroying, and managing keys.  Encrypt and Decrypt are noticeably absent.
+| mdr\_engineer\_readonly\_assumerole | Read only access to AWS console with ability to escalate to Terraformer role
+| mdr\_terraformer | Full read/write access to (almost) everything.  Has some limitations around PassRole and AssumeRole

+ 3 - 0
base/iam/okta_saml_roles/account_alias.tf

@@ -0,0 +1,3 @@
+resource "aws_iam_account_alias" "alias" {
+  account_alias = var.account_alias
+}

+ 73 - 0
base/iam/okta_saml_roles/assume_role_policy-okta_saml.tf

@@ -0,0 +1,73 @@
+data "aws_iam_policy_document" "okta_saml_assume_role_policy" {
+  statement {
+    sid    = "AllowAssumeRoleViaOkta"
+    effect = "Allow"
+    principals {
+      type        = "Federated"
+      identifiers = [aws_iam_saml_provider.okta.arn]
+    }
+
+    actions = [
+      "sts:AssumeRoleWithSAML",
+    ]
+
+    condition {
+      test     = "StringEquals"
+      variable = "SAML:aud"
+      values = [
+        local.saml_signin_page[local.aws_partition]
+      ]
+    }
+  }
+
+  # Note this could be a security issue.  We are counting on 
+  # All of the other roles, groups, etc in the account to have reasonable
+  # limitations on sts:AssumeRole
+  statement {
+    sid    = "AllowAssumeRoleFromOtherRolesInThisAccount"
+    effect = "Allow"
+    principals {
+      type        = "AWS"
+      identifiers = [
+        "arn:${local.aws_partition}:iam::${local.aws_account}:root"
+      ]
+    }
+
+    actions = [
+      "sts:AssumeRole",
+    ]
+  }
+}
+
+# Notice the source_json here.  I had forgotten how this worked and
+# had to refresh myself.  See terraform AWS provider docs at
+# https://www.terraform.io/docs/providers/aws/d/iam_policy_document.html
+#   "Statements with non-blank sids in the current policy document will overwrite 
+#    statements with the same sid in the source json."
+# The idea here is that IF var.trusted_arns is set, then we append a new SID
+# to the policy to enable AssumeRole from other accounts.
+#
+# This ties to local.tf:
+# assume_role_policy = (length(var.trusted_arns) > 0) ? 
+# data.aws_iam_policy_document.okta_saml_plus_crossaccount_assume_role_policy.json : 
+# data.aws_iam_policy_document.okta_saml_assume_role_policy.json
+#
+# Maybe that local should be defined here in this file and not in locals.tf, not sure which
+# is clearer.
+data "aws_iam_policy_document" "okta_saml_plus_crossaccount_assume_role_policy" {
+
+  source_json = data.aws_iam_policy_document.okta_saml_assume_role_policy.json
+
+  statement {
+    sid    = "AllowAssumeRoleFromOtherAccounts"
+    effect = "Allow"
+    principals {
+      type        = "AWS"
+      identifiers = var.trusted_arns
+    }
+
+    actions = [
+      "sts:AssumeRole",
+    ]
+  }
+}

+ 16 - 0
base/iam/okta_saml_roles/datasources.tf

@@ -0,0 +1,16 @@
+data "aws_caller_identity" "current" {}
+
+data "aws_partition" "current" {}
+
+data "okta_app" "awsapp" {
+  label = var.okta_app
+}
+
+data "okta_app_saml" "awsapp" {
+  id = data.okta_app.awsapp.id
+}
+
+data "okta_app_metadata_saml" "awsapp" {
+  app_id = data.okta_app.awsapp.id
+  key_id = data.okta_app_saml.awsapp.key_id
+}

+ 14 - 0
base/iam/okta_saml_roles/locals.tf

@@ -0,0 +1,14 @@
+locals {
+
+  # Just to shorten up some references
+  aws_partition = data.aws_partition.current.partition
+  aws_account   = data.aws_caller_identity.current.account_id
+
+  assume_role_policy = (length(var.trusted_arns) > 0) ? data.aws_iam_policy_document.okta_saml_plus_crossaccount_assume_role_policy.json : data.aws_iam_policy_document.okta_saml_assume_role_policy.json
+
+  # Used in assume-role policies coming from SAML
+  saml_signin_page = {
+    "aws"        = "https://signin.aws.amazon.com/saml"
+    "aws-us-gov" = "https://signin.amazonaws-us-gov.com/saml"
+  }
+}

+ 28 - 0
base/iam/okta_saml_roles/modules/saml_linked_role/main.tf

@@ -0,0 +1,28 @@
+data "aws_caller_identity" "current" { }
+
+data "aws_partition" "current" { }
+
+resource "aws_iam_role" "this" {
+  path               = var.path
+  assume_role_policy = var.assume_role_policy
+  name               = var.name
+}
+
+resource "okta_group" "this" {
+
+  # aws#ModelClient-Test#IAMFullAccess#449047653882
+  name = format("%s#%s#%s#%s",
+                data.aws_partition.current.partition,
+                var.account_friendly_name,
+                var.name,
+                data.aws_caller_identity.current.account_id)
+}
+
+resource "okta_app_group_assignment" "this" {
+
+  group_id = okta_group.this.id
+  app_id   = var.okta_app_id
+  lifecycle {
+    ignore_changes = [ priority ]
+  }
+}

+ 7 - 0
base/iam/okta_saml_roles/modules/saml_linked_role/outputs.tf

@@ -0,0 +1,7 @@
+output arn {
+    value = aws_iam_role.this.arn
+}
+
+output name {
+    value = aws_iam_role.this.name
+}

+ 20 - 0
base/iam/okta_saml_roles/modules/saml_linked_role/variables.tf

@@ -0,0 +1,20 @@
+variable name {
+    type = string
+}
+
+variable path {
+    type    = string
+    default = "/"
+}
+
+variable assume_role_policy {
+    type = string
+}
+
+variable okta_app_id {
+    type = string
+}
+
+variable account_friendly_name {
+    type = string
+}

+ 71 - 0
base/iam/okta_saml_roles/policy-mdr_engineer.tf

@@ -0,0 +1,71 @@
+#------------------------------------------------------------------------------------------
+# A variant on PowerUserAccess that isn't so damn generous with sts:assumeRole
+#------------------------------------------------------------------------------------------
+data "aws_iam_policy_document" "mdr_engineer" {
+  statement {
+    effect = "Allow"
+    not_actions = [
+      "sts:*",
+      "iam:*",
+      "organizations:*",
+    ]
+    resources = [
+      "*",
+    ]
+  }
+  statement {
+    effect = "Allow"
+    actions = [
+      "iam:CreateServiceLinkedRole",
+      "iam:DeleteServiceLinkedRole",
+      "iam:ListRoles",
+      "iam:ListRolePolicies",
+      "iam:ListInstanceProfiles",
+      "iam:ListPolicies",
+      "iam:GetRole",
+      "iam:GetRolePolicy",
+      "iam:GetInstanceProfile",
+      "iam:GetPolicy",
+      "iam:GetPolicyVersion",
+      "iam:ListAttachedRolePolicies",
+      "organizations:DescribeOrganization",
+    ]
+
+    resources = [
+      "*",
+    ]
+  }
+  statement {
+    effect = "Allow"
+    actions = [
+      "iam:PassRole",
+    ]
+
+    resources = [
+      "arn:${local.aws_partition}:iam::${local.aws_account}:role/instance/*",
+      "arn:${local.aws_partition}:iam::${local.aws_account}:role/lambda/*",
+      "arn:${local.aws_partition}:iam::${local.aws_account}:role/aws_services/*",
+      "arn:${local.aws_partition}:iam::${local.aws_account}:role/fargate/*",
+    ]
+  }
+
+  statement {
+    sid    = "AssumeThisRoleInOtherAccounts"
+    effect = "Allow"
+    actions = [
+      "sts:AssumeRole"
+    ]
+
+    resources = [
+      "arn:${local.aws_partition}:iam::*:role/user/mdr_engineer",
+      "arn:${local.aws_partition}:iam::*:role/mdr_engineer",
+    ]
+  }
+}
+
+resource "aws_iam_policy" "mdr_engineer" {
+  name   = "mdr_engineer"
+  path   = "/user/"
+  policy = data.aws_iam_policy_document.mdr_engineer.json
+}
+

+ 32 - 0
base/iam/okta_saml_roles/policy-mdr_iam_admin.tf

@@ -0,0 +1,32 @@
+data "aws_iam_policy_document" "iam_admin_kms" {
+
+  statement {
+    sid    = "AllowKMSthings"
+    effect = "Allow"
+    actions = [
+      "kms:Create*",
+      "kms:Describe*",
+      "kms:Enable*",
+      "kms:List*",
+      "kms:Put*",
+      "kms:Update*",
+      "kms:Revoke*",
+      "kms:Disable*",
+      "kms:Get*",
+      "kms:Delete*",
+      "kms:TagResource",
+      "kms:UntagResource",
+      "kms:ScheduleKeyDeletion",
+      "kms:CancelKeyDeletion"
+    ]
+    resources = ["*"]
+  }
+
+}
+
+resource "aws_iam_policy" "iam_admin_kms" {
+  name        = "iam_admin_kms"
+  path        = "/user/"
+  description = "KMS access for IAM admins"
+  policy      = data.aws_iam_policy_document.iam_admin_kms.json
+}

+ 54 - 0
base/iam/okta_saml_roles/policy-mdr_readonly_assumerole.tf

@@ -0,0 +1,54 @@
+#------------------------------------------------------------------------------------------
+# A Read Only Engineer.  Assumption is this is everyone's normal working
+# role day-to-day in the AWS console.  When you need it, you then elevate
+# to mdr_terraformer.
+#
+# Note this is NOT JUST READ ONLY ACCESS.  This should only be
+# assigned to ENGINEERS who you expect will able to make changes
+# as needed.  
+#------------------------------------------------------------------------------------------
+data "aws_iam_policy_document" "mdr_engineer_readonly_assumerole" {
+  statement {
+    sid     = "AllowPassRoleForSpecificRoleTypes"
+    effect  = "Allow"
+    actions = [
+      "iam:PassRole",
+    ]
+
+    resources = [
+      "arn:${local.aws_partition}:iam::${local.aws_account}:role/instance/*",
+      "arn:${local.aws_partition}:iam::${local.aws_account}:role/lambda/*",
+      "arn:${local.aws_partition}:iam::${local.aws_account}:role/aws_services/*",
+      "arn:${local.aws_partition}:iam::${local.aws_account}:role/fargate/*",
+    ]
+  }
+
+  statement {
+    sid    = "AssumeThisRoleInOtherAccounts"
+    effect = "Allow"
+    actions = [
+      "sts:AssumeRole"
+    ]
+
+    resources = [
+      "arn:${local.aws_partition}:iam::*:role/user/mdr_engineer_readonly",
+
+      # Give a readonly engineer the ability if needed to elevate to terraformer
+      # In order to make changes when needed.  
+      "arn:${local.aws_partition}:iam::*:role/user/mdr_terraformer",
+
+      # These two are the legacy roles in the older AWS accounts. 
+      # Adding them in the hope we'll be able to get AssumeRole from
+      # one central place to everything...
+      "arn:${local.aws_partition}:iam::*:role/mdr_powerusers",
+      "arn:${local.aws_partition}:iam::*:role/mdr_iam_admins",
+    ]
+  }
+}
+
+resource "aws_iam_policy" "mdr_engineer_readonly_assumerole" {
+  name   = "mdr_engineer_readonly_assumerole"
+  path   = "/user/"
+  policy = data.aws_iam_policy_document.mdr_engineer_readonly_assumerole.json
+}
+

+ 55 - 0
base/iam/okta_saml_roles/policy-mdr_terraformer.tf

@@ -0,0 +1,55 @@
+#------------------------------------------------------------------------------------------
+# A variant on PowerUserAccess that isn't so damn generous with sts:assumeRole
+#------------------------------------------------------------------------------------------
+data "aws_iam_policy_document" "mdr_terraformer" {
+  statement {
+    sid    = "AllowEverythingButAssumeRoleAndPassRole"
+    effect = "Allow"
+    not_actions = [
+      "sts:AssumeRole",
+      "iam:PassRole",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+  statement {
+    sid     = "AllowPassRoleForSpecificRoleTypes"
+    effect = "Allow"
+    actions = [
+      "iam:PassRole",
+    ]
+
+    resources = [
+      "arn:${local.aws_partition}:iam::${local.aws_account}:role/instance/*",
+      "arn:${local.aws_partition}:iam::${local.aws_account}:role/lambda/*",
+      "arn:${local.aws_partition}:iam::${local.aws_account}:role/aws_services/*",
+      "arn:${local.aws_partition}:iam::${local.aws_account}:role/fargate/*",
+    ]
+  }
+
+  statement {
+    sid    = "AssumeThisRoleInOtherAccounts"
+    effect = "Allow"
+    actions = [
+      "sts:AssumeRole"
+    ]
+
+    resources = [
+      "arn:${local.aws_partition}:iam::*:role/user/mdr_terraformer",
+
+      # These two are the legacy roles in the older AWS accounts. 
+      # Adding them in the hope we'll be able to get AssumeRole from
+      # one central place to everything...
+      "arn:${local.aws_partition}:iam::*:role/mdr_powerusers",
+      "arn:${local.aws_partition}:iam::*:role/mdr_iam_admins",
+    ]
+  }
+}
+
+resource "aws_iam_policy" "mdr_terraformer" {
+  name   = "mdr_terraformer"
+  path   = "/user/"
+  policy = data.aws_iam_policy_document.mdr_terraformer.json
+}
+

+ 14 - 0
base/iam/okta_saml_roles/role-mdr_engineer.tf

@@ -0,0 +1,14 @@
+module "role-mdr_engineer" {
+  source = "./modules/saml_linked_role"
+
+  name                  = "mdr_engineer"
+  account_friendly_name = aws_iam_account_alias.alias.account_alias
+  path                  = "/user/"
+  assume_role_policy    = local.assume_role_policy
+  okta_app_id           = data.okta_app.awsapp.id
+}
+
+resource aws_iam_role_policy_attachment "mdr_engineer-mdr_engineer" {
+  role       = module.role-mdr_engineer.name
+  policy_arn = aws_iam_policy.mdr_engineer.arn
+}

+ 29 - 0
base/iam/okta_saml_roles/role-mdr_engineer_readonly.tf

@@ -0,0 +1,29 @@
+#------------------------------------------------------------------------------------------
+# A Read Only Engineer.  Assumption is this is everyone's normal working
+# role day-to-day in the AWS console.  When you need it, you then elevate
+# to mdr_terraformer.
+#
+# Note this is NOT JUST READ ONLY ACCESS.  This should only be
+# assigned to ENGINEERS who you expect will able to make changes
+# as needed.  
+#------------------------------------------------------------------------------------------
+
+module "role-mdr_engineer_readonly" {
+  source = "./modules/saml_linked_role"
+
+  name                  = "mdr_engineer_readonly"
+  account_friendly_name = aws_iam_account_alias.alias.account_alias
+  path                  = "/user/"
+  assume_role_policy    = local.assume_role_policy
+  okta_app_id           = data.okta_app.awsapp.id
+}
+
+resource "aws_iam_role_policy_attachment" "mdr_engineer_readonly_ReadOnlyAccess" {
+  role       = module.role-mdr_engineer_readonly.name
+  policy_arn = "arn:${local.aws_partition}:iam::aws:policy/ReadOnlyAccess"
+}
+
+resource "aws_iam_role_policy_attachment" "mdr_engineer_readonly_assumerole" {
+  role       = module.role-mdr_engineer_readonly.name
+  policy_arn = aws_iam_policy.mdr_engineer_readonly_assumerole.arn
+}

+ 19 - 0
base/iam/okta_saml_roles/role-mdr_iam_admin.tf

@@ -0,0 +1,19 @@
+module "role-mdr_iam_admin" {
+  source = "./modules/saml_linked_role"
+
+  name                  = "mdr_iam_admin"
+  account_friendly_name = aws_iam_account_alias.alias.account_alias
+  path                  = "/user/"
+  assume_role_policy    = local.assume_role_policy
+  okta_app_id           = data.okta_app.awsapp.id
+}
+
+resource "aws_iam_role_policy_attachment" "mdr_iam_admin_IAMFullAccess" {
+  role       = module.role-mdr_iam_admin.name
+  policy_arn = "arn:${local.aws_partition}:iam::aws:policy/IAMFullAccess"
+}
+
+resource "aws_iam_role_policy_attachment" "mdr_iam_admin-iam_admin_kms" {
+  role       = module.role-mdr_iam_admin.name
+  policy_arn = aws_iam_policy.iam_admin_kms.arn
+}

+ 14 - 0
base/iam/okta_saml_roles/role-mdr_terraformer.tf

@@ -0,0 +1,14 @@
+module "role-mdr_terraformer" {
+  source = "./modules/saml_linked_role"
+
+  name                  = "mdr_terraformer"
+  account_friendly_name = aws_iam_account_alias.alias.account_alias
+  path                  = "/user/"
+  assume_role_policy    = local.assume_role_policy
+  okta_app_id           = data.okta_app.awsapp.id
+}
+
+resource aws_iam_role_policy_attachment "mdr_terraformer-mdr_terraformer" {
+  role       = module.role-mdr_terraformer.name
+  policy_arn = aws_iam_policy.mdr_terraformer.arn
+}

+ 5 - 0
base/iam/okta_saml_roles/saml_provider.tf

@@ -0,0 +1,5 @@
+resource "aws_iam_saml_provider" "okta" {
+  name                   = "OKTA"
+  saml_metadata_document = data.okta_app_metadata_saml.awsapp.metadata
+}
+

+ 12 - 0
base/iam/okta_saml_roles/variables.tf

@@ -0,0 +1,12 @@
+variable okta_app {
+  type = string
+}
+
+variable account_alias {
+  type = string
+}
+
+variable trusted_arns {
+  type    = list(string)
+  default = []
+}

+ 4 - 0
base/iam/okta_saml_roles/versions.tf

@@ -0,0 +1,4 @@
+
+terraform {
+  required_version = ">= 0.12, < 0.13"
+}

+ 23 - 0
base/iam/standard_iam_policies/README.md

@@ -0,0 +1,23 @@
+# Standard IAM Policies module
+
+Defines several well-known IAM policies.
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| aws  | ~2.0?   |
+| okta | ?       |
+
+## Inputs
+
+(none)
+
+## Policies created
+
+| Policy Name        | Description |
+|--------------------|-------------|
+| mdr\_engineer      | "legacy" policy.  Gives effectively PowerUserAccess but with limitations on iam:PassRole and sts:AssumeRole.
+| iam\_admin\_kms    | "legacy" policy.  Gives several `kms:*` actions related to creating, destroying, and managing keys.  Encrypt and Decrypt are noticeably absent.
+| mdr\_engineer\_readonly\_assumerole | Read only access to AWS console with ability to escalate to Terraformer role
+| mdr\_terraformer | Full read/write access to (almost) everything.  Has some limitations around PassRole and AssumeRole

+ 4 - 0
base/iam/standard_iam_policies/datasources.tf

@@ -0,0 +1,4 @@
+data "aws_caller_identity" "current" {}
+
+data "aws_partition" "current" {}
+

+ 7 - 0
base/iam/standard_iam_policies/locals.tf

@@ -0,0 +1,7 @@
+locals {
+
+  # Just to shorten up some references
+  aws_partition = data.aws_partition.current.partition
+  aws_account   = data.aws_caller_identity.current.account_id
+
+}

+ 10 - 0
base/iam/standard_iam_policies/outputs.tf

@@ -0,0 +1,10 @@
+
+# I am unsure about this as an approach versus making a variable for each
+output arns {
+    value = {
+        "mdr_engineer" = aws_iam_policy.mdr_engineer.arn,
+        "iam_admin_kms" = aws_iam_policy.iam_admin_kms.arn,
+        "mdr_readonly_assumerole" = aws_iam_policy.mdr_engineer_readonly_assumerole.arn,
+        "mdr_terraformer" = aws_iam_policy.mdr_terraformer.arn
+    }
+}

+ 71 - 0
base/iam/standard_iam_policies/policy-mdr_engineer.tf

@@ -0,0 +1,71 @@
+#------------------------------------------------------------------------------------------
+# A variant on PowerUserAccess that isn't so damn generous with sts:assumeRole
+#------------------------------------------------------------------------------------------
+data "aws_iam_policy_document" "mdr_engineer" {
+  statement {
+    effect = "Allow"
+    not_actions = [
+      "sts:*",
+      "iam:*",
+      "organizations:*",
+    ]
+    resources = [
+      "*",
+    ]
+  }
+  statement {
+    effect = "Allow"
+    actions = [
+      "iam:CreateServiceLinkedRole",
+      "iam:DeleteServiceLinkedRole",
+      "iam:ListRoles",
+      "iam:ListRolePolicies",
+      "iam:ListInstanceProfiles",
+      "iam:ListPolicies",
+      "iam:GetRole",
+      "iam:GetRolePolicy",
+      "iam:GetInstanceProfile",
+      "iam:GetPolicy",
+      "iam:GetPolicyVersion",
+      "iam:ListAttachedRolePolicies",
+      "organizations:DescribeOrganization",
+    ]
+
+    resources = [
+      "*",
+    ]
+  }
+  statement {
+    effect = "Allow"
+    actions = [
+      "iam:PassRole",
+    ]
+
+    resources = [
+      "arn:${local.aws_partition}:iam::${local.aws_account}:role/instance/*",
+      "arn:${local.aws_partition}:iam::${local.aws_account}:role/lambda/*",
+      "arn:${local.aws_partition}:iam::${local.aws_account}:role/aws_services/*",
+      "arn:${local.aws_partition}:iam::${local.aws_account}:role/fargate/*",
+    ]
+  }
+
+  statement {
+    sid    = "AssumeThisRoleInOtherAccounts"
+    effect = "Allow"
+    actions = [
+      "sts:AssumeRole"
+    ]
+
+    resources = [
+      "arn:${local.aws_partition}:iam::*:role/user/mdr_engineer",
+      "arn:${local.aws_partition}:iam::*:role/mdr_engineer",
+    ]
+  }
+}
+
+resource "aws_iam_policy" "mdr_engineer" {
+  name   = "mdr_engineer"
+  path   = "/user/"
+  policy = data.aws_iam_policy_document.mdr_engineer.json
+}
+

+ 32 - 0
base/iam/standard_iam_policies/policy-mdr_iam_admin.tf

@@ -0,0 +1,32 @@
+data "aws_iam_policy_document" "iam_admin_kms" {
+
+  statement {
+    sid    = "AllowKMSthings"
+    effect = "Allow"
+    actions = [
+      "kms:Create*",
+      "kms:Describe*",
+      "kms:Enable*",
+      "kms:List*",
+      "kms:Put*",
+      "kms:Update*",
+      "kms:Revoke*",
+      "kms:Disable*",
+      "kms:Get*",
+      "kms:Delete*",
+      "kms:TagResource",
+      "kms:UntagResource",
+      "kms:ScheduleKeyDeletion",
+      "kms:CancelKeyDeletion"
+    ]
+    resources = ["*"]
+  }
+
+}
+
+resource "aws_iam_policy" "iam_admin_kms" {
+  name        = "iam_admin_kms"
+  path        = "/user/"
+  description = "KMS access for IAM admins"
+  policy      = data.aws_iam_policy_document.iam_admin_kms.json
+}

+ 55 - 0
base/iam/standard_iam_policies/policy-mdr_readonly_assumerole.tf

@@ -0,0 +1,55 @@
+#------------------------------------------------------------------------------------------
+# A Read Only Engineer.  Assumption is this is everyone's normal working
+# role day-to-day in the AWS console.  When you need it, you then elevate
+# to mdr_terraformer.
+#
+# Note this is NOT JUST READ ONLY ACCESS.  This should only be
+# assigned to ENGINEERS who you expect will able to make changes
+# as needed.  
+#------------------------------------------------------------------------------------------
+data "aws_iam_policy_document" "mdr_engineer_readonly_assumerole" {
+  statement {
+    sid     = "AllowPassRoleForSpecificRoleTypes"
+    effect  = "Allow"
+    actions = [
+      "iam:PassRole",
+    ]
+
+    resources = [
+      "arn:${local.aws_partition}:iam::${local.aws_account}:role/instance/*",
+      "arn:${local.aws_partition}:iam::${local.aws_account}:role/lambda/*",
+      "arn:${local.aws_partition}:iam::${local.aws_account}:role/aws_services/*",
+      "arn:${local.aws_partition}:iam::${local.aws_account}:role/fargate/*",
+    ]
+  }
+
+  statement {
+    sid    = "AssumeThisRoleInOtherAccounts"
+    effect = "Allow"
+    actions = [
+      "sts:AssumeRole"
+    ]
+
+    resources = [
+      "arn:${local.aws_partition}:iam::*:role/user/mdr_engineer_readonly",
+      "arn:${local.aws_partition}:iam::*:role/user/mdr_developer_readonly",
+
+      # Give a readonly engineer the ability if needed to elevate to terraformer
+      # In order to make changes when needed.  
+      "arn:${local.aws_partition}:iam::*:role/user/mdr_terraformer",
+
+      # These two are the legacy roles in the older AWS accounts. 
+      # Adding them in the hope we'll be able to get AssumeRole from
+      # one central place to everything...
+      "arn:${local.aws_partition}:iam::*:role/mdr_powerusers",
+      "arn:${local.aws_partition}:iam::*:role/mdr_iam_admins",
+    ]
+  }
+}
+
+resource "aws_iam_policy" "mdr_engineer_readonly_assumerole" {
+  name   = "mdr_engineer_readonly_assumerole"
+  path   = "/user/"
+  policy = data.aws_iam_policy_document.mdr_engineer_readonly_assumerole.json
+}
+

+ 55 - 0
base/iam/standard_iam_policies/policy-mdr_terraformer.tf

@@ -0,0 +1,55 @@
+#------------------------------------------------------------------------------------------
+# A variant on PowerUserAccess that isn't so damn generous with sts:assumeRole
+#------------------------------------------------------------------------------------------
+data "aws_iam_policy_document" "mdr_terraformer" {
+  statement {
+    sid    = "AllowEverythingButAssumeRoleAndPassRole"
+    effect = "Allow"
+    not_actions = [
+      "sts:AssumeRole",
+      "iam:PassRole",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+  statement {
+    sid     = "AllowPassRoleForSpecificRoleTypes"
+    effect = "Allow"
+    actions = [
+      "iam:PassRole",
+    ]
+
+    resources = [
+      "arn:${local.aws_partition}:iam::${local.aws_account}:role/instance/*",
+      "arn:${local.aws_partition}:iam::${local.aws_account}:role/lambda/*",
+      "arn:${local.aws_partition}:iam::${local.aws_account}:role/aws_services/*",
+      "arn:${local.aws_partition}:iam::${local.aws_account}:role/fargate/*",
+    ]
+  }
+
+  statement {
+    sid    = "AssumeThisRoleInOtherAccounts"
+    effect = "Allow"
+    actions = [
+      "sts:AssumeRole"
+    ]
+
+    resources = [
+      "arn:${local.aws_partition}:iam::*:role/user/mdr_terraformer",
+
+      # These two are the legacy roles in the older AWS accounts. 
+      # Adding them in the hope we'll be able to get AssumeRole from
+      # one central place to everything...
+      "arn:${local.aws_partition}:iam::*:role/mdr_powerusers",
+      "arn:${local.aws_partition}:iam::*:role/mdr_iam_admins",
+    ]
+  }
+}
+
+resource "aws_iam_policy" "mdr_terraformer" {
+  name   = "mdr_terraformer"
+  path   = "/user/"
+  policy = data.aws_iam_policy_document.mdr_terraformer.json
+}
+

+ 3 - 0
base/iam/standard_iam_policies/versions.tf

@@ -0,0 +1,3 @@
+terraform {
+  required_version = ">= 0.12, < 0.13"
+}

+ 3 - 0
base/standard_vpc/README.md

@@ -0,0 +1,3 @@
+# Standard VPC
+
+Sample module to set up a 'standard' vpc, which is a VPC with 3 private and 3 public subnets.

+ 144 - 0
base/standard_vpc/main.tf

@@ -0,0 +1,144 @@
+data "aws_availability_zones" "available" {
+  state = "available"
+}
+
+module "vpc" {
+  source = "terraform-aws-modules/vpc/aws"
+  version = "~> v2.0"
+  name = "${var.name}"
+  cidr = "${var.cidr}"
+
+  azs = slice(data.aws_availability_zones.available.names,0,3)
+
+  private_subnets = [
+      "${cidrsubnet(var.cidr,3,0)}",
+      "${cidrsubnet(var.cidr,3,1)}",
+      "${cidrsubnet(var.cidr,3,2)}",
+  ]
+
+  # Potentially, we could route all accounts through the transit gateway to
+  # save costs and provide one point of exit to the Internet. But at this time,
+  # I'm keeping it consistent with our legacy accounts.
+  #
+  # If we decide to do that, we should consider either dropping to a /23 per customer,
+  # or a /24 for each subnet (seems wasteful).
+  #public_subnets = [ ]
+  public_subnets = [ 
+      "${cidrsubnet(var.cidr,3,4)}",
+      "${cidrsubnet(var.cidr,3,5)}",
+      "${cidrsubnet(var.cidr,3,6)}",
+  ]
+
+  enable_nat_gateway = true
+  enable_vpn_gateway = false
+  enable_dns_hostnames = true
+  enable_s3_endpoint = true
+  enable_dynamodb_endpoint = true
+  enable_sts_endpoint = true
+  enable_kms_endpoint = true
+  enable_dhcp_options = true
+
+  enable_ec2_endpoint              = true
+  ec2_endpoint_private_dns_enabled = true
+  kms_endpoint_private_dns_enabled = true
+  sts_endpoint_private_dns_enabled = true
+  ec2_endpoint_security_group_ids  =  [ "${module.aws_endpoints_sg.this_security_group_id}" ]
+  kms_endpoint_security_group_ids  =  [ "${module.aws_endpoints_sg.this_security_group_id}" ]
+  sts_endpoint_security_group_ids  =  [ "${module.aws_endpoints_sg.this_security_group_id}" ]
+
+  dhcp_options_domain_name = var.inside_domain
+
+  tags = merge(var.standard_tags, var.tags)
+
+  nat_eip_tags = {
+    "eip_type" = "natgw"
+    Name = var.name
+  }
+}
+
+resource "aws_vpc_endpoint" "ec2messages" {
+  vpc_id            = module.vpc.vpc_id
+  service_name      = "com.amazonaws.${var.aws_region}.ec2messages"
+  vpc_endpoint_type = "Interface"
+
+  subnet_ids = slice(module.vpc.public_subnets,0,3)
+  security_group_ids = [
+    module.aws_endpoints_sg.this_security_group_id
+  ]
+  private_dns_enabled = true
+}
+
+resource "aws_vpc_endpoint" "ssm" {
+  vpc_id            = module.vpc.vpc_id
+  service_name      = "com.amazonaws.${var.aws_region}.ssm"
+  vpc_endpoint_type = "Interface"
+
+  subnet_ids = slice(module.vpc.public_subnets,0,3)
+  security_group_ids = [
+    module.aws_endpoints_sg.this_security_group_id
+  ]
+  private_dns_enabled = true
+}
+data "aws_vpc_endpoint_service" "ecr_api_endpoint" {
+  service = "ecr.api"
+}
+
+data "aws_vpc_endpoint_service" "ecr_dkr_endpoint" {
+  service = "ecr.dkr"
+}
+
+resource "aws_vpc_endpoint" "ecr_api" {
+  vpc_id             = module.vpc.vpc_id
+  service_name       = data.aws_vpc_endpoint_service.ecr_api_endpoint.service_name
+  vpc_endpoint_type  = "Interface"
+
+  subnet_ids         = module.vpc.private_subnets
+  security_group_ids = [
+    module.aws_endpoints_sg.this_security_group_id
+  ]
+  private_dns_enabled = true
+}
+
+resource "aws_vpc_endpoint" "ecr_dkr" {
+  vpc_id             = module.vpc.vpc_id
+  service_name       = data.aws_vpc_endpoint_service.ecr_dkr_endpoint.service_name
+  vpc_endpoint_type  = "Interface"
+
+  subnet_ids         = module.vpc.private_subnets
+  security_group_ids = [
+    module.aws_endpoints_sg.this_security_group_id
+  ]
+  private_dns_enabled = true
+}
+
+data "aws_vpc_endpoint_service" "logs_endpoint" {
+  service = "logs"
+}
+
+resource "aws_vpc_endpoint" "logs" {
+  vpc_id             = module.vpc.vpc_id
+  service_name       = data.aws_vpc_endpoint_service.logs_endpoint.service_name
+  vpc_endpoint_type  = "Interface"
+
+  subnet_ids         = module.vpc.private_subnets
+  security_group_ids = [
+    module.aws_endpoints_sg.this_security_group_id
+  ]
+  private_dns_enabled = true
+}
+
+data "aws_vpc_endpoint_service" "monitoring_endpoint" {
+  service = "monitoring"
+}
+
+resource "aws_vpc_endpoint" "monitoring" {
+  vpc_id             = module.vpc.vpc_id
+  service_name       = data.aws_vpc_endpoint_service.monitoring_endpoint.service_name
+  vpc_endpoint_type  = "Interface"
+
+  subnet_ids         = module.vpc.private_subnets
+  security_group_ids = [
+    module.aws_endpoints_sg.this_security_group_id
+  ]
+  private_dns_enabled = true
+}

+ 21 - 0
base/standard_vpc/outputs.tf

@@ -0,0 +1,21 @@
+output vpc_id {
+  value = module.vpc.vpc_id
+}
+
+output public_subnets {
+  value = module.vpc.public_subnets
+}
+
+output private_subnets {
+  value = module.vpc.private_subnets
+}
+
+output allow_all_sg_id {
+  value = module.allow_all_sg.this_security_group_id
+}
+
+output allow_all_outbound_sg_id {
+  value = module.allow_all_outbound_sg.this_security_group_id
+}
+
+

+ 89 - 0
base/standard_vpc/security-groups.tf

@@ -0,0 +1,89 @@
+# Several of these security groups will have customer IPs listed in them to allow
+# POP systems to access our services.
+#
+
+locals {
+}
+
+module "aws_endpoints_sg" {
+  use_name_prefix = false
+  source = "terraform-aws-modules/security-group/aws"
+  version = "~> 3"
+  name        = "aws_endpoints"
+  tags        = merge(var.standard_tags, var.tags)
+  vpc_id      = module.vpc.vpc_id
+
+  ingress_cidr_blocks = [ module.vpc.vpc_cidr_block ]
+  egress_cidr_blocks = [ module.vpc.vpc_cidr_block ]
+  egress_ipv6_cidr_blocks = [ ]
+
+  egress_rules = [ "all-all" ]
+  ingress_rules = [ "all-all" ]
+}
+
+
+#TODO: Probably want this one available everywhere
+#module "vpc_default_security_groups" {
+#  source = "../modules/vpc_security_groups"
+#  version = "~> 2.17"
+#  name                   = "toolsvpc"
+#  tags                   = merge(var.standard_tags, var.tags)
+#  this_vpc               = "${module.vpc.vpc_id}"
+#
+#  ec2_prefix_list_count  = 1
+#  ec2_prefix_lists       = [ "${module.vpc.vpc_endpoint_s3_pl_id}" ]
+#  salt_masters_sg        = "${module.salt_masters_sg.this_security_group_id}"
+#  bastion_ssh_sg         = "${module.bastion_servers_sg.this_security_group_id}"
+#  proxy_servers_sg       = "${module.proxy_servers_sg.this_security_group_id}"
+#  sensu_servers_sg       = "${module.sensu_servers_sg.this_security_group_id}"
+#  repo_servers_sg        = "${module.repo_servers_sg.this_security_group_id}"
+#  idm_inbound_sg         = "${module.idm_inbound_sg.this_security_group_id}"
+#  openvpn_servers_sg     = "${module.openvpn_servers_sg.this_security_group_id}"
+#  phantom_servers_sg     = "${module.phantom_servers_sg.this_security_group_id}"
+#  mailrelay_sg           = "${module.mailrelay_sg.this_security_group_id}"
+#  moose_sg               = "${module.moose_inbound_sg.this_security_group_id}"
+#  vuln_scanner_sg_count = 1
+#  vuln_scanner_sgs      = [ "${module.vuln_scanners_sg.this_security_group_id}" ]
+#}
+
+
+module "allow_all_sg" {
+  use_name_prefix = false
+  source = "terraform-aws-modules/security-group/aws"
+  version = "~> 3"
+  name        = "allow-all"
+  tags        = merge(var.standard_tags, var.tags)
+  vpc_id      = module.vpc.vpc_id
+
+  ingress_cidr_blocks = [ "0.0.0.0/0" ]
+  egress_cidr_blocks = [ "0.0.0.0/0" ]
+  ingress_rules = [ "all-all" ]
+  egress_rules = [ "all-all" ]
+}
+
+module "allow_all_outbound_sg" {
+  use_name_prefix = false
+  source = "terraform-aws-modules/security-group/aws"
+  version = "~> 3"
+  name        = "allow-all-outbound"
+  tags        = merge(var.standard_tags, var.tags)
+  vpc_id      = module.vpc.vpc_id
+
+  egress_rules = [ "all-all" ]
+}
+
+# TODO: Do we still want direct ssh as a standard SG? I think we want
+# to avoid this, so I'd say create it only with resources that need it.
+#module "ssh_all_sg" {
+#  use_name_prefix = false
+#  source = "terraform-aws-modules/security-group/aws"
+#  version = "~> 2.17"
+#  name        = "ssh-any"
+#  tags        = merge(var.standard_tags, var.tags)
+#  vpc_id      = "${module.vpc.vpc_id}"
+#
+#  ingress_cidr_blocks = "${local.ssh_jump_whitelist}"
+#
+#  egress_cidr_blocks = [ "0.0.0.0/0" ]
+#  ingress_rules = [ "ssh-tcp", "all-icmp" ]
+#}

+ 202 - 0
base/standard_vpc/typicalhost.tf.disabled

@@ -0,0 +1,202 @@
+# TODO: We probably want this in this module as a standard group in all VPCs, but disabling
+# for now due to complexity.
+#
+# For a "typical host" we have some simple expectations
+#   - able to talk to one of the various salt masters
+#   - able to talk to Amazon's DNS servers
+#   - allow inbound SSH from bastion
+#   - any outbound RPM repo access needed
+#   - 9998/tcp to moose indexers
+#
+#
+# The following is a little complicated because the mainline security-group module
+# is lacking a little in being able to be super expressive w/ rules.  So we
+# create the base SG with the module, and then attach more detailed rules to it when
+# complete
+module "typical_host_sg" {
+  use_name_prefix = false
+  source = "terraform-aws-modules/security-group/aws"
+  version = "~> 2.17"
+  name        = "typical-host"
+  tags        = "${local.standard_tags}"
+  vpc_id      = "${module.vpc.vpc_id}"
+
+  ingress_cidr_blocks = [ "10.0.0.0/8" ]
+  ingress_rules = [ "all-icmp" ]
+
+  egress_ipv6_cidr_blocks = [ ]
+
+  egress_with_cidr_blocks = [
+    {
+      description = "TCP DNS to Amazon VPC DNS Server"
+      rule        = "dns-tcp"
+      cidr_blocks = "${cidrhost(module.vpc.vpc_cidr_block,2)}/32"
+    },
+    {
+      description = "UDP DNS to Amazon VPC DNS Server"
+      rule        = "dns-udp"
+      cidr_blocks = "${cidrhost(module.vpc.vpc_cidr_block,2)}/32"
+    },
+
+    {
+      description = "ICMP"
+      rule 				= "all-icmp"
+      cidr_blocks = "10.0.0.0/8"
+  	},
+
+	]
+
+  #egress_with_ipv6_cidr_blocks = [
+  #  {
+  #    description = "Saltstack RPM Repos IPv6"
+  #    rule 				= "https-443-tcp"
+  #    ipv6_cidr_blocks = "2604:a880:400:d0::2:e001/128"
+  #  }
+  #]
+}
+
+resource "aws_security_group_rule" "outbound_to_salt_masters"
+{
+  type = "egress"
+  from_port = 4505
+  to_port = 4506
+  protocol = 6
+  source_security_group_id = "${module.salt_masters_sg.this_security_group_id}"
+  security_group_id = "${module.typical_host_sg.this_security_group_id}"
+  description = "Connect to Salt Masters"
+}
+
+resource "aws_security_group_rule" "outbound_to_repo_servers_80"
+{
+  type = "egress"
+  from_port = 80
+  to_port = 80
+  protocol = 6
+  source_security_group_id = "${module.repo_servers_sg.this_security_group_id}"
+  security_group_id = "${module.typical_host_sg.this_security_group_id}"
+  description = "Connect to Repo Servers"
+}
+
+resource "aws_security_group_rule" "inbound_ssh_bastion"
+{
+  type = "ingress"
+  from_port = 22
+  to_port = 22
+  protocol = 6
+  security_group_id = "${module.typical_host_sg.this_security_group_id}"
+  source_security_group_id = "${module.bastion_servers_sg.this_security_group_id}"
+  #cidr_blocks = [ "${formatlist("%s/32",module.bastion.private_ip)}" ]
+  description = "Inbound SSH from bastions"
+}
+
+resource "aws_security_group_rule" "typical_host_inbound_ssh_openvpn"
+{
+  type = "ingress"
+  from_port = 22
+  to_port = 22
+  protocol = 6
+  security_group_id = "${module.typical_host_sg.this_security_group_id}"
+  source_security_group_id = "${module.openvpn_servers_sg.this_security_group_id}"
+  description = "Inbound SSH from openvpn"
+}
+
+resource "aws_security_group_rule" "outbound_to_ec2_endpoints"
+{
+  type = "egress"
+  from_port = 0
+  to_port = 0
+  protocol = -1
+  security_group_id = "${module.typical_host_sg.this_security_group_id}"
+  source_security_group_id = "${module.aws_endpoints_sg.this_security_group_id}"
+  description = "Outbound to EC2 endpoints"
+}
+
+resource "aws_security_group_rule" "outbound_to_ec2_s3_endpoint"
+{
+  type = "egress"
+  from_port = 0
+  to_port = 0
+  protocol = -1
+  security_group_id = "${module.typical_host_sg.this_security_group_id}"
+  prefix_list_ids = [ "${module.vpc.vpc_endpoint_s3_pl_id}" ]
+  description = "Outbound to S3 endpoint"
+}
+
+resource "aws_security_group_rule" "outbound_to_squid_http"
+{
+  type = "egress"
+  from_port = 80
+  to_port = 80
+  protocol = 6
+  source_security_group_id = "${module.proxy_servers_sg.this_security_group_id}"
+  security_group_id = "${module.typical_host_sg.this_security_group_id}"
+  description  = "HTTPS outbound to proxies"
+}
+
+resource "aws_security_group_rule" "outbound_to_mailrelay_25"
+{
+  type = "egress"
+  from_port = 25
+  to_port = 25
+  protocol = 6
+  source_security_group_id = "${module.mailrelay_sg.this_security_group_id}"
+  security_group_id = "${module.typical_host_sg.this_security_group_id}"
+  description = "Outbound Email to mailrelay"
+}
+
+resource "aws_security_group_rule" "outbound_to_sensu"
+{
+  type = "egress"
+  from_port = 8081
+  to_port   = 8081
+  protocol  = "tcp"
+  source_security_group_id = "${module.sensu_servers_sg.this_security_group_id}"
+  security_group_id        = "${module.typical_host_sg.this_security_group_id}"
+  description              = "Sensu Outbound"
+}
+
+resource "aws_security_group_rule" "outbound_to_moose_s2s"
+{
+  type = "egress"
+  from_port = 9997
+  to_port   = 9998
+  protocol  = "tcp"
+  #cidr_blocks              = [ "${module.vpc.vpc_cidr_block}" ]
+  source_security_group_id = "${module.moose_inbound_sg.this_security_group_id}"
+  security_group_id        = "${module.typical_host_sg.this_security_group_id}"
+  description              = "Splunk UF outbound to Moose Indexers"
+}
+
+resource "aws_security_group_rule" "outbound_to_moose_idxc"
+{
+  type = "egress"
+  from_port = 8089
+  to_port   = 8089
+  protocol  = "tcp"
+  #cidr_blocks              = [ "${module.vpc.vpc_cidr_block}" ]
+  source_security_group_id = "${module.moose_inbound_sg.this_security_group_id}"
+  security_group_id        = "${module.typical_host_sg.this_security_group_id}"
+  description              = "Outbound IDXC Discovery to MOOSE"
+}
+
+resource "aws_security_group_rule" "outbound_to_moose_hec"
+{
+  type = "egress"
+  from_port = 8088
+  to_port = 8088
+  protocol = 6
+  source_security_group_id = "${module.moose_inbound_sg.this_security_group_id}"
+  security_group_id = "${module.typical_host_sg.this_security_group_id}"
+  description = "Connect to HEC"
+}
+
+resource "aws_security_group_rule" "inbound_from_vuln_scanners"
+{
+  type = "ingress"
+  from_port = -1
+  to_port = -1
+  protocol = -1
+  source_security_group_id = "${module.vuln_scanners_sg.this_security_group_id}"
+  security_group_id = "${module.typical_host_sg.this_security_group_id}"
+  description = "Allow all from Vuln Scanners"
+}

+ 51 - 0
base/standard_vpc/vars.tf

@@ -0,0 +1,51 @@
+variable "cidr" {
+  description = "The CIDR Block for the VPC"
+  type        = string
+}
+
+variable "name" {
+  description = "The name for the VPC"
+  type        = string
+}
+
+variable "tags" {
+  description = "Tags to add to the resource (in addition to global standard tags)"
+  type        = map
+  default     = { }
+}
+
+# ----------------------------------
+# Below this line are variables inherited from higher levels, so they
+# do not need to be explicitly passed to this module.
+variable "standard_tags" {
+  type        = map
+}
+
+variable "inside_domain" {
+  type        = string
+}
+
+variable "aws_region" {
+  type        = string
+}
+
+#variable "environment_vars" {
+#  description = "Environment Vars"
+#  type        = map
+#}
+#
+#variable "partition_vars" {
+#  description = "Partition Vars"
+#  type        = map
+#}
+#
+#variable "region_vars" {
+#  description = "Region Vars"
+#  type        = map
+#}
+#
+#variable "account_vars" {
+#  description = "Account Vars"
+#  type        = map
+#}
+

+ 3 - 0
base/standard_vpc/version.tf

@@ -0,0 +1,3 @@
+terraform {
+  required_version = "~> 0.12"
+}

+ 4 - 0
base/tfstate/tfstate-s3/0.1/datasources.tf

@@ -0,0 +1,4 @@
+data "aws_caller_identity" "current" {}
+
+data "aws_partition" "current" {}
+

+ 22 - 0
base/tfstate/tfstate-s3/0.1/dynamodb.tf

@@ -0,0 +1,22 @@
+resource "aws_dynamodb_table" "lock_table" {
+  name           = var.lock_table_name
+  billing_mode   = "PAY_PER_REQUEST"
+  hash_key       = "LockID"
+
+  depends_on = [ var.module_depends_on ]
+
+  server_side_encryption {
+    enabled = true
+    kms_key_arn = aws_kms_key.tfstate.arn
+  }
+
+  attribute {
+    name = "LockID"
+    type = "S"
+  }
+
+  tags = {
+    Name = var.lock_table_name
+  }
+
+}

+ 109 - 0
base/tfstate/tfstate-s3/0.1/kms.tf

@@ -0,0 +1,109 @@
+resource "aws_kms_key" "tfstate" {
+  description             = "tfstate bucket default S3 SSE-KMS"
+  deletion_window_in_days = 30
+  enable_key_rotation     = true
+  policy                  = data.aws_iam_policy_document.kms_key_policy_tfstate.json
+
+  depends_on = [ var.module_depends_on ]
+}
+
+resource "aws_kms_alias" "tfstate" {
+  name          = "alias/tfstate"
+  target_key_id = aws_kms_key.tfstate.key_id
+
+  depends_on = [ var.module_depends_on ]
+}
+
+
+data "aws_iam_policy_document" "kms_key_policy_tfstate" {
+
+  policy_id = "key-consolepolicy-3"
+  statement {
+    sid    = "Enable IAM User Permissions"
+    effect = "Allow"
+    principals {
+      type        = "AWS"
+      identifiers = ["arn:${local.aws_partition}:iam::${local.aws_account}:root"]
+    }
+    actions   = ["kms:*"]
+    resources = ["*"]
+  }
+
+  statement {
+    sid    = "Allow access for Key Administrators"
+    effect = "Allow"
+    principals {
+      type = "AWS"
+      identifiers = [
+        # FIXME:  I'm trying to decide if these should be hard-coded or
+        # parameters, or some mix/match of each.  
+        "arn:${local.aws_partition}:iam::${local.aws_account}:user/MDRAdmin",
+        #"arn:${local.aws_partition}:iam::${local.aws_account}:role/user/mdr_engineer",
+        #"arn:${local.aws_partition}:iam::${local.aws_account}:role/user/mdr_iam_admin"
+      ]
+    }
+
+    actions = [
+      "kms:Create*",
+      "kms:Describe*",
+      "kms:Enable*",
+      "kms:List*",
+      "kms:Put*",
+      "kms:Update*",
+      "kms:Revoke*",
+      "kms:Disable*",
+      "kms:Get*",
+      "kms:Delete*",
+      "kms:TagResource",
+      "kms:UntagResource",
+      "kms:ScheduleKeyDeletion",
+      "kms:CancelKeyDeletion"
+    ]
+    resources = ["*"]
+  }
+
+  statement {
+    sid    = "Allow use of the key"
+    effect = "Allow"
+    principals {
+      type = "AWS"
+      identifiers = [
+        "arn:${local.aws_partition}:iam::${local.aws_account}:user/MDRAdmin",
+        #"arn:${local.aws_partition}:iam::${local.aws_account}:role/user/mdr_engineer",
+        #"arn:${local.aws_partition}:iam::${local.aws_account}:role/user/mdr_iam_admin"
+      ]
+    }
+    actions = [
+      "kms:Encrypt",
+      "kms:Decrypt",
+      "kms:ReEncrypt*",
+      "kms:GenerateDataKey*",
+      "kms:DescribeKey"
+    ]
+    resources = ["*"]
+  }
+
+  statement {
+    sid    = "Allow attachment of persistent resources"
+    effect = "Allow"
+    principals {
+      type = "AWS"
+      identifiers = [
+        "arn:${local.aws_partition}:iam::${local.aws_account}:user/MDRAdmin",
+        #"arn:${local.aws_partition}:iam::${local.aws_account}:role/user/mdr_engineer",
+        #"arn:${local.aws_partition}:iam::${local.aws_account}:role/user/mdr_iam_admin"
+      ]
+    }
+    actions = [
+      "kms:CreateGrant",
+      "kms:ListGrants",
+      "kms:RevokeGrant"
+    ]
+    resources = ["*"]
+    condition {
+      test     = "Bool"
+      variable = "kms:GrantIsForAWSResource"
+      values   = ["true"]
+    }
+  }
+}

+ 4 - 0
base/tfstate/tfstate-s3/0.1/locals.tf

@@ -0,0 +1,4 @@
+locals {
+  aws_partition = data.aws_partition.current.partition
+  aws_account   = data.aws_caller_identity.current.account_id
+}

+ 3 - 0
base/tfstate/tfstate-s3/0.1/outputs.tf

@@ -0,0 +1,3 @@
+output "lock_table_arn" {
+  value = "${aws_dynamodb_table.lock_table.arn}"
+}

+ 48 - 0
base/tfstate/tfstate-s3/0.1/s3.tf

@@ -0,0 +1,48 @@
+resource "aws_s3_bucket" "tfstate" {
+  bucket = var.bucket_name
+  acl    = "private"
+
+  depends_on = [ var.module_depends_on ]
+
+  versioning {
+    enabled = true
+  }
+
+  # FIXME: Does this keep a cross-account dependency?
+  #logging {
+  #target_bucket = "dps-s3-logs"
+  #target_prefix = "aws_terraform_s3_state_access_logs/"
+  #}
+
+  lifecycle_rule {
+    enabled                                = true
+    prefix                                 = ""
+    abort_incomplete_multipart_upload_days = 7
+
+    noncurrent_version_transition {
+      days          = 30
+      storage_class = "STANDARD_IA"
+    }
+
+    noncurrent_version_expiration {
+      days = 730
+    }
+  }
+
+  server_side_encryption_configuration {
+    rule {
+      apply_server_side_encryption_by_default {
+        kms_master_key_id = aws_kms_key.tfstate.arn
+        sse_algorithm     = "aws:kms"
+      }
+    }
+  }
+}
+
+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
+}

+ 12 - 0
base/tfstate/tfstate-s3/0.1/variables.tf

@@ -0,0 +1,12 @@
+variable bucket_name {
+  type = string
+}
+
+variable lock_table_name {
+  type = string
+}
+
+variable "module_depends_on" {
+  type    = any
+  default = null
+}

+ 3 - 0
base/vmray_instances/README.md

@@ -0,0 +1,3 @@
+# vmray instances
+
+Builds and configures the instances required for VMRay

+ 21 - 0
base/vmray_instances/cloud-init/cloud-init.tpl

@@ -0,0 +1,21 @@
+#cloud-config
+# TODO: This needs to be customized/fixed
+preserve_hostname: false
+hostname: ${hostname}
+fqdn: ${fqdn}
+
+runcmd:
+ - echo "${fqdn}" > /etc/salt/minion_id
+ - /bin/systemctl restart salt-minion 
+ - /bin/systemctl enable salt-minion
+ - /bin/systemctl start amazon-ssm-agent
+ - /bin/systemctl enable amazon-ssm-agent
+ - /usr/sbin/aide --update --verbose=0
+ - /bin/cp /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
+
+# httpd here to avoid a chicken and egg for repo server
+# installing it via salt state won't work because by then
+# the salt states could have us pointed to ourselves as the repo server
+packages:
+ - httpd
+

+ 25 - 0
base/vmray_instances/fipsnotes.md

@@ -0,0 +1,25 @@
+# Quick notes on activating FIPS on ubuntu pro
+
+```
+sudo add-apt-repository ppa:canonical-server/ua-client-daily --yes
+sudo apt update
+sudo apt install ubuntu-advantage-tools ubuntu-advantage-pro --yes
+
+# Interactive:
+sudo ua enable fips --beta 
+# have to press "r" a few times during the process, even though it doesn't prompt
+sudo apt install linux-aws-fips --yes
+
+
+# Non-interactive
+KERNEL=$(awk -F"'" '/menuentry.*fips/ { print $(NF-1); exit }' /boot/grub/grub.cfg)
+sudo tee /etc/default/grub.d/99-fips.cfg << __EOF__ 
+GRUB_DEFAULT="1>$KERNEL"
+GRUB_CMDLINE_LINUX_DEFAULT="\$GRUB_CMDLINE_LINUX_DEFAULT fips=1"
+__EOF__
+# Paste this too or just hit <enter>
+
+sudo update-grub
+shutdown -r now
+```
+

+ 103 - 0
base/vmray_instances/main.tf

@@ -0,0 +1,103 @@
+data "aws_ami" "ubuntu_pro" {
+  # Ubuntu Pro 18.04 Product ID: fc15dd7b-2aa0-47e5-bb05-94c75950b5de
+
+  most_recent = true
+  owners = [ var.aws_marketplace_ubuntu_owner_id ]
+
+  # sadly, it looks like the image isn't named correctly in govcloud. I've given
+  # canonical feedback on this, but for now we'll use the ami id directly.
+  #filter {
+  #  name   = "name"
+  #  values = ["ubuntu-pro/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*", ]
+  #}
+
+  filter {
+    name   = "image-id"
+    values = [ "ami-0a4050943619c0460" ]
+  }
+
+  filter {
+    name   = "root-device-type"
+    values = ["ebs"]
+  }
+
+  filter {
+    name   = "virtualization-type"
+    values = ["hvm"]
+  }
+}
+
+module "vmray-server" {
+  source                 = "terraform-aws-modules/ec2-instance/aws"
+  version                = "~> 2.0"
+
+  name                   = "vmray-server"
+  instance_count         = 1
+  disable_api_termination = var.instance_termination_protection
+
+  ami                    = data.aws_ami.ubuntu_pro.image_id
+  instance_type          = var.vmray_server_instance_type
+  key_name               = var.vmray_key_name
+  vpc_security_group_ids = [ aws_security_group.vmray_sg.id ]
+  subnet_id              = data.terraform_remote_state.standard_vpc.outputs.public_subnets[0]
+  tags = merge(var.standard_tags, var.tags)
+
+  ebs_optimized          = true
+  monitoring             = false # Do we use this?
+
+  user_data_base64 = "${data.template_cloudinit_config.cloud-init-vmray-server.rendered}"
+}
+
+#
+module "vmray-worker" {
+  source                 = "terraform-aws-modules/ec2-instance/aws"
+  version                = "~> 2.0"
+
+  name                   = "vmray-worker"
+  instance_count         = var.vmray_worker_instance_count
+  disable_api_termination = var.instance_termination_protection
+
+  ami                    = data.aws_ami.ubuntu_pro.image_id
+  instance_type          = var.vmray_worker_instance_type
+  key_name               = var.vmray_key_name
+  vpc_security_group_ids = [ aws_security_group.vmray_sg.id ]
+  subnet_ids             = data.terraform_remote_state.standard_vpc.outputs.public_subnets
+  tags = merge(var.standard_tags, var.tags)
+
+  ebs_optimized          = true
+  monitoring             = false
+
+  #user_data_base64 = "${data.template_cloudinit_config.vmray-worker.*.rendered}"
+}
+
+
+data "template_file" "cloud-init-vmray-server" {
+  # Should these be in a common directory? I suspect they'd be reusable
+  template = "${file("${path.module}/cloud-init/cloud-init.tpl")}"
+
+  vars = {
+    hostname = "vmray_server"
+    fqdn = "vmrayserver.TODO.makemeavariable"
+    environment = var.environment
+  }
+}
+
+# Render a multi-part cloud-init config making use of the part
+# above, and other source files
+data "template_cloudinit_config" "cloud-init-vmray-server" {
+  gzip          = true
+  base64_encode = true
+
+  # Main cloud-config configuration file.
+  part {
+    filename     = "init.cfg"
+    content_type = "text/cloud-config"
+    content      = "${data.template_file.cloud-init-vmray-server.rendered}"
+  }
+
+  # Additional parts as needed
+  #part {
+  #  content_type = "text/x-shellscript"
+  #  content      = "ffbaz"
+  #}
+}

+ 23 - 0
base/vmray_instances/outputs.tf

@@ -0,0 +1,23 @@
+output vmray_server_arn {
+  value = module.vmray-server.arn
+}
+
+output vmray_server_public_ip {
+  value = module.vmray-server.public_ip
+}
+
+output vmray_server_private_ip {
+  value = module.vmray-server.private_ip
+}
+
+output vmray_worker_arn {
+  value = module.vmray-worker.arn
+}
+
+output vmray_worker_public_ip {
+  value = module.vmray-worker.public_ip
+}
+
+output vmray_worker_private_ip {
+  value = module.vmray-worker.private_ip
+}

+ 11 - 0
base/vmray_instances/remote_state.tf

@@ -0,0 +1,11 @@
+data "terraform_remote_state" "standard_vpc" {
+  backend = "s3"
+  config = {
+    bucket = var.remote_state_bucket
+    region = var.aws_region
+    encrypt = true
+    key = "aws/test/aws-us-gov/mdr-test-malware/010-standard-vpc/terraform.tfstate" # TODO
+    profile = var.common_profile
+    role_arn = "arn:${var.aws_partition}:iam::${var.common_services_account}:role/user/mdr_terraformer"
+  }
+}

+ 36 - 0
base/vmray_instances/security-groups.tf

@@ -0,0 +1,36 @@
+resource "aws_security_group" "vmray_sg" {
+  name        = "vmray_sg"
+  description = "Security Rules Specific to VMRay"
+  vpc_id      = data.terraform_remote_state.standard_vpc.outputs.vpc_id
+
+  tags        = merge(var.standard_tags, var.tags)
+}
+
+resource "aws_security_group_rule" "vmray-ssh" {
+  type              = "ingress"
+  from_port         = 22
+  to_port           = 22
+  protocol          = "tcp"
+  cidr_blocks       = var.portal_test_whitelist
+  security_group_id = aws_security_group.vmray_sg.id
+}
+
+resource "aws_security_group_rule" "vmray-https" {
+  type              = "ingress"
+  from_port         = 443
+  to_port           = 443
+  protocol          = "tcp"
+  cidr_blocks       = var.portal_test_whitelist
+  security_group_id = aws_security_group.vmray_sg.id
+}
+
+resource "aws_security_group_rule" "vmray-egress" {
+  type              = "egress"
+  from_port         = 0 # all ports
+  to_port           = 0 # all ports
+  protocol          = "all"
+  cidr_blocks       = [ "0.0.0.0/0" ]
+  security_group_id = aws_security_group.vmray_sg.id
+}
+
+

+ 75 - 0
base/vmray_instances/vars.tf

@@ -0,0 +1,75 @@
+variable "vmray_server_instance_type" {
+  description = "Instance type for the controlling server."
+  type        = string
+}
+
+variable "vmray_worker_instance_type" {
+  description = "Instance type for the worker(s). Must be a bare metal instance type."
+  type        = string
+}
+
+variable "vmray_worker_instance_count" {
+  description = "How many workers to build. These are expensive."
+  type        = number
+}
+
+variable "vmray_key_name" {
+  description = "Provisioning key for new instance. Will be cleaned up via salt. Do not change after initial provisioning."
+  type = string
+}
+
+variable "tags" {
+  description = "Tags to add to the resource (in addition to global standard tags)"
+  type        = map
+  default     = { }
+}
+
+# ----------------------------------
+# Below this line are variables inherited from higher levels, so they
+# do not need to be explicitly passed to this module.
+variable "instance_termination_protection" {
+  type        = bool
+}
+
+variable "standard_tags" {
+  type        = map
+}
+
+variable "inside_domain" {
+  type        = string
+}
+
+variable "aws_marketplace_ubuntu_owner_id" {
+  type        = string
+}
+
+variable "environment" {
+  type        = string
+}
+
+variable "portal_test_whitelist" {
+  type        = list
+}
+
+
+# ----------------------------------
+# Required for remote state, though they can be used elsewhere
+variable "remote_state_bucket" {
+  type = string
+}
+
+variable "aws_region" {
+  type = string
+}
+
+variable "aws_partition" {
+  type = string
+}
+
+variable "common_services_account" {
+  type = string
+}
+
+variable "common_profile" {
+  type = string
+}

+ 3 - 0
base/vmray_instances/version.tf

@@ -0,0 +1,3 @@
+terraform {
+  required_version = "~> 0.12"
+}

+ 3 - 0
submodules/README.md

@@ -0,0 +1,3 @@
+# submodules
+
+This directory contains modules that are called from other modules.

+ 8 - 0
thirdparty/README.md

@@ -0,0 +1,8 @@
+# thirdparty
+
+This directory contains modules developed by third parties. Please be sure to include:
+* The URL of the original repository
+* The URL of a copy of the original repository (fork it to our github, please!)
+* The version, if any, that is present.
+* The date of the copy
+* Any custom changes