docs(organization): document external IAM management for logging sinks at scale (#3746)

* docs(organization): document external IAM management for logging sinks at scale

* Update TOC

---------

Co-authored-by: Julio Castillo <jccb@google.com>
This commit is contained in:
Martin Bergo
2026-02-18 16:08:23 +01:00
committed by GitHub
parent f474173d45
commit 60ec6db9cd
2 changed files with 110 additions and 0 deletions

View File

@@ -26,6 +26,7 @@ To manage organization policies, the `orgpolicy.googleapis.com` service should b
- [Privileged Access Manager (PAM) Entitlements Factory](#privileged-access-manager-pam-entitlements-factory)
- [Hierarchical Firewall Policy Attachments](#hierarchical-firewall-policy-attachments)
- [Log Sinks](#log-sinks)
- [Externally Managing IAM for Log Sinks](#externally-managing-iam-for-log-sinks)
- [Data Access Logs](#data-access-logs)
- [Custom Roles](#custom-roles)
- [Custom Roles Factory](#custom-roles-factory)
@@ -447,6 +448,68 @@ module "org" {
# tftest inventory=logging.yaml
```
### Externally Managing IAM for Log Sinks
By default the module creates one conditional IAM binding per sink for `roles/logging.bucketWriter` on the destination project. GCP enforces a hard limit of [20 conditional bindings per role and principal](https://cloud.google.com/iam/docs/conditions-overview#limitations) on a single resource. If you route many sinks to the same destination project, you will hit this limit.
Set `iam = false` on the affected sinks and manage the IAM binding externally, consolidating multiple destinations into fewer bindings using an OR'd CEL condition expression (max 12 logical operators per condition).
```hcl
module "log-bucket-0" {
source = "./fabric/modules/logging-bucket"
parent = var.project_id
name = "audit-0"
}
module "log-bucket-1" {
source = "./fabric/modules/logging-bucket"
parent = var.project_id
name = "audit-1"
}
module "org" {
source = "./fabric/modules/organization"
organization_id = var.organization_id
logging_sinks = {
audit-0 = {
destination = module.log-bucket-0.id
filter = "severity=NOTICE"
type = "logging"
iam = false
}
audit-1 = {
destination = module.log-bucket-1.id
filter = "severity=WARNING"
type = "logging"
iam = false
}
}
}
resource "google_project_iam_member" "log-bucket-writer" {
project = var.project_id
role = "roles/logging.bucketWriter"
member = module.org.sink_writer_identities["audit-0"]
condition {
title = "log_bucket_writer"
description = "Grants bucketWriter for audit-0, audit-1."
expression = join(" || ", [
"resource.name.endsWith('${module.log-bucket-0.id}')",
"resource.name.endsWith('${module.log-bucket-1.id}')",
# add up to 11 more
])
}
lifecycle {
create_before_destroy = true
}
}
# tftest inventory=logging-iam-external.yaml
```
When you exceed 13 sinks per binding, use Terraform's `chunklist()` with `for_each` to generate multiple `google_project_iam_member` resources automatically.
For production-scale deployments or strict per-sink isolation, consider using [user-managed service accounts for log routing](https://cloud.google.com/logging/docs/routing/user-managed-service-accounts) instead of the default shared writer identity. This removes the conditional binding limit entirely and provides per-sink auditability.
## Data Access Logs
Activation of data access logs can be controlled via the `logging_data_access` variable.

View File

@@ -0,0 +1,47 @@
# 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.
values:
module.log-bucket-0.google_logging_project_bucket_config.bucket[0]:
bucket_id: audit-0
location: global
project: project-id
retention_days: 30
module.log-bucket-1.google_logging_project_bucket_config.bucket[0]:
bucket_id: audit-1
location: global
project: project-id
retention_days: 30
module.org.google_logging_organization_sink.sink["audit-0"]:
filter: severity=NOTICE
include_children: true
name: audit-0
org_id: '1122334455'
module.org.google_logging_organization_sink.sink["audit-1"]:
filter: severity=WARNING
include_children: true
name: audit-1
org_id: '1122334455'
google_project_iam_member.log-bucket-writer:
project: project-id
role: roles/logging.bucketWriter
condition:
- title: log_bucket_writer
counts:
google_logging_organization_sink: 2
google_logging_project_bucket_config: 2
google_project_iam_member: 1
modules: 3
resources: 5