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:
parent
c7dd5bc1d6
commit
2c7dab3bb2
|
@ -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(map(list(string)))</code> | | <code>{}</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(object({ combining_function = string conditions = list(object({ ip_subnetworks = list(string) required_access_levels = list(string) members = list(string) negate = string regions = list(string) })) }))">map(object({…}))</code> | | <code>{}</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(map(list(string)))</code> | | <code>{}</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(map(list(string)))</code> | | <code>{}</code> |
|
||||
| perimeter_projects | Perimeter -> Enforced Mode -> Projects Number mapping. Enforced mode can be 'enforced' or 'dry_run'. | <code>map(map(list(number)))</code> | | <code>{}</code> |
|
||||
| perimeters | Set of Perimeters. | <code title="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) }) }))">map(object({…}))</code> | | <code>{}</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(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) members = list(string) negate = bool regions = list(string) required_access_levels = list(string) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| access_policy_create | Access Policy configuration, fill in to create. Parent is in 'organizations/123456' format. | <code title="object({ parent = string title = string })">object({…})</code> | | <code>null</code> |
|
||||
| service_perimeters_bridge | Bridge service perimeters. | <code title="map(object({ spec_resources = list(string) status_resources = list(string) use_explicit_dry_run_spec = bool }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| service_perimeters_regular | Regular service perimeters. | <code title="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 }))">map(object({…}))</code> | | <code>{}</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 -->
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
]
|
||||
}
|
|
@ -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
|
||||
]
|
||||
}
|
|
@ -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 = {}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
pytest>=4.6.0
|
||||
PyYAML>=5.3
|
||||
tftest>=1.6.1
|
||||
tftest>=1.6.2
|
||||
marko>=0.9.1
|
||||
|
|
Loading…
Reference in New Issue