Skip to content

Commit 7d50b62

Browse files
committed
Add GitHub OIDC authentication for review app deployments
Add GitHub Actions OIDC identity provider in the integration account and create per-repository IAM roles for forms-admin, forms-runner, and forms-product-page. These roles allow GitHub-hosted runners to authenticate to AWS and deploy review apps. The IAM roles are scoped to pull_request events only and have permissions limited to the resources needed for review app deployments (ECS services, task definitions, ECR, S3 state files, and application autoscaling).
1 parent 8745943 commit 7d50b62

File tree

4 files changed

+230
-0
lines changed

4 files changed

+230
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
resource "aws_iam_openid_connect_provider" "github" {
2+
url = "https://token.actions.githubusercontent.com"
3+
client_id_list = ["sts.amazonaws.com"]
4+
thumbprint_list = ["7560d6f40fa55195f740ee2b1b7c0b4836cbe103"] # echo | openssl s_client -servername token.actions.githubusercontent.com -showcerts -connect token.actions.githubusercontent.com:443 2>/dev/null | openssl x509 -fingerprint -sha1 -noout | sed 's/://g' | awk -F= '{print tolower($2)}'
5+
}

infra/deployments/integration/account/outputs.tf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,8 @@ output "codeconnection_arn" {
1616
output "kinesis_subscription_role_arn" {
1717
value = aws_iam_role.kinesis_subscription_role.arn
1818
}
19+
20+
output "github_oidc_provider_arn" {
21+
description = "The ARN of the GitHub OIDC provider"
22+
value = aws_iam_openid_connect_provider.github.arn
23+
}
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
locals {
2+
github_actions_apps = {
3+
"forms-admin" = {
4+
ecr_repository_arn = module.forms_admin_container_repo.arn
5+
}
6+
"forms-runner" = {
7+
ecr_repository_arn = module.forms_runner_container_repo.arn
8+
}
9+
"forms-product-page" = {
10+
ecr_repository_arn = module.forms_product_page_container_repo.arn
11+
}
12+
}
13+
}
14+
15+
resource "aws_iam_role" "github_actions" {
16+
for_each = local.github_actions_apps
17+
18+
name = "review-github-actions-${each.key}"
19+
description = "Role assumed by GitHub Actions workflows for ${each.key} review apps"
20+
21+
assume_role_policy = jsonencode({
22+
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
29+
}
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+
}
37+
}
38+
}
39+
]
40+
})
41+
}
42+
43+
resource "aws_iam_role_policy" "github_actions" {
44+
for_each = local.github_actions_apps
45+
46+
role = aws_iam_role.github_actions[each.key].name
47+
policy = data.aws_iam_policy_document.github_actions[each.key].json
48+
}
49+
50+
data "aws_iam_policy_document" "github_actions" {
51+
for_each = local.github_actions_apps
52+
53+
statement {
54+
sid = "UseECSServices"
55+
effect = "Allow"
56+
actions = [
57+
"ecs:*Service",
58+
"ecs:*Services",
59+
"ecs:TagResource"
60+
]
61+
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-*"
64+
]
65+
}
66+
67+
statement {
68+
sid = "UseECSTaskDefinitions"
69+
effect = "Allow"
70+
actions = [
71+
"ecs:*TaskDefinition",
72+
"ecs:*TaskDefinitions",
73+
"ecs:TagResource"
74+
]
75+
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"
86+
]
87+
resources = ["*"]
88+
}
89+
90+
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"
132+
actions = [
133+
"ecr:BatchCheckLayerAvailability",
134+
"ecr:BatchGetImage",
135+
"ecr:CompleteLayerUpload",
136+
"ecr:GetDownloadUrlForLayer",
137+
"ecr:InitiateLayerUpload",
138+
"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/*"
167+
]
168+
}
169+
170+
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+
]
180+
}
181+
182+
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+
]
196+
resources = [
197+
"arn:aws:application-autoscaling:eu-west-2:${data.aws_caller_identity.current.account_id}:scalable-target/*"
198+
]
199+
}
200+
201+
statement {
202+
sid = "UseAppAutoscalingNeedingStar"
203+
effect = "Allow"
204+
actions = [
205+
"application-autoscaling:DescribeScalableTargets",
206+
"application-autoscaling:DescribeScalingPolicies",
207+
"application-autoscaling:DescribeScheduledActions",
208+
]
209+
resources = [
210+
"*"
211+
]
212+
}
213+
}

infra/deployments/integration/review/outputs.tf

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,10 @@ output "traefik_basic_auth_credentials" {
4848
value = data.aws_ssm_parameter.traefik_basic_auth_credentials.value
4949
sensitive = true
5050
}
51+
52+
output "github_actions_role_arns" {
53+
description = "IAM role ARNs for GitHub Actions review app deployments"
54+
value = {
55+
for app, role in aws_iam_role.github_actions : app => role.arn
56+
}
57+
}

0 commit comments

Comments
 (0)