GCP Cloud Run Set IAM Policy


Description

Detects new roles granted to users to Cloud Run Services. This could potentially allow the user to perform actions within the project and its resources, which could pose a security risk.

Query · python

from panther_gcp_helpers import gcp_alert_context


def rule(event):
    if event.get("severity") == "ERROR":
        return False

    method_name = event.deep_get("protoPayload", "methodName", default="")
    if not method_name.endswith("Services.SetIamPolicy"):
        return False

    authorization_info = event.deep_walk("protoPayload", "authorizationInfo")
    if not authorization_info:
        return False

    for auth in authorization_info:
        if auth.get("permission") == "run.services.setIamPolicy" and auth.get("granted") is True:
            return True
    return False


def title(event):
    actor = event.deep_get(
        "protoPayload", "authenticationInfo", "principalEmail", default="<ACTOR_NOT_FOUND>"
    )
    resource = event.deep_get("protoPayload", "resourceName", default="<RESOURCE_NOT_FOUND>")
    service_name = resource.split("/")[-1] if "/" in resource else resource

    # Extract roles from the response bindings - there could be multiple
    bindings = event.deep_get("protoPayload", "response", "bindings", default=[])

    # Handle multiple roles if present
    roles = []
    for binding in bindings:
        if binding.get("role"):
            roles.append(binding.get("role"))

    # Format roles for title
    if not roles:
        roles_str = "<NO_ROLES_FOUND>"
    elif len(roles) == 1:
        roles_str = roles[0]
    else:
        # If multiple roles, mention the count and list the first one
        roles_str = f"{len(roles)} roles including {roles[0]}"

    project_id = event.deep_get("resource", "labels", "project_id", default="<PROJECT_NOT_FOUND>")

    return (
        f"[GCP]: [{actor}] modified IAM policy for Cloud Run service [{service_name}] "
        f"with [{roles_str}] in project [{project_id}]"
    )


def alert_context(event):
    context = gcp_alert_context(event)

    # Extract the service name from the resource path for better context
    resource = event.deep_get("protoPayload", "resourceName", default="")
    if resource:
        context["service_name"] = resource.split("/")[-1] if "/" in resource else resource

    # Get bindings and role information
    bindings = event.deep_get("protoPayload", "response", "bindings", default=[])

    # Collect all roles and members
    all_roles = []
    all_members = []
    role_to_members = {}

    for binding in bindings:
        role = binding.get("role")
        members = binding.get("members", [])

        if role:
            all_roles.append(role)

        if members:
            all_members.extend(members)

            # Create mapping of role to members
            if role:
                role_to_members[role] = members

    # Store all collected information in the context
    context["assigned_roles"] = all_roles
    context["members_granted"] = all_members
    context["role_to_members_mapping"] = role_to_members

    return context

Analyst notes

Confirm this was authorized and necessary behavior

Raw source GCP Cloud Run Set IAM Policy · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
AnalysisType: rule
LogTypes:
  - GCP.AuditLog
Description:
  Detects new roles granted to users to Cloud Run Services. This could potentially allow the user to perform
  actions within the project and its resources, which could pose a security risk.
