diff --git a/fast/stages/1-resman/README.md b/fast/stages/1-resman/README.md index 3111cd8b0..0a1499dec 100644 --- a/fast/stages/1-resman/README.md +++ b/fast/stages/1-resman/README.md @@ -153,7 +153,7 @@ Tags can also be specified via a factory in a similar way to organization polici A specific set of tag values used in org-level organization policy conditions can be optionally defined in the bootstrap stage, and can be referenced here if stage service accounts need specific permissions on those. -As an example, consider this tag value defined via the boostrap stage tfvars. +As an example, consider this tag value defined via the bootstrap stage tfvars. ```tfvars org_policies_config = { @@ -165,7 +165,7 @@ org_policies_config = { } ``` -The tag is then used in the boostrap stage to modify the behaviour of the relevant organization policy. +The tag is then used in the bootstrap stage to modify the behaviour of the relevant organization policy. ```yaml storage.publicAccessPrevention: @@ -329,8 +329,8 @@ terraform apply | [custom_roles](variables-fast.tf#L54) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 0-bootstrap | | [factories_config](variables.tf#L20) | Configuration for the resource factories or external data. | object({…}) | | {} | | | [fast_addon](variables-addons.tf#L17) | FAST addons configurations for stages 2. Keys are used as short names for the add-on resources. | map(object({…})) | | {} | | -| [fast_stage_2](variables-stages.tf#L17) | FAST stages 2 configurations. | map(object({…})) | | {} | | -| [fast_stage_3](variables-stages.tf#L117) | FAST stages 3 configurations. | map(object({…})) | | {} | | +| [fast_stage_2](variables-stages.tf#L17) | FAST stages 2 configurations. | map(object({…})) | | {} | | +| [fast_stage_3](variables-stages.tf#L125) | FAST stages 3 configurations. | map(object({…})) | | {} | | | [groups](variables-fast.tf#L93) | Group names or IAM-format principals to grant organization-level permissions. If just the name is provided, the 'group:' principal and organization domain are interpolated. | object({…}) | | {} | 0-bootstrap | | [locations](variables-fast.tf#L109) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {} | 0-bootstrap | | [org_policy_tags](variables-fast.tf#L153) | Organization policy tags. | object({…}) | | {} | 0-bootstrap | diff --git a/fast/stages/1-resman/stage-3.tf b/fast/stages/1-resman/stage-3.tf index 3a5af762d..97e4f7c39 100644 --- a/fast/stages/1-resman/stage-3.tf +++ b/fast/stages/1-resman/stage-3.tf @@ -1,5 +1,5 @@ /** - * Copyright 2024 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,6 +60,16 @@ locals { }, var.fast_stage_3 ) + _stage_3_iam = { for k, v in local._stage3 : k => { + "roles/logging.admin" = [module.stage3-sa-rw[k].iam_email] + "roles/owner" = [module.stage3-sa-rw[k].iam_email] + "roles/resourcemanager.folderAdmin" = [module.stage3-sa-rw[k].iam_email] + "roles/resourcemanager.projectCreator" = [module.stage3-sa-rw[k].iam_email] + "roles/compute.xpnAdmin" = [module.stage3-sa-rw[k].iam_email] + "roles/viewer" = [module.stage3-sa-ro[k].iam_email] + "roles/resourcemanager.folderViewer" = [module.stage3-sa-ro[k].iam_email] + } + } # normalize attributes stage3 = { for k, v in local._stage3 : k => merge(v, { @@ -78,7 +88,7 @@ locals { members = [ for m in vv.members : contains(["ro", "rw"], m) ? "${k}-${m}" : m ] - condition = vv.condition == null ? null : { + condition = lookup(vv, "condition", null) == null ? null : { title = vv.condition.title expression = templatestring(vv.condition.expression, { custom_roles = var.custom_roles @@ -95,7 +105,7 @@ locals { kk => { role = vv.role member = contains(["ro", "rw"], vv.member) ? "${k}-${vv.member}" : vv.member - condition = vv.condition == null ? null : { + condition = lookup(vv, "condition", null) == null ? null : { title = vv.condition.title expression = templatestring(vv.condition.expression, { custom_roles = var.custom_roles @@ -107,6 +117,10 @@ locals { } } } + iam_by_principals = { + for kk, vv in v.folder_config.iam_by_principals : + (contains(["ro", "rw"], kk) ? "${k}-${kk}" : kk) => vv + } }) }) if !contains( @@ -144,17 +158,40 @@ module "stage3-folder" { ) name = each.value.folder_config.name iam = { - "roles/logging.admin" = [module.stage3-sa-rw[each.key].iam_email] - "roles/owner" = [module.stage3-sa-rw[each.key].iam_email] - "roles/resourcemanager.folderAdmin" = [module.stage3-sa-rw[each.key].iam_email] - "roles/resourcemanager.projectCreator" = [module.stage3-sa-rw[each.key].iam_email] - "roles/compute.xpnAdmin" = [module.stage3-sa-rw[each.key].iam_email] - "roles/viewer" = [module.stage3-sa-ro[each.key].iam_email] - "roles/resourcemanager.folderViewer" = [module.stage3-sa-ro[each.key].iam_email] - + # merge inputs/factory bindings with static role bindings in loocal._stage_3_iam + for role in concat(keys(each.value.folder_config.iam), keys(local._stage_3_iam[each.key])) : + lookup(var.custom_roles, role, role) => [ + for m in concat( + lookup(local._stage_3_iam[each.key], role, []), + lookup(each.value.folder_config.iam, role, []) + ) : lookup(local.principals_iam, m, m) + ] } - iam_by_principals = each.value.folder_config.iam_by_principals - org_policies = each.value.folder_config.org_policies + + iam_bindings = { + for k, v in each.value.folder_config.iam_bindings : k => merge(v, { + members = [ + for m in v.members : lookup(local.principals_iam, m, m) + ] + role = lookup(var.custom_roles, v.role, v.role) + condition = v.condition + }) + } + iam_bindings_additive = { + for k, v in each.value.folder_config.iam_bindings_additive : k => merge(v, { + member = lookup(local.principals_iam, v.member, v.member) + role = lookup(var.custom_roles, v.role, v.role) + condition = v.condition + }) + } + iam_by_principals = { + for k, v in each.value.folder_config.iam_by_principals : + lookup(local.principals_iam, k, k) => [ + for r in v : lookup(var.custom_roles, r, r) + ] + } + + org_policies = each.value.folder_config.org_policies tag_bindings = merge( { (var.tag_names.environment) = local.tag_values["${var.tag_names.environment}/${var.environments[each.value.environment].tag_name}"].id diff --git a/fast/stages/1-resman/variables-stages.tf b/fast/stages/1-resman/variables-stages.tf index 74e04ae27..ef52cbbac 100644 --- a/fast/stages/1-resman/variables-stages.tf +++ b/fast/stages/1-resman/variables-stages.tf @@ -34,7 +34,15 @@ variable "fast_stage_2" { parent_id = optional(string) create_env_folders = optional(bool, true) iam = optional(map(list(string)), {}) - iam_bindings = optional(map(list(string)), {}) + iam_bindings = optional(map(object({ + member = string + role = string + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) + })), {}) iam_bindings_additive = optional(map(object({ member = string role = string @@ -136,7 +144,15 @@ variable "fast_stage_3" { parent_id = optional(string) tag_bindings = optional(map(string), {}) iam = optional(map(list(string)), {}) - iam_bindings = optional(map(list(string)), {}) + iam_bindings = optional(map(object({ + member = string + role = string + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) + })), {}) iam_bindings_additive = optional(map(object({ member = string role = string