@@ -17,9 +17,50 @@ variable "tf_access_role_arn" {
1717 default = " "
1818}
1919
20+ variable "tf_access_additional_backends" {
21+ type = map (object ({
22+ bucket_arn = string
23+ dynamodb_table_arn = optional (string , " " )
24+ role_arn = string
25+ }))
26+ description = <<- EOT
27+ Map of additional Terraform state backends to grant SSO permission sets access to.
28+ Each entry creates three permission sets: TerraformPlanAccess-<key>, TerraformApplyAccess-<key>, and TerraformStateAccess-<key>.
29+
30+ The map key should be a descriptive name for the backend (e.g., "core", "plat", "prod").
31+ This key will be title-cased and appended to the permission set names with a hyphen.
32+
33+ Example:
34+ ```
35+ tf_access_additional_backends = {
36+ core = {
37+ bucket_arn = "arn:aws:s3:::example-core-tfstate"
38+ dynamodb_table_arn = "arn:aws:dynamodb:us-east-1:123456789012:table/example-core-tfstate-lock"
39+ role_arn = "arn:aws:iam::123456789012:role/example-core-gbl-root-tfstate"
40+ }
41+ plat = {
42+ bucket_arn = "arn:aws:s3:::example-plat-tfstate"
43+ role_arn = "arn:aws:iam::123456789012:role/example-plat-gbl-root-tfstate"
44+ }
45+ }
46+ ```
47+ EOT
48+ default = {}
49+ }
50+
2051locals {
2152 tf_access_enabled = module. this . enabled && var. tf_access_bucket_arn != " " && var. tf_access_role_arn != " "
2253
54+ # Additional backends access
55+ tf_access_additional_backends_enabled = module. this . enabled && length (var. tf_access_additional_backends ) > 0
56+
57+ # Helper to title-case the backend names for permission set names
58+ # "core" -> "Core", "plat" -> "Plat", "prod-us-east-1" -> "ProdUsEast1"
59+ backend_names_titlecase = {
60+ for key , config in var . tf_access_additional_backends :
61+ key = > join (" " , [for part in split (" -" , key) : title (part)])
62+ }
63+
2364 # Terraform Plan Access permission set
2465 terraform_plan_access_permission_set = local. tf_access_enabled ? [{
2566 name = " TerraformPlanAccess" ,
@@ -55,6 +96,46 @@ locals {
5596 policy_attachments = []
5697 customer_managed_policy_attachments = []
5798 }] : []
99+
100+ # Additional backends permission sets
101+ terraform_plan_access_additional_permission_sets = local. tf_access_additional_backends_enabled ? [
102+ for key , config in var . tf_access_additional_backends : {
103+ name = " TerraformPlanAccess-${ local . backend_names_titlecase [key ]} "
104+ description = " Allow read-only access to Terraform state for planning (${ key } backend)"
105+ relay_state = " "
106+ session_duration = var . session_duration
107+ tags = {}
108+ inline_policy = data . aws_iam_policy_document . terraform_plan_access_additional [key ]. json
109+ policy_attachments = [" arn:${ local . aws_partition } :iam::aws:policy/ReadOnlyAccess" ]
110+ customer_managed_policy_attachments = []
111+ }
112+ ] : []
113+
114+ terraform_apply_access_additional_permission_sets = local. tf_access_additional_backends_enabled ? [
115+ for key , config in var . tf_access_additional_backends : {
116+ name = " TerraformApplyAccess-${ local . backend_names_titlecase [key ]} "
117+ description = " Allow full access to Terraform state and account for applying changes (${ key } backend)"
118+ relay_state = " "
119+ session_duration = var . session_duration
120+ tags = {}
121+ inline_policy = data . aws_iam_policy_document . terraform_apply_access_additional [key ]. json
122+ policy_attachments = [" arn:${ local . aws_partition } :iam::aws:policy/AdministratorAccess" ]
123+ customer_managed_policy_attachments = []
124+ }
125+ ] : []
126+
127+ terraform_state_access_additional_permission_sets = local. tf_access_additional_backends_enabled ? [
128+ for key , config in var . tf_access_additional_backends : {
129+ name = " TerraformStateAccess-${ local . backend_names_titlecase [key ]} "
130+ description = " Allow read/write access to Terraform state backend only (${ key } backend)"
131+ relay_state = " "
132+ session_duration = var . session_duration
133+ tags = {}
134+ inline_policy = data . aws_iam_policy_document . terraform_state_access_additional [key ]. json
135+ policy_attachments = []
136+ customer_managed_policy_attachments = []
137+ }
138+ ] : []
58139}
59140
60141# Terraform Plan Access - Read-only state access, read-only account access
@@ -187,3 +268,135 @@ data "aws_iam_policy_document" "terraform_state_access" {
187268 resources = [var . tf_access_role_arn ]
188269 }
189270}
271+
272+ # Additional backends policy documents
273+
274+ # Terraform Plan Access - Read-only state access for additional backends
275+ data "aws_iam_policy_document" "terraform_plan_access_additional" {
276+ for_each = local. tf_access_additional_backends_enabled ? var. tf_access_additional_backends : {}
277+
278+ # Read-only access to Terraform state S3 bucket
279+ statement {
280+ sid = " TerraformStateBackendS3BucketReadOnly"
281+ effect = " Allow"
282+ actions = [
283+ " s3:ListBucket" ,
284+ " s3:GetObject" ,
285+ ]
286+ resources = [each . value . bucket_arn , " ${ each . value . bucket_arn } /*" ]
287+ }
288+
289+ # Allow assuming the Terraform state backend role
290+ statement {
291+ sid = " TerraformStateBackendAssumeRole"
292+ effect = " Allow"
293+ actions = [
294+ " sts:AssumeRole" ,
295+ " sts:TagSession" ,
296+ " sts:SetSourceIdentity" ,
297+ ]
298+ resources = [each . value . role_arn ]
299+ }
300+
301+ # Allow EC2 DescribeRegions - required by many Terraform modules for region validation
302+ statement {
303+ sid = " EC2DescribeRegions"
304+ effect = " Allow"
305+ actions = [
306+ " ec2:DescribeRegions" ,
307+ ]
308+ resources = [" *" ]
309+ }
310+ }
311+
312+ # Terraform Apply Access - Read/write state access for additional backends
313+ data "aws_iam_policy_document" "terraform_apply_access_additional" {
314+ for_each = local. tf_access_additional_backends_enabled ? var. tf_access_additional_backends : {}
315+
316+ statement {
317+ sid = " TerraformStateBackendS3Bucket"
318+ effect = " Allow"
319+ actions = [
320+ " s3:ListBucket" ,
321+ " s3:GetObject" ,
322+ " s3:PutObject" ,
323+ ]
324+ resources = [each . value . bucket_arn , " ${ each . value . bucket_arn } /*" ]
325+ }
326+
327+ # Conditional DynamoDB access (only if table ARN is provided)
328+ dynamic "statement" {
329+ for_each = each. value . dynamodb_table_arn != " " ? [1 ] : []
330+
331+ content {
332+ sid = " TerraformStateBackendDynamoDbTable"
333+ effect = " Allow"
334+ actions = [" dynamodb:GetItem" , " dynamodb:PutItem" , " dynamodb:DeleteItem" ]
335+ resources = [each . value . dynamodb_table_arn ]
336+ }
337+ }
338+
339+ # Allow assuming the Terraform state backend role
340+ statement {
341+ sid = " TerraformStateBackendAssumeRole"
342+ effect = " Allow"
343+ actions = [
344+ " sts:AssumeRole" ,
345+ " sts:TagSession" ,
346+ " sts:SetSourceIdentity" ,
347+ ]
348+ resources = [each . value . role_arn ]
349+ }
350+
351+ # Allow EC2 DescribeRegions - required by many Terraform modules for region validation
352+ statement {
353+ sid = " EC2DescribeRegions"
354+ effect = " Allow"
355+ actions = [
356+ " ec2:DescribeRegions" ,
357+ ]
358+ resources = [" *" ]
359+ }
360+ }
361+
362+ # Terraform State Access - Read/write state only for additional backends
363+ data "aws_iam_policy_document" "terraform_state_access_additional" {
364+ for_each = local. tf_access_additional_backends_enabled ? var. tf_access_additional_backends : {}
365+
366+ # Read/write access to Terraform state S3 bucket
367+ statement {
368+ sid = " TerraformStateBackendS3Bucket"
369+ effect = " Allow"
370+ actions = [
371+ " s3:ListBucket" ,
372+ " s3:GetObject" ,
373+ " s3:PutObject" ,
374+ " s3:DeleteObject" ,
375+ ]
376+ resources = [each . value . bucket_arn , " ${ each . value . bucket_arn } /*" ]
377+ }
378+
379+ # DynamoDB table access for state locking (if configured)
380+ dynamic "statement" {
381+ for_each = each. value . dynamodb_table_arn != " " ? [1 ] : []
382+
383+ content {
384+ sid = " TerraformStateBackendDynamoDbTable"
385+ effect = " Allow"
386+ actions = [" dynamodb:GetItem" , " dynamodb:PutItem" , " dynamodb:DeleteItem" ]
387+ resources = [each . value . dynamodb_table_arn ]
388+ }
389+ }
390+
391+ # Allow assuming the Terraform state backend role
392+ statement {
393+ sid = " TerraformStateBackendAssumeRole"
394+ effect = " Allow"
395+ actions = [
396+ " sts:AssumeRole" ,
397+ " sts:TagSession" ,
398+ " sts:SetSourceIdentity" ,
399+ ]
400+ resources = [each . value . role_arn ]
401+ }
402+ }
0 commit comments