Merge branch 'fast-dev'

This commit is contained in:
Ludo
2025-04-18 17:34:08 +02:00
208 changed files with 3735 additions and 696 deletions

View File

@@ -254,7 +254,7 @@ module "cloud_run" {
}
deletion_protection = false
}
# tftest modules=4 resources=56 fixtures=fixtures/shared-vpc.tf inventory=service-vpc-access-connector-create-sharedvpc.yaml e2e
# tftest modules=4 resources=59 fixtures=fixtures/shared-vpc.tf inventory=service-vpc-access-connector-create-sharedvpc.yaml e2e
```
## Using Customer-Managed Encryption Key
@@ -420,7 +420,7 @@ module "cloud_run" {
## Cloud Run Invoker IAM Disable
To disables IAM permission check for `run.routes.invoke` for callers of this service set the `invoker_iam_disabled` variable of the module to `true` (default `false`). There should be no requirement to pass the `roles/run.invoker` to the IAM block to enable public access. This allows for the org policy `domain restricted sharing` org policy remain enabled.
To disables IAM permission check for `run.routes.invoke` for callers of this service set the `invoker_iam_disabled` variable of the module to `true` (default `false`). There should be no requirement to pass the `roles/run.invoker` to the IAM block to enable public access. This allows for the org policy `domain restricted sharing` org policy remain enabled.
```hcl
module "cloud_run" {

View File

@@ -249,7 +249,8 @@ module "addresses" {
| [ipsec_interconnect_addresses](outputs.tf#L41) | Allocated internal addresses for HA VPN over Cloud Interconnect. | |
| [network_attachment_ids](outputs.tf#L49) | IDs of network attachments. | |
| [psa_addresses](outputs.tf#L57) | Allocated internal addresses for PSA endpoints. | |
| [psc_addresses](outputs.tf#L65) | Allocated internal addresses for PSC endpoints. | |
| [psc](outputs.tf#L65) | Allocated resources for PSC endpoints. | |
| [psc_addresses](outputs.tf#L99) | Allocated internal addresses for PSC endpoints. | |
## Fixtures

View File

@@ -62,6 +62,40 @@ output "psa_addresses" {
}
}
output "psc" {
description = "Allocated resources for PSC endpoints."
value = merge(
{
for k, v in local.global_psc :
k => {
address = {
address = google_compute_global_address.psc[k].address
id = google_compute_global_address.psc[k].id
name = google_compute_global_address.psc[k].name
}
forwarding_rule = {
id = try(google_compute_global_forwarding_rule.psc_consumer[k].id, null)
name = try(google_compute_global_forwarding_rule.psc_consumer[k].name, null)
}
}
},
{
for k, v in local.regional_psc :
k => {
address = {
address = google_compute_address.psc[k].address
id = google_compute_address.psc[k].id
name = google_compute_address.psc[k].name
}
forwarding_rule = {
id = try(google_compute_forwarding_rule.psc_consumer[k].id, null)
name = try(google_compute_forwarding_rule.psc_consumer[k].name, null)
}
}
}
)
}
output "psc_addresses" {
description = "Allocated internal addresses for PSC endpoints."
value = merge(

View File

@@ -234,7 +234,25 @@ locals {
try(v.tag_bindings, null),
local.__projects_config.data_defaults.tag_bindings
)
vpc_sc = ( # type: object
tags = {
for tag_name, tag_data in try(v.tags, {}) : tag_name => {
description = try(tag_data.description, "Managed by the Terraform project-factory module.")
id = try(tag_data.id, null)
iam = try(tag_data.iam, {})
iam_bindings = try(tag_data.iam_bindings, {})
iam_bindings_additive = try(tag_data.iam_bindings_additive, {})
values = {
for value_name, value_data in try(tag_data.values, {}) : value_name => {
description = try(value_data.description, "Managed by the Terraform project-factory module.")
id = try(value_data.id, null)
iam = try(value_data.iam, {})
iam_bindings = try(value_data.iam_bindings, {})
iam_bindings_additive = try(value_data.iam_bindings_additive, {})
}
}
}
}
vpc_sc = (
local.__projects_config.data_overrides.vpc_sc != null
? local.__projects_config.data_overrides.vpc_sc
: (
@@ -255,10 +273,10 @@ locals {
})
}
# tflint-ignore: terraform_unused_declarations
_projects_uniqunees_validation = {
_projects_uniqueness_validation = {
# will raise error, if the same project (derived from file name, or provided in the YAML file)
# is sued more than once
# is used more than once
for k, v in local._projects_output :
"${v.prefix}-${v.name}" => k
"${v.prefix != null ? v.prefix : ""}-${v.name}" => k
}
}

View File

@@ -95,10 +95,17 @@ module "vpc" {
flow_sampling = 0.5
aggregation_interval = "INTERVAL_10_MIN"
}
},
# hybrid subnet
{
name = "hybrid"
region = "europe-west1"
ip_cidr_range = "10.0.4.0/24"
allow_subnet_cidr_routes_overlap = true
}
]
}
# tftest modules=1 resources=8 inventory=subnet-options.yaml e2e
# tftest modules=1 resources=9 inventory=subnet-options.yaml e2e
```
### Subnet IAM

View File

@@ -17,6 +17,9 @@
"enable_private_access": {
"type": "boolean"
},
"allow_subnet_cidr_routes_overlap": {
"type": "boolean"
},
"flow_logs_config": {
"type": "object",
"additionalProperties": false,

View File

@@ -196,8 +196,8 @@ Interpolations leverage contexts from two separate sources: an internal set for
The following table lists the available context interpolations. External contexts are passed in via the `factories_config.contexts` variable. IAM principals are interpolated in all IAM attributes except `iam_by_principal`. First two columns show for which attribute of which resource context is interpolated. `external contexts` column show in which map passed as `var.factories_config.context` key will be looked up.
* Internally created folders creates keys under `${folder_name_1}[/${folder_name_2}/${folder_name_3}]`
* IAM principals are resolved within context of managed project or use `${project}/${service_account}` to refer service account from other projects managed by the same project factory instance.
- Internally created folders creates keys under `${folder_name_1}[/${folder_name_2}/${folder_name_3}]`
- IAM principals are resolved within context of managed project or use `${project}/${service_account}` to refer service account from other projects managed by the same project factory instance.
| resource | attribute | external contexts | internal contexts |
|---------------------|-----------------|---------------------|------------------------------------|
@@ -378,6 +378,16 @@ shared_vpc_service_config:
- container-engine
billing_budgets:
- test-100
tags:
my-tag-key-1:
values:
my-value-1:
description: My value 1
my-value-2:
description: My value 3
iam:
roles/resourcemanager.tagUser:
- user:user@example.com
# tftest-file id=6 path=data/projects/dev-ta-app0-be.yaml schema=project.schema.json
```
@@ -390,9 +400,9 @@ services:
- storage.googleapis.com
iam:
"roles/owner":
- rw
- automation/rw
"roles/viewer":
- ro
- automation/ro
shared_vpc_host_config:
enabled: true
automation:
@@ -406,12 +416,12 @@ automation:
description: Team B app 0 Terraform state bucket.
iam:
roles/storage.objectCreator:
- rw
- automation/rw
roles/storage.objectViewer:
- gcp-devops
- group:team-b-admins@example.org
- rw
- ro
- automation/rw
- automation/ro
# tftest-file id=7 path=data/projects/dev-tb-app0-0.yaml schema=project.schema.json
```
@@ -484,7 +494,7 @@ service_accounts:
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [factories_config](variables.tf#L121) | Path to folder with YAML resource description data files. | <code title="object&#40;&#123;&#10; budgets &#61; optional&#40;object&#40;&#123;&#10; billing_account &#61; string&#10; budgets_data_path &#61; string&#10; notification_channels &#61; optional&#40;map&#40;any&#41;, &#123;&#125;&#41;&#10; &#125;&#41;&#41;&#10; context &#61; optional&#40;object&#40;&#123;&#10; folder_ids &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; iam_principals &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; tag_values &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; vpc_host_projects &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; notification_channels &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; folders_data_path &#61; optional&#40;string&#41;&#10; projects_data_path &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | |
| [factories_config](variables.tf#L121) | Path to folder with YAML resource description data files. | <code title="object&#40;&#123;&#10; budgets &#61; optional&#40;object&#40;&#123;&#10; billing_account &#61; string&#10; budgets_data_path &#61; string&#10; notification_channels &#61; optional&#40;map&#40;any&#41;, &#123;&#125;&#41;&#10; &#125;&#41;&#41;&#10; context &#61; optional&#40;object&#40;&#123;&#10; folder_ids &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; iam_principals &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; perimeters &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; tag_values &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; vpc_host_projects &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; notification_channels &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; folders_data_path &#61; optional&#40;string&#41;&#10; projects_data_path &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | |
| [data_defaults](variables.tf#L17) | Optional default values used when corresponding project data from files are missing. | <code title="object&#40;&#123;&#10; billing_account &#61; optional&#40;string&#41;&#10; contacts &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; factories_config &#61; optional&#40;object&#40;&#123;&#10; custom_roles &#61; optional&#40;string&#41;&#10; observability &#61; optional&#40;string&#41;&#10; org_policies &#61; optional&#40;string&#41;&#10; quotas &#61; optional&#40;string&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; labels &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; metric_scopes &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; parent &#61; optional&#40;string&#41;&#10; prefix &#61; optional&#40;string&#41;&#10; service_encryption_key_ids &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; services &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; shared_vpc_service_config &#61; optional&#40;object&#40;&#123;&#10; host_project &#61; string&#10; network_users &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; service_agent_iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; service_agent_subnet_iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; service_iam_grants &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; network_subnet_users &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; &#125;&#41;, &#123; host_project &#61; null &#125;&#41;&#10; storage_location &#61; optional&#40;string&#41;&#10; tag_bindings &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; service_accounts &#61; optional&#40;map&#40;object&#40;&#123;&#10; display_name &#61; optional&#40;string, &#34;Terraform-managed.&#34;&#41;&#10; iam_self_roles &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; vpc_sc &#61; optional&#40;object&#40;&#123;&#10; perimeter_name &#61; string&#10; perimeter_bridges &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; is_dry_run &#61; optional&#40;bool, false&#41;&#10; &#125;&#41;&#41;&#10; logging_data_access &#61; optional&#40;map&#40;object&#40;&#123;&#10; ADMIN_READ &#61; optional&#40;object&#40;&#123; exempted_members &#61; optional&#40;list&#40;string&#41;&#41; &#125;&#41;&#41;,&#10; DATA_READ &#61; optional&#40;object&#40;&#123; exempted_members &#61; optional&#40;list&#40;string&#41;&#41; &#125;&#41;&#41;,&#10; DATA_WRITE &#61; optional&#40;object&#40;&#123; exempted_members &#61; optional&#40;list&#40;string&#41;&#41; &#125;&#41;&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</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&#40;&#123;&#10; contacts &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; labels &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; metric_scopes &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; service_encryption_key_ids &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; services &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; tag_bindings &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; service_accounts &#61; optional&#40;map&#40;object&#40;&#123;&#10; display_name &#61; optional&#40;string, &#34;Terraform-managed.&#34;&#41;&#10; iam_self_roles &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</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&#40;&#123;&#10; billing_account &#61; optional&#40;string&#41;&#10; contacts &#61; optional&#40;map&#40;list&#40;string&#41;&#41;&#41;&#10; factories_config &#61; optional&#40;object&#40;&#123;&#10; custom_roles &#61; optional&#40;string&#41;&#10; observability &#61; optional&#40;string&#41;&#10; org_policies &#61; optional&#40;string&#41;&#10; quotas &#61; optional&#40;string&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; parent &#61; optional&#40;string&#41;&#10; prefix &#61; optional&#40;string&#41;&#10; service_encryption_key_ids &#61; optional&#40;map&#40;list&#40;string&#41;&#41;&#41;&#10; storage_location &#61; optional&#40;string&#41;&#10; tag_bindings &#61; optional&#40;map&#40;string&#41;&#41;&#10; services &#61; optional&#40;list&#40;string&#41;&#41;&#10; service_accounts &#61; optional&#40;map&#40;object&#40;&#123;&#10; display_name &#61; optional&#40;string, &#34;Terraform-managed.&#34;&#41;&#10; iam_self_roles &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#41;&#10; vpc_sc &#61; optional&#40;object&#40;&#123;&#10; perimeter_name &#61; string&#10; perimeter_bridges &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; is_dry_run &#61; optional&#40;bool, false&#41;&#10; &#125;&#41;&#41;&#10; logging_data_access &#61; optional&#40;map&#40;object&#40;&#123;&#10; ADMIN_READ &#61; optional&#40;object&#40;&#123; exempted_members &#61; optional&#40;list&#40;string&#41;&#41; &#125;&#41;&#41;,&#10; DATA_READ &#61; optional&#40;object&#40;&#123; exempted_members &#61; optional&#40;list&#40;string&#41;&#41; &#125;&#41;&#41;,&#10; DATA_WRITE &#61; optional&#40;object&#40;&#123; exempted_members &#61; optional&#40;list&#40;string&#41;&#41; &#125;&#41;&#41;&#10; &#125;&#41;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |

View File

@@ -22,6 +22,7 @@ locals {
k => merge(try(v.automation.bucket, {}), {
automation_project = v.automation.project
prefix = v.prefix
project_name = v.name
}) if try(v.automation.bucket, null) != null
}
automation_sa = flatten([
@@ -31,6 +32,7 @@ locals {
name = ks
prefix = v.prefix
project = k
project_name = v.name
})
]
])
@@ -43,7 +45,7 @@ module "automation-bucket" {
# from the IAM dependency in the outputs of the main project
project_id = each.value.automation_project
prefix = each.value.prefix
name = "${each.key}-tf-state"
name = "${each.value.project_name}-tf-state"
encryption_key = lookup(each.value, "encryption_key", null)
iam = {
for k, v in lookup(each.value, "iam", {}) : k => [
@@ -58,9 +60,20 @@ module "automation-bucket" {
for k, v in lookup(each.value, "iam_bindings", {}) : k => merge(v, {
members = [
for vv in v.members : try(
# rw (infer local project and automation prefix)
module.automation-service-accounts["${each.key}/automation/${vv}"].iam_email,
# automation/rw or sa (infer local project)
module.automation-service-accounts["${each.key}/${vv}"].iam_email,
# project/automation/rw project/sa
var.factories_config.context.iam_principals[vv],
vv
# fully specified principal
vv,
# passthrough + error handling using tonumber until Terraform gets fail/raise function
(
strcontains(vv, ":")
? vv
: tonumber("[Error] Invalid member: '${vv}' in automation bucket '${each.key}'")
)
)
]
})
@@ -94,19 +107,20 @@ module "automation-bucket" {
module "automation-service-accounts" {
source = "../iam-service-account"
for_each = {
for k in local.automation_sa : "${k.project}/${k.name}" => k
for k in local.automation_sa : "${k.project}/automation/${k.name}" => k
}
# we cannot use interpolation here as we would get a cycle
# from the IAM dependency in the outputs of the main project
project_id = each.value.automation_project
prefix = each.value.prefix
name = "${each.value.project}-${each.value.name}"
name = "${each.value.project_name}-${each.value.name}"
description = lookup(each.value, "description", null)
display_name = lookup(
each.value,
"display_name",
"Service account ${each.value.name} for ${each.value.project}."
)
# TODO: also support short form for service accounts in this project
iam = {
for k, v in lookup(each.value, "iam", {}) : k => [
for vv in v : lookup(

View File

@@ -234,7 +234,25 @@ locals {
try(v.tag_bindings, null),
local.__projects_config.data_defaults.tag_bindings
)
vpc_sc = ( # type: object
tags = {
for tag_name, tag_data in try(v.tags, {}) : tag_name => {
description = try(tag_data.description, "Managed by the Terraform project-factory module.")
id = try(tag_data.id, null)
iam = try(tag_data.iam, {})
iam_bindings = try(tag_data.iam_bindings, {})
iam_bindings_additive = try(tag_data.iam_bindings_additive, {})
values = {
for value_name, value_data in try(tag_data.values, {}) : value_name => {
description = try(value_data.description, "Managed by the Terraform project-factory module.")
id = try(value_data.id, null)
iam = try(value_data.iam, {})
iam_bindings = try(value_data.iam_bindings, {})
iam_bindings_additive = try(value_data.iam_bindings_additive, {})
}
}
}
}
vpc_sc = (
local.__projects_config.data_overrides.vpc_sc != null
? local.__projects_config.data_overrides.vpc_sc
: (
@@ -255,10 +273,10 @@ locals {
})
}
# tflint-ignore: terraform_unused_declarations
_projects_uniqunees_validation = {
_projects_uniqueness_validation = {
# will raise error, if the same project (derived from file name, or provided in the YAML file)
# is sued more than once
# is used more than once
for k, v in local._projects_output :
"${v.prefix}-${v.name}" => k
"${v.prefix != null ? v.prefix : ""}-${v.name}" => k
}
}

View File

@@ -23,8 +23,7 @@ locals {
trimsuffix(f, ".yaml") => merge(
{ parent = dirname(f) == "." ? "default" : dirname(f) },
yamldecode(file("${local._folders_path}/${f}"))
)
if !endswith(f, "/_config.yaml")
) if !endswith(f, "/_config.yaml")
}
)
_project_path = try(pathexpand(var.factories_config.projects_data_path), null)
@@ -32,8 +31,10 @@ locals {
for f in try(fileset(local._project_path, "**/*.yaml"), []) :
trimsuffix(f, ".yaml") => yamldecode(file("${local._project_path}/${f}"))
}
_projects_input = merge(local._hierarchy_projects_full_path, local._projects_full_path)
_projects_input = merge(
local._hierarchy_projects_full_path,
local._projects_full_path
)
_project_budgets = flatten([
for k, v in local._projects_input : [
for b in try(v.billing_budgets, []) : {
@@ -55,7 +56,6 @@ locals {
project_budgets = {
for v in local._project_budgets : v.budget => v.project...
}
buckets = flatten([
for k, v in local.projects : [
for name, opts in v.buckets : {
@@ -98,6 +98,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, {})

View File

@@ -87,7 +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)
}
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" {
@@ -105,16 +118,22 @@ module "projects-iam" {
iam = {
for k, v in lookup(each.value, "iam", {}) : k => [
for vv in v : try(
# project service accounts
# project service accounts (sa)
module.service-accounts["${each.key}/${vv}"].iam_email,
# automation service account
# 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 projects service accounts
# other projects service accounts (project/sa)
module.service-accounts[vv].iam_email,
# other automation service account
# 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}'")
(
strcontains(vv, ":")
? vv
: tonumber("[Error] Invalid member: '${vv}' in project '${each.key}'")
)
)
]
}
@@ -122,16 +141,22 @@ module "projects-iam" {
for k, v in lookup(each.value, "iam_bindings", {}) : k => merge(v, {
members = [
for vv in v.members : try(
# project service accounts
# project service accounts (sa)
module.service-accounts["${each.key}/${vv}"].iam_email,
# automation service account
# 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 projects service accounts
# other projects service accounts (project/sa)
module.service-accounts[vv].iam_email,
# other automation service account
# 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}'")
(
strcontains(vv, ":")
? vv
: tonumber("[Error] Invalid member: '${vv}' in project '${each.key}'")
)
)
]
})
@@ -139,16 +164,22 @@ module "projects-iam" {
iam_bindings_additive = {
for k, v in lookup(each.value, "iam_bindings_additive", {}) : k => merge(v, {
member = try(
# project service accounts
# project service accounts (sa)
module.service-accounts["${each.key}/${v.member}"].iam_email,
# automation service account
# 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
# other projects service accounts (project/sa)
module.service-accounts[v.member].iam_email,
# other automation service account
# other automation service account (project/automation/rw)
local.context.iam_principals[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}'")
(
strcontains(v.member, ":")
? v.member
: tonumber("[Error] Invalid member: '${v.member}' in project '${each.key}'")
)
)
})
}
@@ -163,32 +194,38 @@ module "projects-iam" {
try(each.value.shared_vpc_service_config.host_project, null) == null
? null
: merge(each.value.shared_vpc_service_config, {
host_project = lookup(
var.factories_config.context.vpc_host_projects,
each.value.shared_vpc_service_config.host_project,
host_project = try(
var.factories_config.context.vpc_host_projects[each.value.shared_vpc_service_config.host_project],
module.projects[each.value.shared_vpc_service_config.host_project].project_id,
each.value.shared_vpc_service_config.host_project
)
network_users = [
for v in try(each.value.shared_vpc_service_config.network_users, []) :
for vv in try(each.value.shared_vpc_service_config.network_users, []) :
try(
# project service accounts
module.service-accounts["${each.key}/${v}"].iam_email,
# automation service account
local.context.iam_principals["${each.key}/${v}"],
# other projects service accounts
module.service-accounts[v].iam_email,
# other automation service account
local.context.iam_principals[v],
# project service accounts (sa)
module.service-accounts["${each.key}/${vv}"].iam_email,
# 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 projects service accounts (project/sa)
module.service-accounts[vv].iam_email,
# other automation service account (project/automation/rw)
local.context.iam_principals[vv],
# passthrough + error handling using tonumber until Terraform gets fail/raise function
strcontains(v, ":") ? v : tonumber("[Error] Invalid member: '${v}' in project '${each.key}'")
(
strcontains(vv, ":")
? vv
: tonumber("[Error] Invalid member: '${vv}' in project '${each.key}'")
)
)
]
# TODO: network subnet users
})
)
# add service agents config, so Service Agents can be referred in the IAM grants
service_agents_config = {
grant_default_roles = false # Default roles are granted in module.project
# default roles are granted in module.project
grant_default_roles = false
}
}
@@ -204,16 +241,22 @@ module "buckets" {
iam = {
for k, v in each.value.iam : k => [
for vv in v : try(
# project service accounts
# project service accounts (sa)
module.service-accounts["${each.value.project_key}/${vv}"].iam_email,
# automation service account
# automation service account (rw)
local.context.iam_principals["${each.value.project_key}/automation/${vv}"],
# automation service account (automation/rw)
local.context.iam_principals["${each.value.project_key}/${vv}"],
# other projects service accounts
# other projects service accounts (project/sa)
module.service-accounts[vv].iam_email,
# other automation service account
# 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}' for bucket '${each.key}' in project '${each.value.project}'")
(
strcontains(vv, ":")
? vv
: tonumber("[Error] Invalid member: '${vv}' in project '${each.value.project_key}'")
)
)
]
}
@@ -221,16 +264,22 @@ module "buckets" {
for k, v in each.value.iam_bindings : k => merge(v, {
members = [
for vv in v.members : try(
# project service accounts
# project service accounts (sa)
module.service-accounts["${each.value.project}/${vv}"].iam_email,
# automation service account
# automation service account (rw)
local.context.iam_principals["${each.value.project}/automation/${vv}"],
# automation service account (automation/rw)
local.context.iam_principals["${each.value.project}/${vv}"],
# other projects service accounts
# other projects service accounts (project/sa)
module.service-accounts[vv].iam_email,
# other automation service account
# 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}' for bucket '${each.key}' in project '${each.value.project}'")
(
strcontains(vv, ":")
? vv
: tonumber("[Error] Invalid member: '${vv}' in project '${each.value.project}'")
)
)
]
})
@@ -238,16 +287,22 @@ module "buckets" {
iam_bindings_additive = {
for k, v in each.value.iam_bindings_additive : k => merge(v, {
member = try(
# project service accounts
# project service accounts (sa)
module.service-accounts["${each.value.project}/${v.member}"].iam_email,
# automation service account
# automation service account (rw)
local.context.iam_principals["${each.value.project}/automation/${v.member}"],
# automation service account (automation/rw)
local.context.iam_principals["${each.value.project}/${v.member}"],
# other projects service accounts
# other projects service accounts (project/sa)
module.service-accounts[v.member].iam_email,
# other automation service account
# other automation service account (project/automation/rw)
local.context.iam_principals[v.member],
# passthrough + error handling using tonumber until Terraform gets fail/raise function
strcontains(v.member, ":") ? v.member : tonumber("[Error] Invalid member: '${v.member}' for bucket '${each.key}' in project '${each.value.project}'")
(
strcontains(v.member, ":")
? v.member
: tonumber("[Error] Invalid member: '${v.member}' in project '${each.value.project}'")
)
)
})
}
@@ -270,6 +325,24 @@ module "service-accounts" {
project_id = module.projects[each.value.project_key].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 :

View File

@@ -200,6 +200,9 @@
"display_name": {
"type": "string"
},
"iam": {
"$ref": "#/$defs/iam"
},
"iam_self_roles": {
"type": "array",
"items": {
@@ -319,6 +322,54 @@
}
}
},
"tags": {
"type": "object",
"additionalProperties": {
"type": "object",
"additionalProperties": false,
"properties": {
"description": {
"type": "string"
},
"iam": {
"$ref": "#/$defs/iam"
},
"iam_bindings": {
"$ref": "#/$defs/iam_bindings"
},
"iam_bindings_additive": {
"$ref": "#/$defs/iam_bindings_additive"
},
"id": {
"type": "string"
},
"values": {
"type": "object",
"additionalProperties": {
"type": "object",
"additionalProperties": false,
"properties": {
"description": {
"type": "string"
},
"iam": {
"$ref": "#/$defs/iam"
},
"iam_bindings": {
"$ref": "#/$defs/iam_bindings"
},
"iam_bindings_additive": {
"$ref": "#/$defs/iam_bindings_additive"
},
"id": {
"type": "string"
}
}
}
}
}
}
},
"vpc_sc": {
"type": "object",
"additionalItems": false,

View File

@@ -131,6 +131,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), {})

View File

@@ -37,9 +37,16 @@ locals {
for agent in lookup(local._service_agents_by_api, api, []) :
(agent.name) => merge(agent, {
email = (
var.universe == null || api != "cloudservices"
? templatestring(agent.identity, { project_number = local.project.number, universe_domain = local._universe_domain })
: format("%s@cloudservices.%siam.gserviceaccount.com", local.project.number, local._universe_domain)
# If universe variable is set, enfore the use of the service-PROJECT_NUMBER@gcp-sa-ekms.UNVIVERSE-system.iam.gserviceaccount.com
# instead of service-PROJECT_NUMBER@gcp-sa-kms.UNVIVERSE-system.iam.gserviceaccount.com
# as in the TPC universes, the partner KMS is enforced by design
var.universe != null && api == "cloudkms.googleapis.com"
? format("service-%s@gcp-sa-ekms.%siam.gserviceaccount.com", local.project.number, local._universe_domain)
: (
var.universe == null || api != "cloudservices"
? templatestring(agent.identity, { project_number = local.project.number, universe_domain = local._universe_domain })
: format("%s@cloudservices.%siam.gserviceaccount.com", local.project.number, local._universe_domain)
)
)
})
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2023 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.

View File

@@ -209,7 +209,7 @@ module "test" {
r1 = {
status = {
access_levels = ["a1", "a2"]
resources = ["projects/11111", "projects/111111"]
resources = ["projects/1111", "projects/2222"]
restricted_services = ["storage.googleapis.com"]
egress_policies = ["gcs-sa-foo"]
ingress_policies = ["sa-tf-test", "sa-roles"]
@@ -226,11 +226,11 @@ module "test" {
## Factories
This module implements support for three distinct factories, used to create and manage access levels, egress policies and ingress policies via YAML files. The YAML files syntax is a 1:1 match for the corresponding variables, and the factory data is merged at runtime with any data set in variables, which take precedence in case of key overlaps.
This module implements support for five distinct factories, used to create and manage perimeters, bridges, access levels, egress policies, and ingress policies via YAML files.
JSON Schema files for each factory object are available in the [`schemas`](./schemas/) folder, and can be used to validate input YAML data with [`validate-yaml`](https://github.com/gerald1248/validate-yaml) or any of the available tools and libraries.
This is an example that uses all three factories. Note that the factory configuration points to folders, where each file represents one resource.
This is an example that uses only three factories and leave perimeter management in tfvars. Note that the factory configuration points to folders, where each file represents one resource.
```hcl
module "test" {
@@ -240,12 +240,18 @@ module "test" {
access_levels = "data/access-levels"
egress_policies = "data/egress-policies"
ingress_policies = "data/ingress-policies"
context = {
resource_sets = {
foo_projects = ["projects/321", "projects/654"]
}
}
}
service_perimeters_regular = {
r1 = {
perimeter-north = {
description = "Main perimeter"
status = {
access_levels = ["geo-it", "identity-user1"]
resources = ["projects/11111", "projects/111111"]
resources = ["projects/1111", "projects/2222"]
restricted_services = ["storage.googleapis.com"]
egress_policies = ["gcs-sa-foo"]
ingress_policies = ["sa-tf-test-geo", "sa-tf-test"]
@@ -319,9 +325,55 @@ to:
- service_name: "*"
resources:
- projects/1234567890
- foo_projects
# tftest-file id=i2 path=data/ingress-policies/sa-tf-test-geo.yaml schema=ingress-policy.schema.json
```
But perimeters could also defined in a yaml file.
```hcl
module "test" {
source = "./fabric/modules/vpc-sc"
access_policy = "12345678"
factories_config = {
access_levels = "data/access-levels"
egress_policies = "data/egress-policies"
ingress_policies = "data/ingress-policies"
perimeters = "data/perimeters"
context = {
resource_sets = {
foo_projects = ["projects/321", "projects/654"]
}
}
}
}
# tftest modules=1 resources=3 files=a1,a2,e1,i1,i2,r1 inventory=factory.yaml
```
```yaml
description: Main perimeter
status:
access_levels:
- "geo-it"
- "identity-user1"
resources:
- "projects/1111"
- "projects/2222"
restricted_services:
- "storage.googleapis.com"
egress_policies:
- "gcs-sa-foo"
ingress_policies:
- "sa-tf-test-geo"
- "sa-tf-test"
vpc_accessible_services:
allowed_services:
- "storage.googleapis.com"
enable_restriction: yes
# tftest-file id=r1 path=data/perimeters/perimeter-north.yaml schema=perimeters.schema.json
````
## Notes
- To remove an access level, first remove the binding between perimeter and the access level in `status` and/or `spec` without removing the access level itself. Once you have run `terraform apply`, you'll then be able to remove the access level and run `terraform apply` again.
@@ -350,17 +402,17 @@ to:
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [access_policy](variables.tf#L67) | Access Policy name, set to null if creating one. | <code>string</code> | ✓ | |
| [access_levels](variables.tf#L17) | Access level definitions. | <code title="map&#40;object&#40;&#123;&#10; combining_function &#61; optional&#40;string&#41;&#10; conditions &#61; optional&#40;list&#40;object&#40;&#123;&#10; device_policy &#61; optional&#40;object&#40;&#123;&#10; allowed_device_management_levels &#61; optional&#40;list&#40;string&#41;&#41;&#10; allowed_encryption_statuses &#61; optional&#40;list&#40;string&#41;&#41;&#10; require_admin_approval &#61; bool&#10; require_corp_owned &#61; bool&#10; require_screen_lock &#61; optional&#40;bool&#41;&#10; os_constraints &#61; optional&#40;list&#40;object&#40;&#123;&#10; os_type &#61; string&#10; minimum_version &#61; optional&#40;string&#41;&#10; require_verified_chrome_os &#61; optional&#40;bool&#41;&#10; &#125;&#41;&#41;&#41;&#10; &#125;&#41;&#41;&#10; ip_subnetworks &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; members &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; negate &#61; optional&#40;bool&#41;&#10; regions &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; required_access_levels &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; vpc_subnets &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10; description &#61; optional&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [access_policy_create](variables.tf#L72) | Access Policy configuration, fill in to create. Parent is in 'organizations/123456' format, scopes are in 'folders/456789' or 'projects/project_id' format. | <code title="object&#40;&#123;&#10; parent &#61; string&#10; title &#61; string&#10; scopes &#61; optional&#40;list&#40;string&#41;, null&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [egress_policies](variables.tf#L82) | Egress policy definitions that can be referenced in perimeters. | <code title="map&#40;object&#40;&#123;&#10; title &#61; optional&#40;string&#41;&#10; from &#61; object&#40;&#123;&#10; access_levels &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; identity_type &#61; optional&#40;string&#41;&#10; identities &#61; optional&#40;list&#40;string&#41;&#41;&#10; resources &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; &#125;&#41;&#10; to &#61; object&#40;&#123;&#10; external_resources &#61; optional&#40;list&#40;string&#41;&#41;&#10; operations &#61; optional&#40;list&#40;object&#40;&#123;&#10; method_selectors &#61; optional&#40;list&#40;string&#41;&#41;&#10; permission_selectors &#61; optional&#40;list&#40;string&#41;&#41;&#10; service_name &#61; string&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10; resources &#61; optional&#40;list&#40;string&#41;&#41;&#10; roles &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [factories_config](variables.tf#L125) | Paths to folders that enable factory functionality. | <code title="object&#40;&#123;&#10; access_levels &#61; optional&#40;string&#41;&#10; egress_policies &#61; optional&#40;string&#41;&#10; ingress_policies &#61; optional&#40;string&#41;&#10; restricted_services &#61; optional&#40;string, &#34;data&#47;restricted-services.yaml&#34;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam](variables.tf#L137) | IAM bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_bindings](variables.tf#L143) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | <code title="map&#40;object&#40;&#123;&#10; members &#61; list&#40;string&#41;&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_bindings_additive](variables.tf#L158) | Individual additive IAM bindings. Keys are arbitrary. | <code title="map&#40;object&#40;&#123;&#10; member &#61; string&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [ingress_policies](variables.tf#L173) | Ingress policy definitions that can be referenced in perimeters. | <code title="map&#40;object&#40;&#123;&#10; title &#61; optional&#40;string&#41;&#10; from &#61; object&#40;&#123;&#10; access_levels &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; identity_type &#61; optional&#40;string&#41;&#10; identities &#61; optional&#40;list&#40;string&#41;&#41;&#10; resources &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; &#125;&#41;&#10; to &#61; object&#40;&#123;&#10; operations &#61; optional&#40;list&#40;object&#40;&#123;&#10; method_selectors &#61; optional&#40;list&#40;string&#41;&#41;&#10; permission_selectors &#61; optional&#40;list&#40;string&#41;&#41;&#10; service_name &#61; string&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10; resources &#61; optional&#40;list&#40;string&#41;&#41;&#10; roles &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [service_perimeters_bridge](variables.tf#L215) | Bridge service perimeters. | <code title="map&#40;object&#40;&#123;&#10; spec_resources &#61; optional&#40;list&#40;string&#41;&#41;&#10; status_resources &#61; optional&#40;list&#40;string&#41;&#41;&#10; use_explicit_dry_run_spec &#61; optional&#40;bool, false&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [service_perimeters_regular](variables.tf#L225) | Regular service perimeters. | <code title="map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; spec &#61; optional&#40;object&#40;&#123;&#10; access_levels &#61; optional&#40;list&#40;string&#41;&#41;&#10; resources &#61; optional&#40;list&#40;string&#41;&#41;&#10; restricted_services &#61; optional&#40;list&#40;string&#41;&#41;&#10; egress_policies &#61; optional&#40;list&#40;string&#41;&#41;&#10; ingress_policies &#61; optional&#40;list&#40;string&#41;&#41;&#10; vpc_accessible_services &#61; optional&#40;object&#40;&#123;&#10; allowed_services &#61; list&#40;string&#41;&#10; enable_restriction &#61; optional&#40;bool, true&#41;&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#41;&#10; status &#61; optional&#40;object&#40;&#123;&#10; access_levels &#61; optional&#40;list&#40;string&#41;&#41;&#10; resources &#61; optional&#40;list&#40;string&#41;&#41;&#10; restricted_services &#61; optional&#40;list&#40;string&#41;&#41;&#10; egress_policies &#61; optional&#40;list&#40;string&#41;&#41;&#10; ingress_policies &#61; optional&#40;list&#40;string&#41;&#41;&#10; vpc_accessible_services &#61; optional&#40;object&#40;&#123;&#10; allowed_services &#61; list&#40;string&#41;&#10; enable_restriction &#61; bool&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#41;&#10; use_explicit_dry_run_spec &#61; optional&#40;bool, false&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [access_policy](variables.tf#L68) | Access Policy name, set to null if creating one. | <code>string</code> | ✓ | |
| [access_levels](variables.tf#L17) | Access level definitions. | <code title="map&#40;object&#40;&#123;&#10; combining_function &#61; optional&#40;string&#41;&#10; conditions &#61; optional&#40;list&#40;object&#40;&#123;&#10; device_policy &#61; optional&#40;object&#40;&#123;&#10; allowed_device_management_levels &#61; optional&#40;list&#40;string&#41;&#41;&#10; allowed_encryption_statuses &#61; optional&#40;list&#40;string&#41;&#41;&#10; require_admin_approval &#61; bool&#10; require_corp_owned &#61; bool&#10; require_screen_lock &#61; optional&#40;bool&#41;&#10; os_constraints &#61; optional&#40;list&#40;object&#40;&#123;&#10; os_type &#61; string&#10; minimum_version &#61; optional&#40;string&#41;&#10; require_verified_chrome_os &#61; optional&#40;bool&#41;&#10; &#125;&#41;&#41;&#41;&#10; &#125;&#41;&#41;&#10; ip_subnetworks &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; members &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; negate &#61; optional&#40;bool&#41;&#10; regions &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; required_access_levels &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; vpc_subnets &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10; description &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [access_policy_create](variables.tf#L73) | Access Policy configuration, fill in to create. Parent is in 'organizations/123456' format, scopes are in 'folders/456789' or 'projects/project_id' format. | <code title="object&#40;&#123;&#10; parent &#61; string&#10; title &#61; string&#10; scopes &#61; optional&#40;list&#40;string&#41;, null&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [egress_policies](variables.tf#L83) | Egress policy definitions that can be referenced in perimeters. | <code title="map&#40;object&#40;&#123;&#10; title &#61; optional&#40;string&#41;&#10; from &#61; object&#40;&#123;&#10; access_levels &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; identity_type &#61; optional&#40;string&#41;&#10; identities &#61; optional&#40;list&#40;string&#41;&#41;&#10; resources &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; &#125;&#41;&#10; to &#61; object&#40;&#123;&#10; external_resources &#61; optional&#40;list&#40;string&#41;&#41;&#10; operations &#61; optional&#40;list&#40;object&#40;&#123;&#10; method_selectors &#61; optional&#40;list&#40;string&#41;&#41;&#10; permission_selectors &#61; optional&#40;list&#40;string&#41;&#41;&#10; service_name &#61; string&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10; resources &#61; optional&#40;list&#40;string&#41;&#41;&#10; roles &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [factories_config](variables.tf#L126) | Paths to folders that enable factory functionality. | <code title="object&#40;&#123;&#10; access_levels &#61; optional&#40;string&#41;&#10; bridges &#61; optional&#40;string&#41;&#10; egress_policies &#61; optional&#40;string&#41;&#10; ingress_policies &#61; optional&#40;string&#41;&#10; perimeters &#61; optional&#40;string&#41;&#10; context &#61; optional&#40;object&#40;&#123;&#10; resource_sets &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; service_sets &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; identity_sets &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam](variables.tf#L144) | IAM bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_bindings](variables.tf#L150) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | <code title="map&#40;object&#40;&#123;&#10; members &#61; list&#40;string&#41;&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_bindings_additive](variables.tf#L165) | Individual additive IAM bindings. Keys are arbitrary. | <code title="map&#40;object&#40;&#123;&#10; member &#61; string&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [ingress_policies](variables.tf#L180) | Ingress policy definitions that can be referenced in perimeters. | <code title="map&#40;object&#40;&#123;&#10; title &#61; optional&#40;string&#41;&#10; from &#61; object&#40;&#123;&#10; access_levels &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; identity_type &#61; optional&#40;string&#41;&#10; identities &#61; optional&#40;list&#40;string&#41;&#41;&#10; resources &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; &#125;&#41;&#10; to &#61; object&#40;&#123;&#10; operations &#61; optional&#40;list&#40;object&#40;&#123;&#10; method_selectors &#61; optional&#40;list&#40;string&#41;&#41;&#10; permission_selectors &#61; optional&#40;list&#40;string&#41;&#41;&#10; service_name &#61; string&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10; resources &#61; optional&#40;list&#40;string&#41;&#41;&#10; roles &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [service_perimeters_bridge](variables.tf#L222) | Bridge service perimeters. | <code title="map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; spec_resources &#61; optional&#40;list&#40;string&#41;&#41;&#10; status_resources &#61; optional&#40;list&#40;string&#41;&#41;&#10; use_explicit_dry_run_spec &#61; optional&#40;bool, false&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [service_perimeters_regular](variables.tf#L234) | Regular service perimeters. | <code title="map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; spec &#61; optional&#40;object&#40;&#123;&#10; access_levels &#61; optional&#40;list&#40;string&#41;&#41;&#10; egress_policies &#61; optional&#40;list&#40;string&#41;&#41;&#10; ingress_policies &#61; optional&#40;list&#40;string&#41;&#41;&#10; restricted_services &#61; optional&#40;list&#40;string&#41;&#41;&#10; resources &#61; optional&#40;list&#40;string&#41;&#41;&#10; vpc_accessible_services &#61; optional&#40;object&#40;&#123;&#10; allowed_services &#61; list&#40;string&#41;&#10; enable_restriction &#61; optional&#40;bool, true&#41;&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#41;&#10; status &#61; optional&#40;object&#40;&#123;&#10; access_levels &#61; optional&#40;list&#40;string&#41;&#41;&#10; egress_policies &#61; optional&#40;list&#40;string&#41;&#41;&#10; ingress_policies &#61; optional&#40;list&#40;string&#41;&#41;&#10; resources &#61; optional&#40;list&#40;string&#41;&#41;&#10; restricted_services &#61; optional&#40;list&#40;string&#41;&#41;&#10; vpc_accessible_services &#61; optional&#40;object&#40;&#123;&#10; allowed_services &#61; list&#40;string&#41;&#10; enable_restriction &#61; optional&#40;bool, true&#41;&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#41;&#10; use_explicit_dry_run_spec &#61; optional&#40;bool, false&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
## Outputs
@@ -403,7 +455,7 @@ module "test" {
default = {
status = {
access_levels = ["geo-it"]
resources = ["projects/11111"]
resources = ["projects/1111"]
egress_policies = ["factory-egress-policy"]
ingress_policies = ["variable-policy", "factory-ingress-policy"]
}

View File

@@ -24,7 +24,7 @@ resource "google_access_context_manager_access_level" "basic" {
for_each = merge(local.data.access_levels, var.access_levels)
parent = "accessPolicies/${local.access_policy}"
name = "accessPolicies/${local.access_policy}/accessLevels/${each.key}"
title = each.key
title = coalesce(each.value.title, each.key)
description = each.value.description
basic {
@@ -34,8 +34,11 @@ resource "google_access_context_manager_access_level" "basic" {
for_each = toset(each.value.conditions)
iterator = c
content {
ip_subnetworks = c.value.ip_subnetworks
members = c.value.members
ip_subnetworks = c.value.ip_subnetworks
members = flatten([
for i in c.value.members :
lookup(var.factories_config.context.identity_sets, i, [i])
])
negate = c.value.negate
regions = c.value.regions
required_access_levels = coalesce(c.value.required_access_levels, [])

View File

@@ -22,7 +22,7 @@ locals {
}
}
_data_paths = {
for k in ["access_levels", "egress_policies", "ingress_policies"] : k => (
for k in ["access_levels", "bridges", "egress_policies", "ingress_policies", "perimeters"] : k => (
var.factories_config[k] == null
? null
: pathexpand(var.factories_config[k])
@@ -32,7 +32,6 @@ locals {
access_levels = {
for k, v in local._data.access_levels : k => {
combining_function = try(v.combining_function, null)
description = try(v.description, null)
conditions = [
for c in try(v.conditions, []) : merge({
device_policy = null
@@ -44,6 +43,8 @@ locals {
vpc_subnets = {}
}, c)
]
description = try(v.description, null)
title = try(v.title, null)
}
}
egress_policies = {
@@ -91,6 +92,40 @@ locals {
}
}
}
bridges = {
for k, v in local._data.bridges :
k => {
description = try(v.description, null)
title = try(v.title, null)
spec_resources = try(v.spec_resources, null)
status_resources = try(v.resources, null)
use_explicit_dry_run_spec = try(v.use_explicit_dry_run_spec, false)
}
}
perimeters = {
for k, v in local._data.perimeters :
k => {
description = try(v.description, null)
title = try(v.title, null)
spec = !can(v.spec) ? null : merge(v.spec, {
access_levels = try(v.spec.access_levels, [])
egress_policies = try(v.spec.egress_policies, [])
ingress_policies = try(v.spec.ingress_policies, [])
restricted_services = try(v.spec.restricted_services, [])
resources = try(v.spec.resources, [])
vpc_accessible_services = try(v.spec.vpc_accessible_services, null)
})
status = !can(v.status) ? null : merge(v.status, {
access_levels = try(v.status.access_levels, [])
egress_policies = try(v.status.egress_policies, [])
ingress_policies = try(v.status.ingress_policies, [])
restricted_services = try(v.status.restricted_services, [])
resources = try(v.status.resources, [])
vpc_accessible_services = try(v.status.vpc_accessible_services, null)
})
use_explicit_dry_run_spec = try(v.use_explicit_dry_run_spec, false)
}
}
}
# TODO: add checks that emulate the variable validations
}

View File

@@ -74,8 +74,7 @@
"members": {
"type": "array",
"items": {
"type": "string",
"pattern": "^(?:serviceAccount:|user:)"
"type": "string"
}
},
"negate": {

View File

@@ -33,8 +33,7 @@
"identities": {
"type": "array",
"items": {
"type": "string",
"pattern": "^(?:serviceAccount:|user:|group:|principal:)"
"type": "string"
}
},
"resources": {

View File

@@ -0,0 +1,116 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "perimeters",
"type": "object",
"additionalProperties": false,
"properties": {
"description": {
"type": "string"
},
"title": {
"type": "string"
},
"spec": {
"type": "object",
"properties": {
"access_levels": {
"type": "array",
"items": {
"type": "string"
}
},
"egress_policies": {
"type": "array",
"items": {
"type": "string"
}
},
"ingress_policies": {
"type": "array",
"items": {
"type": "string"
}
},
"restricted_services": {
"type": "array",
"items": {
"type": "string"
}
},
"resources": {
"type": "array",
"items": {
"type": "string"
}
},
"vpc_accessible_services": {
"$ref": "#/$defs/VpcAccessibleServices"
}
},
"additionalProperties": false
},
"status": {
"type": "object",
"properties": {
"access_levels": {
"type": "array",
"items": {
"type": "string"
}
},
"egress_policies": {
"type": "array",
"items": {
"type": "string"
}
},
"ingress_policies": {
"type": "array",
"items": {
"type": "string"
}
},
"resources": {
"type": "array",
"items": {
"type": "string"
}
},
"restricted_services": {
"type": "array",
"items": {
"type": "string"
}
},
"vpc_accessible_services": {
"$ref": "#/$defs/VpcAccessibleServices"
}
},
"additionalProperties": false
},
"use_explicit_dry_run_spec": {
"type": "boolean",
"default": false
}
},
"$defs": {
"VpcAccessibleServices": {
"type": "object",
"additionalProperties": false,
"properties": {
"allowed_services": {
"type": "array",
"items": {
"type": "string"
}
},
"enable_restriction": {
"type": "boolean"
}
},
"required": [
"allowed_services"
]
}
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2023 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.
@@ -20,8 +20,12 @@
# service perimeters are needed, switch to the
# google_access_context_manager_service_perimeters resource
locals {
bridge_perimeters = merge(local.data.bridges, var.service_perimeters_bridge)
}
resource "google_access_context_manager_service_perimeter" "bridge" {
for_each = var.service_perimeters_bridge
for_each = local.bridge_perimeters
parent = "accessPolicies/${local.access_policy}"
name = "accessPolicies/${local.access_policy}/servicePerimeters/${each.key}"
title = each.key
@@ -31,12 +35,21 @@ resource "google_access_context_manager_service_perimeter" "bridge" {
dynamic "spec" {
for_each = each.value.spec_resources == null ? [] : [""]
content {
resources = each.value.spec_resources
resources = flatten([
for r in each.value.spec_resources :
lookup(var.factories_config.context.resource_sets, r, [r])
])
}
}
status {
resources = each.value.status_resources == null ? [] : each.value.status_resources
dynamic "status" {
for_each = each.value.status_resources == null ? [] : [""]
content {
resources = flatten([
for r in each.value.status_resources :
lookup(var.factories_config.context.resource_sets, r, [r])
])
}
}
# lifecycle {

View File

@@ -21,16 +21,17 @@
# google_access_context_manager_service_perimeters resource
locals {
egress_policies = merge(local.data.egress_policies, var.egress_policies)
ingress_policies = merge(local.data.ingress_policies, var.ingress_policies)
egress_policies = merge(local.data.egress_policies, var.egress_policies)
ingress_policies = merge(local.data.ingress_policies, var.ingress_policies)
regular_perimeters = merge(local.data.perimeters, var.service_perimeters_regular)
}
resource "google_access_context_manager_service_perimeter" "regular" {
for_each = var.service_perimeters_regular
for_each = local.regular_perimeters
parent = "accessPolicies/${local.access_policy}"
name = "accessPolicies/${local.access_policy}/servicePerimeters/${each.key}"
description = each.value.description
title = each.key
title = coalesce(each.value.title, each.key)
perimeter_type = "PERIMETER_TYPE_REGULAR"
use_explicit_dry_run_spec = each.value.use_explicit_dry_run_spec
dynamic "spec" {
@@ -43,8 +44,14 @@ resource "google_access_context_manager_service_perimeter" "regular" {
try(google_access_context_manager_access_level.basic[k].id, k)
]
)
resources = spec.value.resources
restricted_services = spec.value.restricted_services
resources = flatten([
for r in spec.value.resources :
lookup(var.factories_config.context.resource_sets, r, [r])
])
restricted_services = flatten([
for r in coalesce(spec.value.restricted_services, []) :
lookup(var.factories_config.context.service_sets, r, [r])
])
dynamic "egress_policies" {
for_each = spec.value.egress_policies == null ? {} : {
@@ -58,7 +65,10 @@ resource "google_access_context_manager_service_perimeter" "regular" {
for_each = policy.value.from == null ? [] : [""]
content {
identity_type = policy.value.from.identity_type
identities = policy.value.from.identities
identities = flatten([
for i in policy.value.from.identities :
lookup(var.factories_config.context.identity_sets, i, [i])
])
source_restriction = (
length(policy.value.from.access_levels) > 0 || length(policy.value.from.resources) > 0
? "SOURCE_RESTRICTION_ENABLED"
@@ -75,7 +85,10 @@ resource "google_access_context_manager_service_perimeter" "regular" {
}
}
dynamic "sources" {
for_each = policy.value.from.resources
for_each = flatten([
for r in policy.value.from.resources :
lookup(var.factories_config.context.resource_sets, r, [r])
])
iterator = resource
content {
resource = resource.value
@@ -87,8 +100,12 @@ resource "google_access_context_manager_service_perimeter" "regular" {
for_each = policy.value.to == null ? [] : [""]
content {
external_resources = policy.value.to.external_resources
resources = policy.value.to.resources
roles = policy.value.to.roles
resources = flatten([
for r in policy.value.to.resources :
lookup(var.factories_config.context.resource_sets, r, [r])
])
roles = policy.value.to.roles
dynamic "operations" {
for_each = toset(policy.value.to.operations)
iterator = o
@@ -125,7 +142,10 @@ resource "google_access_context_manager_service_perimeter" "regular" {
for_each = policy.value.from == null ? [] : [""]
content {
identity_type = policy.value.from.identity_type
identities = policy.value.from.identities
identities = flatten([
for i in policy.value.from.identities :
lookup(var.factories_config.context.identity_sets, i, [i])
])
dynamic "sources" {
for_each = toset(policy.value.from.access_levels)
iterator = s
@@ -136,9 +156,12 @@ resource "google_access_context_manager_service_perimeter" "regular" {
}
}
dynamic "sources" {
for_each = toset(policy.value.from.resources)
for_each = flatten([
for r in policy.value.from.resources :
lookup(var.factories_config.context.resource_sets, r, [r])
])
content {
resource = sources.key
resource = sources.value
}
}
}
@@ -146,8 +169,11 @@ resource "google_access_context_manager_service_perimeter" "regular" {
dynamic "ingress_to" {
for_each = policy.value.to == null ? [] : [""]
content {
resources = policy.value.to.resources
roles = policy.value.to.roles
resources = flatten([
for r in policy.value.to.resources :
lookup(var.factories_config.context.resource_sets, r, [r])
])
roles = policy.value.to.roles
dynamic "operations" {
for_each = toset(policy.value.to.operations)
iterator = o
@@ -175,7 +201,10 @@ resource "google_access_context_manager_service_perimeter" "regular" {
dynamic "vpc_accessible_services" {
for_each = spec.value.vpc_accessible_services == null ? {} : { 1 = 1 }
content {
allowed_services = spec.value.vpc_accessible_services.allowed_services
allowed_services = flatten([
for r in spec.value.vpc_accessible_services.allowed_services :
lookup(var.factories_config.context.service_sets, r, [r])
])
enable_restriction = spec.value.vpc_accessible_services.enable_restriction
}
}
@@ -192,8 +221,14 @@ resource "google_access_context_manager_service_perimeter" "regular" {
try(google_access_context_manager_access_level.basic[k].id, k)
]
)
resources = status.value.resources
restricted_services = status.value.restricted_services
resources = flatten([
for r in status.value.resources :
lookup(var.factories_config.context.resource_sets, r, [r])
])
restricted_services = flatten([
for r in coalesce(status.value.restricted_services, []) :
lookup(var.factories_config.context.service_sets, r, [r])
])
dynamic "egress_policies" {
for_each = status.value.egress_policies == null ? {} : {
@@ -207,7 +242,10 @@ resource "google_access_context_manager_service_perimeter" "regular" {
for_each = policy.value.from == null ? [] : [""]
content {
identity_type = policy.value.from.identity_type
identities = policy.value.from.identities
identities = flatten([
for i in policy.value.from.identities :
lookup(var.factories_config.context.identity_sets, i, [i])
])
source_restriction = (
length(policy.value.from.access_levels) > 0 || length(policy.value.from.resources) > 0
? "SOURCE_RESTRICTION_ENABLED"
@@ -224,7 +262,10 @@ resource "google_access_context_manager_service_perimeter" "regular" {
}
}
dynamic "sources" {
for_each = policy.value.from.resources
for_each = flatten([
for r in policy.value.from.resources :
lookup(var.factories_config.context.resource_sets, r, [r])
])
iterator = resource
content {
resource = resource.value
@@ -274,7 +315,10 @@ resource "google_access_context_manager_service_perimeter" "regular" {
for_each = policy.value.from == null ? [] : [""]
content {
identity_type = policy.value.from.identity_type
identities = policy.value.from.identities
identities = flatten([
for i in policy.value.from.identities :
lookup(var.factories_config.context.identity_sets, i, [i])
])
dynamic "sources" {
for_each = toset(policy.value.from.access_levels)
iterator = s
@@ -286,9 +330,12 @@ resource "google_access_context_manager_service_perimeter" "regular" {
}
}
dynamic "sources" {
for_each = toset(policy.value.from.resources)
for_each = flatten([
for r in policy.value.from.resources :
lookup(var.factories_config.context.resource_sets, r, [r])
])
content {
resource = sources.key
resource = sources.value
}
}
}
@@ -296,8 +343,11 @@ resource "google_access_context_manager_service_perimeter" "regular" {
dynamic "ingress_to" {
for_each = policy.value.to == null ? [] : [""]
content {
resources = policy.value.to.resources
roles = policy.value.to.roles
resources = flatten([
for r in policy.value.to.resources :
lookup(var.factories_config.context.resource_sets, r, [r])
])
roles = policy.value.to.roles
dynamic "operations" {
for_each = toset(policy.value.to.operations)
iterator = o
@@ -325,7 +375,10 @@ resource "google_access_context_manager_service_perimeter" "regular" {
dynamic "vpc_accessible_services" {
for_each = status.value.vpc_accessible_services == null ? {} : { 1 = 1 }
content {
allowed_services = status.value.vpc_accessible_services.allowed_services
allowed_services = flatten([
for r in status.value.vpc_accessible_services.allowed_services :
lookup(var.factories_config.context.service_sets, r, [r])
])
enable_restriction = status.value.vpc_accessible_services.enable_restriction
}
}

View File

@@ -39,6 +39,7 @@ variable "access_levels" {
vpc_subnets = optional(map(list(string)), {})
})), [])
description = optional(string)
title = optional(string)
}))
default = {}
nullable = false
@@ -125,10 +126,16 @@ variable "egress_policies" {
variable "factories_config" {
description = "Paths to folders that enable factory functionality."
type = object({
access_levels = optional(string)
egress_policies = optional(string)
ingress_policies = optional(string)
restricted_services = optional(string, "data/restricted-services.yaml")
access_levels = optional(string)
bridges = optional(string)
egress_policies = optional(string)
ingress_policies = optional(string)
perimeters = optional(string)
context = optional(object({
resource_sets = optional(map(list(string)), {})
service_sets = optional(map(list(string)), {})
identity_sets = optional(map(list(string)), {})
}), {})
})
nullable = false
default = {}
@@ -215,6 +222,8 @@ variable "ingress_policies" {
variable "service_perimeters_bridge" {
description = "Bridge service perimeters."
type = map(object({
description = optional(string)
title = optional(string)
spec_resources = optional(list(string))
status_resources = optional(list(string))
use_explicit_dry_run_spec = optional(bool, false)
@@ -226,12 +235,13 @@ variable "service_perimeters_regular" {
description = "Regular service perimeters."
type = map(object({
description = optional(string)
title = optional(string)
spec = optional(object({
access_levels = optional(list(string))
resources = optional(list(string))
restricted_services = optional(list(string))
egress_policies = optional(list(string))
ingress_policies = optional(list(string))
restricted_services = optional(list(string))
resources = optional(list(string))
vpc_accessible_services = optional(object({
allowed_services = list(string)
enable_restriction = optional(bool, true)
@@ -239,13 +249,13 @@ variable "service_perimeters_regular" {
}))
status = optional(object({
access_levels = optional(list(string))
resources = optional(list(string))
restricted_services = optional(list(string))
egress_policies = optional(list(string))
ingress_policies = optional(list(string))
resources = optional(list(string))
restricted_services = optional(list(string))
vpc_accessible_services = optional(object({
allowed_services = list(string)
enable_restriction = bool
enable_restriction = optional(bool, true)
}))
}))
use_explicit_dry_run_spec = optional(bool, false)