Add support for Workforce Identity to organization module and org setup stage (#3530)
* module-level support * fast stage 0 * fix inventory, add outputs/tfvars
This commit is contained in:
committed by
GitHub
parent
5270586a8e
commit
87ed19bc47
@@ -32,6 +32,7 @@ To manage organization policies, the `orgpolicy.googleapis.com` service should b
|
||||
- [Custom Security Health Analytics Modules Factory](#custom-security-health-analytics-modules-factory)
|
||||
- [Tags](#tags)
|
||||
- [Tags Factory](#tags-factory)
|
||||
- [Workforce Identity](#workforce-identity)
|
||||
- [Files](#files)
|
||||
- [Variables](#variables)
|
||||
- [Outputs](#outputs)
|
||||
@@ -556,7 +557,6 @@ cloudkmKeyRotationPeriod:
|
||||
|
||||
## Tags
|
||||
|
||||
|
||||
Refer to the [Creating and managing tags](https://cloud.google.com/resource-manager/docs/tags/tags-creating-and-managing) documentation for details on usage.
|
||||
|
||||
```hcl
|
||||
@@ -708,6 +708,77 @@ values:
|
||||
|
||||
```
|
||||
|
||||
## Workforce Identity
|
||||
|
||||
A Workforce Identity pool and providers can be created via the `workforce_identity_config` variable.
|
||||
|
||||
Auto-population of provider attributes is supported via the `attribute_mapping_template` provider attribute. Currently only `azuread` and `okta` are supported.
|
||||
|
||||
```hcl
|
||||
module "org" {
|
||||
source = "./fabric/modules/organization"
|
||||
organization_id = var.organization_id
|
||||
workforce_identity_config = {
|
||||
# optional, defaults to 'default'
|
||||
pool_name = "test-pool"
|
||||
providers = {
|
||||
saml-basic = {
|
||||
attribute_mapping_template = "azuread"
|
||||
identity_provider = {
|
||||
saml = {
|
||||
idp_metadata_xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>..."
|
||||
}
|
||||
}
|
||||
}
|
||||
saml-full = {
|
||||
attribute_mapping = {
|
||||
"google.subject" = "assertion.sub"
|
||||
}
|
||||
identity_provider = {
|
||||
saml = {
|
||||
idp_metadata_xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>..."
|
||||
}
|
||||
}
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
# tftest modules=1 resources=4 inventory=wfif.yaml
|
||||
```
|
||||
|
||||
<!-- TFDOC OPTS files:1 -->
|
||||
<!-- BEGIN TFDOC -->
|
||||
## Files
|
||||
@@ -715,6 +786,7 @@ values:
|
||||
| name | description | resources |
|
||||
|---|---|---|
|
||||
| [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> |
|
||||
| [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> |
|
||||
@@ -759,6 +831,7 @@ values:
|
||||
| [tag_bindings](variables-tags.tf#L82) | Tag bindings for this organization, in key => tag value id format. | <code>map(string)</code> | | <code>{}</code> |
|
||||
| [tags](variables-tags.tf#L89) | 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. | <code title="map(object({ description = optional(string, "Managed by the Terraform organization module.") iam = optional(map(list(string)), {}) iam_bindings = optional(map(object({ members = list(string) role = string condition = optional(object({ expression = string title = string description = optional(string) })) })), {}) iam_bindings_additive = optional(map(object({ member = string role = string condition = optional(object({ expression = string title = string description = optional(string) })) })), {}) id = optional(string) values = optional(map(object({ description = optional(string, "Managed by the Terraform organization module.") iam = optional(map(list(string)), {}) iam_bindings = optional(map(object({ members = list(string) role = string condition = optional(object({ expression = string title = string description = optional(string) })) })), {}) iam_bindings_additive = optional(map(object({ member = string role = string condition = optional(object({ expression = string title = string description = optional(string) })) })), {}) id = optional(string) })), {}) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [tags_config](variables-tags.tf#L154) | Fine-grained control on tag resource and IAM creation. | <code title="object({ force_context_ids = optional(bool, false) ignore_iam = optional(bool, false) })">object({…})</code> | | <code>{}</code> |
|
||||
| [workforce_identity_config](variables.tf#L136) | Workforce Identity Federation pools. | <code title="object({ pool_name = optional(string, "default") providers = optional(map(object({ description = optional(string) display_name = optional(string) attribute_condition = optional(string) attribute_mapping = optional(map(string), {}) attribute_mapping_template = optional(string) disabled = optional(bool, false) identity_provider = object({ oidc = optional(object({ issuer_uri = string client_id = string client_secret = optional(string) jwks_json = optional(string) web_sso_config = optional(object({ response_type = optional(string, "CODE") assertion_claims_behavior = optional(string, "ONLY_ID_TOKEN_CLAIMS") additional_scopes = optional(list(string)) })) })) saml = optional(object({ idp_metadata_xml = string })) }) oauth2_client_config = optional(object({ extended_attributes = optional(object({ issuer_uri = string client_id = string client_secret = string attributes_type = optional(string) query_filter = optional(string) })) extra_attributes = optional(object({ issuer_uri = string client_id = string client_secret = string attributes_type = optional(string) query_filter = optional(string) })) }), {}) })), {}) })">object({…})</code> | | <code>null</code> |
|
||||
|
||||
## Outputs
|
||||
|
||||
@@ -777,4 +850,5 @@ values:
|
||||
| [sink_writer_identities](outputs.tf#L101) | Writer identities created for each sink. | |
|
||||
| [tag_keys](outputs.tf#L109) | Tag key resources. | |
|
||||
| [tag_values](outputs.tf#L118) | Tag value resources. | |
|
||||
| [workforce_identity_provider_names](outputs.tf#L126) | Workforce Identity provider names. | |
|
||||
<!-- END TFDOC -->
|
||||
|
||||
168
modules/organization/identity-providers.tf
Normal file
168
modules/organization/identity-providers.tf
Normal file
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* Copyright 2025 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.
|
||||
*/
|
||||
|
||||
# tfdoc:file:description Workforce Identity Federation provider definitions.
|
||||
|
||||
locals {
|
||||
wfif_attribute_mappings = {
|
||||
azuread = {
|
||||
"google.subject" = "assertion.subject"
|
||||
"google.display_name" = "assertion.attributes.userprincipalname[0]"
|
||||
"google.groups" = "assertion.attributes.groups"
|
||||
"attribute.first_name" = "assertion.attributes.givenname[0]"
|
||||
"attribute.last_name" = "assertion.attributes.surname[0]"
|
||||
"attribute.user_email" = "assertion.attributes.mail[0]"
|
||||
}
|
||||
okta = {
|
||||
"google.subject" = "assertion.subject"
|
||||
"google.display_name" = "assertion.subject"
|
||||
"google.groups" = "assertion.attributes.groups"
|
||||
"attribute.first_name" = "assertion.attributes.firstName[0]"
|
||||
"attribute.last_name" = "assertion.attributes.lastName[0]"
|
||||
"attribute.user_email" = "assertion.attributes.email[0]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_iam_workforce_pool" "default" {
|
||||
count = var.workforce_identity_config == null ? 0 : 1
|
||||
parent = "organizations/${var.organization_id}"
|
||||
location = "global"
|
||||
workforce_pool_id = var.workforce_identity_config.pool_name
|
||||
}
|
||||
|
||||
resource "google_iam_workforce_pool_provider" "default" {
|
||||
for_each = try(var.workforce_identity_config.providers, {})
|
||||
provider_id = each.key
|
||||
attribute_condition = each.value.attribute_condition
|
||||
description = each.value.description
|
||||
disabled = each.value.disabled
|
||||
display_name = each.value.display_name
|
||||
attribute_mapping = merge(
|
||||
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
|
||||
dynamic "saml" {
|
||||
for_each = each.value.identity_provider.saml == null ? [] : [""]
|
||||
content {
|
||||
idp_metadata_xml = each.value.identity_provider.saml.idp_metadata_xml
|
||||
}
|
||||
}
|
||||
dynamic "oidc" {
|
||||
for_each = each.value.identity_provider.oidc == null ? [] : [""]
|
||||
content {
|
||||
issuer_uri = each.value.identity_provider.oidc.issuer_uri
|
||||
client_id = each.value.identity_provider.oidc.client_id
|
||||
jwks_json = each.value.identity_provider.oidc.jwks_json
|
||||
dynamic "client_secret" {
|
||||
for_each = (
|
||||
each.value.identity_provider.oidc.client_secret == null ? [] : [""]
|
||||
)
|
||||
content {
|
||||
value {
|
||||
plain_text = each.value.identity_provider.oidc.client_secret
|
||||
}
|
||||
}
|
||||
}
|
||||
dynamic "web_sso_config" {
|
||||
for_each = (
|
||||
each.value.identity_provider.oidc.web_sso_config == null ? [] : [""]
|
||||
)
|
||||
content {
|
||||
response_type = (
|
||||
each.value.identity_provider.oidc.web_sso_config.response_type
|
||||
)
|
||||
assertion_claims_behavior = (
|
||||
each.value.identity_provider.oidc.web_sso_config.assertion_claims_behavior
|
||||
)
|
||||
additional_scopes = (
|
||||
each.value.identity_provider.oidc.web_sso_config.additional_scopes
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dynamic "extra_attributes_oauth2_client" {
|
||||
for_each = (
|
||||
try(each.value.oauth2_client_config.extra_attributes, null) == null ? [] : [""]
|
||||
)
|
||||
content {
|
||||
issuer_uri = (
|
||||
each.value.oauth2_client_config.extra_attributes.issuer_uri
|
||||
)
|
||||
client_id = (
|
||||
each.value.oauth2_client_config.extra_attributes.client_id
|
||||
)
|
||||
attributes_type = (
|
||||
each.value.oauth2_client_config.extra_attributes.attributes_type
|
||||
)
|
||||
dynamic "client_secret" {
|
||||
for_each = (
|
||||
each.value.oauth2_client_config.extra_attributes.client_secret == null ? [] : [""]
|
||||
)
|
||||
content {
|
||||
value {
|
||||
plain_text = each.value.oauth2_client_config.extra_attributes.client_secret
|
||||
}
|
||||
}
|
||||
}
|
||||
dynamic "query_parameters" {
|
||||
for_each = (
|
||||
each.value.oauth2_client_config.extra_attributes.query_filter == null ? [] : [""]
|
||||
)
|
||||
content {
|
||||
filter = each.value.oauth2_client_config.extra_attributes.query_filter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dynamic "extended_attributes_oauth2_client" {
|
||||
for_each = (
|
||||
try(each.value.oauth2_client_config.extended_attributes, null) == null ? [] : [""]
|
||||
)
|
||||
content {
|
||||
issuer_uri = (
|
||||
each.value.oauth2_client_config.extended_attributes.issuer_uri
|
||||
)
|
||||
client_id = (
|
||||
each.value.oauth2_client_config.extended_attributes.client_id
|
||||
)
|
||||
attributes_type = (
|
||||
each.value.oauth2_client_config.extended_attributes.attributes_type
|
||||
)
|
||||
dynamic "client_secret" {
|
||||
for_each = (
|
||||
each.value.oauth2_client_config.extended_attributes.client_secret == null ? [] : [""]
|
||||
)
|
||||
content {
|
||||
value {
|
||||
plain_text = each.value.oauth2_client_config.extended_attributes.client_secret
|
||||
}
|
||||
}
|
||||
}
|
||||
dynamic "query_parameters" {
|
||||
for_each = (
|
||||
each.value.oauth2_client_config.extended_attributes.query_filter == null ? [] : [""]
|
||||
)
|
||||
content {
|
||||
filter = each.value.oauth2_client_config.extended_attributes.query_filter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,3 +123,9 @@ output "tag_values" {
|
||||
}
|
||||
}
|
||||
|
||||
output "workforce_identity_provider_names" {
|
||||
description = "Workforce Identity provider names."
|
||||
value = {
|
||||
for k, v in google_iam_workforce_pool_provider.default : k => v.name
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,3 +132,115 @@ variable "organization_id" {
|
||||
error_message = "The organization_id must in the form organizations/nnn."
|
||||
}
|
||||
}
|
||||
|
||||
variable "workforce_identity_config" {
|
||||
description = "Workforce Identity Federation pools."
|
||||
type = object({
|
||||
pool_name = optional(string, "default")
|
||||
providers = optional(map(object({
|
||||
description = optional(string)
|
||||
display_name = optional(string)
|
||||
attribute_condition = optional(string)
|
||||
attribute_mapping = optional(map(string), {})
|
||||
attribute_mapping_template = optional(string)
|
||||
disabled = optional(bool, false)
|
||||
identity_provider = object({
|
||||
oidc = optional(object({
|
||||
issuer_uri = string
|
||||
client_id = string
|
||||
client_secret = optional(string)
|
||||
jwks_json = optional(string)
|
||||
web_sso_config = optional(object({
|
||||
# TODO: validation
|
||||
response_type = optional(string, "CODE")
|
||||
assertion_claims_behavior = optional(string, "ONLY_ID_TOKEN_CLAIMS")
|
||||
additional_scopes = optional(list(string))
|
||||
}))
|
||||
}))
|
||||
saml = optional(object({
|
||||
idp_metadata_xml = string
|
||||
}))
|
||||
})
|
||||
oauth2_client_config = optional(object({
|
||||
extended_attributes = optional(object({
|
||||
issuer_uri = string
|
||||
client_id = string
|
||||
client_secret = string
|
||||
attributes_type = optional(string)
|
||||
query_filter = optional(string)
|
||||
}))
|
||||
extra_attributes = optional(object({
|
||||
issuer_uri = string
|
||||
client_id = string
|
||||
client_secret = string
|
||||
attributes_type = optional(string)
|
||||
query_filter = optional(string)
|
||||
}))
|
||||
}), {})
|
||||
})), {})
|
||||
})
|
||||
nullable = true
|
||||
default = null
|
||||
validation {
|
||||
condition = alltrue([
|
||||
for v in try(var.workforce_identity_config.providers, {}) : contains(
|
||||
["azuread", "okta"],
|
||||
coalesce(v.attribute_mapping_template, "azuread")
|
||||
)
|
||||
])
|
||||
error_message = "Supported mapping templates are: azuread, okta."
|
||||
}
|
||||
validation {
|
||||
condition = alltrue([
|
||||
for v in try(var.workforce_identity_config.providers, {}) : (
|
||||
(try(v.identity_provider.oidc, null) == null ? 0 : 1) +
|
||||
(try(v.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_config.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."
|
||||
}
|
||||
validation {
|
||||
condition = alltrue([
|
||||
for v in try(var.workforce_identity_config.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."
|
||||
}
|
||||
validation {
|
||||
condition = alltrue([
|
||||
for v in try(var.workforce_identity_config.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.."
|
||||
}
|
||||
validation {
|
||||
condition = alltrue([
|
||||
for v in try(var.workforce_identity_config.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.."
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user