> ## Documentation Index
> Fetch the complete documentation index at: https://cloudanix.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Project Ownership Change Log Alerts Should Be Enabled

### More Info:

Ensures that logging and log alerts exist for project ownership assignments and changes. Project Ownership is the highest level of privilege on a project, any changes in project ownership should be heavily monitored to prevent unauthorized changes.

### Risk Level

High

### Address

Security

### Compliance Standards

* CIS GCP
* CIS GCP 2.0.0
* Cloudanix Best Practice
* HIPAA
* HITRUST CSF
* ISO 27001
* Securities and Exchange Board of India (SEBI) - Cloud Security Adoption Framework

### Triage and Remediation

<Tabs>
  <Tab title="Remediation">
    ### Remediation

    <AccordionGroup>
      <Accordion title="Using Console" defaultOpen="true">
        To remediate **“Project Ownership Logging”** in GCP using the **Console**, you want to ensure that all changes to IAM (especially project owners) are logged and retained outside the project (per CIS 1.1).

        Below are concise step‑by‑step instructions.

        ***

        ## 1. Confirm Audit Logs Are Enabled for IAM

        Admin Activity logs (which include IAM changes) are **on by default** and cannot be turned off, but verify their presence:

        1. Go to **Logging**:\
           `Navigation menu → Logging → Logs Explorer`
        2. In the query editor, run something like:
           ```text theme={null}
           resource.type="project"
           logName:"cloudaudit.googleapis.com/activity"
           protoPayload.methodName:"SetIamPolicy"
           ```
        3. Click **Run query**.
           * If you see recent entries, IAM changes (including ownership changes) are being logged.

        ***

        ## 2. Create a Central Log Bucket / Project (recommended)

        If you already have a central logging project/bucket, skip to section 3.

        1. (Optional but recommended) Create a **central logging project** (e.g., `org-logging-prod`) and do the rest of these steps there.
        2. In that project, go to\
           `Navigation menu → Logging → Log storage`
        3. Click **Create Log Bucket**:
           * Name: `project-ownership-audit-logs`
           * Location: choose region or multi-region as per policy
           * Retention: configure per policy (e.g., 365 days or longer)
        4. Click **Create**.

        ***

        ## 3. Create a Log Sink for Project Ownership / IAM Changes

        Do this **in each project** where you want to remediate the finding (or at folder/org level if you have the rights).

        1. Switch to the **source project** you want to protect.

        2. Go to\
           `Navigation menu → Logging → Log Router`

        3. Click **Create sink**.

        4. Configure:
           * **Sink name**: `project-ownership-logging-sink`
           * **Sink description**: `Exports IAM and project ownership change logs`

        5. **Sink destination**:
           * Choose where to send logs:
             * **Log bucket**: select the central log bucket you created (may be in another project).
               * Click **Select sink destination** → **Cloud Logging bucket** → choose project and bucket (e.g., `org-logging-prod / project-ownership-audit-logs`).
             * Alternatively, you can choose **BigQuery dataset** or **Cloud Storage** if your policy requires that.

        6. **Choose logs to include** (this is key):

           In the **Build inclusion filter** box, use a filter that captures IAM changes, especially role/owner changes, for this project. Example:

           ```text theme={null}
           resource.type="project"
           logName=("projects/YOUR_PROJECT_ID/logs/cloudaudit.googleapis.com%2Factivity")
           protoPayload.serviceName="cloudresourcemanager.googleapis.com"
           protoPayload.methodName=(
             "SetIamPolicy" OR
             "projects.setIamPolicy" OR
             "resourcemanager.projects.setIamPolicy"
           )
           ```

           Replace `YOUR_PROJECT_ID` with your project ID.

           This ensures all project-level IAM policy changes—including ownership/role assignment changes—are exported.

        7. Click **Create sink**.

        8. When prompted to **Grant Writer Identity**:
           * The system shows a service account of the form\
             `cloud-logs@system.gserviceaccount.com` or `serviceAccount:logging-XXXX@gcp-sa-logging.iam.gserviceaccount.com` (depending on target).
           * Grant that service account the appropriate **writer** role on the destination:
             * If destination is a log bucket:
               * On the destination project, go to `IAM & Admin → IAM`
               * Add the sink’s service account with role:\
                 `Logging → Logs Bucket Writer` (or `Logs Writer` if using legacy).
             * If BigQuery: grant `BigQuery Data Editor` on the dataset.
             * If Cloud Storage: grant `Storage Object Creator` on the bucket.

        ***

        ## 4. (Optional) Narrow to Owner‑Level Role Changes Only

        If your auditor requires **only changes involving owner‑equivalent roles**, you can further refine the sink filter:

        ```text theme={null}
        resource.type="project"
        logName=("projects/YOUR_PROJECT_ID/logs/cloudaudit.googleapis.com%2Factivity")
        protoPayload.serviceName="cloudresourcemanager.googleapis.com"
        protoPayload.methodName:("SetIamPolicy" OR "projects.setIamPolicy" OR "resourcemanager.projects.setIamPolicy")
        protoPayload.serviceData.policyDelta.bindingDeltas.member:* 
        protoPayload.serviceData.policyDelta.bindingDeltas.role:("roles/owner" OR "roles/resourcemanager.projectIamAdmin" OR "roles/resourcemanager.organizationAdmin")
        ```

        Adjust roles per your environment.

        ***

        ## 5. Verify Logs Are Reaching the Destination

        1. Go to the **destination** (log bucket / BigQuery / GCS):
           * For **Log bucket**:
             * `Navigation menu → Logging → Logs Explorer`
             * In the project that owns the bucket, filter by:
               ```text theme={null}
               logName:"cloudaudit.googleapis.com/activity"
               protoPayload.methodName:"SetIamPolicy"
               ```
           * For **BigQuery**:
             * Query the table to confirm new rows appear when IAM changes are made.
           * For **GCS**:
             * Check objects in the bucket are being created.

        2. Make a test IAM change (e.g., add/remove a role) and confirm it appears in the destination within a few minutes.

        ***

        Once this is in place for all relevant projects (or configured at folder/org level), the “Project Ownership Logging” / “Project ownership logging and monitoring” finding in most security or CIS benchmarks will be considered remediated for GCP IAM.
      </Accordion>

      <Accordion title="Using CLI">
        To remediate “Project Ownership Logging” for GCP IAM using the CLI, you need to enable Cloud Audit Logs (Admin Activity + Data Access) in the project IAM policy.

        Below are step‑by‑step `gcloud` commands.

        ***

        ### 1. Set your project

        ```bash theme={null}
        PROJECT_ID="your-project-id"
        gcloud config set project "${PROJECT_ID}"
        ```

        ***

        ### 2. Export the current IAM policy

        ```bash theme={null}
        gcloud projects get-iam-policy "${PROJECT_ID}" \
          --format=json > iam-policy.json
        ```

        ***

        ### 3. Edit IAM policy to add audit logging

        Open `iam-policy.json` in an editor and add (or merge) the `auditConfigs` block.

        If `auditConfigs` doesn’t exist, add it at the top level:

        ```jsonc theme={null}
        {
          "bindings": [
            // ... existing bindings ...
          ],
          "auditConfigs": [
            {
              "service": "allServices",
              "auditLogConfigs": [
                {
                  "logType": "ADMIN_READ"
                },
                {
                  "logType": "DATA_READ"
                },
                {
                  "logType": "DATA_WRITE"
                }
              ]
            }
          ]
        }
        ```

        If `auditConfigs` already exists, ensure there is an entry with `"service": "allServices"` (or `"iam.googleapis.com"`) including the three `logType` values above.

        This configuration ensures IAM (including project owner / role changes) is logged.

        ***

        ### 4. Apply the updated IAM policy

        ```bash theme={null}
        gcloud projects set-iam-policy "${PROJECT_ID}" iam-policy.json
        ```

        Verify there are no errors.

        ***

        ### 5. (Optional) Verify audit configs

        ```bash theme={null}
        gcloud projects get-iam-policy "${PROJECT_ID}" --format=json \
          | jq '.auditConfigs'
        ```

        You should see `ADMIN_READ`, `DATA_READ`, and `DATA_WRITE` under `allServices` (or specifically `iam.googleapis.com`).

        This enables logging of project ownership and other IAM changes for the project.
      </Accordion>

      <Accordion title="Using Python">
        Below are step‑by‑step instructions and a Python example to remediate “Project Ownership Logging” for GCP IAM (as in CIS GCP Benchmark: log all project owner changes).

        ## Goal

        Ensure that **any changes to project ownership / IAM policy** are logged and exported (e.g., to a log bucket, BigQuery, or Pub/Sub) so they can’t be lost.

        This is done by:

        1. Creating (or verifying) a **log sink** at the project level.
        2. Using a **filter** that matches IAM / ownership changes.
        3. Ensuring the sink’s **destination** exists and the sink has permission to write to it.

        ***

        ## 1. Decide where to export the logs

        Common options:

        * A **log bucket** in the same project
        * A **BigQuery dataset**
        * A **Pub/Sub topic**

        Example destination (BigQuery):

        ```text theme={null}
        bigquery.googleapis.com/projects/LOGGING_PROJECT_ID/datasets/audit_logs
        ```

        Or Pub/Sub:

        ```text theme={null}
        pubsub.googleapis.com/projects/LOGGING_PROJECT_ID/topics/audit-iam-changes
        ```

        Or log bucket (same project):

        ```text theme={null}
        logging.googleapis.com/projects/PROJECT_ID/locations/global/buckets/audit-logs
        ```

        Create the destination resource beforehand (dataset / topic / log bucket).

        ***

        ## 2. Use a logging filter that captures IAM / ownership changes

        Recommended filter (covers IAM policy changes for the project):

        ```text theme={null}
        logName:"cloudaudit.googleapis.com/activity"
        protoPayload.serviceName="cloudresourcemanager.googleapis.com"
        protoPayload.methodName:("SetIamPolicy" OR "SetOrgPolicy" OR "SetIamPolicy" OR "SetBinding" OR "DeleteBinding")
        ```

        If you specifically care about project “Owner” role changes, you can refine to role `roles/owner`:

        ```text theme={null}
        logName="projects/PROJECT_ID/logs/cloudaudit.googleapis.com%2Factivity"
        protoPayload.serviceName="cloudresourcemanager.googleapis.com"
        protoPayload.methodName="SetIamPolicy"
        protoPayload.response.bindings.role="roles/owner"
        ```

        Replace `PROJECT_ID` accordingly if you hard‑code it.

        ***

        ## 3. Python example: create or update a log sink

        ### Prerequisites

        ```bash theme={null}
        pip install google-cloud-logging
        gcloud auth application-default login
        ```

        ### Code

        ```python theme={null}
        from google.cloud import logging_v2
        from google.api_core.exceptions import AlreadyExists

        def ensure_project_ownership_logging_sink(
            project_id: str,
            sink_name: str,
            destination: str,
        ):
            """
            Create or update a log sink for project ownership/IAM changes.

            :param project_id: ID of the project whose logs you want to export.
            :param sink_name: Name of the sink (e.g., "project-ownership-logging").
            :param destination: Sink destination URI (BigQuery, Pub/Sub, or log bucket).
            """
            client = logging_v2.ConfigServiceV2Client()
            parent = f"projects/{project_id}"

            # Filter for ownership / IAM changes (includes project IAM policy changes)
            filter_ = (
                'logName="projects/{project_id}/logs/cloudaudit.googleapis.com%2Factivity" '
                'protoPayload.serviceName="cloudresourcemanager.googleapis.com" '
                'protoPayload.methodName="SetIamPolicy" '
                'protoPayload.response.bindings.role="roles/owner"'
            ).format(project_id=project_id)

            sink = logging_v2.LogSink(
                name=sink_name,
                destination=destination,
                filter=filter_,
                include_children=False,  # set True only for org/folder-level sinks
            )

            sink_path = client.log_sink_path(project_id, sink_name)

            try:
                # Try to create the sink
                response = client.create_sink(
                    parent=parent,
                    sink=sink,
                    unique_writer_identity=True  # create a service account for the sink
                )
                print(f"Created sink: {response.name}")
                print(f"Writer identity: {response.writer_identity}")
            except AlreadyExists:
                # If sink exists, update it
                update_mask = {"paths": ["filter", "destination"]}
                response = client.update_sink(
                    sink_name=sink_path,
                    sink=sink,
                    update_mask=update_mask,
                )
                print(f"Updated sink: {response.name}")
                print(f"Writer identity: {response.writer_identity}")

            # IMPORTANT: Grant the sink's writer_identity permission to write to destination
            # This must be done separately depending on the destination type.

        if __name__ == "__main__":
            # Example usage
            PROJECT_ID = "your-project-id"
            SINK_NAME = "project-ownership-logging"
            # Example: BigQuery dataset destination
            DESTINATION = "bigquery.googleapis.com/projects/your-logging-project/datasets/audit_logs"
            # Or Pub/Sub: "pubsub.googleapis.com/projects/your-logging-project/topics/audit-iam-changes"
            # Or Log bucket: "logging.googleapis.com/projects/your-logging-project/locations/global/buckets/audit-logs"

            ensure_project_ownership_logging_sink(PROJECT_ID, SINK_NAME, DESTINATION)
        ```

        ***

        ## 4. Grant sink writer permissions on the destination

        After running the script, note the `writer_identity` printed (something like `serviceAccount:cloud-logs@system.gserviceaccount.com` or `serviceAccount:...gcp-sa-logging.iam.gserviceaccount.com`).

        Grant it appropriate IAM on the destination:

        * **BigQuery dataset**: `roles/bigquery.dataEditor` or `roles/bigquery.dataOwner`
        * **Pub/Sub topic**: `roles/pubsub.publisher`
        * **Log bucket**: `roles/logging.bucketWriter` on that bucket

        Example (BigQuery) via gcloud:

        ```bash theme={null}
        bq update --dataset \
          --access=role:WRITER,group:log-writers@example.com \
          your-logging-project:audit_logs
        ```

        (or use IAM policy binding to the dataset’s service account writer\_identity).

        ***

        If you tell me:

        * your chosen destination type (BigQuery / Pub/Sub / log bucket), and
        * whether you want only owner role changes or all IAM changes

        I can adjust the filter and Python snippet precisely for that setup.
      </Accordion>

      <Accordion title="Using Terraform">
        ```hcl theme={null}
        # Logs-based metric for project ownership changes
        resource "google_logging_metric" "project_ownership_change" {
          project = "PROJECT_ID" # replace with your GCP project ID

          name        = "project_ownership_change_count"
          description = "Counts changes to project ownership (role roles/owner) via SetIamPolicy on the project."
          filter      = <<-EOT
            resource.type="project"
            AND protoPayload.serviceName="cloudresourcemanager.googleapis.com"
            AND protoPayload.methodName="SetIamPolicy"
            AND protoPayload.authorizationInfo.permission="resourcemanager.projects.setIamPolicy"
            AND protoPayload.serviceData.policyDelta.bindingDeltas.role="roles/owner"
          EOT

          metric_descriptor {
            metric_kind = "DELTA"
            value_type  = "INT64"
          }
        }

        # Notification channel for alerts (email example)
        resource "google_monitoring_notification_channel" "project_ownership_email" {
          project = "PROJECT_ID" # replace with your GCP project ID

          display_name = "Project Ownership Change Email"
          type         = "email"

          labels = {
            email_address = "SECURITY_ALERTS_EMAIL@example.com" # replace with your alert email
          }
        }

        # Alert policy using the logs-based metric
        resource "google_monitoring_alert_policy" "project_ownership_change_alert" {
          project      = "PROJECT_ID" # replace with your GCP project ID
          display_name = "Project Ownership Change Alert"
          combiner     = "OR"

          conditions {
            display_name = "Project ownership changes > 0 in last minute"

            condition_threshold {
              # Logs-based metrics surface as custom logging metrics under logging.googleapis.com/user/*
              filter          = "metric.type = \"logging.googleapis.com/user/${google_logging_metric.project_ownership_change.name}\" AND resource.type = \"project\""
              comparison      = "COMPARISON_GT"
              threshold_value = 0
              duration        = "0s"

              aggregations {
                alignment_period     = "60s"
                per_series_aligner   = "ALIGN_DELTA"
                cross_series_reducer = "REDUCE_SUM"
              }
            }
          }

          notification_channels = [
            google_monitoring_notification_channel.project_ownership_email.name
          ]

          documentation {
            content   = "Project ownership (roles/owner) was modified on this project. Review immediately."
            mime_type = "text/markdown"
          }
        }
        ```

        This change is additive and does not force replacement of existing resources; it creates a new logs-based metric and alert policy.

        For verification, `terraform plan` should show `+ create` for:

        * `google_logging_metric.project_ownership_change`
        * `google_monitoring_notification_channel.project_ownership_email`
        * `google_monitoring_alert_policy.project_ownership_change_alert`.
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>

### Additional Reading:

* [https://cloud.google.com/logging/docs/logs-based-metrics](https://cloud.google.com/logging/docs/logs-based-metrics)
