Skip to content

feat: Add scopes, update directional rules variable definition and dry-run lifecycle ignore_changes #199

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ You can add a delay using terraform's [`null_resource`](https://www.terraform.io
|------|-------------|------|---------|:--------:|
| 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 |
| scopes | Folder or project on which this policy is applicable. Format: 'folders/FOLDER\_ID' or 'projects/PROJECT\_NUMBER' | `list(string)` | `[]` | no |

## Outputs

Expand Down
48 changes: 48 additions & 0 deletions build/int.cloudbuild.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,54 @@ steps:
- verify-simple-example-bridge-local
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do destroy simple-example-bridge-local']
# scoped example with ingress rule
- id: init-scoped-example-with-ingress-rule
waitFor:
- destroy-simple-example-bridge-local
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
args: ['/bin/bash', '-c', 'cft test run TestScopedExampleWithIngressRule --stage init --verbose']

- id: apply-scoped-example-with-ingress-rule
waitFor:
- init-scoped-example-with-ingress-rule
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
args: ['/bin/bash', '-c', 'cft test run TestScopedExampleWithIngressRule --stage apply --verbose']

- id: verify-scoped-example-with-ingress-rule
waitFor:
- apply-scoped-example-with-ingress-rule
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
args: ['/bin/bash', '-c', 'cft test run TestScopedExampleWithIngressRule --stage verify --verbose']

- id: destroy-scoped-example-with-ingress-rule
waitFor:
- verify-scoped-example-with-ingress-rule
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
args: ['/bin/bash', '-c', 'cft test run TestScopedExampleWithIngressRule --stage destroy --verbose']
# scoped example with egress rule
- id: init-scoped-example-with-egress-rule
waitFor:
- destroy-scoped-example-with-ingress-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 init --verbose']

- id: apply-scoped-example-with-egress-rule
waitFor:
- init-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 apply --verbose']

- id: verify-scoped-example-with-egress-rule
waitFor:
- apply-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 verify --verbose']

- id: destroy-scoped-example-with-egress-rule
waitFor:
- 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']
tags:
- 'ci'
- 'integration'
Expand Down
50 changes: 50 additions & 0 deletions examples/scoped_example_with_egress_rule/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Scoped Example with Egress Rule

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.

# Requirements

1. Make sure you've gone through the root [Requirement Section](../../README.md#requirements) on any project in your organization.
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.
3. Grant the service account the following permissions on the public project:
- roles/storage.Admin

You may use the following gcloud commands:
`gcloud projects add-iam-policy-binding <project-id> --member=serviceAccount:<service-account-email> --role=roles/storage.Admin`

1. Enable Storage API on the protected project.
2. If you want to run the integration tests for this example, repeat step #3 and #4 on the protected project.

<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| access\_level\_name | Access level name of the Access Policy. | `string` | `"terraform_members_e"` | no |
| buckets\_names | Buckets Names as list of strings | `list(string)` | <pre>[<br> "bucket1-e",<br> "bucket2-e"<br>]</pre> | no |
| buckets\_prefix | Bucket Prefix | `string` | `"test-bucket-e"` | no |
| 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 |
| 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 |
| perimeter\_name | Perimeter name of the Access Policy.. | `string` | `"regular_perimeter_e"` | no |
| policy\_name | The policy's name. | `string` | n/a | yes |
| 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 |
| 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 |
| regions | The request must originate from one of the provided countries/regions. Format: A valid ISO 3166-1 alpha-2 code. | `list(string)` | `[]` | no |
| 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\_id | Resource name of the AccessPolicy. |
| policy\_name | Name of the parent policy |
| protected\_project\_id | Project id of the project INSIDE the regular service perimeter |
| service\_perimeter\_name | Service perimeter name |

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

To provision this example, run the following from within this directory:
- `terraform init` to get the plugins
- `terraform plan` to see the infrastructure plan
- `terraform apply` to apply the infrastructure build
- `terraform destroy` to destroy the built infrastructure
131 changes: 131 additions & 0 deletions examples/scoped_example_with_egress_rule/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/**
* 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.
*/

module "access_context_manager_policy" {
source = "terraform-google-modules/vpc-service-controls/google"
version = "~> 7.1"

parent_id = var.parent_id
policy_name = var.policy_name
scopes = var.scopes

depends_on = [module.gcs_buckets]
}

module "access_level_members" {
source = "terraform-google-modules/vpc-service-controls/google//modules/access_level"
version = "~> 7.1"

description = "Simple Example Access Level"
policy = module.access_context_manager_policy.policy_id
name = var.access_level_name
members = var.members
regions = var.regions
}

