This module offers a unified interface to manage VPC Service Controls Access Policy, Access Levels, and Service Perimeters.
Given the complexity of the underlying resources, the module intentionally mimics their interfaces to make it easier to map their documentation onto its variables, and reduce the internal complexity.
If you are using Application Default Credentials with Terraform and run into permission issues, make sure to check out the recommended provider configuration in the VPC SC resources documentation.
- Examples
- Automatic Project ID to Project Number Conversion
- Factories
- Notes
- Files
- Variables
- Outputs
- Tests
By default, the module is configured to use an existing policy, passed in by name in the access_policy variable:
module "test" {
source = "./fabric/modules/vpc-sc"
access_policy = "12345678"
}
# tftest modules=0 resources=0If you need the module to create the policy for you, use the access_policy_create variable, and set access_policy to null:
module "test" {
source = "./fabric/modules/vpc-sc"
access_policy = null
access_policy_create = {
parent = "organizations/123456"
title = "vpcsc-policy"
}
}
# tftest modules=1 resources=1 inventory=access-policy.yamlIf you need the module to create a scoped policy for you, specify 'scopes' of the policy in the access_policy_create variable:
module "test" {
source = "./fabric/modules/vpc-sc"
access_policy = null
access_policy_create = {
parent = "organizations/123456"
title = "vpcsc-policy"
scopes = ["folders/456789"]
}
}
# tftest modules=1 resources=1 inventory=scoped-access-policy.yamlThe usual IAM interface is also implemented here, and can be used with service accounts or user principals:
module "test" {
source = "./fabric/modules/vpc-sc"
access_policy = "12345678"
iam = {
"roles/accesscontextmanager.policyAdmin" = [
"user:foo@example.org"
]
}
}
# tftest modules=1 resources=1As highlighted above, the access_levels type replicates the underlying resource structure.
module "test" {
source = "./fabric/modules/vpc-sc"
access_policy = "12345678"
access_levels = {
a1 = {
conditions = [
{ members = ["user:user1@example.com"] }
]
}
a2 = {
combining_function = "OR"
conditions = [
{ regions = ["IT", "FR"] },
{ ip_subnetworks = ["101.101.101.0/24"] }
]
}
}
}
# tftest modules=1 resources=2 inventory=access-levels.yamlPerimeters are defined via perimeters variable, or the dedicated factory.
Perimeters by default manage all their attributes authoritatively. To have perimeter resources managed externally (e.g. from the project factory) set the perimeter-level attribute ignore_resource_changes at the perimeter level.
module "test" {
source = "./fabric/modules/vpc-sc"
access_policy = "12345678"
access_levels = {
a1 = {
conditions = [
{ members = ["user:user1@example.com"] }
]
}
a2 = {
conditions = [
{ members = ["user:user2@example.com"] }
]
}
}
egress_policies = {
# allow writing to external GCS bucket from a specific SA
gcs-sa-foo = {
from = {
identities = [
"serviceAccount:foo@myproject.iam.gserviceaccount.com"
]
}
to = {
operations = [{
method_selectors = ["*"]
service_name = "storage.googleapis.com"
}]
resources = ["projects/123456789"]
}
}
}
ingress_policies = {
# allow management from external automation SA
sa-tf-test = {
from = {
identities = [
"serviceAccount:test-tf-0@myproject.iam.gserviceaccount.com",
"serviceAccount:test-tf-1@myproject.iam.gserviceaccount.com"
]
access_levels = ["$access_levels:a1"]
}
to = {
operations = [{ service_name = "*" }]
resources = ["*"]
}
}
sa-roles = {
from = {
identities = [
"serviceAccount:test-tf-2@myproject.iam.gserviceaccount.com",
]
access_levels = ["*"]
}
to = {
operations = [{ service_name = "*" }]
resources = ["*"]
roles = ["roles/storage.objectViewer"]
}
}
}
perimeters = {
r1 = {
status = {
access_levels = ["$access_levels:a1", "$access_levels:a2"]
resources = ["projects/1111", "projects/2222"]
restricted_services = ["storage.googleapis.com"]
egress_policies = ["$egress_policies:gcs-sa-foo"]
ingress_policies = [
"$ingress_policies:sa-tf-test",
"$ingress_policies:sa-roles"
]
vpc_accessible_services = {
allowed_services = ["storage.googleapis.com"]
enable_restriction = true
}
}
}
}
}
# tftest modules=1 resources=3 inventory=regular.yamlAs 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.
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 = ["$ingress_policies:i1"]
resources = ["projects/my-destionation-project"]
}
use_explicit_dry_run_spec = true
}
}
}
# tftest skip because uses data sourcesThis 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 folder, and can be used to validate input YAML data with validate-yaml or any of the available tools and libraries.
3Note that the factory configuration points to folders, where each file represents one resource.
module "test" {
source = "./fabric/modules/vpc-sc"
access_policy = "12345678"
context = {
resource_sets = {
foo_projects = ["projects/321", "projects/654"]
}
}
factories_config = {
access_levels = "data/access-levels"
egress_policies = "data/egress-policies"
ingress_policies = "data/ingress-policies"
perimeters = "data/perimeters"
}
}
# tftest modules=1 resources=3 files=p1,a1,a2,e1,i1,i2 inventory=factory.yamldescription: Main perimeter
status:
access_levels:
- $access_levels:geo-it
- $access_levels:identity-user1
resources:
- projects/1111
- projects/2222
restricted_services:
- storage.googleapis.com
egress_policies:
- $egress_policies:gcs-sa-foo
ingress_policies:
- $ingress_policies:sa-tf-test-geo
- $ingress_policies: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=perimeter.schema.jsondescription: "Main perimeter"
status:
access_levels:
- $access_levels:geo-it
- $access_levels:identity-user1
resources:
- projects/1111
- projects/2222
restricted_services:
- storage.googleapis.com
egress_policies:
- $egress_policies:gcs-sa-foo
ingress_policies:
- $ingress_policies:sa-tf-test-geo
- $ingress_policies: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=perimeter.schema.jsonconditions:
- members:
- user:user1@example.com
# tftest-file id=a1 path=data/access-levels/identity-user1.yaml schema=access-level.schema.jsonconditions:
- regions:
- IT
# tftest-file id=a2 path=data/access-levels/geo-it.yaml schema=access-level.schema.jsonfrom:
identities:
- serviceAccount:foo@myproject.iam.gserviceaccount.com
- serviceAccount:bar@myproject.iam.gserviceaccount.com
to:
operations:
- method_selectors:
- "*"
service_name: storage.googleapis.com
resources:
- projects/123456789
# tftest-file id=e1 path=data/egress-policies/gcs-sa-foo.yaml schema=egress-policy.schema.jsonfrom:
access_levels:
- "*"
identities:
- serviceAccount:test-tf-0@myproject.iam.gserviceaccount.com
- serviceAccount:test-tf-1@myproject.iam.gserviceaccount.com
to:
operations:
- service_name: compute.googleapis.com
method_selectors:
- ProjectsService.Get
- RegionsService.Get
resources:
- "*"
# tftest-file id=i1 path=data/ingress-policies/sa-tf-test.yaml schema=ingress-policy.schema.jsonfrom:
access_levels:
- $access_levels:geo-it
identities:
- serviceAccount:test-tf@myproject.iam.gserviceaccount.com
to:
operations:
- service_name: "*"
resources:
- projects/1234567890
- $resource_sets:foo_projects
# tftest-file id=i2 path=data/ingress-policies/sa-tf-test-geo.yaml schema=ingress-policy.schema.json- To remove an access level, first remove the binding between perimeter and the access level in
statusand/orspecwithout removing the access level itself. Once you have runterraform apply, you'll then be able to remove the access level and runterraform applyagain.
| name | description | resources |
|---|---|---|
| access-levels.tf | Access level resources. | google_access_context_manager_access_level |
| factory.tf | None | |
| iam.tf | IAM bindings | google_access_context_manager_access_policy_iam_binding · google_access_context_manager_access_policy_iam_member |
| main.tf | Module-level locals and resources. | google_access_context_manager_access_policy |
| outputs.tf | Module outputs. | |
| perimeters-additive.tf | Regular service perimeter resources which ignore resource changes. | google_access_context_manager_service_perimeter |
| perimeters.tf | Regular service perimeter resources. | google_access_context_manager_service_perimeter |
| variables.tf | Module variables. | |
| versions.tf | Version pins. |
| name | description | type | required | default |
|---|---|---|---|---|
| access_policy | Access Policy name, set to null if creating one. | string |
✓ | |
| access_levels | Access level definitions. | map(object({…})) |
{} |
|
| access_policy_create | Access Policy configuration, fill in to create. Parent is in 'organizations/123456' format, scopes are in 'folders/456789' or 'projects/project_id' format. | object({…}) |
null |
|
| context | External context used in replacements. | object({…}) |
{} |
|
| egress_policies | Egress policy definitions that can be referenced in perimeters. | map(object({…})) |
{} |
|
| factories_config | Paths to folders that enable factory functionality. | object({…}) |
{} |
|
| iam | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) |
{} |
|
| iam_bindings | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…})) |
{} |
|
| iam_bindings_additive | Individual additive IAM bindings. Keys are arbitrary. | map(object({…})) |
{} |
|
| ingress_policies | Ingress policy definitions that can be referenced in perimeters. | map(object({…})) |
{} |
|
| perimeters | Regular service perimeters. | map(object({…})) |
{} |
|
| project_id_search_scope | Set this to an organization or folder ID to use Cloud Asset Inventory to automatically translate project ids to numbers. | string |
null |
| name | description | sensitive |
|---|---|---|
| access_level_names | Access level resources. | |
| access_levels | Access level resources. | |
| access_policy | Access policy resource, if autocreated. | |
| access_policy_name | Access policy name. | |
| id | Fully qualified access policy id. | |
| perimeters | Regular service perimeter resources. |
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"
}
ingress_policies = {
variable-policy = {
from = {
identities = [
"serviceAccount:sa-0@myproject.iam.gserviceaccount.com"
]
access_levels = ["*"]
}
to = {
operations = [{ service_name = "*" }]
resources = ["*"]
}
}
}
perimeters = {
default = {
status = {
access_levels = ["geo-it"]
resources = ["projects/1111"]
egress_policies = ["$egress_policies:factory-egress-policy"]
ingress_policies = [
"$ingress_policies:variable-policy",
"$ingress_policies:factory-ingress-policy"
]
}
}
}
}
# tftest modules=1 resources=2 files=t1a1,t1i1,t1e1conditions:
- regions:
- IT
# tftest-file id=t1a1 path=data/access-levels/geo-it.yaml schema=access-level.schema.jsonfrom:
access_levels:
- geo-it
identity_type: ANY_IDENTITY
to:
operations:
- service_name: "*"
resources:
- projects/1234567890
# tftest-file id=t1i1 path=data/ingress-policies/factory-ingress-policy.yaml schema=ingress-policy.schema.jsonfrom:
identity_type: ANY_IDENTITY
to:
operations:
- service_name: "*"
resources:
- "*"
# tftest-file id=t1e1 path=data/egress-policies/factory-egress-policy.yaml schema=egress-policy.schema.json