New vpc-sc module implementation (#406)

* first implementation

* minimal output

* split service perimeters in regular and bridge

* tests and fixes

* new vpc-sc implementation

* remove providers file used for testing

* remove provider used during development
This commit is contained in:
Ludovico Magnocavallo 2021-12-31 13:29:22 +01:00 committed by GitHub
parent c7dd5bc1d6
commit 2c7dab3bb2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 924 additions and 681 deletions

View File

@ -1,236 +1,148 @@
# VPC Service Control Module
# VPC Service Controls
This module allows managing VPC Service Control (VPC-SC) properties:
This module offers a unified interface to manage VPC Service Controls [Access Policy](https://cloud.google.com/access-context-manager/docs/create-access-policy), [Access Levels](https://cloud.google.com/access-context-manager/docs/manage-access-levels), and [Service Perimeters](https://cloud.google.com/vpc-service-controls/docs/service-perimeters).
- [Access Policy](https://cloud.google.com/access-context-manager/docs/create-access-policy)
- [Access Levels](https://cloud.google.com/access-context-manager/docs/manage-access-levels)
- [VPC-SC Perimeters](https://cloud.google.com/vpc-service-controls/docs/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. The tradeoff is some verbosity, and a very complex type for the `service_perimeters_regular` variable (while [optional type attributes](https://www.terraform.io/language/expressions/type-constraints#experimental-optional-object-type-attributes) are still an experiment).
The Use of this module requires credentials with the [correct permissions](https://cloud.google.com/access-context-manager/docs/access-control) to use Access Context Manager.
If you are using [Application Default Credentials](https://cloud.google.com/sdk/gcloud/reference/auth/application-default) with Terraform and run into permissions issues, make sure to check out the recommended provider configuration in the [VPC SC resources documentation](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/access_context_manager_access_level).
## Example VCP-SC standard perimeter
## Examples
### Access policy
By default, the module is configured to use an existing policy, passed in by name in the `access_policy` variable:
```hcl
module "vpc-sc" {
source = "./modules/vpc-sc"
organization_id = "organizations/112233"
access_policy_title = "My Access Policy"
module "test" {
source = "./modules/vpc-sc"
access_policy = "accessPolicies/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`:
```hcl
module "test" {
source = "./modules/vpc-sc"
access_policy = null
access_policy_create = {
parent = "organizations/123456"
title = "vpcsc-policy"
}
}
# tftest:modules=1:resources=1
```
### Access levels
As highlighted above, the `access_levels` type replicates the underlying resource structure.
```hcl
module "test" {
source = "./modules/vpc-sc"
access_policy = "accessPolicies/12345678"
access_levels = {
my_trusted_proxy = {
combining_function = "AND"
a1 = {
combining_function = null
conditions = [{
ip_subnetworks = ["85.85.85.52/32"]
required_access_levels = null
members = []
negate = false
regions = null
members = ["user:ludomagno@google.com"],
device_policy = null, ip_subnetworks = null, negate = null,
regions = null, required_access_levels = null
}]
}
a2 = {
combining_function = "OR"
conditions = [{
regions = ["IT", "FR"],
device_policy = null, ip_subnetworks = null, members = null,
negate = null, required_access_levels = null
},{
ip_subnetworks = ["101.101.101.0/24"],
device_policy = null, members = null, negate = null,
regions = null, required_access_levels = null
}]
}
}
access_level_perimeters = {
enforced = {
my_trusted_proxy = ["perimeter"]
}
}
ingress_policies = {
ingress_1 = {
ingress_from = {
identity_type = "ANY_IDENTITY"
}
ingress_to = {
resources = ["*"]
operations = {
"storage.googleapis.com" = [{ method = "google.storage.objects.create" }]
"bigquery.googleapis.com" = [{ method = "BigQueryStorage.ReadRows" }]
}
}
}
}
ingress_policies_perimeters = {
enforced = {
ingress_1 = ["default"]
}
}
egress_policies = {
egress_1 = {
egress_from = {
identity_type = "ANY_USER_ACCOUNT"
}
egress_to = {
resources = ["*"]
operations = {
"storage.googleapis.com" = [{ method = "google.storage.objects.create" }],
"bigquery.googleapis.com" = [{ method = "BigQueryStorage.ReadRows" },{ method = "TableService.ListTables" }, { permission = "bigquery.jobs.get" }]
}
}
}
}
egress_policies_perimeters = {
enforced = {
egress_1 = ["perimeter"]
}
}
perimeters = {
perimeter = {
type = "PERIMETER_TYPE_REGULAR"
dry_run_config = null
enforced_config = {
restricted_services = ["storage.googleapis.com"]
vpc_accessible_services = ["storage.googleapis.com"]
}
}
}
perimeter_projects = {
perimeter = {
enforced = [111111111, 222222222]
}
}
}
# tftest:modules=1:resources=3
```
## Example VCP-SC standard perimeter with one service and one project in dry run mode
```hcl
module "vpc-sc" {
source = "./modules/vpc-sc"
organization_id = "organizations/112233"
access_policy_title = "My Access Policy"
access_levels = {
my_trusted_proxy = {
combining_function = "AND"
conditions = [{
ip_subnetworks = ["85.85.85.52/32"]
required_access_levels = null
members = []
negate = false
regions = null
}]
}
}
access_level_perimeters = {
enforced = {
my_trusted_proxy = ["perimeter"]
}
}
perimeters = {
perimeter = {
type = "PERIMETER_TYPE_REGULAR"
dry_run_config = {
restricted_services = ["storage.googleapis.com", "bigquery.googleapis.com"]
vpc_accessible_services = ["storage.googleapis.com", "bigquery.googleapis.com"]
}
enforced_config = {
restricted_services = ["storage.googleapis.com"]
vpc_accessible_services = ["storage.googleapis.com"]
}
}
}
perimeter_projects = {
perimeter = {
enforced = [111111111, 222222222]
dry_run = [333333333]
}
}
}
# tftest:modules=1:resources=3
```
## Example VCP-SC: 2 standard perimeters with one bridge between the two (dry run mode).
```hcl
module "vpc-sc" {
source = "./modules/vpc-sc"
organization_id = "organizations/112233"
access_policy_title = "My Access Policy"
perimeters = {
perimeter_1 = {
type = "PERIMETER_TYPE_REGULAR"
dry_run_config = {
restricted_services = ["storage.googleapis.com", "bigquery.googleapis.com"]
vpc_accessible_services = ["storage.googleapis.com", "bigquery.googleapis.com"]
}
enforced_config = null
}
perimeter_2 = {
type = "PERIMETER_TYPE_REGULAR"
dry_run_config = {
restricted_services = ["storage.googleapis.com", "bigquery.googleapis.com"]
vpc_accessible_services = ["storage.googleapis.com", "bigquery.googleapis.com"]
}
enforced_config = null
}
perimeter_bridge = {
type = "PERIMETER_TYPE_BRIDGE"
dry_run_config = null
enforced_config = null
}
}
perimeter_projects = {
perimeter_1 = {
enforced = []
dry_run = [111111111]
}
perimeter_2 = {
enforced = []
dry_run = [222222222]
}
perimeter_bridge = {
enforced = []
dry_run = [111111111, 222222222]
}
}
}
# tftest:modules=1:resources=4
```
## Example VCP-SC standard perimeter with one service and one project in dry run mode in a Organization with an already existent access policy
```hcl
module "vpc-sc-first" {
source = "./modules/vpc-sc"
organization_id = "organizations/112233"
access_policy_create = false
access_policy_name = "My Access Policy"
access_levels = {
my_trusted_proxy = {
combining_function = "AND"
conditions = [{
ip_subnetworks = ["85.85.85.52/32"]
required_access_levels = null
members = []
negate = false
regions = null
}]
}
}
access_level_perimeters = {
enforced = {
my_trusted_proxy = ["perimeter"]
}
}
perimeters = {
perimeter = {
type = "PERIMETER_TYPE_REGULAR"
dry_run_config = {
restricted_services = ["storage.googleapis.com", "bigquery.googleapis.com"]
vpc_accessible_services = ["storage.googleapis.com", "bigquery.googleapis.com"]
}
enforced_config = {
restricted_services = ["storage.googleapis.com"]
vpc_accessible_services = ["storage.googleapis.com"]
}
}
}
perimeter_projects = {
perimeter = {
enforced = [111111111, 222222222]
dry_run = [333333333]
}
}
}
# tftest:modules=1:resources=2
```
### Service perimeters
Bridge and regulare 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, simply use the module's outputs in the provided variables. 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
```hcl
module "test" {
source = "./modules/vpc-sc"
access_policy = "accessPolicies/12345678"
service_perimeters_bridge = {
b1 = {
status_resources = ["projects/111110", "projects/111111"]
spec_resources = null
use_explicit_dry_run_spec = false
}
b2 = {
status_resources = ["projects/222220", "projects/222221"]
spec_resources = ["projects/222220", "projects/222221"]
use_explicit_dry_run_spec = true
}
}
}
# tftest:modules=1:resources=2
```
#### Regular type
```hcl
module "test" {
source = "./modules/vpc-sc"
access_policy = "accessPolicies/12345678"
access_levels = {
a1 = {
combining_function = null
conditions = [{
members = ["user:ludomagno@google.com"],
device_policy = null, ip_subnetworks = null, negate = null,
regions = null, required_access_levels = null
}]
}
}
service_perimeters_regular = {
r1 = {
spec = null
status = {
access_levels = [module.test.access_level_names["a1"]]
resources = ["projects/11111", "projects/111111"]
restricted_services = ["storage.googleapis.com"]
egress_policies = null
ingress_policies = null
vpc_accessible_services = {
allowed_services = ["compute.googleapis.com"]
enable_restriction = true
}
}
use_explicit_dry_run_spec = false
}
}
}
# tftest:modules=1:resources=2
```
## TODO
- [ ] implement support for the `google_access_context_manager_gcp_user_access_binding` resource
<!-- BEGIN TFDOC -->
@ -238,28 +150,24 @@ module "vpc-sc-first" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| organization_id | Organization id in organizations/nnnnnn format. | <code>string</code> | ✓ | |
| access_level_perimeters | Enforced mode -> Access Level -> Perimeters mapping. Enforced mode can be 'enforced' or 'dry_run' | <code>map&#40;map&#40;list&#40;string&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| access_levels | Map of Access Levels to be created. For each Access Level you can specify 'ip_subnetworks, required_access_levels, members, negate or regions'. | <code title="map&#40;object&#40;&#123;&#10; combining_function &#61; string&#10; conditions &#61; list&#40;object&#40;&#123;&#10; ip_subnetworks &#61; list&#40;string&#41;&#10; required_access_levels &#61; list&#40;string&#41;&#10; members &#61; list&#40;string&#41;&#10; negate &#61; string&#10; regions &#61; list&#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> |
| access_policy_create | Enable autocreation of the Access Policy | <code>bool</code> | | <code>true</code> |
| access_policy_name | Referenced Access Policy name | <code>string</code> | | <code>null</code> |
| access_policy_title | Access Policy title to be created. | <code>string</code> | | <code>null</code> |
| egress_policies | List of EgressPolicies in the form described in the [documentation](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/access_context_manager_service_perimeter#egress_policies) | <code></code> | | <code>null</code> |
| egress_policies_perimeters | Enforced mode -> Egress Policy -> Perimeters mapping. Enforced mode can be 'enforced' or 'dry_run' | <code>map&#40;map&#40;list&#40;string&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| ingress_policies | List of IngressPolicies in the form described in the [documentation](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/access_context_manager_service_perimeter#ingress_policies) | <code></code> | | <code>null</code> |
| ingress_policies_perimeters | Enforced mode -> Ingress Policy -> Perimeters mapping. Enforced mode can be 'enforced' or 'dry_run' | <code>map&#40;map&#40;list&#40;string&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| perimeter_projects | Perimeter -> Enforced Mode -> Projects Number mapping. Enforced mode can be 'enforced' or 'dry_run'. | <code>map&#40;map&#40;list&#40;number&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| perimeters | Set of Perimeters. | <code title="map&#40;object&#40;&#123;&#10; type &#61; string&#10; dry_run_config &#61; object&#40;&#123;&#10; restricted_services &#61; list&#40;string&#41;&#10; vpc_accessible_services &#61; list&#40;string&#41;&#10; &#125;&#41;&#10; enforced_config &#61; object&#40;&#123;&#10; restricted_services &#61; list&#40;string&#41;&#10; vpc_accessible_services &#61; list&#40;string&#41;&#10; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| access_policy | Access Policy name, leave null to use auto-created one. | <code>string</code> | ✓ | |
| access_levels | Map of access levels in name => [conditions] format. | <code title="map&#40;object&#40;&#123;&#10; combining_function &#61; string&#10; conditions &#61; list&#40;object&#40;&#123;&#10; device_policy &#61; object&#40;&#123;&#10; require_screen_lock &#61; bool&#10; allowed_encryption_statuses &#61; list&#40;string&#41;&#10; allowed_device_management_levels &#61; list&#40;string&#41;&#10; os_constraints &#61; list&#40;object&#40;&#123;&#10; minimum_version &#61; string&#10; os_type &#61; string&#10; require_verified_chrome_os &#61; bool&#10; &#125;&#41;&#41;&#10; require_admin_approval &#61; bool&#10; require_corp_owned &#61; bool&#10; &#125;&#41;&#10; ip_subnetworks &#61; list&#40;string&#41;&#10; members &#61; list&#40;string&#41;&#10; negate &#61; bool&#10; regions &#61; list&#40;string&#41;&#10; required_access_levels &#61; list&#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> |
| access_policy_create | Access Policy configuration, fill in to create. Parent is in 'organizations/123456' format. | <code title="object&#40;&#123;&#10; parent &#61; string&#10; title &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| service_perimeters_bridge | Bridge service perimeters. | <code title="map&#40;object&#40;&#123;&#10; spec_resources &#61; list&#40;string&#41;&#10; status_resources &#61; list&#40;string&#41;&#10; use_explicit_dry_run_spec &#61; bool&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| service_perimeters_regular | Regular service perimeters. | <code title="map&#40;object&#40;&#123;&#10; spec &#61; object&#40;&#123;&#10; access_levels &#61; list&#40;string&#41;&#10; resources &#61; list&#40;string&#41;&#10; restricted_services &#61; list&#40;string&#41;&#10; egress_policies &#61; list&#40;object&#40;&#123;&#10; egress_from &#61; object&#40;&#123;&#10; identity_type &#61; string&#10; identities &#61; list&#40;string&#41;&#10; &#125;&#41;&#10; egress_to &#61; object&#40;&#123;&#10; operations &#61; list&#40;object&#40;&#123;&#10; method_selectors &#61; list&#40;string&#41;&#10; service_name &#61; string&#10; &#125;&#41;&#41;&#10; resources &#61; list&#40;string&#41;&#10; &#125;&#41;&#10; &#125;&#41;&#41;&#10; ingress_policies &#61; list&#40;object&#40;&#123;&#10; ingress_from &#61; object&#40;&#123;&#10; identity_type &#61; string&#10; identities &#61; list&#40;string&#41;&#10; source_access_levels &#61; list&#40;string&#41;&#10; source_resources &#61; list&#40;string&#41;&#10; &#125;&#41;&#10; ingress_to &#61; object&#40;&#123;&#10; operations &#61; list&#40;object&#40;&#123;&#10; method_selectors &#61; list&#40;string&#41;&#10; service_name &#61; string&#10; &#125;&#41;&#41;&#10; resources &#61; list&#40;string&#41;&#10; &#125;&#41;&#10; &#125;&#41;&#41;&#10; vpc_accessible_services &#61; object&#40;&#123;&#10; allowed_services &#61; list&#40;string&#41;&#10; enable_restriction &#61; bool&#10; &#125;&#41;&#10; &#125;&#41;&#10; status &#61; object&#40;&#123;&#10; access_levels &#61; list&#40;string&#41;&#10; resources &#61; list&#40;string&#41;&#10; restricted_services &#61; list&#40;string&#41;&#10; egress_policies &#61; list&#40;object&#40;&#123;&#10; egress_from &#61; object&#40;&#123;&#10; identity_type &#61; string&#10; identities &#61; list&#40;string&#41;&#10; &#125;&#41;&#10; egress_to &#61; object&#40;&#123;&#10; operations &#61; list&#40;object&#40;&#123;&#10; method_selectors &#61; list&#40;string&#41;&#10; service_name &#61; string&#10; &#125;&#41;&#41;&#10; resources &#61; list&#40;string&#41;&#10; &#125;&#41;&#10; &#125;&#41;&#41;&#10; ingress_policies &#61; list&#40;object&#40;&#123;&#10; ingress_from &#61; object&#40;&#123;&#10; identity_type &#61; string&#10; identities &#61; list&#40;string&#41;&#10; source_access_levels &#61; list&#40;string&#41;&#10; source_resources &#61; list&#40;string&#41;&#10; &#125;&#41;&#10; ingress_to &#61; object&#40;&#123;&#10; operations &#61; list&#40;object&#40;&#123;&#10; method_selectors &#61; list&#40;string&#41;&#10; service_name &#61; string&#10; &#125;&#41;&#41;&#10; resources &#61; list&#40;string&#41;&#10; &#125;&#41;&#10; &#125;&#41;&#41;&#10; vpc_accessible_services &#61; object&#40;&#123;&#10; allowed_services &#61; list&#40;string&#41;&#10; enable_restriction &#61; bool&#10; &#125;&#41;&#10; &#125;&#41;&#10; use_explicit_dry_run_spec &#61; bool&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
## Outputs
| name | description | sensitive |
|---|---|:---:|
| access_levels | Access Levels. | |
| access_policy_name | Access Policy resource | |
| organization_id | Organization id dependent on module resources. | |
| perimeters_bridge | VPC-SC bridge perimeter resources. | |
| perimeters_standard | VPC-SC standard perimeter resources. | |
| access_level_names | Access level resources. | |
| access_levels | Access level resources. | |
| access_policy | Access policy resource, if autocreated. | |
| access_policy_name | Access policy name. | |
| service_perimeters_bridge | Bridge service perimeter resources. | |
| service_perimeters_regular | Regular service perimeter resources. | |
<!-- END TFDOC -->

View File

@ -0,0 +1,78 @@
/**
* Copyright 2021 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.
*/
# TODO(ludomagno): add a second variable and resource for custom access levels
# this code implements "additive" access levels, if "authoritative"
# access levels are needed, switch to the
# google_access_context_manager_access_levels resource
resource "google_access_context_manager_access_level" "basic" {
for_each = var.access_levels
parent = "accessPolicies/${local.access_policy}"
name = "accessPolicies/${local.access_policy}/accessLevels/${each.key}"
title = each.key
basic {
combining_function = each.value.combining_function
dynamic "conditions" {
for_each = toset(
each.value.conditions == null ? [] : each.value.conditions
)
iterator = condition
content {
dynamic "device_policy" {
for_each = toset(
condition.key.device_policy == null ? [] : [condition.key.device_policy]
)
iterator = device_policy
content {
dynamic "os_constraints" {
for_each = toset(
device_policy.key.os_constraints == null ? [] : device_policy.key.os_constraints
)
iterator = os_constraint
content {
minimum_version = os_constraint.key.minimum_version
os_type = os_constraint.key.os_type
require_verified_chrome_os = os_constraint.key.require_verified_chrome_os
}
}
allowed_encryption_statuses = device_policy.key.allowed_encryption_statuses
allowed_device_management_levels = device_policy.key.allowed_device_management_levels
require_admin_approval = device_policy.key.require_admin_approval
require_corp_owned = device_policy.key.require_corp_owned
require_screen_lock = device_policy.key.require_screen_lock
}
}
ip_subnetworks = (
condition.key.ip_subnetworks == null ? [] : condition.key.ip_subnetworks
)
members = (
condition.key.members == null ? [] : condition.key.members
)
negate = condition.key.negate
regions = (
condition.key.regions == null ? [] : condition.key.regions
)
required_access_levels = (
condition.key.required_access_levels == null
? []
: condition.key.required_access_levels
)
}
}
}
}

View File

@ -15,340 +15,14 @@
*/
locals {
access_policy_name = (
var.access_policy_create
? try(google_access_context_manager_access_policy.default[0].name, null)
: var.access_policy_name
access_policy = try(
google_access_context_manager_access_policy.default.0.name,
var.access_policy
)
standard_perimeters = {
for key, value in var.perimeters :
key => value if value.type == "PERIMETER_TYPE_REGULAR"
}
bridge_perimeters = {
for key, value in var.perimeters :
key => value if value.type == "PERIMETER_TYPE_BRIDGE"
}
perimeter_access_levels_enforced = try(transpose(var.access_level_perimeters.enforced), null)
perimeter_access_levels_dry_run = try(transpose(var.access_level_perimeters.dry_run), null)
perimeter_ingress_policies_enforced = try(transpose(var.ingress_policies_perimeters.enforced), null)
perimeter_ingress_policies_dry_run = try(transpose(var.ingress_policies_perimeters.dry_run), null)
perimeter_egress_policies_enforced = try(transpose(var.egress_policies_perimeters.enforced), null)
perimeter_egress_policies_dry_run = try(transpose(var.egress_policies_perimeters.dry_run), null)
}
resource "google_access_context_manager_access_policy" "default" {
count = var.access_policy_create ? 1 : 0
parent = var.organization_id
title = var.access_policy_title == null ? "${var.organization_id}-title" : var.access_policy_title
}
resource "google_access_context_manager_access_level" "default" {
for_each = var.access_levels
parent = "accessPolicies/${local.access_policy_name}"
name = "accessPolicies/${local.access_policy_name}/accessLevels/${each.key}"
title = each.key
dynamic "basic" {
for_each = try(toset(each.value.conditions), [])
iterator = condition
content {
combining_function = try(each.value.combining_function, null)
conditions {
ip_subnetworks = try(condition.value.ip_subnetworks, null)
required_access_levels = try(condition.value.required_access_levels, null)
members = try(condition.value.members, null)
negate = try(condition.value.negate, null)
regions = try(condition.value.regions, null)
}
}
}
}
resource "google_access_context_manager_service_perimeter" "standard" {
for_each = local.standard_perimeters
parent = "accessPolicies/${local.access_policy_name}"
description = "Terraform managed."
name = "accessPolicies/${local.access_policy_name}/servicePerimeters/${each.key}"
title = each.key
perimeter_type = each.value.type
# Enforced mode configuration
dynamic "status" {
for_each = each.value.enforced_config != null ? [""] : []
content {
resources = formatlist(
"projects/%s", try(lookup(var.perimeter_projects, each.key, {}).enforced, [])
)
restricted_services = each.value.enforced_config.restricted_services
access_levels = formatlist(
"accessPolicies/${local.access_policy_name}/accessLevels/%s",
try(lookup(local.perimeter_access_levels_enforced, each.key, []), [])
)
dynamic "vpc_accessible_services" {
for_each = try(length(each.value.enforced_config.vpc_accessible_services) != 0 ? [""] : [], [])
content {
enable_restriction = true
allowed_services = each.value.enforced_config.vpc_accessible_services
}
}
dynamic "egress_policies" {
for_each = try(local.perimeter_egress_policies_enforced[each.key] != null ? local.perimeter_egress_policies_enforced[each.key] : [], [])
content {
dynamic "egress_from" {
for_each = try(var.egress_policies[egress_policies.value].egress_from != null ? [""] : [], [])
content {
identity_type = try(var.egress_policies[egress_policies.value].egress_from.identity_type, null)
identities = try(var.egress_policies[egress_policies.value].egress_from.identities, null)
}
}
dynamic "egress_to" {
for_each = try(var.egress_policies[egress_policies.value].egress_to != null ? [""] : [], [])
content {
resources = try(var.egress_policies[egress_policies.value].egress_to.resources, null)
dynamic "operations" {
for_each = try(var.egress_policies[egress_policies.value].egress_to.operations, [])
content {
service_name = try(operations.key, null)
dynamic "method_selectors" {
for_each = try(operations.value, [])
content {
method = try(method_selectors.value.method, null)
permission = try(method_selectors.value.permission, null)
}
}
}
}
}
}
}
}
dynamic "ingress_policies" {
for_each = try(local.perimeter_ingress_policies_enforced[each.key] != null ? local.perimeter_ingress_policies_enforced[each.key] : [], [])
content {
dynamic "ingress_from" {
for_each = try(var.ingress_policies[ingress_policies.value].ingress_from != null ? [""] : [], [])
content {
identity_type = try(var.ingress_policies[ingress_policies.value].ingress_from.identity_type, null)
identities = try(var.ingress_policies[ingress_policies.value].ingress_from.identities, null)
dynamic "sources" {
for_each = toset(try([var.ingress_policies[ingress_policies.value].ingress_from.sources], []))
content {
access_level = try(sources.value.access_level, null)
resource = try(sources.value.resource, null)
}
}
}
}
dynamic "ingress_to" {
for_each = try(var.ingress_policies[ingress_policies.value].ingress_to != null ? [""] : [], [])
content {
resources = try(var.ingress_policies[ingress_policies.value].ingress_to.resources, null)
dynamic "operations" {
for_each = try(var.ingress_policies[ingress_policies.value].ingress_to.operations, [])
content {
service_name = try(operations.key, null)
dynamic "method_selectors" {
for_each = try(operations.value, [])
content {
method = try(method_selectors.value.method, null)
permission = try(method_selectors.value.permission, null)
}
}
}
}
}
}
}
}
}
}
# Dry run mode configuration
use_explicit_dry_run_spec = each.value.dry_run_config != null ? true : false
dynamic "spec" {
for_each = each.value.dry_run_config != null ? [""] : []
content {
resources = formatlist(
"projects/%s", try(lookup(var.perimeter_projects, each.key, {}).dry_run, [])
)
restricted_services = try(each.value.dry_run_config.restricted_services, null)
access_levels = formatlist(
"accessPolicies/${local.access_policy_name}/accessLevels/%s",
try(lookup(local.perimeter_access_levels_dry_run, each.key, []), [])
)
dynamic "vpc_accessible_services" {
for_each = try(length(each.value.dry_run_config.vpc_accessible_services) != 0 ? [""] : [], [])
content {
enable_restriction = true
allowed_services = try(each.value.dry_run_config.vpc_accessible_services, null)
}
}
dynamic "egress_policies" {
for_each = try(local.perimeter_egress_policies_dry_run[each.key] != null ? local.perimeter_egress_policies_dry_run[each.key] : [], [])
content {
dynamic "egress_from" {
for_each = try(var.egress_policies[egress_policies.value].egress_from != null ? [""] : [], [])
content {
identity_type = try(var.egress_policies[egress_policies.value].egress_from.identity_type, null)
identities = try(var.egress_policies[egress_policies.value].egress_from.identities, null)
}
}
dynamic "egress_to" {
for_each = try(var.egress_policies[egress_policies.value].egress_to != null ? [""] : [], [])
content {
resources = try(var.egress_policies[egress_policies.value].egress_to.resources, null)
dynamic "operations" {
for_each = try(var.egress_policies[egress_policies.value].egress_to.operations, [])
content {
service_name = try(operations.key, null)
dynamic "method_selectors" {
for_each = try(operations.value, [])
content {
method = try(method_selectors.value.method, null)
permission = try(method_selectors.value.permission, null)
}
}
}
}
}
}
}
}
dynamic "ingress_policies" {
for_each = try(local.perimeter_ingress_policies_dry_run[each.key] != null ? local.perimeter_ingress_policies_dry_run[each.key] : [], [])
content {
dynamic "ingress_from" {
for_each = try(var.ingress_policies[ingress_policies.value].ingress_from != null ? [""] : [], [])
content {
identity_type = try(var.ingress_policies[ingress_policies.value].ingress_from.identity_type, null)
identities = try(var.ingress_policies[ingress_policies.value].ingress_from.identities, null)
dynamic "sources" {
for_each = toset(try([var.ingress_policies[ingress_policies.value].ingress_from.sources], []))
content {
access_level = try(sources.value.access_level, null)
resource = try(sources.value.resource, null)
}
}
}
}
dynamic "ingress_to" {
for_each = try(var.ingress_policies[ingress_policies.value].ingress_to != null ? [""] : [], [])
content {
resources = try(var.ingress_policies[ingress_policies.value].ingress_to.resources, null)
dynamic "operations" {
for_each = try(var.ingress_policies[ingress_policies.value].ingress_to.operations, [])
content {
service_name = try(operations.key, null)
dynamic "method_selectors" {
for_each = try(operations.value, [])
content {
method = try(method_selectors.value.method, null)
permission = try(method_selectors.value.permission, null)
}
}
}
}
}
}
}
}
}
}
# Uncomment if used alongside `google_access_context_manager_service_perimeter_resource`,
# so they don't fight over which resources should be in the policy.
# lifecycle {
# ignore_changes = [status[0].resources]
# }
depends_on = [
google_access_context_manager_access_level.default,
]
}
resource "google_access_context_manager_service_perimeter" "bridge" {
for_each = local.bridge_perimeters
parent = "accessPolicies/${local.access_policy_name}"
description = "Terraform managed."
name = "accessPolicies/${local.access_policy_name}/servicePerimeters/${each.key}"
title = each.key
perimeter_type = each.value.type
# Enforced mode configuration
dynamic "status" {
for_each = try(lookup(var.perimeter_projects, each.key, {}).enforced, []) != null ? [""] : []
content {
resources = formatlist("projects/%s", try(lookup(var.perimeter_projects, each.key, {}).enforced, []))
}
}
# Dry run mode configuration
use_explicit_dry_run_spec = try(lookup(var.perimeter_projects, each.key, null).dry_run, null) != null ? true : null
dynamic "spec" {
for_each = try(lookup(var.perimeter_projects, each.key, null).dry_run, null) != null ? [""] : []
content {
resources = try(formatlist("projects/%s", lookup(var.perimeter_projects, each.key, {}).dry_run), null)
restricted_services = []
access_levels = []
}
}
# Uncomment if used alongside `google_access_context_manager_service_perimeter_resource`,
# so they don't fight over which resources should be in the policy.
# lifecycle {
# ignore_changes = [status[0].resources]
# }
depends_on = [
google_access_context_manager_service_perimeter.standard,
google_access_context_manager_access_level.default,
]
count = var.access_policy_create != null ? 1 : 0
parent = var.access_policy_create.parent
title = var.access_policy_create.title
}

View File

@ -14,39 +14,35 @@
* limitations under the License.
*/
output "access_levels" {
description = "Access Levels."
output "access_level_names" {
description = "Access level resources."
value = {
for key, value in google_access_context_manager_access_level.default :
key => value
for k, v in google_access_context_manager_access_level.basic :
k => v.name
}
}
output "access_levels" {
description = "Access level resources."
value = google_access_context_manager_access_level.basic
}
output "access_policy" {
description = "Access policy resource, if autocreated."
value = try(google_access_context_manager_access_policy.default.0, null)
}
output "access_policy_name" {
description = "Access Policy resource"
value = local.access_policy_name
description = "Access policy name."
value = local.access_policy
}
output "organization_id" {
description = "Organization id dependent on module resources."
value = var.organization_id
depends_on = [
google_access_context_manager_access_policy.default
]
output "service_perimeters_bridge" {
description = "Bridge service perimeter resources."
value = google_access_context_manager_service_perimeter.bridge
}
output "perimeters_bridge" {
description = "VPC-SC bridge perimeter resources."
value = {
for key, value in google_access_context_manager_service_perimeter.bridge :
key => value
}
}
output "perimeters_standard" {
description = "VPC-SC standard perimeter resources."
value = {
for key, value in google_access_context_manager_service_perimeter.standard :
key => value
}
output "service_perimeters_regular" {
description = "Regular service perimeter resources."
value = google_access_context_manager_service_perimeter.regular
}

View File

@ -0,0 +1,41 @@
/**
* Copyright 2021 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.
*/
# this code implements "additive" service perimeters, if "authoritative"
# service perimeters are needed, switch to the
# google_access_context_manager_service_perimeters resource
resource "google_access_context_manager_service_perimeter" "bridge" {
for_each = var.service_perimeters_bridge
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
spec {
resources = each.value.spec_resources
}
status {
resources = each.value.status_resources
}
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
]
}

View File

@ -0,0 +1,311 @@
/**
* Copyright 2021 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.
*/
# this code implements "additive" service perimeters, if "authoritative"
# service perimeters are needed, switch to the
# google_access_context_manager_service_perimeters resource
resource "google_access_context_manager_service_perimeter" "regular" {
for_each = var.service_perimeters_regular
parent = "accessPolicies/${local.access_policy}"
name = "accessPolicies/${local.access_policy}/servicePerimeters/${each.key}"
title = each.key
perimeter_type = "PERIMETER_TYPE_REGULAR"
use_explicit_dry_run_spec = each.value.use_explicit_dry_run_spec
dynamic "spec" {
for_each = each.value.spec == null ? {} : { 1 = 1 }
content {
access_levels = each.value.spec.access_levels
resources = each.value.spec.resources
restricted_services = each.value.spec.restricted_services
# begin egress_policies
dynamic "egress_policies" {
for_each = toset(
each.value.spec.egress_policies == null
? []
: each.value.spec.egress_policies
)
iterator = policy
content {
# begin egress_from
dynamic "egress_from" {
for_each = policy.key.egress_from == null ? {} : { 1 = 1 }
content {
identity_type = policy.key.egress_from.identity_type
identities = policy.key.egress_from.identities
}
}
# end egress_from
# begin egress_to
dynamic "egress_to" {
for_each = policy.key.egress_to == null ? {} : { 1 = 1 }
content {
resources = policy.key.egress_to.resources
dynamic "operations" {
for_each = toset(
policy.key.egress_to.operations == null
? []
: policy.key.egress_to.operations
)
iterator = operation
content {
service_name = operation.service_name
dynamic "method_selectors" {
for_each = toset(
operation.key.method_selectors == null
? []
: operation.key.method_selectors
)
content {
method = method_selectors.key
}
}
}
}
}
}
# end egress_to
}
}
# end egress_policies
# begin ingress_policies
dynamic "ingress_policies" {
for_each = toset(
each.value.spec.ingress_policies == null
? []
: each.value.spec.ingress_policies
)
iterator = policy
content {
# begin ingress_from
dynamic "ingress_from" {
for_each = policy.key.ingress_from == null ? {} : { 1 = 1 }
content {
identity_type = policy.key.ingress_from.identity_type
identities = policy.key.ingress_from.identities
# begin sources
dynamic "sources" {
for_each = toset(
policy.key.ingress_from.source_access_levels == null
? []
: policy.key.ingress_from.source_access_levels
)
content {
access_level = sources.key
}
}
dynamic "sources" {
for_each = toset(
policy.key.ingress_from.source_resources == null
? []
: policy.key.ingress_from.source_resources
)
content {
resource = sources.key
}
}
# end sources
}
}
# end ingress_from
# begin ingress_to
dynamic "ingress_to" {
for_each = policy.key.ingress_to == null ? {} : { 1 = 1 }
content {
resources = policy.key.ingress_to.resources
dynamic "operations" {
for_each = toset(
policy.key.ingress_to.operations == null
? []
: policy.key.ingress_to.operations
)
iterator = operation
content {
service_name = operation.service_name
dynamic "method_selectors" {
for_each = toset(
operation.key.method_selectors == null
? []
: operation.key.method_selectors
)
content {
method = method_selectors.key
}
}
}
}
}
}
# end ingress_to
}
}
# end ingress_policies
# begin vpc_accessible_services
dynamic "vpc_accessible_services" {
for_each = each.value.spec.vpc_accessible_services == null ? {} : { 1 = 1 }
content {
allowed_services = each.value.spec.vpc_accessible_services.allowed_services
enable_restriction = each.value.spec.vpc_accessible_services.enable_restriction
}
}
# end vpc_accessible_services
}
}
dynamic "status" {
for_each = each.value.status == null ? {} : { 1 = 1 }
content {
access_levels = each.value.status.access_levels
resources = each.value.status.resources
restricted_services = each.value.status.restricted_services
# begin egress_policies
dynamic "egress_policies" {
for_each = toset(
each.value.status.egress_policies == null
? []
: each.value.status.egress_policies
)
iterator = policy
content {
# begin egress_from
dynamic "egress_from" {
for_each = policy.key.egress_from == null ? {} : { 1 = 1 }
content {
identity_type = policy.key.egress_from.identity_type
identities = policy.key.egress_from.identities
}
}
# end egress_from
# begin egress_to
dynamic "egress_to" {
for_each = policy.key.egress_to == null ? {} : { 1 = 1 }
content {
resources = policy.key.egress_to.resources
dynamic "operations" {
for_each = toset(
policy.key.egress_to.operations == null
? []
: policy.key.egress_to.operations
)
content {
service_name = operations.key.service_name
dynamic "method_selectors" {
for_each = toset(
operations.key.method_selectors == null
? []
: operations.key.method_selectors
)
content {
method = method_selectors.key
}
}
}
}
}
}
# end egress_to
}
}
# end egress_policies
# begin ingress_policies
dynamic "ingress_policies" {
for_each = toset(
each.value.status.ingress_policies == null
? []
: each.value.status.ingress_policies
)
iterator = policy
content {
# begin ingress_from
dynamic "ingress_from" {
for_each = policy.key.ingress_from == null ? {} : { 1 = 1 }
content {
identity_type = policy.key.ingress_from.identity_type
identities = policy.key.ingress_from.identities
# begin sources
dynamic "sources" {
for_each = toset(
policy.key.ingress_from.source_access_levels == null
? []
: policy.key.ingress_from.source_access_levels
)
content {
access_level = sources.key
}
}
dynamic "sources" {
for_each = toset(
policy.key.ingress_from.source_resources == null
? []
: policy.key.ingress_from.source_resources
)
content {
resource = sources.key
}
}
# end sources
}
}
# end ingress_from
# begin ingress_to
dynamic "ingress_to" {
for_each = policy.key.ingress_to == null ? {} : { 1 = 1 }
content {
resources = policy.key.ingress_to.resources
dynamic "operations" {
for_each = toset(
policy.key.ingress_to.operations == null
? []
: policy.key.ingress_to.operations
)
content {
service_name = operations.key.service_name
dynamic "method_selectors" {
for_each = toset(
operations.key.method_selectors == null
? []
: operations.key.method_selectors
)
content {
method = method_selectors.key
}
}
}
}
}
}
# end ingress_to
}
}
# end ingress_policies
# begin vpc_accessible_services
dynamic "vpc_accessible_services" {
for_each = each.value.status.vpc_accessible_services == null ? {} : { 1 = 1 }
content {
allowed_services = each.value.status.vpc_accessible_services.allowed_services
enable_restriction = each.value.status.vpc_accessible_services.enable_restriction
}
}
# end vpc_accessible_services
}
}
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
]
}

View File

@ -14,90 +14,145 @@
* limitations under the License.
*/
variable "access_level_perimeters" {
description = "Enforced mode -> Access Level -> Perimeters mapping. Enforced mode can be 'enforced' or 'dry_run'"
type = map(map(list(string)))
default = {}
}
variable "access_levels" {
description = "Map of Access Levels to be created. For each Access Level you can specify 'ip_subnetworks, required_access_levels, members, negate or regions'."
description = "Map of access levels in name => [conditions] format."
type = map(object({
combining_function = string
conditions = list(object({
device_policy = object({
require_screen_lock = bool
allowed_encryption_statuses = list(string)
allowed_device_management_levels = list(string)
os_constraints = list(object({
minimum_version = string
os_type = string
require_verified_chrome_os = bool
}))
require_admin_approval = bool
require_corp_owned = bool
})
ip_subnetworks = list(string)
required_access_levels = list(string)
members = list(string)
negate = string
negate = bool
regions = list(string)
required_access_levels = list(string)
}))
}))
default = {}
validation {
condition = alltrue([
for k, v in var.access_levels : (
v.combining_function == null ||
v.combining_function == "AND" ||
v.combining_function == "OR"
)
])
error_message = "Invalid `combining_function` value (null, \"AND\", \"OR\" accepted)."
}
}
variable "access_policy" {
description = "Access Policy name, leave null to use auto-created one."
type = string
}
variable "access_policy_create" {
description = "Enable autocreation of the Access Policy"
type = bool
default = true
description = "Access Policy configuration, fill in to create. Parent is in 'organizations/123456' format."
type = object({
parent = string
title = string
})
default = null
}
variable "access_policy_name" {
description = "Referenced Access Policy name"
type = string
default = null
}
variable "access_policy_title" {
description = "Access Policy title to be created."
type = string
default = null
}
variable "egress_policies" {
description = "List of EgressPolicies in the form described in the [documentation](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/access_context_manager_service_perimeter#egress_policies)"
default = null
}
variable "egress_policies_perimeters" {
description = "Enforced mode -> Egress Policy -> Perimeters mapping. Enforced mode can be 'enforced' or 'dry_run'"
type = map(map(list(string)))
default = {}
}
variable "ingress_policies" {
description = "List of IngressPolicies in the form described in the [documentation](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/access_context_manager_service_perimeter#ingress_policies)"
default = null
}
variable "ingress_policies_perimeters" {
description = "Enforced mode -> Ingress Policy -> Perimeters mapping. Enforced mode can be 'enforced' or 'dry_run'"
type = map(map(list(string)))
default = {}
}
variable "organization_id" {
description = "Organization id in organizations/nnnnnn format."
type = string
}
variable "perimeter_projects" {
description = "Perimeter -> Enforced Mode -> Projects Number mapping. Enforced mode can be 'enforced' or 'dry_run'."
type = map(map(list(number)))
default = {}
}
variable "perimeters" {
description = "Set of Perimeters."
variable "service_perimeters_bridge" {
description = "Bridge service perimeters."
type = map(object({
type = string
dry_run_config = object({
restricted_services = list(string)
vpc_accessible_services = list(string)
})
enforced_config = object({
restricted_services = list(string)
vpc_accessible_services = list(string)
})
spec_resources = list(string)
status_resources = list(string)
use_explicit_dry_run_spec = bool
}))
default = {}
}
variable "service_perimeters_regular" {
description = "Regular service perimeters."
type = map(object({
spec = object({
access_levels = list(string)
resources = list(string)
restricted_services = list(string)
egress_policies = list(object({
egress_from = object({
identity_type = string
identities = list(string)
})
egress_to = object({
operations = list(object({
method_selectors = list(string)
service_name = string
}))
resources = list(string)
})
}))
ingress_policies = list(object({
ingress_from = object({
identity_type = string
identities = list(string)
source_access_levels = list(string)
source_resources = list(string)
})
ingress_to = object({
operations = list(object({
method_selectors = list(string)
service_name = string
}))
resources = list(string)
})
}))
vpc_accessible_services = object({
allowed_services = list(string)
enable_restriction = bool
})
})
status = object({
access_levels = list(string)
resources = list(string)
restricted_services = list(string)
egress_policies = list(object({
egress_from = object({
identity_type = string
identities = list(string)
})
egress_to = object({
operations = list(object({
method_selectors = list(string)
service_name = string
}))
resources = list(string)
})
}))
ingress_policies = list(object({
ingress_from = object({
identity_type = string
identities = list(string)
source_access_levels = list(string)
source_resources = list(string)
})
ingress_to = object({
operations = list(object({
method_selectors = list(string)
service_name = string
}))
resources = list(string)
})
}))
vpc_accessible_services = object({
allowed_services = list(string)
enable_restriction = bool
})
})
use_explicit_dry_run_spec = bool
}))
default = {}
}

View File

@ -79,9 +79,10 @@ def example_plan_runner(_plan_runner):
"Runs Terraform plan and returns count of modules and resources."
plan = _plan_runner(fixture_path)
# the fixture is the example we are testing
modules = plan.modules or {}
return (
len(plan.modules),
sum(len(m.resources) for m in plan.modules.values()))
len(modules),
sum(len(m.resources) for m in modules.values()))
return run_plan

View File

@ -4,26 +4,10 @@
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
# 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.
terraform {
required_version = ">= 1.0.0"
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.0.0"
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.0.0"
}
}
}

View File

@ -0,0 +1,146 @@
/**
* Copyright 2021 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.
*/
variable "access_policy" {
description = "Access Policy name, leave null to use auto-created one."
type = string
default = null
}
variable "access_policy_create" {
description = "Access Policy configuration, fill in to create. Parent is in 'organizations/123456' format."
type = object({
parent = string
title = string
})
default = {
parent = "organizations/123456"
title = "vpcsc-policy"
}
}
module "test" {
source = "../../../../modules/vpc-sc"
access_policy = var.access_policy
access_policy_create = var.access_policy_create
access_levels = {
a1 = {
combining_function = null
conditions = [
{
device_policy = null
ip_subnetworks = null
members = ["user:ludomagno@google.com"]
negate = null
regions = null
required_access_levels = null
}
]
}
a2 = {
combining_function = "OR"
conditions = [
{
device_policy = null
ip_subnetworks = null
members = null
negate = null
regions = ["IT", "FR"]
required_access_levels = null
},
{
device_policy = null
ip_subnetworks = null
members = null
negate = null
regions = ["US"]
required_access_levels = null
}
]
}
}
service_perimeters_bridge = {
b1 = {
status_resources = ["projects/111110", "projects/111111"]
spec_resources = null
use_explicit_dry_run_spec = false
}
b2 = {
status_resources = ["projects/111110", "projects/222220"]
spec_resources = ["projects/111110", "projects/222220"]
use_explicit_dry_run_spec = true
}
}
service_perimeters_regular = {
r1 = {
spec = null
status = {
access_levels = [module.test.access_level_names["a1"]]
resources = ["projects/11111", "projects/111111"]
restricted_services = ["storage.googleapis.com"]
egress_policies = null
ingress_policies = null
vpc_accessible_services = {
allowed_services = ["compute.googleapis.com"]
enable_restriction = true
}
}
use_explicit_dry_run_spec = false
}
r2 = {
spec = null
status = {
access_levels = [module.test.access_level_names["a1"]]
resources = ["projects/222220", "projects/222221"]
restricted_services = ["storage.googleapis.com"]
egress_policies = [
{
egress_from = {
identity_type = null
identities = ["user:foo@example.com"]
}
egress_to = {
operations = null
resources = ["projects/333330"]
}
}
]
ingress_policies = [
{
ingress_from = {
identity_type = null
identities = null
source_access_levels = [module.test.access_level_names["a2"]]
source_resources = ["projects/333330"]
}
ingress_to = {
operations = [{
method_selectors = null
service_name = "compute.googleapis.com"
}]
resources = ["projects/222220"]
}
}
]
vpc_accessible_services = {
allowed_services = ["compute.googleapis.com"]
enable_restriction = true
}
}
use_explicit_dry_run_spec = false
}
}
}

View File

@ -0,0 +1,49 @@
# Copyright 2021 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.
import os
FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture')
def test_create_policy(plan_runner):
"Test with auto-created policy."
_, resources = plan_runner(FIXTURES_DIR)
counts = {}
for r in resources:
n = f'{r["type"]}.{r["name"]}'
counts[n] = counts.get(n, 0) + 1
assert counts == {
'google_access_context_manager_access_level.basic': 2,
'google_access_context_manager_access_policy.default': 1,
'google_access_context_manager_service_perimeter.bridge': 2,
'google_access_context_manager_service_perimeter.regular': 2
}
def test_use_policy(plan_runner):
"Test with existing policy."
_, resources = plan_runner(FIXTURES_DIR, access_policy_create="null",
access_policy="accessPolicies/foobar")
counts = {}
for r in resources:
n = f'{r["type"]}.{r["name"]}'
counts[n] = counts.get(n, 0) + 1
assert counts == {
'google_access_context_manager_access_level.basic': 2,
'google_access_context_manager_service_perimeter.bridge': 2,
'google_access_context_manager_service_perimeter.regular': 2
}

View File

@ -1,4 +1,4 @@
pytest>=4.6.0
PyYAML>=5.3
tftest>=1.6.1
tftest>=1.6.2
marko>=0.9.1