浏览代码

Creates a generic_s3_bucket_with_role module

Creates:
* S3 Bucket
* KMS Key
* Role that can be assumed to do things with the above

Also: Updates portal terraform to allow assuming a role created by this
module.

To be tagged v4.1.10
Fred Damstra [afs macbook] 3 年之前
父节点
当前提交
1992399174

+ 24 - 0
base/customer_portal/ecr.tf

@@ -109,3 +109,27 @@ resource "aws_iam_role_policy_attachment" "portal_server_s3_binaries" {
   policy_arn = data.aws_iam_policy.default_instance_policy_s3_binaries.arn
 }
 
+# Assume Role Policy -- Needed for S3 Bucket Access in Cross-Accounts
+data "aws_iam_policy_document" "portal_server_assumerole" {
+  statement {
+    actions = [
+      "sts:AssumeRole"
+    ]
+
+    resources = [
+      "arn:${var.aws_partition}:iam::*:role/service/xdr-${var.environment}-portal-shared-artifacts",
+      "arn:${var.aws_partition}:iam::*:role/service/xdr-${var.environment}-*-portal-customer-artifacts",
+    ]
+  }
+}
+
+resource "aws_iam_policy" "portal_server_assumerole_policy" {
+  name     = "portal_server_assumerole"
+  path     = "/launchroles/"
+  policy   = data.aws_iam_policy_document.portal_server_assumerole.json
+}
+
+resource "aws_iam_role_policy_attachment" "portal_server_assumerole" {
+  role       = aws_iam_role.portal_server.name
+  policy_arn = aws_iam_policy.portal_server_assumerole_policy.arn
+}

+ 30 - 0
base/generic_s3_bucket_with_role/README.md

@@ -0,0 +1,30 @@
+# Creates an S3 bucket, KMS key, and a role that can be assumed to access them 
+
+## Full Bucket and Role Names
+The bucket and roles will be prefixed with "xdr-{environment}-". If the splunk_prefix is required, it will need to be passed in as part of the name.
+
+## Important Note about Lifecycles
+Versioning is enabled in the bucket. The current version and the previous version are always kept. Older versions are expired after 90 days.
+
+All items transition to a intelligent tiering after 30 days.
+
+## Testing
+For testing, the instance must assume-role first. To do this from the command-line:
+
+```
+aws --region us-gov-east-1 sts assume-role --role-arn [ARN] --role-session-name ftd_testing
+# Output will contain AccessKeyId and SecretAcessKeyID
+AWS_ACCESS_KEY_ID=[REPLACE] AWS_SECRET_ACCESS_KEY=[REPLACE] AWS_SESSION_TOKEN=[replace] aws --region us-gov-east-1 s3 ls
+```
+
+NOTE: You cannot generate the correct presigned url with older versions of the AWS CLI, such as the one presently (2022-04-19) installed on the portal boxes. You will get the error:
+```
+<Error>
+  <Code>InvalidRequest</Code>
+  <Message>The authorization mechanism you have provided is not supported. Please use AWS4-HMAC-SHA256.</Message>
+  <RequestId>VJF9V2CAQR1XZER6</RequestId>
+  <HostId>bJ/waruDGO4FC2VQoTRRtwGnehzOScUpu8JbXnCQ7L8vVULm9RGLF8EqAXjSAViM+HdXXDI4rqM=</HostId>
+</Error>
+```
+
+Newer boto3 requests will use signature version 4, which works correctly.

+ 140 - 0
base/generic_s3_bucket_with_role/iam.tf

