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

@@ -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."
}
}