diff --git a/.vscode/settings.json b/.vscode/settings.json
index 0e43c4ee5..3640ee323 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -7,6 +7,7 @@
"url": "http://json-schema.org/draft-07/schema#"
}
],
+ "files.insertFinalNewline": true,
"yaml.schemas": {
"modules/organization/schemas/custom-role.schema.json": [
"data/**/custom-roles/**/*yaml"
diff --git a/modules/apigee/recipe-apigee-swp/README.md b/modules/apigee/recipe-apigee-swp/README.md
index a16513f75..400b97b1a 100644
--- a/modules/apigee/recipe-apigee-swp/README.md
+++ b/modules/apigee/recipe-apigee-swp/README.md
@@ -54,4 +54,4 @@ module "recipe_apigee_swp" {
subnet_proxy_only_ip_cidr_range = "10.16.2.0/24"
}
}
-# tftest modules=10 resources=43
+# tftest modules=10 resources=44
diff --git a/modules/folder/README.md b/modules/folder/README.md
index 4c2dde157..bfc5b02e6 100644
--- a/modules/folder/README.md
+++ b/modules/folder/README.md
@@ -9,6 +9,7 @@ This module allows the creation and management of folders, including support for
- [Assured Workload Folder](#assured-workload-folder)
- [Privileged Access Manager (PAM) Entitlements](#privileged-access-manager-pam-entitlements)
- [Privileged Access Manager (PAM) Entitlements Factory](#privileged-access-manager-pam-entitlements-factory)
+- [Service Agents](#service-agents)
- [Organization policies](#organization-policies)
- [Organization Policy Factory](#organization-policy-factory)
- [Hierarchical Firewall Policy Attachments](#hierarchical-firewall-policy-attachments)
@@ -135,7 +136,7 @@ module "folder" {
Note that using PAM entitlements requires specific roles to be granted to the users and groups that will be using them. For more information, see the [official documentation](https://cloud.google.com/iam/docs/pam-permissions-and-setup#before-you-begin).
-Additionally, the Privileged Access Manager Service Agent must be created and granted the `roles/privilegedaccessmanager.folderServiceAgent` role. The service agent is not created automatically, and you can find the `gcloud` command to create it in the `service_agents` output of this module. For more information on service agents, see the [official documentation](https://cloud.google.com/iam/docs/service-agents). Refer to the [organization module's documentation](../organization/README.md#privileged-access-manager-pam-entitlements) for an example on how to grant the required role.
+Additionally, the Privileged Access Manager Service Agent must be created and granted the `roles/privilegedaccessmanager.folderServiceAgent` role. The service agent can be created automatically by adding `privilegedaccessmanager.googleapis.com` to the `services` list in the `service_agents_config` variable. Refer to the [organization module's documentation](../organization/README.md#privileged-access-manager-pam-entitlements) for an example on how to grant the required role.
```hcl
module "folder" {
@@ -179,6 +180,26 @@ module "folder" {
}
```
+## Service Agents
+
+The module allows managing service agents at the folder level. Service agent creation is triggered by adding them to the `service_agents_config.services` variable.
+
+```hcl
+module "folder" {
+ source = "./fabric/modules/folder"
+ parent = var.folder_id
+ name = "Folder name"
+ service_agents_config = {
+ services = [
+ "osconfig.googleapis.com",
+ "privilegedaccessmanager.googleapis.com",
+ "progressiverollout.googleapis.com"
+ ]
+ }
+}
+# tftest inventory=agents.yaml
+```
+
## Organization policies
To manage organization policies, the `orgpolicy.googleapis.com` service should be enabled in the quota project.
@@ -720,7 +741,7 @@ module "folder" {
| [pam.tf](./pam.tf) | None | google_privileged_access_manager_entitlement |
| [scc-mute-configs.tf](./scc-mute-configs.tf) | Folder-level SCC mute configurations. | google_scc_v2_folder_mute_config |
| [scc-sha-custom-modules.tf](./scc-sha-custom-modules.tf) | Folder-level Custom modules with Security Health Analytics. | google_scc_management_folder_security_health_analytics_custom_module |
-| [service-agents.tf](./service-agents.tf) | Service agents supporting resources. | |
+| [service-agents.tf](./service-agents.tf) | Service agents supporting resources. | google_folder_service_identity |
| [tags.tf](./tags.tf) | None | google_tags_tag_binding |
| [variables-iam.tf](./variables-iam.tf) | None | |
| [variables-logging.tf](./variables-logging.tf) | None | |
@@ -760,7 +781,8 @@ module "folder" {
| [parent](variables.tf#L272) | Parent in folders/folder_id or organizations/org_id format. | string | | null |
| [scc_mute_configs](variables-scc.tf#L17) | SCC mute configurations keyed by name. | map(object({…})) | | {} |
| [scc_sha_custom_modules](variables-scc.tf#L27) | SCC custom modules keyed by module name. | map(object({…})) | | {} |
-| [tag_bindings](variables.tf#L286) | Tag bindings for this folder, in key => tag value id format. | map(string) | | null |
+| [service_agents_config](variables.tf#L286) | Service agents configuration. | object({…}) | | {} |
+| [tag_bindings](variables.tf#L296) | Tag bindings for this folder, in key => tag value id format. | map(string) | | null |
## Outputs
@@ -774,5 +796,5 @@ module "folder" {
| [organization_policies_ids](outputs.tf#L54) | Map of ORGANIZATION_POLICIES => ID in the folder. | |
| [scc_custom_sha_modules_ids](outputs.tf#L59) | Map of SCC CUSTOM SHA MODULES => ID in the folder. | |
| [service_agents](outputs.tf#L64) | Identities of all folder-level service agents. | |
-| [sink_writer_identities](outputs.tf#L69) | Writer identities created for each sink. | |
+| [sink_writer_identities](outputs.tf#L72) | Writer identities created for each sink. | |
diff --git a/modules/folder/outputs.tf b/modules/folder/outputs.tf
index b1c14d76d..7a2a8e356 100644
--- a/modules/folder/outputs.tf
+++ b/modules/folder/outputs.tf
@@ -64,6 +64,9 @@ output "scc_custom_sha_modules_ids" {
output "service_agents" {
description = "Identities of all folder-level service agents."
value = local.service_agents
+ depends_on = [
+ google_folder_service_identity.default
+ ]
}
output "sink_writer_identities" {
diff --git a/modules/folder/service-agents.tf b/modules/folder/service-agents.tf
index 90b4a2c8a..177989573 100644
--- a/modules/folder/service-agents.tf
+++ b/modules/folder/service-agents.tf
@@ -18,22 +18,21 @@
locals {
_sa_raw = yamldecode(file("${path.module}/service-agents.yaml"))
- _sa0 = {
+ service_agents = {
for agent in local._sa_raw :
agent.name => {
- create_command = (
- "gcloud beta services identity create --service=${agent.api} --folder=${local.folder_number}"
- )
+ name = agent.name
+ api = agent.api
display_name = agent.display_name
- email = templatestring(agent.identity, {
- folder_number = local.folder_number
- })
- }
- }
- service_agents = {
- for k, v in local._sa0 :
- k => merge(v, {
- iam_email = "serviceAccount:${v.email}"
- })
+ email = templatestring(agent.identity, { folder_number = local.folder_number })
+ iam_email = "serviceAccount:${templatestring(agent.identity, { folder_number = local.folder_number })}"
+ } if contains(var.service_agents_config.services, agent.api)
}
}
+
+resource "google_folder_service_identity" "default" {
+ provider = google-beta
+ for_each = var.service_agents_config.create_agents ? local.service_agents : {}
+ folder = local.folder_number
+ service = each.value.api
+}
diff --git a/modules/folder/service-agents.yaml b/modules/folder/service-agents.yaml
index e1e2da4aa..401972126 100644
--- a/modules/folder/service-agents.yaml
+++ b/modules/folder/service-agents.yaml
@@ -16,70 +16,40 @@
display_name: Access Approval Service Agent
api: accessapproval.googleapis.com
identity: service-f${folder_number}@gcp-sa-accessapproval.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- name: assuredworkloads
display_name: Assured Workloads Service Agent
api: assuredworkloads.googleapis.com
identity: service-folder-${folder_number}@gcp-sa-assuredworkloads.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- name: audit-manager
display_name: Audit Manager Service Agent
api: auditmanager.googleapis.com
identity: service-folder-${folder_number}@gcp-sa-audit-manager.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- name: cloudcontrolspartner
display_name: Cloud Controls Partner Service Agent
api: cloudcontrolspartner.googleapis.com
identity: service-folder-${folder_number}@gcp-sa-cloudcontrolspartner.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- name: logging
display_name: Cloud Logging Service Agent
api: logging.googleapis.com
identity: service-folder-${folder_number}@gcp-sa-logging.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- name: observability
display_name: Cloud Observability Service Account
api: observability.googleapis.com
identity: service-folder-${folder_number}@gcp-sa-observability.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- name: osconfig-rollout
display_name: Google Cloud OS Config Rollout Service Agent
api: osconfig.googleapis.com
identity: service-folder-${folder_number}@gcp-sa-osconfig-rollout.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- name: osconfig
display_name: Google Cloud OS Config Service Agent
api: osconfig.googleapis.com
identity: service-folder-${folder_number}@gcp-sa-osconfig.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- name: pam
display_name: Privileged Access Manager Service Agent
api: privilegedaccessmanager.googleapis.com
identity: service-folder-${folder_number}@gcp-sa-pam.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- name: progrollout
display_name: Progressive Rollout Service Agent
api: progressiverollout.googleapis.com
identity: service-folder-${folder_number}@gcp-sa-progrollout.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
diff --git a/modules/folder/variables.tf b/modules/folder/variables.tf
index 0f29d1552..8be11070a 100644
--- a/modules/folder/variables.tf
+++ b/modules/folder/variables.tf
@@ -283,6 +283,16 @@ variable "parent" {
}
}
+variable "service_agents_config" {
+ description = "Service agents configuration."
+ type = object({
+ services = optional(list(string), [])
+ create_agents = optional(bool, true)
+ })
+ default = {}
+ nullable = false
+}
+
variable "tag_bindings" {
description = "Tag bindings for this folder, in key => tag value id format."
type = map(string)
diff --git a/modules/organization/README.md b/modules/organization/README.md
index 72882bf5e..2ba7d4f8d 100644
--- a/modules/organization/README.md
+++ b/modules/organization/README.md
@@ -18,6 +18,7 @@ To manage organization policies, the `orgpolicy.googleapis.com` service should b
- [Example](#example)
- [IAM](#iam)
- [Conditional IAM by Principals](#conditional-iam-by-principals)
+- [Service Agents](#service-agents)
- [Organization Policies](#organization-policies)
- [Organization Policy Factory](#organization-policy-factory)
- [Organization Policy Custom Constraints](#organization-policy-custom-constraints)
@@ -166,6 +167,25 @@ module "org" {
# tftest modules=1 resources=2 inventory=iam-bpc.yaml
```
+## Service Agents
+
+The module allows managing service agents at the organization level. Service agent creation is triggered by adding them to the `service_agents_config.services` variable.
+
+```hcl
+module "org" {
+ source = "./fabric/modules/organization"
+ organization_id = var.organization_id
+ service_agents_config = {
+ services = [
+ "osconfig.googleapis.com",
+ "privilegedaccessmanager.googleapis.com",
+ "progressiverollout.googleapis.com"
+ ]
+ }
+}
+# tftest inventory=agents.yaml
+```
+
## Organization Policies
### Organization Policy Factory
@@ -269,19 +289,22 @@ custom.dataprocNoMoreThan10Workers:
Note that using PAM entitlements requires specific roles to be granted to the users and groups that will be using them. For more information, see the [official documentation](https://cloud.google.com/iam/docs/pam-permissions-and-setup#before-you-begin).
-Additionally, the Privileged Access Manager Service Agent must be created and granted the `roles/privilegedaccessmanager.organizationServiceAgent` role. The service agent is not created automatically, and you can find the `gcloud` command to create it in the `service_agents` output of this module. For more information on service agents, see the [official documentation](https://cloud.google.com/iam/docs/service-agents).
+Additionally, the Privileged Access Manager Service Agent must be created and granted the `roles/privilegedaccessmanager.organizationServiceAgent` role. The service agent can be created automatically by adding `privilegedaccessmanager.googleapis.com` to the `services` list in the `service_agents_config` variable.
-The following example shows how to grant the required role to the PAM service agent:
+The following example shows how to create the service agent and grant the required role:
```hcl
module "organization" {
source = "./fabric/modules/organization"
- organization_id = var.org_id
+ organization_id = var.organization_id
factories_config = {
pam_entitlements = "factory/"
}
+ service_agents_config = {
+ services = ["privilegedaccessmanager.googleapis.com"]
+ }
iam = {
- "roles/privilegedaccessmanager.serviceAgent" = [
+ "roles/privilegedaccessmanager.organizationServiceAgent" = [
module.organization.service_agents.pam.iam_email
]
}
@@ -991,7 +1014,7 @@ module "org" {
| [pam.tf](./pam.tf) | None | google_privileged_access_manager_entitlement |
| [scc-mute-configs.tf](./scc-mute-configs.tf) | Organization-level SCC mute configurations. | google_scc_v2_organization_mute_config |
| [scc-sha-custom-modules.tf](./scc-sha-custom-modules.tf) | Organization-level Custom modules with Security Health Analytics. | google_scc_management_organization_security_health_analytics_custom_module |
-| [service-agents.tf](./service-agents.tf) | Service agents supporting resources. | |
+| [service-agents.tf](./service-agents.tf) | Service agents supporting resources. | google_organization_service_identity |
| [tags.tf](./tags.tf) | Manages GCP Secure Tags, keys, values, and IAM. | google_tags_tag_binding · google_tags_tag_key · google_tags_tag_key_iam_binding · google_tags_tag_key_iam_member · google_tags_tag_value · google_tags_tag_value_iam_binding · google_tags_tag_value_iam_member |
| [variables-iam.tf](./variables-iam.tf) | None | |
| [variables-identity-providers.tf](./variables-identity-providers.tf) | None | |
@@ -1030,6 +1053,7 @@ module "org" {
| [pam_entitlements](variables-pam.tf#L17) | Privileged Access Manager entitlements for this resource, keyed by entitlement ID. | map(object({…})) | | {} |
| [scc_mute_configs](variables-scc.tf#L17) | SCC mute configurations keyed by name. | map(object({…})) | | {} |
| [scc_sha_custom_modules](variables-scc.tf#L28) | SCC custom modules keyed by module name. | map(object({…})) | | {} |
+| [service_agents_config](variables.tf#L182) | Service agents configuration. | object({…}) | | {} |
| [tag_bindings](variables-tags.tf#L89) | Tag bindings for this organization, in key => tag value id format. | map(string) | | {} |
| [tags](variables-tags.tf#L96) | Tags by key name. If `id` is provided, key or value creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} |
| [tags_config](variables-tags.tf#L161) | Fine-grained control on tag resource and IAM creation. | object({…}) | | {} |
@@ -1053,10 +1077,10 @@ module "org" {
| [scc_custom_sha_modules_ids](outputs.tf#L118) | Map of SCC CUSTOM SHA MODULES => ID in the organization. | |
| [scc_mute_configs](outputs.tf#L123) | SCC mute configurations. | |
| [service_agents](outputs.tf#L128) | Identities of all organization-level service agents. | |
-| [sink_writer_identities](outputs.tf#L133) | Writer identities created for each sink. | |
-| [tag_keys](outputs.tf#L141) | Tag key resources. | |
-| [tag_values](outputs.tf#L150) | Tag value resources. | |
-| [workforce_identity_pool_ids](outputs.tf#L158) | Workforce identity pool ids. | |
-| [workforce_identity_provider_names](outputs.tf#L165) | Workforce Identity provider names. | |
-| [workforce_identity_providers](outputs.tf#L172) | Workforce Identity provider attributes. | |
+| [sink_writer_identities](outputs.tf#L136) | Writer identities created for each sink. | |
+| [tag_keys](outputs.tf#L144) | Tag key resources. | |
+| [tag_values](outputs.tf#L153) | Tag value resources. | |
+| [workforce_identity_pool_ids](outputs.tf#L161) | Workforce identity pool ids. | |
+| [workforce_identity_provider_names](outputs.tf#L168) | Workforce Identity provider names. | |
+| [workforce_identity_providers](outputs.tf#L175) | Workforce Identity provider attributes. | |
diff --git a/modules/organization/outputs.tf b/modules/organization/outputs.tf
index fba1ec910..b8e46acc4 100644
--- a/modules/organization/outputs.tf
+++ b/modules/organization/outputs.tf
@@ -128,6 +128,9 @@ output "scc_mute_configs" {
output "service_agents" {
description = "Identities of all organization-level service agents."
value = local.service_agents
+ depends_on = [
+ google_organization_service_identity.default
+ ]
}
output "sink_writer_identities" {
diff --git a/modules/organization/service-agents.tf b/modules/organization/service-agents.tf
index 24bdc9a48..327e5a3d1 100644
--- a/modules/organization/service-agents.tf
+++ b/modules/organization/service-agents.tf
@@ -18,27 +18,25 @@
locals {
_sa_raw = yamldecode(file("${path.module}/service-agents.yaml"))
- _sa0 = {
+ service_agents = {
for agent in local._sa_raw :
agent.name => {
- create_command = (
- "gcloud beta services identity create --service=${agent.api} --organization=${local.organization_id_numeric}"
- )
+ name = agent.name
+ api = agent.api
display_name = agent.display_name
- email = templatestring(agent.identity, {
- organization_number = local.organization_id_numeric
- })
-
- }
- }
- service_agents = {
- for k, v in local._sa0 :
- k => merge(v, {
- iam_email = "serviceAccount:${v.email}"
- })
+ email = templatestring(agent.identity, { organization_number = local.organization_id_numeric })
+ iam_email = "serviceAccount:${templatestring(agent.identity, { organization_number = local.organization_id_numeric })}"
+ } if contains(var.service_agents_config.services, agent.api)
}
service_agents_ctx = {
for k, v in local.service_agents :
"$service_agents:${k}" => v.iam_email
}
}
+
+resource "google_organization_service_identity" "default" {
+ provider = google-beta
+ for_each = var.service_agents_config.create_agents ? local.service_agents : {}
+ organization = local.organization_id_numeric
+ service = each.value.api
+}
diff --git a/modules/organization/service-agents.yaml b/modules/organization/service-agents.yaml
index d7e91a0ed..fe3923029 100644
--- a/modules/organization/service-agents.yaml
+++ b/modules/organization/service-agents.yaml
@@ -16,208 +16,104 @@
display_name: Access Approval Service Agent
api: accessapproval.googleapis.com
identity: service-o${organization_number}@gcp-sa-accessapproval.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
- name: assuredoss
display_name: Assured OSS Service Agent
api: assuredoss.googleapis.com
identity: service-org-${organization_number}@gcp-sa-assuredoss.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
- name: asm-hpsa
display_name: Attack Surface Management Service Agent
api: securitycenter.googleapis.com
identity: service-org-${organization_number}@gcp-sa-asm-hpsa.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
- name: audit-manager
display_name: Audit Manager Service Agent
api: auditmanager.googleapis.com
identity: service-org-${organization_number}@gcp-sa-audit-manager.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
- name: chronicle-soar
display_name: Chronicle Soar Service Agent
api: chronicle.googleapis.com
identity: service-org-${organization_number}@gcp-sa-chronicle-soar.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
- name: effectivepolicy
display_name: Cloud Asset Effective Policy Service Agent
api: cloudasset.googleapis.com
identity: service-org-${organization_number}@gcp-sa-effectivepolicy.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
- name: othercloudcfg
display_name: Cloud Asset Other Cloud Config Service Agent
api: cloudasset.googleapis.com
identity: service-org-${organization_number}@gcp-sa-othercloudcfg.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
- name: cloudkms
display_name: Cloud KMS Organization Service Agent
api: cloudkms.googleapis.com
identity: service-org-${organization_number}@gcp-sa-cloudkms.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
- name: logging
display_name: Cloud Logging Service Agent
api: logging.googleapis.com
identity: service-org-${organization_number}@gcp-sa-logging.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
- name: nss-hpsa
display_name: Cloud Notebook Security Scanner Service Agent
api: notebooksecurityscanner.googleapis.com
identity: service-org-${organization_number}@gcp-sa-nss-hpsa.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
- name: observability
display_name: Cloud Observability Service Account
api: observability.googleapis.com
identity: service-org-${organization_number}@gcp-sa-observability.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
- name: cloudresourcemanager
display_name: Cloud Resource Manager Service Agent
api: cloudresourcemanager.googleapis.com
identity: service-org-${organization_number}@gcp-sa-cloudresourcemanager.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
- name: riskmanager
display_name: Cloud Risk Manager Service Agent
api: dlp.googleapis.com
identity: organizations-${organization_number}@gcp-sa-riskmanager.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
- name: scc-bulk-export
display_name: Cloud Security Command Center Bulk Export Service Account
api: securitycenter.googleapis.com
identity: service-org-${organization_number}@gcp-sa-scc-bulk-export.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
- name: scc-notification
display_name: Cloud Security Command Center Notification Service Account
api: securitycenter.googleapis.com
identity: service-org-${organization_number}@gcp-sa-scc-notification.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
- name: security-center-api
display_name: Cloud Security Command Center Service Agent
api: securitycenter.googleapis.com
identity: service-org-${organization_number}@security-center-api.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
- name: csc-hpsa
display_name: Cloud Security Compliance Service Agent
api: cloudsecuritycompliance.googleapis.com
identity: service-org-${organization_number}@gcp-sa-csc-hpsa.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
- name: ktd-hpsa
display_name: Container Threat Detection Service Agent
api: containerthreatdetection.googleapis.com
identity: service-org-${organization_number}@gcp-sa-ktd-hpsa.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
- name: dataplex-cmek
display_name: Dataplex Cmek Service Agent
api: dataplex.googleapis.com
identity: service-org-${organization_number}@gcp-sa-dataplex-cmek.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
- name: dataplex
display_name: Dataplex Service Agent
api: dataplex.googleapis.com
identity: service-org-${organization_number}@gcp-sa-dataplex.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
- name: osconfig-rollout
display_name: Google Cloud OS Config Rollout Service Agent
api: osconfig.googleapis.com
identity: service-org-${organization_number}@gcp-sa-osconfig-rollout.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
- name: osconfig
display_name: Google Cloud OS Config Service Agent
api: osconfig.googleapis.com
identity: service-org-${organization_number}@gcp-sa-osconfig.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
- name: v1-remediator
display_name: Policy Remediator Service Agent (prod)
api: policyremediator.googleapis.com
identity: service-org-${organization_number}@gcp-sa-v1-remediator.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
- name: pam
display_name: Privileged Access Manager Service Agent
api: privilegedaccessmanager.googleapis.com
identity: service-org-${organization_number}@gcp-sa-pam.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
- name: progrollout
display_name: Progressive Rollout Service Agent
api: progressiverollout.googleapis.com
identity: service-org-${organization_number}@gcp-sa-progrollout.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
- name: sccspanner
display_name: SCC CMEK Spanner Service Agent (PROD)
api: securitycenter.googleapis.com
identity: service-org-${organization_number}@gcp-sa-sccspanner.iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
diff --git a/modules/organization/variables.tf b/modules/organization/variables.tf
index 478dc4469..27181c4f0 100644
--- a/modules/organization/variables.tf
+++ b/modules/organization/variables.tf
@@ -178,3 +178,13 @@ variable "organization_id" {
error_message = "The organization_id must in the form organizations/nnn."
}
}
+
+variable "service_agents_config" {
+ description = "Service agents configuration."
+ type = object({
+ services = optional(list(string), [])
+ create_agents = optional(bool, true)
+ })
+ default = {}
+ nullable = false
+}
diff --git a/modules/project/service-agents.yaml b/modules/project/service-agents.yaml
index 25d011987..e13398fe2 100644
--- a/modules/project/service-agents.yaml
+++ b/modules/project/service-agents.yaml
@@ -44,6 +44,14 @@
is_primary: false
aliases: []
skip_iam: false
+- name: aiplatform-pie
+ display_name: AI Platform Private Instance (PIE) Service Agent
+ api: aiplatform.googleapis.com
+ identity: service-${project_number}@gcp-sa-aiplatform-pie.${universe_domain}iam.gserviceaccount.com
+ role: roles/aiplatform.serviceAgent
+ is_primary: false
+ aliases: []
+ skip_iam: false
- name: vertex-eval
display_name: AI Platform Rapid Eval Service Agent
api: aiplatform.googleapis.com
@@ -132,6 +140,14 @@
is_primary: true
aliases: []
skip_iam: false
+- name: agentgateway
+ display_name: Agent Gateway Service Account
+ api: networkservices.googleapis.com
+ identity: service-${project_number}@gcp-sa-agentgateway.${universe_domain}iam.gserviceaccount.com
+ role: roles/agentgateway.serviceAgent
+ is_primary: false
+ aliases: []
+ skip_iam: false
- name: alloydb
display_name: AlloyDB Service Account
api: alloydb.googleapis.com
@@ -277,6 +293,14 @@
is_primary: true
aliases: []
skip_iam: false
+- name: appoptimize
+ display_name: App Optimize Service Agent
+ api: appoptimize.googleapis.com
+ identity: service-${project_number}@gcp-sa-appoptimize.${universe_domain}iam.gserviceaccount.com
+ role: null
+ is_primary: true
+ aliases: []
+ skip_iam: false
- name: integrations
display_name: Application Integration Service Agent
api: integrations.googleapis.com
@@ -309,6 +333,14 @@
is_primary: true
aliases: []
skip_iam: false
+- name: autoannotate
+ display_name: Auto Annotate Service Account
+ api: storage.googleapis.com
+ identity: service-${project_number}@gcp-sa-autoannotate.${universe_domain}iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+ skip_iam: false
- name: recommendationengine
display_name: AutoML Recommendations Service Account
api: recommendationengine.googleapis.com
@@ -406,6 +438,14 @@
is_primary: true
aliases: []
skip_iam: false
+- name: bqms
+ display_name: BigQuery Migration Service Agent
+ api: bigquerymigration.googleapis.com
+ identity: service-${project_number}@gcp-sa-bqms.${universe_domain}iam.gserviceaccount.com
+ role: null
+ is_primary: true
+ aliases: []
+ skip_iam: false
- name: prod-bigqueryomni
display_name: BigQuery Omni Service Agent
api: bigquery.googleapis.com
@@ -446,14 +486,6 @@
is_primary: true
aliases: []
skip_iam: false
-- name: chronicle-sv
- display_name: Chronicle Security Validation Service Account
- api: chronicle.googleapis.com
- identity: service-${project_number}@gcp-sa-chronicle-sv.${universe_domain}iam.gserviceaccount.com
- role: null
- is_primary: false
- aliases: []
- skip_iam: false
- name: chronicle
display_name: Chronicle Service Account
api: chronicle.googleapis.com
@@ -648,14 +680,6 @@
is_primary: false
aliases: []
skip_iam: false
-- name: lifesciences
- display_name: Cloud Life Sciences Service Agent
- api: lifesciences.googleapis.com
- identity: service-${project_number}@gcp-sa-lifesciences.${universe_domain}iam.gserviceaccount.com
- role: roles/lifesciences.serviceAgent
- is_primary: true
- aliases: []
- skip_iam: false
- name: logging
display_name: Cloud Logging Service Account
api: logging.googleapis.com
@@ -864,6 +888,14 @@
is_primary: true
aliases: []
skip_iam: false
+- name: hypercomputecluster
+ display_name: Cluster Director Service Agent
+ api: hypercomputecluster.googleapis.com
+ identity: service-${project_number}@gcp-sa-hypercomputecluster.${universe_domain}iam.gserviceaccount.com
+ role: roles/hypercomputecluster.serviceAgent
+ is_primary: true
+ aliases: []
+ skip_iam: false
- name: compute-system
display_name: Compute Engine Service Agent
api: compute.googleapis.com
@@ -985,6 +1017,14 @@
is_primary: true
aliases: []
skip_iam: false
+- name: ces
+ display_name: Customer Engagement Suite Service Account
+ api: ces.googleapis.com
+ identity: service-${project_number}@gcp-sa-ces.${universe_domain}iam.gserviceaccount.com
+ role: roles/ces.serviceAgent
+ is_primary: true
+ aliases: []
+ skip_iam: false
- name: dataconnectors
display_name: Data Connectors Service Account
api: dataconnectors.googleapis.com
@@ -1009,6 +1049,14 @@
is_primary: true
aliases: []
skip_iam: false
+- name: datastudio-cmek
+ display_name: Data Studio CMEK Service Account
+ api: datastudio.googleapis.com
+ identity: service-${project_number}@gcp-sa-datastudio-cmek.${universe_domain}iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+ skip_iam: false
- name: datastudio
display_name: Data Studio Service Account
api: datastudio.googleapis.com
@@ -1241,6 +1289,14 @@
is_primary: true
aliases: []
skip_iam: false
+- name: storage-search
+ display_name: GCS Search Service Account
+ api: storage.googleapis.com
+ identity: service-${project_number}@gcp-sa-storage-search.${universe_domain}iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+ skip_iam: false
- name: gkedataplanev2
display_name: GKE Dataplane V2 Service Account
api: gkedataplanev2.googleapis.com
@@ -1282,6 +1338,14 @@
is_primary: true
aliases: []
skip_iam: false
+- name: generativelanguage
+ display_name: Generative Language Service Agent
+ api: generativelanguage.googleapis.com
+ identity: service-${project_number}@gcp-sa-generativelanguage.${universe_domain}iam.gserviceaccount.com
+ role: null
+ is_primary: true
+ aliases: []
+ skip_iam: false
- name: gkeonprem
display_name: Gke On-Prem Service Account
api: gkeonprem.googleapis.com
@@ -1401,6 +1465,14 @@
aliases:
- storage
skip_iam: false
+- name: diagon
+ display_name: Hypercompute Diagon Service Account
+ api: hypercomputecluster.googleapis.com
+ identity: service-${project_number}@gcp-sa-diagon.${universe_domain}iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+ skip_iam: false
- name: iap
display_name: IAP Service Account
api: iap.googleapis.com
@@ -1417,6 +1489,14 @@
is_primary: false
aliases: []
skip_iam: false
+- name: global-spanner
+ display_name: Infra Spanner Production Service Account
+ api: spanner.googleapis.com
+ identity: service-${project_number}@gcp-sa-global-spanner.${universe_domain}iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+ skip_iam: false
- name: config
display_name: Infrastructure Manager Service Account
api: config.googleapis.com
@@ -1860,6 +1940,14 @@
is_primary: true
aliases: []
skip_iam: false
+- name: vs-cmek
+ display_name: Vector Search Cmek Service Account
+ api: vectorsearch.googleapis.com
+ identity: service-${project_number}@gcp-sa-vs-cmek.${universe_domain}iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+ skip_iam: false
- name: vectorsearch
display_name: Vector Search Service Account
api: vectorsearch.googleapis.com
@@ -1868,6 +1956,14 @@
is_primary: true
aliases: []
skip_iam: false
+- name: vertex-sandbox
+ display_name: Vertex AI Agent Sandbox Service Agent
+ api: aiplatform.googleapis.com
+ identity: service-${project_number}@gcp-sa-vertex-sandbox.${universe_domain}iam.gserviceaccount.com
+ role: roles/aiplatform.agentSandboxServiceAgent
+ is_primary: false
+ aliases: []
+ skip_iam: false
- name: vertex-shtune
display_name: Vertex AI Ancillary Secure Fine Tuning Service Agent
api: aiplatform.googleapis.com
@@ -1964,6 +2060,14 @@
is_primary: false
aliases: []
skip_iam: false
+- name: vertex-vtc
+ display_name: Vertex AI Training Cluster Service Agent
+ api: aiplatform.googleapis.com
+ identity: service-${project_number}@gcp-sa-vertex-vtc.${universe_domain}iam.gserviceaccount.com
+ role: null
+ is_primary: false
+ aliases: []
+ skip_iam: false
- name: vertex-agent
display_name: Vertex Agent Service Agent
api: aiplatform.googleapis.com
@@ -2012,3 +2116,4 @@
is_primary: false
aliases: []
skip_iam: false
+
diff --git a/tests/modules/folder/examples/agents.yaml b/tests/modules/folder/examples/agents.yaml
new file mode 100644
index 000000000..bab15407f
--- /dev/null
+++ b/tests/modules/folder/examples/agents.yaml
@@ -0,0 +1,39 @@
+# Copyright 2026 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.folder.google_folder.folder[0]:
+ deletion_protection: false
+ display_name: Folder name
+ parent: folders/1122334455
+ tags: null
+ timeouts: null
+ module.folder.google_folder_service_identity.default["osconfig"]:
+ service: osconfig.googleapis.com
+ timeouts: null
+ module.folder.google_folder_service_identity.default["osconfig-rollout"]:
+ service: osconfig.googleapis.com
+ timeouts: null
+ module.folder.google_folder_service_identity.default["pam"]:
+ service: privilegedaccessmanager.googleapis.com
+ timeouts: null
+ module.folder.google_folder_service_identity.default["progrollout"]:
+ service: progressiverollout.googleapis.com
+ timeouts: null
+
+counts:
+ google_folder: 1
+ google_folder_service_identity: 4
+ modules: 1
+ resources: 5
diff --git a/tests/modules/organization/examples/agents.yaml b/tests/modules/organization/examples/agents.yaml
new file mode 100644
index 000000000..0a449e23c
--- /dev/null
+++ b/tests/modules/organization/examples/agents.yaml
@@ -0,0 +1,36 @@
+# Copyright 2026 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.org.google_organization_service_identity.default["osconfig"]:
+ organization: '1122334455'
+ service: osconfig.googleapis.com
+ timeouts: null
+ module.org.google_organization_service_identity.default["osconfig-rollout"]:
+ organization: '1122334455'
+ service: osconfig.googleapis.com
+ timeouts: null
+ module.org.google_organization_service_identity.default["pam"]:
+ organization: '1122334455'
+ service: privilegedaccessmanager.googleapis.com
+ timeouts: null
+ module.org.google_organization_service_identity.default["progrollout"]:
+ organization: '1122334455'
+ service: progressiverollout.googleapis.com
+ timeouts: null
+
+counts:
+ google_organization_service_identity: 4
+ modules: 1
+ resources: 4
diff --git a/tools/build_service_agents.py b/tools/build_service_agents.py
index a032ca98e..4c432e434 100755
--- a/tools/build_service_agents.py
+++ b/tools/build_service_agents.py
@@ -24,6 +24,7 @@
# ]
# ///
+from collections import Counter
from dataclasses import asdict, dataclass
from itertools import chain
@@ -133,6 +134,17 @@ class Agent:
is_primary: bool
aliases: list[str]
skip_iam: bool
+ node_type: str
+
+ def to_dict(self):
+ d = asdict(self)
+ d.pop('node_type', None)
+ if self.node_type in ['organization', 'folder']:
+ d.pop('is_primary', None)
+ d.pop('role', None)
+ d.pop('skip_iam', None)
+ d.pop('aliases', None)
+ return d
@click.command()
@@ -180,6 +192,12 @@ def main(mode, e2e=False):
if identity in IGNORED_AGENTS or '-IDENTIFIER' in identity:
continue
+ role = col2.code.get_text() if 'roles/' in agent_text else None
+
+ # Ignore Apigee Core service agent as it shares email with primary agent
+ if identity == 'service-PROJECT_NUMBER@gcp-sa-apigee.iam.gserviceaccount.com' and role == 'roles/apigee.coreServiceAgent':
+ continue
+
if identity in AGENT_NAME_OVERRIDE:
name = AGENT_NAME_OVERRIDE[identity]
else:
@@ -221,6 +239,7 @@ def main(mode, e2e=False):
is_primary=PRIMARY_OVERRIDE.get(name, is_primary),
aliases=ALIASES.get(name, []),
skip_iam=skip_iam,
+ node_type=mode,
)
if mode == 'project' and agent.name == 'cloudservices':
@@ -231,7 +250,11 @@ def main(mode, e2e=False):
# make sure all names and aliases are different:
names = set(agent.name for agent in agents)
- assert len(names) == len(agents)
+ duplicate_names = [
+ name for name, count in Counter(agent.name for agent in agents).items()
+ if count > 1
+ ]
+ assert len(names) == len(agents), f"duplicate names found: {duplicate_names}"
aliases = set(chain.from_iterable(agent.aliases for agent in agents))
assert aliases.isdisjoint(names)
@@ -244,7 +267,7 @@ def main(mode, e2e=False):
header = open(__file__).readlines()[2:15]
print("".join(header))
# and print all the agents
- print(yaml.safe_dump([asdict(a) for a in agents], sort_keys=False))
+ print(yaml.safe_dump([a.to_dict() for a in agents], sort_keys=False))
else:
jit_services = {}
result = {"locals": {"jit_services": jit_services}}