1+ data "aws_region" "current" {}
2+
13locals {
24 github_actions_apps = {
35 " forms-admin" = {
@@ -12,32 +14,103 @@ locals {
1214 }
1315}
1416
17+ # S3 bucket for CodeBuild artifacts (terraform outputs)
18+ module "codebuild_artifacts" {
19+ source = " ../../../modules/secure-bucket"
20+
21+ name = " forms-review-codebuild-artifacts"
22+ versioning_enabled = false
23+ access_logging_enabled = false
24+ }
25+
26+ resource "aws_s3_bucket_lifecycle_configuration" "codebuild_artifacts" {
27+ bucket = module. codebuild_artifacts . name
28+
29+ rule {
30+ id = " expire-old-artifacts"
31+ status = " Enabled"
32+
33+ filter {}
34+
35+ expiration {
36+ days = 1
37+ }
38+
39+ abort_incomplete_multipart_upload {
40+ days_after_initiation = 1
41+ }
42+ }
43+ }
44+
45+ # CodeBuild projects for each app
46+ module "codebuild_deploy" {
47+ for_each = local. github_actions_apps
48+ source = " ./review-app-codebuild"
49+
50+ application_name = each. key
51+ action = " deploy"
52+ github_repository = " https://github.com/alphagov/${ each . key } "
53+ codeconnection_arn = data. terraform_remote_state . account . outputs . codeconnection_arn
54+ artifacts_bucket_name = module. codebuild_artifacts . name
55+ ecs_cluster_arn = aws_ecs_cluster. review . arn
56+ ecs_cluster_name = aws_ecs_cluster. review . name
57+ ecr_repository_arn = each. value . ecr_repository_arn
58+ task_execution_role_arn = aws_iam_role. ecs_execution . arn
59+ autoscaling_role_arn = aws_iam_service_linked_role. app_autoscaling . arn
60+ deploy_account_id = var. deploy_account_id
61+ }
62+
63+ module "codebuild_destroy" {
64+ for_each = local. github_actions_apps
65+ source = " ./review-app-codebuild"
66+
67+ application_name = each. key
68+ action = " destroy"
69+ github_repository = " https://github.com/alphagov/${ each . key } "
70+ codeconnection_arn = data. terraform_remote_state . account . outputs . codeconnection_arn
71+ artifacts_bucket_name = module. codebuild_artifacts . name
72+ ecs_cluster_arn = aws_ecs_cluster. review . arn
73+ ecs_cluster_name = aws_ecs_cluster. review . name
74+ ecr_repository_arn = each. value . ecr_repository_arn
75+ task_execution_role_arn = aws_iam_role. ecs_execution . arn
76+ autoscaling_role_arn = aws_iam_service_linked_role. app_autoscaling . arn
77+ deploy_account_id = var. deploy_account_id
78+ }
79+
80+ # OIDC roles with minimal permissions (trigger CodeBuild + push to ECR)
81+ data "aws_iam_policy_document" "github_actions_assume_role" {
82+ for_each = local. github_actions_apps
83+
84+ statement {
85+ effect = " Allow"
86+ actions = [" sts:AssumeRoleWithWebIdentity" ]
87+
88+ principals {
89+ type = " Federated"
90+ identifiers = [data . terraform_remote_state . account . outputs . github_oidc_provider_arn ]
91+ }
92+
93+ condition {
94+ test = " StringEquals"
95+ variable = " token.actions.githubusercontent.com:aud"
96+ values = [" sts.amazonaws.com" ]
97+ }
98+
99+ condition {
100+ test = " StringLike"
101+ variable = " token.actions.githubusercontent.com:sub"
102+ values = [" repo:alphagov/${ each . key } :pull_request" ]
103+ }
104+ }
105+ }
106+
15107resource "aws_iam_role" "github_actions" {
16108 for_each = local. github_actions_apps
17109
18110 name = " review-github-actions-${ each . key } "
19111 description = " Role assumed by GitHub Actions workflows for ${ each . key } review apps"
20112
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- })
113+ assume_role_policy = data. aws_iam_policy_document . github_actions_assume_role [each . key ]. json
41114}
42115
43116resource "aws_iam_role_policy" "github_actions" {
@@ -50,164 +123,60 @@ resource "aws_iam_role_policy" "github_actions" {
50123data "aws_iam_policy_document" "github_actions" {
51124 for_each = local. github_actions_apps
52125
126+ # Trigger CodeBuild projects
53127 statement {
54- sid = " UseECSServices"
55- effect = " Allow"
56- actions = [
57- " ecs:*Service" ,
58- " ecs:*Services" ,
59- " ecs:TagResource"
60- ]
128+ sid = " TriggerCodeBuild"
129+ actions = [" codebuild:StartBuild" , " codebuild:BatchGetBuilds" ]
61130 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-* "
131+ module . codebuild_deploy [ each . key ] . project_arn ,
132+ module . codebuild_destroy [ each . key ] . project_arn
64133 ]
65134 }
66135
136+ # Read CodeBuild logs
67137 statement {
68- sid = " UseECSTaskDefinitions"
69- effect = " Allow"
70- actions = [
71- " ecs:*TaskDefinition" ,
72- " ecs:*TaskDefinitions" ,
73- " ecs:TagResource"
74- ]
138+ sid = " ReadCodeBuildLogs"
139+ actions = [" logs:GetLogEvents" ]
75140 resources = [
76- " arn:aws:ecs:eu-west-2:${ data . aws_caller_identity . current . account_id } :task-definition/${ each . key } -pr-*"
141+ module . codebuild_deploy [each . key ]. log_group_arn ,
142+ module . codebuild_destroy [each . key ]. log_group_arn
77143 ]
78144 }
79145
146+ # Push container images to ECR
80147 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"
148+ sid = " PushToECR"
132149 actions = [
133150 " ecr:BatchCheckLayerAvailability" ,
134- " ecr:BatchGetImage" ,
135151 " ecr:CompleteLayerUpload" ,
136- " ecr:GetDownloadUrlForLayer" ,
137152 " ecr:InitiateLayerUpload" ,
138153 " 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/*"
154+ " ecr:UploadLayerPart"
167155 ]
156+ resources = [each . value . ecr_repository_arn ]
168157 }
169158
170159 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- ]
160+ sid = " ECRLogin"
161+ actions = [" ecr:GetAuthorizationToken" ]
162+ resources = [" *" ]
180163 }
181164
165+ # Wait for ECS service stability (read-only)
182166 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- ]
167+ sid = " WaitForECSStability"
168+ actions = [" ecs:DescribeServices" ]
196169 resources = [
197- " arn:aws:application-autoscaling:eu-west-2 :${ data . aws_caller_identity . current . account_id } :scalable-target/ *"
170+ " arn:aws:ecs: ${ data . aws_region . current . name } :${ data . aws_caller_identity . current . account_id } :service/ ${ aws_ecs_cluster . review . name } / ${ each . key } -pr- *"
198171 ]
199172 }
200173
174+ # Read and delete CodeBuild artifacts (terraform outputs)
201175 statement {
202- sid = " UseAppAutoscalingNeedingStar"
203- effect = " Allow"
204- actions = [
205- " application-autoscaling:DescribeScalableTargets" ,
206- " application-autoscaling:DescribeScalingPolicies" ,
207- " application-autoscaling:DescribeScheduledActions" ,
208- ]
176+ sid = " ReadArtifacts"
177+ actions = [" s3:GetObject" , " s3:DeleteObject" ]
209178 resources = [
210- " * "
179+ " ${ module . codebuild_artifacts . arn } /*/review- ${ each . key } -deploy/outputs.json "
211180 ]
212181 }
213182}
0 commit comments