DisplayName: "GCP Cloud Run Set IAM Policy"
RuleID: "GCP.Cloud.Run.Set.IAM.Policy"
Enabled: true
Filename: gcp_cloud_run_set_iam_policy.py
Reference: https://cloud.google.com/run/docs/securing/managing-access
Runbook: Confirm this was authorized and necessary behavior
Severity: High
DedupPeriodMinutes: 60
Threshold: 1
Tests:
  - Name: GCP Run IAM Policy Set
    ExpectedResult: true
    Log:
      {
        "insertId": "l3jvzyd2s2s",
        "logName": "projects/some-project/logs/cloudaudit.googleapis.com%2Factivity",
        "protoPayload":
          {
            "at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog",
            "authenticationInfo":
              {
                "principalEmail": "some.user@company.com",
                "principalSubject": "user:some.user@company.com",
              },
            "authorizationInfo":
              [
                {
                  "granted": true,
                  "permission": "run.services.setIamPolicy",
                  "resource": "projects/some-project/locations/us-west1/services/cloudrun-exfil",
                  "resourceAttributes": {},
                },
                {
                  "granted": true,
                  "permission": "run.services.setIamPolicy",
                  "resourceAttributes": {},
                },
              ],
            "methodName": "google.cloud.run.v1.Services.SetIamPolicy",
            "request":
              {
                "@type": "type.googleapis.com/google.iam.v1.SetIamPolicyRequest",
                "policy":
                  {
                    "bindings":
                      [
                        {
                          "members": ["user:some.user@company.com"],
                          "role": "roles/run.invoker",
                        },
                      ],
                  },
                "resource": "projects/some-project/locations/us-west1/services/cloudrun-exfil",
              },
            "requestMetadata":
              {
                "callerIP": "1.2.3.4",
                "callerSuppliedUserAgent": "(gzip),gzip(gfe)",
                "destinationAttributes": {},
                "requestAttributes":
                  { "auth": {}, "time": "2024-02-02T09:44:26.173186Z" },
              },
            "resourceLocation": { "currentLocations": ["us-west1"] },
            "resourceName": "projects/some-project/locations/us-west1/services/cloudrun-exfil",
            "response":
              {
                "@type": "type.googleapis.com/google.iam.v1.Policy",
                "bindings":
                  [
                    {
                      "members": ["user:some.user@company.com"],
                      "role": "roles/run.invoker",
                    },
                  ],
                "etag": "BwYQYvUoBxs=",
              },
            "serviceName": "run.googleapis.com",
          },
        "receiveTimestamp": "2024-02-02 09:44:26.653891982",
        "resource":
          {
            "labels":
              {
                "configuration_name": "",
                "location": "us-west1",
                "project_id": "some-project",
                "revision_name": "",
                "service_name": "",
              },
            "type": "cloud_run_revision",
          },
        "severity": "NOTICE",
        "timestamp": "2024-02-02 09:44:26.029835000",
      }
  - Name: GCP Run IAM Policy Not Set
    ExpectedResult: false
    Log:
      {
        "insertId": "l3jvzyd2s2s",
        "logName": "projects/some-project/logs/cloudaudit.googleapis.com%2Factivity",
        "protoPayload":
          {
            "at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog",
            "authenticationInfo":
              {
                "principalEmail": "some.user@company.com",
                "principalSubject": "user:some.user@company.com",
              },
            "authorizationInfo":
              [
                {
                  "granted": false,
                  "permission": "run.services.setIamPolicy",
                  "resource": "projects/some-project/locations/us-west1/services/cloudrun-exfil",
                  "resourceAttributes": {},
                },
                {
                  "granted": false,
                  "permission": "run.services.setIamPolicy",
                  "resourceAttributes": {},
                },
              ],
            "methodName": "google.cloud.run.v1.Services.SetIamPolicy",
            "request":
              {
                "@type": "type.googleapis.com/google.iam.v1.SetIamPolicyRequest",
                "policy":
                  {
                    "bindings":
                      [
                        {
                          "members": ["user:some.user@company.com"],
                          "role": "roles/run.invoker",
                        },
                      ],
                  },
                "resource": "projects/some-project/locations/us-west1/services/cloudrun-exfil",
              },
            "requestMetadata": ...,
            "resourceLocation": ...,
            "resourceName": "projects/some-project/locations/us-west1/services/cloudrun-exfil",
            "serviceName": "run.googleapis.com",
          },
        "receiveTimestamp": "2024-02-02 09:44:26.653891982",
        "resource": ...,
        "severity": "NOTICE",
        "timestamp": "2024-02-02 09:44:26.029835000",
      }
  - Name: No method provided
    ExpectedResult: false
    Log:
      {
        "insertId": "l3jvzyd2s2s",
        "logName": "projects/some-project/logs/cloudaudit.googleapis.com%2Factivity",
        "protoPayload":
          {
            "at_sign_type": "type.googleapis.com/google.cloud.audit.AuditLog",
            "authenticationInfo":
              {
                "principalEmail": "some.user@company.com",
                "principalSubject": "user:some.user@company.com",
              },
            "authorizationInfo":
              [
                {
                  "granted": false,
                  "permission": "run.services.setIamPolicy",
                  "resource": "projects/some-project/locations/us-west1/services/cloudrun-exfil",
                  "resourceAttributes": {},
                },
                {
                  "granted": false,
                  "permission": "run.services.setIamPolicy",
                  "resourceAttributes": {},
                },
              ],
            "request":
              {
                "@type": "type.googleapis.com/google.iam.v1.SetIamPolicyRequest",
                "policy":
                  {
                    "bindings":
                      [
                        {
                          "members": ["user:some.user@company.com"],
                          "role": "roles/run.invoker",
                        },
                      ],
                  },
                "resource": "projects/some-project/locations/us-west1/services/cloudrun-exfil",
              },
            "requestMetadata": ...,
            "resourceLocation": ...,
            "resourceName": "projects/some-project/locations/us-west1/services/cloudrun-exfil",
            "serviceName": "run.googleapis.com",
          },
        "receiveTimestamp": "2024-02-02 09:44:26.653891982",
        "resource": ...,
        "severity": "NOTICE",
        "timestamp": "2024-02-02 09:44:26.029835000",
      }


