Browse Source

Adds initial codebuild_portal_lambda code

Brad Poulton 3 years ago
parent
commit
88fea6eab3

+ 30 - 0
base/codebuild_portal_lambda/README.md

@@ -0,0 +1,30 @@
+ How to Use this Module
+
+This module is where the CodeBuild artifact for the portal lambda is created. It uses the Terraform GitHub Provider and requires a Personal Access Token. This should be your Personal Access Token not mdr-aws-codebuild's token (see how-to below). The provider will look in the environmental variables for the token. 
+
+```
+export GITHUB_TOKEN=<gihub_token>
+```
+
+This module should NOT create the github repo. That is a manual process. I am not comfortable with terraform adding/removing github repos. The github repo should have the same name as the name variable in the terrafgrunt.hcl file. The user associated with the Personal Access Token needs to have admin permissions on the github repo. 
+
+## Github Service Account ( mdr-aws-codebuild )
+
+AWS CodeBuild needs a Github Personal Access Token to pull code after the code in a repository has been updated. This is the `mdr-aws-codebuild` account. The secret github token is stored in the C2 account and automatically added.
+
+Despite the Web Interface, there can be only one GHE token per account and region.
+
+The `mdr-aws-codebuild` user must have read access to the repositories you wish to build from.
+
+## Creating a Personal Access Token
+
+1) In github, go to `settings->Developer Settings->Personal Access Tokens`
+2) Click 'Generate Token'
+3) Give it a name (e.g. `terragrunt`)
+4) Give it the following permission groups:
+  * repo
+  * admin:repo_hook
+5) Create the token.
+
+Record it someplace safe.
+

+ 27 - 0
base/codebuild_portal_lambda/ghe-key.tf

@@ -0,0 +1,27 @@
+data "aws_secretsmanager_secret" "ghe-key" {
+  name = "GHE/mdr-aws-codebuild/key"
+  provider = aws.c2
+}
+
+data "aws_secretsmanager_secret_version" "ghe-key" {
+  secret_id = data.aws_secretsmanager_secret.ghe-key.id
+  provider = aws.c2
+}
+
+#locals {
+#  If key was in json format, we would need to decode it.
+#  secret_ghe_key = jsondecode(data.aws_secretsmanager_secret_version.ghe-key.secret_string)
+#}
+
+
+# Note some AWS craziness here. The GitHub credential is not tied to a build, even though it _looks_
+# like it is in the Web UI. There can only be one GitHub credential per account+region::
+# https://docs.aws.amazon.com/cdk/api/v1/docs/@aws-cdk_aws-codebuild.GitHubSourceCredentials.html
+#
+# "Note: CodeBuild only allows a single credential for GitHub to be saved in a given AWS account 
+#        in a given region - any attempt to add more than one will result in an error."
+resource "aws_codebuild_source_credential" "github_token" {
+  auth_type   = "PERSONAL_ACCESS_TOKEN"
+  server_type = "GITHUB_ENTERPRISE"
+  token       = data.aws_secretsmanager_secret_version.ghe-key.secret_string
+}

+ 126 - 0
base/codebuild_portal_lambda/iam.tf

