diff --git a/build/int.cloudbuild.yaml b/build/int.cloudbuild.yaml index 29b2db67..76a84575 100644 --- a/build/int.cloudbuild.yaml +++ b/build/int.cloudbuild.yaml @@ -121,6 +121,30 @@ steps: - verify-scoped-example-with-egress-rule name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' args: ['/bin/bash', '-c', 'cft test run TestScopedExampleWithEgressRule --stage destroy --verbose'] +# scoped example with access level dry-run +- id: init-scoped-example-access-level-dry-run + waitFor: + - destroy-scoped-example-with-egress-rule + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'cft test run TestScopedExampleAccessLevelDryRun --stage init --verbose'] + +- id: apply-scoped-example-access-level-dry-run + waitFor: + - init-scoped-example-access-level-dry-run + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'cft test run TestScopedExampleAccessLevelDryRun --stage apply --verbose'] + +- id: verify-scoped-example-access-level-dry-run + waitFor: + - apply-scoped-example-access-level-dry-run + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'cft test run TestScopedExampleAccessLevelDryRun --stage verify --verbose'] + +- id: destroy-scoped-example-access-level-dry-run + waitFor: + - verify-scoped-example-access-level-dry-run + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'cft test run TestScopedExampleAccessLevelDryRun --stage destroy --verbose'] tags: - 'ci' - 'integration' diff --git a/examples/simple_example_access_level_dry_run/README.md b/examples/scoped_example_access_level_dry_run/README.md similarity index 69% rename from examples/simple_example_access_level_dry_run/README.md rename to examples/scoped_example_access_level_dry_run/README.md index 1e5ff369..ef8f1b18 100644 --- a/examples/simple_example_access_level_dry_run/README.md +++ b/examples/scoped_example_access_level_dry_run/README.md @@ -15,12 +15,17 @@ This example illustrates how to use the `vpc-service-controls` module to configu | ip\_subnetworks | A list of CIDR block IP subnetwork specification. May be IPv4 or IPv6. Note that for a CIDR IP address block, the specified IP address portion must be properly truncated (i.e. all the host bits must be zero) or the input is considered malformed. For example, "192.0.2.0/24" is accepted but "192.0.2.1/24" is not. Similarly, for IPv6, "2001:db8::/32" is accepted whereas "2001:db8::1/32" is not. The originating IP of a request must be in one of the listed subnets in order for this Condition to be true. If empty, all IP addresses are allowed. | `list(string)` | n/a | yes | | parent\_id | The parent of this AccessPolicy in the Cloud Resource Hierarchy. As of now, only organization are accepted as parent. | `string` | n/a | yes | | policy\_name | The policy's name. | `string` | n/a | yes | -| protected\_project\_id | Project number of the project INSIDE the regular service perimeter. | `number` | n/a | yes | +| protected\_project\_number | Project number of the project INSIDE the regular service perimeter. | `number` | n/a | yes | +| scopes | Folder or project on which this policy is applicable. Format: 'folders/FOLDER\_ID' or 'projects/PROJECT\_NUMBER' | `list(string)` | `[]` | no | ## Outputs | Name | Description | |------|-------------| -| policy\_name | n/a | +| access\_levels\_dry\_run | Access Level in Dry\_run mode | +| policy\_id | Resource name of the AccessPolicy. | +| policy\_name | Name of the AccessPolicy. | +| protected\_project\_number | Project number of the project INSIDE the regular service perimeter | +| service\_perimeter\_name | Service perimeter name | diff --git a/examples/simple_example_access_level_dry_run/main.tf b/examples/scoped_example_access_level_dry_run/main.tf similarity index 75% rename from examples/simple_example_access_level_dry_run/main.tf rename to examples/scoped_example_access_level_dry_run/main.tf index 76de1417..a6c40147 100644 --- a/examples/simple_example_access_level_dry_run/main.tf +++ b/examples/scoped_example_access_level_dry_run/main.tf @@ -16,15 +16,16 @@ module "access_context_manager_policy" { source = "terraform-google-modules/vpc-service-controls/google" - version = "~> 7.0" + version = "~> 7.1" parent_id = var.parent_id policy_name = var.policy_name + scopes = var.scopes } module "access_level_1" { source = "terraform-google-modules/vpc-service-controls/google//modules/access_level" - version = "~> 7.0" + version = "~> 7.1" policy = module.access_context_manager_policy.policy_id name = "single_ip_policy" @@ -34,7 +35,7 @@ module "access_level_1" { module "access_level_2" { source = "terraform-google-modules/vpc-service-controls/google//modules/access_level" - version = "~> 7.0" + version = "~> 7.1" policy = module.access_context_manager_policy.policy_id name = "single_ip_policy_dry_run" @@ -42,25 +43,35 @@ module "access_level_2" { description = "Some description" } +resource "time_sleep" "wait_for_access_levels" { + create_duration = "90s" + destroy_duration = "90s" + + depends_on = [ + module.access_level_1, + module.access_level_2 + ] +} + module "regular_service_perimeter_1" { source = "terraform-google-modules/vpc-service-controls/google//modules/regular_service_perimeter" - version = "~> 7.0" + version = "~> 7.1" policy = module.access_context_manager_policy.policy_id - perimeter_name = "regular_perimeter_1" + perimeter_name = "regular_perimeter_1_dry_run" description = "Some description" - resources = [var.protected_project_id] + resources = [var.protected_project_number] restricted_services = ["bigquery.googleapis.com", "storage.googleapis.com"] + access_levels = [module.access_level_1.name] - access_levels = [module.access_level_1.name] - - - resources_dry_run = [var.protected_project_id] + resources_dry_run = [var.protected_project_number] restricted_services_dry_run = ["storage.googleapis.com"] access_levels_dry_run = [module.access_level_2.name] shared_resources = { - all = [var.protected_project_id] + all = [var.protected_project_number] } + + depends_on = [time_sleep.wait_for_access_levels] } diff --git a/examples/scoped_example_access_level_dry_run/outputs.tf b/examples/scoped_example_access_level_dry_run/outputs.tf new file mode 100644 index 00000000..ef1ee9f6 --- /dev/null +++ b/examples/scoped_example_access_level_dry_run/outputs.tf @@ -0,0 +1,40 @@ +/** + * Copyright 2024 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. + */ + +output "policy_id" { + description = "Resource name of the AccessPolicy." + value = module.access_context_manager_policy.policy_id +} + +output "policy_name" { + description = "Name of the AccessPolicy." + value = var.policy_name +} + +output "protected_project_number" { + description = "Project number of the project INSIDE the regular service perimeter" + value = var.protected_project_number +} + +output "access_levels_dry_run" { + description = "Access Level in Dry_run mode" + value = module.access_level_2.name +} + +output "service_perimeter_name" { + description = "Service perimeter name" + value = module.regular_service_perimeter_1.perimeter_name +} diff --git a/examples/simple_example_access_level_dry_run/variables.tf b/examples/scoped_example_access_level_dry_run/variables.tf similarity index 87% rename from examples/simple_example_access_level_dry_run/variables.tf rename to examples/scoped_example_access_level_dry_run/variables.tf index ceb33995..9e3cad5d 100644 --- a/examples/simple_example_access_level_dry_run/variables.tf +++ b/examples/scoped_example_access_level_dry_run/variables.tf @@ -24,7 +24,7 @@ variable "policy_name" { type = string } -variable "protected_project_id" { +variable "protected_project_number" { description = "Project number of the project INSIDE the regular service perimeter." type = number } @@ -33,3 +33,9 @@ variable "ip_subnetworks" { description = "A list of CIDR block IP subnetwork specification. May be IPv4 or IPv6. Note that for a CIDR IP address block, the specified IP address portion must be properly truncated (i.e. all the host bits must be zero) or the input is considered malformed. For example, \"192.0.2.0/24\" is accepted but \"192.0.2.1/24\" is not. Similarly, for IPv6, \"2001:db8::/32\" is accepted whereas \"2001:db8::1/32\" is not. The originating IP of a request must be in one of the listed subnets in order for this Condition to be true. If empty, all IP addresses are allowed." type = list(string) } + +variable "scopes" { + description = "Folder or project on which this policy is applicable. Format: 'folders/FOLDER_ID' or 'projects/PROJECT_NUMBER'" + type = list(string) + default = [] +} diff --git a/modules/regular_service_perimeter/README.md b/modules/regular_service_perimeter/README.md index caef91e2..f642fad5 100644 --- a/modules/regular_service_perimeter/README.md +++ b/modules/regular_service_perimeter/README.md @@ -105,6 +105,7 @@ module "regular_service_perimeter_1" { | perimeter\_name | Name of the perimeter. Should be one unified string. Must only be letters, numbers and underscores | `string` | n/a | yes | | policy | Name of the parent policy | `string` | n/a | yes | | resource\_keys | A list of keys to use for the Terraform state. The order should correspond to var.resources and the keys must not be dynamically computed. If `null`, var.resources will be used as keys. | `list(string)` | `null` | no | +| resource\_keys\_dry\_run | A list of keys to use for the Terraform state. The order should correspond to var.resources\_dry\_run and the keys must not be dynamically computed. If `null`, var.resources\_dry\_run will be used as keys. | `list(string)` | `null` | no | | resources | A list of GCP resources that are inside of the service perimeter. Currently only projects and VPC networks are allowed. | `list(string)` | `[]` | no | | resources\_dry\_run | (Dry-run) A list of GCP resources that are inside of the service perimeter. Currently only projects and VPC networks are allowed. If set, a dry-run policy will be set. | `list(string)` | `[]` | no | | restricted\_services | GCP services that are subject to the Service Perimeter restrictions. Must contain a list of services. For example, if storage.googleapis.com is specified, access to the storage buckets inside the perimeter must meet the perimeter's access restrictions. | `list(string)` | `[]` | no | diff --git a/modules/regular_service_perimeter/main.tf b/modules/regular_service_perimeter/main.tf index cd5a0ab4..ca790389 100644 --- a/modules/regular_service_perimeter/main.tf +++ b/modules/regular_service_perimeter/main.tf @@ -49,7 +49,6 @@ resource "google_access_context_manager_service_perimeter" "regular_service_peri for_each = local.dry_run ? ["dry-run"] : [] content { restricted_services = var.restricted_services_dry_run - resources = [for item in var.resources_dry_run : can(regex("global/networks", item)) ? format("//compute.googleapis.com/%s", item) : format("projects/%s", item)] access_levels = formatlist( "accessPolicies/${var.policy}/accessLevels/%s", var.access_levels_dry_run @@ -86,6 +85,13 @@ locals { for rk in local.resource_keys : rk => var.resources[index(local.resource_keys, rk)] } + + #dry-run + resource_keys_dry_run = var.resource_keys_dry_run != null ? var.resource_keys_dry_run : var.resources_dry_run + resources_dry_run = { + for rk in local.resource_keys_dry_run : + rk => var.resources_dry_run[index(local.resource_keys_dry_run, rk)] + } } resource "google_access_context_manager_service_perimeter_resource" "service_perimeter_resource" { @@ -93,3 +99,9 @@ resource "google_access_context_manager_service_perimeter_resource" "service_per perimeter_name = google_access_context_manager_service_perimeter.regular_service_perimeter.name resource = can(regex("global/networks", each.value)) ? "//compute.googleapis.com/${each.value}" : "projects/${each.value}" } + +resource "google_access_context_manager_service_perimeter_dry_run_resource" "dry_run_service_perimeter_resource" { + for_each = local.resources_dry_run + perimeter_name = google_access_context_manager_service_perimeter.regular_service_perimeter.name + resource = can(regex("global/networks", each.value)) ? "//compute.googleapis.com/${each.value}" : "projects/${each.value}" +} diff --git a/modules/regular_service_perimeter/variables.tf b/modules/regular_service_perimeter/variables.tf index a59d163f..1ebecb78 100644 --- a/modules/regular_service_perimeter/variables.tf +++ b/modules/regular_service_perimeter/variables.tf @@ -65,6 +65,12 @@ variable "resources_dry_run" { default = [] } +variable "resource_keys_dry_run" { + description = "A list of keys to use for the Terraform state. The order should correspond to var.resources_dry_run and the keys must not be dynamically computed. If `null`, var.resources_dry_run will be used as keys." + type = list(string) + default = null +} + variable "access_levels_dry_run" { description = "(Dry-run) A list of AccessLevel resource names that allow resources within the ServicePerimeter to be accessed from the internet. AccessLevels listed must be in the same policy as this ServicePerimeter. Referencing a nonexistent AccessLevel is a syntax error. If no AccessLevel names are listed, resources within the perimeter can only be accessed via GCP calls with request origins within the perimeter. Example: 'accessPolicies/MY_POLICY/accessLevels/MY_LEVEL'. For Service Perimeter Bridge, must be empty. If set, a dry-run policy will be set." type = list(string) @@ -77,7 +83,6 @@ variable "shared_resources" { default = { all = [] } } -## Have to solve it like this don't want use optional flag because is still experimental variable "egress_policies" { description = "A list of all [egress policies](https://cloud.google.com/vpc-service-controls/docs/ingress-egress-rules#egress-rules-reference), each list object has a `from` and `to` value that describes egress_from and egress_to.\n\nExample: `[{ from={ identities=[], identity_type=\"ID_TYPE\" }, to={ resources=[], operations={ \"SRV_NAME\"={ OP_TYPE=[] }}}}]`\n\nValid Values:\n`ID_TYPE` = `null` or `IDENTITY_TYPE_UNSPECIFIED` (only allow indentities from list); `ANY_IDENTITY`; `ANY_USER_ACCOUNT`; `ANY_SERVICE_ACCOUNT`\n`SRV_NAME` = \"`*`\" (allow all services) or [Specific Services](https://cloud.google.com/vpc-service-controls/docs/supported-products#supported_products)\n`OP_TYPE` = [methods](https://cloud.google.com/vpc-service-controls/docs/supported-method-restrictions) or [permissions](https://cloud.google.com/vpc-service-controls/docs/supported-method-restrictions)" type = list(object({ @@ -103,8 +108,7 @@ variable "egress_policies" { default = [] } -## Have to solve it like this don't want use optional flag because is still experimental -variable "ingress_policies" { # TODO +variable "ingress_policies" { description = "A list of all [ingress policies](https://cloud.google.com/vpc-service-controls/docs/ingress-egress-rules#ingress-rules-reference), each list object has a `from` and `to` value that describes ingress_from and ingress_to.\n\nExample: `[{ from={ sources={ resources=[], access_levels=[] }, identities=[], identity_type=\"ID_TYPE\" }, to={ resources=[], operations={ \"SRV_NAME\"={ OP_TYPE=[] }}}}]`\n\nValid Values:\n`ID_TYPE` = `null` or `IDENTITY_TYPE_UNSPECIFIED` (only allow indentities from list); `ANY_IDENTITY`; `ANY_USER_ACCOUNT`; `ANY_SERVICE_ACCOUNT`\n`SRV_NAME` = \"`*`\" (allow all services) or [Specific Services](https://cloud.google.com/vpc-service-controls/docs/supported-products#supported_products)\n`OP_TYPE` = [methods](https://cloud.google.com/vpc-service-controls/docs/supported-method-restrictions) or [permissions](https://cloud.google.com/vpc-service-controls/docs/supported-method-restrictions)" type = list(object({ title = optional(string, null) @@ -128,7 +132,6 @@ variable "ingress_policies" { # TODO default = [] } -## Have to solve it like this don't want use optional flag because is still experimental variable "egress_policies_dry_run" { description = "A list of all [egress policies](https://cloud.google.com/vpc-service-controls/docs/ingress-egress-rules#egress-rules-reference), each list object has a `from` and `to` value that describes egress_from and egress_to. Use same formatting as `egress_policies`." type = list(object({ @@ -154,7 +157,6 @@ variable "egress_policies_dry_run" { default = [] } -## Have to solve it like this don't want use optional flag because is still experimental variable "ingress_policies_dry_run" { description = "A list of all [ingress policies](https://cloud.google.com/vpc-service-controls/docs/ingress-egress-rules#ingress-rules-reference), each list object has a `from` and `to` value that describes ingress_from and ingress_to. Use same formatting as `ingress_policies`." type = list(object({ diff --git a/examples/simple_example_access_level_dry_run/outputs.tf b/test/fixtures/scoped_example_access_level_dry_run/main.tf similarity index 55% rename from examples/simple_example_access_level_dry_run/outputs.tf rename to test/fixtures/scoped_example_access_level_dry_run/main.tf index 432957db..9e9a64d8 100644 --- a/examples/simple_example_access_level_dry_run/outputs.tf +++ b/test/fixtures/scoped_example_access_level_dry_run/main.tf @@ -1,5 +1,6 @@ + /** - * Copyright 2024 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +15,19 @@ * limitations under the License. */ -output "policy_name" { - value = var.policy_name +resource "random_string" "suffix" { + length = 6 + special = false + upper = false +} + +module "example" { + source = "../../../examples/scoped_example_access_level_dry_run" + + parent_id = var.parent_id + policy_name = "int_test_vpc_al_dry_run_${random_string.suffix.result}" + scopes = var.scopes + + protected_project_number = var.protected_project_ids["number"] + ip_subnetworks = ["192.0.2.0/24"] } diff --git a/test/fixtures/scoped_example_access_level_dry_run/outputs.tf b/test/fixtures/scoped_example_access_level_dry_run/outputs.tf new file mode 100644 index 00000000..97208a73 --- /dev/null +++ b/test/fixtures/scoped_example_access_level_dry_run/outputs.tf @@ -0,0 +1,40 @@ +/** + * Copyright 2025 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. + */ + +output "policy_id" { + description = "Resource name of the AccessPolicy." + value = module.example.policy_id +} + +output "policy_name" { + description = "Name of the parent policy" + value = module.example.policy_name +} + +output "protected_project_number" { + description = "Project number of the project INSIDE the regular service perimeter" + value = module.example.protected_project_number +} + +output "service_perimeter_name" { + description = "Service perimeter name" + value = module.example.service_perimeter_name +} + +output "access_levels_dry_run" { + description = "Access Level in Dry_run mode" + value = module.example.access_levels_dry_run +} diff --git a/test/fixtures/scoped_example_access_level_dry_run/variables.tf b/test/fixtures/scoped_example_access_level_dry_run/variables.tf new file mode 100644 index 00000000..097fc5f2 --- /dev/null +++ b/test/fixtures/scoped_example_access_level_dry_run/variables.tf @@ -0,0 +1,31 @@ +/** + * Copyright 2025 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 "parent_id" { + description = "The parent of this AccessPolicy in the Cloud Resource Hierarchy. As of now, only organization are accepted as parent." + type = string +} + +variable "protected_project_ids" { + description = "Project id and number of the project INSIDE the regular service perimeter. This map variable expects an \"id\" for the project id and \"number\" key for the project number." + type = object({ id = string, number = number }) +} + +variable "scopes" { + description = "Folder or project on which this policy is applicable. Format: 'folders/FOLDER_ID' or 'projects/PROJECT_NUMBER'" + type = list(string) + default = [] +} diff --git a/test/integration/scoped_example_access_level_dry_run/scoped_example_access_level_dry_run_test.go b/test/integration/scoped_example_access_level_dry_run/scoped_example_access_level_dry_run_test.go new file mode 100644 index 00000000..ed9692f0 --- /dev/null +++ b/test/integration/scoped_example_access_level_dry_run/scoped_example_access_level_dry_run_test.go @@ -0,0 +1,86 @@ +// Copyright 2025 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. + +package scoped_example_access_level_dry_run_test + +import ( + "fmt" + "testing" + "time" + + "github.com/tidwall/gjson" + + "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/gcloud" + "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/tft" + "github.com/gruntwork-io/terratest/modules/terraform" + "github.com/stretchr/testify/assert" +) + +var ( + RetryableTransientErrors = map[string]string{ + // Editing VPC Service Controls is eventually consistent. + ".*Error 403.*Request is prohibited by organization's policy.*vpcServiceControlsUniqueIdentifier.*": "Request is prohibited by organization's policy.", + } +) + +// getResultFieldStrSlice parses a field of a results list into a string slice +func GetResultFieldStrSlice(rs []gjson.Result, field string) []string { + s := make([]string, 0) + for _, r := range rs { + s = append(s, r.Get(field).String()) + } + return s +} + +func TestScopedExampleAccessLevelDryRun(t *testing.T) { + + setup := tft.NewTFBlueprintTest(t, + tft.WithTFDir("../../setup"), + ) + protectedProjectNumber := terraform.OutputMap(t, setup.GetTFOptions(), "protected_project_ids")["number"] + + bpt := tft.NewTFBlueprintTest(t, + tft.WithRetryableTerraformErrors(RetryableTransientErrors, 6, 2*time.Minute), + ) + + bpt.DefineVerify(func(assert *assert.Assertions) { + bpt.DefaultVerify(assert) + + policyID := bpt.GetStringOutput("policy_id") + scopedPolicy := gcloud.Runf(t, "access-context-manager policies describe %s", policyID) + assert.Equal(fmt.Sprintf("projects/%s", protectedProjectNumber), scopedPolicy.Get("scopes").Array()[0].String(), "scoped project should be %s", protectedProjectNumber) + + servicePerimeterLink := fmt.Sprintf("accessPolicies/%s/servicePerimeters/%s", policyID, bpt.GetStringOutput("service_perimeter_name")) + servicePerimeter := gcloud.Runf(t, "access-context-manager perimeters describe %s --policy %s", servicePerimeterLink, policyID) + + assert.True(servicePerimeter.Get("useExplicitDryRunSpec").Bool(), "should use explicit Dry Run spec") + + // Access Level + accessLevelsDryRunName := bpt.GetStringOutput("access_levels_dry_run") + accessLevelsDryRun := servicePerimeter.Get("spec.accessLevels").Array() + assert.Equal(1, len(accessLevelsDryRun), "should have only one Access Level in Dry-run") + assert.Equal(fmt.Sprintf("accessPolicies/%s/accessLevels/%s", policyID, accessLevelsDryRunName), accessLevelsDryRun[0].String(), "project %s should be protected in Dry-run", protectedProjectNumber) + + //Resource + resourcesDryRun := servicePerimeter.Get("spec.resources").Array() + assert.Equal(1, len(resourcesDryRun), "should have only one resource protected in Dry-run") + assert.Equal(fmt.Sprintf("projects/%s", protectedProjectNumber), resourcesDryRun[0].String(), "project %s should be protected in Dry-run", protectedProjectNumber) + + //Restricted Services + restrictedServicesDryRun := servicePerimeter.Get("spec.restrictedServices").Array() + assert.Equal(1, len(restrictedServicesDryRun), "should have only one service protected in Dry-run") + assert.Equal("storage.googleapis.com", restrictedServicesDryRun[0].String(), "service 'storage.googleapis.com' should be protected in Dry-run") + }) + bpt.Test() +} diff --git a/test/task_helper_functions.sh b/test/task_helper_functions.sh index 7bdd05dc..a3dae9c3 100755 --- a/test/task_helper_functions.sh +++ b/test/task_helper_functions.sh @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Delete any exisiting oragization AccessPolicy +# Delete any existing organization AccessPolicy # shellcheck disable=SC2154 remove_gcloud_org_accesspolicy() { source_test_env @@ -22,8 +22,10 @@ remove_gcloud_org_accesspolicy() { local policy policy=$(gcloud access-context-manager policies list --organization="${TF_VAR_org_id}" | grep "${TF_VAR_org_id}" | awk '{print $1}') - if [[ -n "${policy}" ]]; then - echo "Removing Access Policy ${policy}" - gcloud access-context-manager policies delete "${policy}" --quiet - fi + # shellcheck disable=SC2068 + for i in ${policy[@]} + do + echo "Removing Access Policy ${i}" + gcloud access-context-manager policies delete "accessPolicies/${i}" --quiet + done }