Allow custom roles in context, add support for shared VPC IAM to project and project factory (#3163)
* add support for custom roles and hos tproject iam to project modules * align vpc factory
This commit is contained in:
committed by
GitHub
parent
065d79efbf
commit
fe0a8128dc
@@ -54,21 +54,26 @@ locals {
|
||||
)
|
||||
service_encryption_key_ids = {}
|
||||
services = []
|
||||
shared_vpc_service_config = merge({
|
||||
host_project = null
|
||||
network_users = []
|
||||
service_agent_iam = {}
|
||||
service_agent_subnet_iam = {}
|
||||
service_iam_grants = []
|
||||
network_subnet_users = {}
|
||||
}, try(local._projects_config.data_defaults.shared_vpc_service_config, {
|
||||
shared_vpc_service_config = merge(
|
||||
{
|
||||
host_project = null
|
||||
iam_bindings_additive = {}
|
||||
network_users = []
|
||||
service_agent_iam = {}
|
||||
service_agent_subnet_iam = {}
|
||||
service_iam_grants = []
|
||||
network_subnet_users = {}
|
||||
})
|
||||
},
|
||||
try(local._projects_config.data_defaults.shared_vpc_service_config, {
|
||||
host_project = null
|
||||
iam_bindings_additive = {}
|
||||
network_users = []
|
||||
service_agent_iam = {}
|
||||
service_agent_subnet_iam = {}
|
||||
service_iam_grants = []
|
||||
network_subnet_users = {}
|
||||
}
|
||||
)
|
||||
)
|
||||
storage_location = null
|
||||
tag_bindings = {}
|
||||
@@ -245,6 +250,7 @@ locals {
|
||||
? merge(
|
||||
{
|
||||
host_project = null
|
||||
iam_bindings_additive = {}
|
||||
network_users = []
|
||||
service_agent_iam = {}
|
||||
service_agent_subnet_iam = {}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -54,21 +54,26 @@ locals {
|
||||
)
|
||||
service_encryption_key_ids = {}
|
||||
services = []
|
||||
shared_vpc_service_config = merge({
|
||||
host_project = null
|
||||
network_users = []
|
||||
service_agent_iam = {}
|
||||
service_agent_subnet_iam = {}
|
||||
service_iam_grants = []
|
||||
network_subnet_users = {}
|
||||
}, try(local._projects_config.data_defaults.shared_vpc_service_config, {
|
||||
shared_vpc_service_config = merge(
|
||||
{
|
||||
host_project = null
|
||||
iam_bindings_additive = {}
|
||||
network_users = []
|
||||
service_agent_iam = {}
|
||||
service_agent_subnet_iam = {}
|
||||
service_iam_grants = []
|
||||
network_subnet_users = {}
|
||||
})
|
||||
},
|
||||
try(local._projects_config.data_defaults.shared_vpc_service_config, {
|
||||
host_project = null
|
||||
iam_bindings_additive = {}
|
||||
network_users = []
|
||||
service_agent_iam = {}
|
||||
service_agent_subnet_iam = {}
|
||||
service_iam_grants = []
|
||||
network_subnet_users = {}
|
||||
}
|
||||
)
|
||||
)
|
||||
storage_location = null
|
||||
tag_bindings = {}
|
||||
@@ -245,6 +250,7 @@ locals {
|
||||
? merge(
|
||||
{
|
||||
host_project = null
|
||||
iam_bindings_additive = {}
|
||||
network_users = []
|
||||
service_agent_iam = {}
|
||||
service_agent_subnet_iam = {}
|
||||
|
||||
@@ -136,7 +136,8 @@ module "projects-iam" {
|
||||
}
|
||||
}
|
||||
iam = {
|
||||
for k, v in lookup(each.value, "iam", {}) : k => [
|
||||
for k, v in lookup(each.value, "iam", {}) :
|
||||
lookup(var.factories_config.context.custom_roles, k, k) => [
|
||||
for vv in v : try(
|
||||
# project service accounts (sa)
|
||||
module.service-accounts["${each.key}/${vv}"].iam_email,
|
||||
@@ -184,6 +185,7 @@ module "projects-iam" {
|
||||
)
|
||||
)
|
||||
]
|
||||
role = lookup(var.factories_config.context.custom_roles, v.role, v.role)
|
||||
})
|
||||
}
|
||||
iam_bindings_additive = {
|
||||
@@ -208,6 +210,7 @@ module "projects-iam" {
|
||||
: tonumber("[Error] Invalid member: '${v.member}' in project '${each.key}'")
|
||||
)
|
||||
)
|
||||
role = lookup(var.factories_config.context.custom_roles, v.role, v.role)
|
||||
})
|
||||
}
|
||||
# IAM by principals would trigger dynamic key errors so we don't interpolate
|
||||
@@ -231,7 +234,9 @@ module "projects-iam" {
|
||||
? k
|
||||
: tonumber("[Error] Invalid member: '${k}' in project '${each.key}'")
|
||||
)
|
||||
) => v
|
||||
) => [
|
||||
for vv in v : lookup(var.factories_config.context.custom_roles, vv, vv)
|
||||
]
|
||||
}
|
||||
# Shared VPC configuration is done at stage 2, to avoid dependency cycle between project service accounts and
|
||||
# IAM grants done for those service accounts
|
||||
@@ -244,6 +249,31 @@ module "projects-iam" {
|
||||
module.projects[each.value.shared_vpc_service_config.host_project].project_id,
|
||||
each.value.shared_vpc_service_config.host_project
|
||||
)
|
||||
iam_bindings_additive = {
|
||||
for k, v in try(each.value.shared_vpc_service_config.iam_bindings_additive, {}) : k => merge(v, {
|
||||
member = try(
|
||||
# project service accounts (sa)
|
||||
module.service-accounts["${each.key}/${v.member}"].iam_email,
|
||||
# automation service account (rw)
|
||||
local.context.iam_principals["${each.key}/automation/${v.member}"],
|
||||
# automation service account (automation/rw)
|
||||
local.context.iam_principals["${each.key}/${v.member}"],
|
||||
# other projects service accounts (project/sa)
|
||||
module.service-accounts[v.member].iam_email,
|
||||
# other automation service account (project/automation/rw)
|
||||
local.context.iam_principals[v.member],
|
||||
# project's service identities
|
||||
local.service_agents_email[each.key][v.member],
|
||||
# passthrough + error handling using tonumber until Terraform gets fail/raise function
|
||||
(
|
||||
strcontains(v.member, ":")
|
||||
? v.member
|
||||
: tonumber("[Error] Invalid member: '${v.member}' in project '${each.key}'")
|
||||
)
|
||||
)
|
||||
role = lookup(var.factories_config.context.custom_roles, v.role, v.role)
|
||||
})
|
||||
}
|
||||
network_users = [
|
||||
for vv in try(each.value.shared_vpc_service_config.network_users, []) :
|
||||
try(
|
||||
|
||||
@@ -310,6 +310,9 @@
|
||||
"host_project": {
|
||||
"type": "string"
|
||||
},
|
||||
"iam_bindings_additive": {
|
||||
"$ref": "#/$defs/iam_bindings_additive"
|
||||
},
|
||||
"network_users": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@@ -557,7 +560,7 @@
|
||||
},
|
||||
"role": {
|
||||
"type": "string",
|
||||
"pattern": "^roles/"
|
||||
"pattern": "^[a-zA-Z0-9_/]+$"
|
||||
},
|
||||
"condition": {
|
||||
"type": "object",
|
||||
|
||||
@@ -41,7 +41,16 @@ variable "data_defaults" {
|
||||
service_encryption_key_ids = optional(map(list(string)), {})
|
||||
services = optional(list(string), [])
|
||||
shared_vpc_service_config = optional(object({
|
||||
host_project = string
|
||||
host_project = string
|
||||
iam_bindings_additive = optional(map(object({
|
||||
member = string
|
||||
role = string
|
||||
condition = optional(object({
|
||||
expression = string
|
||||
title = string
|
||||
description = optional(string)
|
||||
}))
|
||||
})), {})
|
||||
network_users = optional(list(string), [])
|
||||
service_agent_iam = optional(map(list(string)), {})
|
||||
service_agent_subnet_iam = optional(map(list(string)), {})
|
||||
@@ -140,6 +149,7 @@ variable "factories_config" {
|
||||
notification_channels = optional(map(any), {})
|
||||
}))
|
||||
context = optional(object({
|
||||
custom_roles = optional(map(string), {})
|
||||
folder_ids = optional(map(string), {})
|
||||
iam_principals = optional(map(string), {})
|
||||
kms_keys = optional(map(string), {})
|
||||
|
||||
@@ -1728,12 +1728,12 @@ alerts:
|
||||
| [service_encryption_key_ids](variables.tf#L210) | Service Agents to be granted encryption/decryption permissions over Cloud KMS encryption keys. Format {SERVICE_AGENT => [KEY_ID]}. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [services](variables.tf#L217) | Service APIs to enable. | <code>list(string)</code> | | <code>[]</code> |
|
||||
| [shared_vpc_host_config](variables.tf#L223) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | <code title="object({ enabled = bool service_projects = optional(list(string), []) })">object({…})</code> | | <code>null</code> |
|
||||
| [shared_vpc_service_config](variables.tf#L233) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | <code title="object({ host_project = string network_users = optional(list(string), []) service_agent_iam = optional(map(list(string)), {}) service_agent_subnet_iam = optional(map(list(string)), {}) service_iam_grants = optional(list(string), []) network_subnet_users = optional(map(list(string)), {}) })">object({…})</code> | | <code title="{ host_project = null }">{…}</code> |
|
||||
| [skip_delete](variables.tf#L261) | Deprecated. Use deletion_policy. | <code>bool</code> | | <code>null</code> |
|
||||
| [shared_vpc_service_config](variables.tf#L233) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | <code title="object({ host_project = string iam_bindings_additive = optional(map(object({ member = string role = string condition = optional(object({ expression = string title = string description = optional(string) })) })), {}) network_users = optional(list(string), []) service_agent_iam = optional(map(list(string)), {}) service_agent_subnet_iam = optional(map(list(string)), {}) service_iam_grants = optional(list(string), []) network_subnet_users = optional(map(list(string)), {}) })">object({…})</code> | | <code title="{ host_project = null }">{…}</code> |
|
||||
| [skip_delete](variables.tf#L270) | Deprecated. Use deletion_policy. | <code>bool</code> | | <code>null</code> |
|
||||
| [tag_bindings](variables-tags.tf#L81) | Tag bindings for this project, in key => tag value id format. | <code>map(string)</code> | | <code>null</code> |
|
||||
| [tags](variables-tags.tf#L88) | Tags by key name. If `id` is provided, key or value creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | <code title="map(object({ description = optional(string, "Managed by the Terraform project module.") iam = optional(map(list(string)), {}) iam_bindings = optional(map(object({ members = list(string) role = string condition = optional(object({ expression = string title = string description = optional(string) })) })), {}) iam_bindings_additive = optional(map(object({ member = string role = string condition = optional(object({ expression = string title = string description = optional(string) })) })), {}) id = optional(string) values = optional(map(object({ description = optional(string, "Managed by the Terraform project module.") iam = optional(map(list(string)), {}) iam_bindings = optional(map(object({ members = list(string) role = string condition = optional(object({ expression = string title = string description = optional(string) })) })), {}) iam_bindings_additive = optional(map(object({ member = string role = string condition = optional(object({ expression = string title = string description = optional(string) })) })), {}) id = optional(string) })), {}) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [universe](variables.tf#L273) | GCP universe where to deploy the project. The prefix will be prepended to the project id. | <code title="object({ prefix = string unavailable_services = optional(list(string), []) })">object({…})</code> | | <code>null</code> |
|
||||
| [vpc_sc](variables.tf#L282) | VPC-SC configuration for the project, use when `ignore_changes` for resources is set in the VPC-SC module. | <code title="object({ perimeter_name = string perimeter_bridges = optional(list(string), []) is_dry_run = optional(bool, false) })">object({…})</code> | | <code>null</code> |
|
||||
| [universe](variables.tf#L282) | GCP universe where to deploy the project. The prefix will be prepended to the project id. | <code title="object({ prefix = string unavailable_services = optional(list(string), []) })">object({…})</code> | | <code>null</code> |
|
||||
| [vpc_sc](variables.tf#L291) | VPC-SC configuration for the project, use when `ignore_changes` for resources is set in the VPC-SC module. | <code title="object({ perimeter_name = string perimeter_bridges = optional(list(string), []) is_dry_run = optional(bool, false) })">object({…})</code> | | <code>null</code> |
|
||||
|
||||
## Outputs
|
||||
|
||||
|
||||
@@ -129,11 +129,25 @@ resource "google_project_iam_member" "shared_vpc_host_robots" {
|
||||
}
|
||||
|
||||
resource "google_project_iam_member" "shared_vpc_host_iam" {
|
||||
for_each = toset(var.shared_vpc_service_config.network_users)
|
||||
project = var.shared_vpc_service_config.host_project
|
||||
role = "roles/compute.networkUser"
|
||||
member = each.value
|
||||
depends_on = []
|
||||
for_each = toset(var.shared_vpc_service_config.network_users)
|
||||
project = var.shared_vpc_service_config.host_project
|
||||
role = "roles/compute.networkUser"
|
||||
member = each.value
|
||||
}
|
||||
|
||||
resource "google_project_iam_member" "shared_vpc_host_iam_additive" {
|
||||
for_each = try(var.shared_vpc_service_config.iam_bindings_additive, {})
|
||||
project = var.shared_vpc_service_config.host_project
|
||||
role = each.value.role
|
||||
member = each.value.member
|
||||
dynamic "condition" {
|
||||
for_each = each.value.condition == null ? [] : [""]
|
||||
content {
|
||||
expression = each.value.condition.expression
|
||||
title = each.value.condition.title
|
||||
description = each.value.condition.description
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_compute_subnetwork_iam_member" "shared_vpc_host_robots" {
|
||||
|
||||
@@ -234,7 +234,16 @@ variable "shared_vpc_service_config" {
|
||||
description = "Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config)."
|
||||
# the list of valid service identities is in service-agents.yaml
|
||||
type = object({
|
||||
host_project = string
|
||||
host_project = string
|
||||
iam_bindings_additive = optional(map(object({
|
||||
member = string
|
||||
role = string
|
||||
condition = optional(object({
|
||||
expression = string
|
||||
title = string
|
||||
description = optional(string)
|
||||
}))
|
||||
})), {})
|
||||
network_users = optional(list(string), [])
|
||||
service_agent_iam = optional(map(list(string)), {})
|
||||
service_agent_subnet_iam = optional(map(list(string)), {})
|
||||
|
||||
Reference in New Issue
Block a user