Skip to content

Commit 7c4ad50

Browse files
committed
Refactor OIDC review apps deployment w/ CodeBuild
Instead of giving GitHub Actions direct access to deploy review apps to ECS, we set up CodeBuild projects that GitHub Actions can trigger via OIDC. This reduces the permissions granted to GitHub Actions, as they no longer need direct access to ECS, ECR, and other resources.
1 parent 6cd1716 commit 7c4ad50

File tree

7 files changed

+407
-143
lines changed

7 files changed

+407
-143
lines changed

infra/deployments/integration/review/github_actions_oidc.tf

Lines changed: 97 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,66 @@ locals {
1212
}
1313
}
1414

15+
# S3 bucket for CodeBuild artifacts (terraform outputs)
16+
module "codebuild_artifacts" {
17+
source = "../../../modules/secure-bucket"
18+
19+
name = "forms-review-codebuild-artifacts"
20+
versioning_enabled = false
21+
access_logging_enabled = false
22+
}
23+
24+
resource "aws_s3_bucket_lifecycle_configuration" "codebuild_artifacts" {
25+
bucket = module.codebuild_artifacts.name
26+
27+
rule {
28+
id = "expire-old-artifacts"
29+
status = "Enabled"
30+
31+
filter {}
32+
33+
expiration {
34+
days = 1
35+
}
36+
}
37+
}
38+
39+
# CodeBuild projects for each app
40+
module "codebuild_deploy" {
41+
for_each = local.github_actions_apps
42+
source = "./review-app-codebuild"
43+
44+
application_name = each.key
45+
action = "deploy"
46+
github_repository = "https://github.com/alphagov/${each.key}"
47+
codeconnection_arn = data.terraform_remote_state.account.outputs.codeconnection_arn
48+
artifacts_bucket_name = module.codebuild_artifacts.name
49+
ecs_cluster_arn = aws_ecs_cluster.review.arn
50+
ecs_cluster_name = aws_ecs_cluster.review.name
51+
ecr_repository_arn = each.value.ecr_repository_arn
52+
task_execution_role_arn = aws_iam_role.ecs_execution.arn
53+
autoscaling_role_arn = aws_iam_service_linked_role.app_autoscaling.arn
54+
deploy_account_id = var.deploy_account_id
55+
}
56+
57+
module "codebuild_destroy" {
58+
for_each = local.github_actions_apps
59+
source = "./review-app-codebuild"
60+
61+
application_name = each.key
62+
action = "destroy"
63+
github_repository = "https://github.com/alphagov/${each.key}"
64+
codeconnection_arn = data.terraform_remote_state.account.outputs.codeconnection_arn
65+
artifacts_bucket_name = module.codebuild_artifacts.name
66+
ecs_cluster_arn = aws_ecs_cluster.review.arn
67+
ecs_cluster_name = aws_ecs_cluster.review.name
68+
ecr_repository_arn = each.value.ecr_repository_arn
69+
task_execution_role_arn = aws_iam_role.ecs_execution.arn
70+
autoscaling_role_arn = aws_iam_service_linked_role.app_autoscaling.arn
71+
deploy_account_id = var.deploy_account_id
72+
}
73+
74+
# OIDC roles with minimal permissions (trigger CodeBuild + push to ECR)
1575
resource "aws_iam_role" "github_actions" {
1676
for_each = local.github_actions_apps
1777

@@ -20,23 +80,21 @@ resource "aws_iam_role" "github_actions" {
2080

2181
assume_role_policy = jsonencode({
2282
Version = "2012-10-17"
23-
Statement = [
24-
{
25-
Effect = "Allow"
26-
Action = "sts:AssumeRoleWithWebIdentity"
27-
Principal = {
28-
Federated = data.terraform_remote_state.account.outputs.github_oidc_provider_arn
83+
Statement = [{
84+
Effect = "Allow"
85+
Action = "sts:AssumeRoleWithWebIdentity"
86+
Principal = {
87+
Federated = data.terraform_remote_state.account.outputs.github_oidc_provider_arn
88+
}
89+
Condition = {
90+
StringEquals = {
91+
"token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
2992
}
30-
Condition = {
31-
StringEquals = {
32-
"token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
33-
}
34-
StringLike = {
35-
"token.actions.githubusercontent.com:sub" = "repo:alphagov/${each.key}:pull_request"
36-
}
93+
StringLike = {
94+
"token.actions.githubusercontent.com:sub" = "repo:alphagov/${each.key}:pull_request"
3795
}
3896
}
39-
]
97+
}]
4098
})
4199
}
42100

@@ -50,164 +108,60 @@ resource "aws_iam_role_policy" "github_actions" {
50108
data "aws_iam_policy_document" "github_actions" {
51109
for_each = local.github_actions_apps
52110

111+
# Trigger CodeBuild projects
53112
statement {
54-
sid = "UseECSServices"
55-
effect = "Allow"
56-
actions = [
57-
"ecs:*Service",
58-
"ecs:*Services",
59-
"ecs:TagResource"
60-
]
113+
sid = "TriggerCodeBuild"
114+
actions = ["codebuild:StartBuild", "codebuild:BatchGetBuilds"]
61115
resources = [
62-
aws_ecs_cluster.review.arn,
63-
"arn:aws:ecs:eu-west-2:${data.aws_caller_identity.current.account_id}:service/${aws_ecs_cluster.review.name}/${each.key}-pr-*"
116+
module.codebuild_deploy[each.key].project_arn,
117+
module.codebuild_destroy[each.key].project_arn
64118
]
65119
}
66120

121+
# Read CodeBuild logs
67122
statement {
68-
sid = "UseECSTaskDefinitions"
69-
effect = "Allow"
70-
actions = [
71-
"ecs:*TaskDefinition",
72-
"ecs:*TaskDefinitions",
73-
"ecs:TagResource"
74-
]
123+
sid = "ReadCodeBuildLogs"
124+
actions = ["logs:GetLogEvents"]
75125
resources = [
76-
"arn:aws:ecs:eu-west-2:${data.aws_caller_identity.current.account_id}:task-definition/${each.key}-pr-*"
77-
]
78-
}
79-
80-
statement {
81-
sid = "AllowTaskDefinitionsNeedingStar"
82-
effect = "Allow"
83-
actions = [
84-
"ecs:DeregisterTaskDefinition",
85-
"ecs:DescribeTaskDefinition"
126+
module.codebuild_deploy[each.key].log_group_arn,
127+
module.codebuild_destroy[each.key].log_group_arn
86128
]
87-
resources = ["*"]
88129
}
89130

131+
# Push container images to ECR
90132
statement {
91-
sid = "ReadTerraformStateFiles"
92-
effect = "Allow"
93-
actions = [
94-
"s3:GetObject",
95-
"s3:GetObjectVersion",
96-
"s3:HeadObject",
97-
"s3:ListBucket"
98-
]
99-
resources = [
100-
"arn:aws:s3:::gds-forms-integration-tfstate",
101-
"arn:aws:s3:::gds-forms-integration-tfstate/review.tfstate",
102-
"arn:aws:s3:::gds-forms-integration-tfstate/review-apps/${each.key}/pr-*.tfstate"
103-
]
104-
}
105-
106-
statement {
107-
sid = "WriteTerraformStateFiles"
108-
effect = "Allow"
109-
actions = [
110-
"s3:PutObject",
111-
"s3:PutObjectVersion"
112-
]
113-
resources = [
114-
"arn:aws:s3:::gds-forms-integration-tfstate/review-apps/${each.key}/pr-*.tfstate"
115-
]
116-
}
117-
118-
statement {
119-
sid = "ReleaseTerraformStateLock"
120-
actions = [
121-
"s3:DeleteObject",
122-
]
123-
resources = [
124-
"arn:aws:s3:::gds-forms-integration-tfstate/review-apps/${each.key}/pr-*.tflock"
125-
]
126-
effect = "Allow"
127-
}
128-
129-
statement {
130-
sid = "UseLocalECR"
131-
effect = "Allow"
133+
sid = "PushToECR"
132134
actions = [
133135
"ecr:BatchCheckLayerAvailability",
134-
"ecr:BatchGetImage",
135136
"ecr:CompleteLayerUpload",
136-
"ecr:GetDownloadUrlForLayer",
137137
"ecr:InitiateLayerUpload",
138138
"ecr:PutImage",
139-
"ecr:UploadLayerPart",
140-
]
141-
resources = [
142-
each.value.ecr_repository_arn
143-
]
144-
}
145-
146-
statement {
147-
sid = "LogIntoLocalECR"
148-
effect = "Allow"
149-
actions = [
150-
"ecr:GetAuthorizationToken",
151-
]
152-
resources = [
153-
"*"
154-
]
155-
}
156-
157-
statement {
158-
sid = "UseDeployECR"
159-
effect = "Allow"
160-
actions = [
161-
"ecr:BatchCheckLayerAvailability",
162-
"ecr:BatchGetImage",
163-
"ecr:GetDownloadUrlForLayer"
164-
]
165-
resources = [
166-
"arn:aws:ecr:eu-west-2:${var.deploy_account_id}:repository/*"
139+
"ecr:UploadLayerPart"
167140
]
141+
resources = [each.value.ecr_repository_arn]
168142
}
169143

170144
statement {
171-
sid = "AllowIAMPassRole"
172-
effect = "Allow"
173-
actions = [
174-
"iam:PassRole"
175-
]
176-
resources = [
177-
aws_iam_role.ecs_execution.arn,
178-
aws_iam_service_linked_role.app_autoscaling.arn,
179-
]
145+
sid = "ECRLogin"
146+
actions = ["ecr:GetAuthorizationToken"]
147+
resources = ["*"]
180148
}
181149

150+
# Wait for ECS service stability (read-only)
182151
statement {
183-
sid = "UseAppAutoscaling"
184-
effect = "Allow"
185-
actions = [
186-
"application-autoscaling:*ScalableTarget",
187-
"application-autoscaling:*ScalableTargets",
188-
"application-autoscaling:*ScheduledAction",
189-
"application-autoscaling:*ScheduledActions",
190-
"application-autoscaling:*ScalingPolicy",
191-
"application-autoscaling:*ScalingPolicies",
192-
"application-autoscaling:ListTagsForResource",
193-
"application-autoscaling:TagResource",
194-
"application-autoscaling:UntagResource"
195-
]
152+
sid = "WaitForECSStability"
153+
actions = ["ecs:DescribeServices"]
196154
resources = [
197-
"arn:aws:application-autoscaling:eu-west-2:${data.aws_caller_identity.current.account_id}:scalable-target/*"
155+
"arn:aws:ecs:eu-west-2:${data.aws_caller_identity.current.account_id}:service/${aws_ecs_cluster.review.name}/${each.key}-pr-*"
198156
]
199157
}
200158

159+
# Read and delete CodeBuild artifacts (terraform outputs)
201160
statement {
202-
sid = "UseAppAutoscalingNeedingStar"
203-
effect = "Allow"
204-
actions = [
205-
"application-autoscaling:DescribeScalableTargets",
206-
"application-autoscaling:DescribeScalingPolicies",
207-
"application-autoscaling:DescribeScheduledActions",
208-
]
161+
sid = "ReadArtifacts"
162+
actions = ["s3:GetObject", "s3:DeleteObject"]
209163
resources = [
210-
"*"
164+
"${module.codebuild_artifacts.arn}/*/review-${each.key}-deploy/outputs.json"
211165
]
212166
}
213167
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
version: 0.2
2+
3+
phases:
4+
install:
5+
commands:
6+
- set -e
7+
- export TERRAFORM_VERSION=$(< .review_apps/.terraform-version)
8+
- curl "https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_arm64.zip" -o "terraform-${TERRAFORM_VERSION}.zip"
9+
- unzip "terraform-${TERRAFORM_VERSION}.zip"
10+
- mv terraform /usr/local/bin/terraform
11+
finally:
12+
- terraform --version
13+
14+
pre_build:
15+
commands:
16+
- cd .review_apps
17+
- terraform init -backend-config="key=review-apps/${APPLICATION_NAME}/pr-${PR_NUMBER}.tfstate"
18+
19+
build:
20+
commands:
21+
- |
22+
terraform apply \
23+
-var "pull_request_number=${PR_NUMBER}" \
24+
-var "${APPLICATION_NAME//-/_}_container_image=${CONTAINER_IMAGE}" \
25+
-no-color \
26+
-auto-approve
27+
- terraform output -json > $CODEBUILD_SRC_DIR/outputs.json
28+
29+
artifacts:
30+
files:
31+
- outputs.json
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
version: 0.2
2+
3+
phases:
4+
install:
5+
commands:
6+
- set -e
7+
- export TERRAFORM_VERSION=$(< .review_apps/.terraform-version)
8+
- curl "https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_arm64.zip" -o "terraform-${TERRAFORM_VERSION}.zip"
9+
- unzip "terraform-${TERRAFORM_VERSION}.zip"
10+
- mv terraform /usr/local/bin/terraform
11+
finally:
12+
- terraform --version
13+
14+
pre_build:
15+
commands:
16+
- cd .review_apps
17+
- terraform init -backend-config="key=review-apps/${APPLICATION_NAME}/pr-${PR_NUMBER}.tfstate"
18+
19+
build:
20+
commands:
21+
- |
22+
terraform destroy \
23+
-var "pull_request_number=${PR_NUMBER}" \
24+
-var "${APPLICATION_NAME//-/_}_container_image=placeholder" \
25+
-no-color \
26+
-auto-approve

0 commit comments

Comments
 (0)