@@ -0,0 +1,140 @@
+data "aws_iam_policy_document" "assume_role_policy" {
+  statement {
+    sid     = "AllowRoles"
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+
+    principals {
+      type        = "AWS"
+      identifiers = var.role_assumers
+    }
+  }
+}
+
+resource "aws_iam_role" "role" {
+  name = local.fullname
+  path  = "/service/"
+  force_detach_policies = true # causes "DeleteConflict" if not present
+
+  assume_role_policy = data.aws_iam_policy_document.assume_role_policy.json
+
+  tags = merge(var.standard_tags, var.tags)
+}
+
+# Appears the role can automatically create presigned URLs
+#resource "aws_iam_role_policy_attachment" "policy_attach_presigned_url" {
+#  count = var.allow_presigned ? 1 : 0
+#
+#  role = aws_iam_role.role.name
+#  policy_arn = aws_iam_policy.policy_presigned_url.arn
+#}
+#
+#resource "aws_iam_policy" "policy_presigned_url" {
+#  count = var.allow_presigned ? 1 : 0
+#
+#  name_prefix  = var.name
+#  path  = "/service/"
+#  description = "Policy to allow signing of URLs for the ${local.fullname} bucket"
+#  policy = data.aws_iam_policy_document.policy_doc_presigned_url.json
+#}
+#
+#data "aws_iam_policy_document" "policy_doc_presigned_url" {
+#  count = var.allow_presigned ? 1 : 0
+#
+#  statement {
+#    sid = "TODO"
+#    effect = "Allow"
+#    actions = [
+#      "s3:ListAllMyBuckets",
+#      "s3:HeadBucket",
+#    ]
+#    resources = [ "*" ]
+#  }
+#}
+
+resource "aws_iam_role_policy_attachment" "policy_attach" {
+  role = aws_iam_role.role.name
+  policy_arn = aws_iam_policy.policy.arn
+}
+
+resource "aws_iam_policy" "policy" {
+  name_prefix  = var.name
+  path  = "/service/"
+  description = "Policy to allow use of the ${local.fullname} bucket"
+  policy = data.aws_iam_policy_document.policy_doc.json
+}
+
+data "aws_iam_policy_document" "policy_doc" {
+  statement {
+    sid = "GeneralBucketAccess"
+    effect = "Allow"
+    actions = [
+      "s3:ListAllMyBuckets",
+      "s3:HeadBucket",
+    ]
+    resources = [ "*" ]
+  }
+
+  statement {
+    sid = "S3BucketAccess"
+    effect = "Allow"
+    actions = [
+      "s3:GetLifecycleConfiguration",
+      "s3:DeleteObjectVersion",
+      "s3:ListBucketVersions",
+      "s3:GetBucketLogging",
+      "s3:RestoreObject",
+      "s3:ListBuckets",
+      "s3:ListObjects",
+      "s3:ListObjectsV2",
+      "s3:GetBucketVersioning",
+      "s3:PutObject",
+      "s3:GetObject",
+      "s3:PutLifecycleConfiguration",
+      "s3:GetBucketCORS",
+      "s3:DeleteObject",
+      "s3:GetBucketLocation",
+      "s3:GetObjectVersion",
+    ]
+    resources = [
+      aws_s3_bucket.bucket.arn,
+      "${aws_s3_bucket.bucket.arn}/*",
+    ]
+  }
+
+  statement {
+    sid = "S3ReadOnlyBucketAccess"
+    effect = "Allow"
+    actions = [
+      "s3:ListBucketVersions",
+      "s3:ListBuckets",
+      "s3:GetBucketVersioning",
+      "s3:GetObject",
+      "s3:GetBucketCORS",
+      "s3:GetBucketLocation",
+      "s3:GetObjectVersion",
+    ]
+    resources = [
+      aws_s3_bucket.bucket.arn,
+      "${aws_s3_bucket.bucket.arn}/*",
+    ]
+  }
+
+  statement {
+    sid = "KMSKeyAccess"
+    effect = "Allow"
+    actions = [
+      "kms:Decrypt",
+      "kms:GenerateDataKeyWithoutPlaintext",
+      "kms:Verify",
+      "kms:GenerateDataKeyPairWithoutPlaintext",
+      "kms:GenerateDataKeyPair",
+      "kms:ReEncryptFrom",
+      "kms:Encrypt",
+      "kms:GenerateDataKey",
+      "kms:ReEncryptTo",
+      "kms:Sign",
+    ]
+    resources = [ aws_kms_key.bucketkey.arn ]
+  }  
+}

+ 97 - 0
base/generic_s3_bucket_with_role/kms.tf

@@ -0,0 +1,97 @@
+resource "aws_kms_key" "bucketkey" {
+  description             = "S3 KMS for ${local.fullname}."
+  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" {
+  depends_on = [ aws_iam_role.role ]
+  policy_id = local.fullname
+  statement {
+    sid    = "Enable IAM User Permissions"
+    effect = "Allow"
+    principals {
+      type = "AWS"
+      identifiers = local.principals
+    }
+    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 = local.principals
+    }
+    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"]
+    }
+  }
+}

+ 10 - 0
base/generic_s3_bucket_with_role/locals.tf

