Skip to content
Merged
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pipeline repo
main.tf <--module deployed here
```

Segregation enables the pipeline to run commands against the code in "your repo" without affecting the pipeline infrastructure. Typically this could be an infrastructure or bootstrap repo for the AWS account thats used to provision infrastructure and/or multiple pipelines.
Segregation enables the pipeline to run commands against the code in "your repo" without affecting the pipeline infrastructure.

Review the [example code directory](./example-code) and ensure the code in your repo is compatible.

Expand Down Expand Up @@ -91,6 +91,12 @@ module "pipeline" {
checkov_version = "3.2.0"
tflint_version = "0.55.0"

vpc = {
vpc_id = "vpc-011a22334455bb66c",
subnets = ["subnet-011aabbcc2233d4ef"],
security_group_ids = ["sg-001abcd2233ee4455"],
}

tags = join(",", [
"Environment[Dev,Prod]",
"Source"
Expand All @@ -103,6 +109,9 @@ module "pipeline" {
}
```

<<<<<<< HEAD
See [optional inputs](./docs/optional_inputs.md) for descriptions.
=======
`branch` is the branch to source. It defaults to `main`.

`mode` is [pipeline execution mode](https://docs.aws.amazon.com/codepipeline/latest/userguide/concepts-how-it-works.html#concepts-how-it-works-executions). It defaults to `SUPERSEDED`.`detect_changes` is used with third-party services, like GitHub. It enables AWS CodeConnections to invoke the pipeline when there is a commit to the repo. It defaults to `false`.
Expand All @@ -125,6 +134,8 @@ module "pipeline" {

`tflint_version` controls the [tflint](https://github.com/terraform-linters/tflint) version. It defaults to 0.48.0.

`vpc` configures the CodeBuild projects to [run in a VPC](https://docs.aws.amazon.com/codebuild/latest/userguide/vpc-support.html).

`tags` enables tag validation with [tag-nag](https://github.com/jakebark/tag-nag). Input a list of tag keys and/or tag keys and values to enforce. Input must be passed as a string, see [commands](https://github.com/jakebark/tag-nag?tab=readme-ov-file#commands).

`tagnag_version` controls the [tag-nag](https://github.com/jakebark/tag-nag) version. It defaults to 0.5.8.
Expand Down Expand Up @@ -160,6 +171,7 @@ Permissions to your CodeCommit repository, CodeBuild projects, and CodePipeline
- [Limit pushes and merges to branches in AWS CodeCommit](https://docs.aws.amazon.com/codecommit/latest/userguide/how-to-conditional-branch.html)

Checkov skips can be used where Checkov policies conflict with your organization's practices or design decisions. The `checkov_skip` module input allows you to set skips for all resources in your repository. For example, if your organization operates in a single region you may want to add `CKV_AWS_144` (Ensure that S3 bucket has cross-region replication enabled). For individual resource skips, you can still use [inline code comments](https://www.checkov.io/2.Basics/Suppressing%20and%20Skipping%20Policies.html).
>>>>>>> 50d0801 (codebuild vpc (#19))

## Related Resources

Expand Down
59 changes: 52 additions & 7 deletions codebuild.tf
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module "validation" {
build_spec = "${each.key}.yml"
log_group = aws_cloudwatch_log_group.this.name
image = each.value
vpc = var.vpc
}

module "plan" {
Expand All @@ -22,6 +23,7 @@ module "plan" {
build_spec = "plan.yml"
log_group = aws_cloudwatch_log_group.this.name
image = "hashicorp/terraform:${var.terraform_version}"
vpc = var.vpc
}

module "apply" {
Expand All @@ -33,6 +35,7 @@ module "apply" {
build_spec = "apply.yml"
log_group = aws_cloudwatch_log_group.this.name
image = "hashicorp/terraform:${var.terraform_version}"
vpc = var.vpc
}

resource "aws_iam_role" "codebuild" {
Expand Down Expand Up @@ -78,7 +81,6 @@ data "aws_iam_policy_document" "codebuild" {
"logs:CreateLogStream",
"logs:PutLogEvents"
]

resources = [
"arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:*"
]
Expand All @@ -91,7 +93,6 @@ data "aws_iam_policy_document" "codebuild" {
"codebuild:UpdateReport",
"codebuild:BatchPutTestCases"
]

resources = [
aws_codebuild_report_group.sast.arn
]
Expand All @@ -103,7 +104,6 @@ data "aws_iam_policy_document" "codebuild" {
"s3:GetObject",
"s3:PutObject"
]

resources = [
"${aws_s3_bucket.this.arn}/*",
]
Expand All @@ -114,7 +114,6 @@ data "aws_iam_policy_document" "codebuild" {
actions = [
"sts:AssumeRole"
]

resources = [
"*"
]
Expand All @@ -127,7 +126,6 @@ data "aws_iam_policy_document" "codebuild" {
"dynamodb:PutItem",
"dynamodb:DeleteItem"
]

resources = [
"*" // for s3 backend
]
Expand All @@ -141,7 +139,6 @@ data "aws_iam_policy_document" "codebuild" {
"s3:DeleteObject",
"s3:ListBucket"
]

resources = [
"*" // for s3 backend
]
Expand All @@ -153,11 +150,59 @@ data "aws_iam_policy_document" "codebuild" {
"kms:GenerateDataKey*",
"kms:Decrypt"
]

resources = [
"*"
]
}

// https://docs.aws.amazon.com/codebuild/latest/userguide/auth-and-access-control-iam-identity-based-access-control.html#customer-managed-policies-example-create-vpc-network-interface
dynamic "statement" {
for_each = var.vpc == null ? [] : [var.vpc]
content {
effect = "Allow"
actions = [
"ec2:CreateNetworkInterface",
"ec2:DescribeDhcpOptions",
"ec2:DescribeNetworkInterfaces",
"ec2:DeleteNetworkInterface",
"ec2:DescribeSubnets",
"ec2:DescribeSecurityGroups",
"ec2:DescribeVpcs"
]
resources = [
"*"
]
}
}

dynamic "statement" {
for_each = var.vpc == null ? [] : [var.vpc]
content {
effect = "Allow"
actions = [
"ec2:CreateNetworkInterfacePermission"

]
resources = [
"arn:aws:ec2:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:network-interface/*"
]
condition {
test = "StringEquals"
variable = "ec2:AuthorizedService"
values = [
"codebuild.amazonaws.com"
]
}
condition {
test = "ArnEquals"
variable = "ec2:Subnet"
values = [
for id in var.vpc["subnets"] :
"arn:aws:ec2:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:subnet/${id}"
]
}
}
}
}

resource "aws_codebuild_report_group" "sast" {
Expand Down
3 changes: 0 additions & 3 deletions codepipeline.tf
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,6 @@ data "aws_iam_policy_document" "codepipeline" {
"s3:PutObjectAcl",
"s3:PutObject"
]

resources = [
"${aws_s3_bucket.this.arn}",
"${aws_s3_bucket.this.arn}/*"
Expand All @@ -203,7 +202,6 @@ data "aws_iam_policy_document" "codepipeline" {
"codecommit:CancelUploadArchive",
"codestar-connections:UseConnection"
]

resources = [
var.connection == null ? "arn:aws:codecommit:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:${var.repo}" : var.connection
]
Expand All @@ -215,7 +213,6 @@ data "aws_iam_policy_document" "codepipeline" {
"codebuild:BatchGetBuilds",
"codebuild:StartBuild"
]

resources = [
"arn:aws:codebuild:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:project/${var.pipeline_name}-*"
]
Expand Down
18 changes: 18 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Architecture

![image info](./img/architecture.png)

1. **(1a)** User commits to a third-party repository, this invokes the AWS Codepipeline pipeline; *or* **(1b)** User commits to a CodeCommit repository, this invokes an Amazon EventBridge rule, which runs the pipeline.
2. The pipeline validates the code and then runs a terraform plan against all of the target AWS accounts. Manual approval is then required to run the terraform apply.
3. Resources are deployed to the target AWS accounts using [Terraform Workspaces](https://developer.hashicorp.com/terraform/language/state/workspaces). Each AWS account is assigned their own Workspace using their AWS Account ID.
4. Artifacts and logs are exported to Amazon S3 and CloudWatch logs.

## Pipeline Validation

| Check | Description |
|---|---|
| validate | runs `terraform validate` to make sure that the code is syntactically valid. |
| lint | runs [tfLint](https://github.com/terraform-linters/tflint) which will find errors, depreciated syntax, and check naming conventions. |
| fmt | runs `terraform fmt --recursive --check` to ensure code is consistently formatted. |
| sast | runs [checkov](https://www.checkov.io/) for security best practices. |
| tags (optional)| runs [tag-nag](https://github.com/jakebark/tag-nag) to validate tags.|
8 changes: 8 additions & 0 deletions docs/best_practices.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Best Practices

Permissions to your CodeCommit repository, CodeBuild projects, and CodePipeline pipeline should be tightly controlled. Here are some ideas:
- [Specify approval permission for specific pipelines and approval actions](https://docs.aws.amazon.com/codepipeline/latest/userguide/approvals-iam-permissions.html#approvals-iam-permissions-limited)
- [Using identity-based policies for AWS CodeBuild](https://docs.aws.amazon.com/codebuild/latest/userguide/auth-and-access-control-iam-identity-based-access-control.html)
- [Limit pushes and merges to branches in AWS CodeCommit](https://docs.aws.amazon.com/codecommit/latest/userguide/how-to-conditional-branch.html)

Checkov skips can be used where Checkov policies conflict with your organization's practices or design decisions. The `checkov_skip` module input allows you to set skips for all resources in your repository. For example, if your organization operates in a single region you may want to add `CKV_AWS_144` (Ensure that S3 bucket has cross-region replication enabled). For individual resource skips, you can still use [inline code comments](https://www.checkov.io/2.Basics/Suppressing%20and%20Skipping%20Policies.html).
33 changes: 33 additions & 0 deletions docs/optional_inputs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Optional inputs

`branch` is the branch to source. It defaults to `main`.

`mode` is [pipeline execution mode](https://docs.aws.amazon.com/codepipeline/latest/userguide/concepts-how-it-works.html#concepts-how-it-works-executions). It defaults to `SUPERSEDED`.`detect_changes` is used with third-party services, like GitHub. It enables AWS CodeConnections to invoke the pipeline when there is a commit to the repo. It defaults to `false`.

`kms_key` is the arn of an *existing* AWS KMS key. This input will encrypt the Amazon S3 bucket with a AWS KMS key of your choice. Otherwise the bucket will be encrypted using SSE-S3. Your AWS KMS key policy will need to allow codebuild and codepipeline to `kms:GenerateDataKey*` and `kms:Decrypt`.

`access_logging_bucket` S3 server access logs bucket ARN, enables server access logging on the S3 artifact bucket.

`artifact_retention` controls the S3 artifact bucket retention period. It defaults to 90 (days).

`workspace_directory` enables the use of workspace variable files (eg ./workspaces/<workspace>.tfvars. The input is the directory name that you wish to use. This input is recommended for advanced variable management, where complex and/or signficant amounts of different variables are applied to different AWS accounts.

`codebuild_policy` replaces the [AWSAdministratorAccess](https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AdministratorAccess.html) IAM policy. This can be used if you want to scope the permissions of the pipeline.

`build_timeout` is the CodeBuild project build timeout. It defaults to 10 (minutes).

`terraform_version` controls the terraform version. It defaults to 1.5.7.

`checkov_version` controls the [Checkov](https://www.checkov.io/) version. It defaults to latest.

`tflint_version` controls the [tflint](https://github.com/terraform-linters/tflint) version. It defaults to 0.48.0.

`vpc` configures the CodeBuild projects to [run in a VPC](https://docs.aws.amazon.com/codebuild/latest/userguide/vpc-support.html).

`tags` enables tag validation with [tag-nag](https://github.com/jakebark/tag-nag). Input a list of tag keys and/or tag keys and values to enforce. Input must be passed as a string, see [commands](https://github.com/jakebark/tag-nag?tab=readme-ov-file#commands).

`tagnag_version` controls the [tag-nag](https://github.com/jakebark/tag-nag) version. It defaults to 0.5.8.

`checkov_skip` defines [Checkov](https://www.checkov.io/) skips for the pipeline. This is useful for organization-wide policies, removing the need to add individual resource skips.


11 changes: 11 additions & 0 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Troubleshooting

| Issue | Fix |
|---|---|
| Failed lint or validate | Read the report or logs to discover why the code has failed, then make a new commit. |
| Failed fmt | This means your code is not formatted. Run `terraform fmt --recursive` on your code, then make a new commit. |
| Failed SAST | Read the Checkov logs (Details > Reports) and either make the correction in code or add a skip to the module inputs. |
| Failed plan or apply stage | Read the report or logs to discover error in terraform code, then make a new commit. |
| Pipeline fails on apply with `the action failed because no branch named main was found ...` | Either nothing has been committed to the repo or the branch is incorrect (Eg using `Master` not `Main`). Either commit to the Main branch or change the module input to fix this. |
| `Invalid count argument` for `aws_s3_bucket_server_side_encryption_configuration` | The AWS KMS key must exist before the pipeline is created. If you create both at the same time, there is a dependency issue. |
| Unable to find state file | Check state storage :env > AWS Account ID > backend key |
3 changes: 0 additions & 3 deletions eventbridge.tf
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,10 @@ data "aws_iam_policy_document" "eventbridge_assume" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]

principals {
type = "Service"
identifiers = ["events.amazonaws.com"]
}

condition {
test = "StringEquals"
variable = "aws:SourceAccount"
Expand All @@ -79,7 +77,6 @@ data "aws_iam_policy_document" "eventbridge" {
actions = [
"codepipeline:StartPipelineExecution"
]

resources = [
aws_codepipeline.this.arn
]
Expand Down
10 changes: 9 additions & 1 deletion modules/codebuild/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ resource "aws_codebuild_project" "this" {
value = environment_variable.value
}
}

}

logs_config {
Expand All @@ -39,5 +38,14 @@ resource "aws_codebuild_project" "this" {
insecure_ssl = false
report_build_status = false
}

dynamic "vpc_config" {
for_each = var.vpc == null ? [] : [var.vpc]
content {
vpc_id = vpc_config.value.vpc_id
subnets = vpc_config.value.subnets
security_group_ids = vpc_config.value.security_group_ids
}
}
}

9 changes: 9 additions & 0 deletions modules/codebuild/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,12 @@ variable "log_group" {
variable "image" {
type = string
}

variable "vpc" {
type = object({
vpc_id = string
subnets = list(string)
security_group_ids = list(string)
})
default = null
}
9 changes: 9 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,12 @@ variable "workspace_directory" {
type = string
default = ""
}

variable "vpc" {
type = object({
vpc_id = string
subnets = list(string)
security_group_ids = list(string)
})
default = null
}