diff --git a/blueprints/factories/project-factory/README.md b/blueprints/factories/project-factory/README.md
index c121d2b2..1025c825 100644
--- a/blueprints/factories/project-factory/README.md
+++ b/blueprints/factories/project-factory/README.md
@@ -76,7 +76,7 @@ module "projects" {
service_identities_iam = try(each.value.service_identities_iam, {})
vpc = try(each.value.vpc, null)
}
-# tftest modules=7 resources=36 inventory=example.yaml
+# tftest modules=7 resources=38 inventory=example.yaml
```
### Projects configuration
@@ -212,6 +212,16 @@ vpc:
# Host project the project will be service project of
host_project: fast-prod-net-spoke-0
+ # [opt] Services for which set up the IAM in the host project
+ service_iam_grants:
+ - dataproc.googleapis.com
+
+ # [opt] Roles to rant service project service identities in host project
+ service_identity_iam:
+ "roles/compute.networkUser":
+ - cloudservices
+ - container-engine
+
# [opt] Subnets in the host project where principals will be granted networkUser
# in region/subnet-name => [principals]
subnets_iam:
@@ -248,7 +258,7 @@ vpc:
| [service_identities_iam](variables.tf#L184) | Custom IAM settings for service identities in service => [role] format. | map(list(string))
| | {}
|
| [service_identities_iam_additive](variables.tf#L191) | Custom additive IAM settings for service identities in service => [role] format. | map(list(string))
| | {}
|
| [services](variables.tf#L198) | Services to be enabled for the project. | list(string)
| | []
|
-| [vpc](variables.tf#L205) | VPC configuration for the project. | object({…})
| | null
|
+| [vpc](variables.tf#L205) | VPC configuration for the project. | object({…})
| | {…}
|
## Outputs
diff --git a/blueprints/factories/project-factory/main.tf b/blueprints/factories/project-factory/main.tf
index f70684f9..9dbecf2f 100644
--- a/blueprints/factories/project-factory/main.tf
+++ b/blueprints/factories/project-factory/main.tf
@@ -15,6 +15,19 @@
*/
locals {
+ _gke_config_service_identity_iam = {
+ "roles/compute.networkUser" = compact([
+ var.vpc.gke_setup.enable_host_service_agent ? "container-engine" : null,
+ local.vpc_cloudservices ? "cloudservices" : null
+ ])
+ "roles/compute.securityAdmin" = compact([
+ var.vpc.gke_setup.enable_security_admin ? "container-engine" : null,
+ ])
+ "roles/container.hostServiceAgentUser" = compact([
+ var.vpc.gke_setup.enable_host_service_agent ? "container-engine" : null
+ ])
+ }
+
_group_iam = {
for r in local._group_iam_bindings : r => [
for k, v in var.group_iam :
@@ -76,10 +89,10 @@ locals {
]
}
_vpc_subnet_bindings = (
- local.vpc.subnets_iam == null || local.vpc.host_project == null
+ var.vpc.subnets_iam == null || var.vpc.host_project == null
? []
: flatten([
- for subnet, members in local.vpc.subnets_iam : [
+ for subnet, members in var.vpc.subnets_iam : [
for member in members : {
region = split("/", subnet)[0]
subnet = split("/", subnet)[1]
@@ -131,19 +144,18 @@ locals {
coalesce(var.labels, {}), coalesce(try(var.defaults.labels, {}), {})
)
services = distinct(concat(var.services, local._services))
- vpc = coalesce(var.vpc, {
- host_project = null, gke_setup = null, subnets_iam = null
- })
vpc_cloudservices = (
- local.vpc_gke_service_agent ||
+ var.vpc.gke_setup.enable_host_service_agent ||
contains(var.services, "compute.googleapis.com")
)
- vpc_gke_security_admin = coalesce(
- try(local.vpc.gke_setup.enable_security_admin, null), false
- )
- vpc_gke_service_agent = coalesce(
- try(local.vpc.gke_setup.enable_host_service_agent, null), false
- )
+
+ vpc_service_identity_iam = {
+ for role in setunion(keys(local._gke_config_service_identity_iam), keys(var.vpc.service_identity_iam)) :
+ role => setunion(
+ lookup(local._gke_config_service_identity_iam, role, []),
+ lookup(var.vpc.service_identity_iam, role, []),
+ )
+ }
vpc_subnet_bindings = {
for binding in local._vpc_subnet_bindings :
"${binding.subnet}:${binding.member}" => binding
@@ -169,7 +181,7 @@ module "billing-alert" {
module "dns" {
source = "../../../modules/dns"
for_each = toset(var.dns_zones)
- project_id = coalesce(local.vpc.host_project, module.project.project_id)
+ project_id = coalesce(var.vpc.host_project, module.project.project_id)
name = each.value
zone_config = {
domain = "${each.value}.${var.defaults.environment_dns_zone}"
@@ -194,20 +206,10 @@ module "project" {
service_encryption_key_ids = var.kms_service_agents
services = local.services
shared_vpc_service_config = var.vpc == null ? null : {
- host_project = local.vpc.host_project
+ host_project = var.vpc.host_project
# these are non-authoritative
- service_identity_iam = {
- "roles/compute.networkUser" = compact([
- local.vpc_gke_service_agent ? "container-engine" : null,
- local.vpc_cloudservices ? "cloudservices" : null
- ])
- "roles/compute.securityAdmin" = compact([
- local.vpc_gke_security_admin ? "container-engine" : null,
- ])
- "roles/container.hostServiceAgentUser" = compact([
- local.vpc_gke_service_agent ? "container-engine" : null
- ])
- }
+ service_identity_iam = local.vpc_service_identity_iam
+ service_iam_grants = var.vpc.service_iam_grants
}
}
@@ -221,8 +223,8 @@ module "service-accounts" {
resource "google_compute_subnetwork_iam_member" "default" {
for_each = local.vpc_subnet_bindings
- project = local.vpc.host_project
- subnetwork = "projects/${local.vpc.host_project}/regions/${each.value.region}/subnetworks/${each.value.subnet}"
+ project = var.vpc.host_project
+ subnetwork = "projects/${var.vpc.host_project}/regions/${each.value.region}/subnetworks/${each.value.subnet}"
region = each.value.region
role = "roles/compute.networkUser"
member = (
diff --git a/blueprints/factories/project-factory/sample-data/projects/project.yaml b/blueprints/factories/project-factory/sample-data/projects/project.yaml
index d8cf982e..e5957c77 100644
--- a/blueprints/factories/project-factory/sample-data/projects/project.yaml
+++ b/blueprints/factories/project-factory/sample-data/projects/project.yaml
@@ -99,6 +99,16 @@ vpc:
# Host project the project will be service project of
host_project: fast-dev-net-spoke-0
+ # [opt] Services for which set up the IAM in the host project
+ service_iam_grants:
+ - dataproc.googleapis.com
+
+ # [opt] Roles to rant service project service identities in host project
+ service_identity_iam:
+ "roles/compute.networkUser":
+ - cloudservices
+ - container-engine
+
# [opt] Subnets in the host project where principals will be granted networkUser
# in region/subnet-name => [principals]
subnets_iam:
diff --git a/blueprints/factories/project-factory/variables.tf b/blueprints/factories/project-factory/variables.tf
index a2089bcf..f4dbc629 100644
--- a/blueprints/factories/project-factory/variables.tf
+++ b/blueprints/factories/project-factory/variables.tf
@@ -206,11 +206,23 @@ variable "vpc" {
description = "VPC configuration for the project."
type = object({
host_project = string
- gke_setup = object({
- enable_security_admin = bool
- enable_host_service_agent = bool
- })
- subnets_iam = map(list(string))
+ gke_setup = optional(object({
+ enable_security_admin = optional(bool, false)
+ enable_host_service_agent = optional(bool, false)
+ }), {})
+ service_iam_grants = optional(list(string), [])
+ service_identity_iam = optional(map(list(string)), {})
+ subnets_iam = optional(map(list(string)), {})
})
- default = null
+ default = {
+ host_project = null
+ }
+ nullable = false
+ validation {
+ condition = var.vpc.host_project != null || (
+ var.vpc.host_project == null && length(var.vpc.gke_setup) == 0 && length(var.vpc.service_iam_grants) == 0 &&
+ length(var.vpc.service_identity_iam) == 0 && length(var.vpc.subnets_iam) == 0
+ )
+ error_message = "host_project is required if providing any additional configuration for vpc"
+ }
}
diff --git a/modules/project/README.md b/modules/project/README.md
index b6f21193..a39df3ce 100644
--- a/modules/project/README.md
+++ b/modules/project/README.md
@@ -259,6 +259,30 @@ module "service-project" {
# tftest modules=2 resources=8 inventory=shared-vpc.yaml
```
+The module allows also granting necessary permissions in host project to service identities by specifying which services will be used in service project in `grant_iam_for_services`.
+```hcl
+module "host-project" {
+ source = "./fabric/modules/project"
+ name = "my-host-project"
+ shared_vpc_host_config = {
+ enabled = true
+ }
+}
+
+module "service-project" {
+ source = "./fabric/modules/project"
+ name = "my-service-project"
+ services = [
+ "container.googleapis.com",
+ ]
+ shared_vpc_service_config = {
+ host_project = module.host-project.project_id
+ service_iam_grants = module.service-project.services
+ }
+}
+# tftest modules=2 resources=9 inventory=shared-vpc-auto-grants.yaml
+```
+
## Organization Policies
To manage organization policies, the `orgpolicy.googleapis.com` service should be enabled in the quota project.
@@ -577,7 +601,6 @@ output "compute_robot" {
```
-
## Files
@@ -631,9 +654,9 @@ output "compute_robot" {
| [service_perimeter_standard](variables.tf#L272) | Name of VPC-SC Standard perimeter to add project into. See comment in the variables file for format. | string
| | null
|
| [services](variables.tf#L278) | Service APIs to enable. | list(string)
| | []
|
| [shared_vpc_host_config](variables.tf#L284) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | object({…})
| | null
|
-| [shared_vpc_service_config](variables.tf#L293) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | object({…})
| | null
|
-| [skip_delete](variables.tf#L303) | Allows the underlying resources to be destroyed without destroying the project itself. | bool
| | false
|
-| [tag_bindings](variables.tf#L309) | Tag bindings for this project, in key => tag value id format. | map(string)
| | null
|
+| [shared_vpc_service_config](variables.tf#L293) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | object({…})
| | {…}
|
+| [skip_delete](variables.tf#L315) | Allows the underlying resources to be destroyed without destroying the project itself. | bool
| | false
|
+| [tag_bindings](variables.tf#L321) | Tag bindings for this project, in key => tag value id format. | map(string)
| | null
|
## Outputs
@@ -645,6 +668,6 @@ output "compute_robot" {
| [number](outputs.tf#L56) | Project number. | |
| [project_id](outputs.tf#L75) | Project id. | |
| [service_accounts](outputs.tf#L94) | Product robot service accounts in project. | |
-| [sink_writer_identities](outputs.tf#L110) | Writer identities created for each sink. | |
+| [services](outputs.tf#L110) | Service APIs to enabled in the project. | |
+| [sink_writer_identities](outputs.tf#L119) | Writer identities created for each sink. | |
-
diff --git a/modules/project/outputs.tf b/modules/project/outputs.tf
index 81f20cdd..ae7bbc6e 100644
--- a/modules/project/outputs.tf
+++ b/modules/project/outputs.tf
@@ -107,6 +107,15 @@ output "service_accounts" {
]
}
+output "services" {
+ description = "Service APIs to enabled in the project."
+ value = var.services
+ depends_on = [
+ google_project_service.project_services,
+ google_project_service_identity.jit_si,
+ ]
+}
+
output "sink_writer_identities" {
description = "Writer identities created for each sink."
value = {
diff --git a/modules/project/shared-vpc.tf b/modules/project/shared-vpc.tf
index ee2d6b41..d728f42b 100644
--- a/modules/project/shared-vpc.tf
+++ b/modules/project/shared-vpc.tf
@@ -17,15 +17,25 @@
# tfdoc:file:description Shared VPC project-level configuration.
locals {
+ _shared_vpc_agent_config = yamldecode(file("${path.module}/sharedvpc-agent-iam.yaml"))
+ _shared_vpc_agent_config_filtered = [
+ for config in local._shared_vpc_agent_config : config
+ if contains(var.shared_vpc_service_config.service_iam_grants, config.service)
+ ]
+ _shared_vpc_agent_grants = flatten(flatten([
+ for api in local._shared_vpc_agent_config_filtered : [
+ for service, roles in api.agents : [
+ for role in roles : { role = role, service = service }
+ ]
+ ]
+ ]))
+
# compute the host project IAM bindings for this project's service identities
_svpc_service_iam = flatten([
- for role, services in local._svpc_service_identity_iam : [
+ for role, services in var.shared_vpc_service_config.service_identity_iam : [
for service in services : { role = role, service = service }
]
])
- _svpc_service_identity_iam = coalesce(
- local.svpc_service_config.service_identity_iam, {}
- )
svpc_host_config = {
enabled = coalesce(
try(var.shared_vpc_host_config.enabled, null), false
@@ -34,11 +44,9 @@ locals {
try(var.shared_vpc_host_config.service_projects, null), []
)
}
- svpc_service_config = coalesce(var.shared_vpc_service_config, {
- host_project = null, service_identity_iam = {}
- })
+
svpc_service_iam = {
- for b in local._svpc_service_iam : "${b.role}:${b.service}" => b
+ for b in setunion(local._svpc_service_iam, local._shared_vpc_agent_grants) : "${b.role}:${b.service}" => b
}
}
@@ -59,7 +67,7 @@ resource "google_compute_shared_vpc_service_project" "service_projects" {
resource "google_compute_shared_vpc_service_project" "shared_vpc_service" {
provider = google-beta
- count = local.svpc_service_config.host_project != null ? 1 : 0
+ count = var.shared_vpc_service_config.host_project != null ? 1 : 0
host_project = var.shared_vpc_service_config.host_project
service_project = local.project.project_id
}
diff --git a/modules/project/sharedvpc-agent-iam.yaml b/modules/project/sharedvpc-agent-iam.yaml
new file mode 100644
index 00000000..3cb8ee3d
--- /dev/null
+++ b/modules/project/sharedvpc-agent-iam.yaml
@@ -0,0 +1,97 @@
+# Copyright 2023 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.
+
+# Cloud Composer
+# https://cloud.google.com/composer/docs/how-to/managing/configuring-shared-vpc#edit_permissions_for_the_composer_agent_service_account
+- service: composer.googleapis.com
+ agents:
+ composer:
+ - roles/compute.networkUser
+ - roles/composer.sharedVpcAgent
+
+# Compute Engine
+# TODO: identify docs
+- service: compute.googleapis.com
+ agents:
+ cloudservices:
+ - roles/compute.networkUser
+
+# Google Kubernetes Engine
+# https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-shared-vpc#enabling_and_granting_roles
+- service: container.googleapis.com
+ agents:
+ container:
+ - roles/compute.networkUser
+ - roles/container.hostServiceAgentUser
+ - roles/compute.securityAdmin # to manage firewall rules
+ cloudservices:
+ - roles/compute.networkUser
+
+# Dataflow
+# https://cloud.google.com/dataflow/docs/guides/specifying-networks#shared
+- service: dataflow.googleapis.com
+ agents:
+ dataflow:
+ - roles/compute.networkUser
+
+# Cloud Data Fusion
+# https://cloud.google.com/data-fusion/docs/how-to/create-private-ip#shared-vpc-network_1
+- service: datafusion.googleapis.com
+ agents:
+ datafusion:
+ - roles/compute.networkUser
+ dataproc:
+ - roles/compute.networkUser
+
+# Dataproc
+# https://cloud.google.com/dataproc/docs/concepts/configuring-clusters/network#create_a_cluster_that_uses_a_network_in_another_project
+- service: dataproc.googleapis.com
+ agents:
+ dataproc:
+ - roles/compute.networkUser
+ cloudservices:
+ - roles/compute.networkUser
+
+# Change Data Capture | Datastream
+# https://cloud.google.com/datastream/docs/create-a-private-connectivity-configuration
+- service: datastream.googleapis.com
+ agents:
+ datastream:
+ - roles/compute.networkAdmin
+
+# Cloud Functions
+# For shared connectors in host project
+# https://cloud.google.com/functions/docs/networking/shared-vpc-host-project
+- service: cloudfunctions.googleapis.com
+ agents:
+ cloudfunctions:
+ - roles/vpcaccess.user
+
+# Cloud Run
+# For shared connectors in host project
+# https://cloud.google.com/run/docs/configuring/shared-vpc-host-project
+- service: run.googleapis.com
+ agents:
+ run:
+ - roles/vpcaccess.user
+
+# Cloud Run / Cloud Functions
+# For connectors in service project
+# https://cloud.google.com/functions/docs/networking/shared-vpc-service-projects#grant-permissions
+- service: vpcaccess.googleapis.com
+ agents:
+ vpcaccess:
+ - roles/compute.networkUser
+ cloudservices:
+ - roles/compute.networkUser
diff --git a/modules/project/variables.tf b/modules/project/variables.tf
index bbd9fb60..eb6b5da7 100644
--- a/modules/project/variables.tf
+++ b/modules/project/variables.tf
@@ -292,12 +292,24 @@ variable "shared_vpc_host_config" {
variable "shared_vpc_service_config" {
description = "Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config)."
- # the list of valid service identities is in service-accounts.tf
+ # the list of valid service identities is in service-agents.yaml
type = object({
host_project = string
- service_identity_iam = optional(map(list(string)))
+ service_identity_iam = optional(map(list(string)), {})
+ service_iam_grants = optional(list(string), [])
})
- default = null
+ default = {
+ host_project = null
+ }
+ nullable = false
+ validation {
+ condition = var.shared_vpc_service_config.host_project != null || (
+ var.shared_vpc_service_config.host_project == null &&
+ length(var.shared_vpc_service_config.service_iam_grants) == 0 &&
+ length(var.shared_vpc_service_config.service_iam_grants) == 0
+ )
+ error_message = "You need to provide host_project when providing service_identity_iam or service_iam_grants"
+ }
}
variable "skip_delete" {
diff --git a/tests/blueprints/factories/project_factory/examples/example.yaml b/tests/blueprints/factories/project_factory/examples/example.yaml
index ee4a1b48..82c6dd53 100644
--- a/tests/blueprints/factories/project_factory/examples/example.yaml
+++ b/tests/blueprints/factories/project_factory/examples/example.yaml
@@ -170,6 +170,9 @@ values:
condition: []
project: fast-dev-net-spoke-0
role: roles/compute.securityAdmin
+ module.projects["project"].module.project.google_project_iam_member.shared_vpc_host_robots["roles/compute.networkUser:dataproc"]:
+ project: fast-dev-net-spoke-0
+ role: roles/compute.networkUser
module.projects["project"].module.project.google_kms_crypto_key_iam_member.service_identity_cmek["compute.key1"]:
condition: []
crypto_key_id: key1
@@ -245,7 +248,7 @@ counts:
google_org_policy_policy: 3
google_project: 1
google_project_iam_binding: 3
- google_project_iam_member: 2
+ google_project_iam_member: 4
google_project_service: 8
google_service_account: 2
google_storage_project_service_account: 1
diff --git a/tests/modules/project/examples/shared-vpc-auto-grants.yaml b/tests/modules/project/examples/shared-vpc-auto-grants.yaml
new file mode 100644
index 00000000..22661239
--- /dev/null
+++ b/tests/modules/project/examples/shared-vpc-auto-grants.yaml
@@ -0,0 +1,42 @@
+# Copyright 2023 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.
+
+values:
+ module.host-project.google_compute_shared_vpc_host_project.shared_vpc_host[0]:
+ project: my-host-project
+ module.host-project.google_project.project[0]:
+ project_id: my-host-project
+ module.service-project.google_compute_shared_vpc_service_project.shared_vpc_service[0]:
+ host_project: my-host-project
+ service_project: my-service-project
+ module.service-project.google_project.project[0]:
+ project_id: my-service-project
+ module.service-project.google_project_iam_member.shared_vpc_host_robots["roles/compute.networkUser:cloudservices"]:
+ condition: []
+ project: my-host-project
+ role: roles/compute.networkUser
+ module.service-project.google_project_iam_member.shared_vpc_host_robots["roles/compute.networkUser:container"]:
+ condition: []
+ project: my-host-project
+ role: roles/compute.networkUser
+ module.service-project.google_project_iam_member.shared_vpc_host_robots["roles/container.hostServiceAgentUser:container"]:
+ condition: []
+ project: my-host-project
+ role: roles/container.hostServiceAgentUser
+
+counts:
+ google_compute_shared_vpc_host_project: 1
+ google_compute_shared_vpc_service_project: 1
+ google_project: 2
+ google_project_iam_member: 4