@@ -0,0 +1,126 @@
+resource "aws_iam_role" "codebuild_service_role" {
+  name     = "codebuild_${var.name}_role"
+  path     = "/aws_services/"
+
+  assume_role_policy = <<EOF
+{
+    "Version": "2012-10-17",
+    "Statement": [
+      {
+        "Effect": "Allow",
+        "Principal": {
+          "Service": [
+            "codebuild.amazonaws.com"
+            ]
+        },
+        "Action": "sts:AssumeRole"
+      }
+    ]
+  }
+EOF
+}
+
+resource "aws_iam_role_policy_attachment" "codebuild_service_policy_attach" {
+  role       = aws_iam_role.codebuild_service_role.name
+  policy_arn = aws_iam_policy.codebuild_service_policy.arn
+}
+
+# Some things about this policy I'm not perfectly sure about, like
+# should the account number be hardcoded?  Also, it reads like we'll have to
+# update it each time we have a new repository added to codecommit - that
+# or we'll need to authorize the codebuild role to be able to pull from any 
+# codecommit repo.  Which may be fine?
+resource "aws_iam_policy" "codebuild_service_policy" {
+  name        = "codebuild_${var.name}_policy"
+  description = "Policy for AWS codebuild for ${var.name}"
+  path     = "/aws_services/"
+
+  policy = <<EOF
+{
+    "Version": "2012-10-17",
+    "Statement": [
+        {
+            "Effect": "Allow",
+            "Resource": [
+                "arn:${var.aws_partition}:logs:${var.aws_region}:${var.aws_account_id}:log-group:/aws/codebuild/*"
+            ],
+            "Action": [
+                "logs:CreateLogGroup",
+                "logs:CreateLogStream",
+                "logs:PutLogEvents"
+            ]
+        },
+        {
+            "Effect": "Allow",
+            "Resource": [
+                "arn:${var.aws_partition}:s3:::codepipeline-${var.aws_region}-*"
+            ],
+            "Action": [
+                "s3:PutObject",
+                "s3:GetObject",
+                "s3:GetObjectVersion"
+            ]
+        },
+        {
+            "Effect": "Allow",
+            "Resource": [
+                "arn:${var.aws_partition}:lambda:${var.aws_region}:${var.aws_account_id}:function:portal_*"
+            ],
+            "Action": [
+                "lambda:UpdateFunctionCode"
+            ]
+        },
+        {
+            "Effect": "Allow",
+            "Resource": [
+                "arn:${var.aws_partition}:codecommit:${var.aws_region}:${var.aws_account_id}:*"
+            ],
+            "Action": [
+                "codecommit:GitPull"
+            ]
+        },
+        {
+            "Effect": "Allow",
+            "Resource": [
+                "arn:${var.aws_partition}:s3:::xdr-${var.environment}-codebuild-${var.name}/*",
+                "arn:${var.aws_partition}:s3:::*"
+            ],
+            "Action": [
+                "s3:PutObject",
+                "s3:GetObject*",
+                "s3:ListBucket",
+                "s3:DeleteObject"
+            ]
+        },
+        {
+            "Sid": "WriteToECR",
+            "Effect": "Allow",
+            "Resource": [
+                "*"
+            ],
+            "Action": [
+              "ecr:GetAuthorizationToken",
+              "ecr:BatchCheckLayerAvailability",
+              "ecr:CompleteLayerUpload",
+              "ecr:GetAuthorizationToken",
+              "ecr:InitiateLayerUpload",
+              "ecr:PutImage",
+              "ecr:UploadLayerPart"
+            ]
+        },
+        {
+            "Sid": "PullFromECR",
+            "Effect": "Allow",
+            "Resource": [
+                "*"
+            ],
+            "Action": [
+              "ecr:GetDownloadUrlForLayer",
+              "ecr:BatchGetImage",
+              "ecr:BatchCheckLayerAvailability"
+            ]
+        }
+    ]
+}
+EOF
+}

+ 149 - 0
base/codebuild_portal_lambda/kms.tf

@@ -0,0 +1,149 @@
+#Codebuild artifacts by rule must be encrypted by a KMS key
+# using the default aws/s3 key doesn't work with cross-account access
+resource "aws_kms_key" "s3_codebuild" {
+  description             = "Codebuild ${var.name}"
+  enable_key_rotation     = true
+  policy                  = data.aws_iam_policy_document.codebuild_kms_key_encryption_policy.json
+}
+
+resource "aws_kms_alias" "codebuilt-artifacts" {
+  name          = "alias/codebuild-${var.name}"
+  target_key_id = aws_kms_key.s3_codebuild.key_id
+}
+
+
+data "aws_iam_policy_document" "codebuild_kms_key_encryption_policy" {
+  #policy_id = "key-consolepolicy-3"
+  statement {
+    sid = "Enable IAM User Permissions"
+    effect = "Allow"
+    principals {
+      type = "AWS"
+      identifiers = [ 
+        "arn:${var.aws_partition}:iam::${var.aws_account_id}:role/user/mdr_terraformer",
+        "arn:${var.aws_partition}:iam::${var.aws_account_id}:user/MDRAdmin"
+        ]
+    }
+    actions   = [ "kms:*" ]
+    resources = [ "*" ]
+  }
+
+  statement {
+    sid = "Allow access for Key Administrators"
+    effect = "Allow"
+    principals {
+      type = "AWS"
+      identifiers = [
+        "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"
+    effect = "Allow"
+    principals {
+      type = "AWS"
+      identifiers = [
+        "arn:${var.aws_partition}:iam::${var.aws_account_id}:role/msoc-default-instance-role"
+      ]
+    }
+    actions = [
+      "kms:Encrypt",
+      "kms:Decrypt",
+      "kms:ReEncrypt*",
+      "kms:GenerateDataKey*",
+      "kms:DescribeKey"
+    ]
+    resources = [ "*" ]
+  }
+
+  statement  {
+    sid = "Allow access through Amazon S3 for all principals in the account that are authorized to use Amazon S3"
+    effect = "Allow"
+    principals {
+      type = "AWS"
+      identifiers = [ "*" ]
+    }
+    actions = [
+        "kms:Encrypt",
+        "kms:Decrypt",
+        "kms:ReEncrypt*",
+        "kms:GenerateDataKey*",
+        "kms:DescribeKey"
+    ]
+    resources = [ "*" ]
+
+    condition {
+      test = "StringEquals"
+      variable = "kms.ViaService"
+      values = [ "s3.${var.aws_region}.amazonaws.com" ]
+    }
+
+    condition {
+      test = "StringEquals"
+      variable = "kms.CallerAccount"
+      values = [ var.aws_account_id ]
+    }
+  }
+
+  statement  {
+    sid = "Allow access from the codebuild role"
+    effect = "Allow"
+    principals {
+      type = "AWS"
+    
+      identifiers = [ 
+        aws_iam_role.codebuild_service_role.arn,
+      ]
+    }
+    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}:role/msoc-default-instance-role"
+      ]
+    }
+    actions = [
+      "kms:CreateGrant",
+      "kms:ListGrants",
+      "kms:RevokeGrant"
+    ]
+    resources = [ "*" ]
+    condition {
+      test = "Bool"
+      variable =  "kms:GrantIsForAWSResource"
+      values = [ "true" ]
+      }
+    }
+}

