Implement support for VPC-SC perimeter membership from project factory (#3007)
* support project factory-level vpc-sc perimeter interpolation * fix ro role * add support for IAM on service accounts * fix typo
This commit is contained in:
committed by
GitHub
parent
25b6020a14
commit
69188fa9d9
@@ -144,7 +144,8 @@ module "organization" {
|
||||
|| api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([%s])
|
||||
EOT
|
||||
, join(",", formatlist("'%s'", [
|
||||
"roles/accesscontextmanager.policyAdmin",
|
||||
"roles/accesscontextmanager.policyEditor",
|
||||
"roles/accesscontextmanager.policyReader",
|
||||
"roles/cloudasset.viewer",
|
||||
"roles/compute.orgFirewallPolicyAdmin",
|
||||
"roles/compute.orgFirewallPolicyUser",
|
||||
|
||||
@@ -25,3 +25,9 @@ organization_config:
|
||||
description: Org policy tag scoped grant for project factory.
|
||||
expression: |
|
||||
resource.matchTag('${organization.id}/${tag_names.context}', 'project-factory')
|
||||
sa_pf_vpcsc_ro:
|
||||
member: ro
|
||||
role: roles/accesscontextmanager.policyReader
|
||||
sa_pf_vpcsc_rw:
|
||||
member: rw
|
||||
role: roles/accesscontextmanager.policyEditor
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
locals {
|
||||
tfvars = {
|
||||
perimeters = {
|
||||
for k, v in try(module.vpc-sc[0].service_perimeters_regular, {}) :
|
||||
for k, v in try(module.vpc-sc.service_perimeters_regular, {}) :
|
||||
k => v.id
|
||||
}
|
||||
perimeters_bridge = {
|
||||
for k, v in try(module.vpc-sc[0].service_perimeters_bridge, {}) :
|
||||
for k, v in try(module.vpc-sc.service_perimeters_bridge, {}) :
|
||||
k => v.id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,4 +2,4 @@ FAST_STAGE_DESCRIPTION="project factory (org level)"
|
||||
FAST_STAGE_LEVEL=2
|
||||
FAST_STAGE_NAME=project-factory
|
||||
FAST_STAGE_DEPS="0-globals 0-bootstrap 1-resman"
|
||||
FAST_STAGE_OPTIONAL="2-networking 2-security"
|
||||
FAST_STAGE_OPTIONAL="1-vpcsc 2-networking 2-security"
|
||||
@@ -354,16 +354,17 @@ The approach is not shown here but reasonably easy to implement. The main projec
|
||||
|---|---|:---:|:---:|:---:|:---:|
|
||||
| [automation](variables-fast.tf#L17) | Automation resources created by the bootstrap stage. | <code title="object({ outputs_bucket = string })">object({…})</code> | ✓ | | <code>0-bootstrap</code> |
|
||||
| [billing_account](variables-fast.tf#L26) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | <code title="object({ id = string is_org_level = optional(bool, true) })">object({…})</code> | ✓ | | <code>0-bootstrap</code> |
|
||||
| [prefix](variables-fast.tf#L74) | Prefix used for resources that need unique names. Use a maximum of 9 chars for organizations, and 11 chars for tenants. | <code>string</code> | ✓ | | <code>0-bootstrap</code> |
|
||||
| [prefix](variables-fast.tf#L82) | Prefix used for resources that need unique names. Use a maximum of 9 chars for organizations, and 11 chars for tenants. | <code>string</code> | ✓ | | <code>0-bootstrap</code> |
|
||||
| [factories_config](variables.tf#L17) | Configuration for YAML-based factories. | <code title="object({ folders_data_path = optional(string, "data/hierarchy") projects_data_path = optional(string, "data/projects") budgets = optional(object({ billing_account = string budgets_data_path = optional(string, "data/budgets") notification_channels = optional(map(any), {}) })) context = optional(object({ folder_ids = optional(map(string), {}) iam_principals = optional(map(string), {}) tag_values = optional(map(string), {}) vpc_host_projects = optional(map(string), {}) }), {}) })">object({…})</code> | | <code>{}</code> | |
|
||||
| [folder_ids](variables-fast.tf#L39) | Folders created in the resource management stage. | <code>map(string)</code> | | <code>{}</code> | <code>1-resman</code> |
|
||||
| [groups](variables-fast.tf#L47) | 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. | <code>map(string)</code> | | <code>{}</code> | <code>0-bootstrap</code> |
|
||||
| [host_project_ids](variables-fast.tf#L56) | Host project for the shared VPC. | <code>map(string)</code> | | <code>{}</code> | <code>2-networking</code> |
|
||||
| [locations](variables-fast.tf#L64) | Optional locations for GCS, BigQuery, and logging buckets created here. | <code title="object({ gcs = optional(string) })">object({…})</code> | | <code>{}</code> | <code>0-bootstrap</code> |
|
||||
| [outputs_location](variables.tf#L39) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | <code>string</code> | | <code>null</code> | |
|
||||
| [service_accounts](variables-fast.tf#L84) | Automation service accounts in name => email format. | <code>map(string)</code> | | <code>{}</code> | <code>1-resman</code> |
|
||||
| [perimeters](variables-fast.tf#L74) | Optional VPC-SC perimeter ids. | <code>map(string)</code> | | <code>{}</code> | <code>1-vpcsc</code> |
|
||||
| [service_accounts](variables-fast.tf#L92) | Automation service accounts in name => email format. | <code>map(string)</code> | | <code>{}</code> | <code>1-resman</code> |
|
||||
| [stage_name](variables.tf#L45) | FAST stage name. Used to separate output files across different factories. | <code>string</code> | | <code>"2-project-factory"</code> | |
|
||||
| [tag_values](variables-fast.tf#L92) | FAST-managed resource manager tag values. | <code>map(string)</code> | | <code>{}</code> | <code>1-resman</code> |
|
||||
| [tag_values](variables-fast.tf#L100) | FAST-managed resource manager tag values. | <code>map(string)</code> | | <code>{}</code> | <code>1-resman</code> |
|
||||
|
||||
## Outputs
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ module "projects" {
|
||||
var.groups,
|
||||
var.factories_config.context.iam_principals
|
||||
)
|
||||
perimeters = var.perimeters
|
||||
tag_values = merge(
|
||||
var.tag_values,
|
||||
var.factories_config.context.tag_values
|
||||
|
||||
@@ -71,6 +71,14 @@ variable "locations" {
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "perimeters" {
|
||||
# tfdoc:variable:source 1-vpcsc
|
||||
description = "Optional VPC-SC perimeter ids."
|
||||
type = map(string)
|
||||
nullable = false
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "prefix" {
|
||||
# tfdoc:variable:source 0-bootstrap
|
||||
description = "Prefix used for resources that need unique names. Use a maximum of 9 chars for organizations, and 11 chars for tenants."
|
||||
|
||||
@@ -494,7 +494,7 @@ service_accounts:
|
||||
|
||||
| name | description | type | required | default |
|
||||
|---|---|:---:|:---:|:---:|
|
||||
| [factories_config](variables.tf#L120) | Path to folder with YAML resource description data files. | <code title="object({ budgets = optional(object({ billing_account = string budgets_data_path = string notification_channels = optional(map(any), {}) })) context = optional(object({ folder_ids = optional(map(string), {}) iam_principals = optional(map(string), {}) tag_values = optional(map(string), {}) vpc_host_projects = optional(map(string), {}) notification_channels = optional(map(string), {}) }), {}) folders_data_path = optional(string) projects_data_path = optional(string) })">object({…})</code> | ✓ | |
|
||||
| [factories_config](variables.tf#L120) | Path to folder with YAML resource description data files. | <code title="object({ budgets = optional(object({ billing_account = string budgets_data_path = string notification_channels = optional(map(any), {}) })) context = optional(object({ folder_ids = optional(map(string), {}) iam_principals = optional(map(string), {}) perimeters = optional(map(string), {}) tag_values = optional(map(string), {}) vpc_host_projects = optional(map(string), {}) notification_channels = optional(map(string), {}) }), {}) folders_data_path = optional(string) projects_data_path = optional(string) })">object({…})</code> | ✓ | |
|
||||
| [data_defaults](variables.tf#L17) | Optional default values used when corresponding project data from files are missing. | <code title="object({ billing_account = optional(string) contacts = optional(map(list(string)), {}) factories_config = optional(object({ custom_roles = optional(string) observability = optional(string) org_policies = optional(string) quotas = optional(string) }), {}) labels = optional(map(string), {}) metric_scopes = optional(list(string), []) parent = optional(string) prefix = optional(string) service_encryption_key_ids = optional(map(list(string)), {}) services = optional(list(string), []) shared_vpc_service_config = optional(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)), {}) }), { host_project = null }) storage_location = optional(string) tag_bindings = optional(map(string), {}) service_accounts = optional(map(object({ display_name = optional(string, "Terraform-managed.") iam_self_roles = optional(list(string)) })), {}) vpc_sc = optional(object({ perimeter_name = string perimeter_bridges = optional(list(string), []) is_dry_run = optional(bool, false) })) logging_data_access = optional(map(object({ ADMIN_READ = optional(object({ exempted_members = optional(list(string)) })), DATA_READ = optional(object({ exempted_members = optional(list(string)) })), DATA_WRITE = optional(object({ exempted_members = optional(list(string)) })) })), {}) })">object({…})</code> | | <code>{}</code> |
|
||||
| [data_merges](variables.tf#L64) | Optional values that will be merged with corresponding data from files. Combines with `data_defaults`, file data, and `data_overrides`. | <code title="object({ contacts = optional(map(list(string)), {}) labels = optional(map(string), {}) metric_scopes = optional(list(string), []) service_encryption_key_ids = optional(map(list(string)), {}) services = optional(list(string), []) tag_bindings = optional(map(string), {}) service_accounts = optional(map(object({ display_name = optional(string, "Terraform-managed.") iam_self_roles = optional(list(string)) })), {}) })">object({…})</code> | | <code>{}</code> |
|
||||
| [data_overrides](variables.tf#L83) | Optional values that override corresponding data from files. Takes precedence over file data and `data_defaults`. | <code title="object({ billing_account = optional(string) contacts = optional(map(list(string))) factories_config = optional(object({ custom_roles = optional(string) observability = optional(string) org_policies = optional(string) quotas = optional(string) }), {}) parent = optional(string) prefix = optional(string) service_encryption_key_ids = optional(map(list(string))) storage_location = optional(string) tag_bindings = optional(map(string)) services = optional(list(string)) service_accounts = optional(map(object({ display_name = optional(string, "Terraform-managed.") iam_self_roles = optional(list(string)) }))) vpc_sc = optional(object({ perimeter_name = string perimeter_bridges = optional(list(string), []) is_dry_run = optional(bool, false) })) logging_data_access = optional(map(object({ ADMIN_READ = optional(object({ exempted_members = optional(list(string)) })), DATA_READ = optional(object({ exempted_members = optional(list(string)) })), DATA_WRITE = optional(object({ exempted_members = optional(list(string)) })) })), {}) })">object({…})</code> | | <code>{}</code> |
|
||||
|
||||
@@ -97,6 +97,7 @@ locals {
|
||||
try(var.data_defaults.service_accounts.display_name, null),
|
||||
"Terraform-managed."
|
||||
)
|
||||
iam = try(opts.iam, {})
|
||||
iam_billing_roles = try(opts.iam_billing_roles, {})
|
||||
iam_organization_roles = try(opts.iam_organization_roles, {})
|
||||
iam_sa_roles = try(opts.iam_sa_roles, {})
|
||||
|
||||
@@ -87,8 +87,20 @@ module "projects" {
|
||||
for k, v in merge(each.value.tag_bindings, var.data_merges.tag_bindings) :
|
||||
k => lookup(var.factories_config.context.tag_values, v, v)
|
||||
}
|
||||
tags = each.value.tags
|
||||
vpc_sc = each.value.vpc_sc
|
||||
tags = each.value.tags
|
||||
vpc_sc = each.value.vpc_sc == null ? null : {
|
||||
perimeter_name = (
|
||||
each.value.vpc_sc.perimeter_name == null
|
||||
? null
|
||||
: lookup(
|
||||
var.factories_config.context.perimeters,
|
||||
each.value.vpc_sc.perimeter_name,
|
||||
each.value.vpc_sc.perimeter_name
|
||||
)
|
||||
)
|
||||
perimeter_bridges = each.value.vpc_sc.perimeter_bridges
|
||||
is_dry_run = each.value.vpc_sc.is_dry_run
|
||||
}
|
||||
}
|
||||
|
||||
module "projects-iam" {
|
||||
@@ -313,6 +325,24 @@ module "service-accounts" {
|
||||
project_id = module.projects[each.value.project].project_id
|
||||
name = each.value.name
|
||||
display_name = each.value.display_name
|
||||
iam = {
|
||||
for k, v in lookup(each.value, "iam", {}) : k => [
|
||||
for vv in v : try(
|
||||
# automation service account (rw)
|
||||
local.context.iam_principals["${each.key}/automation/${vv}"],
|
||||
# automation service account (automation/rw)
|
||||
local.context.iam_principals["${each.key}/${vv}"],
|
||||
# other automation service account (project/automation/rw)
|
||||
local.context.iam_principals[vv],
|
||||
# passthrough + error handling using tonumber until Terraform gets fail/raise function
|
||||
(
|
||||
strcontains(vv, ":")
|
||||
? vv
|
||||
: tonumber("[Error] Invalid member: '${vv}' in project '${each.key}'")
|
||||
)
|
||||
)
|
||||
]
|
||||
}
|
||||
iam_project_roles = merge(
|
||||
{
|
||||
for k, v in each.value.iam_project_roles :
|
||||
|
||||
@@ -200,6 +200,9 @@
|
||||
"display_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"iam": {
|
||||
"$ref": "#/$defs/iam"
|
||||
},
|
||||
"iam_self_roles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@@ -618,4 +621,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,6 +130,7 @@ variable "factories_config" {
|
||||
# TODO: add KMS keys
|
||||
folder_ids = optional(map(string), {})
|
||||
iam_principals = optional(map(string), {})
|
||||
perimeters = optional(map(string), {})
|
||||
tag_values = optional(map(string), {})
|
||||
vpc_host_projects = optional(map(string), {})
|
||||
notification_channels = optional(map(string), {})
|
||||
|
||||
Reference in New Issue
Block a user