@@ -0,0 +1,10 @@
+locals {
+  fullname = "xdr-${var.environment}-${var.name}"
+  principals = concat([
+      "arn:${var.aws_partition}:iam::${var.aws_account_id}:user/MDRAdmin",
+      "arn:${var.aws_partition}:iam::${var.aws_account_id}:role/user/mdr_terraformer",
+      aws_iam_role.role.arn
+    ],
+    var.extra_principals
+  )
+}

+ 8 - 0
base/generic_s3_bucket_with_role/outputs.tf

@@ -0,0 +1,8 @@
+output "role_arn" {
+  # The role to assume into
+  value = aws_iam_role.role.arn
+}
+
+output "BucketName" {
+  value = aws_s3_bucket.bucket.id
+}

+ 88 - 0
base/generic_s3_bucket_with_role/s3.tf

@@ -0,0 +1,88 @@
+resource "aws_s3_bucket" "bucket" {
+  bucket = local.fullname
+  tags   = merge(var.standard_tags, var.tags)
+}
+
+resource "aws_s3_bucket_versioning" "s3_version_bucket" {
+  bucket = aws_s3_bucket.bucket.id
+  versioning_configuration {
+    status = "Enabled"
+  }
+}
+
+resource "aws_s3_bucket_acl" "s3_acl_bucket" {
+  bucket = aws_s3_bucket.bucket.id
+  acl    = "private"
+}
+
+resource "aws_s3_bucket_server_side_encryption_configuration" "s3_sse_bucket" {
+  bucket = aws_s3_bucket.bucket.id
+  rule {
+    apply_server_side_encryption_by_default {
+      kms_master_key_id = aws_kms_key.bucketkey.arn
+      sse_algorithm     = "aws:kms"
+    }
+  }
+}
+
+resource "aws_s3_bucket_lifecycle_configuration" "s3_lifecyle_bucket" {
+  bucket = aws_s3_bucket.bucket.id
+  rule {
+    id     = "INTELLIGENT_TIERING"
+    status = "Enabled"
+    filter {} # Required for noncurrent_version_expiration to work
+    abort_incomplete_multipart_upload {
+      days_after_initiation = 2
+    }
+    transition {
+      days          = 30
+      storage_class = "INTELLIGENT_TIERING"
+    }
+    noncurrent_version_expiration {
+      # We always keep the current version and the previous version, and delete any other versions after 90 days
+      newer_noncurrent_versions = 2
+      noncurrent_days = 90
+    }
+  }
+}
+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
+
+  # Not technically dependent, but prevents a "Conflicting conditional operation" conflict.
+  # See https://github.com/hashicorp/terraform-provider-aws/issues/7628
+  depends_on = [aws_s3_bucket_policy.s3_bucket_policy]
+}
+
+data "aws_iam_policy_document" "s3_bucket_policy" {
+  statement {
+    sid    = "AccountAllow"
+    effect = "Allow"
+
+    resources = [
+      aws_s3_bucket.bucket.arn,
+      "${aws_s3_bucket.bucket.arn}/*",
+    ]
+
+    actions = [
+      "s3:GetObject",
+      "s3:ListBucket",
+    ]
+
+    principals {
+      type        = "AWS"
+      identifiers = local.principals
+    }
+  }
+}
+
+resource "aws_s3_bucket_policy" "s3_bucket_policy" {
+  depends_on = [ aws_iam_role.role ] # 2022-04-22: FTD: Copied this across, but not sure why this dependency.
+
+  bucket     = aws_s3_bucket.bucket.id
+
+  policy     = data.aws_iam_policy_document.s3_bucket_policy.json
+}

+ 41 - 0
base/generic_s3_bucket_with_role/vars.tf

@@ -0,0 +1,41 @@
+variable "splunk_prefix" {
+  type = string
+}
+
+variable "extra_principals" {
+  description = "Additional principals that are granted direct access."
+  type = list(string)
+  default = []
+}
+
+# Appears you can't actually limit this.
+#variable "allow_presigned" {
+#  type = bool
+#  description = "Allows the role to presign URLs to share with external users"
+#}
+
+variable "name" {
+  description = "Suffix to end a lot of things, such as the role bucket name."
+  type = string
+}
+
+variable "role_assumers" {
+  description = "List of principals allowed to assume the role."
+  type = list(string)
+  default = []
+}
+
+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 }
+variable "aws_region" { type = string }
+variable "aws_partition" { type = string }
+variable "environment" { type = string }