Skip to content

Latest commit

 

History

History
 
 

vpc-sc

VPC Service Controls

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 permissions issues, make sure to check out the recommended provider configuration in the VPC SC resources documentation.

Examples

Access policy

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=0

If 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.yaml

Scoped policy

If 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.yaml

Access policy IAM

The 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:[email protected]"
    ]
  }
}
# tftest modules=1 resources=1

Access levels

As 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:[email protected]"] }
      ]
    }
    a2 = {
      combining_function = "OR"
      conditions = [
        { regions = ["IT", "FR"] },
        { ip_subnetworks = ["101.101.101.0/24"] }
      ]
    }
  }
}
# 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 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.

/* Resources for both perimeters have a lifecycle block that ignores changes to spec and status resources (projects), to allow using the additive resource google_access_context_manager_service_perimeter_resource at project creation. If this is not needed, the lifecycle blocks can be safely commented in the code. */

Bridge type

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

module "test" {
  source        = "./fabric/modules/vpc-sc"
  access_policy = "12345678"
  access_levels = {
    a1 = {
      conditions = [
        { members = ["user:[email protected]"] }
      ]
    }
    a2 = {
      conditions = [
        { members = ["user:[email protected]"] }
      ]
    }
  }
  egress_policies = {
    # allow writing to external GCS bucket from a specific SA
    gcs-sa-foo = {
      from = {
        identities = [
          "serviceAccount:[email protected]"
        ]
      }
      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:[email protected]",
          "serviceAccount:[email protected]"
        ]
        access_levels = ["*"]
      }
      to = {
        operations = [{ service_name = "*" }]
        resources  = ["*"]
      }
    }
  }
  service_perimeters_regular = {
    r1 = {
      status = {
        access_levels       = ["a1", "a2"]
        resources           = ["projects/11111", "projects/111111"]
        restricted_services = ["storage.googleapis.com"]
        egress_policies     = ["gcs-sa-foo"]
        ingress_policies    = ["sa-tf-test"]
        vpc_accessible_services = {
          allowed_services   = ["storage.googleapis.com"]
          enable_restriction = true
        }
      }
    }
  }
}
# tftest modules=1 resources=3 inventory=regular.yaml

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.

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.

This is an example that uses all three factories. Note that the factory configuration points to folders, where each file represents one resource.

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"
  }
  service_perimeters_regular = {
    r1 = {
      status = {
        access_levels       = ["geo-it", "identity-user1"]
        resources           = ["projects/11111", "projects/111111"]
        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
conditions:
  - members:
    - user:[email protected]
# tftest-file id=a1 path=data/access-levels/identity-user1.yaml schema=access-level.schema.json
conditions:
  - regions:
      - IT
# tftest-file id=a2 path=data/access-levels/geo-it.yaml schema=access-level.schema.json
from:
  identities:
    - serviceAccount:[email protected]
    - serviceAccount:[email protected]
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.json
from:
  access_levels:
    - "*"
  identities:
    - serviceAccount:[email protected]
    - serviceAccount:[email protected]
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.json
from:
  access_levels:
    - geo-it
  identities:
    - serviceAccount:[email protected]
to:
  operations:
    - service_name: "*"
  resources:
    - projects/1234567890
# tftest-file id=i2 path=data/ingress-policies/sa-tf-test-geo.yaml schema=ingress-policy.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

Files

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.
service-perimeters-bridge.tf Bridge service perimeter resources. google_access_context_manager_service_perimeter
service-perimeters-regular.tf Regular service perimeter resources. google_access_context_manager_service_perimeter
variables.tf Module variables.
versions.tf Version pins.

Variables

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
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({…})) {}
service_perimeters_bridge Bridge service perimeters. map(object({…})) {}
service_perimeters_regular Regular service perimeters. map(object({…})) {}

Outputs

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.
service_perimeters_bridge Bridge service perimeter resources.
service_perimeters_regular Regular service perimeter resources.

Tests

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:[email protected]"
        ]
        access_levels = ["*"]
      }
      to = {
        operations = [{ service_name = "*" }]
        resources  = ["*"]
      }
    }
  }
  service_perimeters_regular = {
    default = {
      status = {
        access_levels    = ["geo-it"]
        resources        = ["projects/11111"]
        egress_policies  = ["variable-policy", "factory-egress-policy"]
        ingress_policies = ["variable-policy", "factory-ingress-policy"]
      }
    }
  }
}
# tftest modules=1 resources=2 files=t1a1,t1i1,t1e1
conditions:
  - regions:
      - IT
# tftest-file id=t1a1 path=data/access-levels/geo-it.yaml schema=access-level.schema.json
from:
  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.json
from:
  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