Add perimeter factory to modules/vpc-sc (#2919)

* Add perimeter factory

Proposal to allow the management of perimeters in yaml factory.
Project discovery is extended to multiple perimeters with query filtering.

Doc clean uo

* default perimeters desc to null

* linting

* Revert changes to FAST

* Remove test and schema from FAST

* Align vars and perimeter factory

* Interpolate resource_sets in more places

* Silence linter

* Update README.md

---------

Co-authored-by: Julio Castillo <jccb@google.com>
Co-authored-by: Ludovico Magnocavallo <ludo@qix.it>
This commit is contained in:
karpok78
2025-02-22 07:49:05 +01:00
committed by GitHub
parent 16727ded25
commit 07a70eedb5
10 changed files with 326 additions and 67 deletions

View File

@@ -198,7 +198,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"]
@@ -215,11 +215,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" {
@@ -229,12 +229,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"]
@@ -308,9 +314,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.
@@ -339,17 +391,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; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [factories_config](variables.tf#L124) | 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#L136) | IAM bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_bindings](variables.tf#L142) | 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#L157) | 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#L172) | 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; &#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#L213) | 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#L223) | 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; &#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; 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; &#125;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam](variables.tf#L142) | IAM bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_bindings](variables.tf#L148) | 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#L163) | 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#L178) | 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; &#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#L219) | 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#L231) | 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
@@ -392,7 +444,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 {

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 = {
@@ -89,6 +90,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

@@ -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 ? {} : {
@@ -75,7 +82,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,7 +97,11 @@ 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
resources = flatten([
for r in policy.value.to.resources :
lookup(var.factories_config.context.resource_sets, r, [r])
])
dynamic "operations" {
for_each = toset(policy.value.to.operations)
iterator = o
@@ -135,7 +149,10 @@ 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
}
@@ -145,7 +162,10 @@ resource "google_access_context_manager_service_perimeter" "regular" {
dynamic "ingress_to" {
for_each = policy.value.to == null ? [] : [""]
content {
resources = policy.value.to.resources
resources = flatten([
for r in policy.value.to.resources :
lookup(var.factories_config.context.resource_sets, r, [r])
])
dynamic "operations" {
for_each = toset(policy.value.to.operations)
iterator = o
@@ -173,7 +193,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
}
}
@@ -190,8 +213,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 ? {} : {
@@ -222,7 +251,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
@@ -293,7 +325,10 @@ resource "google_access_context_manager_service_perimeter" "regular" {
dynamic "ingress_to" {
for_each = policy.value.to == null ? [] : [""]
content {
resources = policy.value.to.resources
resources = flatten([
for r in policy.value.to.resources :
lookup(var.factories_config.context.resource_sets, r, [r])
])
dynamic "operations" {
for_each = toset(policy.value.to.operations)
iterator = o
@@ -321,7 +356,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
@@ -124,10 +125,15 @@ 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)), {})
}), {})
})
nullable = false
default = {}
@@ -213,6 +219,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)
@@ -224,12 +232,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)
@@ -237,13 +246,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)

View File

@@ -44,13 +44,7 @@ values:
- projects/222221
restricted_services: null
vpc_accessible_services: []
status:
- access_levels: null
egress_policies: []
ingress_policies: []
resources: null
restricted_services: null
vpc_accessible_services: []
status: []
title: b2
use_explicit_dry_run_spec: true

View File

@@ -49,9 +49,9 @@ values:
parent: accessPolicies/12345678
timeouts: null
title: identity-user1
module.test.google_access_context_manager_service_perimeter.regular["r1"]:
description: null
name: accessPolicies/12345678/servicePerimeters/r1
module.test.google_access_context_manager_service_perimeter.regular["perimeter-north"]:
description: Main perimeter
name: accessPolicies/12345678/servicePerimeters/perimeter-north
parent: accessPolicies/12345678
perimeter_type: PERIMETER_TYPE_REGULAR
spec: []
@@ -62,7 +62,7 @@ values:
- serviceAccount:bar@myproject.iam.gserviceaccount.com
- serviceAccount:foo@myproject.iam.gserviceaccount.com
identity_type: null
source_restriction: 'SOURCE_RESTRICTION_DISABLED'
source_restriction: SOURCE_RESTRICTION_DISABLED
sources: []
egress_to:
- external_resources: null
@@ -104,9 +104,11 @@ values:
service_name: '*'
resources:
- projects/1234567890
- projects/321
- projects/654
resources:
- projects/11111
- projects/111111
- projects/1111
- projects/2222
restricted_services:
- storage.googleapis.com
vpc_accessible_services:
@@ -114,7 +116,7 @@ values:
- storage.googleapis.com
enable_restriction: true
timeouts: null
title: r1
title: perimeter-north
use_explicit_dry_run_spec: false
counts:

View File

@@ -1,4 +1,4 @@
# 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.
@@ -78,8 +78,8 @@ values:
resources:
- '*'
resources:
- projects/11111
- projects/111111
- projects/1111
- projects/2222
restricted_services:
- storage.googleapis.com
vpc_accessible_services: