ソースを参照

Merge pull request #340 from mdr-engineering/feature/cw_NA_GitHubActionsS3

Terraform to update the GHE instance role for S3 access
Colby Williams 3 年 前
コミット
28c75d646b

+ 31 - 31
base/github/github_servers.tf

@@ -11,54 +11,54 @@ data "aws_kms_key" "ebs-key" {
 }
 
 resource "aws_instance" "ghe" {
-  count                       = var.instance_count
+  count = var.instance_count
 
-  ami                         = aws_ami_copy.github.id
-  instance_type               = var.instance_type
-  subnet_id                   = var.private_subnets[count.index]
-  vpc_security_group_ids      = [ data.aws_security_group.typical-host.id, aws_security_group.ghe_server.id ]
-  associate_public_ip_address = false
-  ebs_optimized               = true
-  tenancy = "default"
-  disable_api_termination = var.instance_termination_protection
+  ami                                  = aws_ami_copy.github.id
+  instance_type                        = var.instance_type
+  subnet_id                            = var.private_subnets[count.index]
+  vpc_security_group_ids               = [data.aws_security_group.typical-host.id, aws_security_group.ghe_server.id]
+  associate_public_ip_address          = false
+  ebs_optimized                        = true
+  tenancy                              = "default"
+  disable_api_termination              = var.instance_termination_protection
   instance_initiated_shutdown_behavior = "stop"
-  key_name = "msoc-build"
-  monitoring = false
-  iam_instance_profile = "msoc-default-instance-profile"
-  
+  key_name                             = "msoc-build"
+  monitoring                           = false
+  iam_instance_profile                 = "github_instance_profile"
+
   # single space to disable default module behavior
   root_block_device {
-      volume_size           = 200
-      volume_type           = "gp3"
-      iops                  = 3000
-      delete_on_termination = true
-      encrypted             = true
-      kms_key_id            = data.aws_kms_key.ebs-key.arn
-    } 
+    volume_size           = 200
+    volume_type           = "gp3"
+    iops                  = 3000
+    delete_on_termination = true
+    encrypted             = true
+    kms_key_id            = data.aws_kms_key.ebs-key.arn
+  }
 
   ebs_block_device {
     # github data
     # Note: Not in AMI
-    device_name = "/dev/xvdf"
-    volume_size = var.github_data_volume_size
+    device_name           = "/dev/xvdf"
+    volume_size           = var.github_data_volume_size
     delete_on_termination = true
-    encrypted = true
-    kms_key_id = data.aws_kms_key.ebs-key.arn
-    volume_type = "gp3"
-    iops = 3000
+    encrypted             = true
+    kms_key_id            = data.aws_kms_key.ebs-key.arn
+    volume_type           = "gp3"
+    iops                  = 3000
   }
 