+ 70 - 0
base/codebuild_portal_lambda/main.tf

@@ -0,0 +1,70 @@
+data "github_repository" "this" {
+    name    = var.name
+}
+
+resource "aws_codebuild_project" "this" {
+  name                  = var.name
+  description           = "Project for ${var.name}"
+  service_role          = aws_iam_role.codebuild_service_role.arn
+  encryption_key        = aws_kms_key.s3_codebuild.arn
+  badge_enabled         = var.badge_enabled
+  concurrent_build_limit = 1
+  build_timeout          = 60
+
+  source {
+    type                = "GITHUB_ENTERPRISE"
+    location            = data.github_repository.this.http_clone_url
+    report_build_status = true
+    git_submodules_config {
+      fetch_submodules = true
+    }
+  }
+
+  source_version = var.source_version
+
+
+
+  environment {
+    compute_type        = "BUILD_GENERAL1_SMALL"
+    image               = "aws/codebuild/standard:5.0"
+    type                = "LINUX_CONTAINER"
+
+    environment_variable {
+      name = "ARTIFACTS_PATH"
+      type = "PLAINTEXT"
+      value = "s3://${aws_s3_bucket.bucket.id}/"
+    }
+
+  }
+
+  artifacts {
+    type                = "S3"
+    location            = aws_s3_bucket.bucket.id
+    name                = "/"
+    path                = var.name
+    namespace_type      = "NONE"
+    packaging           = "NONE"
+  }
+  
+  tags = merge(var.standard_tags, var.tags)
+}
+
+resource "aws_codebuild_webhook" "this" {
+  project_name  = var.name
+  branch_filter = var.webhook_branch_filter
+
+  depends_on = [ aws_codebuild_project.this  ]
+}
+
+resource "github_repository_webhook" "this" {
+  active     = true
+  events     = ["push"]
+  repository = data.github_repository.this.name
+
+  configuration {
+    url          = aws_codebuild_webhook.this.payload_url
+    secret       = aws_codebuild_webhook.this.secret
+    content_type = "json"
+    insecure_ssl = false
+  }
+}

+ 57 - 0
base/codebuild_portal_lambda/s3.tf

@@ -0,0 +1,57 @@
+locals {
+  bucket_name  = "xdr-${var.environment}-codebuild-portal-data-sync"
+  accounts     = [var.aws_account_id]
+  account_arns = [for a in local.accounts : "arn:${var.aws_partition}:iam::${a}:root"]
+}
+
+#S3 bucket for codebuild output
+resource "aws_s3_bucket" "bucket" {
+  bucket        = local.bucket_name
+  force_destroy = true
+  acl           = "private"
+  tags = merge(var.standard_tags, var.tags)
+
+  versioning {
+    enabled = false
+  }
+
+  server_side_encryption_configuration {
+    rule {
+      apply_server_side_encryption_by_default {
+        kms_master_key_id = aws_kms_key.s3_codebuild.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.artifacts]
+}
+
+resource "aws_s3_bucket_policy" "artifacts" {
+  bucket = aws_s3_bucket.bucket.id
+  policy = data.aws_iam_policy_document.artifacts.json
+}
+
+data "aws_iam_policy_document" "artifacts" {
+  statement {
+    sid = "AllowS3Access"
+    actions = [ "s3:GetObject", "s3:GetObjectVersion" ]
+    effect = "Allow"
+    resources = [ "${aws_s3_bucket.bucket.arn}/*" ]
+    principals {
+      type = "AWS"
+      identifiers = local.account_arns
+    }
+  }
+}
+

+ 33 - 0
base/codebuild_portal_lambda/vars.tf

@@ -0,0 +1,33 @@
+variable "tags" {
+  description = "Tags to add to the resource (in addition to global standard tags)"
+  type        = map
+  default     = { }
+}
+variable "standard_tags" { type = map }
+variable "environment" { type = string }
+variable "aws_region" { type = string }
+variable "aws_partition" { type = string }
+variable "aws_partition_alias" { type = string }
+variable "aws_account_id" { type = string }
+variable "name" { type = string }
+
+variable "kms_key" {
+    type = string
+    default = ""
+}
+
+variable "source_version" {
+  description = "Tag or branch for the git repository."
+  type = string
+  default = "master"
+}
+
+variable "badge_enabled" {
+    type = string
+    default = "false"
+}
+
+variable "webhook_branch_filter" {
+    type = string
+    default = "^(master|develop)$"
+}