diff --git a/fast/stages/0-org-setup/organization.tf b/fast/stages/0-org-setup/organization.tf index 165a1e50d..d2ced0949 100644 --- a/fast/stages/0-org-setup/organization.tf +++ b/fast/stages/0-org-setup/organization.tf @@ -99,8 +99,8 @@ module "organization" { tags_config = { ignore_iam = true } - workforce_identity_config = try( - local.organization.workforce_identity_config, null + workforce_identity_pools = try( + local.organization.workforce_identity_pools, null ) } diff --git a/fast/stages/0-org-setup/schemas/organization.schema.json b/fast/stages/0-org-setup/schemas/organization.schema.json index f7fc110d1..23772c8bb 100644 --- a/fast/stages/0-org-setup/schemas/organization.schema.json +++ b/fast/stages/0-org-setup/schemas/organization.schema.json @@ -353,177 +353,180 @@ } } }, - "workforce_identity_config": { + "workforce_identity_pools": { "type": "object", "additionalProperties": false, - "properties": { - "pool_name": { - "type": "string" - }, - "display_name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "disabled": { - "type": "boolean" - }, - "session_duration": { - "type": "string" - }, - "access_restrictions": { + "patternProperties": { + "^[a-z][a-z0-9-]+[a-z0-9]$": { "type": "object", "additionalProperties": false, "properties": { - "allowed_services": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "domain": { - "type": "string" - } - } - } + "display_name": { + "type": "string" }, - "disable_programmatic_signin": { + "description": { + "type": "string" + }, + "disabled": { "type": "boolean" - } - } - }, - "providers": { - "type": "object", - "additionalProperties": false, - "patternProperties": { - "^[a-z][a-z0-9-]+[a-z0-9]$": { + }, + "session_duration": { + "type": "string" + }, + "access_restrictions": { "type": "object", "additionalProperties": false, "properties": { - "description": { - "type": "string" + "allowed_services": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "domain": { + "type": "string" + } + } + } }, - "display_name": { - "type": "string" - }, - "attribute_condition": { - "type": "string" - }, - "attribute_mapping": { - "type": "object" - }, - "attribute_mapping_template": { - "type": "string", - "enum": [ - "azuread", - "okta" - ] - }, - "disabled": { + "disable_programmatic_signin": { "type": "boolean" - }, - "identity_provider": { + } + } + }, + "providers": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^[a-z][a-z0-9-]+[a-z0-9]$": { "type": "object", - "oneOf": [ - { - "properties": { - "oidc": { - "type": "object", - "description": "OpenID Connect configuration.", + "additionalProperties": false, + "properties": { + "description": { + "type": "string" + }, + "display_name": { + "type": "string" + }, + "attribute_condition": { + "type": "string" + }, + "attribute_mapping": { + "type": "object" + }, + "attribute_mapping_template": { + "type": "string", + "enum": [ + "azuread", + "okta" + ] + }, + "disabled": { + "type": "boolean" + }, + "identity_provider": { + "type": "object", + "oneOf": [ + { "properties": { - "issuer_uri": { - "type": "string", - "description": "The URI of the OIDC issuer." - }, - "client_id": { - "type": "string", - "description": "The client ID." - }, - "client_secret": { - "type": "string", - "description": "The client secret (optional)." - }, - "jwks_json": { - "type": "string", - "description": "JSON Web Key Set as a JSON string (optional)." - }, - "web_sso_config": { + "oidc": { "type": "object", - "description": "Optional Web SSO configuration for OIDC.", + "description": "OpenID Connect configuration.", "properties": { - "response_type": { + "issuer_uri": { "type": "string", - "default": "CODE", - "enum": [ - "CODE", - "ID_TOKEN" - ] + "description": "The URI of the OIDC issuer." }, - "assertion_claims_behavior": { + "client_id": { "type": "string", - "default": "ONLY_ID_TOKEN_CLAIMS", - "enum": [ - "MERGE_USER_INFO_OVER_ID_TOKEN_CLAIMS", - "ONLY_ID_TOKEN_CLAIMS" - ] + "description": "The client ID." }, - "additional_scopes": { - "type": "array", - "items": { - "type": "string" - } + "client_secret": { + "type": "string", + "description": "The client secret (optional)." + }, + "jwks_json": { + "type": "string", + "description": "JSON Web Key Set as a JSON string (optional)." + }, + "web_sso_config": { + "type": "object", + "description": "Optional Web SSO configuration for OIDC.", + "properties": { + "response_type": { + "type": "string", + "default": "CODE", + "enum": [ + "CODE", + "ID_TOKEN" + ] + }, + "assertion_claims_behavior": { + "type": "string", + "default": "ONLY_ID_TOKEN_CLAIMS", + "enum": [ + "MERGE_USER_INFO_OVER_ID_TOKEN_CLAIMS", + "ONLY_ID_TOKEN_CLAIMS" + ] + }, + "additional_scopes": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [], + "additionalProperties": false } }, - "required": [], + "required": [ + "issuer_uri", + "client_id" + ], "additionalProperties": false } }, "required": [ - "issuer_uri", - "client_id" + "oidc" ], "additionalProperties": false - } - }, - "required": [ - "oidc" - ], - "additionalProperties": false - }, - { - "properties": { - "saml": { - "type": "object", - "description": "SAML configuration.", + }, + { "properties": { - "idp_metadata_xml": { - "type": "string", - "description": "SAML IdP metadata XML." + "saml": { + "type": "object", + "description": "SAML configuration.", + "properties": { + "idp_metadata_xml": { + "type": "string", + "description": "SAML IdP metadata XML." + } + }, + "required": [ + "idp_metadata_xml" + ], + "additionalProperties": false } }, "required": [ - "idp_metadata_xml" + "saml" ], "additionalProperties": false } - }, - "required": [ - "saml" - ], - "additionalProperties": false - } - ] - }, - "oauth2_client_config": { - "type": "object", - "additionalProperties": false, - "properties": { - "extended_attributes": { - "$ref": "#/$defs/wfif_oauth2_client_attrs" + ] }, - "extra_attributes": { - "$ref": "#/$defs/wfif_oauth2_client_attrs" + "oauth2_client_config": { + "type": "object", + "additionalProperties": false, + "properties": { + "extended_attributes": { + "$ref": "#/$defs/wfif_oauth2_client_attrs" + }, + "extra_attributes": { + "$ref": "#/$defs/wfif_oauth2_client_attrs" + } + } } } } diff --git a/fast/stages/0-org-setup/schemas/organization.schema.md b/fast/stages/0-org-setup/schemas/organization.schema.md index 086b1875d..84379b1f9 100644 --- a/fast/stages/0-org-setup/schemas/organization.schema.md +++ b/fast/stages/0-org-setup/schemas/organization.schema.md @@ -98,36 +98,37 @@ - **pam_entitlements**: *reference([pam_entitlements](#refs-pam_entitlements))* - **tags**: *object*
*additional properties: object* -- **workforce_identity_config**: *object* +- **workforce_identity_pools**: *object*
*additional properties: false* - - **pool_name**: *string* - - **display_name**: *string* - - **description**: *string* - - **disabled**: *boolean* - - **session_duration**: *string* - - **access_restrictions**: *object* + - **`^[a-z][a-z0-9-]+[a-z0-9]$`**: *object*
*additional properties: false* - - **allowed_services**: *array* - - items: *object* -
*additional properties: false* - - **domain**: *string* - - **disable_programmatic_signin**: *boolean* - - **providers**: *object* -
*additional properties: false* - - **`^[a-z][a-z0-9-]+[a-z0-9]$`**: *object* + - **display_name**: *string* + - **description**: *string* + - **disabled**: *boolean* + - **session_duration**: *string* + - **access_restrictions**: *object*
*additional properties: false* - - **description**: *string* - - **display_name**: *string* - - **attribute_condition**: *string* - - **attribute_mapping**: *object* - - **attribute_mapping_template**: *string* -
*enum: ['azuread', 'okta']* - - **disabled**: *boolean* - - **identity_provider**: *object* - - **oauth2_client_config**: *object* + - **allowed_services**: *array* + - items: *object* +
*additional properties: false* + - **domain**: *string* + - **disable_programmatic_signin**: *boolean* + - **providers**: *object* +
*additional properties: false* + - **`^[a-z][a-z0-9-]+[a-z0-9]$`**: *object*
*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))* + - **description**: *string* + - **display_name**: *string* + - **attribute_condition**: *string* + - **attribute_mapping**: *object* + - **attribute_mapping_template**: *string* +
*enum: ['azuread', 'okta']* + - **disabled**: *boolean* + - **identity_provider**: *object* + - **oauth2_client_config**: *object* +
*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))* ## Definitions diff --git a/modules/organization/README.md b/modules/organization/README.md index 1e5b5e2f5..da0cf91bb 100644 --- a/modules/organization/README.md +++ b/modules/organization/README.md @@ -911,60 +911,60 @@ Auto-population of provider attributes is supported via the `attribute_mapping_t module "org" { source = "./fabric/modules/organization" organization_id = var.organization_id - workforce_identity_config = { - # optional, defaults to 'default' - pool_name = "test-pool" - display_name = "Test Pool" - description = "Workforce pool for testing." - providers = { - saml-basic = { - attribute_mapping_template = "azuread" - identity_provider = { - saml = { - idp_metadata_xml = "..." - } - } - } - saml-full = { - attribute_mapping = { - "google.subject" = "assertion.sub" - } - identity_provider = { - saml = { - idp_metadata_xml = "..." - } - } - oauth2_client_config = { - extra_attributes = { - issuer_uri = "https://login.microsoftonline.com/abcdef/v2.0" - client_id = "client-id" - client_secret = "client-secret" - attributes_type = "AZURE_AD_GROUPS_ID" - query_filter = "mail:gcp" - } - } - } - oidc-full = { - attribute_mapping = { - "google.subject" = "assertion.sub" - } - identity_provider = { - oidc = { - issuer_uri = "https://sts.windows.net/abcd01234/" - client_id = "https://analysis.windows.net/powerbi/connector/GoogleBigQuery" - client_secret = "client-secret" - web_sso_config = { - response_type = "CODE" - assertion_claims_behavior = "MERGE_USER_INFO_OVER_ID_TOKEN_CLAIMS" + workforce_identity_pools = { + "test-pool" = { + display_name = "Test Pool" + description = "Workforce pool for testing." + providers = { + saml-basic = { + attribute_mapping_template = "azuread" + identity_provider = { + saml = { + idp_metadata_xml = "..." } } } - oauth2_client_config = { - extra_attributes = { - issuer_uri = "https://login.microsoftonline.com/abcd01234/v2.0" - client_id = "client-id" - client_secret = "client-secret" - attributes_type = "AZURE_AD_GROUPS_MAIL" + saml-full = { + attribute_mapping = { + "google.subject" = "assertion.sub" + } + identity_provider = { + saml = { + idp_metadata_xml = "..." + } + } + oauth2_client_config = { + extra_attributes = { + issuer_uri = "https://login.microsoftonline.com/abcdef/v2.0" + client_id = "client-id" + client_secret = "client-secret" + attributes_type = "AZURE_AD_GROUPS_ID" + query_filter = "mail:gcp" + } + } + } + oidc-full = { + attribute_mapping = { + "google.subject" = "assertion.sub" + } + identity_provider = { + oidc = { + issuer_uri = "https://sts.windows.net/abcd01234/" + client_id = "https://analysis.windows.net/powerbi/connector/GoogleBigQuery" + client_secret = "client-secret" + web_sso_config = { + response_type = "CODE" + assertion_claims_behavior = "MERGE_USER_INFO_OVER_ID_TOKEN_CLAIMS" + } + } + } + oauth2_client_config = { + extra_attributes = { + issuer_uri = "https://login.microsoftonline.com/abcd01234/v2.0" + client_id = "client-id" + client_secret = "client-secret" + attributes_type = "AZURE_AD_GROUPS_MAIL" + } } } } @@ -1033,7 +1033,7 @@ module "org" { | [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({…}) | | {} | -| [workforce_identity_config](variables-identity-providers.tf#L17) | Workforce Identity Federation pool and providers. | object({…}) | | null | +| [workforce_identity_pools](variables-identity-providers.tf#L17) | Workforce Identity Federation pools and providers. | map(object({…})) | | {} | ## Outputs @@ -1055,6 +1055,7 @@ module "org" { | [sink_writer_identities](outputs.tf#L125) | Writer identities created for each sink. | | | [tag_keys](outputs.tf#L133) | Tag key resources. | | | [tag_values](outputs.tf#L142) | Tag value resources. | | -| [workforce_identity_provider_names](outputs.tf#L150) | Workforce Identity provider names. | | -| [workforce_identity_providers](outputs.tf#L157) | Workforce Identity provider attributes. | | +| [workforce_identity_pool_ids](outputs.tf#L150) | Workforce identity pool ids. | | +| [workforce_identity_provider_names](outputs.tf#L157) | Workforce Identity provider names. | | +| [workforce_identity_providers](outputs.tf#L164) | Workforce Identity provider attributes. | | diff --git a/modules/organization/identity-providers.tf b/modules/organization/identity-providers.tf index 66d04ae27..39aeed87f 100644 --- a/modules/organization/identity-providers.tf +++ b/modules/organization/identity-providers.tf @@ -35,23 +35,32 @@ locals { "attribute.user_email" = "assertion.attributes.email[0]" } } + + wfif_providers = merge([ + for k, v in var.workforce_identity_pools : { + for pk, pv in v.providers : "${k}/${pk}" => merge(pv, { + provider_id = pk + pool = k + }) + } + ]...) } resource "google_iam_workforce_pool" "default" { - count = var.workforce_identity_config == null ? 0 : 1 + for_each = var.workforce_identity_pools parent = var.organization_id location = "global" - workforce_pool_id = var.workforce_identity_config.pool_name - description = var.workforce_identity_config.description - disabled = var.workforce_identity_config.disabled - display_name = var.workforce_identity_config.display_name - session_duration = var.workforce_identity_config.session_duration + workforce_pool_id = each.key + description = each.value.description + disabled = each.value.disabled + display_name = each.value.display_name + session_duration = each.value.session_duration dynamic "access_restrictions" { - for_each = var.workforce_identity_config.access_restrictions != null ? [""] : [] + for_each = each.value.access_restrictions != null ? [""] : [] content { - disable_programmatic_signin = var.workforce_identity_config.access_restrictions.disable_programmatic_signin + disable_programmatic_signin = each.value.access_restrictions.disable_programmatic_signin dynamic "allowed_services" { - for_each = coalesce(var.workforce_identity_config.access_restrictions.allowed_services, []) + for_each = coalesce(each.value.access_restrictions.allowed_services, []) content { domain = allowed_services.value.domain } @@ -61,8 +70,8 @@ resource "google_iam_workforce_pool" "default" { } resource "google_iam_workforce_pool_provider" "default" { - for_each = try(var.workforce_identity_config.providers, {}) - provider_id = each.key + 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 @@ -71,8 +80,10 @@ resource "google_iam_workforce_pool_provider" "default" { try(local.wfif_attribute_mappings[each.value.attribute_mapping_template], {}), each.value.attribute_mapping ) - location = google_iam_workforce_pool.default[0].location - workforce_pool_id = google_iam_workforce_pool.default[0].workforce_pool_id + location = "global" + workforce_pool_id = ( + google_iam_workforce_pool.default[each.value.pool].workforce_pool_id + ) dynamic "saml" { for_each = each.value.identity_provider.saml == null ? [] : [""] content { diff --git a/modules/organization/outputs.tf b/modules/organization/outputs.tf index 3eec6ceea..38e979c80 100644 --- a/modules/organization/outputs.tf +++ b/modules/organization/outputs.tf @@ -147,6 +147,13 @@ output "tag_values" { } } +output "workforce_identity_pool_ids" { + description = "Workforce identity pool ids." + value = { + for k, v in google_iam_workforce_pool.default : k => v.name + } +} + output "workforce_identity_provider_names" { description = "Workforce Identity provider names." value = { @@ -157,9 +164,9 @@ output "workforce_identity_provider_names" { output "workforce_identity_providers" { description = "Workforce Identity provider attributes." value = { - for k, v in google_iam_workforce_pool_provider.default : k => { - name = v.name - pool = try(google_iam_workforce_pool.default[0].name, null) + for k, v in local.wfif_providers : k => { + name = google_iam_workforce_pool_provider.default[k].name + pool = google_iam_workforce_pool.default[v.pool].name } } } diff --git a/modules/organization/variables-identity-providers.tf b/modules/organization/variables-identity-providers.tf index f698665c9..63a4ef7eb 100644 --- a/modules/organization/variables-identity-providers.tf +++ b/modules/organization/variables-identity-providers.tf @@ -14,10 +14,9 @@ * limitations under the License. */ -variable "workforce_identity_config" { - description = "Workforce Identity Federation pool and providers." - type = object({ - pool_name = optional(string, "default") +variable "workforce_identity_pools" { + description = "Workforce Identity Federation pools and providers." + type = map(object({ description = optional(string) disabled = optional(bool) display_name = optional(string) @@ -69,12 +68,12 @@ variable "workforce_identity_config" { })) }), {}) })), {}) - }) - nullable = true - default = null + })) + nullable = false + default = {} validation { condition = alltrue([ - for v in try(var.workforce_identity_config.providers, {}) : contains( + for v in try(var.workforce_identity_pools.providers, {}) : contains( ["azuread", "okta"], coalesce(v.attribute_mapping_template, "azuread") ) @@ -83,7 +82,7 @@ variable "workforce_identity_config" { } validation { condition = alltrue([ - for v in try(var.workforce_identity_config.providers, {}) : ( + 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 @@ -92,7 +91,7 @@ variable "workforce_identity_config" { } validation { condition = alltrue([ - for v in try(var.workforce_identity_config.providers, {}) : contains( + 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 @@ -103,7 +102,7 @@ variable "workforce_identity_config" { } validation { condition = alltrue([ - for v in try(var.workforce_identity_config.providers, {}) : contains( + 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 @@ -114,7 +113,7 @@ variable "workforce_identity_config" { } validation { condition = alltrue([ - for v in try(var.workforce_identity_config.providers, {}) : contains( + 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 @@ -125,7 +124,7 @@ variable "workforce_identity_config" { } validation { condition = alltrue([ - for v in try(var.workforce_identity_config.providers, {}) : contains( + 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 diff --git a/tests/modules/organization/examples/wfif.yaml b/tests/modules/organization/examples/wfif.yaml index cb46b67b1..d44d4ac3c 100644 --- a/tests/modules/organization/examples/wfif.yaml +++ b/tests/modules/organization/examples/wfif.yaml @@ -13,7 +13,7 @@ # limitations under the License. values: - module.org.google_iam_workforce_pool.default[0]: + module.org.google_iam_workforce_pool.default["test-pool"]: access_restrictions: [] description: Workforce pool for testing. disabled: null @@ -23,11 +23,12 @@ values: session_duration: 3600s timeouts: null workforce_pool_id: test-pool - module.org.google_iam_workforce_pool_provider.default["oidc-full"]: + module.org.google_iam_workforce_pool_provider.default["test-pool/oidc-full"]: attribute_condition: null attribute_mapping: google.subject: assertion.sub description: null + detailed_audit_logging: null disabled: false display_name: null extended_attributes_oauth2_client: [] @@ -53,9 +54,10 @@ values: response_type: CODE provider_id: oidc-full saml: [] + scim_usage: null timeouts: null workforce_pool_id: test-pool - module.org.google_iam_workforce_pool_provider.default["saml-basic"]: + module.org.google_iam_workforce_pool_provider.default["test-pool/saml-basic"]: attribute_condition: null attribute_mapping: attribute.first_name: assertion.attributes.givenname[0] @@ -65,6 +67,7 @@ values: google.groups: assertion.attributes.groups google.subject: assertion.subject description: null + detailed_audit_logging: null disabled: false display_name: null extended_attributes_oauth2_client: [] @@ -74,13 +77,15 @@ values: provider_id: saml-basic saml: - idp_metadata_xml: ... + scim_usage: null timeouts: null workforce_pool_id: test-pool - module.org.google_iam_workforce_pool_provider.default["saml-full"]: + module.org.google_iam_workforce_pool_provider.default["test-pool/saml-full"]: attribute_condition: null attribute_mapping: google.subject: assertion.sub description: null + detailed_audit_logging: null disabled: false display_name: null extended_attributes_oauth2_client: [] @@ -98,6 +103,7 @@ values: provider_id: saml-full saml: - idp_metadata_xml: ... + scim_usage: null timeouts: null workforce_pool_id: test-pool