-  tags = merge( var.standard_tags, var.tags, var.instance_tags, { Name = format("%s-%s", "github-enterprise", count.index) })
-  volume_tags = merge( var.standard_tags, var.tags, { Name = format("%s-%s", "github-enterprise", count.index) })
+  tags        = merge(var.standard_tags, var.tags, var.instance_tags, { Name = format("%s-%s", "github-enterprise", count.index) })
+  volume_tags = merge(var.standard_tags, var.tags, { Name = format("%s-%s", "github-enterprise", count.index) })
 }
 
 # Would need this a second time if count > 0
 module "private_dns_record_ghe_backup_0" {
   source = "../../submodules/dns/private_A_record"
 
-  name = format("%s-%s", "github-enterprise", 0)
-  ip_addresses = [ aws_instance.ghe[0].private_ip ]
-  dns_info = var.dns_info
+  name            = format("%s-%s", "github-enterprise", 0)
+  ip_addresses    = [aws_instance.ghe[0].private_ip]
+  dns_info        = var.dns_info
   reverse_enabled = var.reverse_enabled
 
   providers = {

+ 121 - 0
base/github/instance_profile.tf

@@ -0,0 +1,121 @@
+#############################
+# GitHub Enterprise instance profile
+#
+# Includes policies for GitHub Enterprise:
+#  * Same policies as the default instance profile
+resource "aws_iam_instance_profile" "github_instance_profile" {
+  name = "xdr-github-instance-profile"
+  path = "/instance/"
+  role = aws_iam_role.github_instance_role.name
+}
+
+resource "aws_iam_role" "github_instance_role" {
+  name               = "xdr-github-instance-role"
+  path               = "/instance/"
+  assume_role_policy = <<EOF
+{
+    "Version": "2012-10-17",
+    "Statement": [
+      {
+        "Sid": "",
+        "Effect": "Allow",
+        "Principal": {
+          "Service": [
+            "ec2.amazonaws.com",
+            "ssm.amazonaws.com"
+            ]
+        },
+        "Action": "sts:AssumeRole"
+      }
+    ]
+  }
+EOF
+}
+
+# These 3 are the default profile attachments:
+resource "aws_iam_role_policy_attachment" "github_instance_AmazonEC2RoleforSSM" {
+  role       = aws_iam_role.github_instance_role.name
+  policy_arn = "arn:${var.aws_partition}:iam::aws:policy/service-role/AmazonEC2RoleforSSM"
+}
+
+resource "aws_iam_role_policy_attachment" "github_instance_default_policy_attach" {
+  role       = aws_iam_role.github_instance_role.name
+  policy_arn = "arn:${var.aws_partition}:iam::${var.aws_account_id}:policy/launchroles/default_instance_tag_read"
+}
+
+resource "aws_iam_role_policy_attachment" "github_instance_cloudwatch_policy_attach" {
+  role       = aws_iam_role.github_instance_role.name
+  policy_arn = "arn:${var.aws_partition}:iam::${var.aws_account_id}:policy/cloudwatch_events"
+}
+
+# GitHub Enterprise Specific Policy
+resource "aws_iam_policy" "github_instance_policy" {
+  name        = "github_instance_policy"
+  path        = "/launchroles/"
+  description = "This policy allows github-specific functions"
+  policy      = data.aws_iam_policy_document.github_instance_policy_doc.json
+}
+
+data "aws_iam_policy_document" "github_instance_policy_doc" {
+  # Allow using S3 for GH Actions
+  statement {
+    sid    = "GeneralBucketAccess"
+    effect = "Allow"
+    actions = [
+      "s3:ListAllMyBuckets",
+      "s3:HeadBucket",
+    ]
+    resources = ["*"]
+  }
+
+  statement {
+    sid    = "S3BucketAccess"
+    effect = "Allow"
+    actions = [
+      "s3:PutObject",
+      "s3:GetObject",
+      "s3:ListBucketMultipartUploads",
+      "s3:ListMultipartUploadParts",
+      "s3:AbortMultipartUpload",
+      "s3:DeleteObject",
+      "s3:ListBuckets",
+      #      "s3:GetLifecycleConfiguration",
+      #      "s3:DeleteObjectVersion",
+      #      "s3:ListBucketVersions",
+      #      "s3:GetBucketLogging",
+      #      "s3:RestoreObject",
+      #      "s3:GetBucketVersioning",
+      #      "s3:PutLifecycleConfiguration",
+      #      "s3:GetBucketCORS",
+      #      "s3:GetBucketLocation",
+      #      "s3:GetObjectVersion",
+    ]
+    resources = [
+      "arn:${var.aws_partition}:s3:::xdr-github-enterprise-${var.environment}-github-actions",
+      "arn:${var.aws_partition}:s3:::xdr-github-enterprise-${var.environment}-github-actions/*",
+    ]
+  }
+
+  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 = ["*"]
+  }
+}
+
+resource "aws_iam_role_policy_attachment" "github_instance_policy_attach" {
+  role       = aws_iam_role.github_instance_role.name
+  policy_arn = aws_iam_policy.github_instance_policy.arn
+}

+ 108 - 0
base/github_actions_s3_bucket/kms.tf

@@ -0,0 +1,108 @@
+locals {
+  kms_users = concat(
+    [
+      # Anybody else need access?
+    ],
+    local.account_arns
+  )
+}
+
+resource "aws_kms_key" "bucketkey" {
+  description             = "S3 KMS for ${local.bucket_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/GHEActions"
+  target_key_id = aws_kms_key.bucketkey.key_id
+}
+
+data "aws_iam_policy_document" "kms_key_policy" {
+  policy_id = local.bucket_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 = local.kms_users
+    }
+    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"]
+    }
+  }
+}

+ 78 - 0
base/github_actions_s3_bucket/main.tf

@@ -0,0 +1,78 @@
+locals {
+  bucket_name  = "xdr-github-enterprise-${var.environment}-github-actions"
+  accounts     = [var.aws_account_id]
+  account_arns = [for a in local.accounts : "arn:${var.aws_partition}:iam::${a}:root"]
+}
+
+resource "aws_s3_bucket" "bucket" {
+  bucket = local.bucket_name
+  acl    = "private"
+
+  versioning {
+    enabled = true
+  }
+
+  tags = merge(var.standard_tags, var.tags)
+
+  lifecycle_rule {
+    id      = "STANDARD_IA"
+    enabled = true
+
+    abort_incomplete_multipart_upload_days = 2
+
+    transition {
+      days          = 30
+      storage_class = "STANDARD_IA"
+    }
+
+  }
+
+  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
+
+  # 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.policy]
+}
+
+resource "aws_s3_bucket_policy" "policy" {
+  bucket = aws_s3_bucket.bucket.id
+
+  policy = <<POLICY
+{
+  "Version": "2012-10-17",
+  "Id": "AllowThisAccount",
+  "Statement": [
+    {
+      "Sid": "AccountAllow",
+      "Effect": "Allow",
+      "Principal": {
+        "AWS": ${jsonencode(local.account_arns)}
+      },
+      "Action": [
+        "s3:GetObject",
+        "s3:ListBucket"
+      ],
+      "Resource": [
+        "${aws_s3_bucket.bucket.arn}",
+        "${aws_s3_bucket.bucket.arn}/*"
+      ]
+    }
+  ]
+}
+POLICY
+}

+ 3 - 0
base/github_actions_s3_bucket/outputs.tf

@@ -0,0 +1,3 @@
+output "BucketName" {
+  value = aws_s3_bucket.bucket.id
+}

+ 14 - 0
base/github_actions_s3_bucket/vars.tf

@@ -0,0 +1,14 @@
+variable "tags" {
+  description = "Tags for the bucket and kms key."
+  type        = map(any)
+}
+
+# ----------------------------------
+# 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(any) }
+variable "aws_account_id" { type = string }
+variable "account_list" { type = list(any) }
+variable "aws_region" { type = string }
+variable "aws_partition" { type = string }
+variable "environment" { type = string }