{
  "version": "Notebook/1.0",
  "items": [
    {
      "type": 1,
      "content": {
        "json": "# Google Cloud Platform Audit Logs\n\nUnified monitoring and compliance insights for Google Cloud audit data ingested into Microsoft Sentinel."
      },
      "name": "text - header"
    },
    {
      "type": 9,
      "content": {
        "version": "KqlParameterItem/1.0",
        "parameters": [
          {
            "id": "3b65f4ad-8f8b-4d98-8a58-ef16e7a31e0f",
            "version": "KqlParameterItem/1.0",
            "name": "TimeRange",
            "label": "Time Range",
            "type": 4,
            "isRequired": true,
            "value": {
              "durationMs": 14860800000,
              "endTime": "2026-06-22T16:07:00.000Z"
            },
            "typeSettings": {
              "selectableValues": [
                {
                  "durationMs": 3600000
                },
                {
                  "durationMs": 86400000
                },
                {
                  "durationMs": 604800000
                },
                {
                  "durationMs": 2592000000
                },
                {
                  "durationMs": 7776000000
                }
              ],
              "allowCustom": true
            }
          }
        ],
        "style": "pills",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces"
      },
      "name": "parameters - global filters"
    },
    {
      "type": 1,
      "content": {
        "json": "## 📊 Summary"
      },
      "name": "text - executive summary"
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "let Base =\n    GCPAuditLogs\n    | where TimeGenerated {TimeRange}\n    | extend Principal = coalesce(iff(isempty(PrincipalEmail), tostring(AuthenticationInfo.principalEmail), PrincipalEmail), \"Unknown\")\n    | extend StatusCode = tostring(Status.code)\n    | extend StatusDetail = coalesce(StatusMessage, tostring(Status.message))\n    | extend ResourceTypeNorm = case(isempty(tostring(GCPResourceType)), case(isempty(tostring(ResourceOriginalState.type)), \"Unknown\", tostring(ResourceOriginalState.type)), tostring(GCPResourceType))\n    | extend LocationNorm = case(isempty(tostring(ResourceLocation)), case(isempty(tostring(ResourceOriginalState.location)), \"Unknown\", tostring(ResourceOriginalState.location)), tostring(ResourceLocation))\n    | extend SeverityNorm = case(isempty(Severity), \"UNKNOWN\", toupper(Severity))\n    | extend ProjectNorm = case(isempty(ProjectId), \"Unknown\", ProjectId)\n    | extend ServiceNorm = case(isempty(ServiceName), \"Unknown\", ServiceName)\n    | extend MethodNorm = case(isempty(MethodName), \"Unknown\", MethodName)\n    | extend CallerIp = coalesce(tostring(RequestMetadata.callerIp), tostring(RequestMetadata.requestAttributes.ip))\n    | extend CallerIp = case(isempty(CallerIp), \"Unknown\", CallerIp)\n    | extend StatusCategory = case(SeverityNorm in (\"CRITICAL\", \"ALERT\", \"ERROR\"), \"High\", SeverityNorm == \"WARNING\", \"Medium\", \"Informational\")\n;\nBase\n| summarize TotalEvents = count()",
        "size": 4,
        "title": "Total Audit Events",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces",
        "visualization": "card",
        "tileSettings": {
          "showBorder": false
        },
        "textSettings": {
          "style": "bignumber"
        }
      },
      "customWidth": "25",
      "name": "card - total events"
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "let Base =\n    GCPAuditLogs\n    | where TimeGenerated {TimeRange}\n    | extend Principal = coalesce(iff(isempty(PrincipalEmail), tostring(AuthenticationInfo.principalEmail), PrincipalEmail), \"Unknown\")\n    | extend StatusCode = tostring(Status.code)\n    | extend StatusDetail = coalesce(StatusMessage, tostring(Status.message))\n    | extend ResourceTypeNorm = case(isempty(tostring(GCPResourceType)), case(isempty(tostring(ResourceOriginalState.type)), \"Unknown\", tostring(ResourceOriginalState.type)), tostring(GCPResourceType))\n    | extend LocationNorm = case(isempty(tostring(ResourceLocation)), case(isempty(tostring(ResourceOriginalState.location)), \"Unknown\", tostring(ResourceOriginalState.location)), tostring(ResourceLocation))\n    | extend SeverityNorm = case(isempty(Severity), \"UNKNOWN\", toupper(Severity))\n    | extend ProjectNorm = case(isempty(ProjectId), \"Unknown\", ProjectId)\n    | extend ServiceNorm = case(isempty(ServiceName), \"Unknown\", ServiceName)\n    | extend MethodNorm = case(isempty(MethodName), \"Unknown\", MethodName)\n    | extend CallerIp = coalesce(tostring(RequestMetadata.callerIp), tostring(RequestMetadata.requestAttributes.ip))\n    | extend CallerIp = case(isempty(CallerIp), \"Unknown\", CallerIp)\n    | extend StatusCategory = case(SeverityNorm in (\"CRITICAL\", \"ALERT\", \"ERROR\"), \"High\", SeverityNorm == \"WARNING\", \"Medium\", \"Informational\")\n;\nBase\n| summarize ['Unique Projects'] = dcount(ProjectNorm)",
        "size": 4,
        "title": "Projects Covered",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces",
        "visualization": "card",
        "tileSettings": {
          "showBorder": false
        },
        "textSettings": {
          "style": "bignumber"
        }
      },
      "customWidth": "25",
      "name": "card - projects"
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "let Base =\n    GCPAuditLogs\n    | where TimeGenerated {TimeRange}\n    | extend Principal = coalesce(iff(isempty(PrincipalEmail), tostring(AuthenticationInfo.principalEmail), PrincipalEmail), \"Unknown\")\n    | extend StatusCode = tostring(Status.code)\n    | extend StatusDetail = coalesce(StatusMessage, tostring(Status.message))\n    | extend ResourceTypeNorm = case(isempty(tostring(GCPResourceType)), case(isempty(tostring(ResourceOriginalState.type)), \"Unknown\", tostring(ResourceOriginalState.type)), tostring(GCPResourceType))\n    | extend LocationNorm = case(isempty(tostring(ResourceLocation)), case(isempty(tostring(ResourceOriginalState.location)), \"Unknown\", tostring(ResourceOriginalState.location)), tostring(ResourceLocation))\n    | extend SeverityNorm = case(isempty(Severity), \"UNKNOWN\", toupper(Severity))\n    | extend ProjectNorm = case(isempty(ProjectId), \"Unknown\", ProjectId)\n    | extend ServiceNorm = case(isempty(ServiceName), \"Unknown\", ServiceName)\n    | extend MethodNorm = case(isempty(MethodName), \"Unknown\", MethodName)\n    | extend CallerIp = coalesce(tostring(RequestMetadata.callerIp), tostring(RequestMetadata.requestAttributes.ip))\n    | extend CallerIp = case(isempty(CallerIp), \"Unknown\", CallerIp)\n    | extend StatusCategory = case(SeverityNorm in (\"CRITICAL\", \"ALERT\", \"ERROR\"), \"High\", SeverityNorm == \"WARNING\", \"Medium\", \"Informational\")\n;\nBase\n| summarize ['Distinct Principals'] = dcount(Principal)",
        "size": 4,
        "title": "Unique Principals",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces",
        "visualization": "card",
        "tileSettings": {
          "showBorder": false
        },
        "textSettings": {
          "style": "bignumber"
        }
      },
      "customWidth": "25",
      "name": "card - principals"
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "let Base =\n    GCPAuditLogs\n    | where TimeGenerated {TimeRange}\n    | extend Principal = coalesce(iff(isempty(PrincipalEmail), tostring(AuthenticationInfo.principalEmail), PrincipalEmail), \"Unknown\")\n    | extend StatusCode = tostring(Status.code)\n    | extend StatusDetail = coalesce(StatusMessage, tostring(Status.message))\n    | extend ResourceTypeNorm = case(isempty(tostring(GCPResourceType)), case(isempty(tostring(ResourceOriginalState.type)), \"Unknown\", tostring(ResourceOriginalState.type)), tostring(GCPResourceType))\n    | extend LocationNorm = case(isempty(tostring(ResourceLocation)), case(isempty(tostring(ResourceOriginalState.location)), \"Unknown\", tostring(ResourceOriginalState.location)), tostring(ResourceLocation))\n    | extend SeverityNorm = case(isempty(Severity), \"UNKNOWN\", toupper(Severity))\n    | extend ProjectNorm = case(isempty(ProjectId), \"Unknown\", ProjectId)\n    | extend ServiceNorm = case(isempty(ServiceName), \"Unknown\", ServiceName)\n    | extend MethodNorm = case(isempty(MethodName), \"Unknown\", MethodName)\n    | extend CallerIp = coalesce(tostring(RequestMetadata.callerIp), tostring(RequestMetadata.requestAttributes.ip))\n    | extend CallerIp = case(isempty(CallerIp), \"Unknown\", CallerIp)\n    | extend StatusCategory = case(SeverityNorm in (\"CRITICAL\", \"ALERT\", \"ERROR\"), \"High\", SeverityNorm == \"WARNING\", \"Medium\", \"Informational\")\n;\nBase\n| summarize ['High Severity Events'] = countif(SeverityNorm in (\"CRITICAL\", \"ALERT\", \"ERROR\"))",
        "size": 4,
        "title": "High Severity Alerts",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces",
        "visualization": "card",
        "tileSettings": {
          "showBorder": false
        },
        "textSettings": {
          "style": "bignumber"
        }
      },
      "customWidth": "25",
      "name": "card - high severity"
    },
    {
      "type": 1,
      "content": {
        "json": "## 🔍 Activity Patterns"
      },
      "name": "text - activity header"
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "let Base =\n    GCPAuditLogs\n    | where TimeGenerated {TimeRange}\n    | extend Principal = coalesce(iff(isempty(PrincipalEmail), tostring(AuthenticationInfo.principalEmail), PrincipalEmail), \"Unknown\")\n    | extend StatusCode = tostring(Status.code)\n    | extend StatusDetail = coalesce(StatusMessage, tostring(Status.message))\n    | extend ResourceTypeNorm = case(isempty(tostring(GCPResourceType)), case(isempty(tostring(ResourceOriginalState.type)), \"Unknown\", tostring(ResourceOriginalState.type)), tostring(GCPResourceType))\n    | extend LocationNorm = case(isempty(tostring(ResourceLocation)), case(isempty(tostring(ResourceOriginalState.location)), \"Unknown\", tostring(ResourceOriginalState.location)), tostring(ResourceLocation))\n    | extend SeverityNorm = case(isempty(Severity), \"UNKNOWN\", toupper(Severity))\n    | extend ProjectNorm = case(isempty(ProjectId), \"Unknown\", ProjectId)\n    | extend ServiceNorm = case(isempty(ServiceName), \"Unknown\", ServiceName)\n    | extend MethodNorm = case(isempty(MethodName), \"Unknown\", MethodName)\n    | extend CallerIp = coalesce(tostring(RequestMetadata.callerIp), tostring(RequestMetadata.requestAttributes.ip))\n    | extend CallerIp = case(isempty(CallerIp), \"Unknown\", CallerIp)\n    | extend StatusCategory = case(SeverityNorm in (\"CRITICAL\", \"ALERT\", \"ERROR\"), \"High\", SeverityNorm == \"WARNING\", \"Medium\", \"Informational\")\n;\nBase\n| summarize Events = count() by bin(TimeGenerated, 1h), SeverityNorm\n| order by TimeGenerated asc",
        "size": 0,
        "title": "Audit Events Over Time by Severity",
        "timeContextFromParameter": "TimeRange",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces",
        "visualization": "areachart",
        "chartSettings": {
          "seriesLabelSettings": [],
          "yAxisSettings": {
            "numberFormat": {
              "unit": 27
            }
          }
        }
      },
      "name": "chart - timeline"
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "let Base =\n    GCPAuditLogs\n    | where TimeGenerated {TimeRange}\n    | extend Principal = coalesce(iff(isempty(PrincipalEmail), tostring(AuthenticationInfo.principalEmail), PrincipalEmail), \"Unknown\")\n    | extend StatusCode = tostring(Status.code)\n    | extend StatusDetail = coalesce(StatusMessage, tostring(Status.message))\n    | extend ResourceTypeNorm = case(isempty(tostring(GCPResourceType)), case(isempty(tostring(ResourceOriginalState.type)), \"Unknown\", tostring(ResourceOriginalState.type)), tostring(GCPResourceType))\n    | extend LocationNorm = case(isempty(tostring(ResourceLocation)), case(isempty(tostring(ResourceOriginalState.location)), \"Unknown\", tostring(ResourceOriginalState.location)), tostring(ResourceLocation))\n    | extend SeverityNorm = case(isempty(Severity), \"UNKNOWN\", toupper(Severity))\n    | extend ProjectNorm = case(isempty(ProjectId), \"Unknown\", ProjectId)\n    | extend ServiceNorm = case(isempty(ServiceName), \"Unknown\", ServiceName)\n    | extend MethodNorm = case(isempty(MethodName), \"Unknown\", MethodName)\n    | extend CallerIp = coalesce(tostring(RequestMetadata.callerIp), tostring(RequestMetadata.requestAttributes.ip))\n    | extend CallerIp = case(isempty(CallerIp), \"Unknown\", CallerIp)\n    | extend StatusCategory = case(SeverityNorm in (\"CRITICAL\", \"ALERT\", \"ERROR\"), \"High\", SeverityNorm == \"WARNING\", \"Medium\", \"Informational\")\n;\nBase\n| summarize Events = count(), ['Projects Impacted'] = dcount(ProjectNorm), ['Distinct Principals'] = dcount(Principal) by ServiceNorm\n| top 15 by Events desc\n| order by Events desc",
        "size": 0,
        "title": "Top GCP Services & Methods",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces",
        "visualization": "categoricalbar",
        "chartSettings": {
          "seriesLabelSettings": [],
          "xColumn": "ServiceNorm",
          "yColumn": "Events",
          "seriesColumn": "MethodNorm",
          "grouping": "grouped"
        }
      },
      "name": "chart - services"
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "let Base =\n    GCPAuditLogs\n    | where TimeGenerated {TimeRange}\n    | extend Principal = coalesce(iff(isempty(PrincipalEmail), tostring(AuthenticationInfo.principalEmail), PrincipalEmail), \"Unknown\")\n    | extend StatusCode = tostring(Status.code)\n    | extend StatusDetail = coalesce(StatusMessage, tostring(Status.message))\n    | extend ResourceTypeNorm = case(isempty(tostring(GCPResourceType)), case(isempty(tostring(ResourceOriginalState.type)), \"Unknown\", tostring(ResourceOriginalState.type)), tostring(GCPResourceType))\n    | extend LocationNorm = case(isempty(tostring(ResourceLocation)), case(isempty(tostring(ResourceOriginalState.location)), \"Unknown\", tostring(ResourceOriginalState.location)), tostring(ResourceLocation))\n    | extend SeverityNorm = case(isempty(Severity), \"UNKNOWN\", toupper(Severity))\n    | extend ProjectNorm = case(isempty(ProjectId), \"Unknown\", ProjectId)\n    | extend ServiceNorm = case(isempty(ServiceName), \"Unknown\", ServiceName)\n    | extend MethodNorm = case(isempty(MethodName), \"Unknown\", MethodName)\n    | extend CallerIp = coalesce(tostring(RequestMetadata.callerIp), tostring(RequestMetadata.requestAttributes.ip))\n    | extend CallerIp = case(isempty(CallerIp), \"Unknown\", CallerIp)\n    | extend StatusCategory = case(SeverityNorm in (\"CRITICAL\", \"ALERT\", \"ERROR\"), \"High\", SeverityNorm == \"WARNING\", \"Medium\", \"Informational\")\n;\nBase\n| summarize Events = count(), ['High Severity'] = countif(SeverityNorm in (\"CRITICAL\", \"ALERT\", \"ERROR\")) by ProjectNorm, ResourceTypeNorm\n| top 15 by Events desc\n| order by Events desc",
        "size": 0,
        "title": "Projects & Resource Types with Highest Activity",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces",
        "visualization": "categoricalbar",
        "chartSettings": {
          "seriesLabelSettings": [],
          "xColumn": "ProjectNorm",
          "yColumn": "Events",
          "seriesColumn": "ResourceTypeNorm",
          "grouping": "grouped"
        }
      },
      "name": "chart - projects"
    },
    {
      "type": 1,
      "content": {
        "json": "## 🛡️ Sensitive Operations & Errors"
      },
      "name": "text - sensitive ops"
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "let Base =\n    GCPAuditLogs\n    | where TimeGenerated {TimeRange}\n    | extend Principal = coalesce(iff(isempty(PrincipalEmail), tostring(AuthenticationInfo.principalEmail), PrincipalEmail), \"Unknown\")\n    | extend StatusCode = tostring(Status.code)\n    | extend StatusDetail = coalesce(StatusMessage, tostring(Status.message))\n    | extend ResourceTypeNorm = case(isempty(tostring(GCPResourceType)), case(isempty(tostring(ResourceOriginalState.type)), \"Unknown\", tostring(ResourceOriginalState.type)), tostring(GCPResourceType))\n    | extend LocationNorm = case(isempty(tostring(ResourceLocation)), case(isempty(tostring(ResourceOriginalState.location)), \"Unknown\", tostring(ResourceOriginalState.location)), tostring(ResourceLocation))\n    | extend SeverityNorm = case(isempty(Severity), \"UNKNOWN\", toupper(Severity))\n    | extend ProjectNorm = case(isempty(ProjectId), \"Unknown\", ProjectId)\n    | extend ServiceNorm = case(isempty(ServiceName), \"Unknown\", ServiceName)\n    | extend MethodNorm = case(isempty(MethodName), \"Unknown\", MethodName)\n    | extend CallerIp = coalesce(tostring(RequestMetadata.callerIp), tostring(RequestMetadata.requestAttributes.ip))\n    | extend CallerIp = case(isempty(CallerIp), \"Unknown\", CallerIp)\n    | extend StatusCategory = case(SeverityNorm in (\"CRITICAL\", \"ALERT\", \"ERROR\"), \"High\", SeverityNorm == \"WARNING\", \"Medium\", \"Informational\")\n;\nBase\n| where MethodNorm has_any (\"SETIAMPOLICY\", \"SETPOLICY\", \"DELETE\", \"DISABLE\", \"MAKE\", \"REMOVE\", \"UPDATE\")\n| extend ['Risk Category'] = case(\n    MethodNorm has \"SETIAM\", \"IAM Policy Change\",\n    MethodNorm has \"DELETE\" or MethodNorm has \"REMOVE\", \"Deletion\",\n    MethodNorm has \"DISABLE\", \"Logging Disabled\",\n    MethodNorm has \"MAKE\" and MethodNorm has \"PUBLIC\", \"Public Exposure\",\n    MethodNorm has \"UPDATE\", \"Configuration Update\",\n    \"Administrative Action\")\n| project ['Timestamp'] = TimeGenerated, ['Project'] = ProjectNorm, ['Service'] = ServiceNorm, ['Method'] = MethodNorm, ['Risk Category'], ['Principal'] = Principal, ['Severity'] = SeverityNorm, ['Status Code'] = StatusCode, ['Status Detail'] = StatusDetail, ['Caller IP'] = CallerIp, ['Resource Type'] = ResourceTypeNorm, ['Source File'] = SourceFile\n| order by ['Timestamp'] desc\n| take 100",
        "size": 0,
        "title": "Privileged & Configuration-Changing Operations (Top 100)",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces",
        "visualization": "table",
        "gridSettings": {
          "rowLimit": 100,
          "filter": true
        }
      },
      "name": "table - sensitive operations"
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "let Base =\n    GCPAuditLogs\n    | where TimeGenerated {TimeRange}\n    | extend Principal = coalesce(iff(isempty(PrincipalEmail), tostring(AuthenticationInfo.principalEmail), PrincipalEmail), \"Unknown\")\n    | extend StatusCode = tostring(Status.code)\n    | extend StatusDetail = coalesce(StatusMessage, tostring(Status.message))\n    | extend ResourceTypeNorm = case(isempty(tostring(GCPResourceType)), case(isempty(tostring(ResourceOriginalState.type)), \"Unknown\", tostring(ResourceOriginalState.type)), tostring(GCPResourceType))\n    | extend LocationNorm = case(isempty(tostring(ResourceLocation)), case(isempty(tostring(ResourceOriginalState.location)), \"Unknown\", tostring(ResourceOriginalState.location)), tostring(ResourceLocation))\n    | extend SeverityNorm = case(isempty(Severity), \"UNKNOWN\", toupper(Severity))\n    | extend ProjectNorm = case(isempty(ProjectId), \"Unknown\", ProjectId)\n    | extend ServiceNorm = case(isempty(ServiceName), \"Unknown\", ServiceName)\n    | extend MethodNorm = case(isempty(MethodName), \"Unknown\", MethodName)\n    | extend CallerIp = coalesce(tostring(RequestMetadata.callerIp), tostring(RequestMetadata.requestAttributes.ip))\n    | extend CallerIp = case(isempty(CallerIp), \"Unknown\", CallerIp)\n    | extend StatusCategory = case(SeverityNorm in (\"CRITICAL\", \"ALERT\", \"ERROR\"), \"High\", SeverityNorm == \"WARNING\", \"Medium\", \"Informational\")\n;\nBase\n| where StatusCode !in (\"\", \"0\", \"200\") or SeverityNorm in (\"CRITICAL\", \"ALERT\", \"ERROR\")\n| project ['Timestamp'] = TimeGenerated, ['Project'] = ProjectNorm, ['Service'] = ServiceNorm, ['Method'] = MethodNorm, ['Principal'] = Principal, ['Severity'] = SeverityNorm, ['Status Code'] = StatusCode, ['Status Detail'] = StatusDetail, ['Caller IP'] = CallerIp, ['Resource Type'] = ResourceTypeNorm, ['Source File'] = SourceFile\n| order by ['Timestamp'] desc\n| take 100",
        "size": 0,
        "title": "Failed or High-Severity Operations",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces",
        "visualization": "table",
        "gridSettings": {
          "rowLimit": 100,
          "filter": true
        }
      },
      "name": "table - failed operations"
    },
    {
      "type": 1,
      "content": {
        "json": "## 🌐 Network & Geography"
      },
      "name": "text - network"
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "let Base =\n    GCPAuditLogs\n    | where TimeGenerated {TimeRange}\n    | extend Principal = coalesce(iff(isempty(PrincipalEmail), tostring(AuthenticationInfo.principalEmail), PrincipalEmail), \"Unknown\")\n    | extend StatusCode = tostring(Status.code)\n    | extend StatusDetail = coalesce(StatusMessage, tostring(Status.message))\n    | extend ResourceTypeNorm = case(isempty(tostring(GCPResourceType)), case(isempty(tostring(ResourceOriginalState.type)), \"Unknown\", tostring(ResourceOriginalState.type)), tostring(GCPResourceType))\n    | extend LocationNorm = case(isempty(tostring(ResourceLocation)), case(isempty(tostring(ResourceOriginalState.location)), \"Unknown\", tostring(ResourceOriginalState.location)), tostring(ResourceLocation))\n    | extend SeverityNorm = case(isempty(Severity), \"UNKNOWN\", toupper(Severity))\n    | extend ProjectNorm = case(isempty(ProjectId), \"Unknown\", ProjectId)\n    | extend ServiceNorm = case(isempty(ServiceName), \"Unknown\", ServiceName)\n    | extend MethodNorm = case(isempty(MethodName), \"Unknown\", MethodName)\n    | extend CallerIp = coalesce(tostring(RequestMetadata.callerIp), tostring(RequestMetadata.requestAttributes.ip))\n    | extend CallerIp = case(isempty(CallerIp), \"Unknown\", CallerIp)\n    | extend StatusCategory = case(SeverityNorm in (\"CRITICAL\", \"ALERT\", \"ERROR\"), \"High\", SeverityNorm == \"WARNING\", \"Medium\", \"Informational\")\n;\nBase\n| where LocationNorm != \"Unknown\"\n| summarize Events = count(), ['Unique Principals'] = dcount(Principal) by LocationNorm\n| top 15 by Events desc",
        "size": 0,
        "title": "Top GCP Regions / Locations",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces",
        "visualization": "columnchart",
        "chartSettings": {
          "seriesLabelSettings": [],
          "yAxisSettings": {
            "numberFormat": {
              "unit": 27
            }
          }
        }
      },
      "name": "chart - locations"
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "let Base =\n    GCPAuditLogs\n    | where TimeGenerated {TimeRange}\n    | extend Principal = coalesce(iff(isempty(PrincipalEmail), tostring(AuthenticationInfo.principalEmail), PrincipalEmail), \"Unknown\")\n    | extend StatusCode = tostring(Status.code)\n    | extend StatusDetail = coalesce(StatusMessage, tostring(Status.message))\n    | extend ResourceTypeNorm = case(isempty(tostring(GCPResourceType)), case(isempty(tostring(ResourceOriginalState.type)), \"Unknown\", tostring(ResourceOriginalState.type)), tostring(GCPResourceType))\n    | extend LocationNorm = case(isempty(tostring(ResourceLocation)), case(isempty(tostring(ResourceOriginalState.location)), \"Unknown\", tostring(ResourceOriginalState.location)), tostring(ResourceLocation))\n    | extend SeverityNorm = case(isempty(Severity), \"UNKNOWN\", toupper(Severity))\n    | extend ProjectNorm = case(isempty(ProjectId), \"Unknown\", ProjectId)\n    | extend ServiceNorm = case(isempty(ServiceName), \"Unknown\", ServiceName)\n    | extend MethodNorm = case(isempty(MethodName), \"Unknown\", MethodName)\n      | extend CallerIp = coalesce(\n        tostring(RequestMetadata.callerIp),\n        tostring(RequestMetadata.requestAttributes.ip)\n    )\n    | extend CallerIp = case(isempty(CallerIp), \"Unknown\", CallerIp)\n    | extend StatusCategory = case(SeverityNorm in (\"CRITICAL\", \"ALERT\", \"ERROR\"), \"High\", SeverityNorm == \"WARNING\", \"Medium\", \"Informational\")\n;\nBase\n| where CallerIp != \"Unknown\"\n| summarize Events = count(), ['Distinct Services'] = dcount(ServiceNorm), ['Distinct Projects'] = dcount(ProjectNorm) by CallerIp\n| top 20 by Events desc",
        "size": 0,
        "title": "Top Caller IPs",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces",
        "visualization": "table",
        "gridSettings": {
          "rowLimit": 20,
          "filter": true
        }
      },
      "name": "table - caller ip"
    },
    {
      "type": 1,
      "content": {
        "json": "## 📄 Investigation Detail"
      },
      "name": "text - details"
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "let Base =\n    GCPAuditLogs\n    | where TimeGenerated {TimeRange}\n    | extend Principal = coalesce(iff(isempty(PrincipalEmail), tostring(AuthenticationInfo.principalEmail), PrincipalEmail), \"Unknown\")\n    | extend StatusCode = tostring(Status.code)\n    | extend StatusDetail = coalesce(StatusMessage, tostring(Status.message))\n    | extend ResourceTypeNorm = case(isempty(tostring(GCPResourceType)), case(isempty(tostring(ResourceOriginalState.type)), \"Unknown\", tostring(ResourceOriginalState.type)), tostring(GCPResourceType))\n    | extend LocationNorm = case(isempty(tostring(ResourceLocation)), case(isempty(tostring(ResourceOriginalState.location)), \"Unknown\", tostring(ResourceOriginalState.location)), tostring(ResourceLocation))\n    | extend SeverityNorm = case(isempty(Severity), \"UNKNOWN\", toupper(Severity))\n    | extend ProjectNorm = case(isempty(ProjectId), \"Unknown\", ProjectId)\n    | extend ServiceNorm = case(isempty(ServiceName), \"Unknown\", ServiceName)\n    | extend MethodNorm = case(isempty(MethodName), \"Unknown\", MethodName)\n    | extend CallerIp = coalesce(tostring(RequestMetadata.callerIp), tostring(RequestMetadata.requestAttributes.ip))\n    | extend CallerIp = case(isempty(CallerIp), \"Unknown\", CallerIp)\n    | extend StatusCategory = case(SeverityNorm in (\"CRITICAL\", \"ALERT\", \"ERROR\"), \"High\", SeverityNorm == \"WARNING\", \"Medium\", \"Informational\")\n;\nBase\n| project ['Timestamp'] = TimeGenerated, ['Project'] = ProjectNorm, ['Service'] = ServiceNorm, ['Method'] = MethodNorm, ['Principal'] = Principal, ['Severity'] = SeverityNorm, ['Status Code'] = StatusCode, ['Status Detail'] = StatusDetail, ['Resource Type'] = ResourceTypeNorm, ['Location'] = LocationNorm, ['Log Name'] = LogName, ['Source File'] = SourceFile, AuthenticationInfo, AuthorizationInfo, RequestMetadata, Request, Response, ServiceData\n| order by ['Timestamp'] desc\n| take 200",
        "size": 0,
        "title": "Latest 200 Audit Records (Pivot-Friendly)",
        "queryType": 0,
        "resourceType": "microsoft.operationalinsights/workspaces",
        "visualization": "table",
        "gridSettings": {
          "rowLimit": 200,
          "filter": true
        },
        "sortBy": []
      },
      "name": "table - raw logs"
    }
  ],  
  "fromTemplateId": "sentinel-GCPAuditLogs",
  "$schema": "https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json"
}