resource "time_sleep" "wait_for_members" {
create_duration = "90s"
destroy_duration = "90s"

depends_on = [module.access_level_members]
}

module "regular_service_perimeter_1" {
source = "terraform-google-modules/vpc-service-controls/google//modules/regular_service_perimeter"
version = "~> 7.1"


policy = module.access_context_manager_policy.policy_id
perimeter_name = var.perimeter_name

description = "Perimeter shielding bigquery project"
resources = [var.protected_project_ids["number"]]
access_levels = [module.access_level_members.name]

restricted_services = ["bigquery.googleapis.com", "storage.googleapis.com"]

egress_policies = [
{
title = "Read outside buckets from project"
from = {
sources = {
resources = ["projects/${var.protected_project_ids["number"]}"]
},
identity_type = "ANY_SERVICE_ACCOUNT"
}
to = {
resources = [
"projects/${var.public_project_ids["number"]}"
]
operations = {
"storage.googleapis.com" = {
methods = [
"google.storage.objects.get",
"google.storage.objects.list"
]
}
}
}
},
{
title = "Use permissions for Big Query access" # See https://cloud.google.com/vpc-service-controls/docs/supported-method-restrictions
from = {
sources = {
resources = ["projects/${var.protected_project_ids["number"]}"]
},
identity_type = "ANY_SERVICE_ACCOUNT"
}
to = {
resources = [
"projects/${var.public_project_ids["number"]}"
]
operations = {
"bigquery.googleapis.com" = {
permissions = [
"bigquery.datasets.get",
"bigquery.models.getData",
"bigquery.models.getMetadata",
"bigquery.models.list",
"bigquery.tables.get",
"bigquery.tables.getData",
"bigquery.tables.list"
]
}
}
}
},
]

shared_resources = {
all = [var.protected_project_ids["number"]]
}

depends_on = [
module.gcs_buckets,
time_sleep.wait_for_members
]
}

module "gcs_buckets" {
source = "terraform-google-modules/cloud-storage/google"
version = "~> 10.0"
project_id = var.public_project_ids["id"]
names = var.buckets_names
randomize_suffix = true
prefix = var.buckets_prefix
set_admin_roles = true
admins = var.members
}
36 changes: 36 additions & 0 deletions examples/scoped_example_with_egress_rule/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* 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.access_context_manager_policy.policy_id
}

output "policy_name" {
description = "Name of the parent policy"
value = var.policy_name
}


output "protected_project_id" {
description = "Project id of the project INSIDE the regular service perimeter"
value = var.protected_project_ids["id"]
}

output "service_perimeter_name" {
description = "Service perimeter name"
value = module.regular_service_perimeter_1.perimeter_name
}
76 changes: 76 additions & 0 deletions examples/scoped_example_with_egress_rule/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* 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 "policy_name" {
description = "The policy's name."
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 "public_project_ids" {
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."
type = object({ id = string, number = number })
}

variable "members" {
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}"
type = list(string)
}

variable "regions" {
description = "The request must originate from one of the provided countries/regions. Format: A valid ISO 3166-1 alpha-2 code."
type = list(string)
default = []
}

variable "perimeter_name" {
description = "Perimeter name of the Access Policy.."
type = string
default = "regular_perimeter_e"
}

variable "access_level_name" {
description = "Access level name of the Access Policy."
type = string
default = "terraform_members_e"
}

variable "buckets_prefix" {
description = "Bucket Prefix"
type = string
default = "test-bucket-e"
}

variable "buckets_names" {
description = "Buckets Names as list of strings"
type = list(string)
default = ["bucket1-e", "bucket2-e"]
}

variable "scopes" {
description = "Folder or project on which this policy is applicable. Format: 'folders/FOLDER_ID' or 'projects/PROJECT_NUMBER'"
type = list(string)
default = []
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Simple Example with Ingress Rule
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.

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.

# Requirements

1. Make sure you've gone through the root [Requirement Section](../../README.md#requirements) on any project in your organization.
Expand All @@ -26,8 +28,10 @@ You may use the following gcloud commands:
| perimeter\_name | Perimeter name of the Access Policy.. | `string` | `"regular_perimeter_1"` | no |
| policy\_name | The policy's name. | `string` | n/a | yes |
| 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 |
| 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 |
| read\_bucket\_identities | List of all identities should get read access on bucket | `list(string)` | `[]` | no |
| regions | The request must originate from one of the provided countries/regions. Format: A valid ISO 3166-1 alpha-2 code. | `list(string)` | `[]` | no |
| scopes | Folder or project on which this policy is applicable. Format: 'folders/FOLDER\_ID' or 'projects/PROJECT\_NUMBER' | `list(string)` | `[]` | no |

## Outputs

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

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

Expand Down
Loading