Skip to content

Commit 8b14973

Browse files
authored
feat: Add scopes, update directional rules variable definition and dry-run lifecycle ignore_changes (#199)
1 parent c7c776d commit 8b14973

File tree

27 files changed

+1043
-75
lines changed

27 files changed

+1043
-75
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ You can add a delay using terraform's [`null_resource`](https://www.terraform.io
6767
|------|-------------|------|---------|:--------:|
6868
| 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 |
6969
| policy\_name | The policy's name. | `string` | n/a | yes |
70+
| scopes | Folder or project on which this policy is applicable. Format: 'folders/FOLDER\_ID' or 'projects/PROJECT\_NUMBER' | `list(string)` | `[]` | no |
7071

7172
## Outputs
7273

build/int.cloudbuild.yaml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,54 @@ steps:
7373
- verify-simple-example-bridge-local
7474
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
7575
args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do destroy simple-example-bridge-local']
76+
# scoped example with ingress rule
77+
- id: init-scoped-example-with-ingress-rule
78+
waitFor:
79+
- destroy-simple-example-bridge-local
80+
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
81+
args: ['/bin/bash', '-c', 'cft test run TestScopedExampleWithIngressRule --stage init --verbose']
82+
83+
- id: apply-scoped-example-with-ingress-rule
84+
waitFor:
85+
- init-scoped-example-with-ingress-rule
86+
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
87+
args: ['/bin/bash', '-c', 'cft test run TestScopedExampleWithIngressRule --stage apply --verbose']
88+
89+
- id: verify-scoped-example-with-ingress-rule
90+
waitFor:
91+
- apply-scoped-example-with-ingress-rule
92+
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
93+
args: ['/bin/bash', '-c', 'cft test run TestScopedExampleWithIngressRule --stage verify --verbose']
94+
95+
- id: destroy-scoped-example-with-ingress-rule
96+
waitFor:
97+
- verify-scoped-example-with-ingress-rule
98+
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
99+
args: ['/bin/bash', '-c', 'cft test run TestScopedExampleWithIngressRule --stage destroy --verbose']
100+
# scoped example with egress rule
101+
- id: init-scoped-example-with-egress-rule
102+
waitFor:
103+
- destroy-scoped-example-with-ingress-rule
104+
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
105+
args: ['/bin/bash', '-c', 'cft test run TestScopedExampleWithEgressRule --stage init --verbose']
106+
107+
- id: apply-scoped-example-with-egress-rule
108+
waitFor:
109+
- init-scoped-example-with-egress-rule
110+
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
111+
args: ['/bin/bash', '-c', 'cft test run TestScopedExampleWithEgressRule --stage apply --verbose']
112+
113+
- id: verify-scoped-example-with-egress-rule
114+
waitFor:
115+
- apply-scoped-example-with-egress-rule
116+
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
117+
args: ['/bin/bash', '-c', 'cft test run TestScopedExampleWithEgressRule --stage verify --verbose']
118+
119+
- id: destroy-scoped-example-with-egress-rule
120+
waitFor:
121+
- verify-scoped-example-with-egress-rule
122+
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
123+
args: ['/bin/bash', '-c', 'cft test run TestScopedExampleWithEgressRule --stage destroy --verbose']
76124
tags:
77125
- 'ci'
78126
- 'integration'
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Scoped Example with Egress Rule
2+
3+
This example illustrates how to use the `vpc-service-controls` module to configure a scoped org policy, a regular perimeter and external storage buckets that can be access in it from inside read only via egress rule.
4+
5+
# Requirements
6+
7+
1. Make sure you've gone through the root [Requirement Section](../../README.md#requirements) on any project in your organization.
8+
2. If you need to run integration tests for this example, select a second project in your organization. The project you already configured will be referred as the protected project that will be inside of the regular service perimeter. The second project will be the public project, which will be outside of the regular service perimeter.
9+
3. Grant the service account the following permissions on the public project:
10+
- roles/storage.Admin
11+
12+
You may use the following gcloud commands:
13+
`gcloud projects add-iam-policy-binding <project-id> --member=serviceAccount:<service-account-email> --role=roles/storage.Admin`
14+
15+
1. Enable Storage API on the protected project.
16+
2. If you want to run the integration tests for this example, repeat step #3 and #4 on the protected project.
17+
18+
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
19+
## Inputs
20+
21+
| Name | Description | Type | Default | Required |
22+
|------|-------------|------|---------|:--------:|
23+
| access\_level\_name | Access level name of the Access Policy. | `string` | `"terraform_members_e"` | no |
24+
| buckets\_names | Buckets Names as list of strings | `list(string)` | <pre>[<br> "bucket1-e",<br> "bucket2-e"<br>]</pre> | no |
25+
| buckets\_prefix | Bucket Prefix | `string` | `"test-bucket-e"` | no |
26+
| members | An allowed list of members (users, service accounts). The signed-in identity originating the request must be a part of one of the provided members. If not specified, a request may come from any user (logged in/not logged in, etc.). Formats: user:{emailid}, serviceAccount:{emailid} | `list(string)` | n/a | yes |
27+
| 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 |
28+
| perimeter\_name | Perimeter name of the Access Policy.. | `string` | `"regular_perimeter_e"` | no |
29+
| policy\_name | The policy's name. | `string` | n/a | yes |
30+
| protected\_project\_ids | 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. | `object({ id = string, number = number })` | n/a | yes |
31+
| public\_project\_ids | Project id and number of the project OUTSIDE the regular service perimeter. This map variable expects an "id" for the project id and "number" key for the project number. | `object({ id = string, number = number })` | n/a | yes |
32+
| regions | The request must originate from one of the provided countries/regions. Format: A valid ISO 3166-1 alpha-2 code. | `list(string)` | `[]` | no |
33+
| scopes | Folder or project on which this policy is applicable. Format: 'folders/FOLDER\_ID' or 'projects/PROJECT\_NUMBER' | `list(string)` | `[]` | no |
34+
35+
## Outputs
36+
37+
| Name | Description |
38+
|------|-------------|
39+
| policy\_id | Resource name of the AccessPolicy. |
40+
| policy\_name | Name of the parent policy |
41+
| protected\_project\_id | Project id of the project INSIDE the regular service perimeter |
42+
| service\_perimeter\_name | Service perimeter name |
43+
44+
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
45+
46+
To provision this example, run the following from within this directory:
47+
- `terraform init` to get the plugins
48+
- `terraform plan` to see the infrastructure plan
49+
- `terraform apply` to apply the infrastructure build
50+
- `terraform destroy` to destroy the built infrastructure
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/**
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
module "access_context_manager_policy" {
18+
source = "terraform-google-modules/vpc-service-controls/google"
19+
version = "~> 7.1"
20+
21+
parent_id = var.parent_id
22+
policy_name = var.policy_name
23+
scopes = var.scopes
24+
25+
depends_on = [module.gcs_buckets]
26+
}
27+
28+
module "access_level_members" {
29+
source = "terraform-google-modules/vpc-service-controls/google//modules/access_level"
30+
version = "~> 7.1"
31+
32+
description = "Simple Example Access Level"
33+
policy = module.access_context_manager_policy.policy_id
34+
name = var.access_level_name
35+
members = var.members
36+
regions = var.regions
37+
}
38+
39+
resource "time_sleep" "wait_for_members" {
40+
create_duration = "90s"
41+
destroy_duration = "90s"
42+
43+
depends_on = [module.access_level_members]
44+
}
45+
46+
module "regular_service_perimeter_1" {
47+
source = "terraform-google-modules/vpc-service-controls/google//modules/regular_service_perimeter"
48+
version = "~> 7.1"
49+
50+
51+
policy = module.access_context_manager_policy.policy_id
52+
perimeter_name = var.perimeter_name
53+
54+
description = "Perimeter shielding bigquery project"
55+
resources = [var.protected_project_ids["number"]]
56+
access_levels = [module.access_level_members.name]
57+
58+
restricted_services = ["bigquery.googleapis.com", "storage.googleapis.com"]
59+
60+
egress_policies = [
61+
{
62+
title = "Read outside buckets from project"
63+
from = {
64+
sources = {
65+
resources = ["projects/${var.protected_project_ids["number"]}"]
66+
},
67+
identity_type = "ANY_SERVICE_ACCOUNT"
68+
}
69+
to = {
70+
resources = [
71+
"projects/${var.public_project_ids["number"]}"
72+
]
73+
operations = {
74+
"storage.googleapis.com" = {
75+
methods = [
76+
"google.storage.objects.get",
77+
"google.storage.objects.list"
78+
]
79+
}
80+
}
81+
}
82+
},
83+
{
84+
title = "Use permissions for Big Query access" # See https://cloud.google.com/vpc-service-controls/docs/supported-method-restrictions
85+
from = {
86+
sources = {
87+
resources = ["projects/${var.protected_project_ids["number"]}"]
88+
},
89+
identity_type = "ANY_SERVICE_ACCOUNT"
90+
}
91+
to = {
92+
resources = [
93+
"projects/${var.public_project_ids["number"]}"
94+
]
95+
operations = {
96+
"bigquery.googleapis.com" = {
97+
permissions = [
98+
"bigquery.datasets.get",
99+
"bigquery.models.getData",
100+
"bigquery.models.getMetadata",
101+
"bigquery.models.list",
102+
"bigquery.tables.get",
103+
"bigquery.tables.getData",
104+
"bigquery.tables.list"
105+
]
106+
}
107+
}
108+
}
109+
},
110+
]
111+
112+
shared_resources = {
113+
all = [var.protected_project_ids["number"]]
114+
}
115+
116+
depends_on = [
117+
module.gcs_buckets,
118+
time_sleep.wait_for_members
119+
]
120+
}
121+
122+
module "gcs_buckets" {
123+
source = "terraform-google-modules/cloud-storage/google"
124+
version = "~> 10.0"
125+
project_id = var.public_project_ids["id"]
126+
names = var.buckets_names
127+
randomize_suffix = true
128+
prefix = var.buckets_prefix
129+
set_admin_roles = true
130+
admins = var.members
131+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
output "policy_id" {
18+
description = "Resource name of the AccessPolicy."
19+
value = module.access_context_manager_policy.policy_id
20+
}
21+
22+
output "policy_name" {
23+
description = "Name of the parent policy"
24+
value = var.policy_name
25+
}
26+
27+
28+
output "protected_project_id" {
29+
description = "Project id of the project INSIDE the regular service perimeter"
30+
value = var.protected_project_ids["id"]
31+
}
32+
33+
output "service_perimeter_name" {
34+
description = "Service perimeter name"
35+
value = module.regular_service_perimeter_1.perimeter_name
36+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/**
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
variable "parent_id" {
18+
description = "The parent of this AccessPolicy in the Cloud Resource Hierarchy. As of now, only organization are accepted as parent."
19+
type = string
20+
}
21+
22+
variable "policy_name" {
23+
description = "The policy's name."
24+
type = string
25+
}
26+
27+
variable "protected_project_ids" {
28+
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."
29+
type = object({ id = string, number = number })
30+
}
31+
32+
variable "public_project_ids" {
33+
description = "Project id and number of the project OUTSIDE the regular service perimeter. This map variable expects an \"id\" for the project id and \"number\" key for the project number."
34+
type = object({ id = string, number = number })
35+
}
36+
37+
variable "members" {
38+
description = "An allowed list of members (users, service accounts). The signed-in identity originating the request must be a part of one of the provided members. If not specified, a request may come from any user (logged in/not logged in, etc.). Formats: user:{emailid}, serviceAccount:{emailid}"
39+
type = list(string)
40+
}
41+
42+
variable "regions" {
43+
description = "The request must originate from one of the provided countries/regions. Format: A valid ISO 3166-1 alpha-2 code."
44+
type = list(string)
45+
default = []
46+
}
47+
48+
variable "perimeter_name" {
49+
description = "Perimeter name of the Access Policy.."
50+
type = string
51+
default = "regular_perimeter_e"
52+
}
53+
54+
variable "access_level_name" {
55+
description = "Access level name of the Access Policy."
56+
type = string
57+
default = "terraform_members_e"
58+
}
59+
60+
variable "buckets_prefix" {
61+
description = "Bucket Prefix"
62+
type = string
63+
default = "test-bucket-e"
64+
}
65+
66+
variable "buckets_names" {
67+
description = "Buckets Names as list of strings"
68+
type = list(string)
69+
default = ["bucket1-e", "bucket2-e"]
70+
}
71+
72+
variable "scopes" {
73+
description = "Folder or project on which this policy is applicable. Format: 'folders/FOLDER_ID' or 'projects/PROJECT_NUMBER'"
74+
type = list(string)
75+
default = []
76+
}

examples/simple_example_with_ingress_rule/README.md renamed to examples/scoped_example_with_ingress_rule/README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Simple Example with Ingress Rule
2-
This example illustrates how to use the `vpc-service-controls` module to configure an org policy, a regular perimeter with storage buckets that can be access in it from outside read only via ingress rule.
2+
3+
This example illustrates how to use the `vpc-service-controls` module to configure a scoped org policy, a regular perimeter with storage buckets that can be access in it from outside read only via ingress rule.
4+
35
# Requirements
46

57
1. Make sure you've gone through the root [Requirement Section](../../README.md#requirements) on any project in your organization.
@@ -26,8 +28,10 @@ You may use the following gcloud commands:
2628
| perimeter\_name | Perimeter name of the Access Policy.. | `string` | `"regular_perimeter_1"` | no |
2729
| policy\_name | The policy's name. | `string` | n/a | yes |
2830
| protected\_project\_ids | 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. | `object({ id = string, number = number })` | n/a | yes |
31+
| public\_project\_ids | Project id and number of the project OUTSIDE the regular service perimeter. This map variable expects an "id" for the project id and "number" key for the project number. | `object({ id = string, number = number })` | n/a | yes |
2932
| read\_bucket\_identities | List of all identities should get read access on bucket | `list(string)` | `[]` | no |
3033
| regions | The request must originate from one of the provided countries/regions. Format: A valid ISO 3166-1 alpha-2 code. | `list(string)` | `[]` | no |
34+
| scopes | Folder or project on which this policy is applicable. Format: 'folders/FOLDER\_ID' or 'projects/PROJECT\_NUMBER' | `list(string)` | `[]` | no |
3135

3236
## Outputs
3337

@@ -36,6 +40,7 @@ You may use the following gcloud commands:
3640
| policy\_id | Resource name of the AccessPolicy. |
3741
| policy\_name | Name of the parent policy |
3842
| protected\_project\_id | Project id of the project INSIDE the regular service perimeter |
43+
| service\_perimeter\_name | Service perimeter name |
3944

4045
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
4146

0 commit comments

Comments
 (0)