Refactor vps-sc module for Terraform 1.3 (#963)

* wip

* example tests

* module tests

* streamline example

* fast

* tfdoc

* use collections.Counter in tests
This commit is contained in:
Ludovico Magnocavallo
2022-11-10 19:34:45 +01:00
committed by GitHub
parent b7bfcf3575
commit a9c47681d8
13 changed files with 715 additions and 803 deletions

View File

@@ -2,7 +2,7 @@
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).
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).
Given the complexity of the underlying resources, the module intentionally mimics their interfaces to make it easier to map their documentation onto its variables, and reduce the internal complexity.
If you are using [Application Default Credentials](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).
@@ -44,21 +44,16 @@ module "test" {
access_policy = "12345678"
access_levels = {
a1 = {
combining_function = null
conditions = [{
members = ["user:user1@example.com"], ip_subnetworks = null,
negate = null, regions = null, required_access_levels = null
}]
conditions = [
{ members = ["user:user1@example.com"] }
]
}
a2 = {
combining_function = "OR"
conditions = [{
regions = ["IT", "FR"], ip_subnetworks = null,
members = null, negate = null, required_access_levels = null
},{
ip_subnetworks = ["101.101.101.0/24"], members = null,
negate = null, regions = null, required_access_levels = null
}]
conditions = [
{ regions = ["IT", "FR"] },
{ ip_subnetworks = ["101.101.101.0/24"] }
]
}
}
}
@@ -85,12 +80,9 @@ module "test" {
access_policy = "12345678"
service_perimeters_bridge = {
b1 = {
status_resources = ["projects/111110", "projects/111111"]
spec_resources = null
use_explicit_dry_run_spec = false
status_resources = ["projects/111110", "projects/111111"]
}
b2 = {
status_resources = null
spec_resources = ["projects/222220", "projects/222221"]
use_explicit_dry_run_spec = true
}
@@ -107,65 +99,61 @@ module "test" {
access_policy = "12345678"
access_levels = {
a1 = {
combining_function = null
conditions = [{
members = ["user:user1@example.com"], ip_subnetworks = null,
negate = null, regions = null, required_access_levels = null
}]
conditions = [
{ members = ["user:user1@example.com"] }
]
}
a2 = {
combining_function = null
conditions = [{
members = ["user:user2@example.com"], ip_subnetworks = null,
negate = null, regions = null, required_access_levels = null
}]
conditions = [
{ members = ["user:user2@example.com"] }
]
}
}
egress_policies = {
# allow writing to external GCS bucket from a specific SA
gcs-sa-foo = {
from = {
identities = [
"serviceAccount:foo@myproject.iam.gserviceaccount.com"
]
}
to = {
operations = [{
method_selectors = ["*"]
service_name = "storage.googleapis.com"
}]
resources = ["projects/123456789"]
}
}
}
ingress_policies = {
# allow management from external automation SA
sa-tf-test = {
from = {
identities = [
"serviceAccount:test-tf@myproject.iam.gserviceaccount.com",
],
source_access_levels = ["*"]
}
to = {
operations = [{ service_name = "*" }]
resources = ["*"]
}
}
}
service_perimeters_regular = {
r1 = {
spec = null
status = {
access_levels = [module.test.access_level_names["a1"], "a2"]
access_levels = ["a1", "a2"]
resources = ["projects/11111", "projects/111111"]
restricted_services = ["storage.googleapis.com"]
# example: allow writing to external GCS bucket
egress_policies = [
{
egress_from = {
identity_type = null
identities = [
"serviceAccount:foo@myproject.iam.gserviceaccount.com"
]
}
egress_to = {
operations = [{
method_selectors = ["*"], service_name = "storage.googleapis.com"
}]
resources = ["projects/123456789"]
}
}
]
# example: allow management from external automation SA
ingress_policies = [
{
ingress_from = {
identities = [
"serviceAccount:test-tf@myproject.iam.gserviceaccount.com",
],
source_access_levels = ["*"], identity_type = null, source_resources = null
}
ingress_to = {
operations = [{ method_selectors = [], service_name = "*" }]
resources = ["*"]
}
}
]
egress_policies = ["gcs-sa-foo"]
ingress_policies = ["sa-tf-test"]
vpc_accessible_services = {
allowed_services = ["storage.googleapis.com"]
enable_restriction = true
}
}
use_explicit_dry_run_spec = false
}
}
}
@@ -179,17 +167,33 @@ module "test" {
## TODO
- [ ] implement support for the `google_access_context_manager_gcp_user_access_binding` resource
<!-- TFDOC OPTS files:1 -->
<!-- BEGIN TFDOC -->
## Files
| name | description | resources |
|---|---|---|
| [access-levels.tf](./access-levels.tf) | Access level resources. | <code>google_access_context_manager_access_level</code> |
| [main.tf](./main.tf) | Module-level locals and resources. | <code>google_access_context_manager_access_policy</code> |
| [outputs.tf](./outputs.tf) | Module outputs. | |
| [service-perimeters-bridge.tf](./service-perimeters-bridge.tf) | Bridge service perimeter resources. | <code>google_access_context_manager_service_perimeter</code> |
| [service-perimeters-regular.tf](./service-perimeters-regular.tf) | Regular service perimeter resources. | <code>google_access_context_manager_service_perimeter</code> |
| [variables.tf](./variables.tf) | Module variables. | |
| [versions.tf](./versions.tf) | Version pins. | |
## Variables
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [access_policy](variables.tf#L55) | Access Policy name, leave null to use auto-created one. | <code>string</code> | ✓ | |
| [access_levels](variables.tf#L17) | 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; 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](variables.tf#L60) | 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](variables.tf#L69) | 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](variables.tf#L79) | 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> |
| [access_policy](variables.tf#L56) | Access Policy name, set to null if creating one. | <code>string</code> | ✓ | |
| [access_levels](variables.tf#L17) | Access level definitions. | <code title="map&#40;object&#40;&#123;&#10; combining_function &#61; optional&#40;string&#41;&#10; conditions &#61; optional&#40;list&#40;object&#40;&#123;&#10; device_policy &#61; optional&#40;object&#40;&#123;&#10; allowed_device_management_levels &#61; optional&#40;list&#40;string&#41;&#41;&#10; allowed_encryption_statuses &#61; optional&#40;list&#40;string&#41;&#41;&#10; require_admin_approval &#61; bool&#10; require_corp_owned &#61; bool&#10; require_screen_lock &#61; optional&#40;bool&#41;&#10; os_constraints &#61; optional&#40;list&#40;object&#40;&#123;&#10; os_type &#61; string&#10; minimum_version &#61; optional&#40;string&#41;&#10; require_verified_chrome_os &#61; optional&#40;bool&#41;&#10; &#125;&#41;&#41;&#41;&#10; &#125;&#41;&#41;&#10; ip_subnetworks &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; members &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; negate &#61; optional&#40;bool&#41;&#10; regions &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; required_access_levels &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10; description &#61; optional&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [access_policy_create](variables.tf#L61) | 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> |
| [egress_policies](variables.tf#L70) | Egress policy definitions that can be referenced in perimeters. | <code title="map&#40;object&#40;&#123;&#10; from &#61; object&#40;&#123;&#10; identity_type &#61; optional&#40;string, &#34;ANY_IDENTITY&#34;&#41;&#10; identities &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#10; to &#61; object&#40;&#123;&#10; operations &#61; optional&#40;list&#40;object&#40;&#123;&#10; method_selectors &#61; optional&#40;list&#40;string&#41;&#41;&#10; service_name &#61; string&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10; resources &#61; optional&#40;list&#40;string&#41;&#41;&#10; resource_type_external &#61; optional&#40;bool, false&#41;&#10; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [ingress_policies](variables.tf#L99) | Ingress policy definitions that can be referenced in perimeters. | <code title="map&#40;object&#40;&#123;&#10; from &#61; object&#40;&#123;&#10; access_levels &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; identity_type &#61; optional&#40;string&#41;&#10; identities &#61; optional&#40;list&#40;string&#41;&#41;&#10; resources &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; &#125;&#41;&#10; to &#61; object&#40;&#123;&#10; operations &#61; optional&#40;list&#40;object&#40;&#123;&#10; method_selectors &#61; optional&#40;list&#40;string&#41;&#41;&#10; service_name &#61; string&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10; resources &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [service_perimeters_bridge](variables.tf#L130) | Bridge service perimeters. | <code title="map&#40;object&#40;&#123;&#10; spec_resources &#61; optional&#40;list&#40;string&#41;&#41;&#10; status_resources &#61; optional&#40;list&#40;string&#41;&#41;&#10; use_explicit_dry_run_spec &#61; optional&#40;bool, false&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [service_perimeters_regular](variables.tf#L140) | Regular service perimeters. | <code title="map&#40;object&#40;&#123;&#10; spec &#61; optional&#40;object&#40;&#123;&#10; access_levels &#61; optional&#40;list&#40;string&#41;&#41;&#10; resources &#61; optional&#40;list&#40;string&#41;&#41;&#10; restricted_services &#61; optional&#40;list&#40;string&#41;&#41;&#10; egress_policies &#61; optional&#40;list&#40;string&#41;&#41;&#10; ingress_policies &#61; optional&#40;list&#40;string&#41;&#41;&#10; vpc_accessible_services &#61; optional&#40;object&#40;&#123;&#10; allowed_services &#61; list&#40;string&#41;&#10; enable_restriction &#61; bool&#10; &#125;&#41;&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; status &#61; optional&#40;object&#40;&#123;&#10; access_levels &#61; optional&#40;list&#40;string&#41;&#41;&#10; resources &#61; optional&#40;list&#40;string&#41;&#41;&#10; restricted_services &#61; optional&#40;list&#40;string&#41;&#41;&#10; egress_policies &#61; optional&#40;list&#40;string&#41;&#41;&#10; ingress_policies &#61; optional&#40;list&#40;string&#41;&#41;&#10; vpc_accessible_services &#61; optional&#40;object&#40;&#123;&#10; allowed_services &#61; list&#40;string&#41;&#10; enable_restriction &#61; bool&#10; &#125;&#41;&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; use_explicit_dry_run_spec &#61; optional&#40;bool, false&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
## Outputs