Add support for SCIM to workforce identity (#3951)

* Add support for SCIM to workforce identity

* Update schemas and tests
This commit is contained in:
Julio Castillo
2026-05-10 13:21:09 +02:00
committed by GitHub
parent 91fe329aca
commit 78a5ffa198
7 changed files with 219 additions and 70 deletions

View File

@@ -424,6 +424,10 @@
"disabled": {
"type": "boolean"
},
"detailed_audit_logging": {
"type": "boolean",
"default": false
},
"identity_provider": {
"type": "object",
"oneOf": [
@@ -527,6 +531,44 @@
"$ref": "#/$defs/wfif_oauth2_client_attrs"
}
}
},
"scim_tenant": {
"type": "object",
"additionalProperties": false,
"required": [
"id",
"claim_mapping"
],
"properties": {
"id": {
"type": "string"
},
"claim_mapping": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"location": {
"type": "string",
"default": "global"
},
"display_name": {
"type": "string"
},
"description": {
"type": "string"
},
"hard_delete": {
"type": "boolean"
}
}
},
"scim_usage": {
"type": "string",
"enum": [
"ENABLED_FOR_GROUPS"
]
}
}
}

View File

@@ -124,11 +124,24 @@
- **attribute_mapping_template**: *string*
<br>*enum: ['azuread', 'okta']*
- **disabled**: *boolean*
- **detailed_audit_logging**: *boolean*
- **identity_provider**: *object*
- **oauth2_client_config**: *object*
<br>*additional properties: false*
- **extended_attributes**: *reference([wfif_oauth2_client_attrs](#refs-wfif_oauth2_client_attrs))*
- **extra_attributes**: *reference([wfif_oauth2_client_attrs](#refs-wfif_oauth2_client_attrs))*
- **scim_tenant**: *object*
<br>*additional properties: false*
- ⁺**id**: *string*
- ⁺**claim_mapping**: *object*
<br>*additional properties: string*
- **location**: *string*
<br>*default: global*
- **display_name**: *string*
- **description**: *string*
- **hard_delete**: *boolean*
- **scim_usage**: *string*
<br>*enum: ['ENABLED_FOR_GROUPS']*
## Definitions

View File

@@ -971,6 +971,7 @@ module "org" {
}
}
oidc-full = {
scim_usage = "ENABLED_FOR_GROUPS"
attribute_mapping = {
"google.subject" = "assertion.sub"
}
@@ -993,12 +994,20 @@ module "org" {
attributes_type = "AZURE_AD_GROUPS_MAIL"
}
}
scim_tenant = {
id = "my-scim-tenant"
display_name = "My SCIM Tenant"
claim_mapping = {
"google.subject" = "user.externalId"
"google.group" = "group.externalId"
}
}
}
}
}
}
}
# tftest modules=1 resources=4 inventory=wfif.yaml
# tftest inventory=wfif.yaml
```
<!-- TFDOC OPTS files:1 -->
@@ -1009,7 +1018,7 @@ module "org" {
|---|---|---|
| [assets.tf](./assets.tf) | None | <code>google_cloud_asset_organization_feed</code> |
| [iam.tf](./iam.tf) | IAM bindings. | <code>google_organization_iam_binding</code> · <code>google_organization_iam_custom_role</code> · <code>google_organization_iam_member</code> |
| [identity-providers.tf](./identity-providers.tf) | Workforce Identity Federation provider definitions. | <code>google_iam_workforce_pool</code> · <code>google_iam_workforce_pool_provider</code> |
| [identity-providers.tf](./identity-providers.tf) | Workforce Identity Federation provider definitions. | <code>google_iam_workforce_pool</code> · <code>google_iam_workforce_pool_provider</code> · <code>google_iam_workforce_pool_provider_scim_tenant</code> |
| [logging.tf](./logging.tf) | Log sinks and data access logs. | <code>google_bigquery_dataset_iam_member</code> · <code>google_logging_organization_exclusion</code> · <code>google_logging_organization_settings</code> · <code>google_logging_organization_sink</code> · <code>google_organization_iam_audit_config</code> · <code>google_project_iam_member</code> · <code>google_pubsub_topic_iam_member</code> · <code>google_storage_bucket_iam_member</code> |
| [main.tf](./main.tf) | Module-level locals and resources. | <code>google_compute_firewall_policy_association</code> · <code>google_essential_contacts_contact</code> |
| [org-policy-custom-constraints.tf](./org-policy-custom-constraints.tf) | None | <code>google_org_policy_custom_constraint</code> |
@@ -1080,11 +1089,12 @@ module "org" {
| [organization_policies_ids](outputs.tf#L113) | Map of ORGANIZATION_POLICIES => ID in the organization. | |
| [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#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. | |
| [scim_tenants](outputs.tf#L128) | Workforce Identity provider SCIM tenants. | |
| [service_agents](outputs.tf#L142) | Identities of all organization-level service agents. | |
| [sink_writer_identities](outputs.tf#L150) | Writer identities created for each sink. | |
| [tag_keys](outputs.tf#L158) | Tag key resources. | |
| [tag_values](outputs.tf#L167) | Tag value resources. | |
| [workforce_identity_pool_ids](outputs.tf#L175) | Workforce identity pool ids. | |
| [workforce_identity_provider_names](outputs.tf#L182) | Workforce Identity provider names. | |
| [workforce_identity_providers](outputs.tf#L189) | Workforce Identity provider attributes. | |
<!-- END TFDOC -->

View File

@@ -44,6 +44,14 @@ locals {
})
}
]...)
wfif_scim_tenants = {
for k, v in local.wfif_providers : k => merge(v.scim_tenant, {
provider_id = v.provider_id
pool = v.pool
})
if try(v.scim_tenant, null) != null
}
}
resource "google_iam_workforce_pool" "default" {
@@ -70,12 +78,14 @@ resource "google_iam_workforce_pool" "default" {
}
resource "google_iam_workforce_pool_provider" "default" {
for_each = local.wfif_providers
provider_id = each.value.provider_id
attribute_condition = each.value.attribute_condition
description = each.value.description
disabled = each.value.disabled
display_name = each.value.display_name
for_each = local.wfif_providers
provider_id = each.value.provider_id
attribute_condition = each.value.attribute_condition
description = each.value.description
disabled = each.value.disabled
detailed_audit_logging = each.value.detailed_audit_logging
display_name = each.value.display_name
scim_usage = each.value.scim_usage
attribute_mapping = merge(
try(local.wfif_attribute_mappings[each.value.attribute_mapping_template], {}),
each.value.attribute_mapping
@@ -193,3 +203,15 @@ resource "google_iam_workforce_pool_provider" "default" {
}
}
}
resource "google_iam_workforce_pool_provider_scim_tenant" "default" {
for_each = local.wfif_scim_tenants
location = each.value.location
workforce_pool_id = google_iam_workforce_pool.default[each.value.pool].workforce_pool_id
provider_id = google_iam_workforce_pool_provider.default[each.key].provider_id
scim_tenant_id = each.value.id
display_name = each.value.display_name
description = each.value.description
claim_mapping = each.value.claim_mapping
hard_delete = each.value.hard_delete
}

View File

@@ -125,6 +125,20 @@ output "scc_mute_configs" {
value = google_scc_v2_organization_mute_config.scc_mute_configs
}
output "scim_tenants" {
description = "Workforce Identity provider SCIM tenants."
value = {
for k, v in google_iam_workforce_pool_provider_scim_tenant.default : k => {
id = v.id
pool = v.workforce_pool_id
provider = v.provider_id
state = v.state
base_uri = v.base_uri
service_agent = v.service_agent
}
}
}
output "service_agents" {
description = "Identities of all organization-level service agents."
value = local.service_agents

View File

@@ -34,6 +34,8 @@ variable "workforce_identity_pools" {
attribute_mapping = optional(map(string), {})
attribute_mapping_template = optional(string)
disabled = optional(bool, false)
detailed_audit_logging = optional(bool, false)
scim_usage = optional(string)
identity_provider = object({
oidc = optional(object({
issuer_uri = string
@@ -67,70 +69,101 @@ variable "workforce_identity_pools" {
query_filter = optional(string)
}))
}), {})
scim_tenant = optional(object({
id = string
claim_mapping = map(string)
location = optional(string, "global")
display_name = optional(string)
description = optional(string)
hard_delete = optional(bool)
}))
})), {})
}))
nullable = false
default = {}
validation {
condition = alltrue([
for v in try(var.workforce_identity_pools.providers, {}) : contains(
["azuread", "okta"],
coalesce(v.attribute_mapping_template, "azuread")
)
])
condition = alltrue(flatten([
for pool in values(var.workforce_identity_pools) : [
for prov in values(pool.providers) : (
prov.attribute_mapping_template == null || contains(
["azuread", "okta"],
prov.attribute_mapping_template
)
)
]
]))
error_message = "Supported mapping templates are: azuread, okta."
}
validation {
condition = alltrue([
for v in try(var.workforce_identity_pools.providers, {}) : (
(try(v.identity_provider.oidc, null) == null ? 0 : 1) +
(try(v.identity_provider.saml, null) == null ? 0 : 1)
) == 1
])
condition = alltrue(flatten([
for pool in values(var.workforce_identity_pools) : [
for prov in values(pool.providers) : (
(try(prov.identity_provider.oidc, null) == null ? 0 : 1) +
(try(prov.identity_provider.saml, null) == null ? 0 : 1)
) == 1
]
]))
error_message = "Only one of identity_provider.oidc or identity_provider.saml can be defined."
}
validation {
condition = alltrue([
for v in try(var.workforce_identity_pools.providers, {}) : contains(
["CODE", "ID_TOKEN"],
coalesce(try(
v.identity_provider.oidc.web_sso_config.response_type, null
), "CODE")
)
])
error_message = "Invalid OIDC web SSO config response type."
condition = alltrue(flatten([
for pool in values(var.workforce_identity_pools) : [
for prov in values(pool.providers) : (
try(prov.identity_provider.oidc.web_sso_config.response_type, null) == null || contains(
["CODE", "ID_TOKEN"],
try(prov.identity_provider.oidc.web_sso_config.response_type, null)
)
)
]
]))
error_message = "Supported OIDC web SSO config response types are: CODE, ID_TOKEN."
}
validation {
condition = alltrue([
for v in try(var.workforce_identity_pools.providers, {}) : contains(
["MERGE_USER_INFO_OVER_ID_TOKEN_CLAIMS", "ONLY_ID_TOKEN_CLAIMS"],
coalesce(try(
v.identity_provider.oidc.web_sso_config.assertion_claims_behavior, null
), "MERGE_USER_INFO_OVER_ID_TOKEN_CLAIMS")
)
])
error_message = "Invalid OIDC web SSO config assertion claims behavior."
condition = alltrue(flatten([
for pool in values(var.workforce_identity_pools) : [
for prov in values(pool.providers) : (
try(prov.identity_provider.oidc.web_sso_config.assertion_claims_behavior, null) == null || contains(
["MERGE_USER_INFO_OVER_ID_TOKEN_CLAIMS", "ONLY_ID_TOKEN_CLAIMS"],
try(prov.identity_provider.oidc.web_sso_config.assertion_claims_behavior, null)
)
)
]
]))
error_message = "Supported OIDC web SSO config assertion claims behaviors are: MERGE_USER_INFO_OVER_ID_TOKEN_CLAIMS, ONLY_ID_TOKEN_CLAIMS."
}
validation {
condition = alltrue([
for v in try(var.workforce_identity_pools.providers, {}) : contains(
["AZURE_AD_GROUPS_MAIL", "AZURE_AD_GROUPS_ID"],
coalesce(try(
v.oauth2_client_config.extended_attributes.attributes_type, null
), "AZURE_AD_GROUPS_MAIL")
)
])
error_message = "Invalid AzureAD attribute type in OAuth 2.0 client extended attributes.."
condition = alltrue(flatten([
for pool in values(var.workforce_identity_pools) : [
for prov in values(pool.providers) : (
try(prov.oauth2_client_config.extended_attributes.attributes_type, null) == null || contains(
["AZURE_AD_GROUPS_MAIL", "AZURE_AD_GROUPS_ID"],
try(prov.oauth2_client_config.extended_attributes.attributes_type, null)
)
)
]
]))
error_message = "Supported AzureAD attribute types in OAuth 2.0 client extended attributes are: AZURE_AD_GROUPS_MAIL, AZURE_AD_GROUPS_ID."
}
validation {
condition = alltrue([
for v in try(var.workforce_identity_pools.providers, {}) : contains(
["AZURE_AD_GROUPS_MAIL", "AZURE_AD_GROUPS_ID"],
coalesce(try(
v.oauth2_client_config.extra_attributes.attributes_type, null
), "AZURE_AD_GROUPS_MAIL")
)
])
error_message = "Invalid AzureAD attribute type in OAuth 2.0 client extra attributes.."
condition = alltrue(flatten([
for pool in values(var.workforce_identity_pools) : [
for prov in values(pool.providers) : (
try(prov.oauth2_client_config.extra_attributes.attributes_type, null) == null || contains(
["AZURE_AD_GROUPS_MAIL", "AZURE_AD_GROUPS_ID"],
try(prov.oauth2_client_config.extra_attributes.attributes_type, null)
)
)
]
]))
error_message = "Supported AzureAD attribute types in OAuth 2.0 client extra attributes are: AZURE_AD_GROUPS_MAIL, AZURE_AD_GROUPS_ID."
}
validation {
condition = alltrue(flatten([
for pool in values(var.workforce_identity_pools) : [
for prov in values(pool.providers) :
prov.scim_usage == null || prov.scim_usage == "ENABLED_FOR_GROUPS"
]
]))
error_message = "Supported scim_usage values are: ENABLED_FOR_GROUPS."
}
}

View File

@@ -1,10 +1,10 @@
# Copyright 2025 Google LLC
# 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
# https://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,
@@ -28,7 +28,7 @@ values:
attribute_mapping:
google.subject: assertion.sub
description: null
detailed_audit_logging: null
detailed_audit_logging: false
disabled: false
display_name: null
extended_attributes_oauth2_client: []
@@ -54,7 +54,7 @@ values:
response_type: CODE
provider_id: oidc-full
saml: []
scim_usage: null
scim_usage: ENABLED_FOR_GROUPS
timeouts: null
workforce_pool_id: test-pool
module.org.google_iam_workforce_pool_provider.default["test-pool/saml-basic"]:
@@ -67,7 +67,7 @@ values:
google.groups: assertion.attributes.groups
google.subject: assertion.subject
description: null
detailed_audit_logging: null
detailed_audit_logging: false
disabled: false
display_name: null
extended_attributes_oauth2_client: []
@@ -85,7 +85,7 @@ values:
attribute_mapping:
google.subject: assertion.sub
description: null
detailed_audit_logging: null
detailed_audit_logging: false
disabled: false
display_name: null
extended_attributes_oauth2_client: []
@@ -106,9 +106,24 @@ values:
scim_usage: null
timeouts: null
workforce_pool_id: test-pool
module.org.google_iam_workforce_pool_provider_scim_tenant.default["test-pool/oidc-full"]:
claim_mapping:
google.group: group.externalId
google.subject: user.externalId
description: null
display_name: My SCIM Tenant
hard_delete: false
location: global
provider_id: oidc-full
scim_tenant_id: my-scim-tenant
timeouts: null
workforce_pool_id: test-pool
counts:
google_iam_workforce_pool: 1
google_iam_workforce_pool_provider: 3
google_iam_workforce_pool_provider_scim_tenant: 1
modules: 1
resources: 4
resources: 5
outputs: {}