VPC SC module refactor (#3062)
* Remove bridge perimeters * Update FAST stages * Allow project ids in perimeter definitions * Preserve order order for ingress/egress policies * Use CAI * Use CAI * Fix tests
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# Copyright 2024 Google LLC
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -125,7 +125,7 @@ module "vpc-sc" {
|
||||
access_levels = var.vpc_sc_access_levels
|
||||
egress_policies = var.vpc_sc_egress_policies
|
||||
ingress_policies = merge(var.vpc_sc_ingress_policies, local._sink_ingress_policies)
|
||||
service_perimeters_regular = {
|
||||
perimeters = {
|
||||
shielded = {
|
||||
# Move `spec` definition to `status` and comment `use_explicit_dry_run_spec` variable to enforce VPC-SC configuration
|
||||
# Before enforcing configuration check logs and create Access Level, Ingress/Egress policy as needed
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2024 Google LLC
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -569,7 +569,7 @@ module "vpc_sc" {
|
||||
}
|
||||
}
|
||||
}
|
||||
service_perimeters_regular = {
|
||||
perimeters = {
|
||||
cloudrun = {
|
||||
status = {
|
||||
resources = [
|
||||
|
||||
@@ -318,6 +318,6 @@ Some references that might be useful in setting up this stage:
|
||||
|
||||
| name | description | sensitive | consumers |
|
||||
|---|---|:---:|---|
|
||||
| [tfvars](outputs.tf#L43) | Terraform variable files for the following stages. | ✓ | |
|
||||
| [vpc_sc_perimeter_default](outputs.tf#L49) | Raw default perimeter resource. | ✓ | |
|
||||
| [tfvars](outputs.tf#L39) | Terraform variable files for the following stages. | ✓ | |
|
||||
| [vpc_sc_perimeter_default](outputs.tf#L45) | Raw default perimeter resource. | ✓ | |
|
||||
<!-- END TFDOC -->
|
||||
|
||||
@@ -59,6 +59,7 @@ module "vpc-sc" {
|
||||
context = local.context
|
||||
}
|
||||
)
|
||||
ingress_policies = var.ingress_policies
|
||||
service_perimeters_regular = var.perimeters
|
||||
ingress_policies = var.ingress_policies
|
||||
perimeters = var.perimeters
|
||||
project_id_search_scope = "organizations/${var.organization.id}"
|
||||
}
|
||||
|
||||
@@ -17,11 +17,7 @@
|
||||
locals {
|
||||
tfvars = {
|
||||
perimeters = {
|
||||
for k, v in try(module.vpc-sc.service_perimeters_regular, {}) :
|
||||
k => v.id
|
||||
}
|
||||
perimeters_bridge = {
|
||||
for k, v in try(module.vpc-sc.service_perimeters_bridge, {}) :
|
||||
for k, v in try(module.vpc-sc.perimeters, {}) :
|
||||
k => v.id
|
||||
}
|
||||
}
|
||||
@@ -49,5 +45,5 @@ output "tfvars" {
|
||||
output "vpc_sc_perimeter_default" {
|
||||
description = "Raw default perimeter resource."
|
||||
sensitive = true
|
||||
value = try(module.vpc-sc.service_perimeters_regular["default"], null)
|
||||
value = try(module.vpc-sc.perimeters["default"], null)
|
||||
}
|
||||
|
||||
@@ -12,12 +12,10 @@ If you are using [Application Default Credentials](https://cloud.google.com/sdk/
|
||||
- [Scoped policy](#scoped-policy)
|
||||
- [Access policy IAM](#access-policy-iam)
|
||||
- [Access levels](#access-levels)
|
||||
- [Service perimeters](#service-perimeters)
|
||||
- [Bridge type](#bridge-type)
|
||||
- [Regular type](#regular-type)
|
||||
- [Perimeters](#perimeters)
|
||||
- [Automatic Project ID to Project Number Conversion](#automatic-project-id-to-project-number-conversion)
|
||||
- [Factories](#factories)
|
||||
- [Notes](#notes)
|
||||
- [TODO](#todo)
|
||||
- [Files](#files)
|
||||
- [Variables](#variables)
|
||||
- [Outputs](#outputs)
|
||||
@@ -112,36 +110,7 @@ module "test" {
|
||||
# tftest modules=1 resources=2 inventory=access-levels.yaml
|
||||
```
|
||||
|
||||
### Service perimeters
|
||||
|
||||
Bridge and regular service perimeters use two separate variables, as bridge perimeters only accept a limited number of arguments, and can leverage a much simpler interface.
|
||||
|
||||
The regular perimeters variable exposes all the complexity of the underlying resource, use [its documentation](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/access_context_manager_service_perimeter) as a reference about the possible values and configurations.
|
||||
|
||||
If you need to refer to access levels created by the same module in regular service perimeters, you can either use the module's outputs in the provided variables, or the key used to identify the relevant access level. The example below shows how to do this in practice.
|
||||
|
||||
If you are managing perimeter membership outside of this module via `google_access_context_manager_service_perimeter_resource`, for example at project creation in a project factory, you might want to uncomment the lifecycle blocks that are defined but currently unused in `service-perimeters-regular.tf` and `service-perimeters-bridge.tf`.
|
||||
|
||||
#### Bridge type
|
||||
|
||||
```hcl
|
||||
module "test" {
|
||||
source = "./fabric/modules/vpc-sc"
|
||||
access_policy = "12345678"
|
||||
service_perimeters_bridge = {
|
||||
b1 = {
|
||||
status_resources = ["projects/111110", "projects/111111"]
|
||||
}
|
||||
b2 = {
|
||||
spec_resources = ["projects/222220", "projects/222221"]
|
||||
use_explicit_dry_run_spec = true
|
||||
}
|
||||
}
|
||||
}
|
||||
# tftest modules=1 resources=2 inventory=bridge.yaml
|
||||
```
|
||||
|
||||
#### Regular type
|
||||
### Perimeters
|
||||
|
||||
```hcl
|
||||
module "test" {
|
||||
@@ -205,7 +174,7 @@ module "test" {
|
||||
}
|
||||
}
|
||||
}
|
||||
service_perimeters_regular = {
|
||||
perimeters = {
|
||||
r1 = {
|
||||
status = {
|
||||
access_levels = ["a1", "a2"]
|
||||
@@ -224,13 +193,56 @@ module "test" {
|
||||
# tftest modules=1 resources=3 inventory=regular.yaml
|
||||
```
|
||||
|
||||
## Automatic Project ID to Project Number Conversion
|
||||
|
||||
As a convenience, this module can optionally convert project IDs to project numbers. Set `var.project_id_search_scope` to a folder or organization ID to define the search scope.
|
||||
|
||||
The caller must have `cloudasset.assets.searchAllResources` permission to perform the search. Roles like `roles/accesscontextmanager.policyAdmin`, `roles/cloudasset.viewer`, or `roles/viewer` grant this.
|
||||
|
||||
```hcl
|
||||
module "vpc-sc" {
|
||||
source = "./fabric/modules/vpc-sc"
|
||||
project_id_search_scope = var.org_id
|
||||
|
||||
access_policy = "12345678"
|
||||
ingress_policies = {
|
||||
i1 = {
|
||||
from = {
|
||||
identities = [
|
||||
"serviceAccount:foo@myproject.iam.gserviceaccount.com"
|
||||
]
|
||||
resources = ["projects/my-source-project"]
|
||||
}
|
||||
to = {
|
||||
operations = [{
|
||||
method_selectors = ["*"]
|
||||
service_name = "storage.googleapis.com"
|
||||
}]
|
||||
resources = ["projects/my-destionation-project"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
perimeters = {
|
||||
p = {
|
||||
spec = {
|
||||
ingress_policies = ["i1"]
|
||||
resources = ["projects/my-destionation-project"]
|
||||
}
|
||||
use_explicit_dry_run_spec = true
|
||||
}
|
||||
}
|
||||
}
|
||||
# tftest skip because uses data sources
|
||||
```
|
||||
|
||||
## Factories
|
||||
|
||||
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.
|
||||
This module implements support for four distinct factories, used to create and manage perimeters, 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 only three factories and leave perimeter management in tfvars. Note that the factory configuration points to folders, where each file represents one resource.
|
||||
3Note that the factory configuration points to folders, where each file represents one resource.
|
||||
|
||||
```hcl
|
||||
module "test" {
|
||||
@@ -240,30 +252,62 @@ module "test" {
|
||||
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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
service_perimeters_regular = {
|
||||
perimeter-north = {
|
||||
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 = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
# tftest modules=1 resources=3 files=a1,a2,e1,i1,i2 inventory=factory.yaml
|
||||
# tftest modules=1 resources=3 files=p1,a1,a2,e1,i1,i2 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=p1 path=data/perimeters/perimeter-north.yaml schema=perimeters.schema.json
|
||||
```
|
||||
|
||||
```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: true
|
||||
# tftest-file id=p1 path=data/perimeters/perimeter-north.yaml schema=perimeters.schema.json
|
||||
```
|
||||
|
||||
```yaml
|
||||
@@ -329,59 +373,10 @@ to:
|
||||
# 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.
|
||||
|
||||
## TODO
|
||||
|
||||
- [ ] implement support for the `google_access_context_manager_gcp_user_access_binding` resource
|
||||
|
||||
<!-- TFDOC OPTS files:1 -->
|
||||
<!-- BEGIN TFDOC -->
|
||||
## Files
|
||||
@@ -393,8 +388,7 @@ status:
|
||||
| [iam.tf](./iam.tf) | IAM bindings | <code>google_access_context_manager_access_policy_iam_binding</code> · <code>google_access_context_manager_access_policy_iam_member</code> |
|
||||
| [main.tf](./main.tf) | Module-level locals and resources. | <code>google_access_context_manager_access_policy</code> |
|
||||
| [outputs.tf](./outputs.tf) | Module outputs. | |
|
||||
| [service-perimeters-bridge.tf](./service-perimeters-bridge.tf) | Bridge service perimeter resources. | <code>google_access_context_manager_service_perimeter</code> |
|
||||
| [service-perimeters-regular.tf](./service-perimeters-regular.tf) | Regular service perimeter resources. | <code>google_access_context_manager_service_perimeter</code> |
|
||||
| [perimeters.tf](./perimeters.tf) | Regular service perimeter resources. | <code>google_access_context_manager_service_perimeter</code> |
|
||||
| [variables.tf](./variables.tf) | Module variables. | |
|
||||
| [versions.tf](./versions.tf) | Version pins. | |
|
||||
|
||||
@@ -406,13 +400,13 @@ status:
|
||||
| [access_levels](variables.tf#L17) | Access level definitions. | <code title="map(object({ combining_function = optional(string) conditions = optional(list(object({ device_policy = optional(object({ allowed_device_management_levels = optional(list(string)) allowed_encryption_statuses = optional(list(string)) require_admin_approval = bool require_corp_owned = bool require_screen_lock = optional(bool) os_constraints = optional(list(object({ os_type = string minimum_version = optional(string) require_verified_chrome_os = optional(bool) }))) })) ip_subnetworks = optional(list(string), []) members = optional(list(string), []) negate = optional(bool) regions = optional(list(string), []) required_access_levels = optional(list(string), []) vpc_subnets = optional(map(list(string)), {}) })), []) description = optional(string) title = optional(string) }))">map(object({…}))</code> | | <code>{}</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({ parent = string title = string scopes = optional(list(string), null) })">object({…})</code> | | <code>null</code> |
|
||||
| [egress_policies](variables.tf#L83) | Egress policy definitions that can be referenced in perimeters. | <code title="map(object({ title = optional(string) from = object({ access_levels = optional(list(string), []) identity_type = optional(string) identities = optional(list(string)) resources = optional(list(string), []) }) to = object({ external_resources = optional(list(string)) operations = optional(list(object({ method_selectors = optional(list(string)) permission_selectors = optional(list(string)) service_name = string })), []) resources = optional(list(string)) roles = optional(list(string)) }) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [factories_config](variables.tf#L126) | Paths to folders that enable factory functionality. | <code title="object({ 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)), {}) }), {}) })">object({…})</code> | | <code>{}</code> |
|
||||
| [iam](variables.tf#L144) | IAM bindings in {ROLE => [MEMBERS]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_bindings](variables.tf#L150) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | <code title="map(object({ members = list(string) role = string condition = optional(object({ expression = string title = string description = optional(string) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [iam_bindings_additive](variables.tf#L165) | Individual additive IAM bindings. Keys are arbitrary. | <code title="map(object({ member = string role = string condition = optional(object({ expression = string title = string description = optional(string) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [ingress_policies](variables.tf#L180) | Ingress policy definitions that can be referenced in perimeters. | <code title="map(object({ title = optional(string) from = object({ access_levels = optional(list(string), []) identity_type = optional(string) identities = optional(list(string)) resources = optional(list(string), []) }) to = object({ operations = optional(list(object({ method_selectors = optional(list(string)) permission_selectors = optional(list(string)) service_name = string })), []) resources = optional(list(string)) roles = optional(list(string)) }) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [service_perimeters_bridge](variables.tf#L222) | Bridge service perimeters. | <code title="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) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [service_perimeters_regular](variables.tf#L234) | Regular service perimeters. | <code title="map(object({ description = optional(string) title = optional(string) spec = optional(object({ access_levels = 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) })) })) status = optional(object({ access_levels = 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 = optional(bool, true) })) })) use_explicit_dry_run_spec = optional(bool, false) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [factories_config](variables.tf#L126) | Paths to folders that enable factory functionality. | <code title="object({ access_levels = 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)), {}) }), {}) })">object({…})</code> | | <code>{}</code> |
|
||||
| [iam](variables.tf#L143) | IAM bindings in {ROLE => [MEMBERS]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_bindings](variables.tf#L149) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | <code title="map(object({ members = list(string) role = string condition = optional(object({ expression = string title = string description = optional(string) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [iam_bindings_additive](variables.tf#L164) | Individual additive IAM bindings. Keys are arbitrary. | <code title="map(object({ member = string role = string condition = optional(object({ expression = string title = string description = optional(string) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [ingress_policies](variables.tf#L179) | Ingress policy definitions that can be referenced in perimeters. | <code title="map(object({ title = optional(string) from = object({ access_levels = optional(list(string), []) identity_type = optional(string) identities = optional(list(string)) resources = optional(list(string), []) }) to = object({ operations = optional(list(object({ method_selectors = optional(list(string)) permission_selectors = optional(list(string)) service_name = string })), []) resources = optional(list(string)) roles = optional(list(string)) }) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [perimeters](variables.tf#L221) | Regular service perimeters. | <code title="map(object({ description = optional(string) title = optional(string) spec = optional(object({ access_levels = 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) })) })) status = optional(object({ access_levels = 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 = optional(bool, true) })) })) use_explicit_dry_run_spec = optional(bool, false) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [project_id_search_scope](variables.tf#L254) | Set this to an organization or folder ID to use Cloud Asset Inventory to automatically translate project ids to numbers. | <code>string</code> | | <code>null</code> |
|
||||
|
||||
## Outputs
|
||||
|
||||
@@ -423,8 +417,7 @@ status:
|
||||
| [access_policy](outputs.tf#L30) | Access policy resource, if autocreated. | |
|
||||
| [access_policy_name](outputs.tf#L37) | Access policy name. | |
|
||||
| [id](outputs.tf#L42) | Fully qualified access policy id. | |
|
||||
| [service_perimeters_bridge](outputs.tf#L47) | Bridge service perimeter resources. | |
|
||||
| [service_perimeters_regular](outputs.tf#L52) | Regular service perimeter resources. | |
|
||||
| [perimeters](outputs.tf#L47) | Regular service perimeter resources. | |
|
||||
<!-- END TFDOC -->
|
||||
## Tests
|
||||
|
||||
@@ -451,7 +444,7 @@ module "test" {
|
||||
}
|
||||
}
|
||||
}
|
||||
service_perimeters_regular = {
|
||||
perimeters = {
|
||||
default = {
|
||||
status = {
|
||||
access_levels = ["geo-it"]
|
||||
|
||||
@@ -22,7 +22,7 @@ locals {
|
||||
}
|
||||
}
|
||||
_data_paths = {
|
||||
for k in ["access_levels", "bridges", "egress_policies", "ingress_policies", "perimeters"] : k => (
|
||||
for k in ["access_levels", "egress_policies", "ingress_policies", "perimeters"] : k => (
|
||||
var.factories_config[k] == null
|
||||
? null
|
||||
: pathexpand(var.factories_config[k])
|
||||
@@ -92,16 +92,6 @@ 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 => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2024 Google LLC
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,6 +19,49 @@ locals {
|
||||
google_access_context_manager_access_policy.default[0].name,
|
||||
var.access_policy
|
||||
)
|
||||
cai_query = join(" OR ",
|
||||
formatlist(
|
||||
"\"//cloudresourcemanager.googleapis.com/projects/%s\"",
|
||||
local._project_ids
|
||||
)
|
||||
)
|
||||
do_cai_query = (
|
||||
var.project_id_search_scope == null
|
||||
? false
|
||||
: length(local._project_ids) > 0
|
||||
)
|
||||
|
||||
# collect project ids and convert them to numbers
|
||||
_all_project_identifiers = distinct(flatten([
|
||||
for k, v in local.perimeters : [
|
||||
try(v.status.resources, []),
|
||||
try(v.spec.resources, []),
|
||||
[
|
||||
for _, vv in local.ingress_policies : [
|
||||
try(vv.from.resources, []),
|
||||
try(vv.to.resources, [])
|
||||
]
|
||||
],
|
||||
[
|
||||
for _, vv in local.egress_policies : [
|
||||
try(vv.from.resources, []),
|
||||
try(vv.to.resources, [])
|
||||
]
|
||||
],
|
||||
]
|
||||
]))
|
||||
_project_ids = [
|
||||
for x in local._all_project_identifiers :
|
||||
trimprefix(x, "projects/")
|
||||
if can(regex("^projects/[a-z]", x))
|
||||
]
|
||||
project_number = (local.do_cai_query
|
||||
? {
|
||||
for x in data.google_cloud_asset_search_all_resources.projects[0].results :
|
||||
(trimprefix(x.name, "//cloudresourcemanager.googleapis.com/")) => x.project
|
||||
}
|
||||
: {}
|
||||
)
|
||||
}
|
||||
|
||||
resource "google_access_context_manager_access_policy" "default" {
|
||||
@@ -27,3 +70,12 @@ resource "google_access_context_manager_access_policy" "default" {
|
||||
title = var.access_policy_create.title
|
||||
scopes = var.access_policy_create.scopes
|
||||
}
|
||||
|
||||
data "google_cloud_asset_search_all_resources" "projects" {
|
||||
count = local.do_cai_query ? 1 : 0
|
||||
scope = var.project_id_search_scope
|
||||
asset_types = [
|
||||
"cloudresourcemanager.googleapis.com/Project"
|
||||
]
|
||||
query = "name=${local.cai_query}"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2022 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.
|
||||
@@ -44,12 +44,7 @@ output "id" {
|
||||
value = local.access_policy
|
||||
}
|
||||
|
||||
output "service_perimeters_bridge" {
|
||||
description = "Bridge service perimeter resources."
|
||||
value = google_access_context_manager_service_perimeter.bridge
|
||||
}
|
||||
|
||||
output "service_perimeters_regular" {
|
||||
output "perimeters" {
|
||||
description = "Regular service perimeter resources."
|
||||
value = google_access_context_manager_service_perimeter.regular
|
||||
}
|
||||
|
||||
@@ -21,13 +21,13 @@
|
||||
# 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)
|
||||
regular_perimeters = merge(local.data.perimeters, var.service_perimeters_regular)
|
||||
egress_policies = merge(local.data.egress_policies, var.egress_policies)
|
||||
ingress_policies = merge(local.data.ingress_policies, var.ingress_policies)
|
||||
perimeters = merge(local.data.perimeters, var.perimeters)
|
||||
}
|
||||
|
||||
resource "google_access_context_manager_service_perimeter" "regular" {
|
||||
for_each = local.regular_perimeters
|
||||
for_each = local.perimeters
|
||||
parent = "accessPolicies/${local.access_policy}"
|
||||
name = "accessPolicies/${local.access_policy}/servicePerimeters/${each.key}"
|
||||
description = each.value.description
|
||||
@@ -45,8 +45,10 @@ resource "google_access_context_manager_service_perimeter" "regular" {
|
||||
]
|
||||
)
|
||||
resources = flatten([
|
||||
for r in spec.value.resources :
|
||||
lookup(var.factories_config.context.resource_sets, r, [r])
|
||||
for r in spec.value.resources : try(
|
||||
var.factories_config.context.resource_sets[r],
|
||||
[local.project_number[r]], [r]
|
||||
)
|
||||
])
|
||||
restricted_services = flatten([
|
||||
for r in coalesce(spec.value.restricted_services, []) :
|
||||
@@ -54,13 +56,13 @@ resource "google_access_context_manager_service_perimeter" "regular" {
|
||||
])
|
||||
|
||||
dynamic "egress_policies" {
|
||||
for_each = spec.value.egress_policies == null ? {} : {
|
||||
for_each = spec.value.egress_policies == null ? [] : [
|
||||
for k in spec.value.egress_policies :
|
||||
k => local.egress_policies[k]
|
||||
}
|
||||
merge(local.egress_policies[k], { key = k })
|
||||
]
|
||||
iterator = policy
|
||||
content {
|
||||
title = coalesce(policy.value.title, policy.key)
|
||||
title = coalesce(policy.value.title, policy.value.key)
|
||||
dynamic "egress_from" {
|
||||
for_each = policy.value.from == null ? [] : [""]
|
||||
content {
|
||||
@@ -86,8 +88,10 @@ resource "google_access_context_manager_service_perimeter" "regular" {
|
||||
}
|
||||
dynamic "sources" {
|
||||
for_each = flatten([
|
||||
for r in policy.value.from.resources :
|
||||
lookup(var.factories_config.context.resource_sets, r, [r])
|
||||
for r in policy.value.from.resources : try(
|
||||
var.factories_config.context.resource_sets[r],
|
||||
[local.project_number[r]], [r]
|
||||
)
|
||||
])
|
||||
iterator = resource
|
||||
content {
|
||||
@@ -101,8 +105,10 @@ resource "google_access_context_manager_service_perimeter" "regular" {
|
||||
content {
|
||||
external_resources = policy.value.to.external_resources
|
||||
resources = flatten([
|
||||
for r in policy.value.to.resources :
|
||||
lookup(var.factories_config.context.resource_sets, r, [r])
|
||||
for r in policy.value.to.resources : try(
|
||||
var.factories_config.context.resource_sets[r],
|
||||
[local.project_number[r]], [r]
|
||||
)
|
||||
])
|
||||
|
||||
roles = policy.value.to.roles
|
||||
@@ -131,13 +137,13 @@ resource "google_access_context_manager_service_perimeter" "regular" {
|
||||
}
|
||||
|
||||
dynamic "ingress_policies" {
|
||||
for_each = spec.value.ingress_policies == null ? {} : {
|
||||
for_each = spec.value.ingress_policies == null ? [] : [
|
||||
for k in spec.value.ingress_policies :
|
||||
k => local.ingress_policies[k]
|
||||
}
|
||||
merge(local.ingress_policies[k], { key = k })
|
||||
]
|
||||
iterator = policy
|
||||
content {
|
||||
title = coalesce(policy.value.title, policy.key)
|
||||
title = coalesce(policy.value.title, policy.value.key)
|
||||
dynamic "ingress_from" {
|
||||
for_each = policy.value.from == null ? [] : [""]
|
||||
content {
|
||||
@@ -157,8 +163,10 @@ resource "google_access_context_manager_service_perimeter" "regular" {
|
||||
}
|
||||
dynamic "sources" {
|
||||
for_each = flatten([
|
||||
for r in policy.value.from.resources :
|
||||
lookup(var.factories_config.context.resource_sets, r, [r])
|
||||
for r in policy.value.from.resources : try(
|
||||
var.factories_config.context.resource_sets[r],
|
||||
[local.project_number[r]], [r]
|
||||
)
|
||||
])
|
||||
content {
|
||||
resource = sources.value
|
||||
@@ -170,8 +178,10 @@ resource "google_access_context_manager_service_perimeter" "regular" {
|
||||
for_each = policy.value.to == null ? [] : [""]
|
||||
content {
|
||||
resources = flatten([
|
||||
for r in policy.value.to.resources :
|
||||
lookup(var.factories_config.context.resource_sets, r, [r])
|
||||
for r in policy.value.to.resources : try(
|
||||
var.factories_config.context.resource_sets[r],
|
||||
[local.project_number[r]], [r]
|
||||
)
|
||||
])
|
||||
roles = policy.value.to.roles
|
||||
dynamic "operations" {
|
||||
@@ -199,7 +209,7 @@ resource "google_access_context_manager_service_perimeter" "regular" {
|
||||
}
|
||||
|
||||
dynamic "vpc_accessible_services" {
|
||||
for_each = spec.value.vpc_accessible_services == null ? {} : { 1 = 1 }
|
||||
for_each = spec.value.vpc_accessible_services == null ? [] : [""]
|
||||
content {
|
||||
allowed_services = flatten([
|
||||
for r in spec.value.vpc_accessible_services.allowed_services :
|
||||
@@ -222,8 +232,10 @@ resource "google_access_context_manager_service_perimeter" "regular" {
|
||||
]
|
||||
)
|
||||
resources = flatten([
|
||||
for r in status.value.resources :
|
||||
lookup(var.factories_config.context.resource_sets, r, [r])
|
||||
for r in status.value.resources : try(
|
||||
var.factories_config.context.resource_sets[r],
|
||||
[local.project_number[r]], [r]
|
||||
)
|
||||
])
|
||||
restricted_services = flatten([
|
||||
for r in coalesce(status.value.restricted_services, []) :
|
||||
@@ -231,13 +243,13 @@ resource "google_access_context_manager_service_perimeter" "regular" {
|
||||
])
|
||||
|
||||
dynamic "egress_policies" {
|
||||
for_each = status.value.egress_policies == null ? {} : {
|
||||
for_each = status.value.egress_policies == null ? [] : [
|
||||
for k in status.value.egress_policies :
|
||||
k => local.egress_policies[k]
|
||||
}
|
||||
merge(local.egress_policies[k], { key = k })
|
||||
]
|
||||
iterator = policy
|
||||
content {
|
||||
title = coalesce(policy.value.title, policy.key)
|
||||
title = coalesce(policy.value.title, policy.value.key)
|
||||
dynamic "egress_from" {
|
||||
for_each = policy.value.from == null ? [] : [""]
|
||||
content {
|
||||
@@ -263,8 +275,10 @@ resource "google_access_context_manager_service_perimeter" "regular" {
|
||||
}
|
||||
dynamic "sources" {
|
||||
for_each = flatten([
|
||||
for r in policy.value.from.resources :
|
||||
lookup(var.factories_config.context.resource_sets, r, [r])
|
||||
for r in policy.value.from.resources : try(
|
||||
var.factories_config.context.resource_sets[r],
|
||||
[local.project_number[r]], [r]
|
||||
)
|
||||
])
|
||||
iterator = resource
|
||||
content {
|
||||
@@ -304,13 +318,13 @@ resource "google_access_context_manager_service_perimeter" "regular" {
|
||||
}
|
||||
|
||||
dynamic "ingress_policies" {
|
||||
for_each = status.value.ingress_policies == null ? {} : {
|
||||
for_each = status.value.ingress_policies == null ? [] : [
|
||||
for k in status.value.ingress_policies :
|
||||
k => local.ingress_policies[k]
|
||||
}
|
||||
merge(local.ingress_policies[k], { key = k })
|
||||
]
|
||||
iterator = policy
|
||||
content {
|
||||
title = coalesce(policy.value.title, policy.key)
|
||||
title = coalesce(policy.value.title, policy.value.key)
|
||||
dynamic "ingress_from" {
|
||||
for_each = policy.value.from == null ? [] : [""]
|
||||
content {
|
||||
@@ -331,8 +345,10 @@ resource "google_access_context_manager_service_perimeter" "regular" {
|
||||
}
|
||||
dynamic "sources" {
|
||||
for_each = flatten([
|
||||
for r in policy.value.from.resources :
|
||||
lookup(var.factories_config.context.resource_sets, r, [r])
|
||||
for r in policy.value.from.resources : try(
|
||||
var.factories_config.context.resource_sets[r],
|
||||
[local.project_number[r]], [r]
|
||||
)
|
||||
])
|
||||
content {
|
||||
resource = sources.value
|
||||
@@ -344,8 +360,10 @@ resource "google_access_context_manager_service_perimeter" "regular" {
|
||||
for_each = policy.value.to == null ? [] : [""]
|
||||
content {
|
||||
resources = flatten([
|
||||
for r in policy.value.to.resources :
|
||||
lookup(var.factories_config.context.resource_sets, r, [r])
|
||||
for r in policy.value.to.resources : try(
|
||||
var.factories_config.context.resource_sets[r],
|
||||
[local.project_number[r]], [r]
|
||||
)
|
||||
])
|
||||
roles = policy.value.to.roles
|
||||
dynamic "operations" {
|
||||
@@ -373,7 +391,7 @@ resource "google_access_context_manager_service_perimeter" "regular" {
|
||||
}
|
||||
|
||||
dynamic "vpc_accessible_services" {
|
||||
for_each = status.value.vpc_accessible_services == null ? {} : { 1 = 1 }
|
||||
for_each = status.value.vpc_accessible_services == null ? [] : [""]
|
||||
content {
|
||||
allowed_services = flatten([
|
||||
for r in status.value.vpc_accessible_services.allowed_services :
|
||||
@@ -1,64 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
# tfdoc:file:description Bridge service perimeter resources.
|
||||
|
||||
# this code implements "additive" service perimeters, if "authoritative"
|
||||
# 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 = local.bridge_perimeters
|
||||
parent = "accessPolicies/${local.access_policy}"
|
||||
name = "accessPolicies/${local.access_policy}/servicePerimeters/${each.key}"
|
||||
title = each.key
|
||||
perimeter_type = "PERIMETER_TYPE_BRIDGE"
|
||||
use_explicit_dry_run_spec = each.value.use_explicit_dry_run_spec
|
||||
|
||||
dynamic "spec" {
|
||||
for_each = each.value.spec_resources == null ? [] : [""]
|
||||
content {
|
||||
resources = flatten([
|
||||
for r in each.value.spec_resources :
|
||||
lookup(var.factories_config.context.resource_sets, r, [r])
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
# ignore_changes = [spec[0].resources, status[0].resources]
|
||||
# }
|
||||
|
||||
depends_on = [
|
||||
google_access_context_manager_access_policy.default,
|
||||
google_access_context_manager_access_level.basic,
|
||||
google_access_context_manager_service_perimeter.regular
|
||||
]
|
||||
}
|
||||
@@ -127,7 +127,6 @@ variable "factories_config" {
|
||||
description = "Paths to folders that enable factory functionality."
|
||||
type = object({
|
||||
access_levels = optional(string)
|
||||
bridges = optional(string)
|
||||
egress_policies = optional(string)
|
||||
ingress_policies = optional(string)
|
||||
perimeters = optional(string)
|
||||
@@ -219,19 +218,7 @@ 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)
|
||||
}))
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "service_perimeters_regular" {
|
||||
variable "perimeters" {
|
||||
description = "Regular service perimeters."
|
||||
type = map(object({
|
||||
description = optional(string)
|
||||
@@ -263,3 +250,9 @@ variable "service_perimeters_regular" {
|
||||
default = {}
|
||||
nullable = false
|
||||
}
|
||||
|
||||
variable "project_id_search_scope" {
|
||||
description = "Set this to an organization or folder ID to use Cloud Asset Inventory to automatically translate project ids to numbers."
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
@@ -73,7 +73,25 @@ values:
|
||||
service_name: storage.googleapis.com
|
||||
resources:
|
||||
- projects/123456789
|
||||
roles: []
|
||||
title: gcs-sa-foo
|
||||
ingress_policies:
|
||||
- ingress_from:
|
||||
- identities:
|
||||
- serviceAccount:test-tf@myproject.iam.gserviceaccount.com
|
||||
identity_type: null
|
||||
sources:
|
||||
- resource: null
|
||||
ingress_to:
|
||||
- operations:
|
||||
- method_selectors: []
|
||||
service_name: '*'
|
||||
resources:
|
||||
- projects/1234567890
|
||||
- projects/321
|
||||
- projects/654
|
||||
roles: []
|
||||
title: sa-tf-test-geo
|
||||
- ingress_from:
|
||||
- identities:
|
||||
- serviceAccount:test-tf-0@myproject.iam.gserviceaccount.com
|
||||
@@ -92,20 +110,8 @@ values:
|
||||
service_name: compute.googleapis.com
|
||||
resources:
|
||||
- '*'
|
||||
- ingress_from:
|
||||
- identities:
|
||||
- serviceAccount:test-tf@myproject.iam.gserviceaccount.com
|
||||
identity_type: null
|
||||
sources:
|
||||
- resource: null
|
||||
ingress_to:
|
||||
- operations:
|
||||
- method_selectors: []
|
||||
service_name: '*'
|
||||
resources:
|
||||
- projects/1234567890
|
||||
- projects/321
|
||||
- projects/654
|
||||
roles: []
|
||||
title: sa-tf-test
|
||||
resources:
|
||||
- projects/1111
|
||||
- projects/2222
|
||||
|
||||
@@ -75,22 +75,6 @@ values:
|
||||
roles: null
|
||||
title: gcs-sa-foo
|
||||
ingress_policies:
|
||||
- ingress_from:
|
||||
- identities:
|
||||
- serviceAccount:test-tf-2@myproject.iam.gserviceaccount.com
|
||||
identity_type: null
|
||||
sources:
|
||||
- access_level: '*'
|
||||
resource: null
|
||||
ingress_to:
|
||||
- operations:
|
||||
- method_selectors: []
|
||||
service_name: '*'
|
||||
resources:
|
||||
- '*'
|
||||
roles:
|
||||
- roles/storage.objectViewer
|
||||
title: sa-roles
|
||||
- ingress_from:
|
||||
- identities:
|
||||
- serviceAccount:test-tf-0@myproject.iam.gserviceaccount.com
|
||||
@@ -107,6 +91,22 @@ values:
|
||||
- '*'
|
||||
roles: null
|
||||
title: sa-tf-test
|
||||
- ingress_from:
|
||||
- identities:
|
||||
- serviceAccount:test-tf-2@myproject.iam.gserviceaccount.com
|
||||
identity_type: null
|
||||
sources:
|
||||
- access_level: '*'
|
||||
resource: null
|
||||
ingress_to:
|
||||
- operations:
|
||||
- method_selectors: []
|
||||
service_name: '*'
|
||||
resources:
|
||||
- '*'
|
||||
roles:
|
||||
- roles/storage.objectViewer
|
||||
title: sa-roles
|
||||
resources:
|
||||
- projects/1111
|
||||
- projects/2222
|
||||
|
||||
Reference in New Issue
Block a user