# ------ paired body: gcp_cloud_run_set_iam_policy.py ------

from panther_gcp_helpers import gcp_alert_context


def rule(event):
    if event.get("severity") == "ERROR":
        return False

    method_name = event.deep_get("protoPayload", "methodName", default="")
    if not method_name.endswith("Services.SetIamPolicy"):
        return False

    authorization_info = event.deep_walk("protoPayload", "authorizationInfo")
    if not authorization_info:
        return False

    for auth in authorization_info:
        if auth.get("permission") == "run.services.setIamPolicy" and auth.get("granted") is True:
            return True
    return False


def title(event):
    actor = event.deep_get(
        "protoPayload", "authenticationInfo", "principalEmail", default="<ACTOR_NOT_FOUND>"
    )
    resource = event.deep_get("protoPayload", "resourceName", default="<RESOURCE_NOT_FOUND>")
    service_name = resource.split("/")[-1] if "/" in resource else resource

    # Extract roles from the response bindings - there could be multiple
    bindings = event.deep_get("protoPayload", "response", "bindings", default=[])

    # Handle multiple roles if present
    roles = []
    for binding in bindings:
        if binding.get("role"):
            roles.append(binding.get("role"))

    # Format roles for title
    if not roles:
        roles_str = "<NO_ROLES_FOUND>"
    elif len(roles) == 1:
        roles_str = roles[0]
    else:
        # If multiple roles, mention the count and list the first one
        roles_str = f"{len(roles)} roles including {roles[0]}"

    project_id = event.deep_get("resource", "labels", "project_id", default="<PROJECT_NOT_FOUND>")

    return (
        f"[GCP]: [{actor}] modified IAM policy for Cloud Run service [{service_name}] "
        f"with [{roles_str}] in project [{project_id}]"
    )


def alert_context(event):
    context = gcp_alert_context(event)

    # Extract the service name from the resource path for better context
    resource = event.deep_get("protoPayload", "resourceName", default="")
    if resource:
        context["service_name"] = resource.split("/")[-1] if "/" in resource else resource

    # Get bindings and role information
    bindings = event.deep_get("protoPayload", "response", "bindings", default=[])

    # Collect all roles and members
    all_roles = []
    all_members = []
    role_to_members = {}

    for binding in bindings:
        role = binding.get("role")
        members = binding.get("members", [])

        if role:
            all_roles.append(role)

        if members:
            all_members.extend(members)

            # Create mapping of role to members
            if role:
                role_to_members[role] = members

    # Store all collected information in the context
    context["assigned_roles"] = all_roles
    context["members_granted"] = all_members
    context["role_to_members_mapping"] = role_to_members

    return context

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.