Skip to content

[bug] folder log sinks fail with log_bucket_not_found due to Project ID vs Project Number mismatch in IAM CEL condition #4030

Description

@emailabhigarg

Affected components

  • Fabric modules
  • FAST
  • Other

The problem

When creating folder-level logging sinks targeting a log bucket in a different project using the CFF folder module, log routing fails with the error log_bucket_not_found.

This is caused by a latent mismatch in the automatic project-level IAM binding (google_project_iam_member.bucket_sinks_binding) generated by the module:

The module generates a CEL condition that matches the target resource name against the Project ID (string).
However, Google Cloud's IAM CEL condition evaluator evaluates resource.name using the numeric Project Number.
As a result, the condition evaluates to false, the permission is denied, and the sink is unable to write logs to the target bucket.

Release version

v56.1.0

Terraform version

Terraform v1.14.8

Steps to reproduce

Instaniate CFF folder module with a folder log sink pointing to a log bucket:
hcl

module "folder" {
  source        = "git::https://github.com/GoogleCloudPlatform/cloud-foundation-fabric//modules/folder"
  id            = "folders/123456789"
  folder_create = false
  logging_sinks = {
    my-sink = {
      destination = "logging.googleapis.com/projects/my-observe-project/locations/us-central1/buckets/my-log-bucket"
      type        = "logging"
    }
  }
}

Apply the HCL.
Check the folder's logging sinks in the GCP Console or run gcloud logging read "resource.type="logging_sink" AND severity=ERROR" --folder=123456789. You will find the routing error log_bucket_not_found.

Expected behavior

Folder's gcp-sa-logging agent should be able to write logs to log sink logging bucket and shouldnt get IAM issue.

Actual behavior

In modules/folder/logging.tf, the project-level IAM binding for logging bucket sinks is defined as:

resource "google_project_iam_member" "bucket_sinks_binding" {
  for_each = local.sink_bindings["logging"]
  project  = split("/", each.value.destination)[1]
  role     = "roles/logging.bucketWriter"
  member   = google_logging_folder_sink.sink[each.key].writer_identity
  condition {
    title       = "${each.key} bucket writer"
    description = "Grants bucketWriter to serviceAccount:${google_logging_folder_sink.sink[each.key].writer_identity} used by log sink ${each.key} on folders/${local.folder_id}"
    expression  = "resource.name.endsWith('projects/${split("/", each.value.destination)[1]}/locations/${split("/", each.value.destination)[3]}/buckets/${split("/", each.value.destination)[5]}')"
  }
}

The Bug:
The expression uses split("/", each.value.destination)[1], which extracts the Project ID string (e.g., hhs-observe-p-mxty) because the destination attribute is passed in from Terraform resource IDs.

This generates a CEL condition: resource.name.endsWith('projects/PROJECT_ID/locations/LOCATION/buckets/BUCKET_ID')

During live log routing, GCP IAM evaluates this condition. However, GCP's IAM policy engine populates resource.name using the numeric Project Number (e.g., 650484525631) instead of the Project ID.

Because projects/650484525631/... does not end with projects/PROJECT_ID/..., the condition evaluates to false, causing log routing to fail silently or log a log_bucket_not_found configuration error.

Additional context

Proposed Fix
The CEL expression should be updated to be project-number-independent or robust enough to support both formats.

Since the target bucket's location and bucket ID are the only parts needed to uniquely identify the destination bucket on the project, the expression can be simplified using endsWith to match only the trailing path:

hcl

expression = "resource.name.endsWith('locations/${split("/", each.value.destination)[3]}/buckets/${split("/", each.value.destination)[5]}')"
Alternatively, the module can resolve the project number from the project resource (if available in context) or allow it to be passed in, but matching on the trailing location and bucket ID path is the cleanest and most robust project-number-independent solution.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions