-
Notifications
You must be signed in to change notification settings - Fork 910
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow granting network user role on host project from project module …
…and factory (#1930) * Update shared vpc config for project factory and project module for more granular Shared VPC configuration --------- Co-authored-by: Ludovico Magnocavallo <[email protected]>
- Loading branch information
1 parent
db31c1b
commit c50b732
Showing
9 changed files
with
217 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,7 +57,7 @@ module "project-factory" { | |
# location where the yaml files are read from | ||
factory_data_path = "data" | ||
} | ||
# tftest modules=7 resources=31 files=prj-app-1,prj-app-2,prj-app-3 inventory=example.yaml | ||
# tftest modules=7 resources=33 files=prj-app-1,prj-app-2,prj-app-3 inventory=example.yaml | ||
``` | ||
|
||
```yaml | ||
|
@@ -85,9 +85,15 @@ service_accounts: | |
|
||
```yaml | ||
labels: | ||
app: app-2 | ||
team: foo | ||
app: app-2 | ||
team: foo | ||
parent: folders/12345678 | ||
org_policies: | ||
"compute.restrictSharedVpcSubnetworks": | ||
rules: | ||
- allow: | ||
values: | ||
- projects/foo-host/regions/europe-west1/subnetworks/prod-default-ew1 | ||
service_accounts: | ||
app-2-be: {} | ||
services: | ||
|
@@ -98,13 +104,17 @@ services: | |
shared_vpc_service_config: | ||
host_project: foo-host | ||
service_identity_iam: | ||
"roles/compute.networkUser": | ||
- cloudservices | ||
- container-engine | ||
"roles/vpcaccess.user": | ||
- cloudrun | ||
- cloudrun | ||
"roles/container.hostServiceAgentUser": | ||
- container-engine | ||
- container-engine | ||
service_identity_subnet_iam: | ||
europe-west1/prod-default-ew1: | ||
- cloudservices | ||
- container-engine | ||
network_subnet_users: | ||
europe-west1/prod-default-ew1: | ||
- group:[email protected] | ||
|
||
# tftest-file id=prj-app-2 path=data/prj-app-2.yaml | ||
``` | ||
|
@@ -117,15 +127,16 @@ services: | |
|
||
# tftest-file id=prj-app-3 path=data/prj-app-3.yaml | ||
``` | ||
|
||
<!-- BEGIN TFDOC --> | ||
## Variables | ||
|
||
| name | description | type | required | default | | ||
|---|---|:---:|:---:|:---:| | ||
| [factory_data_path](variables.tf#L89) | Path to folder with YAML project description data files. | <code>string</code> | ✓ | | | ||
| [data_defaults](variables.tf#L17) | Optional default values used when corresponding project data from files are missing. | <code title="object({ billing_account = optional(string) contacts = optional(map(list(string)), {}) labels = optional(map(string), {}) metric_scopes = optional(list(string), []) parent = optional(string) prefix = optional(string) service_encryption_key_ids = optional(map(list(string)), {}) service_perimeter_bridges = optional(list(string), []) service_perimeter_standard = optional(string) services = optional(list(string), []) shared_vpc_service_config = optional(object({ host_project = string service_identity_iam = optional(map(list(string)), {}) service_identity_subnet_iam = optional(map(list(string)), {}) service_iam_grants = optional(list(string), []) }), { host_project = null }) tag_bindings = optional(map(string), {}) service_accounts = optional(map(object({ display_name = optional(string, "Terraform-managed.") iam_project_roles = optional(list(string)) })), {}) })">object({…})</code> | | <code>{}</code> | | ||
| [data_merges](variables.tf#L47) | Optional values that will be merged with corresponding data from files. Combines with `data_defaults`, file data, and `data_overrides`. | <code title="object({ contacts = optional(map(list(string)), {}) labels = optional(map(string), {}) metric_scopes = optional(list(string), []) service_encryption_key_ids = optional(map(list(string)), {}) service_perimeter_bridges = optional(list(string), []) services = optional(list(string), []) tag_bindings = optional(map(string), {}) service_accounts = optional(map(object({ display_name = optional(string, "Terraform-managed.") iam_project_roles = optional(list(string)) })), {}) })">object({…})</code> | | <code>{}</code> | | ||
| [data_overrides](variables.tf#L67) | Optional values that override corresponding data from files. Takes precedence over file data and `data_defaults`. | <code title="object({ billing_account = optional(string) contacts = optional(map(list(string))) parent = optional(string) prefix = optional(string) service_encryption_key_ids = optional(map(list(string))) service_perimeter_bridges = optional(list(string)) service_perimeter_standard = optional(string) tag_bindings = optional(map(string)) services = optional(list(string)) service_accounts = optional(map(object({ display_name = optional(string, "Terraform-managed.") iam_project_roles = optional(list(string)) }))) })">object({…})</code> | | <code>{}</code> | | ||
| [factory_data_path](variables.tf#L91) | Path to folder with YAML project description data files. | <code>string</code> | ✓ | | | ||
| [data_defaults](variables.tf#L17) | Optional default values used when corresponding project data from files are missing. | <code title="object({ billing_account = optional(string) contacts = optional(map(list(string)), {}) labels = optional(map(string), {}) metric_scopes = optional(list(string), []) parent = optional(string) prefix = optional(string) service_encryption_key_ids = optional(map(list(string)), {}) service_perimeter_bridges = optional(list(string), []) service_perimeter_standard = optional(string) services = optional(list(string), []) shared_vpc_service_config = optional(object({ host_project = string network_users = optional(list(string), []) service_identity_iam = optional(map(list(string)), {}) service_identity_subnet_iam = optional(map(list(string)), {}) service_iam_grants = optional(list(string), []) network_subnet_users = optional(map(list(string)), {}) }), { host_project = null }) tag_bindings = optional(map(string), {}) service_accounts = optional(map(object({ display_name = optional(string, "Terraform-managed.") iam_project_roles = optional(list(string)) })), {}) })">object({…})</code> | | <code>{}</code> | | ||
| [data_merges](variables.tf#L49) | Optional values that will be merged with corresponding data from files. Combines with `data_defaults`, file data, and `data_overrides`. | <code title="object({ contacts = optional(map(list(string)), {}) labels = optional(map(string), {}) metric_scopes = optional(list(string), []) service_encryption_key_ids = optional(map(list(string)), {}) service_perimeter_bridges = optional(list(string), []) services = optional(list(string), []) tag_bindings = optional(map(string), {}) service_accounts = optional(map(object({ display_name = optional(string, "Terraform-managed.") iam_project_roles = optional(list(string)) })), {}) })">object({…})</code> | | <code>{}</code> | | ||
| [data_overrides](variables.tf#L69) | Optional values that override corresponding data from files. Takes precedence over file data and `data_defaults`. | <code title="object({ billing_account = optional(string) contacts = optional(map(list(string))) parent = optional(string) prefix = optional(string) service_encryption_key_ids = optional(map(list(string))) service_perimeter_bridges = optional(list(string)) service_perimeter_standard = optional(string) tag_bindings = optional(map(string)) services = optional(list(string)) service_accounts = optional(map(object({ display_name = optional(string, "Terraform-managed.") iam_project_roles = optional(list(string)) }))) })">object({…})</code> | | <code>{}</code> | | ||
|
||
## Outputs | ||
|
||
|
@@ -134,6 +145,7 @@ services: | |
| [projects](outputs.tf#L17) | Project module outputs. | | | ||
| [service_accounts](outputs.tf#L22) | Service account emails. | | | ||
<!-- END TFDOC --> | ||
|
||
## Tests | ||
|
||
These tests validate fixes to the project factory. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -233,7 +233,7 @@ The module allows managing Shared VPC status for both hosts and service projects | |
Project service association for VPC host projects can be | ||
|
||
- authoritatively managed in the host project by enabling Shared VPC and specifying the set of service projects, or | ||
- additively managed in service projects by by enabling Shared VPC in the host project and then "attaching" each service project independently | ||
- additively managed in service projects by enabling Shared VPC in the host project and then "attaching" each service project independently | ||
|
||
IAM bindings in the host project for API service identities can be managed from service projects in two different ways: | ||
|
||
|
@@ -317,9 +317,51 @@ module "service-project" { | |
# tftest modules=2 resources=9 inventory=shared-vpc-auto-grants.yaml e2e | ||
``` | ||
|
||
In specific cases it might make sense to selectively grant the `compute.networkUser` role for service identities at the subnet level, and while that is best done via org policies it's also supported by this module. | ||
The `compute.networkUser` role for identities other than API services (e.g. users, groups or service accounts) can be managed via the `network_users` attribute, by specifying the list of identities. Avoid using dynamically generated lists, as this attribute is involved in a `for_each` loop and may result in Terraform errors. | ||
|
||
In this example, Compute service identity will be granted compute.networkUser in the `gce` subnet defined in `europe-west1` region. | ||
Note that this configuration grants the role at project level which results in the identities being able to configure resources on all the VPCs and subnets belonging to the host project. The most reliable way to restrict which subnets can be used on the newly created project is via the `compute.restrictSharedVpcSubnetworks` organization policy. For more information on the Org Policy configuration check the corresponding [Organization Policy section](#organization-policies). The following example details this configuration. | ||
|
||
```hcl | ||
module "host-project" { | ||
source = "./fabric/modules/project" | ||
billing_account = var.billing_account_id | ||
name = "host" | ||
parent = var.folder_id | ||
prefix = var.prefix | ||
shared_vpc_host_config = { | ||
enabled = true | ||
} | ||
} | ||
module "service-project" { | ||
source = "./fabric/modules/project" | ||
billing_account = var.billing_account_id | ||
name = "service" | ||
parent = var.folder_id | ||
prefix = var.prefix | ||
org_policies = { | ||
"compute.restrictSharedVpcSubnetworks" = { | ||
rules = [{ | ||
allow = { | ||
values = ["projects/host/regions/europe-west1/subnetworks/prod-default-ew1"] | ||
} | ||
}] | ||
} | ||
} | ||
services = [ | ||
"container.googleapis.com", | ||
] | ||
shared_vpc_service_config = { | ||
host_project = module.host-project.project_id | ||
network_users = ["group:[email protected]"] | ||
# reuse the list of services from the module's outputs | ||
service_iam_grants = module.service-project.services | ||
} | ||
} | ||
# tftest modules=2 resources=11 inventory=shared-vpc-host-project-iam.yaml e2e | ||
``` | ||
|
||
In specific cases it might make sense to selectively grant the `compute.networkUser` role for service identities at the subnet level, and while that is best done via org policies it's also supported by this module. In this example, Compute service identity and `[email protected]` Google Group will be granted compute.networkUser in the `gce` subnet defined in `europe-west1` region via the `service_identity_subnet_iam` and `network_subnet_users` attributes. | ||
|
||
```hcl | ||
module "host-project" { | ||
|
@@ -347,9 +389,12 @@ module "service-project" { | |
service_identity_subnet_iam = { | ||
"europe-west1/gce" = ["compute"] | ||
} | ||
network_subnet_users = { | ||
"europe-west1/gce" = ["group:[email protected]"] | ||
} | ||
} | ||
} | ||
# tftest modules=2 resources=6 inventory=shared-vpc-subnet-grants.yaml | ||
# tftest modules=2 resources=7 inventory=shared-vpc-subnet-grants.yaml | ||
``` | ||
|
||
## Organization Policies | ||
|
@@ -929,9 +974,9 @@ module "bucket" { | |
| [service_perimeter_standard](variables.tf#L280) | Name of VPC-SC Standard perimeter to add project into. See comment in the variables file for format. | <code>string</code> | | <code>null</code> | | ||
| [services](variables.tf#L286) | Service APIs to enable. | <code>list(string)</code> | | <code>[]</code> | | ||
| [shared_vpc_host_config](variables.tf#L292) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | <code title="object({ enabled = bool service_projects = optional(list(string), []) })">object({…})</code> | | <code>null</code> | | ||
| [shared_vpc_service_config](variables.tf#L301) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | <code title="object({ host_project = string service_identity_iam = optional(map(list(string)), {}) service_identity_subnet_iam = optional(map(list(string)), {}) service_iam_grants = optional(list(string), []) })">object({…})</code> | | <code title="{ host_project = null }">{…}</code> | | ||
| [skip_delete](variables.tf#L324) | Allows the underlying resources to be destroyed without destroying the project itself. | <code>bool</code> | | <code>false</code> | | ||
| [tag_bindings](variables.tf#L330) | Tag bindings for this project, in key => tag value id format. | <code>map(string)</code> | | <code>null</code> | | ||
| [shared_vpc_service_config](variables.tf#L301) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | <code title="object({ host_project = string network_users = optional(list(string), []) service_identity_iam = optional(map(list(string)), {}) service_identity_subnet_iam = optional(map(list(string)), {}) service_iam_grants = optional(list(string), []) network_subnet_users = optional(map(list(string)), {}) })">object({…})</code> | | <code title="{ host_project = null }">{…}</code> | | ||
| [skip_delete](variables.tf#L329) | Allows the underlying resources to be destroyed without destroying the project itself. | <code>bool</code> | | <code>false</code> | | ||
| [tag_bindings](variables.tf#L335) | Tag bindings for this project, in key => tag value id format. | <code>map(string)</code> | | <code>null</code> | | ||
|
||
## Outputs | ||
|
||
|
Oops, something went wrong.